summaryrefslogtreecommitdiffstats
path: root/src/video_core/renderer_opengl/gl_shader_cache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_core/renderer_opengl/gl_shader_cache.cpp')
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp994
1 files changed, 466 insertions, 528 deletions
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 5a01c59ec..8d6cc074c 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -3,606 +3,544 @@
// Refer to the license.txt file included.
#include <atomic>
+#include <fstream>
#include <functional>
#include <mutex>
-#include <optional>
#include <string>
#include <thread>
-#include <unordered_set>
#include "common/alignment.h"
#include "common/assert.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
+#include "common/settings.h"
+#include "common/thread_worker.h"
#include "core/core.h"
-#include "core/frontend/emu_window.h"
+#include "shader_recompiler/backend/glasm/emit_glasm.h"
+#include "shader_recompiler/backend/glsl/emit_glsl.h"
+#include "shader_recompiler/backend/spirv/emit_spirv.h"
+#include "shader_recompiler/frontend/ir/program.h"
+#include "shader_recompiler/frontend/maxwell/control_flow.h"
+#include "shader_recompiler/frontend/maxwell/translate_program.h"
+#include "shader_recompiler/profile.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
-#include "video_core/engines/shader_type.h"
#include "video_core/memory_manager.h"
-#include "video_core/renderer_opengl/gl_arb_decompiler.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_resource_manager.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_util.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
-#include "video_core/shader/memory_util.h"
-#include "video_core/shader/registry.h"
-#include "video_core/shader/shader_ir.h"
#include "video_core/shader_cache.h"
+#include "video_core/shader_environment.h"
#include "video_core/shader_notify.h"
namespace OpenGL {
-
-using Tegra::Engines::ShaderType;
-using VideoCommon::Shader::GetShaderAddress;
-using VideoCommon::Shader::GetShaderCode;
-using VideoCommon::Shader::GetUniqueIdentifier;
-using VideoCommon::Shader::KERNEL_MAIN_OFFSET;
-using VideoCommon::Shader::ProgramCode;
-using VideoCommon::Shader::Registry;
-using VideoCommon::Shader::ShaderIR;
-using VideoCommon::Shader::STAGE_MAIN_OFFSET;
-
namespace {
-
-constexpr VideoCommon::Shader::CompilerSettings COMPILER_SETTINGS{};
-
-/// Gets the shader type from a Maxwell program type
-constexpr GLenum GetGLShaderType(ShaderType shader_type) {
- switch (shader_type) {
- case ShaderType::Vertex:
- return GL_VERTEX_SHADER;
- case ShaderType::Geometry:
- return GL_GEOMETRY_SHADER;
- case ShaderType::Fragment:
- return GL_FRAGMENT_SHADER;
- case ShaderType::Compute:
- return GL_COMPUTE_SHADER;
- default:
- return GL_NONE;
- }
+using Shader::Backend::GLASM::EmitGLASM;
+using Shader::Backend::GLSL::EmitGLSL;
+using Shader::Backend::SPIRV::EmitSPIRV;
+using Shader::Maxwell::MergeDualVertexPrograms;
+using Shader::Maxwell::TranslateProgram;
+using VideoCommon::ComputeEnvironment;
+using VideoCommon::FileEnvironment;
+using VideoCommon::GenericEnvironment;
+using VideoCommon::GraphicsEnvironment;
+using VideoCommon::LoadPipelines;
+using VideoCommon::SerializePipeline;
+using Context = ShaderContext::Context;
+
+constexpr u32 CACHE_VERSION = 5;
+
+template <typename Container>
+auto MakeSpan(Container& container) {
+ return std::span(container.data(), container.size());
}
-constexpr const char* GetShaderTypeName(ShaderType shader_type) {
- switch (shader_type) {
- case ShaderType::Vertex:
- return "VS";
- case ShaderType::TesselationControl:
- return "HS";
- case ShaderType::TesselationEval:
- return "DS";
- case ShaderType::Geometry:
- return "GS";
- case ShaderType::Fragment:
- return "FS";
- case ShaderType::Compute:
- return "CS";
- }
- return "UNK";
+Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key,
+ const Shader::IR::Program& program,
+ const Shader::IR::Program* previous_program,
+ bool glasm_use_storage_buffers, bool use_assembly_shaders) {
+ Shader::RuntimeInfo info;
+ if (previous_program) {
+ info.previous_stage_stores = previous_program->info.stores;
+ } else {
+ // Mark all stores as available for vertex shaders
+ info.previous_stage_stores.mask.set();
+ }
+ switch (program.stage) {
+ case Shader::Stage::VertexB:
+ case Shader::Stage::Geometry:
+ if (!use_assembly_shaders && key.xfb_enabled != 0) {
+ info.xfb_varyings = VideoCommon::MakeTransformFeedbackVaryings(key.xfb_state);
+ }
+ break;
+ case Shader::Stage::TessellationEval:
+ info.tess_clockwise = key.tessellation_clockwise != 0;
+ info.tess_primitive = [&key] {
+ switch (key.tessellation_primitive) {
+ case Maxwell::TessellationPrimitive::Isolines:
+ return Shader::TessPrimitive::Isolines;
+ case Maxwell::TessellationPrimitive::Triangles:
+ return Shader::TessPrimitive::Triangles;
+ case Maxwell::TessellationPrimitive::Quads:
+ return Shader::TessPrimitive::Quads;
+ }
+ UNREACHABLE();
+ return Shader::TessPrimitive::Triangles;
+ }();
+ info.tess_spacing = [&] {
+ switch (key.tessellation_spacing) {
+ case Maxwell::TessellationSpacing::Equal:
+ return Shader::TessSpacing::Equal;
+ case Maxwell::TessellationSpacing::FractionalOdd:
+ return Shader::TessSpacing::FractionalOdd;
+ case Maxwell::TessellationSpacing::FractionalEven:
+ return Shader::TessSpacing::FractionalEven;
+ }
+ UNREACHABLE();
+ return Shader::TessSpacing::Equal;
+ }();
+ break;
+ case Shader::Stage::Fragment:
+ info.force_early_z = key.early_z != 0;
+ break;
+ default:
+ break;
+ }
+ switch (key.gs_input_topology) {
+ case Maxwell::PrimitiveTopology::Points:
+ info.input_topology = Shader::InputTopology::Points;
+ break;
+ case Maxwell::PrimitiveTopology::Lines:
+ case Maxwell::PrimitiveTopology::LineLoop:
+ case Maxwell::PrimitiveTopology::LineStrip:
+ info.input_topology = Shader::InputTopology::Lines;
+ break;
+ case Maxwell::PrimitiveTopology::Triangles:
+ case Maxwell::PrimitiveTopology::TriangleStrip:
+ case Maxwell::PrimitiveTopology::TriangleFan:
+ case Maxwell::PrimitiveTopology::Quads:
+ case Maxwell::PrimitiveTopology::QuadStrip:
+ case Maxwell::PrimitiveTopology::Polygon:
+ case Maxwell::PrimitiveTopology::Patches:
+ info.input_topology = Shader::InputTopology::Triangles;
+ break;
+ case Maxwell::PrimitiveTopology::LinesAdjacency:
+ case Maxwell::PrimitiveTopology::LineStripAdjacency:
+ info.input_topology = Shader::InputTopology::LinesAdjacency;
+ break;
+ case Maxwell::PrimitiveTopology::TrianglesAdjacency:
+ case Maxwell::PrimitiveTopology::TriangleStripAdjacency:
+ info.input_topology = Shader::InputTopology::TrianglesAdjacency;
+ break;
+ }
+ info.glasm_use_storage_buffers = glasm_use_storage_buffers;
+ return info;
}
-constexpr ShaderType GetShaderType(Maxwell::ShaderProgram program_type) {
- switch (program_type) {
- case Maxwell::ShaderProgram::VertexA:
- case Maxwell::ShaderProgram::VertexB:
- return ShaderType::Vertex;
- case Maxwell::ShaderProgram::TesselationControl:
- return ShaderType::TesselationControl;
- case Maxwell::ShaderProgram::TesselationEval:
- return ShaderType::TesselationEval;
- case Maxwell::ShaderProgram::Geometry:
- return ShaderType::Geometry;
- case Maxwell::ShaderProgram::Fragment:
- return ShaderType::Fragment;
- }
- return {};
+void SetXfbState(VideoCommon::TransformFeedbackState& state, const Maxwell& regs) {
+ std::ranges::transform(regs.tfb_layouts, state.layouts.begin(), [](const auto& layout) {
+ return VideoCommon::TransformFeedbackState::Layout{
+ .stream = layout.stream,
+ .varying_count = layout.varying_count,
+ .stride = layout.stride,
+ };
+ });
+ state.varyings = regs.tfb_varying_locs;
}
+} // Anonymous namespace
-constexpr GLenum AssemblyEnum(ShaderType shader_type) {
- switch (shader_type) {
- case ShaderType::Vertex:
- return GL_VERTEX_PROGRAM_NV;
- case ShaderType::TesselationControl:
- return GL_TESS_CONTROL_PROGRAM_NV;
- case ShaderType::TesselationEval:
- return GL_TESS_EVALUATION_PROGRAM_NV;
- case ShaderType::Geometry:
- return GL_GEOMETRY_PROGRAM_NV;
- case ShaderType::Fragment:
- return GL_FRAGMENT_PROGRAM_NV;
- case ShaderType::Compute:
- return GL_COMPUTE_PROGRAM_NV;
+ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindow& emu_window_,
+ Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::Engines::KeplerCompute& kepler_compute_,
+ Tegra::MemoryManager& gpu_memory_, const Device& device_,
+ TextureCache& texture_cache_, BufferCache& buffer_cache_,
+ ProgramManager& program_manager_, StateTracker& state_tracker_,
+ VideoCore::ShaderNotify& shader_notify_)
+ : VideoCommon::ShaderCache{rasterizer_, gpu_memory_, maxwell3d_, kepler_compute_},
+ emu_window{emu_window_}, device{device_}, texture_cache{texture_cache_},
+ buffer_cache{buffer_cache_}, program_manager{program_manager_}, state_tracker{state_tracker_},
+ shader_notify{shader_notify_}, use_asynchronous_shaders{device.UseAsynchronousShaders()},
+ profile{
+ .supported_spirv = 0x00010000,
+
+ .unified_descriptor_binding = false,
+ .support_descriptor_aliasing = false,
+ .support_int8 = false,
+ .support_int16 = false,
+ .support_int64 = device.HasShaderInt64(),
+ .support_vertex_instance_id = true,
+ .support_float_controls = false,
+ .support_separate_denorm_behavior = false,
+ .support_separate_rounding_mode = false,
+ .support_fp16_denorm_preserve = false,
+ .support_fp32_denorm_preserve = false,
+ .support_fp16_denorm_flush = false,
+ .support_fp32_denorm_flush = false,
+ .support_fp16_signed_zero_nan_preserve = false,
+ .support_fp32_signed_zero_nan_preserve = false,
+ .support_fp64_signed_zero_nan_preserve = false,
+ .support_explicit_workgroup_layout = false,
+ .support_vote = true,
+ .support_viewport_index_layer_non_geometry =
+ device.HasNvViewportArray2() || device.HasVertexViewportLayer(),
+ .support_viewport_mask = device.HasNvViewportArray2(),
+ .support_typeless_image_loads = device.HasImageLoadFormatted(),
+ .support_demote_to_helper_invocation = false,
+ .support_int64_atomics = false,
+ .support_derivative_control = device.HasDerivativeControl(),
+ .support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(),
+ .support_gl_nv_gpu_shader_5 = device.HasNvGpuShader5(),
+ .support_gl_amd_gpu_shader_half_float = device.HasAmdShaderHalfFloat(),
+ .support_gl_texture_shadow_lod = device.HasTextureShadowLod(),
+ .support_gl_warp_intrinsics = false,
+ .support_gl_variable_aoffi = device.HasVariableAoffi(),
+ .support_gl_sparse_textures = device.HasSparseTexture2(),
+ .support_gl_derivative_control = device.HasDerivativeControl(),
+
+ .warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyLargerThanGuest(),
+
+ .lower_left_origin_mode = true,
+ .need_declared_frag_colors = true,
+ .need_fastmath_off = device.NeedsFastmathOff(),
+
+ .has_broken_spirv_clamp = true,
+ .has_broken_unsigned_image_offsets = true,
+ .has_broken_signed_operations = true,
+ .has_broken_fp16_float_controls = false,
+ .has_gl_component_indexing_bug = device.HasComponentIndexingBug(),
+ .has_gl_precise_bug = device.HasPreciseBug(),
+ .ignore_nan_fp_comparisons = true,
+ .gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(),
+ },
+ host_info{
+ .support_float16 = false,
+ .support_int64 = device.HasShaderInt64(),
+ } {
+ if (use_asynchronous_shaders) {
+ workers = CreateWorkers();
}
- return {};
}
-std::string MakeShaderID(u64 unique_identifier, ShaderType shader_type) {
- return fmt::format("{}{:016X}", GetShaderTypeName(shader_type), unique_identifier);
-}
+ShaderCache::~ShaderCache() = default;
-std::shared_ptr<Registry> MakeRegistry(const ShaderDiskCacheEntry& entry) {
- const VideoCore::GuestDriverProfile guest_profile{entry.texture_handler_size};
- const VideoCommon::Shader::SerializedRegistryInfo info{guest_profile, entry.bound_buffer,
- entry.graphics_info, entry.compute_info};
- auto registry = std::make_shared<Registry>(entry.type, info);
- for (const auto& [address, value] : entry.keys) {
- const auto [buffer, offset] = address;
- registry->InsertKey(buffer, offset, value);
- }
- for (const auto& [offset, sampler] : entry.bound_samplers) {
- registry->InsertBoundSampler(offset, sampler);
+void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
+ const VideoCore::DiskResourceLoadCallback& callback) {
+ if (title_id == 0) {
+ return;
}
- for (const auto& [key, sampler] : entry.bindless_samplers) {
- const auto [buffer, offset] = key;
- registry->InsertBindlessSampler(buffer, offset, sampler);
+ const auto shader_dir{Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir)};
+ const auto base_dir{shader_dir / fmt::format("{:016x}", title_id)};
+ if (!Common::FS::CreateDir(shader_dir) || !Common::FS::CreateDir(base_dir)) {
+ LOG_ERROR(Common_Filesystem, "Failed to create shader cache directories");
+ return;
}
- return registry;
-}
-
-std::unordered_set<GLenum> GetSupportedFormats() {
- GLint num_formats;
- glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats);
+ shader_cache_filename = base_dir / "opengl.bin";
+
+ if (!workers) {
+ workers = CreateWorkers();
+ }
+ struct {
+ std::mutex mutex;
+ size_t total{};
+ size_t built{};
+ bool has_loaded{};
+ } state;
+
+ const auto load_compute{[&](std::ifstream& file, FileEnvironment env) {
+ ComputePipelineKey key;
+ file.read(reinterpret_cast<char*>(&key), sizeof(key));
+ workers->QueueWork(
+ [this, key, env = std::move(env), &state, &callback](Context* ctx) mutable {
+ ctx->pools.ReleaseContents();
+ auto pipeline{CreateComputePipeline(ctx->pools, key, env)};
+ std::lock_guard lock{state.mutex};
+ if (pipeline) {
+ compute_cache.emplace(key, std::move(pipeline));
+ }
+ ++state.built;
+ if (state.has_loaded) {
+ callback(VideoCore::LoadCallbackStage::Build, state.built, state.total);
+ }
+ });
+ ++state.total;
+ }};
+ const auto load_graphics{[&](std::ifstream& file, std::vector<FileEnvironment> envs) {
+ GraphicsPipelineKey key;
+ file.read(reinterpret_cast<char*>(&key), sizeof(key));
+ workers->QueueWork(
+ [this, key, envs = std::move(envs), &state, &callback](Context* ctx) mutable {
+ boost::container::static_vector<Shader::Environment*, 5> env_ptrs;
+ for (auto& env : envs) {
+ env_ptrs.push_back(&env);
+ }
+ ctx->pools.ReleaseContents();
+ auto pipeline{CreateGraphicsPipeline(ctx->pools, key, MakeSpan(env_ptrs), false)};
+ std::lock_guard lock{state.mutex};
+ if (pipeline) {
+ graphics_cache.emplace(key, std::move(pipeline));
+ }
+ ++state.built;
+ if (state.has_loaded) {
+ callback(VideoCore::LoadCallbackStage::Build, state.built, state.total);
+ }
+ });
+ ++state.total;
+ }};
+ LoadPipelines(stop_loading, shader_cache_filename, CACHE_VERSION, load_compute, load_graphics);
- std::vector<GLint> formats(num_formats);
- glGetIntegerv(GL_PROGRAM_BINARY_FORMATS, formats.data());
+ std::unique_lock lock{state.mutex};
+ callback(VideoCore::LoadCallbackStage::Build, 0, state.total);
+ state.has_loaded = true;
+ lock.unlock();
- std::unordered_set<GLenum> supported_formats;
- for (const GLint format : formats) {
- supported_formats.insert(static_cast<GLenum>(format));
+ workers->WaitForRequests();
+ if (!use_asynchronous_shaders) {
+ workers.reset();
}
- return supported_formats;
}
-} // Anonymous namespace
-
-ProgramSharedPtr BuildShader(const Device& device, ShaderType shader_type, u64 unique_identifier,
- const ShaderIR& ir, const Registry& registry, bool hint_retrievable) {
- if (device.UseDriverCache()) {
- // Ignore hint retrievable if we are using the driver cache
- hint_retrievable = false;
- }
- const std::string shader_id = MakeShaderID(unique_identifier, shader_type);
- LOG_INFO(Render_OpenGL, "{}", shader_id);
-
- auto program = std::make_shared<ProgramHandle>();
-
- if (device.UseAssemblyShaders()) {
- const std::string arb =
- DecompileAssemblyShader(device, ir, registry, shader_type, shader_id);
-
- GLuint& arb_prog = program->assembly_program.handle;
-
-// Commented out functions signal OpenGL errors but are compatible with apitrace.
-// Use them only to capture and replay on apitrace.
-#if 0
- glGenProgramsNV(1, &arb_prog);
- glLoadProgramNV(AssemblyEnum(shader_type), arb_prog, static_cast<GLsizei>(arb.size()),
- reinterpret_cast<const GLubyte*>(arb.data()));
-#else
- glGenProgramsARB(1, &arb_prog);
- glNamedProgramStringEXT(arb_prog, AssemblyEnum(shader_type), GL_PROGRAM_FORMAT_ASCII_ARB,
- static_cast<GLsizei>(arb.size()), arb.data());
-#endif
- const auto err = reinterpret_cast<const char*>(glGetString(GL_PROGRAM_ERROR_STRING_NV));
- if (err && *err) {
- LOG_CRITICAL(Render_OpenGL, "{}", err);
- LOG_INFO(Render_OpenGL, "\n{}", arb);
- }
- } else {
- const std::string glsl = DecompileShader(device, ir, registry, shader_type, shader_id);
- OGLShader shader;
- shader.Create(glsl.c_str(), GetGLShaderType(shader_type));
-
- program->source_program.Create(true, hint_retrievable, shader.handle);
- }
-
- return program;
+GraphicsPipeline* ShaderCache::CurrentGraphicsPipeline() {
+ if (!RefreshStages(graphics_key.unique_hashes)) {
+ current_pipeline = nullptr;
+ return nullptr;
+ }
+ const auto& regs{maxwell3d.regs};
+ graphics_key.raw = 0;
+ graphics_key.early_z.Assign(regs.force_early_fragment_tests != 0 ? 1 : 0);
+ graphics_key.gs_input_topology.Assign(graphics_key.unique_hashes[4] != 0
+ ? regs.draw.topology.Value()
+ : Maxwell::PrimitiveTopology{});
+ graphics_key.tessellation_primitive.Assign(regs.tess_mode.prim.Value());
+ graphics_key.tessellation_spacing.Assign(regs.tess_mode.spacing.Value());
+ graphics_key.tessellation_clockwise.Assign(regs.tess_mode.cw.Value());
+ graphics_key.xfb_enabled.Assign(regs.tfb_enabled != 0 ? 1 : 0);
+ if (graphics_key.xfb_enabled) {
+ SetXfbState(graphics_key.xfb_state, regs);
+ }
+ if (current_pipeline && graphics_key == current_pipeline->Key()) {
+ return BuiltPipeline(current_pipeline);
+ }
+ return CurrentGraphicsPipelineSlowPath();
}
-Shader::Shader(std::shared_ptr<Registry> registry_, ShaderEntries entries_,
- ProgramSharedPtr program_, bool is_built_)
- : registry{std::move(registry_)}, entries{std::move(entries_)}, program{std::move(program_)},
- is_built{is_built_} {
- handle = program->assembly_program.handle;
- if (handle == 0) {
- handle = program->source_program.handle;
+GraphicsPipeline* ShaderCache::CurrentGraphicsPipelineSlowPath() {
+ const auto [pair, is_new]{graphics_cache.try_emplace(graphics_key)};
+ auto& pipeline{pair->second};
+ if (is_new) {
+ pipeline = CreateGraphicsPipeline();
}
- if (is_built) {
- ASSERT(handle != 0);
+ if (!pipeline) {
+ return nullptr;
}
+ current_pipeline = pipeline.get();
+ return BuiltPipeline(current_pipeline);
}
-Shader::~Shader() = default;
-
-GLuint Shader::GetHandle() const {
- DEBUG_ASSERT(registry->IsConsistent());
- return handle;
-}
-
-bool Shader::IsBuilt() const {
- return is_built;
-}
-
-void Shader::AsyncOpenGLBuilt(OGLProgram new_program) {
- program->source_program = std::move(new_program);
- handle = program->source_program.handle;
- is_built = true;
-}
-
-void Shader::AsyncGLASMBuilt(OGLAssemblyProgram new_program) {
- program->assembly_program = std::move(new_program);
- handle = program->assembly_program.handle;
- is_built = true;
-}
-
-std::unique_ptr<Shader> Shader::CreateStageFromMemory(
- const ShaderParameters& params, Maxwell::ShaderProgram program_type, ProgramCode code,
- ProgramCode code_b, VideoCommon::Shader::AsyncShaders& async_shaders, VAddr cpu_addr) {
- const auto shader_type = GetShaderType(program_type);
-
- auto& gpu = params.gpu;
- gpu.ShaderNotify().MarkSharderBuilding();
-
- auto registry = std::make_shared<Registry>(shader_type, gpu.Maxwell3D());
- if (!async_shaders.IsShaderAsync(gpu) || !params.device.UseAsynchronousShaders()) {
- const ShaderIR ir(code, STAGE_MAIN_OFFSET, COMPILER_SETTINGS, *registry);
- // TODO(Rodrigo): Handle VertexA shaders
- // std::optional<ShaderIR> ir_b;
- // if (!code_b.empty()) {
- // ir_b.emplace(code_b, STAGE_MAIN_OFFSET);
- // }
- auto program =
- BuildShader(params.device, shader_type, params.unique_identifier, ir, *registry);
- ShaderDiskCacheEntry entry;
- entry.type = shader_type;
- entry.code = std::move(code);
- entry.code_b = std::move(code_b);
- entry.unique_identifier = params.unique_identifier;
- entry.bound_buffer = registry->GetBoundBuffer();
- entry.graphics_info = registry->GetGraphicsInfo();
- entry.keys = registry->GetKeys();
- entry.bound_samplers = registry->GetBoundSamplers();
- entry.bindless_samplers = registry->GetBindlessSamplers();
- params.disk_cache.SaveEntry(std::move(entry));
-
- gpu.ShaderNotify().MarkShaderComplete();
-
- return std::unique_ptr<Shader>(new Shader(std::move(registry),
- MakeEntries(params.device, ir, shader_type),
- std::move(program), true));
- } else {
- // Required for entries
- const ShaderIR ir(code, STAGE_MAIN_OFFSET, COMPILER_SETTINGS, *registry);
- auto entries = MakeEntries(params.device, ir, shader_type);
-
- async_shaders.QueueOpenGLShader(params.device, shader_type, params.unique_identifier,
- std::move(code), std::move(code_b), STAGE_MAIN_OFFSET,
- COMPILER_SETTINGS, *registry, cpu_addr);
-
- auto program = std::make_shared<ProgramHandle>();
- return std::unique_ptr<Shader>(
- new Shader(std::move(registry), std::move(entries), std::move(program), false));
+GraphicsPipeline* ShaderCache::BuiltPipeline(GraphicsPipeline* pipeline) const noexcept {
+ if (pipeline->IsBuilt()) {
+ return pipeline;
}
-}
-
-std::unique_ptr<Shader> Shader::CreateKernelFromMemory(const ShaderParameters& params,
- ProgramCode code) {
- auto& gpu = params.gpu;
- gpu.ShaderNotify().MarkSharderBuilding();
-
- auto registry = std::make_shared<Registry>(ShaderType::Compute, params.engine);
- const ShaderIR ir(code, KERNEL_MAIN_OFFSET, COMPILER_SETTINGS, *registry);
- const u64 uid = params.unique_identifier;
- auto program = BuildShader(params.device, ShaderType::Compute, uid, ir, *registry);
-
- ShaderDiskCacheEntry entry;
- entry.type = ShaderType::Compute;
- entry.code = std::move(code);
- entry.unique_identifier = uid;
- entry.bound_buffer = registry->GetBoundBuffer();
- entry.compute_info = registry->GetComputeInfo();
- entry.keys = registry->GetKeys();
- entry.bound_samplers = registry->GetBoundSamplers();
- entry.bindless_samplers = registry->GetBindlessSamplers();
- params.disk_cache.SaveEntry(std::move(entry));
-
- gpu.ShaderNotify().MarkShaderComplete();
-
- return std::unique_ptr<Shader>(new Shader(std::move(registry),
- MakeEntries(params.device, ir, ShaderType::Compute),
- std::move(program)));
-}
-
-std::unique_ptr<Shader> Shader::CreateFromCache(const ShaderParameters& params,
- const PrecompiledShader& precompiled_shader) {
- return std::unique_ptr<Shader>(new Shader(
- precompiled_shader.registry, precompiled_shader.entries, precompiled_shader.program));
-}
-
-ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer_,
- Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
- Tegra::Engines::Maxwell3D& maxwell3d_,
- Tegra::Engines::KeplerCompute& kepler_compute_,
- Tegra::MemoryManager& gpu_memory_, const Device& device_)
- : ShaderCache{rasterizer_}, emu_window{emu_window_}, gpu{gpu_}, gpu_memory{gpu_memory_},
- maxwell3d{maxwell3d_}, kepler_compute{kepler_compute_}, device{device_} {}
-
-ShaderCacheOpenGL::~ShaderCacheOpenGL() = default;
-
-void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, std::stop_token stop_loading,
- const VideoCore::DiskResourceLoadCallback& callback) {
- disk_cache.BindTitleID(title_id);
- const std::optional transferable = disk_cache.LoadTransferable();
-
- LOG_INFO(Render_OpenGL, "Total Shader Count: {}",
- transferable.has_value() ? transferable->size() : 0);
-
- if (!transferable) {
- return;
+ if (!use_asynchronous_shaders) {
+ return pipeline;
}
-
- std::vector<ShaderDiskCachePrecompiled> gl_cache;
- if (!device.UseAssemblyShaders() && !device.UseDriverCache()) {
- // Only load precompiled cache when we are not using assembly shaders
- gl_cache = disk_cache.LoadPrecompiled();
+ // If something is using depth, we can assume that games are not rendering anything which
+ // will be used one time.
+ if (maxwell3d.regs.zeta_enable) {
+ return nullptr;
}
- const auto supported_formats = GetSupportedFormats();
-
- // Track if precompiled cache was altered during loading to know if we have to
- // serialize the virtual precompiled cache file back to the hard drive
- bool precompiled_cache_altered = false;
-
- // Inform the frontend about shader build initialization
- if (callback) {
- callback(VideoCore::LoadCallbackStage::Build, 0, transferable->size());
+ // If games are using a small index count, we can assume these are full screen quads.
+ // Usually these shaders are only used once for building textures so we can assume they
+ // can't be built async
+ if (maxwell3d.regs.index_array.count <= 6 || maxwell3d.regs.vertex_buffer.count <= 6) {
+ return pipeline;
}
+ return nullptr;
+}
- std::mutex mutex;
- std::size_t built_shaders = 0; // It doesn't have be atomic since it's used behind a mutex
- std::atomic_bool gl_cache_failed = false;
-
- const auto find_precompiled = [&gl_cache](u64 id) {
- return std::ranges::find(gl_cache, id, &ShaderDiskCachePrecompiled::unique_identifier);
- };
-
- const auto worker = [&](Core::Frontend::GraphicsContext* context, std::size_t begin,
- std::size_t end) {
- const auto scope = context->Acquire();
-
- for (std::size_t i = begin; i < end; ++i) {
- if (stop_loading.stop_requested()) {
- return;
- }
- const auto& entry = (*transferable)[i];
- const u64 uid = entry.unique_identifier;
- const auto it = find_precompiled(uid);
- const auto precompiled_entry = it != gl_cache.end() ? &*it : nullptr;
-
- const bool is_compute = entry.type == ShaderType::Compute;
- const u32 main_offset = is_compute ? KERNEL_MAIN_OFFSET : STAGE_MAIN_OFFSET;
- auto registry = MakeRegistry(entry);
- const ShaderIR ir(entry.code, main_offset, COMPILER_SETTINGS, *registry);
-
- ProgramSharedPtr program;
- if (precompiled_entry) {
- // If the shader is precompiled, attempt to load it with
- program = GeneratePrecompiledProgram(entry, *precompiled_entry, supported_formats);
- if (!program) {
- gl_cache_failed = true;
- }
- }
- if (!program) {
- // Otherwise compile it from GLSL
- program = BuildShader(device, entry.type, uid, ir, *registry, true);
- }
-
- PrecompiledShader shader;
- shader.program = std::move(program);
- shader.registry = std::move(registry);
- shader.entries = MakeEntries(device, ir, entry.type);
-
- std::scoped_lock lock{mutex};
- if (callback) {
- callback(VideoCore::LoadCallbackStage::Build, ++built_shaders,
- transferable->size());
- }
- runtime_cache.emplace(entry.unique_identifier, std::move(shader));
- }
- };
-
- const std::size_t num_workers{std::max(1U, std::thread::hardware_concurrency())};
- const std::size_t bucket_size{transferable->size() / num_workers};
- std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> contexts(num_workers);
- std::vector<std::thread> threads(num_workers);
- for (std::size_t i = 0; i < num_workers; ++i) {
- const bool is_last_worker = i + 1 == num_workers;
- const std::size_t start{bucket_size * i};
- const std::size_t end{is_last_worker ? transferable->size() : start + bucket_size};
-
- // On some platforms the shared context has to be created from the GUI thread
- contexts[i] = emu_window.CreateSharedContext();
- threads[i] = std::thread(worker, contexts[i].get(), start, end);
+ComputePipeline* ShaderCache::CurrentComputePipeline() {
+ const VideoCommon::ShaderInfo* const shader{ComputeShader()};
+ if (!shader) {
+ return nullptr;
}
- for (auto& thread : threads) {
- thread.join();
+ const auto& qmd{kepler_compute.launch_description};
+ const ComputePipelineKey key{
+ .unique_hash = shader->unique_hash,
+ .shared_memory_size = qmd.shared_alloc,
+ .workgroup_size{qmd.block_dim_x, qmd.block_dim_y, qmd.block_dim_z},
+ };
+ const auto [pair, is_new]{compute_cache.try_emplace(key)};
+ auto& pipeline{pair->second};
+ if (!is_new) {
+ return pipeline.get();
}
+ pipeline = CreateComputePipeline(key, shader);
+ return pipeline.get();
+}
- if (gl_cache_failed) {
- // Invalidate the precompiled cache if a shader dumped shader was rejected
- disk_cache.InvalidatePrecompiled();
- precompiled_cache_altered = true;
- return;
- }
- if (stop_loading.stop_requested()) {
- return;
- }
+std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline() {
+ GraphicsEnvironments environments;
+ GetGraphicsEnvironments(environments, graphics_key.unique_hashes);
- if (device.UseAssemblyShaders() || device.UseDriverCache()) {
- // Don't store precompiled binaries for assembly shaders or when using the driver cache
- return;
+ main_pools.ReleaseContents();
+ auto pipeline{CreateGraphicsPipeline(main_pools, graphics_key, environments.Span(),
+ use_asynchronous_shaders)};
+ if (!pipeline || shader_cache_filename.empty()) {
+ return pipeline;
}
-
- // TODO(Rodrigo): Do state tracking for transferable shaders and do a dummy draw
- // before precompiling them
-
- for (std::size_t i = 0; i < transferable->size(); ++i) {
- const u64 id = (*transferable)[i].unique_identifier;
- const auto it = find_precompiled(id);
- if (it == gl_cache.end()) {
- const GLuint program = runtime_cache.at(id).program->source_program.handle;
- disk_cache.SavePrecompiled(id, program);
- precompiled_cache_altered = true;
+ boost::container::static_vector<const GenericEnvironment*, Maxwell::MaxShaderProgram> env_ptrs;
+ for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
+ if (graphics_key.unique_hashes[index] != 0) {
+ env_ptrs.push_back(&environments.envs[index]);
}
}
-
- if (precompiled_cache_altered) {
- disk_cache.SaveVirtualPrecompiledFile();
- }
-}
-
-ProgramSharedPtr ShaderCacheOpenGL::GeneratePrecompiledProgram(
- const ShaderDiskCacheEntry& entry, const ShaderDiskCachePrecompiled& precompiled_entry,
- const std::unordered_set<GLenum>& supported_formats) {
- if (!supported_formats.contains(precompiled_entry.binary_format)) {
- LOG_INFO(Render_OpenGL, "Precompiled cache entry with unsupported format, removing");
- return {};
- }
-
- auto program = std::make_shared<ProgramHandle>();
- GLuint& handle = program->source_program.handle;
- handle = glCreateProgram();
- glProgramParameteri(handle, GL_PROGRAM_SEPARABLE, GL_TRUE);
- glProgramBinary(handle, precompiled_entry.binary_format, precompiled_entry.binary.data(),
- static_cast<GLsizei>(precompiled_entry.binary.size()));
-
- GLint link_status;
- glGetProgramiv(handle, GL_LINK_STATUS, &link_status);
- if (link_status == GL_FALSE) {
- LOG_INFO(Render_OpenGL, "Precompiled cache rejected by the driver, removing");
- return {};
- }
-
- return program;
+ SerializePipeline(graphics_key, env_ptrs, shader_cache_filename, CACHE_VERSION);
+ return pipeline;
}
-Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program,
- VideoCommon::Shader::AsyncShaders& async_shaders) {
- if (!maxwell3d.dirty.flags[Dirty::Shaders]) {
- auto* last_shader = last_shaders[static_cast<std::size_t>(program)];
- if (last_shader->IsBuilt()) {
- return last_shader;
+std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(
+ ShaderContext::ShaderPools& pools, const GraphicsPipelineKey& key,
+ std::span<Shader::Environment* const> envs, bool build_in_parallel) try {
+ LOG_INFO(Render_OpenGL, "0x{:016x}", key.Hash());
+ size_t env_index{};
+ u32 total_storage_buffers{};
+ std::array<Shader::IR::Program, Maxwell::MaxShaderProgram> programs;
+ const bool uses_vertex_a{key.unique_hashes[0] != 0};
+ const bool uses_vertex_b{key.unique_hashes[1] != 0};
+ for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
+ if (key.unique_hashes[index] == 0) {
+ continue;
}
- }
+ Shader::Environment& env{*envs[env_index]};
+ ++env_index;
- const GPUVAddr address{GetShaderAddress(maxwell3d, program)};
+ const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))};
+ Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0);
+ if (!uses_vertex_a || index != 1) {
+ // Normal path
+ programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info);
- if (device.UseAsynchronousShaders() && async_shaders.HasCompletedWork()) {
- auto completed_work = async_shaders.GetCompletedWork();
- for (auto& work : completed_work) {
- Shader* shader = TryGet(work.cpu_address);
- gpu.ShaderNotify().MarkShaderComplete();
- if (shader == nullptr) {
- continue;
+ for (const auto& desc : programs[index].info.storage_buffers_descriptors) {
+ total_storage_buffers += desc.count;
}
- using namespace VideoCommon::Shader;
- if (work.backend == AsyncShaders::Backend::OpenGL) {
- shader->AsyncOpenGLBuilt(std::move(work.program.opengl));
- } else if (work.backend == AsyncShaders::Backend::GLASM) {
- shader->AsyncGLASMBuilt(std::move(work.program.glasm));
+ } else {
+ // VertexB path when VertexA is present.
+ auto& program_va{programs[0]};
+ auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
+ for (const auto& desc : program_vb.info.storage_buffers_descriptors) {
+ total_storage_buffers += desc.count;
}
-
- auto& registry = shader->GetRegistry();
-
- ShaderDiskCacheEntry entry;
- entry.type = work.shader_type;
- entry.code = std::move(work.code);
- entry.code_b = std::move(work.code_b);
- entry.unique_identifier = work.uid;
- entry.bound_buffer = registry.GetBoundBuffer();
- entry.graphics_info = registry.GetGraphicsInfo();
- entry.keys = registry.GetKeys();
- entry.bound_samplers = registry.GetBoundSamplers();
- entry.bindless_samplers = registry.GetBindlessSamplers();
- disk_cache.SaveEntry(std::move(entry));
+ programs[index] = MergeDualVertexPrograms(program_va, program_vb, env);
}
}
-
- // Look up shader in the cache based on address
- const std::optional<VAddr> cpu_addr{gpu_memory.GpuToCpuAddress(address)};
- if (Shader* const shader{cpu_addr ? TryGet(*cpu_addr) : null_shader.get()}) {
- return last_shaders[static_cast<std::size_t>(program)] = shader;
- }
-
- const u8* const host_ptr{gpu_memory.GetPointer(address)};
-
- // No shader found - create a new one
- ProgramCode code{GetShaderCode(gpu_memory, address, host_ptr, false)};
- ProgramCode code_b;
- if (program == Maxwell::ShaderProgram::VertexA) {
- const GPUVAddr address_b{GetShaderAddress(maxwell3d, Maxwell::ShaderProgram::VertexB)};
- const u8* host_ptr_b = gpu_memory.GetPointer(address_b);
- code_b = GetShaderCode(gpu_memory, address_b, host_ptr_b, false);
- }
- const std::size_t code_size = code.size() * sizeof(u64);
-
- const u64 unique_identifier = GetUniqueIdentifier(
- GetShaderType(program), program == Maxwell::ShaderProgram::VertexA, code, code_b);
-
- const ShaderParameters params{gpu, maxwell3d, disk_cache, device,
- *cpu_addr, host_ptr, unique_identifier};
-
- std::unique_ptr<Shader> shader;
- const auto found = runtime_cache.find(unique_identifier);
- if (found == runtime_cache.end()) {
- shader = Shader::CreateStageFromMemory(params, program, std::move(code), std::move(code_b),
- async_shaders, cpu_addr.value_or(0));
- } else {
- shader = Shader::CreateFromCache(params, found->second);
- }
-
- Shader* const result = shader.get();
- if (cpu_addr) {
- Register(std::move(shader), *cpu_addr, code_size);
- } else {
- null_shader = std::move(shader);
+ const u32 glasm_storage_buffer_limit{device.GetMaxGLASMStorageBufferBlocks()};
+ const bool glasm_use_storage_buffers{total_storage_buffers <= glasm_storage_buffer_limit};
+
+ std::array<const Shader::Info*, Maxwell::MaxShaderStage> infos{};
+
+ OGLProgram source_program;
+ std::array<std::string, 5> sources;
+ std::array<std::vector<u32>, 5> sources_spirv;
+ Shader::Backend::Bindings binding;
+ Shader::IR::Program* previous_program{};
+ const bool use_glasm{device.UseAssemblyShaders()};
+ const size_t first_index = uses_vertex_a && uses_vertex_b ? 1 : 0;
+ for (size_t index = first_index; index < Maxwell::MaxShaderProgram; ++index) {
+ if (key.unique_hashes[index] == 0) {
+ continue;
+ }
+ UNIMPLEMENTED_IF(index == 0);
+
+ Shader::IR::Program& program{programs[index]};
+ const size_t stage_index{index - 1};
+ infos[stage_index] = &program.info;
+
+ const auto runtime_info{
+ MakeRuntimeInfo(key, program, previous_program, glasm_use_storage_buffers, use_glasm)};
+ switch (device.GetShaderBackend()) {
+ case Settings::ShaderBackend::GLSL:
+ sources[stage_index] = EmitGLSL(profile, runtime_info, program, binding);
+ break;
+ case Settings::ShaderBackend::GLASM:
+ sources[stage_index] = EmitGLASM(profile, runtime_info, program, binding);
+ break;
+ case Settings::ShaderBackend::SPIRV:
+ sources_spirv[stage_index] = EmitSPIRV(profile, runtime_info, program, binding);
+ break;
+ }
+ previous_program = &program;
}
+ auto* const thread_worker{build_in_parallel ? workers.get() : nullptr};
+ return std::make_unique<GraphicsPipeline>(
+ device, texture_cache, buffer_cache, gpu_memory, maxwell3d, program_manager, state_tracker,
+ thread_worker, &shader_notify, sources, sources_spirv, infos, key);
- return last_shaders[static_cast<std::size_t>(program)] = result;
+} catch (Shader::Exception& exception) {
+ LOG_ERROR(Render_OpenGL, "{}", exception.what());
+ return nullptr;
}
-Shader* ShaderCacheOpenGL::GetComputeKernel(GPUVAddr code_addr) {
- const std::optional<VAddr> cpu_addr{gpu_memory.GpuToCpuAddress(code_addr)};
-
- if (Shader* const kernel = cpu_addr ? TryGet(*cpu_addr) : null_kernel.get()) {
- return kernel;
- }
-
- // No kernel found, create a new one
- const u8* host_ptr{gpu_memory.GetPointer(code_addr)};
- ProgramCode code{GetShaderCode(gpu_memory, code_addr, host_ptr, true)};
- const std::size_t code_size{code.size() * sizeof(u64)};
- const u64 unique_identifier{GetUniqueIdentifier(ShaderType::Compute, false, code)};
-
- const ShaderParameters params{gpu, kepler_compute, disk_cache, device,
- *cpu_addr, host_ptr, unique_identifier};
+std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
+ const ComputePipelineKey& key, const VideoCommon::ShaderInfo* shader) {
+ const GPUVAddr program_base{kepler_compute.regs.code_loc.Address()};
+ const auto& qmd{kepler_compute.launch_description};
+ ComputeEnvironment env{kepler_compute, gpu_memory, program_base, qmd.program_start};
+ env.SetCachedSize(shader->size_bytes);
+
+ main_pools.ReleaseContents();
+ auto pipeline{CreateComputePipeline(main_pools, key, env)};
+ if (!pipeline || shader_cache_filename.empty()) {
+ return pipeline;
+ }
+ SerializePipeline(key, std::array<const GenericEnvironment*, 1>{&env}, shader_cache_filename,
+ CACHE_VERSION);
+ return pipeline;
+}
- std::unique_ptr<Shader> kernel;
- const auto found = runtime_cache.find(unique_identifier);
- if (found == runtime_cache.end()) {
- kernel = Shader::CreateKernelFromMemory(params, std::move(code));
- } else {
- kernel = Shader::CreateFromCache(params, found->second);
- }
+std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
+ ShaderContext::ShaderPools& pools, const ComputePipelineKey& key,
+ Shader::Environment& env) try {
+ LOG_INFO(Render_OpenGL, "0x{:016x}", key.Hash());
+
+ Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()};
+ auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
+
+ u32 num_storage_buffers{};
+ for (const auto& desc : program.info.storage_buffers_descriptors) {
+ num_storage_buffers += desc.count;
+ }
+ Shader::RuntimeInfo info;
+ info.glasm_use_storage_buffers = num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks();
+
+ std::string code{};
+ std::vector<u32> code_spirv;
+ switch (device.GetShaderBackend()) {
+ case Settings::ShaderBackend::GLSL:
+ code = EmitGLSL(profile, program);
+ break;
+ case Settings::ShaderBackend::GLASM:
+ code = EmitGLASM(profile, info, program);
+ break;
+ case Settings::ShaderBackend::SPIRV:
+ code_spirv = EmitSPIRV(profile, program);
+ break;
+ }
+
+ return std::make_unique<ComputePipeline>(device, texture_cache, buffer_cache, gpu_memory,
+ kepler_compute, program_manager, program.info, code,
+ code_spirv);
+} catch (Shader::Exception& exception) {
+ LOG_ERROR(Render_OpenGL, "{}", exception.what());
+ return nullptr;
+}
- Shader* const result = kernel.get();
- if (cpu_addr) {
- Register(std::move(kernel), *cpu_addr, code_size);
- } else {
- null_kernel = std::move(kernel);
- }
- return result;
+std::unique_ptr<ShaderWorker> ShaderCache::CreateWorkers() const {
+ return std::make_unique<ShaderWorker>(std::max(std::thread::hardware_concurrency(), 2U) - 1,
+ "yuzu:ShaderBuilder",
+ [this] { return Context{emu_window}; });
}
} // namespace OpenGL