mirror of
https://github.com/yuzu-emu/yuzu-android.git
synced 2024-12-29 18:35:44 +00:00
gl_shader_cache: Refactor to support disk shader cache
This commit is contained in:
parent
4039086226
commit
a3703f5767
|
@ -11,6 +11,7 @@
|
|||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_disk_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
#include "video_core/renderer_opengl/utils.h"
|
||||
#include "video_core/shader/shader_ir.h"
|
||||
|
@ -19,8 +20,19 @@ namespace OpenGL {
|
|||
|
||||
using VideoCommon::Shader::ProgramCode;
|
||||
|
||||
// One UBO is always reserved for emulation values
|
||||
constexpr u32 RESERVED_UBOS = 1;
|
||||
|
||||
struct UnspecializedShader {
|
||||
std::string code;
|
||||
GLShader::ShaderEntries entries;
|
||||
Maxwell::ShaderProgram program_type;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
/// Gets the address for the specified shader stage program
|
||||
static VAddr GetShaderAddress(Maxwell::ShaderProgram program) {
|
||||
VAddr GetShaderAddress(Maxwell::ShaderProgram program) {
|
||||
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
const auto& shader_config = gpu.regs.shader_config[static_cast<std::size_t>(program)];
|
||||
const auto address = gpu.memory_manager.GpuToCpuAddress(gpu.regs.code_address.CodeAddress() +
|
||||
|
@ -30,7 +42,7 @@ static VAddr GetShaderAddress(Maxwell::ShaderProgram program) {
|
|||
}
|
||||
|
||||
/// Gets the shader program code from memory for the specified address
|
||||
static ProgramCode GetShaderCode(VAddr addr) {
|
||||
ProgramCode GetShaderCode(VAddr addr) {
|
||||
ProgramCode program_code(VideoCommon::Shader::MAX_PROGRAM_LENGTH);
|
||||
Memory::ReadBlock(addr, program_code.data(), program_code.size() * sizeof(u64));
|
||||
return program_code;
|
||||
|
@ -51,38 +63,193 @@ constexpr GLenum GetShaderType(Maxwell::ShaderProgram program_type) {
|
|||
}
|
||||
}
|
||||
|
||||
CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
|
||||
: addr{addr}, program_type{program_type}, setup{GetShaderCode(addr)} {
|
||||
/// Gets if the current instruction offset is a scheduler instruction
|
||||
constexpr bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) {
|
||||
// Sched instructions appear once every 4 instructions.
|
||||
constexpr std::size_t SchedPeriod = 4;
|
||||
const std::size_t absolute_offset = offset - main_offset;
|
||||
return (absolute_offset % SchedPeriod) == 0;
|
||||
}
|
||||
|
||||
GLShader::ProgramResult program_result;
|
||||
/// Describes primitive behavior on geometry shaders
|
||||
constexpr std::tuple<const char*, const char*, u32> GetPrimitiveDescription(GLenum primitive_mode) {
|
||||
switch (primitive_mode) {
|
||||
case GL_POINTS:
|
||||
return {"points", "Points", 1};
|
||||
case GL_LINES:
|
||||
case GL_LINE_STRIP:
|
||||
return {"lines", "Lines", 2};
|
||||
case GL_LINES_ADJACENCY:
|
||||
case GL_LINE_STRIP_ADJACENCY:
|
||||
return {"lines_adjacency", "LinesAdj", 4};
|
||||
case GL_TRIANGLES:
|
||||
case GL_TRIANGLE_STRIP:
|
||||
case GL_TRIANGLE_FAN:
|
||||
return {"triangles", "Triangles", 3};
|
||||
case GL_TRIANGLES_ADJACENCY:
|
||||
case GL_TRIANGLE_STRIP_ADJACENCY:
|
||||
return {"triangles_adjacency", "TrianglesAdj", 6};
|
||||
default:
|
||||
return {"points", "Invalid", 1};
|
||||
}
|
||||
}
|
||||
|
||||
switch (program_type) {
|
||||
case Maxwell::ShaderProgram::VertexA:
|
||||
/// Calculates the size of a program stream
|
||||
std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) {
|
||||
constexpr std::size_t start_offset = 10;
|
||||
std::size_t offset = start_offset;
|
||||
std::size_t size = start_offset * sizeof(u64);
|
||||
while (offset < program.size()) {
|
||||
const u64 instruction = program[offset];
|
||||
if (!IsSchedInstruction(offset, start_offset)) {
|
||||
if (instruction == 0 || (instruction >> 52) == 0x50b) {
|
||||
// End on Maxwell's "nop" instruction
|
||||
break;
|
||||
}
|
||||
}
|
||||
size += sizeof(u64);
|
||||
offset++;
|
||||
}
|
||||
// The last instruction is included in the program size
|
||||
return std::min(size + sizeof(u64), program.size() * sizeof(u64));
|
||||
}
|
||||
|
||||
/// Hashes one (or two) program streams
|
||||
u64 GetUniqueIdentifier(Maxwell::ShaderProgram program_type, const ProgramCode& code,
|
||||
const ProgramCode& code_b) {
|
||||
u64 unique_identifier =
|
||||
Common::CityHash64(reinterpret_cast<const char*>(code.data()), CalculateProgramSize(code));
|
||||
if (program_type != Maxwell::ShaderProgram::VertexA) {
|
||||
return unique_identifier;
|
||||
}
|
||||
// VertexA programs include two programs
|
||||
|
||||
std::size_t seed = 0;
|
||||
boost::hash_combine(seed, unique_identifier);
|
||||
|
||||
const u64 identifier_b = Common::CityHash64(reinterpret_cast<const char*>(code_b.data()),
|
||||
CalculateProgramSize(code_b));
|
||||
boost::hash_combine(seed, identifier_b);
|
||||
return static_cast<u64>(seed);
|
||||
}
|
||||
|
||||
/// Creates an unspecialized program from code streams
|
||||
GLShader::ProgramResult CreateProgram(Maxwell::ShaderProgram program_type, ProgramCode program_code,
|
||||
ProgramCode program_code_b) {
|
||||
GLShader::ShaderSetup setup(std::move(program_code));
|
||||
if (program_type == Maxwell::ShaderProgram::VertexA) {
|
||||
// VertexB is always enabled, so when VertexA is enabled, we have two vertex shaders.
|
||||
// Conventional HW does not support this, so we combine VertexA and VertexB into one
|
||||
// stage here.
|
||||
setup.SetProgramB(GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB)));
|
||||
setup.SetProgramB(std::move(program_code_b));
|
||||
}
|
||||
|
||||
switch (program_type) {
|
||||
case Maxwell::ShaderProgram::VertexA:
|
||||
case Maxwell::ShaderProgram::VertexB:
|
||||
CalculateProperties();
|
||||
program_result = GLShader::GenerateVertexShader(setup);
|
||||
break;
|
||||
return GLShader::GenerateVertexShader(setup);
|
||||
case Maxwell::ShaderProgram::Geometry:
|
||||
CalculateProperties();
|
||||
program_result = GLShader::GenerateGeometryShader(setup);
|
||||
break;
|
||||
return GLShader::GenerateGeometryShader(setup);
|
||||
case Maxwell::ShaderProgram::Fragment:
|
||||
CalculateProperties();
|
||||
program_result = GLShader::GenerateFragmentShader(setup);
|
||||
break;
|
||||
return GLShader::GenerateFragmentShader(setup);
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented program_type={}", static_cast<u32>(program_type));
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEntries& entries,
|
||||
Maxwell::ShaderProgram program_type, BaseBindings base_bindings,
|
||||
GLenum primitive_mode, bool hint_retrievable = false) {
|
||||
std::string source = "#version 430 core\n";
|
||||
source += fmt::format("#define EMULATION_UBO_BINDING {}\n", base_bindings.cbuf++);
|
||||
|
||||
for (const auto& cbuf : entries.const_buffers) {
|
||||
source +=
|
||||
fmt::format("#define CBUF_BINDING_{} {}\n", cbuf.GetIndex(), base_bindings.cbuf++);
|
||||
}
|
||||
for (const auto& gmem : entries.global_memory_entries) {
|
||||
source += fmt::format("#define GMEM_BINDING_{}_{} {}\n", gmem.GetCbufIndex(),
|
||||
gmem.GetCbufOffset(), base_bindings.gmem++);
|
||||
}
|
||||
for (const auto& sampler : entries.samplers) {
|
||||
source += fmt::format("#define SAMPLER_BINDING_{} {}\n", sampler.GetIndex(),
|
||||
base_bindings.sampler++);
|
||||
}
|
||||
|
||||
if (program_type == Maxwell::ShaderProgram::Geometry) {
|
||||
const auto [glsl_topology, _, max_vertices] = GetPrimitiveDescription(primitive_mode);
|
||||
|
||||
source += "layout (" + std::string(glsl_topology) + ") in;\n";
|
||||
source += "#define MAX_VERTEX_INPUT " + std::to_string(max_vertices) + '\n';
|
||||
}
|
||||
|
||||
source += code;
|
||||
|
||||
OGLShader shader;
|
||||
shader.Create(source.c_str(), GetShaderType(program_type));
|
||||
|
||||
auto program = std::make_shared<OGLProgram>();
|
||||
program->Create(true, hint_retrievable, shader.handle);
|
||||
return program;
|
||||
}
|
||||
|
||||
std::set<GLenum> GetSupportedFormats() {
|
||||
std::set<GLenum> supported_formats;
|
||||
|
||||
GLint num_formats{};
|
||||
glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats);
|
||||
|
||||
std::vector<GLint> formats(num_formats);
|
||||
glGetIntegerv(GL_PROGRAM_BINARY_FORMATS, formats.data());
|
||||
|
||||
for (const GLint format : formats)
|
||||
supported_formats.insert(static_cast<GLenum>(format));
|
||||
return supported_formats;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
|
||||
ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
ProgramCode&& program_code, ProgramCode&& program_code_b)
|
||||
: addr{addr}, unique_identifier{unique_identifier}, program_type{program_type},
|
||||
disk_cache{disk_cache}, precompiled_programs{precompiled_programs} {
|
||||
|
||||
const std::size_t code_size = CalculateProgramSize(program_code);
|
||||
const std::size_t code_size_b =
|
||||
program_code_b.empty() ? 0 : CalculateProgramSize(program_code_b);
|
||||
|
||||
GLShader::ProgramResult program_result =
|
||||
CreateProgram(program_type, program_code, program_code_b);
|
||||
if (program_result.first.empty()) {
|
||||
// TODO(Rodrigo): Unimplemented shader stages hit here, avoid using these for now
|
||||
return;
|
||||
}
|
||||
|
||||
code = program_result.first;
|
||||
entries = program_result.second;
|
||||
shader_length = entries.shader_length;
|
||||
|
||||
const ShaderDiskCacheRaw raw(unique_identifier, program_type,
|
||||
static_cast<u32>(code_size / sizeof(u64)),
|
||||
static_cast<u32>(code_size_b / sizeof(u64)),
|
||||
std::move(program_code), std::move(program_code_b));
|
||||
disk_cache.SaveRaw(raw);
|
||||
}
|
||||
|
||||
CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
|
||||
ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
GLShader::ProgramResult result)
|
||||
: addr{addr}, unique_identifier{unique_identifier}, program_type{program_type},
|
||||
disk_cache{disk_cache}, precompiled_programs{precompiled_programs} {
|
||||
|
||||
code = std::move(result.first);
|
||||
entries = result.second;
|
||||
shader_length = entries.shader_length;
|
||||
}
|
||||
|
||||
std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(GLenum primitive_mode,
|
||||
|
@ -94,138 +261,195 @@ std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(GLenum primitive
|
|||
const auto [entry, is_cache_miss] = programs.try_emplace(base_bindings);
|
||||
auto& program = entry->second;
|
||||
if (is_cache_miss) {
|
||||
std::string source = AllocateBindings(base_bindings);
|
||||
source += code;
|
||||
program = TryLoadProgram(primitive_mode, base_bindings);
|
||||
if (!program) {
|
||||
program =
|
||||
SpecializeShader(code, entries, program_type, base_bindings, primitive_mode);
|
||||
disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings));
|
||||
}
|
||||
|
||||
OGLShader shader;
|
||||
shader.Create(source.c_str(), GetShaderType(program_type));
|
||||
program.Create(true, shader.handle);
|
||||
LabelGLObject(GL_PROGRAM, program.handle, addr);
|
||||
LabelGLObject(GL_PROGRAM, program->handle, addr);
|
||||
}
|
||||
|
||||
handle = program.handle;
|
||||
handle = program->handle;
|
||||
}
|
||||
|
||||
// Add const buffer and samplers offset reserved by this shader. One UBO binding is reserved for
|
||||
// emulation values
|
||||
base_bindings.cbuf += static_cast<u32>(entries.const_buffers.size()) + 1;
|
||||
base_bindings.cbuf += static_cast<u32>(entries.const_buffers.size()) + RESERVED_UBOS;
|
||||
base_bindings.gmem += static_cast<u32>(entries.global_memory_entries.size());
|
||||
base_bindings.sampler += static_cast<u32>(entries.samplers.size());
|
||||
|
||||
return {handle, base_bindings};
|
||||
}
|
||||
|
||||
std::string CachedShader::AllocateBindings(BaseBindings base_bindings) {
|
||||
std::string code = "#version 430 core\n";
|
||||
code += fmt::format("#define EMULATION_UBO_BINDING {}\n", base_bindings.cbuf++);
|
||||
|
||||
for (const auto& cbuf : entries.const_buffers) {
|
||||
code += fmt::format("#define CBUF_BINDING_{} {}\n", cbuf.GetIndex(), base_bindings.cbuf++);
|
||||
}
|
||||
|
||||
for (const auto& gmem : entries.global_memory_entries) {
|
||||
code += fmt::format("#define GMEM_BINDING_{}_{} {}\n", gmem.GetCbufIndex(),
|
||||
gmem.GetCbufOffset(), base_bindings.gmem++);
|
||||
}
|
||||
|
||||
for (const auto& sampler : entries.samplers) {
|
||||
code += fmt::format("#define SAMPLER_BINDING_{} {}\n", sampler.GetIndex(),
|
||||
base_bindings.sampler++);
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
GLuint CachedShader::GetGeometryShader(GLenum primitive_mode, BaseBindings base_bindings) {
|
||||
const auto [entry, is_cache_miss] = geometry_programs.try_emplace(base_bindings);
|
||||
auto& programs = entry->second;
|
||||
|
||||
switch (primitive_mode) {
|
||||
case GL_POINTS:
|
||||
return LazyGeometryProgram(programs.points, base_bindings, "points", 1, "ShaderPoints");
|
||||
return LazyGeometryProgram(programs.points, base_bindings, primitive_mode);
|
||||
case GL_LINES:
|
||||
case GL_LINE_STRIP:
|
||||
return LazyGeometryProgram(programs.lines, base_bindings, "lines", 2, "ShaderLines");
|
||||
return LazyGeometryProgram(programs.lines, base_bindings, primitive_mode);
|
||||
case GL_LINES_ADJACENCY:
|
||||
case GL_LINE_STRIP_ADJACENCY:
|
||||
return LazyGeometryProgram(programs.lines_adjacency, base_bindings, "lines_adjacency", 4,
|
||||
"ShaderLinesAdjacency");
|
||||
return LazyGeometryProgram(programs.lines_adjacency, base_bindings, primitive_mode);
|
||||
case GL_TRIANGLES:
|
||||
case GL_TRIANGLE_STRIP:
|
||||
case GL_TRIANGLE_FAN:
|
||||
return LazyGeometryProgram(programs.triangles, base_bindings, "triangles", 3,
|
||||
"ShaderTriangles");
|
||||
return LazyGeometryProgram(programs.triangles, base_bindings, primitive_mode);
|
||||
case GL_TRIANGLES_ADJACENCY:
|
||||
case GL_TRIANGLE_STRIP_ADJACENCY:
|
||||
return LazyGeometryProgram(programs.triangles_adjacency, base_bindings,
|
||||
"triangles_adjacency", 6, "ShaderTrianglesAdjacency");
|
||||
return LazyGeometryProgram(programs.triangles_adjacency, base_bindings, primitive_mode);
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown primitive mode.");
|
||||
return LazyGeometryProgram(programs.points, base_bindings, "points", 1, "ShaderPoints");
|
||||
return LazyGeometryProgram(programs.points, base_bindings, primitive_mode);
|
||||
}
|
||||
}
|
||||
|
||||
GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program, BaseBindings base_bindings,
|
||||
const std::string& glsl_topology, u32 max_vertices,
|
||||
const std::string& debug_name) {
|
||||
if (target_program.handle != 0) {
|
||||
return target_program.handle;
|
||||
GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, BaseBindings base_bindings,
|
||||
GLenum primitive_mode) {
|
||||
if (target_program) {
|
||||
return target_program->handle;
|
||||
}
|
||||
const auto [_, debug_name, __] = GetPrimitiveDescription(primitive_mode);
|
||||
target_program = TryLoadProgram(primitive_mode, base_bindings);
|
||||
if (!target_program) {
|
||||
target_program =
|
||||
SpecializeShader(code, entries, program_type, base_bindings, primitive_mode);
|
||||
disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings));
|
||||
}
|
||||
std::string source = AllocateBindings(base_bindings);
|
||||
source += "layout (" + glsl_topology + ") in;\n";
|
||||
source += "#define MAX_VERTEX_INPUT " + std::to_string(max_vertices) + '\n';
|
||||
source += code;
|
||||
|
||||
OGLShader shader;
|
||||
shader.Create(source.c_str(), GL_GEOMETRY_SHADER);
|
||||
target_program.Create(true, shader.handle);
|
||||
LabelGLObject(GL_PROGRAM, target_program.handle, addr, debug_name);
|
||||
return target_program.handle;
|
||||
LabelGLObject(GL_PROGRAM, target_program->handle, addr, debug_name);
|
||||
|
||||
return target_program->handle;
|
||||
};
|
||||
|
||||
static bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) {
|
||||
// sched instructions appear once every 4 instructions.
|
||||
static constexpr std::size_t SchedPeriod = 4;
|
||||
const std::size_t absolute_offset = offset - main_offset;
|
||||
return (absolute_offset % SchedPeriod) == 0;
|
||||
CachedProgram CachedShader::TryLoadProgram(GLenum primitive_mode,
|
||||
BaseBindings base_bindings) const {
|
||||
const auto found = precompiled_programs.find(GetUsage(primitive_mode, base_bindings));
|
||||
if (found == precompiled_programs.end()) {
|
||||
return {};
|
||||
}
|
||||
return found->second;
|
||||
}
|
||||
|
||||
static std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) {
|
||||
constexpr std::size_t start_offset = 10;
|
||||
std::size_t offset = start_offset;
|
||||
std::size_t size = start_offset * sizeof(u64);
|
||||
while (offset < program.size()) {
|
||||
const u64 inst = program[offset];
|
||||
if (!IsSchedInstruction(offset, start_offset)) {
|
||||
if (inst == 0 || (inst >> 52) == 0x50b) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
size += sizeof(inst);
|
||||
offset++;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void CachedShader::CalculateProperties() {
|
||||
setup.program.real_size = CalculateProgramSize(setup.program.code);
|
||||
setup.program.real_size_b = 0;
|
||||
setup.program.unique_identifier = Common::CityHash64(
|
||||
reinterpret_cast<const char*>(setup.program.code.data()), setup.program.real_size);
|
||||
if (program_type == Maxwell::ShaderProgram::VertexA) {
|
||||
std::size_t seed = 0;
|
||||
boost::hash_combine(seed, setup.program.unique_identifier);
|
||||
setup.program.real_size_b = CalculateProgramSize(setup.program.code_b);
|
||||
const u64 identifier_b = Common::CityHash64(
|
||||
reinterpret_cast<const char*>(setup.program.code_b.data()), setup.program.real_size_b);
|
||||
boost::hash_combine(seed, identifier_b);
|
||||
setup.program.unique_identifier = static_cast<u64>(seed);
|
||||
}
|
||||
ShaderDiskCacheUsage CachedShader::GetUsage(GLenum primitive_mode,
|
||||
BaseBindings base_bindings) const {
|
||||
return {unique_identifier, base_bindings, primitive_mode};
|
||||
}
|
||||
|
||||
ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer) : RasterizerCache{rasterizer} {}
|
||||
|
||||
void ShaderCacheOpenGL::LoadDiskCache() {}
|
||||
void ShaderCacheOpenGL::LoadDiskCache() {
|
||||
std::vector<ShaderDiskCacheRaw> raws;
|
||||
std::vector<ShaderDiskCacheUsage> usages;
|
||||
if (!disk_cache.LoadTransferable(raws, usages)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<ShaderDiskCachePrecompiledEntry> precompiled = disk_cache.LoadPrecompiled();
|
||||
const auto SearchPrecompiled = [&precompiled](const ShaderDiskCacheUsage& usage) {
|
||||
return std::find_if(
|
||||
precompiled.begin(), precompiled.end(),
|
||||
[&usage](const auto& precompiled_entry) { return precompiled_entry.usage == usage; });
|
||||
};
|
||||
|
||||
const std::set<GLenum> supported_formats{GetSupportedFormats()};
|
||||
const auto unspecialized{GenerateUnspecializedShaders(raws)};
|
||||
|
||||
// Build shaders
|
||||
for (std::size_t i = 0; i < usages.size(); ++i) {
|
||||
const auto& usage{usages[i]};
|
||||
LOG_INFO(Render_OpenGL, "Building shader {:016x} ({} of {})", usage.unique_identifier,
|
||||
i + 1, usages.size());
|
||||
|
||||
const auto& unspec{unspecialized.at(usage.unique_identifier)};
|
||||
|
||||
const auto precompiled_it = SearchPrecompiled(usage);
|
||||
const bool is_precompiled = precompiled_it != precompiled.end();
|
||||
|
||||
CachedProgram shader;
|
||||
if (is_precompiled) {
|
||||
shader = GeneratePrecompiledProgram(precompiled, *precompiled_it, supported_formats);
|
||||
}
|
||||
if (!shader) {
|
||||
shader = SpecializeShader(unspec.code, unspec.entries, unspec.program_type,
|
||||
usage.bindings, usage.primitive, true);
|
||||
}
|
||||
precompiled_programs.insert({usage, std::move(shader)});
|
||||
}
|
||||
|
||||
// TODO(Rodrigo): Do state tracking for transferable shaders and do a dummy draw before
|
||||
// precompiling them
|
||||
|
||||
for (std::size_t i = 0; i < usages.size(); ++i) {
|
||||
const auto& usage{usages[i]};
|
||||
if (SearchPrecompiled(usage) == precompiled.end()) {
|
||||
const auto& program = precompiled_programs.at(usage);
|
||||
disk_cache.SavePrecompiled(usage, program->handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CachedProgram ShaderCacheOpenGL::GeneratePrecompiledProgram(
|
||||
std::vector<ShaderDiskCachePrecompiledEntry>& precompiled,
|
||||
const ShaderDiskCachePrecompiledEntry& precompiled_entry,
|
||||
const std::set<GLenum>& supported_formats) {
|
||||
|
||||
if (supported_formats.find(precompiled_entry.binary_format) == supported_formats.end()) {
|
||||
LOG_INFO(Render_OpenGL, "Precompiled cache entry with unsupported format - removing");
|
||||
disk_cache.InvalidatePrecompiled();
|
||||
precompiled.clear();
|
||||
return {};
|
||||
}
|
||||
|
||||
CachedProgram shader = std::make_shared<OGLProgram>();
|
||||
shader->handle = glCreateProgram();
|
||||
glProgramBinary(shader->handle, precompiled_entry.binary_format,
|
||||
precompiled_entry.binary.data(),
|
||||
static_cast<GLsizei>(precompiled_entry.binary.size()));
|
||||
|
||||
GLint link_status{};
|
||||
glGetProgramiv(shader->handle, GL_LINK_STATUS, &link_status);
|
||||
if (link_status == GL_FALSE) {
|
||||
LOG_INFO(Render_OpenGL, "Precompiled cache rejected by the driver - removing");
|
||||
disk_cache.InvalidatePrecompiled();
|
||||
precompiled.clear();
|
||||
|
||||
shader.reset();
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
std::map<u64, UnspecializedShader> ShaderCacheOpenGL::GenerateUnspecializedShaders(
|
||||
const std::vector<ShaderDiskCacheRaw>& raws) {
|
||||
|
||||
std::map<u64, UnspecializedShader> unspecialized;
|
||||
for (const auto& raw : raws) {
|
||||
const u64 calculated_hash =
|
||||
GetUniqueIdentifier(raw.GetProgramType(), raw.GetProgramCode(), raw.GetProgramCodeB());
|
||||
if (raw.GetUniqueIdentifier() != calculated_hash) {
|
||||
LOG_ERROR(
|
||||
Render_OpenGL,
|
||||
"Invalid hash in entry={:016x} (obtained hash={:016x}) - removing shader cache",
|
||||
raw.GetUniqueIdentifier(), calculated_hash);
|
||||
disk_cache.InvalidateTransferable();
|
||||
return {};
|
||||
}
|
||||
|
||||
auto result =
|
||||
CreateProgram(raw.GetProgramType(), raw.GetProgramCode(), raw.GetProgramCodeB());
|
||||
|
||||
precompiled_shaders.insert({raw.GetUniqueIdentifier(), result});
|
||||
|
||||
unspecialized.insert(
|
||||
{raw.GetUniqueIdentifier(),
|
||||
{std::move(result.first), std::move(result.second), raw.GetProgramType()}});
|
||||
}
|
||||
return unspecialized;
|
||||
}
|
||||
|
||||
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
|
||||
if (!Core::System::GetInstance().GPU().Maxwell3D().dirty_flags.shaders) {
|
||||
|
@ -239,7 +463,23 @@ Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
|
|||
|
||||
if (!shader) {
|
||||
// No shader found - create a new one
|
||||
shader = std::make_shared<CachedShader>(program_addr, program);
|
||||
ProgramCode program_code = GetShaderCode(program_addr);
|
||||
ProgramCode program_code_b;
|
||||
if (program == Maxwell::ShaderProgram::VertexA) {
|
||||
program_code_b = GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB));
|
||||
}
|
||||
const u64 unique_identifier = GetUniqueIdentifier(program, program_code, program_code_b);
|
||||
|
||||
const auto found = precompiled_shaders.find(unique_identifier);
|
||||
if (found != precompiled_shaders.end()) {
|
||||
shader =
|
||||
std::make_shared<CachedShader>(program_addr, unique_identifier, program, disk_cache,
|
||||
precompiled_programs, found->second);
|
||||
} else {
|
||||
shader = std::make_shared<CachedShader>(
|
||||
program_addr, unique_identifier, program, disk_cache, precompiled_programs,
|
||||
std::move(program_code), std::move(program_code_b));
|
||||
}
|
||||
Register(shader);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
@ -23,13 +24,25 @@ namespace OpenGL {
|
|||
|
||||
class CachedShader;
|
||||
class RasterizerOpenGL;
|
||||
struct UnspecializedShader;
|
||||
|
||||
using Shader = std::shared_ptr<CachedShader>;
|
||||
using CachedProgram = std::shared_ptr<OGLProgram>;
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
using PrecompiledPrograms = std::map<ShaderDiskCacheUsage, CachedProgram>;
|
||||
using PrecompiledShaders = std::map<u64, GLShader::ProgramResult>;
|
||||
|
||||
class CachedShader final : public RasterizerCacheObject {
|
||||
public:
|
||||
CachedShader(VAddr addr, Maxwell::ShaderProgram program_type);
|
||||
explicit CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
|
||||
ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
ProgramCode&& program_code, ProgramCode&& program_code_b);
|
||||
|
||||
explicit CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
|
||||
ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
GLShader::ProgramResult result);
|
||||
|
||||
VAddr GetAddr() const override {
|
||||
return addr;
|
||||
|
@ -56,33 +69,35 @@ private:
|
|||
// declared by the hardware. Workaround this issue by generating a different shader per input
|
||||
// topology class.
|
||||
struct GeometryPrograms {
|
||||
OGLProgram points;
|
||||
OGLProgram lines;
|
||||
OGLProgram lines_adjacency;
|
||||
OGLProgram triangles;
|
||||
OGLProgram triangles_adjacency;
|
||||
CachedProgram points;
|
||||
CachedProgram lines;
|
||||
CachedProgram lines_adjacency;
|
||||
CachedProgram triangles;
|
||||
CachedProgram triangles_adjacency;
|
||||
};
|
||||
|
||||
std::string AllocateBindings(BaseBindings base_bindings);
|
||||
|
||||
GLuint GetGeometryShader(GLenum primitive_mode, BaseBindings base_bindings);
|
||||
|
||||
/// Generates a geometry shader or returns one that already exists.
|
||||
GLuint LazyGeometryProgram(OGLProgram& target_program, BaseBindings base_bindings,
|
||||
const std::string& glsl_topology, u32 max_vertices,
|
||||
const std::string& debug_name);
|
||||
GLuint LazyGeometryProgram(CachedProgram& target_program, BaseBindings base_bindings,
|
||||
GLenum primitive_mode);
|
||||
|
||||
void CalculateProperties();
|
||||
CachedProgram TryLoadProgram(GLenum primitive_mode, BaseBindings base_bindings) const;
|
||||
|
||||
ShaderDiskCacheUsage GetUsage(GLenum primitive_mode, BaseBindings base_bindings) const;
|
||||
|
||||
const VAddr addr;
|
||||
const u64 unique_identifier;
|
||||
const Maxwell::ShaderProgram program_type;
|
||||
ShaderDiskCacheOpenGL& disk_cache;
|
||||
const PrecompiledPrograms& precompiled_programs;
|
||||
|
||||
VAddr addr{};
|
||||
std::size_t shader_length{};
|
||||
Maxwell::ShaderProgram program_type{};
|
||||
GLShader::ShaderSetup setup;
|
||||
GLShader::ShaderEntries entries;
|
||||
|
||||
std::string code;
|
||||
|
||||
std::map<BaseBindings, OGLProgram> programs;
|
||||
std::map<BaseBindings, CachedProgram> programs;
|
||||
std::map<BaseBindings, GeometryPrograms> geometry_programs;
|
||||
|
||||
std::map<u32, GLuint> cbuf_resource_cache;
|
||||
|
@ -101,7 +116,19 @@ public:
|
|||
Shader GetStageProgram(Maxwell::ShaderProgram program);
|
||||
|
||||
private:
|
||||
std::map<u64, UnspecializedShader> GenerateUnspecializedShaders(
|
||||
const std::vector<ShaderDiskCacheRaw>& raws);
|
||||
|
||||
CachedProgram GeneratePrecompiledProgram(
|
||||
std::vector<ShaderDiskCachePrecompiledEntry>& precompiled,
|
||||
const ShaderDiskCachePrecompiledEntry& precompiled_entry,
|
||||
const std::set<GLenum>& supported_formats);
|
||||
|
||||
std::array<Shader, Maxwell::MaxShaderProgram> last_shaders;
|
||||
|
||||
ShaderDiskCacheOpenGL disk_cache;
|
||||
PrecompiledShaders precompiled_shaders;
|
||||
PrecompiledPrograms precompiled_programs;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
|
Loading…
Reference in a new issue