|
|
|
@ -33,8 +33,6 @@
|
|
|
|
|
#include "VideoCommon/ImageWrite.h"
|
|
|
|
|
#include "VideoCommon/PixelShaderManager.h"
|
|
|
|
|
#include "VideoCommon/Statistics.h"
|
|
|
|
|
#include "VideoCommon/UberShaderPixel.h"
|
|
|
|
|
#include "VideoCommon/UberShaderVertex.h"
|
|
|
|
|
#include "VideoCommon/VertexLoaderManager.h"
|
|
|
|
|
#include "VideoCommon/VertexShaderManager.h"
|
|
|
|
|
#include "VideoCommon/VideoCommon.h"
|
|
|
|
@ -43,8 +41,6 @@ namespace OGL
|
|
|
|
|
{
|
|
|
|
|
static constexpr u32 UBO_LENGTH = 32 * 1024 * 1024;
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<ProgramShaderCache::SharedContextAsyncShaderCompiler>
|
|
|
|
|
ProgramShaderCache::s_async_compiler;
|
|
|
|
|
u32 ProgramShaderCache::s_ubo_buffer_size;
|
|
|
|
|
s32 ProgramShaderCache::s_ubo_align;
|
|
|
|
|
GLuint ProgramShaderCache::s_attributeless_VBO = 0;
|
|
|
|
@ -54,17 +50,9 @@ GLuint ProgramShaderCache::s_last_VAO = 0;
|
|
|
|
|
static std::unique_ptr<StreamBuffer> s_buffer;
|
|
|
|
|
static int num_failures = 0;
|
|
|
|
|
|
|
|
|
|
static LinearDiskCache<SHADERUID, u8> s_program_disk_cache;
|
|
|
|
|
static LinearDiskCache<UBERSHADERUID, u8> s_uber_program_disk_cache;
|
|
|
|
|
static GLuint CurrentProgram = 0;
|
|
|
|
|
ProgramShaderCache::PCache ProgramShaderCache::pshaders;
|
|
|
|
|
ProgramShaderCache::UberPCache ProgramShaderCache::ubershaders;
|
|
|
|
|
ProgramShaderCache::PipelineProgramMap ProgramShaderCache::pipelineprograms;
|
|
|
|
|
std::mutex ProgramShaderCache::pipelineprogramlock;
|
|
|
|
|
ProgramShaderCache::PCacheEntry* ProgramShaderCache::last_entry;
|
|
|
|
|
ProgramShaderCache::PCacheEntry* ProgramShaderCache::last_uber_entry;
|
|
|
|
|
SHADERUID ProgramShaderCache::last_uid;
|
|
|
|
|
UBERSHADERUID ProgramShaderCache::last_uber_uid;
|
|
|
|
|
static std::string s_glsl_header = "";
|
|
|
|
|
|
|
|
|
|
static std::string GetGLSLVersionString()
|
|
|
|
@ -270,143 +258,6 @@ void ProgramShaderCache::UploadConstants()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SHADER* ProgramShaderCache::SetShader(PrimitiveType primitive_type,
|
|
|
|
|
const GLVertexFormat* vertex_format)
|
|
|
|
|
{
|
|
|
|
|
if (g_ActiveConfig.bDisableSpecializedShaders)
|
|
|
|
|
return SetUberShader(primitive_type, vertex_format);
|
|
|
|
|
|
|
|
|
|
SHADERUID uid;
|
|
|
|
|
std::memset(&uid, 0, sizeof(uid));
|
|
|
|
|
uid.puid = GetPixelShaderUid();
|
|
|
|
|
uid.vuid = GetVertexShaderUid();
|
|
|
|
|
uid.guid = GetGeometryShaderUid(primitive_type);
|
|
|
|
|
ClearUnusedPixelShaderUidBits(APIType::OpenGL, &uid.puid);
|
|
|
|
|
|
|
|
|
|
// Check if the shader is already set
|
|
|
|
|
if (last_entry && uid == last_uid)
|
|
|
|
|
{
|
|
|
|
|
last_entry->shader.Bind();
|
|
|
|
|
BindVertexFormat(vertex_format);
|
|
|
|
|
return &last_entry->shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if shader is already in cache
|
|
|
|
|
auto iter = pshaders.find(uid);
|
|
|
|
|
if (iter != pshaders.end())
|
|
|
|
|
{
|
|
|
|
|
PCacheEntry* entry = &iter->second;
|
|
|
|
|
if (entry->pending)
|
|
|
|
|
return SetUberShader(primitive_type, vertex_format);
|
|
|
|
|
|
|
|
|
|
last_uid = uid;
|
|
|
|
|
last_entry = entry;
|
|
|
|
|
BindVertexFormat(vertex_format);
|
|
|
|
|
last_entry->shader.Bind();
|
|
|
|
|
return &last_entry->shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compile the new shader program.
|
|
|
|
|
PCacheEntry& newentry = pshaders[uid];
|
|
|
|
|
newentry.in_cache = false;
|
|
|
|
|
newentry.pending = false;
|
|
|
|
|
|
|
|
|
|
// Can we background compile this shader? Requires background shader compiling to be enabled,
|
|
|
|
|
// and all ubershaders to have been successfully compiled.
|
|
|
|
|
if (g_ActiveConfig.CanBackgroundCompileShaders() && !ubershaders.empty() && s_async_compiler)
|
|
|
|
|
{
|
|
|
|
|
newentry.pending = true;
|
|
|
|
|
s_async_compiler->QueueWorkItem(s_async_compiler->CreateWorkItem<ShaderCompileWorkItem>(uid));
|
|
|
|
|
return SetUberShader(primitive_type, vertex_format);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Synchronous shader compiling.
|
|
|
|
|
ShaderHostConfig host_config = ShaderHostConfig::GetCurrent();
|
|
|
|
|
ShaderCode vcode = GenerateVertexShaderCode(APIType::OpenGL, host_config, uid.vuid.GetUidData());
|
|
|
|
|
ShaderCode pcode = GeneratePixelShaderCode(APIType::OpenGL, host_config, uid.puid.GetUidData());
|
|
|
|
|
ShaderCode gcode;
|
|
|
|
|
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders &&
|
|
|
|
|
!uid.guid.GetUidData()->IsPassthrough())
|
|
|
|
|
gcode = GenerateGeometryShaderCode(APIType::OpenGL, host_config, uid.guid.GetUidData());
|
|
|
|
|
|
|
|
|
|
if (!CompileShader(newentry.shader, vcode.GetBuffer(), pcode.GetBuffer(), gcode.GetBuffer()))
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
|
|
INCSTAT(stats.numPixelShadersCreated);
|
|
|
|
|
SETSTAT(stats.numPixelShadersAlive, pshaders.size());
|
|
|
|
|
|
|
|
|
|
last_uid = uid;
|
|
|
|
|
last_entry = &newentry;
|
|
|
|
|
BindVertexFormat(vertex_format);
|
|
|
|
|
last_entry->shader.Bind();
|
|
|
|
|
return &last_entry->shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SHADER* ProgramShaderCache::SetUberShader(PrimitiveType primitive_type,
|
|
|
|
|
const GLVertexFormat* vertex_format)
|
|
|
|
|
{
|
|
|
|
|
UBERSHADERUID uid;
|
|
|
|
|
std::memset(&uid, 0, sizeof(uid));
|
|
|
|
|
uid.puid = UberShader::GetPixelShaderUid();
|
|
|
|
|
uid.vuid = UberShader::GetVertexShaderUid();
|
|
|
|
|
uid.guid = GetGeometryShaderUid(primitive_type);
|
|
|
|
|
UberShader::ClearUnusedPixelShaderUidBits(APIType::OpenGL, &uid.puid);
|
|
|
|
|
|
|
|
|
|
// We need to use the ubershader vertex format with all attributes enabled.
|
|
|
|
|
// Otherwise, the NV driver can generate variants for the vertex shaders.
|
|
|
|
|
const GLVertexFormat* uber_vertex_format = static_cast<const GLVertexFormat*>(
|
|
|
|
|
VertexLoaderManager::GetUberVertexFormat(vertex_format->GetVertexDeclaration()));
|
|
|
|
|
|
|
|
|
|
// Check if the shader is already set
|
|
|
|
|
if (last_uber_entry && last_uber_uid == uid)
|
|
|
|
|
{
|
|
|
|
|
BindVertexFormat(uber_vertex_format);
|
|
|
|
|
last_uber_entry->shader.Bind();
|
|
|
|
|
return &last_uber_entry->shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if shader is already in cache
|
|
|
|
|
auto iter = ubershaders.find(uid);
|
|
|
|
|
if (iter != ubershaders.end())
|
|
|
|
|
{
|
|
|
|
|
PCacheEntry* entry = &iter->second;
|
|
|
|
|
last_uber_uid = uid;
|
|
|
|
|
last_uber_entry = entry;
|
|
|
|
|
BindVertexFormat(uber_vertex_format);
|
|
|
|
|
last_uber_entry->shader.Bind();
|
|
|
|
|
return &last_uber_entry->shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make an entry in the table
|
|
|
|
|
PCacheEntry& newentry = ubershaders[uid];
|
|
|
|
|
newentry.in_cache = false;
|
|
|
|
|
newentry.pending = false;
|
|
|
|
|
|
|
|
|
|
ShaderHostConfig host_config = ShaderHostConfig::GetCurrent();
|
|
|
|
|
ShaderCode vcode =
|
|
|
|
|
UberShader::GenVertexShader(APIType::OpenGL, host_config, uid.vuid.GetUidData());
|
|
|
|
|
ShaderCode pcode =
|
|
|
|
|
UberShader::GenPixelShader(APIType::OpenGL, host_config, uid.puid.GetUidData());
|
|
|
|
|
ShaderCode gcode;
|
|
|
|
|
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders &&
|
|
|
|
|
!uid.guid.GetUidData()->IsPassthrough())
|
|
|
|
|
{
|
|
|
|
|
gcode = GenerateGeometryShaderCode(APIType::OpenGL, host_config, uid.guid.GetUidData());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!CompileShader(newentry.shader, vcode.GetBuffer(), pcode.GetBuffer(), gcode.GetBuffer()))
|
|
|
|
|
{
|
|
|
|
|
GFX_DEBUGGER_PAUSE_AT(NEXT_ERROR, true);
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
last_uber_uid = uid;
|
|
|
|
|
last_uber_entry = &newentry;
|
|
|
|
|
BindVertexFormat(uber_vertex_format);
|
|
|
|
|
last_uber_entry->shader.Bind();
|
|
|
|
|
return &last_uber_entry->shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProgramShaderCache::CompileShader(SHADER& shader, const std::string& vcode,
|
|
|
|
|
const std::string& pcode, const std::string& gcode)
|
|
|
|
|
{
|
|
|
|
@ -620,11 +471,6 @@ bool ProgramShaderCache::CheckProgramLinkResult(GLuint id, const std::string& vc
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ProgramShaderCache::PCacheEntry ProgramShaderCache::GetShaderProgram()
|
|
|
|
|
{
|
|
|
|
|
return *last_entry;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProgramShaderCache::Init()
|
|
|
|
|
{
|
|
|
|
|
// We have to get the UBO alignment here because
|
|
|
|
@ -642,93 +488,14 @@ void ProgramShaderCache::Init()
|
|
|
|
|
// Then once more to get bytes
|
|
|
|
|
s_buffer = StreamBuffer::Create(GL_UNIFORM_BUFFER, UBO_LENGTH);
|
|
|
|
|
|
|
|
|
|
// The GPU shader code appears to be context-specific on Mesa/i965.
|
|
|
|
|
// This means that if we compiled the ubershaders asynchronously, they will be recompiled
|
|
|
|
|
// on the main thread the first time they are used, causing stutter. Nouveau has been
|
|
|
|
|
// reported to crash if draw calls are invoked on the shared context threads. For now,
|
|
|
|
|
// disable asynchronous compilation on Mesa.
|
|
|
|
|
if (!DriverDetails::HasBug(DriverDetails::BUG_SHARED_CONTEXT_SHADER_COMPILATION))
|
|
|
|
|
s_async_compiler = std::make_unique<SharedContextAsyncShaderCompiler>();
|
|
|
|
|
|
|
|
|
|
// Read our shader cache, only if supported and enabled
|
|
|
|
|
if (g_ogl_config.bSupportsGLSLCache && g_ActiveConfig.bShaderCache)
|
|
|
|
|
LoadProgramBinaries();
|
|
|
|
|
|
|
|
|
|
CreateHeader();
|
|
|
|
|
CreateAttributelessVAO();
|
|
|
|
|
|
|
|
|
|
CurrentProgram = 0;
|
|
|
|
|
last_entry = nullptr;
|
|
|
|
|
last_uber_entry = nullptr;
|
|
|
|
|
|
|
|
|
|
if (g_ActiveConfig.CanPrecompileUberShaders())
|
|
|
|
|
{
|
|
|
|
|
if (s_async_compiler)
|
|
|
|
|
s_async_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderPrecompilerThreads());
|
|
|
|
|
PrecompileUberShaders();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (s_async_compiler)
|
|
|
|
|
{
|
|
|
|
|
// No point using the async compiler without workers.
|
|
|
|
|
s_async_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderCompilerThreads());
|
|
|
|
|
if (!s_async_compiler->HasWorkerThreads())
|
|
|
|
|
s_async_compiler.reset();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProgramShaderCache::RetrieveAsyncShaders()
|
|
|
|
|
{
|
|
|
|
|
if (s_async_compiler)
|
|
|
|
|
s_async_compiler->RetrieveWorkItems();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProgramShaderCache::Reload()
|
|
|
|
|
{
|
|
|
|
|
if (s_async_compiler)
|
|
|
|
|
{
|
|
|
|
|
s_async_compiler->WaitUntilCompletion();
|
|
|
|
|
s_async_compiler->RetrieveWorkItems();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool use_cache = g_ogl_config.bSupportsGLSLCache && g_ActiveConfig.bShaderCache;
|
|
|
|
|
if (use_cache)
|
|
|
|
|
SaveProgramBinaries();
|
|
|
|
|
|
|
|
|
|
s_program_disk_cache.Close();
|
|
|
|
|
s_uber_program_disk_cache.Close();
|
|
|
|
|
DestroyShaders();
|
|
|
|
|
|
|
|
|
|
if (use_cache)
|
|
|
|
|
LoadProgramBinaries();
|
|
|
|
|
|
|
|
|
|
if (g_ActiveConfig.CanPrecompileUberShaders())
|
|
|
|
|
PrecompileUberShaders();
|
|
|
|
|
|
|
|
|
|
CurrentProgram = 0;
|
|
|
|
|
last_entry = nullptr;
|
|
|
|
|
last_uber_entry = nullptr;
|
|
|
|
|
last_uid = {};
|
|
|
|
|
last_uber_uid = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProgramShaderCache::Shutdown()
|
|
|
|
|
{
|
|
|
|
|
if (s_async_compiler)
|
|
|
|
|
{
|
|
|
|
|
s_async_compiler->WaitUntilCompletion();
|
|
|
|
|
s_async_compiler->StopWorkerThreads();
|
|
|
|
|
s_async_compiler->RetrieveWorkItems();
|
|
|
|
|
s_async_compiler.reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// store all shaders in cache on disk
|
|
|
|
|
if (g_ogl_config.bSupportsGLSLCache && g_ActiveConfig.bShaderCache)
|
|
|
|
|
SaveProgramBinaries();
|
|
|
|
|
s_program_disk_cache.Close();
|
|
|
|
|
s_uber_program_disk_cache.Close();
|
|
|
|
|
|
|
|
|
|
DestroyShaders();
|
|
|
|
|
s_buffer.reset();
|
|
|
|
|
|
|
|
|
|
glBindVertexArray(0);
|
|
|
|
@ -781,134 +548,6 @@ void ProgramShaderCache::InvalidateLastProgram()
|
|
|
|
|
CurrentProgram = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GLuint ProgramShaderCache::CreateProgramFromBinary(const u8* value, u32 value_size)
|
|
|
|
|
{
|
|
|
|
|
const u8* binary = value + sizeof(GLenum);
|
|
|
|
|
GLint binary_size = value_size - sizeof(GLenum);
|
|
|
|
|
GLenum prog_format;
|
|
|
|
|
std::memcpy(&prog_format, value, sizeof(GLenum));
|
|
|
|
|
|
|
|
|
|
GLuint progid = glCreateProgram();
|
|
|
|
|
glProgramBinary(progid, prog_format, binary, binary_size);
|
|
|
|
|
|
|
|
|
|
GLint success;
|
|
|
|
|
glGetProgramiv(progid, GL_LINK_STATUS, &success);
|
|
|
|
|
if (!success)
|
|
|
|
|
{
|
|
|
|
|
glDeleteProgram(progid);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return progid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProgramShaderCache::CreateCacheEntryFromBinary(PCacheEntry* entry, const u8* value,
|
|
|
|
|
u32 value_size)
|
|
|
|
|
{
|
|
|
|
|
entry->in_cache = true;
|
|
|
|
|
entry->pending = false;
|
|
|
|
|
entry->shader.glprogid = CreateProgramFromBinary(value, value_size);
|
|
|
|
|
if (entry->shader.glprogid == 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
entry->shader.SetProgramVariables();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProgramShaderCache::LoadProgramBinaries()
|
|
|
|
|
{
|
|
|
|
|
GLint Supported;
|
|
|
|
|
glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &Supported);
|
|
|
|
|
if (!Supported)
|
|
|
|
|
{
|
|
|
|
|
ERROR_LOG(VIDEO, "GL_ARB_get_program_binary is supported, but no binary format is known. So "
|
|
|
|
|
"disable shader cache.");
|
|
|
|
|
g_ogl_config.bSupportsGLSLCache = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Load game-specific shaders.
|
|
|
|
|
std::string cache_filename =
|
|
|
|
|
GetDiskShaderCacheFileName(APIType::OpenGL, "ProgramBinaries", true, true);
|
|
|
|
|
ProgramShaderCacheInserter<SHADERUID> inserter(pshaders);
|
|
|
|
|
s_program_disk_cache.OpenAndRead(cache_filename, inserter);
|
|
|
|
|
|
|
|
|
|
// Load global ubershaders.
|
|
|
|
|
cache_filename =
|
|
|
|
|
GetDiskShaderCacheFileName(APIType::OpenGL, "UberProgramBinaries", false, true);
|
|
|
|
|
ProgramShaderCacheInserter<UBERSHADERUID> uber_inserter(ubershaders);
|
|
|
|
|
s_uber_program_disk_cache.OpenAndRead(cache_filename, uber_inserter);
|
|
|
|
|
}
|
|
|
|
|
SETSTAT(stats.numPixelShadersAlive, pshaders.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool GetProgramBinary(const ProgramShaderCache::PCacheEntry& entry, std::vector<u8>& data)
|
|
|
|
|
{
|
|
|
|
|
// Clear any prior error code
|
|
|
|
|
glGetError();
|
|
|
|
|
|
|
|
|
|
GLint link_status = GL_FALSE, delete_status = GL_TRUE, binary_size = 0;
|
|
|
|
|
glGetProgramiv(entry.shader.glprogid, GL_LINK_STATUS, &link_status);
|
|
|
|
|
glGetProgramiv(entry.shader.glprogid, GL_DELETE_STATUS, &delete_status);
|
|
|
|
|
glGetProgramiv(entry.shader.glprogid, GL_PROGRAM_BINARY_LENGTH, &binary_size);
|
|
|
|
|
if (glGetError() != GL_NO_ERROR || link_status == GL_FALSE || delete_status == GL_TRUE ||
|
|
|
|
|
binary_size == 0)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data.resize(binary_size + sizeof(GLenum));
|
|
|
|
|
|
|
|
|
|
GLsizei length = binary_size;
|
|
|
|
|
GLenum prog_format;
|
|
|
|
|
glGetProgramBinary(entry.shader.glprogid, binary_size, &length, &prog_format,
|
|
|
|
|
&data[sizeof(GLenum)]);
|
|
|
|
|
if (glGetError() != GL_NO_ERROR)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
std::memcpy(&data[0], &prog_format, sizeof(prog_format));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename CacheMapType, typename DiskCacheType>
|
|
|
|
|
static void SaveProgramBinaryMap(CacheMapType& program_map, DiskCacheType& disk_cache)
|
|
|
|
|
{
|
|
|
|
|
std::vector<u8> binary_data;
|
|
|
|
|
for (auto& entry : program_map)
|
|
|
|
|
{
|
|
|
|
|
if (entry.second.in_cache || entry.second.pending)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Entry is now in cache (even if it fails, we don't want to try to save it again).
|
|
|
|
|
entry.second.in_cache = true;
|
|
|
|
|
if (!GetProgramBinary(entry.second, binary_data))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
disk_cache.Append(entry.first, &binary_data[0], static_cast<u32>(binary_data.size()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
disk_cache.Sync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProgramShaderCache::SaveProgramBinaries()
|
|
|
|
|
{
|
|
|
|
|
SaveProgramBinaryMap(pshaders, s_program_disk_cache);
|
|
|
|
|
SaveProgramBinaryMap(ubershaders, s_uber_program_disk_cache);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProgramShaderCache::DestroyShaders()
|
|
|
|
|
{
|
|
|
|
|
glUseProgram(0);
|
|
|
|
|
|
|
|
|
|
for (auto& entry : pshaders)
|
|
|
|
|
entry.second.Destroy();
|
|
|
|
|
pshaders.clear();
|
|
|
|
|
|
|
|
|
|
for (auto& entry : ubershaders)
|
|
|
|
|
entry.second.Destroy();
|
|
|
|
|
ubershaders.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const PipelineProgram* ProgramShaderCache::GetPipelineProgram(const OGLShader* vertex_shader,
|
|
|
|
|
const OGLShader* geometry_shader,
|
|
|
|
|
const OGLShader* pixel_shader)
|
|
|
|
@ -1144,346 +783,4 @@ void ProgramShaderCache::CreateHeader()
|
|
|
|
|
v > GlslEs300 ? "precision highp sampler2DMS;" : "",
|
|
|
|
|
v >= GlslEs310 ? "precision highp image2DArray;" : "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProgramShaderCache::PrecompileUberShaders()
|
|
|
|
|
{
|
|
|
|
|
bool success = true;
|
|
|
|
|
|
|
|
|
|
UberShader::EnumerateVertexShaderUids([&](const UberShader::VertexShaderUid& vuid) {
|
|
|
|
|
UberShader::EnumeratePixelShaderUids([&](const UberShader::PixelShaderUid& puid) {
|
|
|
|
|
// UIDs must have compatible texgens, a mismatching combination will never be queried.
|
|
|
|
|
if (vuid.GetUidData()->num_texgens != puid.GetUidData()->num_texgens)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
EnumerateGeometryShaderUids([&](const GeometryShaderUid& guid) {
|
|
|
|
|
if (guid.GetUidData()->numTexGens != vuid.GetUidData()->num_texgens)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
UBERSHADERUID uid;
|
|
|
|
|
std::memcpy(&uid.vuid, &vuid, sizeof(uid.vuid));
|
|
|
|
|
std::memcpy(&uid.puid, &puid, sizeof(uid.puid));
|
|
|
|
|
std::memcpy(&uid.guid, &guid, sizeof(uid.guid));
|
|
|
|
|
|
|
|
|
|
// The ubershader may already exist if shader caching is enabled.
|
|
|
|
|
if (!success || ubershaders.find(uid) != ubershaders.end())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
PCacheEntry& entry = ubershaders[uid];
|
|
|
|
|
entry.in_cache = false;
|
|
|
|
|
entry.pending = false;
|
|
|
|
|
|
|
|
|
|
// Multi-context path?
|
|
|
|
|
if (s_async_compiler)
|
|
|
|
|
{
|
|
|
|
|
entry.pending = true;
|
|
|
|
|
s_async_compiler->QueueWorkItem(
|
|
|
|
|
s_async_compiler->CreateWorkItem<UberShaderCompileWorkItem>(uid));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ShaderHostConfig host_config = ShaderHostConfig::GetCurrent();
|
|
|
|
|
ShaderCode vcode =
|
|
|
|
|
UberShader::GenVertexShader(APIType::OpenGL, host_config, uid.vuid.GetUidData());
|
|
|
|
|
ShaderCode pcode =
|
|
|
|
|
UberShader::GenPixelShader(APIType::OpenGL, host_config, uid.puid.GetUidData());
|
|
|
|
|
ShaderCode gcode;
|
|
|
|
|
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders &&
|
|
|
|
|
!uid.guid.GetUidData()->IsPassthrough())
|
|
|
|
|
{
|
|
|
|
|
GenerateGeometryShaderCode(APIType::OpenGL, host_config, uid.guid.GetUidData());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Always background compile, even when it's not supported.
|
|
|
|
|
// This way hopefully the driver can still compile the shaders in parallel.
|
|
|
|
|
if (!CompileShader(entry.shader, vcode.GetBuffer(), pcode.GetBuffer(), gcode.GetBuffer()))
|
|
|
|
|
{
|
|
|
|
|
// Stop compiling shaders if any of them fail, no point continuing.
|
|
|
|
|
success = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (s_async_compiler)
|
|
|
|
|
{
|
|
|
|
|
s_async_compiler->WaitUntilCompletion([](size_t completed, size_t total) {
|
|
|
|
|
Host_UpdateProgressDialog(GetStringT("Compiling shaders...").c_str(),
|
|
|
|
|
static_cast<int>(completed), static_cast<int>(total));
|
|
|
|
|
});
|
|
|
|
|
s_async_compiler->RetrieveWorkItems();
|
|
|
|
|
Host_UpdateProgressDialog("", -1, -1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!success)
|
|
|
|
|
{
|
|
|
|
|
PanicAlert("One or more ubershaders failed to compile. Disabling ubershaders.");
|
|
|
|
|
for (auto& it : ubershaders)
|
|
|
|
|
it.second.Destroy();
|
|
|
|
|
ubershaders.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProgramShaderCache::SharedContextAsyncShaderCompiler::WorkerThreadInitMainThread(void** param)
|
|
|
|
|
{
|
|
|
|
|
SharedContextData* ctx_data = new SharedContextData();
|
|
|
|
|
ctx_data->context = GLInterface->CreateSharedContext();
|
|
|
|
|
if (!ctx_data->context)
|
|
|
|
|
{
|
|
|
|
|
PanicAlert("Failed to create shared context for shader compiling.");
|
|
|
|
|
delete ctx_data;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*param = ctx_data;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProgramShaderCache::SharedContextAsyncShaderCompiler::WorkerThreadInitWorkerThread(void* param)
|
|
|
|
|
{
|
|
|
|
|
SharedContextData* ctx_data = reinterpret_cast<SharedContextData*>(param);
|
|
|
|
|
if (!ctx_data->context->MakeCurrent())
|
|
|
|
|
{
|
|
|
|
|
PanicAlert("Failed to make shared context current.");
|
|
|
|
|
ctx_data->context->Shutdown();
|
|
|
|
|
delete ctx_data;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CreatePrerenderArrays(ctx_data);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProgramShaderCache::SharedContextAsyncShaderCompiler::WorkerThreadExit(void* param)
|
|
|
|
|
{
|
|
|
|
|
SharedContextData* ctx_data = reinterpret_cast<SharedContextData*>(param);
|
|
|
|
|
DestroyPrerenderArrays(ctx_data);
|
|
|
|
|
ctx_data->context->Shutdown();
|
|
|
|
|
delete ctx_data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ProgramShaderCache::ShaderCompileWorkItem::ShaderCompileWorkItem(const SHADERUID& uid)
|
|
|
|
|
{
|
|
|
|
|
std::memcpy(&m_uid, &uid, sizeof(m_uid));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProgramShaderCache::ShaderCompileWorkItem::Compile()
|
|
|
|
|
{
|
|
|
|
|
ShaderHostConfig host_config = ShaderHostConfig::GetCurrent();
|
|
|
|
|
ShaderCode vcode =
|
|
|
|
|
GenerateVertexShaderCode(APIType::OpenGL, host_config, m_uid.vuid.GetUidData());
|
|
|
|
|
ShaderCode pcode = GeneratePixelShaderCode(APIType::OpenGL, host_config, m_uid.puid.GetUidData());
|
|
|
|
|
ShaderCode gcode;
|
|
|
|
|
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders &&
|
|
|
|
|
!m_uid.guid.GetUidData()->IsPassthrough())
|
|
|
|
|
gcode = GenerateGeometryShaderCode(APIType::OpenGL, host_config, m_uid.guid.GetUidData());
|
|
|
|
|
|
|
|
|
|
CompileShader(m_program, vcode.GetBuffer(), pcode.GetBuffer(), gcode.GetBuffer());
|
|
|
|
|
DrawPrerenderArray(m_program,
|
|
|
|
|
static_cast<PrimitiveType>(m_uid.guid.GetUidData()->primitive_type));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProgramShaderCache::ShaderCompileWorkItem::Retrieve()
|
|
|
|
|
{
|
|
|
|
|
auto iter = pshaders.find(m_uid);
|
|
|
|
|
if (iter != pshaders.end() && !iter->second.pending)
|
|
|
|
|
{
|
|
|
|
|
// Main thread already compiled this shader.
|
|
|
|
|
m_program.Destroy();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PCacheEntry& entry = pshaders[m_uid];
|
|
|
|
|
entry.shader = m_program;
|
|
|
|
|
entry.in_cache = false;
|
|
|
|
|
entry.pending = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ProgramShaderCache::UberShaderCompileWorkItem::UberShaderCompileWorkItem(const UBERSHADERUID& uid)
|
|
|
|
|
{
|
|
|
|
|
std::memcpy(&m_uid, &uid, sizeof(m_uid));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProgramShaderCache::UberShaderCompileWorkItem::Compile()
|
|
|
|
|
{
|
|
|
|
|
ShaderHostConfig host_config = ShaderHostConfig::GetCurrent();
|
|
|
|
|
ShaderCode vcode =
|
|
|
|
|
UberShader::GenVertexShader(APIType::OpenGL, host_config, m_uid.vuid.GetUidData());
|
|
|
|
|
ShaderCode pcode =
|
|
|
|
|
UberShader::GenPixelShader(APIType::OpenGL, host_config, m_uid.puid.GetUidData());
|
|
|
|
|
ShaderCode gcode;
|
|
|
|
|
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders &&
|
|
|
|
|
!m_uid.guid.GetUidData()->IsPassthrough())
|
|
|
|
|
gcode = GenerateGeometryShaderCode(APIType::OpenGL, host_config, m_uid.guid.GetUidData());
|
|
|
|
|
|
|
|
|
|
CompileShader(m_program, vcode.GetBuffer(), pcode.GetBuffer(), gcode.GetBuffer());
|
|
|
|
|
DrawPrerenderArray(m_program,
|
|
|
|
|
static_cast<PrimitiveType>(m_uid.guid.GetUidData()->primitive_type));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProgramShaderCache::UberShaderCompileWorkItem::Retrieve()
|
|
|
|
|
{
|
|
|
|
|
auto iter = ubershaders.find(m_uid);
|
|
|
|
|
if (iter != ubershaders.end() && !iter->second.pending)
|
|
|
|
|
{
|
|
|
|
|
// Main thread already compiled this shader.
|
|
|
|
|
m_program.Destroy();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PCacheEntry& entry = ubershaders[m_uid];
|
|
|
|
|
entry.shader = m_program;
|
|
|
|
|
entry.in_cache = false;
|
|
|
|
|
entry.pending = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProgramShaderCache::CreatePrerenderArrays(SharedContextData* data)
|
|
|
|
|
{
|
|
|
|
|
// Create a framebuffer object to render into.
|
|
|
|
|
// This is because in EGL, and potentially GLX, we have a surfaceless context.
|
|
|
|
|
glGenTextures(1, &data->prerender_FBO_tex);
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, data->prerender_FBO_tex);
|
|
|
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 1);
|
|
|
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
|
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
|
|
|
|
glGenTextures(1, &data->prerender_FBO_depth);
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, data->prerender_FBO_depth);
|
|
|
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 1);
|
|
|
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
|
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT32F, 1, 1, 1, 0, GL_DEPTH_COMPONENT,
|
|
|
|
|
GL_FLOAT, nullptr);
|
|
|
|
|
glGenFramebuffers(1, &data->prerender_FBO);
|
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, data->prerender_FBO);
|
|
|
|
|
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, data->prerender_FBO_tex, 0, 0);
|
|
|
|
|
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, data->prerender_FBO_depth, 0, 0);
|
|
|
|
|
|
|
|
|
|
// Create VAO for the prerender vertices.
|
|
|
|
|
// We don't use the normal VAO map, since we need to change the VBO pointer.
|
|
|
|
|
glGenVertexArrays(1, &data->prerender_VAO);
|
|
|
|
|
glBindVertexArray(data->prerender_VAO);
|
|
|
|
|
|
|
|
|
|
// Create and populate the prerender VBO. We need enough space to draw 3 triangles.
|
|
|
|
|
static constexpr float vbo_data[] = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f};
|
|
|
|
|
constexpr u32 vbo_stride = sizeof(float) * 3;
|
|
|
|
|
glGenBuffers(1, &data->prerender_VBO);
|
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, data->prerender_VBO);
|
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(vbo_data), vbo_data, GL_STATIC_DRAW);
|
|
|
|
|
|
|
|
|
|
// We only need a position in our prerender vertex.
|
|
|
|
|
glEnableVertexAttribArray(SHADER_POSITION_ATTRIB);
|
|
|
|
|
glVertexAttribPointer(SHADER_POSITION_ATTRIB, 3, GL_FLOAT, GL_FALSE, vbo_stride, nullptr);
|
|
|
|
|
|
|
|
|
|
// The other attributes have to be active to avoid variant generation.
|
|
|
|
|
glEnableVertexAttribArray(SHADER_POSMTX_ATTRIB);
|
|
|
|
|
glVertexAttribIPointer(SHADER_POSMTX_ATTRIB, 1, GL_UNSIGNED_BYTE, vbo_stride, nullptr);
|
|
|
|
|
for (u32 i = 0; i < 3; i++)
|
|
|
|
|
{
|
|
|
|
|
glEnableVertexAttribArray(SHADER_NORM0_ATTRIB + i);
|
|
|
|
|
glVertexAttribPointer(SHADER_NORM0_ATTRIB + i, 3, GL_FLOAT, GL_FALSE, vbo_stride, nullptr);
|
|
|
|
|
}
|
|
|
|
|
for (u32 i = 0; i < 2; i++)
|
|
|
|
|
{
|
|
|
|
|
glEnableVertexAttribArray(SHADER_COLOR0_ATTRIB + i);
|
|
|
|
|
glVertexAttribPointer(SHADER_COLOR0_ATTRIB + i, 4, GL_UNSIGNED_BYTE, GL_TRUE, vbo_stride,
|
|
|
|
|
nullptr);
|
|
|
|
|
}
|
|
|
|
|
for (u32 i = 0; i < 8; i++)
|
|
|
|
|
{
|
|
|
|
|
glEnableVertexAttribArray(SHADER_TEXTURE0_ATTRIB + i);
|
|
|
|
|
glVertexAttribPointer(SHADER_TEXTURE0_ATTRIB + i, 3, GL_FLOAT, GL_FALSE, vbo_stride, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We need an index buffer to set up the same drawing state on Mesa.
|
|
|
|
|
static constexpr u16 ibo_data[] = {0, 1, 2};
|
|
|
|
|
glGenBuffers(1, &data->prerender_IBO);
|
|
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data->prerender_IBO);
|
|
|
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(ibo_data), ibo_data, GL_STATIC_DRAW);
|
|
|
|
|
|
|
|
|
|
// Mesa also requires the primitive restart state matches?
|
|
|
|
|
if (g_ActiveConfig.backend_info.bSupportsPrimitiveRestart)
|
|
|
|
|
{
|
|
|
|
|
if (GLInterface->GetMode() == GLInterfaceMode::MODE_OPENGLES3)
|
|
|
|
|
{
|
|
|
|
|
glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (GLExtensions::Version() >= 310)
|
|
|
|
|
{
|
|
|
|
|
glEnable(GL_PRIMITIVE_RESTART);
|
|
|
|
|
glPrimitiveRestartIndex(65535);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
glEnableClientState(GL_PRIMITIVE_RESTART_NV);
|
|
|
|
|
glPrimitiveRestartIndexNV(65535);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProgramShaderCache::DestroyPrerenderArrays(SharedContextData* data)
|
|
|
|
|
{
|
|
|
|
|
if (data->prerender_VAO)
|
|
|
|
|
{
|
|
|
|
|
glDeleteVertexArrays(1, &data->prerender_VAO);
|
|
|
|
|
data->prerender_VAO = 0;
|
|
|
|
|
}
|
|
|
|
|
if (data->prerender_VBO)
|
|
|
|
|
{
|
|
|
|
|
glDeleteBuffers(1, &data->prerender_VBO);
|
|
|
|
|
data->prerender_VBO = 0;
|
|
|
|
|
}
|
|
|
|
|
if (data->prerender_IBO)
|
|
|
|
|
{
|
|
|
|
|
glDeleteBuffers(1, &data->prerender_IBO);
|
|
|
|
|
data->prerender_IBO = 0;
|
|
|
|
|
}
|
|
|
|
|
if (data->prerender_FBO)
|
|
|
|
|
{
|
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
|
|
|
glDeleteFramebuffers(1, &data->prerender_FBO);
|
|
|
|
|
data->prerender_FBO = 0;
|
|
|
|
|
}
|
|
|
|
|
if (data->prerender_FBO_tex)
|
|
|
|
|
{
|
|
|
|
|
glDeleteTextures(1, &data->prerender_FBO_tex);
|
|
|
|
|
data->prerender_FBO_tex = 0;
|
|
|
|
|
}
|
|
|
|
|
if (data->prerender_FBO_depth)
|
|
|
|
|
{
|
|
|
|
|
glDeleteTextures(1, &data->prerender_FBO_depth);
|
|
|
|
|
data->prerender_FBO_depth = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProgramShaderCache::DrawPrerenderArray(const SHADER& shader, PrimitiveType primitive_type)
|
|
|
|
|
{
|
|
|
|
|
// This is called on a worker thread, so we don't want to use the normal binding process.
|
|
|
|
|
glUseProgram(shader.glprogid);
|
|
|
|
|
|
|
|
|
|
// The number of primitives drawn depends on the type.
|
|
|
|
|
switch (primitive_type)
|
|
|
|
|
{
|
|
|
|
|
case PrimitiveType::Points:
|
|
|
|
|
glDrawElements(GL_POINTS, 1, GL_UNSIGNED_SHORT, nullptr);
|
|
|
|
|
break;
|
|
|
|
|
case PrimitiveType::Lines:
|
|
|
|
|
glDrawElements(GL_LINES, 2, GL_UNSIGNED_SHORT, nullptr);
|
|
|
|
|
break;
|
|
|
|
|
case PrimitiveType::Triangles:
|
|
|
|
|
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, nullptr);
|
|
|
|
|
break;
|
|
|
|
|
case PrimitiveType::TriangleStrip:
|
|
|
|
|
glDrawElements(GL_TRIANGLE_STRIP, 3, GL_UNSIGNED_SHORT, nullptr);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Has to be finished by the time the main thread picks it up.
|
|
|
|
|
GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
|
|
|
glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED);
|
|
|
|
|
glDeleteSync(sync);
|
|
|
|
|
}
|
|
|
|
|
} // namespace OGL
|
|
|
|
|