|
|
|
@ -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; |
|
|
|
} |
|
|
|
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() { |
|
|
|
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); |
|
|
|
} |
|
|
|
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); |
|
|
|
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; |
|
|
|
} |
|
|
|
|
|
|
|
ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer) : RasterizerCache{rasterizer} {} |
|
|
|
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}); |
|
|
|
|
|
|
|
void ShaderCacheOpenGL::LoadDiskCache() {} |
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
|