video_core: vulkan: Add disk shader cache (#1725)

This commit is contained in:
PabloMK7
2026-02-16 15:59:22 +01:00
committed by GitHub
parent 91abe7f7d0
commit 304db9173b
43 changed files with 3134 additions and 547 deletions
+5
View File
@@ -11,6 +11,10 @@ set(HASH_FILES
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.h"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.h"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_disk_cache.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_disk_cache.h"
"${VIDEO_CORE}/renderer_vulkan/vk_pipeline_cache.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_pipeline_cache.h"
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.cpp"
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.h"
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.cpp"
@@ -19,6 +23,7 @@ set(HASH_FILES
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.h"
"${VIDEO_CORE}/shader/generator/pica_fs_config.cpp"
"${VIDEO_CORE}/shader/generator/pica_fs_config.h"
"${VIDEO_CORE}/shader/generator/profile.h"
"${VIDEO_CORE}/shader/generator/shader_gen.cpp"
"${VIDEO_CORE}/shader/generator/shader_gen.h"
"${VIDEO_CORE}/shader/generator/shader_uniforms.cpp"
@@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -23,7 +23,7 @@ object DiskShaderCacheProgress {
}
@JvmStatic
fun loadProgress(stage: LoadCallbackStage, progress: Int, max: Int) {
fun loadProgress(stage: LoadCallbackStage, progress: Int, max: Int, obj: String) {
val emulationActivity = NativeLibrary.sEmulationActivity.get()
if (emulationActivity == null) {
Log.error("[DiskShaderCacheProgress] EmulationActivity not present")
@@ -40,7 +40,7 @@ object DiskShaderCacheProgress {
)
LoadCallbackStage.Build -> emulationViewModel.updateProgress(
emulationActivity.getString(R.string.building_shaders),
emulationActivity.getString(R.string.building_shaders, obj ),
progress,
max
)
+4 -3
View File
@@ -207,9 +207,10 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
env->NewGlobalRef(env->FindClass("org/citra/citra_emu/utils/DiskShaderCacheProgress")));
jclass load_callback_stage_class =
env->FindClass("org/citra/citra_emu/utils/DiskShaderCacheProgress$LoadCallbackStage");
s_disk_cache_load_progress = env->GetStaticMethodID(
s_disk_cache_progress_class, "loadProgress",
"(Lorg/citra/citra_emu/utils/DiskShaderCacheProgress$LoadCallbackStage;II)V");
s_disk_cache_load_progress =
env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress",
"(Lorg/citra/citra_emu/utils/"
"DiskShaderCacheProgress$LoadCallbackStage;IILjava/lang/String;)V");
s_compress_progress_method =
env->GetStaticMethodID(s_native_library_class, "onCompressProgress", "(JJ)V");
// Initialize LoadCallbackStage map
+6 -5
View File
@@ -130,12 +130,13 @@ static bool HandleCoreError(Core::System::ResultStatus result, const std::string
env->NewStringUTF(details.c_str())) != JNI_FALSE;
}
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) {
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max,
const std::string& object) {
JNIEnv* env = IDCache::GetEnvForThread();
env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(),
IDCache::GetDiskCacheLoadProgress(),
IDCache::GetJavaLoadCallbackStage(stage), static_cast<jint>(progress),
static_cast<jint>(max));
static_cast<jint>(max), env->NewStringUTF(object.c_str()));
}
static Camera::NDK::Factory* g_ndk_factory{};
@@ -217,7 +218,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
true, shared_context);
#elif ENABLE_VULKAN
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surface, vulkan_library);
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surface, vulkan_library, false);
secondary_window =
std::make_unique<EmuWindow_Android_Vulkan>(s_secondary_surface, vulkan_library, true);
#else
@@ -267,7 +268,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
stop_run = false;
pause_emulation = false;
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0, "");
system.GPU().ApplyPerProgramSettings(program_id);
@@ -275,7 +276,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
system.GPU().Renderer().Rasterizer()->LoadDefaultDiskResources(stop_run,
&LoadDiskCacheProgress);
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0, "");
SCOPE_EXIT({ TryShutdown(); });
@@ -571,7 +571,7 @@
<!-- Disk Shader Cache -->
<string name="preparing_shaders">Preparing Shaders</string>
<string name="building_shaders">Building Shaders</string>
<string name="building_shaders">Building %s</string>
<!-- About Game Dialog -->
<string name="play">Play</string>
+12 -10
View File
@@ -66,29 +66,31 @@ void EmuThread::run() {
const auto scope = core_context.Acquire();
if (Settings::values.custom_textures && Settings::values.preload_textures) {
emit LoadProgress(VideoCore::LoadCallbackStage::Preload, 0, 0);
emit LoadProgress(VideoCore::LoadCallbackStage::Preload, 0, 0, "");
system.CustomTexManager().PreloadTextures(
stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value,
std::size_t total) { emit LoadProgress(stage, value, total); });
stop_run,
[this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total,
const std::string& object) { emit LoadProgress(stage, value, total, object); });
}
system.GPU().Renderer().Rasterizer()->SetSwitchDiskResourcesCallback(
[this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
emit SwitchDiskResources(stage, value, total);
[this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total,
const std::string& object) {
emit SwitchDiskResources(stage, value, total, object);
});
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0, "");
u64 program_id{};
system.GetAppLoader().ReadProgramId(program_id);
system.GPU().ApplyPerProgramSettings(program_id);
system.GPU().Renderer().Rasterizer()->LoadDefaultDiskResources(
stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
emit LoadProgress(stage, value, total);
});
stop_run,
[this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total,
const std::string& object) { emit LoadProgress(stage, value, total, object); });
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0, "");
core_context.MakeCurrent();
+3 -2
View File
@@ -108,10 +108,11 @@ signals:
void ErrorThrown(Core::System::ResultStatus, std::string);
void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total,
const std::string& object);
void SwitchDiskResources(VideoCore::LoadCallbackStage stage, std::size_t value,
std::size_t total);
std::size_t total, const std::string& object);
void HideLoadingScreen();
};
+3 -2
View File
@@ -4093,14 +4093,15 @@ void GMainWindow::OnEmulatorUpdateAvailable() {
#endif
void GMainWindow::OnSwitchDiskResources(VideoCore::LoadCallbackStage stage, std::size_t value,
std::size_t total) {
std::size_t total, const std::string& object) {
if (stage == VideoCore::LoadCallbackStage::Prepare) {
loading_shaders_label->setText(QString());
loading_shaders_label->setVisible(true);
} else if (stage == VideoCore::LoadCallbackStage::Complete) {
loading_shaders_label->setVisible(false);
} else {
loading_shaders_label->setText(loading_screen->GetStageTranslation(stage, value, total));
loading_shaders_label->setText(
loading_screen->GetStageTranslation(stage, value, total, object));
}
}
+1 -1
View File
@@ -311,7 +311,7 @@ private slots:
void OnEmulatorUpdateAvailable();
#endif
void OnSwitchDiskResources(VideoCore::LoadCallbackStage stage, std::size_t value,
std::size_t total);
std::size_t total, const std::string& object);
#ifdef ENABLE_DEVELOPER_OPTIONS
void StartLaunchStressTest(const QString& game_path);
#endif
+51
View File
@@ -33,6 +33,8 @@
#include "citra_qt/game_list_p.h"
#include "citra_qt/game_list_worker.h"
#include "citra_qt/uisettings.h"
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
@@ -606,6 +608,36 @@ void ForEachOpenGLCacheFile(u64 program_id, auto func) {
QFile file{QString::fromStdString(path)};
func(file);
}
const std::string path =
fmt::format("{}opengl/transferable/{:016X}.bin",
FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir), program_id);
QFile file{QString::fromStdString(path)};
func(file);
}
#endif
#ifdef ENABLE_VULKAN
void ForEachVulkanCacheFile(u64 program_id, auto func) {
for (const std::string_view cache_type : {"vs", "fs", "gs", "pl"}) {
const std::string path = fmt::format("{}vulkan/transferable/{:016X}_{}.vkch",
FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir),
program_id, cache_type);
QFile file{QString::fromStdString(path)};
func(file);
}
FileUtil::ForeachDirectoryEntry(
nullptr,
fmt::format("{}vulkan/pipeline", FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)),
[program_id, &func]([[maybe_unused]] u64* num_entries_out, const std::string& directory,
const std::string& virtual_name) {
if (virtual_name.starts_with(fmt::format("{:016X}", program_id))) {
QFile file{QString::fromStdString(directory + DIR_SEP + virtual_name)};
func(file);
}
return true;
});
}
#endif
@@ -642,6 +674,10 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr
QAction* delete_opengl_disk_shader_cache =
shader_menu->addAction(tr("Delete OpenGL Shader Cache"));
#endif
#ifdef ENABLE_VULKAN
QAction* delete_vulkan_disk_shader_cache =
shader_menu->addAction(tr("Delete Vulkan Shader Cache"));
#endif
QMenu* uninstall_menu = context_menu.addMenu(tr("Uninstall"));
QAction* uninstall_all = uninstall_menu->addAction(tr("Everything"));
@@ -678,6 +714,12 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr
program_id, [&opengl_cache_exists](QFile& file) { opengl_cache_exists |= file.exists(); });
#endif
#ifdef ENABLE_VULKAN
bool vulkan_cache_exists = false;
ForEachVulkanCacheFile(
program_id, [&vulkan_cache_exists](QFile& file) { vulkan_cache_exists |= file.exists(); });
#endif
favorite->setVisible(program_id != 0);
favorite->setCheckable(true);
favorite->setChecked(UISettings::values.favorited_ids.contains(program_id));
@@ -721,6 +763,10 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr
delete_opengl_disk_shader_cache->setEnabled(opengl_cache_exists);
#endif
#ifdef ENABLE_VULKAN
delete_vulkan_disk_shader_cache->setEnabled(vulkan_cache_exists);
#endif
uninstall_all->setEnabled(is_installed || has_update || has_dlc);
uninstall_game->setEnabled(is_installed);
uninstall_update->setEnabled(has_update);
@@ -800,6 +846,11 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr
connect(delete_opengl_disk_shader_cache, &QAction::triggered, this, [program_id] {
ForEachOpenGLCacheFile(program_id, [](QFile& file) { file.remove(); });
});
#endif
#ifdef ENABLE_VULKAN
connect(delete_vulkan_disk_shader_cache, &QAction::triggered, this, [program_id] {
ForEachVulkanCacheFile(program_id, [](QFile& file) { file.remove(); });
});
#endif
connect(uninstall_all, &QAction::triggered, this, [=, this] {
QMessageBox::StandardButton answer = QMessageBox::question(
+9 -9
View File
@@ -68,8 +68,7 @@ const static std::unordered_map<VideoCore::LoadCallbackStage, const char*> stage
QT_TRANSLATE_NOOP("LoadingScreen", "Preloading Textures %1 / %2")},
{VideoCore::LoadCallbackStage::Decompile,
QT_TRANSLATE_NOOP("LoadingScreen", "Preparing Shaders %1 / %2")},
{VideoCore::LoadCallbackStage::Build,
QT_TRANSLATE_NOOP("LoadingScreen", "Loading Shaders %1 / %2")},
{VideoCore::LoadCallbackStage::Build, QT_TRANSLATE_NOOP("LoadingScreen", "Loading %3 %1 / %2")},
{VideoCore::LoadCallbackStage::Complete, QT_TRANSLATE_NOOP("LoadingScreen", "Launching...")},
};
const static std::unordered_map<VideoCore::LoadCallbackStage, const char*> progressbar_style{
@@ -131,7 +130,7 @@ void LoadingScreen::Prepare(Loader::AppLoader& loader) {
}
ui->title->setText(tr("Now Loading\n%1").arg(QString::fromStdString(title)));
eta_shown = false;
OnLoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
OnLoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0, "");
}
void LoadingScreen::OnLoadComplete() {
@@ -139,7 +138,7 @@ void LoadingScreen::OnLoadComplete() {
}
void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value,
std::size_t total) {
std::size_t total, const std::string& object) {
using namespace std::chrono;
const auto now = high_resolution_clock::now();
// reset the timer if the stage changes
@@ -184,7 +183,7 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size
}
// update labels and progress bar
ui->stage->setText(GetStageTranslation(stage, value, total));
ui->stage->setText(GetStageTranslation(stage, value, total, object));
ui->value->setText(estimate);
ui->progress_bar->setValue(static_cast<int>(value));
previous_time = now;
@@ -199,11 +198,12 @@ void LoadingScreen::paintEvent(QPaintEvent* event) {
}
QString LoadingScreen::GetStageTranslation(VideoCore::LoadCallbackStage stage, std::size_t value,
std::size_t total) {
std::size_t total, const std::string& object) {
const auto& stg = tr(stage_translations.at(stage));
if (stage == VideoCore::LoadCallbackStage::Decompile ||
stage == VideoCore::LoadCallbackStage::Build ||
stage == VideoCore::LoadCallbackStage::Preload) {
if (stage == VideoCore::LoadCallbackStage::Build) {
return stg.arg(value).arg(total).arg(QString::fromStdString(object));
} else if (stage == VideoCore::LoadCallbackStage::Decompile ||
stage == VideoCore::LoadCallbackStage::Preload) {
return stg.arg(value).arg(total);
} else {
return stg;
+5 -3
View File
@@ -40,7 +40,8 @@ public:
void Clear();
/// Slot used to update the status of the progress bar
void OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
void OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total,
const std::string& object);
/// Hides the LoadingScreen with a fade out effect
void OnLoadComplete();
@@ -50,10 +51,11 @@ public:
void paintEvent(QPaintEvent* event) override;
QString GetStageTranslation(VideoCore::LoadCallbackStage stage, std::size_t value,
std::size_t total);
std::size_t total, const std::string& object = "");
signals:
void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total,
const std::string& object);
/// Signals that this widget is completely hidden now and should be replaced with the other
/// widget
void Hidden();
+5
View File
@@ -19,6 +19,10 @@ add_custom_command(OUTPUT scm_rev.cpp
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h"
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.cpp"
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.h"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_disk_cache.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_disk_cache.h"
"${VIDEO_CORE}/renderer_vulkan/vk_pipeline_cache.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_pipeline_cache.h"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.h"
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.cpp"
@@ -29,6 +33,7 @@ add_custom_command(OUTPUT scm_rev.cpp
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.h"
"${VIDEO_CORE}/shader/generator/pica_fs_config.cpp"
"${VIDEO_CORE}/shader/generator/pica_fs_config.h"
"${VIDEO_CORE}/shader/generator/profile.h"
"${VIDEO_CORE}/shader/generator/shader_gen.cpp"
"${VIDEO_CORE}/shader/generator/shader_gen.h"
"${VIDEO_CORE}/shader/generator/shader_uniforms.cpp"
+10 -4
View File
@@ -54,8 +54,14 @@ static inline u64 ComputeStructHash64(const T& data) noexcept {
* Combines the seed parameter with the provided hash, producing a new unique hash
* Implementation from: http://boost.sourceforge.net/doc/html/boost/hash_combine.html
*/
[[nodiscard]] inline u64 HashCombine(const u64 seed, const u64 hash) {
return seed ^ (hash + 0x9e3779b9 + (seed << 6) + (seed >> 2));
[[nodiscard]] constexpr u64 HashCombine(u64 seed) {
return seed;
}
template <typename... Ts>
[[nodiscard]] constexpr u64 HashCombine(u64 seed, u64 hash, Ts... rest) {
seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return HashCombine(seed, rest...);
}
template <typename T>
@@ -95,7 +101,7 @@ struct HashableStruct {
return !(*this == o);
};
std::size_t Hash() const noexcept {
u64 Hash() const noexcept {
return Common::ComputeStructHash64<T, Hasher>(state);
}
};
@@ -109,7 +115,7 @@ struct HashableString {
HashableString(const std::string& s) : value(s) {}
HashableString(std::string&& s) noexcept : value(std::move(s)) {}
std::size_t Hash() const noexcept {
u64 Hash() const noexcept {
return ComputeHash64<Hasher>(value.data(), value.size());
}
+5 -2
View File
@@ -53,9 +53,12 @@ std::vector<u8> CompressDataZSTDDefault(std::span<const u8> source) {
return CompressDataZSTD(source, ZSTD_CLEVEL_DEFAULT);
}
std::size_t GetDecompressedSize(std::span<const u8> compressed) {
return ZSTD_getFrameContentSize(compressed.data(), compressed.size());
}
std::vector<u8> DecompressDataZSTD(std::span<const u8> compressed) {
const std::size_t decompressed_size =
ZSTD_getFrameContentSize(compressed.data(), compressed.size());
const std::size_t decompressed_size = GetDecompressedSize(compressed);
if (decompressed_size == ZSTD_CONTENTSIZE_UNKNOWN) {
LOG_ERROR(Common, "ZSTD decompressed size could not be determined.");
+9
View File
@@ -40,6 +40,15 @@ namespace Common::Compression {
*/
[[nodiscard]] std::vector<u8> CompressDataZSTDDefault(std::span<const u8> source);
/**
* Gets the decompressed size of the specified Zstandard compressed memory region.
*
* @param compressed the compressed source memory region.
*
* @return the size of the decompressed data.
*/
[[nodiscard]] std::size_t GetDecompressedSize(std::span<const u8> compressed);
/**
* Decompresses a source memory region with Zstandard and returns the uncompressed data in a vector.
*
+2
View File
@@ -189,6 +189,8 @@ if (ENABLE_VULKAN)
renderer_vulkan/vk_present_window.h
renderer_vulkan/vk_render_manager.cpp
renderer_vulkan/vk_render_manager.h
renderer_vulkan/vk_shader_disk_cache.cpp
renderer_vulkan/vk_shader_disk_cache.h
renderer_vulkan/vk_shader_util.cpp
renderer_vulkan/vk_shader_util.h
renderer_vulkan/vk_stream_buffer.cpp
@@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -228,7 +228,8 @@ void CustomTexManager::PreloadTextures(const std::atomic_bool& stop_run,
material->LoadFromDisk(flip_png_files);
size_sum += material->size;
if (callback) {
callback(VideoCore::LoadCallbackStage::Preload, preloaded, custom_textures.size());
callback(VideoCore::LoadCallbackStage::Preload, preloaded, custom_textures.size(),
"");
}
preloaded++;
}
+12 -4
View File
@@ -94,9 +94,9 @@ public:
}
}
void UpdateProgramCode(const ProgramCode& other) {
void UpdateProgramCode(const ProgramCode& other, u32 other_size = MAX_PROGRAM_CODE_LENGTH) {
program_code = other;
biggest_program_size = program_code.size();
biggest_program_size = std::max(biggest_program_size, other_size);
MakeProgramCodeDirty();
}
@@ -116,9 +116,9 @@ public:
}
}
void UpdateSwizzleData(const SwizzleData& other) {
void UpdateSwizzleData(const SwizzleData& other, u32 other_size = MAX_SWIZZLE_DATA_LENGTH) {
swizzle_data = other;
biggest_swizzle_size = swizzle_data.size();
biggest_swizzle_size = std::max(biggest_swizzle_size, other_size);
MakeSwizzleDataDirty();
}
@@ -130,6 +130,14 @@ public:
return swizzle_data;
}
u32 GetBiggestProgramSize() const {
return biggest_program_size;
}
u32 GetBiggestSwizzleSize() const {
return biggest_swizzle_size;
}
public:
Uniforms uniforms;
PackedAttribute uniform_queue;
+2 -1
View File
@@ -26,7 +26,8 @@ enum class LoadCallbackStage {
Build,
Complete,
};
using DiskResourceLoadCallback = std::function<void(LoadCallbackStage, std::size_t, std::size_t)>;
using DiskResourceLoadCallback =
std::function<void(LoadCallbackStage, std::size_t, std::size_t, const std::string&)>;
class RasterizerInterface {
public:
@@ -211,14 +211,14 @@ void RasterizerOpenGL::SwitchDiskResources(u64 title_id) {
render_window, driver, title_id, !driver.IsOpenGLES()));
if (switch_disk_resources_callback) {
switch_disk_resources_callback(VideoCore::LoadCallbackStage::Prepare, 0, 0);
switch_disk_resources_callback(VideoCore::LoadCallbackStage::Prepare, 0, 0, "");
}
std::atomic_bool stop_loading;
new_manager->LoadDiskCache(stop_loading, switch_disk_resources_callback, accurate_mul);
if (switch_disk_resources_callback) {
switch_disk_resources_callback(VideoCore::LoadCallbackStage::Complete, 0, 0);
switch_disk_resources_callback(VideoCore::LoadCallbackStage::Complete, 0, 0, "");
}
}
@@ -9,6 +9,7 @@
#include <thread>
#include <unordered_map>
#include <variant>
#include "common/hash.h"
#include "common/settings.h"
#include "core/frontend/emu_window.h"
#include "video_core/pica/shader_setup.h"
@@ -92,12 +93,7 @@ static std::tuple<PicaVSConfig, Pica::ShaderSetup> BuildVSConfigFromRaw(
setup.UpdateProgramCode(program_code);
setup.UpdateSwizzleData(swizzle_data);
// Enable the geometry-shader only if we are actually doing per-fragment lighting
// and care about proper quaternions. Otherwise just use standard vertex+fragment shaders
const bool use_geometry_shader = !raw.GetRawShaderConfig().lighting.disable;
return {PicaVSConfig{raw.GetRawShaderConfig(), setup, driver.HasClipCullDistance(),
use_geometry_shader, accurate_mul},
setup};
return {PicaVSConfig{raw.GetRawShaderConfig(), setup}, setup};
}
/**
@@ -163,31 +159,31 @@ public:
~ShaderCache() = default;
template <typename... Args>
std::tuple<GLuint, std::optional<std::string>> Get(const KeyConfigType& config,
Args&&... args) {
auto [iter, new_shader] = shaders.emplace(config, OGLShaderStage{separable});
std::tuple<u64, GLuint, std::optional<std::string>> Get(const KeyConfigType& config,
Args&&... args) {
auto [iter, new_shader] = shaders.emplace(config.Hash(), OGLShaderStage{separable});
OGLShaderStage& cached_shader = iter->second;
std::optional<std::string> result{};
if (new_shader) {
result = CodeGenerator(config, args...);
cached_shader.Create(result->c_str(), ShaderType);
}
return {cached_shader.GetHandle(), std::move(result)};
return {iter->first, cached_shader.GetHandle(), std::move(result)};
}
void Inject(const KeyConfigType& key, OGLProgram&& program) {
OGLShaderStage stage{separable};
stage.Inject(std::move(program));
shaders.emplace(key, std::move(stage));
shaders.emplace(key.Hash(), std::move(stage));
}
void Inject(const KeyConfigType& key, OGLShaderStage&& stage) {
shaders.emplace(key, std::move(stage));
shaders.emplace(key.Hash(), std::move(stage));
}
private:
bool separable;
std::unordered_map<KeyConfigType, OGLShaderStage> shaders;
std::unordered_map<u64, OGLShaderStage> shaders;
};
// This is a cache designed for shaders translated from PICA shaders. The first cache matches the
@@ -195,62 +191,68 @@ private:
// GLSL code. The configuration is like this because there might be leftover code in the PICA shader
// program buffer from the previous shader, which is hashed into the config, resulting several
// different config values from the same shader program.
template <typename KeyConfigType,
std::string (*CodeGenerator)(const Pica::ShaderSetup&, const KeyConfigType&, bool),
template <typename KeyConfigType, typename ExtraConfigType,
std::string (*CodeGenerator)(const Pica::ShaderSetup&, const KeyConfigType&,
const ExtraConfigType&),
GLenum ShaderType>
class ShaderDoubleCache {
public:
explicit ShaderDoubleCache(bool separable) : separable(separable) {}
std::tuple<GLuint, std::optional<std::string>> Get(const KeyConfigType& key,
const Pica::ShaderSetup& setup) {
explicit ShaderDoubleCache(bool _separable) : separable{_separable} {}
std::tuple<u64, GLuint, std::optional<std::string>> Get(const KeyConfigType& key,
const ExtraConfigType& extra,
const Pica::ShaderSetup& setup) {
std::optional<std::string> result{};
auto map_it = shader_map.find(key);
const size_t key_hash = key.Hash();
auto map_it = shader_map.find(key_hash);
if (map_it == shader_map.end()) {
auto program = CodeGenerator(setup, key, separable);
auto program = Common::HashableString(CodeGenerator(setup, key, extra));
if (program.empty()) {
shader_map[key] = nullptr;
return {0, std::nullopt};
shader_map[key_hash] = nullptr;
return {0, 0, std::nullopt};
}
auto [iter, new_shader] = shader_cache.emplace(program, OGLShaderStage{separable});
auto [iter, new_shader] =
shader_cache.emplace(program.Hash(), OGLShaderStage{separable});
OGLShaderStage& cached_shader = iter->second;
if (new_shader) {
result = program;
cached_shader.Create(program.c_str(), ShaderType);
result = std::move(program);
cached_shader.Create((*result).c_str(), ShaderType);
}
shader_map[key] = &cached_shader;
return {cached_shader.GetHandle(), std::move(result)};
shader_map[key_hash] = &cached_shader;
return {key_hash, cached_shader.GetHandle(), std::move(result)};
}
if (map_it->second == nullptr) {
return {0, std::nullopt};
return {0, 0, std::nullopt};
}
return {map_it->second->GetHandle(), std::nullopt};
return {key_hash, map_it->second->GetHandle(), std::nullopt};
}
void Inject(const KeyConfigType& key, std::string decomp, OGLProgram&& program) {
OGLShaderStage stage{separable};
stage.Inject(std::move(program));
const auto iter = shader_cache.emplace(std::move(decomp), std::move(stage)).first;
auto decomp_hash = Common::HashableString(std::move(decomp));
const auto iter = shader_cache.emplace(decomp_hash.Hash(), std::move(stage)).first;
OGLShaderStage& cached_shader = iter->second;
shader_map.insert_or_assign(key, &cached_shader);
shader_map.insert_or_assign(key.Hash(), &cached_shader);
}
void Inject(const KeyConfigType& key, std::string decomp, OGLShaderStage&& stage) {
const auto iter = shader_cache.emplace(std::move(decomp), std::move(stage)).first;
auto decomp_hash = Common::HashableString(std::move(decomp));
const auto iter = shader_cache.emplace(decomp_hash.Hash(), std::move(stage)).first;
OGLShaderStage& cached_shader = iter->second;
shader_map.insert_or_assign(key, &cached_shader);
shader_map.insert_or_assign(key.Hash(), &cached_shader);
}
private:
bool separable;
std::unordered_map<KeyConfigType, OGLShaderStage*> shader_map;
std::unordered_map<std::string, OGLShaderStage> shader_cache;
std::unordered_map<u64, OGLShaderStage*> shader_map;
std::unordered_map<u64, OGLShaderStage> shader_cache;
};
using ProgrammableVertexShaders =
ShaderDoubleCache<PicaVSConfig, &GLSL::GenerateVertexShader, GL_VERTEX_SHADER>;
ShaderDoubleCache<PicaVSConfig, ExtraVSConfig, &GLSL::GenerateVertexShader, GL_VERTEX_SHADER>;
using FixedGeometryShaders =
ShaderCache<PicaFixedGSConfig, &GLSL::GenerateFixedGeometryShader, GL_GEOMETRY_SHADER>;
@@ -326,6 +328,23 @@ public:
std::unordered_map<u64, OGLProgram> program_cache;
OGLPipeline pipeline;
ShaderDiskCache disk_cache;
Pica::Shader::Generator::ExtraVSConfig CalcExtraConfig(
const Pica::Shader::Generator::PicaVSConfig& config, bool accurate_mul) {
auto res = ExtraVSConfig();
// Enable the geometry-shader only if we are actually doing per-fragment lighting
// and care about proper quaternions. Otherwise just use standard vertex+fragment shaders.
const bool use_geometry_shader = !config.state.lighting_disable;
res.use_clip_planes = profile.has_clip_planes;
res.use_geometry_shader = use_geometry_shader;
res.sanitize_mul = accurate_mul;
res.separable_shader = separable;
res.load_flags.fill(AttribLoadFlags::Float);
return res;
}
};
ShaderProgramManager::ShaderProgramManager(Frontend::EmuWindow& emu_window_, const Driver& driver_,
@@ -339,17 +358,15 @@ ShaderProgramManager::~ShaderProgramManager() = default;
bool ShaderProgramManager::UseProgrammableVertexShader(const Pica::RegsInternal& regs,
Pica::ShaderSetup& setup,
bool accurate_mul) {
// Enable the geometry-shader only if we are actually doing per-fragment lighting
// and care about proper quaternions. Otherwise just use standard vertex+fragment shaders
const bool use_geometry_shader = !regs.lighting.disable;
PicaVSConfig config{regs, setup, driver.HasClipCullDistance(), use_geometry_shader,
accurate_mul};
auto [handle, result] = impl->programmable_vertex_shaders.Get(config, setup);
PicaVSConfig config{regs, setup};
ExtraVSConfig extra = impl->CalcExtraConfig(config, accurate_mul);
auto [hash, handle, result] = impl->programmable_vertex_shaders.Get(config, extra, setup);
if (handle == 0)
return false;
impl->current.vs = handle;
impl->current.vs_hash = config.Hash();
impl->current.vs_hash = hash;
// Save VS to the disk cache if its a new shader
if (result) {
@@ -373,10 +390,15 @@ void ShaderProgramManager::UseTrivialVertexShader() {
}
void ShaderProgramManager::UseFixedGeometryShader(const Pica::RegsInternal& regs) {
PicaFixedGSConfig gs_config(regs, driver.HasClipCullDistance());
auto [handle, _] = impl->fixed_geometry_shaders.Get(gs_config, impl->separable);
PicaFixedGSConfig gs_config(regs);
ExtraFixedGSConfig extra{
.use_clip_planes = driver.HasClipCullDistance(),
.separable_shader = impl->separable,
};
auto [hash, handle, _] = impl->fixed_geometry_shaders.Get(gs_config, extra);
impl->current.gs = handle;
impl->current.gs_hash = gs_config.Hash();
impl->current.gs_hash = hash;
}
void ShaderProgramManager::UseTrivialGeometryShader() {
@@ -386,10 +408,10 @@ void ShaderProgramManager::UseTrivialGeometryShader() {
void ShaderProgramManager::UseFragmentShader(const Pica::RegsInternal& regs,
const Pica::Shader::UserConfig& user) {
const FSConfig fs_config{regs, user, impl->profile};
auto [handle, result] = impl->fragment_shaders.Get(fs_config, impl->profile);
const FSConfig fs_config{regs};
auto [hash, handle, result] = impl->fragment_shaders.Get(fs_config, user, impl->profile);
impl->current.fs = handle;
impl->current.fs_hash = fs_config.Hash();
impl->current.fs_hash = hash;
// Save FS to the disk cache if its a new shader
if (result) {
auto& disk_cache = impl->disk_cache;
@@ -457,7 +479,7 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
std::mutex mutex;
std::atomic_bool compilation_failed = false;
if (callback) {
callback(VideoCore::LoadCallbackStage::Decompile, 0, raws.size());
callback(VideoCore::LoadCallbackStage::Decompile, 0, raws.size(), "");
}
std::vector<std::size_t> load_raws_index;
// Loads both decompiled and precompiled shaders from the cache. If either one is missing for
@@ -513,7 +535,7 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
std::move(shader));
} else if (raw.GetProgramType() == ProgramType::FS) {
// TODO: Support UserConfig in disk shader cache
const FSConfig conf(raw.GetRawShaderConfig(), {}, impl->profile);
const FSConfig conf(raw.GetRawShaderConfig());
std::scoped_lock lock(mutex);
impl->fragment_shaders.Inject(conf, std::move(shader));
} else {
@@ -530,7 +552,7 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
load_raws_index.push_back(i);
}
if (callback) {
callback(VideoCore::LoadCallbackStage::Decompile, i, raw_cache.size());
callback(VideoCore::LoadCallbackStage::Decompile, i, raw_cache.size(), "");
}
}
};
@@ -561,7 +583,7 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
break;
}
if (callback) {
callback(VideoCore::LoadCallbackStage::Decompile, ++i, dump_map.size());
callback(VideoCore::LoadCallbackStage::Decompile, ++i, dump_map.size(), "");
}
}
};
@@ -590,7 +612,7 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
const std::size_t load_raws_size = load_all_raws ? raws.size() : load_raws_index.size();
if (callback) {
callback(VideoCore::LoadCallbackStage::Build, 0, load_raws_size);
callback(VideoCore::LoadCallbackStage::Build, 0, load_raws_size, "Shader");
}
compilation_failed = false;
@@ -615,17 +637,18 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
// precompiled file
if (raw.GetProgramType() == ProgramType::VS) {
auto [conf, setup] = BuildVSConfigFromRaw(raw, driver, accurate_mul);
code = GLSL::GenerateVertexShader(setup, conf, impl->separable);
ExtraVSConfig extra = impl->CalcExtraConfig(conf, accurate_mul);
code = GLSL::GenerateVertexShader(setup, conf, extra);
OGLShaderStage stage{impl->separable};
stage.Create(code.c_str(), GL_VERTEX_SHADER);
handle = stage.GetHandle();
sanitize_mul = conf.state.sanitize_mul;
sanitize_mul = accurate_mul;
std::scoped_lock lock(mutex);
impl->programmable_vertex_shaders.Inject(conf, code, std::move(stage));
} else if (raw.GetProgramType() == ProgramType::FS) {
// TODO: Support UserConfig in disk shader cache
const FSConfig fs_config{raw.GetRawShaderConfig(), {}, impl->profile};
code = GLSL::GenerateFragmentShader(fs_config, impl->profile);
const FSConfig fs_config{raw.GetRawShaderConfig()};
code = GLSL::GenerateFragmentShader(fs_config, {}, impl->profile);
OGLShaderStage stage{impl->separable};
stage.Create(code.c_str(), GL_FRAGMENT_SHADER);
handle = stage.GetHandle();
@@ -653,7 +676,8 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
}
if (callback) {
callback(VideoCore::LoadCallbackStage::Build, ++built_shaders, load_raws_size);
callback(VideoCore::LoadCallbackStage::Build, ++built_shaders, load_raws_size,
"Shader");
}
}
};
@@ -4,6 +4,7 @@
#include <boost/container/static_vector.hpp>
#include "common/alignment.h"
#include "common/hash.h"
#include "common/microprofile.h"
#include "video_core/renderer_vulkan/pica_to_vk.h"
@@ -31,25 +32,32 @@ vk::ShaderStageFlagBits MakeShaderStage(std::size_t index) {
return vk::ShaderStageFlagBits::eVertex;
}
u64 PipelineInfo::Hash(const Instance& instance) const {
u64 info_hash = 0;
const auto append_hash = [&info_hash](const auto& data) {
const u64 data_hash = Common::ComputeStructHash64(data);
info_hash = Common::HashCombine(info_hash, data_hash);
};
append_hash(vertex_layout);
append_hash(attachments);
append_hash(blending);
u64 StaticPipelineInfo::OptimizedHash(const Instance& instance) const {
u64 info_hash = Common::HashCombine(
shader_ids[0], shader_ids[1], shader_ids[2], Common::ComputeStructHash64(vertex_layout),
Common::ComputeStructHash64(attachments), Common::ComputeStructHash64(blending));
if (!instance.IsExtendedDynamicStateSupported()) {
append_hash(rasterization);
append_hash(depth_stencil);
info_hash = Common::HashCombine(info_hash, Common::ComputeStructHash64(rasterization),
Common::ComputeStructHash64(depth_stencil));
}
return info_hash;
}
u16 PipelineInfo::GetFinalColorWriteMask(const Instance& instance) {
u16 color_write_mask = state.blending.color_write_mask;
const bool is_logic_op_emulated =
instance.NeedsLogicOpEmulation() && !state.blending.blend_enable;
const bool is_logic_op_noop = state.blending.logic_op == Pica::FramebufferRegs::LogicOp::NoOp;
if (is_logic_op_emulated && is_logic_op_noop) {
// Color output is disabled by logic operation. We use color write mask to skip
// color but allow depth write.
color_write_mask = 0;
}
return color_write_mask;
}
Shader::Shader(const Instance& instance) : device{instance.GetDevice()} {}
Shader::Shader(const Instance& instance, vk::ShaderStageFlagBits stage, std::string code)
@@ -100,20 +108,22 @@ bool GraphicsPipeline::TryBuild(bool wait_built) {
bool GraphicsPipeline::Build(bool fail_on_compile_required) {
MICROPROFILE_SCOPE(Vulkan_Pipeline);
const u32 stride_alignment = instance.GetMinVertexStrideAlignment();
std::array<vk::VertexInputBindingDescription, MAX_VERTEX_BINDINGS> bindings;
for (u32 i = 0; i < info.vertex_layout.binding_count; i++) {
const auto& binding = info.vertex_layout.bindings[i];
for (u32 i = 0; i < info.state.vertex_layout.binding_count; i++) {
const auto& binding = info.state.vertex_layout.bindings[i];
bindings[i] = vk::VertexInputBindingDescription{
.binding = binding.binding,
.stride = binding.stride,
.stride = Common::AlignUp(binding.byte_count.Value(), stride_alignment),
.inputRate = binding.fixed.Value() ? vk::VertexInputRate::eInstance
: vk::VertexInputRate::eVertex,
};
}
std::array<vk::VertexInputAttributeDescription, MAX_VERTEX_ATTRIBUTES> attributes;
for (u32 i = 0; i < info.vertex_layout.attribute_count; i++) {
const auto& attr = info.vertex_layout.attributes[i];
for (u32 i = 0; i < info.state.vertex_layout.attribute_count; i++) {
const auto& attr = info.state.vertex_layout.attributes[i];
const FormatTraits& traits = instance.GetTraits(attr.type, attr.size);
attributes[i] = vk::VertexInputAttributeDescription{
.location = attr.location,
@@ -131,23 +141,23 @@ bool GraphicsPipeline::Build(bool fail_on_compile_required) {
}
const vk::PipelineVertexInputStateCreateInfo vertex_input_info = {
.vertexBindingDescriptionCount = info.vertex_layout.binding_count,
.vertexBindingDescriptionCount = info.state.vertex_layout.binding_count,
.pVertexBindingDescriptions = bindings.data(),
.vertexAttributeDescriptionCount = info.vertex_layout.attribute_count,
.vertexAttributeDescriptionCount = info.state.vertex_layout.attribute_count,
.pVertexAttributeDescriptions = attributes.data(),
};
const vk::PipelineInputAssemblyStateCreateInfo input_assembly = {
.topology = PicaToVK::PrimitiveTopology(info.rasterization.topology),
.topology = PicaToVK::PrimitiveTopology(info.state.rasterization.topology),
.primitiveRestartEnable = false,
};
const vk::PipelineRasterizationStateCreateInfo raster_state = {
.depthClampEnable = false,
.rasterizerDiscardEnable = false,
.cullMode =
PicaToVK::CullMode(info.rasterization.cull_mode, info.rasterization.flip_viewport),
.frontFace = PicaToVK::FrontFace(info.rasterization.cull_mode),
.cullMode = PicaToVK::CullMode(info.state.rasterization.cull_mode,
info.state.rasterization.flip_viewport),
.frontFace = PicaToVK::FrontFace(info.state.rasterization.cull_mode),
.depthBiasEnable = false,
.lineWidth = 1.0f,
};
@@ -158,19 +168,20 @@ bool GraphicsPipeline::Build(bool fail_on_compile_required) {
};
const vk::PipelineColorBlendAttachmentState colorblend_attachment = {
.blendEnable = info.blending.blend_enable,
.srcColorBlendFactor = PicaToVK::BlendFunc(info.blending.src_color_blend_factor),
.dstColorBlendFactor = PicaToVK::BlendFunc(info.blending.dst_color_blend_factor),
.colorBlendOp = PicaToVK::BlendEquation(info.blending.color_blend_eq),
.srcAlphaBlendFactor = PicaToVK::BlendFunc(info.blending.src_alpha_blend_factor),
.dstAlphaBlendFactor = PicaToVK::BlendFunc(info.blending.dst_alpha_blend_factor),
.alphaBlendOp = PicaToVK::BlendEquation(info.blending.alpha_blend_eq),
.colorWriteMask = static_cast<vk::ColorComponentFlags>(info.blending.color_write_mask),
.blendEnable = info.state.blending.blend_enable,
.srcColorBlendFactor = PicaToVK::BlendFunc(info.state.blending.src_color_blend_factor),
.dstColorBlendFactor = PicaToVK::BlendFunc(info.state.blending.dst_color_blend_factor),
.colorBlendOp = PicaToVK::BlendEquation(info.state.blending.color_blend_eq),
.srcAlphaBlendFactor = PicaToVK::BlendFunc(info.state.blending.src_alpha_blend_factor),
.dstAlphaBlendFactor = PicaToVK::BlendFunc(info.state.blending.dst_alpha_blend_factor),
.alphaBlendOp = PicaToVK::BlendEquation(info.state.blending.alpha_blend_eq),
.colorWriteMask =
static_cast<vk::ColorComponentFlags>(info.GetFinalColorWriteMask(instance)),
};
const vk::PipelineColorBlendStateCreateInfo color_blending = {
.logicOpEnable = !info.blending.blend_enable && !instance.NeedsLogicOpEmulation(),
.logicOp = PicaToVK::LogicOp(info.blending.logic_op),
.logicOpEnable = !info.state.blending.blend_enable && !instance.NeedsLogicOpEmulation(),
.logicOp = PicaToVK::LogicOp(info.state.blending.logic_op),
.attachmentCount = 1,
.pAttachments = &colorblend_attachment,
.blendConstants = std::array{1.0f, 1.0f, 1.0f, 1.0f},
@@ -219,18 +230,18 @@ bool GraphicsPipeline::Build(bool fail_on_compile_required) {
};
const vk::StencilOpState stencil_op_state = {
.failOp = PicaToVK::StencilOp(info.depth_stencil.stencil_fail_op),
.passOp = PicaToVK::StencilOp(info.depth_stencil.stencil_pass_op),
.depthFailOp = PicaToVK::StencilOp(info.depth_stencil.stencil_depth_fail_op),
.compareOp = PicaToVK::CompareFunc(info.depth_stencil.stencil_compare_op),
.failOp = PicaToVK::StencilOp(info.state.depth_stencil.stencil_fail_op),
.passOp = PicaToVK::StencilOp(info.state.depth_stencil.stencil_pass_op),
.depthFailOp = PicaToVK::StencilOp(info.state.depth_stencil.stencil_depth_fail_op),
.compareOp = PicaToVK::CompareFunc(info.state.depth_stencil.stencil_compare_op),
};
const vk::PipelineDepthStencilStateCreateInfo depth_info = {
.depthTestEnable = static_cast<u32>(info.depth_stencil.depth_test_enable.Value()),
.depthWriteEnable = static_cast<u32>(info.depth_stencil.depth_write_enable.Value()),
.depthCompareOp = PicaToVK::CompareFunc(info.depth_stencil.depth_compare_op),
.depthTestEnable = static_cast<u32>(info.state.depth_stencil.depth_test_enable.Value()),
.depthWriteEnable = static_cast<u32>(info.state.depth_stencil.depth_write_enable.Value()),
.depthCompareOp = PicaToVK::CompareFunc(info.state.depth_stencil.depth_compare_op),
.depthBoundsTestEnable = false,
.stencilTestEnable = static_cast<u32>(info.depth_stencil.stencil_test_enable.Value()),
.stencilTestEnable = static_cast<u32>(info.state.depth_stencil.stencil_test_enable.Value()),
.front = stencil_op_state,
.back = stencil_op_state,
};
@@ -263,8 +274,8 @@ bool GraphicsPipeline::Build(bool fail_on_compile_required) {
.pColorBlendState = &color_blending,
.pDynamicState = &dynamic_info,
.layout = pipeline_layout,
.renderPass =
renderpass_cache.GetRenderpass(info.attachments.color, info.attachments.depth, false),
.renderPass = renderpass_cache.GetRenderpass(info.state.attachments.color,
info.state.attachments.depth, false),
};
if (fail_on_compile_required) {
@@ -2,12 +2,18 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/hash.h"
#include "common/thread_worker.h"
#include "video_core/pica/regs_pipeline.h"
#include "video_core/pica/regs_rasterizer.h"
#include "video_core/rasterizer_cache/pixel_format.h"
#include "video_core/renderer_vulkan/vk_common.h"
#define LAYOUT_HASH static_cast<u64>(sizeof(T)), static_cast<u64>(alignof(T))
#define FIELD_HASH(x) static_cast<u64>(offsetof(T, x)), static_cast<u64>(sizeof(x))
namespace Common {
struct AsyncHandle {
@@ -51,14 +57,29 @@ constexpr u32 MAX_VERTEX_BINDINGS = 13;
* the overhead of hashing as much as possible
*/
union RasterizationState {
u8 value = 0;
u8 value;
BitField<0, 2, Pica::PipelineRegs::TriangleTopology> topology;
BitField<4, 2, Pica::RasterizerRegs::CullMode> cull_mode;
BitField<6, 1, u8> flip_viewport;
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = RasterizationState;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(topology), FIELD_HASH(cull_mode),
FIELD_HASH(flip_viewport));
}
};
static_assert(std::is_trivial_v<RasterizationState>);
union DepthStencilState {
u32 value = 0;
u32 value;
BitField<0, 1, u32> depth_test_enable;
BitField<1, 1, u32> depth_write_enable;
BitField<2, 1, u32> stencil_test_enable;
@@ -67,14 +88,32 @@ union DepthStencilState {
BitField<9, 3, Pica::FramebufferRegs::StencilAction> stencil_pass_op;
BitField<12, 3, Pica::FramebufferRegs::StencilAction> stencil_depth_fail_op;
BitField<15, 3, Pica::FramebufferRegs::CompareFunc> stencil_compare_op;
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = DepthStencilState;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(depth_test_enable), FIELD_HASH(depth_write_enable),
FIELD_HASH(stencil_test_enable), FIELD_HASH(depth_compare_op),
FIELD_HASH(stencil_fail_op), FIELD_HASH(stencil_pass_op),
FIELD_HASH(stencil_depth_fail_op),
FIELD_HASH(stencil_compare_op));
}
};
static_assert(std::is_trivial_v<DepthStencilState>);
struct BlendingState {
u16 blend_enable;
u16 color_write_mask;
Pica::FramebufferRegs::LogicOp logic_op;
union {
u32 value = 0;
u32 value;
BitField<0, 4, Pica::FramebufferRegs::BlendFactor> src_color_blend_factor;
BitField<4, 4, Pica::FramebufferRegs::BlendFactor> dst_color_blend_factor;
BitField<8, 3, Pica::FramebufferRegs::BlendEquation> color_blend_eq;
@@ -82,9 +121,149 @@ struct BlendingState {
BitField<15, 4, Pica::FramebufferRegs::BlendFactor> dst_alpha_blend_factor;
BitField<19, 3, Pica::FramebufferRegs::BlendEquation> alpha_blend_eq;
};
};
struct DynamicState {
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = BlendingState;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(blend_enable), FIELD_HASH(color_write_mask),
FIELD_HASH(logic_op), FIELD_HASH(src_color_blend_factor),
FIELD_HASH(dst_color_blend_factor), FIELD_HASH(color_blend_eq),
FIELD_HASH(src_alpha_blend_factor),
FIELD_HASH(dst_alpha_blend_factor), FIELD_HASH(alpha_blend_eq));
}
};
static_assert(std::is_trivial_v<BlendingState>);
union VertexBinding {
u16 value;
BitField<0, 4, u16> binding;
BitField<4, 1, u16> fixed;
BitField<5, 11, u16> byte_count;
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = VertexBinding;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(binding), FIELD_HASH(fixed), FIELD_HASH(byte_count));
}
};
static_assert(std::is_trivial_v<VertexBinding>);
union VertexAttribute {
u32 value;
BitField<0, 4, u32> binding;
BitField<4, 4, u32> location;
BitField<8, 3, Pica::PipelineRegs::VertexAttributeFormat> type;
BitField<11, 3, u32> size;
BitField<14, 11, u32> offset;
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = VertexAttribute;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(binding), FIELD_HASH(location), FIELD_HASH(type),
FIELD_HASH(size), FIELD_HASH(offset));
}
};
static_assert(std::is_trivial_v<VertexAttribute>);
struct VertexLayout {
u8 binding_count;
u8 attribute_count;
std::array<VertexBinding, MAX_VERTEX_BINDINGS> bindings;
std::array<VertexAttribute, MAX_VERTEX_ATTRIBUTES> attributes;
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = VertexLayout;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(binding_count), FIELD_HASH(attribute_count),
FIELD_HASH(bindings), FIELD_HASH(attributes),
// nested layout
VertexBinding::StructHash(), VertexAttribute::StructHash());
}
};
static_assert(std::is_trivial_v<VertexLayout>);
struct AttachmentInfo {
VideoCore::PixelFormat color;
VideoCore::PixelFormat depth;
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = AttachmentInfo;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(color), FIELD_HASH(depth));
}
};
static_assert(std::is_trivial_v<AttachmentInfo>);
struct StaticPipelineInfo {
std::array<u64, MAX_SHADER_STAGES> shader_ids;
BlendingState blending;
AttachmentInfo attachments;
VertexLayout vertex_layout;
RasterizationState rasterization;
DepthStencilState depth_stencil;
[[nodiscard]] u64 OptimizedHash(const Instance& instance) const;
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = StaticPipelineInfo;
return Common::HashCombine(
STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(shader_ids), FIELD_HASH(blending), FIELD_HASH(attachments),
FIELD_HASH(vertex_layout), FIELD_HASH(rasterization), FIELD_HASH(depth_stencil),
// nested layout
BlendingState::StructHash(), AttachmentInfo::StructHash(), VertexLayout::StructHash(),
RasterizationState::StructHash(), DepthStencilState::StructHash());
}
};
static_assert(std::is_trivial_v<StaticPipelineInfo>);
struct DynamicPipelineInfo {
u32 blend_color = 0;
u8 stencil_reference;
u8 stencil_compare_mask;
@@ -93,61 +272,28 @@ struct DynamicState {
Common::Rectangle<u32> scissor;
Common::Rectangle<s32> viewport;
bool operator==(const DynamicState& other) const noexcept {
return std::memcmp(this, &other, sizeof(DynamicState)) == 0;
bool operator==(const DynamicPipelineInfo& other) const noexcept {
return std::memcmp(this, &other, sizeof(DynamicPipelineInfo)) == 0;
}
};
union VertexBinding {
u16 value = 0;
BitField<0, 4, u16> binding;
BitField<4, 1, u16> fixed;
BitField<5, 11, u16> stride;
};
union VertexAttribute {
u32 value = 0;
BitField<0, 4, u32> binding;
BitField<4, 4, u32> location;
BitField<8, 3, Pica::PipelineRegs::VertexAttributeFormat> type;
BitField<11, 3, u32> size;
BitField<14, 11, u32> offset;
};
struct VertexLayout {
u8 binding_count;
u8 attribute_count;
std::array<VertexBinding, MAX_VERTEX_BINDINGS> bindings;
std::array<VertexAttribute, MAX_VERTEX_ATTRIBUTES> attributes;
};
struct AttachmentInfo {
VideoCore::PixelFormat color;
VideoCore::PixelFormat depth;
};
/**
* Information about a graphics pipeline
*/
struct PipelineInfo {
BlendingState blending;
AttachmentInfo attachments;
RasterizationState rasterization;
DepthStencilState depth_stencil;
DynamicState dynamic;
VertexLayout vertex_layout;
[[nodiscard]] u64 Hash(const Instance& instance) const;
struct PipelineInfo : Common::HashableStruct<StaticPipelineInfo> {
DynamicPipelineInfo dynamic_info;
[[nodiscard]] bool IsDepthWriteEnabled() const noexcept {
const bool has_stencil = attachments.depth == VideoCore::PixelFormat::D24S8;
const bool has_stencil = state.attachments.depth == VideoCore::PixelFormat::D24S8;
const bool depth_write =
depth_stencil.depth_test_enable && depth_stencil.depth_write_enable;
const bool stencil_write =
has_stencil && depth_stencil.stencil_test_enable && dynamic.stencil_write_mask != 0;
state.depth_stencil.depth_test_enable && state.depth_stencil.depth_write_enable;
const bool stencil_write = has_stencil && state.depth_stencil.stencil_test_enable &&
dynamic_info.stencil_write_mask != 0;
return depth_write || stencil_write;
}
[[nodiscard]] u16 GetFinalColorWriteMask(const Instance& instance);
};
struct Shader : public Common::AsyncHandle {
@@ -195,3 +341,6 @@ private:
};
} // namespace Vulkan
#undef LAYOUT_HASH
#undef FIELD_HASH
+7 -1
View File
@@ -30,8 +30,10 @@ struct FormatTraits {
bool needs_conversion = false;
bool needs_emulation = false;
vk::ImageUsageFlags usage{};
vk::ImageAspectFlags aspect;
vk::ImageAspectFlags aspect{};
vk::Format native = vk::Format::eUndefined;
auto operator<=>(const FormatTraits&) const = default;
};
class Instance {
@@ -48,6 +50,10 @@ public:
const FormatTraits& GetTraits(Pica::PipelineRegs::VertexAttributeFormat format,
u32 count) const;
const std::array<FormatTraits, 16>& GetAllTraits() const {
return attrib_table;
}
/// Returns a formatted string for the driver version
std::string GetDriverVersionName();
@@ -95,6 +95,7 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
GLSL::GenerateTrivialVertexShader(instance.IsShaderClipDistanceSupported(), true)} {
scheduler.RegisterOnDispatch([this] { update_queue.Flush(); });
profile = Pica::Shader::Profile{
.enable_accurate_mul = false,
.has_separable_shaders = true,
.has_clip_planes = instance.IsShaderClipDistanceSupported(),
.has_geometry_shader = instance.UseGeometryShaders(),
@@ -104,8 +105,26 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
.has_blend_minmax_factor = false,
.has_minus_one_to_one_range = false,
.has_logic_op = !instance.NeedsLogicOpEmulation(),
.vk_disable_spirv_optimizer = Settings::values.disable_spirv_optimizer.GetValue(),
.vk_use_spirv_generator = Settings::values.spirv_shader_gen.GetValue(),
.is_vulkan = true,
};
const auto& traits = instance.GetAllTraits();
size_t i = 0;
for (const auto& it : traits) {
profile.vk_format_traits[i].transfer_support = it.transfer_support;
profile.vk_format_traits[i].blit_support = it.blit_support;
profile.vk_format_traits[i].attachment_support = it.attachment_support;
profile.vk_format_traits[i].storage_support = it.storage_support;
profile.vk_format_traits[i].needs_conversion = it.needs_conversion;
profile.vk_format_traits[i].needs_emulation = it.needs_emulation;
profile.vk_format_traits[i].usage_flags = static_cast<u32>(it.usage);
profile.vk_format_traits[i].aspect_flags = static_cast<u32>(it.aspect);
profile.vk_format_traits[i].native_format = static_cast<u32>(it.native);
++i;
}
BuildLayout();
}
@@ -128,18 +147,18 @@ PipelineCache::~PipelineCache() {
SaveDiskCache();
}
void PipelineCache::LoadDiskCache(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
void PipelineCache::LoadPipelineDiskCache(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
vk::PipelineCacheCreateInfo cache_info{};
if (callback) {
callback(VideoCore::LoadCallbackStage::Prepare, 0, 0);
callback(VideoCore::LoadCallbackStage::Prepare, 0, 0, "");
}
if (callback) {
callback(VideoCore::LoadCallbackStage::Build, 0, 1);
callback(VideoCore::LoadCallbackStage::Build, 0, 1, "Driver Pipeline Cache");
}
auto load_cache = [this, &cache_info, &callback](bool allow_fallback) {
auto load_cache = [this, &cache_info](bool allow_fallback) {
const vk::Device device = instance.GetDevice();
try {
pipeline_cache = device.createPipelineCacheUnique(cache_info);
@@ -157,9 +176,6 @@ void PipelineCache::LoadDiskCache(const std::atomic_bool& stop_loading,
}
}
}
if (callback) {
callback(VideoCore::LoadCallbackStage::Complete, 0, 0);
}
};
// Try to load existing pipeline cache if disk cache is enabled and directories exist
@@ -210,6 +226,16 @@ void PipelineCache::LoadDiskCache(const std::atomic_bool& stop_loading,
load_cache(true);
}
void PipelineCache::LoadDiskCache(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
disk_caches.clear();
curr_disk_cache =
disk_caches.emplace_back(std::make_shared<ShaderDiskCache>(*this, GetProgramID()));
curr_disk_cache->Init(stop_loading, callback);
}
void PipelineCache::SaveDiskCache() {
// Save Vulkan pipeline cache
if (!Settings::values.use_disk_shader_cache || !pipeline_cache) {
@@ -239,25 +265,14 @@ void PipelineCache::SaveDiskCache() {
}
}
bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) {
bool PipelineCache::BindPipeline(PipelineInfo& info, bool wait_built) {
MICROPROFILE_SCOPE(Vulkan_Bind);
u64 shader_hash = 0;
for (u32 i = 0; i < MAX_SHADER_STAGES; i++) {
shader_hash = Common::HashCombine(shader_hash, shader_hashes[i]);
info.state.shader_ids[i] = shader_hashes[i];
}
const u64 info_hash = info.Hash(instance);
const u64 pipeline_hash = Common::HashCombine(shader_hash, info_hash);
auto [it, new_pipeline] = graphics_pipelines.try_emplace(pipeline_hash);
if (new_pipeline) {
it.value() =
std::make_unique<GraphicsPipeline>(instance, renderpass_cache, info, *pipeline_cache,
*pipeline_layout, current_shaders, &workers);
}
GraphicsPipeline* const pipeline{it->second.get()};
GraphicsPipeline* const pipeline = curr_disk_cache->GetPipeline(info);
if (!pipeline->IsDone() && !pipeline->TryBuild(wait_built)) {
return false;
}
@@ -265,12 +280,12 @@ bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) {
const bool is_dirty = scheduler.IsStateDirty(StateFlags::Pipeline);
const bool pipeline_dirty = (current_pipeline != pipeline) || is_dirty;
scheduler.Record([this, is_dirty, pipeline_dirty, pipeline,
current_dynamic = current_info.dynamic, dynamic = info.dynamic,
current_dynamic = current_info.dynamic_info, dynamic = info.dynamic_info,
descriptor_sets = bound_descriptor_sets, offsets = offsets,
current_rasterization = current_info.rasterization,
current_depth_stencil = current_info.depth_stencil,
rasterization = info.rasterization,
depth_stencil = info.depth_stencil](vk::CommandBuffer cmdbuf) {
current_rasterization = current_info.state.rasterization,
current_depth_stencil = current_info.state.depth_stencil,
rasterization = info.state.rasterization,
depth_stencil = info.state.depth_stencil](vk::CommandBuffer cmdbuf) {
if (dynamic.viewport != current_dynamic.viewport || is_dirty) {
const vk::Viewport vk_viewport = {
.x = static_cast<f32>(dynamic.viewport.left),
@@ -383,67 +398,54 @@ bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) {
return true;
}
bool PipelineCache::UseProgrammableVertexShader(const Pica::RegsInternal& regs,
Pica::ShaderSetup& setup,
const VertexLayout& layout, bool accurate_mul) {
ExtraVSConfig PipelineCache::CalcExtraConfig(const PicaVSConfig& config) {
auto res = ExtraVSConfig();
// Enable the geometry-shader only if we are actually doing per-fragment lighting
// and care about proper quaternions. Otherwise just use standard vertex+fragment shaders.
// We also don't need the geometry shader if we have the barycentric extension.
const bool use_geometry_shader = instance.UseGeometryShaders() && !regs.lighting.disable &&
const bool use_geometry_shader = instance.UseGeometryShaders() &&
!config.state.lighting_disable &&
!instance.IsFragmentShaderBarycentricSupported();
PicaVSConfig config{regs, setup, instance.IsShaderClipDistanceSupported(), use_geometry_shader,
accurate_mul};
for (u32 i = 0; i < layout.attribute_count; i++) {
const VertexAttribute& attr = layout.attributes[i];
const FormatTraits& traits = instance.GetTraits(attr.type, attr.size);
const u32 location = attr.location.Value();
AttribLoadFlags& flags = config.state.load_flags[location];
res.use_clip_planes = instance.IsShaderClipDistanceSupported();
res.use_geometry_shader = use_geometry_shader;
res.sanitize_mul = profile.enable_accurate_mul;
res.separable_shader = true;
res.load_flags.fill(AttribLoadFlags::Float);
for (u32 i = 0; i < config.state.used_input_vertex_attributes; i++) {
const auto& attr = config.state.input_vertex_attributes[i];
const u32 location = attr.location;
const Pica::PipelineRegs::VertexAttributeFormat type =
static_cast<Pica::PipelineRegs::VertexAttributeFormat>(attr.type);
const FormatTraits& traits = instance.GetTraits(type, attr.size);
AttribLoadFlags& flags = res.load_flags[location];
if (traits.needs_conversion) {
flags = MakeAttribLoadFlag(attr.type);
flags = MakeAttribLoadFlag(type);
}
if (traits.needs_emulation) {
flags |= AttribLoadFlags::ZeroW;
}
}
const auto config_hash = config.Hash();
return res;
}
const auto [it, new_config] = programmable_vertex_map.try_emplace(config_hash);
if (new_config) {
auto program = Common::HashableString(GLSL::GenerateVertexShader(setup, config, true));
if (program.empty()) {
LOG_ERROR(Render_Vulkan, "Failed to retrieve programmable vertex shader");
programmable_vertex_map[config_hash] = nullptr;
return false;
}
bool PipelineCache::UseProgrammableVertexShader(const Pica::RegsInternal& regs,
Pica::ShaderSetup& setup,
const VertexLayout& layout) {
auto [iter, new_program] = programmable_vertex_cache.try_emplace(program.Hash(), instance);
auto& shader = iter->second;
auto res = curr_disk_cache->UseProgrammableVertexShader(regs, setup, layout);
if (new_program) {
shader.program = std::move(program);
const vk::Device device = instance.GetDevice();
workers.QueueWork([device, &shader] {
shader.module = Compile(shader.program, vk::ShaderStageFlagBits::eVertex, device);
shader.MarkDone();
});
}
it->second = &shader;
if (res.has_value()) {
current_shaders[ProgramType::VS] = (*res).second;
shader_hashes[ProgramType::VS] = (*res).first;
return true;
}
Shader* const shader{it->second};
if (!shader) {
LOG_ERROR(Render_Vulkan, "Failed to retrieve programmable vertex shader");
return false;
}
current_shaders[ProgramType::VS] = shader;
shader_hashes[ProgramType::VS] = config.Hash();
return true;
return false;
}
void PipelineCache::UseTrivialVertexShader() {
@@ -452,27 +454,16 @@ void PipelineCache::UseTrivialVertexShader() {
}
bool PipelineCache::UseFixedGeometryShader(const Pica::RegsInternal& regs) {
if (!instance.UseGeometryShaders()) {
UseTrivialGeometryShader();
auto res = curr_disk_cache->UseFixedGeometryShader(regs);
if (res.has_value()) {
current_shaders[ProgramType::GS] = (*res).second;
shader_hashes[ProgramType::GS] = (*res).first;
return true;
}
const PicaFixedGSConfig gs_config{regs, instance.IsShaderClipDistanceSupported()};
auto [it, new_shader] = fixed_geometry_shaders.try_emplace(gs_config.Hash(), instance);
auto& shader = it->second;
if (new_shader) {
workers.QueueWork([gs_config, device = instance.GetDevice(), &shader]() {
const auto code = GLSL::GenerateFixedGeometryShader(gs_config, true);
shader.module = Compile(code, vk::ShaderStageFlagBits::eGeometry, device);
shader.MarkDone();
});
}
current_shaders[ProgramType::GS] = &shader;
shader_hashes[ProgramType::GS] = gs_config.Hash();
return true;
return false;
}
void PipelineCache::UseTrivialGeometryShader() {
@@ -482,27 +473,13 @@ void PipelineCache::UseTrivialGeometryShader() {
void PipelineCache::UseFragmentShader(const Pica::RegsInternal& regs,
const Pica::Shader::UserConfig& user) {
const FSConfig fs_config{regs, user, profile};
const auto [it, new_shader] = fragment_shaders.try_emplace(fs_config.Hash(), instance);
auto& shader = it->second;
if (new_shader) {
workers.QueueWork([fs_config, this, &shader]() {
const bool use_spirv = Settings::values.spirv_shader_gen.GetValue();
if (use_spirv && !fs_config.UsesSpirvIncompatibleConfig()) {
const std::vector code = SPIRV::GenerateFragmentShader(fs_config, profile);
shader.module = CompileSPV(code, instance.GetDevice());
} else {
const std::string code = GLSL::GenerateFragmentShader(fs_config, profile);
shader.module =
Compile(code, vk::ShaderStageFlagBits::eFragment, instance.GetDevice());
}
shader.MarkDone();
});
auto res = curr_disk_cache->UseFragmentShader(regs, user);
if (res.has_value()) {
current_shaders[ProgramType::FS] = (*res).second;
shader_hashes[ProgramType::FS] = (*res).first;
}
current_shaders[ProgramType::FS] = &shader;
shader_hashes[ProgramType::FS] = fs_config.Hash();
}
bool PipelineCache::IsCacheValid(std::span<const u8> data) const {
@@ -558,7 +535,8 @@ bool PipelineCache::EnsureDirectories() const {
};
return create_dir(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)) &&
create_dir(GetVulkanDir()) && create_dir(GetPipelineCacheDir());
create_dir(GetVulkanDir()) && create_dir(GetPipelineCacheDir()) &&
create_dir(GetTransferableDir());
}
std::string PipelineCache::GetVulkanDir() const {
@@ -569,6 +547,10 @@ std::string PipelineCache::GetPipelineCacheDir() const {
return GetVulkanDir() + "pipeline" + DIR_SEP;
}
std::string PipelineCache::GetTransferableDir() const {
return GetVulkanDir() + DIR_SEP + "transferable";
}
void PipelineCache::SwitchPipelineCache(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
if (!Settings::values.use_disk_shader_cache || GetProgramID() == title_id) {
@@ -579,10 +561,10 @@ void PipelineCache::SwitchPipelineCache(u64 title_id, const std::atomic_bool& st
}
if (callback) {
callback(VideoCore::LoadCallbackStage::Prepare, 0, 0);
callback(VideoCore::LoadCallbackStage::Prepare, 0, 0, "");
}
if (callback) {
callback(VideoCore::LoadCallbackStage::Build, 0, 1);
callback(VideoCore::LoadCallbackStage::Build, 0, 1, "Driver Pipeline Cache");
}
// Make sure we have a valid pipeline cache before switching
@@ -603,10 +585,77 @@ void PipelineCache::SwitchPipelineCache(u64 title_id, const std::atomic_bool& st
// Update program ID and load the new pipeline cache
SetProgramID(title_id);
LoadDiskCache(stop_loading, nullptr);
LoadPipelineDiskCache(stop_loading, nullptr);
SwitchDiskCache(title_id, stop_loading, callback);
if (callback) {
callback(VideoCore::LoadCallbackStage::Complete, 0, 0);
callback(VideoCore::LoadCallbackStage::Complete, 0, 0, "");
}
}
void PipelineCache::SwitchDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
// NOTE: curr_disk_cache can be null if emulation restarted without calling
// LoadDefaultDiskResources
// Check if the current cache is for the specified TID.
if (curr_disk_cache && curr_disk_cache->GetProgramID() == title_id) {
return;
}
// Search for an existing manager
size_t new_pos = 0;
for (new_pos = 0; new_pos < disk_caches.size(); new_pos++) {
if (disk_caches[new_pos]->GetProgramID() == title_id) {
break;
}
}
// Manager does not exist, create it and append to the end
if (new_pos >= disk_caches.size()) {
new_pos = disk_caches.size();
auto& new_manager =
disk_caches.emplace_back(std::make_shared<ShaderDiskCache>(*this, title_id));
if (callback) {
callback(VideoCore::LoadCallbackStage::Prepare, 0, 0, "");
}
new_manager->Init(stop_loading, callback);
if (callback) {
callback(VideoCore::LoadCallbackStage::Complete, 0, 0, "");
}
}
auto is_applet = [](u64 tid) {
constexpr u32 APPLET_TID_HIGH = 0x00040030;
return static_cast<u32>(tid >> 32) == APPLET_TID_HIGH;
};
bool prev_applet = curr_disk_cache ? is_applet(curr_disk_cache->GetProgramID()) : false;
bool new_applet = is_applet(disk_caches[new_pos]->GetProgramID());
curr_disk_cache = disk_caches[new_pos];
if (prev_applet) {
// If we came from an applet, clean up all other applets
for (auto it = disk_caches.begin(); it != disk_caches.end();) {
if (it == disk_caches.begin() || *it == curr_disk_cache ||
!is_applet((*it)->GetProgramID())) {
it++;
continue;
}
it = disk_caches.erase(it);
}
}
if (!new_applet) {
// If we are going into a non-applet, clean up everything
for (auto it = disk_caches.begin(); it != disk_caches.end();) {
if (it == disk_caches.begin() || *it == curr_disk_cache) {
it++;
continue;
}
it = disk_caches.erase(it);
}
}
}
@@ -5,11 +5,11 @@
#pragma once
#include <bitset>
#include <tsl/robin_map.h>
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
#include "video_core/renderer_vulkan/vk_resource_pool.h"
#include "video_core/renderer_vulkan/vk_shader_disk_cache.h"
#include "video_core/shader/generator/pica_fs_config.h"
#include "video_core/shader/generator/profile.h"
#include "video_core/shader/generator/shader_gen.h"
@@ -59,6 +59,9 @@ public:
}
/// Loads the pipeline cache stored to disk
void LoadPipelineDiskCache(const std::atomic_bool& stop_loading = std::atomic_bool{false},
const VideoCore::DiskResourceLoadCallback& callback = {});
void LoadDiskCache(const std::atomic_bool& stop_loading = std::atomic_bool{false},
const VideoCore::DiskResourceLoadCallback& callback = {});
@@ -66,11 +69,14 @@ public:
void SaveDiskCache();
/// Binds a pipeline using the provided information
bool BindPipeline(const PipelineInfo& info, bool wait_built = false);
bool BindPipeline(PipelineInfo& info, bool wait_built = false);
Pica::Shader::Generator::ExtraVSConfig CalcExtraConfig(
const Pica::Shader::Generator::PicaVSConfig& config);
/// Binds a PICA decompiled vertex shader
bool UseProgrammableVertexShader(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup,
const VertexLayout& layout, bool accurate_mul);
const VertexLayout& layout);
/// Binds a passthrough vertex shader
void UseTrivialVertexShader();
@@ -98,7 +104,17 @@ public:
current_program_id = program_id;
}
void SetAccurateMul(bool _accurate_mul) {
profile.enable_accurate_mul = _accurate_mul;
}
private:
friend ShaderDiskCache;
/// Switches the disk cache at runtime to use a different title ID
void SwitchDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback);
/// Builds the rasterizer pipeline layout
void BuildLayout();
@@ -114,6 +130,9 @@ private:
/// Returns the pipeline cache storage dir
std::string GetPipelineCacheDir() const;
/// Returns the transferable shader dir
std::string GetTransferableDir() const;
private:
const Instance& instance;
Scheduler& scheduler;
@@ -127,8 +146,6 @@ private:
Common::ThreadWorker workers;
PipelineInfo current_info{};
GraphicsPipeline* current_pipeline{};
tsl::robin_map<u64, std::unique_ptr<GraphicsPipeline>, Common::IdentityHash<u64>>
graphics_pipelines;
std::array<DescriptorHeap, NumDescriptorHeaps> descriptor_heaps;
std::array<vk::DescriptorSet, NumRasterizerSets> bound_descriptor_sets{};
std::array<u32, NumDynamicOffsets> offsets{};
@@ -136,13 +153,11 @@ private:
std::array<u64, MAX_SHADER_STAGES> shader_hashes;
std::array<Shader*, MAX_SHADER_STAGES> current_shaders;
std::unordered_map<size_t, Shader*> programmable_vertex_map;
std::unordered_map<size_t, Shader> programmable_vertex_cache;
std::unordered_map<size_t, Shader> fixed_geometry_shaders;
std::unordered_map<size_t, Shader> fragment_shaders;
Shader trivial_vertex_shader;
u64 current_program_id{0};
std::vector<std::shared_ptr<ShaderDiskCache>> disk_caches;
std::shared_ptr<ShaderDiskCache> curr_disk_cache{};
};
} // namespace Vulkan
@@ -87,7 +87,7 @@ RasterizerVulkan::RasterizerVulkan(Memory::MemorySystem& memory, Pica::PicaCore&
// Define vertex layout for software shaders
MakeSoftwareVertexLayout();
pipeline_info.vertex_layout = software_layout;
pipeline_info.state.vertex_layout = software_layout;
const vk::Device device = instance.GetDevice();
texture_lf_view = device.createBufferViewUnique({
@@ -153,65 +153,63 @@ void RasterizerVulkan::LoadDefaultDiskResources(
}
pipeline_cache.SetProgramID(program_id);
pipeline_cache.SetAccurateMul(accurate_mul);
pipeline_cache.LoadPipelineDiskCache(stop_loading, callback);
pipeline_cache.LoadDiskCache(stop_loading, callback);
if (callback) {
callback(VideoCore::LoadCallbackStage::Complete, 0, 0, "");
}
}
void RasterizerVulkan::SyncDrawState() {
SyncDrawUniforms();
// SyncCullMode();
pipeline_info.rasterization.cull_mode.Assign(regs.rasterizer.cull_mode);
pipeline_info.state.rasterization.cull_mode.Assign(regs.rasterizer.cull_mode);
// If the framebuffer is flipped, request to also flip vulkan viewport
const bool is_flipped = regs.framebuffer.framebuffer.IsFlipped();
pipeline_info.rasterization.flip_viewport.Assign(is_flipped);
pipeline_info.state.rasterization.flip_viewport.Assign(is_flipped);
// SyncBlendEnabled();
pipeline_info.blending.blend_enable = regs.framebuffer.output_merger.alphablend_enable;
pipeline_info.state.blending.blend_enable = regs.framebuffer.output_merger.alphablend_enable;
// SyncBlendFuncs();
pipeline_info.blending.color_blend_eq.Assign(
pipeline_info.state.blending.color_blend_eq.Assign(
regs.framebuffer.output_merger.alpha_blending.blend_equation_rgb);
pipeline_info.blending.alpha_blend_eq.Assign(
pipeline_info.state.blending.alpha_blend_eq.Assign(
regs.framebuffer.output_merger.alpha_blending.blend_equation_a);
pipeline_info.blending.src_color_blend_factor.Assign(
pipeline_info.state.blending.src_color_blend_factor.Assign(
regs.framebuffer.output_merger.alpha_blending.factor_source_rgb);
pipeline_info.blending.dst_color_blend_factor.Assign(
pipeline_info.state.blending.dst_color_blend_factor.Assign(
regs.framebuffer.output_merger.alpha_blending.factor_dest_rgb);
pipeline_info.blending.src_alpha_blend_factor.Assign(
pipeline_info.state.blending.src_alpha_blend_factor.Assign(
regs.framebuffer.output_merger.alpha_blending.factor_source_a);
pipeline_info.blending.dst_alpha_blend_factor.Assign(
pipeline_info.state.blending.dst_alpha_blend_factor.Assign(
regs.framebuffer.output_merger.alpha_blending.factor_dest_a);
// SyncBlendColor();
pipeline_info.dynamic.blend_color = regs.framebuffer.output_merger.blend_const.raw;
pipeline_info.dynamic_info.blend_color = regs.framebuffer.output_merger.blend_const.raw;
// SyncLogicOp();
// SyncColorWriteMask();
pipeline_info.blending.logic_op = regs.framebuffer.output_merger.logic_op;
const bool is_logic_op_emulated =
instance.NeedsLogicOpEmulation() && !regs.framebuffer.output_merger.alphablend_enable;
const bool is_logic_op_noop =
regs.framebuffer.output_merger.logic_op == Pica::FramebufferRegs::LogicOp::NoOp;
if (is_logic_op_emulated && is_logic_op_noop) {
// Color output is disabled by logic operation. We use color write mask to skip
// color but allow depth write.
pipeline_info.blending.color_write_mask = 0;
} else {
const u32 color_mask = regs.framebuffer.framebuffer.allow_color_write != 0
? (regs.framebuffer.output_merger.depth_color_mask >> 8) & 0xF
: 0;
pipeline_info.blending.color_write_mask = color_mask;
}
pipeline_info.state.blending.logic_op = regs.framebuffer.output_merger.logic_op;
const u32 color_mask = regs.framebuffer.framebuffer.allow_color_write != 0
? (regs.framebuffer.output_merger.depth_color_mask >> 8) & 0xF
: 0;
pipeline_info.state.blending.color_write_mask = color_mask;
// SyncStencilTest();
const auto& stencil_test = regs.framebuffer.output_merger.stencil_test;
const bool test_enable = stencil_test.enable && regs.framebuffer.framebuffer.depth_format ==
Pica::FramebufferRegs::DepthFormat::D24S8;
pipeline_info.depth_stencil.stencil_test_enable.Assign(test_enable);
pipeline_info.depth_stencil.stencil_fail_op.Assign(stencil_test.action_stencil_fail);
pipeline_info.depth_stencil.stencil_pass_op.Assign(stencil_test.action_depth_pass);
pipeline_info.depth_stencil.stencil_depth_fail_op.Assign(stencil_test.action_depth_fail);
pipeline_info.depth_stencil.stencil_compare_op.Assign(stencil_test.func);
pipeline_info.dynamic.stencil_reference = stencil_test.reference_value;
pipeline_info.dynamic.stencil_compare_mask = stencil_test.input_mask;
pipeline_info.state.depth_stencil.stencil_test_enable.Assign(test_enable);
pipeline_info.state.depth_stencil.stencil_fail_op.Assign(stencil_test.action_stencil_fail);
pipeline_info.state.depth_stencil.stencil_pass_op.Assign(stencil_test.action_depth_pass);
pipeline_info.state.depth_stencil.stencil_depth_fail_op.Assign(stencil_test.action_depth_fail);
pipeline_info.state.depth_stencil.stencil_compare_op.Assign(stencil_test.func);
pipeline_info.dynamic_info.stencil_reference = stencil_test.reference_value;
pipeline_info.dynamic_info.stencil_compare_mask = stencil_test.input_mask;
// SyncStencilWriteMask();
pipeline_info.dynamic.stencil_write_mask =
pipeline_info.dynamic_info.stencil_write_mask =
(regs.framebuffer.framebuffer.allow_depth_stencil_write != 0)
? static_cast<u32>(regs.framebuffer.output_merger.stencil_test.write_mask)
: 0;
@@ -222,12 +220,12 @@ void RasterizerVulkan::SyncDrawState() {
? regs.framebuffer.output_merger.depth_test_func.Value()
: Pica::FramebufferRegs::CompareFunc::Always;
pipeline_info.depth_stencil.depth_test_enable.Assign(test_enabled);
pipeline_info.depth_stencil.depth_compare_op.Assign(compare_op);
pipeline_info.state.depth_stencil.depth_test_enable.Assign(test_enabled);
pipeline_info.state.depth_stencil.depth_compare_op.Assign(compare_op);
// SyncDepthWriteMask();
const bool write_enable = (regs.framebuffer.framebuffer.allow_depth_stencil_write != 0 &&
regs.framebuffer.output_merger.depth_write_enable);
pipeline_info.depth_stencil.depth_write_enable.Assign(write_enable);
pipeline_info.state.depth_stencil.depth_write_enable.Assign(write_enable);
}
void RasterizerVulkan::SetupVertexArray() {
@@ -246,7 +244,7 @@ void RasterizerVulkan::SetupVertexArray() {
const PAddr base_address = vertex_attributes.GetPhysicalBaseAddress(); // GPUREG_ATTR_BUF_BASE
const u32 stride_alignment = instance.GetMinVertexStrideAlignment();
VertexLayout& layout = pipeline_info.vertex_layout;
VertexLayout& layout = pipeline_info.state.vertex_layout;
layout.binding_count = 0;
layout.attribute_count = 16;
enable_attributes.fill(false);
@@ -322,7 +320,8 @@ void RasterizerVulkan::SetupVertexArray() {
VertexBinding& binding = layout.bindings[layout.binding_count];
binding.binding.Assign(layout.binding_count);
binding.fixed.Assign(0);
binding.stride.Assign(aligned_stride);
// Will be adjusted on pipeline build, to keep the info transferable.
binding.byte_count.Assign(loader.byte_count);
// Keep track of the binding offsets so we can bind the vertex buffer later
binding_offsets[layout.binding_count++] = static_cast<u32>(array_offset + buffer_offset);
@@ -337,7 +336,7 @@ void RasterizerVulkan::SetupVertexArray() {
void RasterizerVulkan::SetupFixedAttribs() {
const auto& vertex_attributes = regs.pipeline.vertex_attributes;
VertexLayout& layout = pipeline_info.vertex_layout;
VertexLayout& layout = pipeline_info.state.vertex_layout;
auto [fixed_ptr, fixed_offset, _] = stream_buffer.Map(16 * sizeof(Common::Vec4f), 0);
binding_offsets[layout.binding_count] = static_cast<u32>(fixed_offset);
@@ -391,7 +390,7 @@ void RasterizerVulkan::SetupFixedAttribs() {
VertexBinding& binding = layout.bindings[layout.binding_count];
binding.binding.Assign(layout.binding_count++);
binding.fixed.Assign(1);
binding.stride.Assign(offset);
binding.byte_count.Assign(offset);
stream_buffer.Commit(offset);
}
@@ -399,7 +398,7 @@ void RasterizerVulkan::SetupFixedAttribs() {
bool RasterizerVulkan::SetupVertexShader() {
MICROPROFILE_SCOPE(Vulkan_VS);
return pipeline_cache.UseProgrammableVertexShader(regs, pica.vs_setup,
pipeline_info.vertex_layout, accurate_mul);
pipeline_info.state.vertex_layout);
}
bool RasterizerVulkan::SetupGeometryShader() {
@@ -412,8 +411,9 @@ bool RasterizerVulkan::SetupGeometryShader() {
// Enable the quaternion fix-up geometry-shader only if we are actually doing per-fragment
// lighting and care about proper quaternions. Otherwise just use standard vertex+fragment
// shaders. We also don't need a geometry shader if the barycentric extension is supported.
if (regs.lighting.disable || instance.IsFragmentShaderBarycentricSupported()) {
// shaders. We also don't need a geometry shader if the barycentric extension is supported,
// but that will be decided later as the GS config needs to be cached anyways.
if (regs.lighting.disable) {
pipeline_cache.UseTrivialGeometryShader();
return true;
}
@@ -431,7 +431,7 @@ bool RasterizerVulkan::AccelerateDrawBatch(bool is_indexed) {
}
}
pipeline_info.rasterization.topology.Assign(regs.pipeline.triangle_topology);
pipeline_info.state.rasterization.topology.Assign(regs.pipeline.triangle_topology);
if (regs.pipeline.triangle_topology == TriangleTopology::Fan &&
!instance.IsTriangleFanSupported()) {
LOG_DEBUG(Render_Vulkan,
@@ -467,7 +467,7 @@ bool RasterizerVulkan::AccelerateDrawBatchInternal(bool is_indexed) {
const DrawParams params = {
.vertex_count = regs.pipeline.num_vertices,
.vertex_offset = -static_cast<s32>(vertex_info.vs_input_index_min),
.binding_count = pipeline_info.vertex_layout.binding_count,
.binding_count = pipeline_info.state.vertex_layout.binding_count,
.bindings = binding_offsets,
.is_indexed = is_indexed,
};
@@ -521,8 +521,8 @@ void RasterizerVulkan::DrawTriangles() {
return;
}
pipeline_info.rasterization.topology.Assign(Pica::PipelineRegs::TriangleTopology::List);
pipeline_info.vertex_layout = software_layout;
pipeline_info.state.rasterization.topology.Assign(Pica::PipelineRegs::TriangleTopology::List);
pipeline_info.state.vertex_layout = software_layout;
pipeline_cache.UseTrivialVertexShader();
pipeline_cache.UseTrivialGeometryShader();
@@ -537,14 +537,14 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
const bool shadow_rendering = regs.framebuffer.IsShadowRendering();
const bool has_stencil = regs.framebuffer.HasStencil();
const bool write_color_fb = shadow_rendering || pipeline_info.blending.color_write_mask;
const bool write_color_fb = shadow_rendering || pipeline_info.GetFinalColorWriteMask(instance);
const bool write_depth_fb = pipeline_info.IsDepthWriteEnabled();
const bool using_color_fb =
regs.framebuffer.framebuffer.GetColorBufferPhysicalAddress() != 0 && write_color_fb;
const bool using_depth_fb =
!shadow_rendering && regs.framebuffer.framebuffer.GetDepthBufferPhysicalAddress() != 0 &&
(write_depth_fb || regs.framebuffer.output_merger.depth_test_enable != 0 ||
(has_stencil && pipeline_info.depth_stencil.stencil_test_enable));
(has_stencil && pipeline_info.state.depth_stencil.stencil_test_enable));
const auto fb_helper = res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb);
const Framebuffer* framebuffer = fb_helper.Framebuffer();
@@ -552,8 +552,8 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
return true;
}
pipeline_info.attachments.color = framebuffer->Format(SurfaceType::Color);
pipeline_info.attachments.depth = framebuffer->Format(SurfaceType::Depth);
pipeline_info.state.attachments.color = framebuffer->Format(SurfaceType::Color);
pipeline_info.state.attachments.depth = framebuffer->Format(SurfaceType::Depth);
// Update scissor uniforms
const auto [scissor_x1, scissor_y2, scissor_x2, scissor_y1] = fb_helper.Scissor();
@@ -585,13 +585,13 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
// Configure viewport and scissor
const auto viewport = fb_helper.Viewport();
pipeline_info.dynamic.viewport = Common::Rectangle<s32>{
pipeline_info.dynamic_info.viewport = Common::Rectangle<s32>{
viewport.x,
viewport.y,
viewport.x + viewport.width,
viewport.y + viewport.height,
};
pipeline_info.dynamic.scissor = draw_rect;
pipeline_info.dynamic_info.scissor = draw_rect;
// Draw the vertex batch
bool succeeded = true;
@@ -807,7 +807,7 @@ void RasterizerVulkan::MakeSoftwareVertexLayout() {
VertexBinding& binding = software_layout.bindings[i];
binding.binding.Assign(i);
binding.fixed.Assign(0);
binding.stride.Assign(sizeof(HardwareVertex));
binding.byte_count.Assign(sizeof(HardwareVertex));
}
u32 offset = 0;
@@ -985,6 +985,7 @@ void RasterizerVulkan::UploadUniforms(bool accelerate_draw) {
void RasterizerVulkan::SwitchDiskResources(u64 title_id) {
std::atomic_bool stop_loading = false;
pipeline_cache.SetAccurateMul(accurate_mul);
pipeline_cache.SwitchPipelineCache(title_id, stop_loading, switch_disk_resources_callback);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,346 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <mutex>
#include <optional>
#include <unordered_set>
#include <tsl/robin_map.h>
#include "common/common_types.h"
#include "common/file_util.h"
#include "video_core/pica/shader_setup.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/shader/generator/pica_fs_config.h"
#include "video_core/shader/generator/profile.h"
#include "video_core/shader/generator/shader_gen.h"
namespace Vulkan {
class PipelineCache;
class ShaderDiskCache {
public:
ShaderDiskCache(PipelineCache& _parent, u64 _title_id) : parent(_parent), title_id(_title_id) {}
void Init(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback);
std::optional<std::pair<u64, Shader* const>> UseProgrammableVertexShader(
const Pica::RegsInternal& regs, Pica::ShaderSetup& setup, const VertexLayout& layout);
std::optional<std::pair<u64, Shader* const>> UseFragmentShader(
const Pica::RegsInternal& regs, const Pica::Shader::UserConfig& user);
std::optional<std::pair<u64, Shader* const>> UseFixedGeometryShader(
const Pica::RegsInternal& regs);
GraphicsPipeline* GetPipeline(const PipelineInfo& info);
u64 GetProgramID() const {
return title_id;
}
private:
static constexpr std::size_t SOURCE_FILE_HASH_LENGTH = 64;
using SourceFileCacheVersionHash = std::array<u8, SOURCE_FILE_HASH_LENGTH>;
static SourceFileCacheVersionHash GetSourceFileCacheVersionHash();
enum class CacheFileType : u32 {
VS_CACHE = 0,
FS_CACHE = 1,
GS_CACHE = 2,
PL_CACHE = 3,
MAX,
};
enum class CacheEntryType : u16 {
// Common
FILE_INFO = 0,
// VS_CACHE
VS_CONFIG = 1,
VS_PROGRAM = 2,
VS_SPIRV = 3,
// FS_CACHE
FS_CONFIG = 4,
FS_SPIRV = 5,
// GS_CACHE
GS_CONFIG = 6,
GS_SPIRV = 7,
// PL_CACHE
PL_CONFIG = 8,
MAX,
};
struct FileInfoEntry {
static constexpr u32 CACHE_FILE_MAGIC = 0x48434B56;
static constexpr u32 CACHE_FILE_VERSION = 0;
u32_le cache_magic;
u32 file_version;
u64 config_struct_hash;
CacheFileType file_type;
SourceFileCacheVersionHash source_hash;
std::array<char, 0x20> build_name;
union {
u8 reserved[0x400];
Pica::Shader::Profile profile;
};
};
static_assert(sizeof(FileInfoEntry) == 1144);
struct VSConfigEntry {
static constexpr u8 EXPECTED_VERSION = 0;
u8 version; // Surprise tool that can help us later
u64 program_entry_id;
u64 spirv_entry_id;
Pica::Shader::Generator::PicaVSConfig vs_config;
};
static_assert(sizeof(VSConfigEntry) == 216);
struct VSProgramEntry {
static constexpr u8 EXPECTED_VERSION = 0;
u8 version; // Surprise tool that can help us later
u32 program_len;
u32 swizzle_len;
Pica::ProgramCode program_code;
Pica::SwizzleData swizzle_code;
};
static_assert(sizeof(VSProgramEntry) == 32780);
struct FSConfigEntry {
static constexpr u8 EXPECTED_VERSION = 0;
u8 version; // Surprise tool that can help us later
Pica::Shader::FSConfig fs_config;
};
static_assert(sizeof(FSConfigEntry) == 276);
struct GSConfigEntry {
static constexpr u8 EXPECTED_VERSION = 0;
u8 version; // Surprise tool that can help us later
Pica::Shader::Generator::PicaFixedGSConfig gs_config;
};
static_assert(sizeof(GSConfigEntry) == 44);
struct PLConfigEntry {
static constexpr u8 EXPECTED_VERSION = 0;
u8 version; // Surprise tool that can help us later
StaticPipelineInfo pl_info;
};
static_assert(sizeof(PLConfigEntry) == 152);
class CacheFile;
class CacheEntry {
public:
static constexpr u32 MAX_ENTRY_SIZE = 4 * 1024 * 1024;
struct CacheEntryFooter {
static constexpr u8 ENTRY_VERSION = 0x24;
union {
u32 first_word{};
BitField<0, 8, u32> version;
BitField<8, 24, u32> entry_id;
};
u32 entry_size{};
u64 reserved{};
};
static_assert(sizeof(CacheEntryFooter) == 0x10);
struct CacheEntryHeader {
static constexpr u8 ENTRY_VERSION = 0x42;
u8 entry_version{};
union {
u8 flags{};
BitField<0, 1, u8> zstd_compressed;
BitField<1, 7, u8> reserved;
};
CacheEntryType type{};
u32 entry_size{};
u64 id{};
CacheEntryType Type() const {
return type;
}
u64 Id() const {
return id;
}
bool Valid() {
constexpr u32 headers_size =
sizeof(CacheEntry::CacheEntryHeader) + sizeof(CacheEntry::CacheEntryFooter);
return entry_version == ENTRY_VERSION && type < CacheEntryType::MAX &&
entry_size < CacheEntry::MAX_ENTRY_SIZE && entry_size >= headers_size;
}
};
static_assert(sizeof(CacheEntryHeader) == 0x10);
bool Valid() const {
return valid;
}
CacheEntryType Type() const {
return header.Type();
}
u64 Id() const {
return header.Id();
}
const std::span<const u8> Data() const {
return data;
}
template <typename T>
const T* Payload() const {
if (data.size() != sizeof(T)) {
return nullptr;
}
return reinterpret_cast<const T*>(data.data());
}
size_t Position() const {
return position;
}
const CacheEntryHeader& Header() const {
return header;
}
private:
friend CacheFile;
CacheEntry() = default;
CacheEntryHeader header{};
size_t position = SIZE_MAX;
bool valid = false;
std::vector<u8> data{};
};
class CacheFile {
public:
enum class CacheOpMode {
READ,
APPEND,
DELETE,
RECREATE,
};
CacheFile() = default;
CacheFile(const std::string& _filepath) : filepath(_filepath) {}
void SetFilePath(const std::string& path) {
filepath = path;
}
CacheEntry ReadFirst();
CacheEntry ReadNext(const CacheEntry& previous);
CacheEntry ReadAt(size_t position);
std::pair<size_t, CacheEntry::CacheEntryHeader> ReadNextHeader(
const ShaderDiskCache::CacheEntry::CacheEntryHeader& previous,
size_t previous_position);
CacheEntry::CacheEntryHeader ReadAtHeader(size_t position);
size_t GetTotalEntries();
template <typename T>
bool Append(CacheEntryType type, u64 id, const T& object, bool compress) {
static_assert(std::is_trivially_copyable_v<T>);
auto bytes = std::as_bytes(std::span{&object, 1});
auto u8_span =
std::span<const u8>(reinterpret_cast<const u8*>(bytes.data()), bytes.size());
return Append(type, id, u8_span, compress);
}
bool Append(CacheEntryType type, u64 id, std::span<const u8> data, bool compress);
bool SwitchMode(CacheOpMode mode);
private:
std::string filepath;
std::mutex mutex;
FileUtil::IOFile file{};
size_t biggest_entry_id = SIZE_MAX;
};
std::string GetVSFile(u64 title_id, bool is_temp) const;
std::string GetFSFile(u64 title_id, bool is_temp) const;
std::string GetGSFile(u64 title_id, bool is_temp) const;
std::string GetPLFile(u64 title_id, bool is_temp) const;
bool RecreateCache(CacheFile& file, CacheFileType type);
bool InitVSCache(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback);
bool InitFSCache(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback);
bool InitGSCache(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback);
bool InitPLCache(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback);
bool AppendVSConfigProgram(CacheFile& file, const Pica::Shader::Generator::PicaVSConfig& config,
const Pica::ShaderSetup& setup, u64 config_id, u64 program_id);
bool AppendVSProgram(CacheFile& file, const VSProgramEntry& entry, u64 program_id);
bool AppendVSConfig(CacheFile& file, const VSConfigEntry& entry, u64 config_id);
bool AppendVSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id);
bool AppendFSConfig(CacheFile& file, const FSConfigEntry& entry, u64 config_id);
bool AppendFSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id);
bool AppendGSConfig(CacheFile& file, const GSConfigEntry& entry, u64 config_id);
bool AppendGSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id);
bool AppendPLConfig(CacheFile& file, const PLConfigEntry& entry, u64 config_id);
CacheFile vs_cache;
CacheFile fs_cache;
CacheFile gs_cache;
CacheFile pl_cache;
PipelineCache& parent;
u64 title_id;
std::unordered_map<u64, Shader> programmable_vertex_cache;
std::unordered_map<u64, Shader*> programmable_vertex_map;
std::unordered_set<u64> known_vertex_programs;
std::unordered_map<u64, Shader> fragment_shaders;
std::unordered_map<size_t, Shader> fixed_geometry_shaders;
std::unordered_set<u64> known_geometry_shaders;
tsl::robin_map<u64, std::unique_ptr<GraphicsPipeline>, Common::IdentityHash<u64>>
graphics_pipelines;
std::unordered_set<u64> known_graphic_pipelines;
};
} // namespace Vulkan
@@ -160,8 +160,8 @@ bool InitializeCompiler() {
}
} // Anonymous namespace
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device,
std::string_view premable) {
std::vector<u32> CompileGLSL(std::string_view code, vk::ShaderStageFlagBits stage,
std::string_view premable) {
if (!InitializeCompiler()) {
return {};
}
@@ -217,7 +217,7 @@ vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, v
LOG_INFO(Render_Vulkan, "SPIR-V conversion messages: {}", spv_messages);
}
return CompileSPV(out_code, device);
return out_code;
}
vk::ShaderModule CompileSPV(std::span<const u32> code, vk::Device device) {
@@ -229,10 +229,15 @@ vk::ShaderModule CompileSPV(std::span<const u32> code, vk::Device device) {
try {
return device.createShaderModule(shader_info);
} catch (vk::SystemError& err) {
UNREACHABLE_MSG("{}", err.what());
LOG_ERROR(Render_Vulkan, "{}", err.what());
}
return {};
}
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device,
std::string_view premable) {
return CompileSPV(CompileGLSL(code, stage, premable), device);
}
} // namespace Vulkan
@@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -14,10 +14,9 @@ namespace Vulkan {
* @brief Creates a vulkan shader module from GLSL by converting it to SPIR-V using glslang.
* @param code The string containing GLSL code.
* @param stage The pipeline stage the shader will be used in.
* @param device The vulkan device handle.
*/
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device,
std::string_view premable = "");
std::vector<u32> CompileGLSL(std::string_view code, vk::ShaderStageFlagBits stage,
std::string_view premable = "");
/**
* @brief Creates a vulkan shader module from SPIR-V bytecode.
@@ -26,4 +25,7 @@ vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, v
*/
vk::ShaderModule CompileSPV(std::span<const u32> code, vk::Device device);
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device,
std::string_view premable = "");
} // namespace Vulkan
@@ -101,8 +101,10 @@ layout (binding = 2, std140) uniform fs_data {
};
)";
FragmentModule::FragmentModule(const FSConfig& config_, const Profile& profile_)
: config{config_}, profile{profile_} {
FragmentModule::FragmentModule(const FSConfig& config_, const UserConfig& user_,
const Profile& profile_)
: config{config_}, user{user_}, profile{profile_} {
config.ApplyProfile(profile_);
out.reserve(RESERVE_SIZE);
DefineExtensions();
DefineInterface();
@@ -504,7 +506,7 @@ void FragmentModule::WriteLighting() {
return fmt::format("2.0 * (sampleTexUnit{}()).rgb - 1.0", lighting.bump_selector.Value());
};
if (config.user.use_custom_normal) {
if (user.use_custom_normal) {
const auto texel = fmt::format("2.0 * (texture(tex_normal, texcoord0)).rgb - 1.0");
out += fmt::format("vec3 surface_normal = {};\n", texel);
out += "vec3 surface_tangent = vec3(1.0, 0.0, 0.0);\n";
@@ -665,7 +667,7 @@ void FragmentModule::WriteLighting() {
const std::string value =
get_lut_value(LightingRegs::SpotlightAttenuationSampler(light_config.num),
light_config.num, lighting.lut_sp.type, lighting.lut_sp.abs_input);
spot_atten = fmt::format("({:#} * {})", lighting.lut_sp.scale, value);
spot_atten = fmt::format("({:#} * {})", lighting.lut_sp.GetScale(), value);
}
// If enabled, compute distance attenuation value
@@ -693,7 +695,7 @@ void FragmentModule::WriteLighting() {
const std::string value =
get_lut_value(LightingRegs::LightingSampler::Distribution0, light_config.num,
lighting.lut_d0.type, lighting.lut_d0.abs_input);
d0_lut_value = fmt::format("({:#} * {})", lighting.lut_d0.scale, value);
d0_lut_value = fmt::format("({:#} * {})", lighting.lut_d0.GetScale(), value);
}
std::string specular_0 = fmt::format("({} * {}.specular_0)", d0_lut_value, light_src);
if (light_config.geometric_factor_0) {
@@ -707,7 +709,7 @@ void FragmentModule::WriteLighting() {
std::string value =
get_lut_value(LightingRegs::LightingSampler::ReflectRed, light_config.num,
lighting.lut_rr.type, lighting.lut_rr.abs_input);
value = fmt::format("({:#} * {})", lighting.lut_rr.scale, value);
value = fmt::format("({:#} * {})", lighting.lut_rr.GetScale(), value);
out += fmt::format("refl_value.r = {};\n", value);
} else {
out += "refl_value.r = 1.0;\n";
@@ -720,7 +722,7 @@ void FragmentModule::WriteLighting() {
std::string value =
get_lut_value(LightingRegs::LightingSampler::ReflectGreen, light_config.num,
lighting.lut_rg.type, lighting.lut_rg.abs_input);
value = fmt::format("({:#} * {})", lighting.lut_rg.scale, value);
value = fmt::format("({:#} * {})", lighting.lut_rg.GetScale(), value);
out += fmt::format("refl_value.g = {};\n", value);
} else {
out += "refl_value.g = refl_value.r;\n";
@@ -733,7 +735,7 @@ void FragmentModule::WriteLighting() {
std::string value =
get_lut_value(LightingRegs::LightingSampler::ReflectBlue, light_config.num,
lighting.lut_rb.type, lighting.lut_rb.abs_input);
value = fmt::format("({:#} * {})", lighting.lut_rb.scale, value);
value = fmt::format("({:#} * {})", lighting.lut_rb.GetScale(), value);
out += fmt::format("refl_value.b = {};\n", value);
} else {
out += "refl_value.b = refl_value.r;\n";
@@ -748,7 +750,7 @@ void FragmentModule::WriteLighting() {
const std::string value =
get_lut_value(LightingRegs::LightingSampler::Distribution1, light_config.num,
lighting.lut_d1.type, lighting.lut_d1.abs_input);
d1_lut_value = fmt::format("({:#} * {})", lighting.lut_d1.scale, value);
d1_lut_value = fmt::format("({:#} * {})", lighting.lut_d1.GetScale(), value);
}
std::string specular_1 =
fmt::format("({} * refl_value * {}.specular_1)", d1_lut_value, light_src);
@@ -765,7 +767,7 @@ void FragmentModule::WriteLighting() {
std::string value =
get_lut_value(LightingRegs::LightingSampler::Fresnel, light_config.num,
lighting.lut_fr.type, lighting.lut_fr.abs_input);
value = fmt::format("({:#} * {})", lighting.lut_fr.scale, value);
value = fmt::format("({:#} * {})", lighting.lut_fr.GetScale(), value);
// Enabled for diffuse lighting alpha component
if (lighting.enable_primary_alpha) {
@@ -1311,7 +1313,7 @@ void FragmentModule::DefineBindingsVK() {
if (config.framebuffer.shadow_rendering) {
out += "layout(set = 2, binding = 0, r32ui) uniform uimage2D shadow_buffer;\n\n";
}
if (config.user.use_custom_normal) {
if (user.use_custom_normal) {
out += "layout(set = 2, binding = 1) uniform sampler2D tex_normal;\n";
}
}
@@ -1332,7 +1334,7 @@ void FragmentModule::DefineBindingsGL() {
}
// Utility textures
if (config.user.use_custom_normal) {
if (user.use_custom_normal) {
out += "layout(binding = 6) uniform sampler2D tex_normal;\n";
}
if (use_blend_fallback) {
@@ -1752,8 +1754,9 @@ void FragmentModule::DefineTexUnitSampler(u32 texture_unit) {
out += "\n}\n";
}
std::string GenerateFragmentShader(const FSConfig& config, const Profile& profile) {
FragmentModule module{config, profile};
std::string GenerateFragmentShader(const FSConfig& config, const UserConfig& user,
const Profile& profile) {
FragmentModule module{config, user, profile};
return module.Generate();
}
@@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -10,7 +10,7 @@ namespace Pica::Shader::Generator::GLSL {
class FragmentModule {
public:
explicit FragmentModule(const FSConfig& config, const Profile& profile);
explicit FragmentModule(const FSConfig& config, const UserConfig& user, const Profile& profile);
~FragmentModule();
/// Emits GLSL source corresponding to the provided pica fragment configuration
@@ -83,7 +83,8 @@ private:
void DefineTexUnitSampler(u32 i);
private:
const FSConfig& config;
FSConfig config;
const UserConfig& user;
const Profile& profile;
std::string out;
bool use_blend_fallback{};
@@ -97,6 +98,7 @@ private:
* configuration (NOTE: Use state in this struct only, not the Pica registers!)
* @returns String of the shader source code
*/
std::string GenerateFragmentShader(const FSConfig& config, const Profile& profile);
std::string GenerateFragmentShader(const FSConfig& config, const UserConfig& user,
const Profile& profile);
} // namespace Pica::Shader::Generator::GLSL
@@ -153,7 +153,9 @@ std::string_view MakeLoadPrefix(AttribLoadFlags flag) {
}
std::string GenerateVertexShader(const ShaderSetup& setup, const PicaVSConfig& config,
bool separable_shader) {
const ExtraVSConfig& extra) {
const bool separable_shader = extra.separable_shader;
std::string out;
if (separable_shader) {
out += "#extension GL_ARB_separate_shader_objects : enable\n";
@@ -179,7 +181,7 @@ std::string GenerateVertexShader(const ShaderSetup& setup, const PicaVSConfig& c
auto program_source =
DecompileProgram(setup.GetProgramCode(), setup.GetSwizzleData(), config.state.main_offset,
get_input_reg, get_output_reg, config.state.sanitize_mul);
get_input_reg, get_output_reg, extra.sanitize_mul);
if (program_source.empty()) {
return "";
@@ -188,7 +190,7 @@ std::string GenerateVertexShader(const ShaderSetup& setup, const PicaVSConfig& c
// input attributes declaration
for (std::size_t i = 0; i < used_regs.size(); ++i) {
if (used_regs[i]) {
const auto flags = config.state.load_flags[i];
const auto flags = extra.load_flags[i];
const std::string_view prefix = MakeLoadPrefix(flags);
out +=
fmt::format("layout(location = {0}) in {1}vec4 vs_in_typed_reg{0};\n", i, prefix);
@@ -197,7 +199,7 @@ std::string GenerateVertexShader(const ShaderSetup& setup, const PicaVSConfig& c
}
out += '\n';
if (config.state.use_geometry_shader) {
if (extra.use_geometry_shader) {
// output attributes declaration
for (u32 i = 0; i < config.state.num_outputs; ++i) {
if (separable_shader) {
@@ -207,19 +209,21 @@ std::string GenerateVertexShader(const ShaderSetup& setup, const PicaVSConfig& c
}
out += "void EmitVtx() {}\n";
} else {
out += GetVertexInterfaceDeclaration(true, config.state.use_clip_planes, separable_shader);
out += GetVertexInterfaceDeclaration(true, extra.use_clip_planes, separable_shader);
// output attributes declaration
for (u32 i = 0; i < config.state.num_outputs; ++i) {
out += fmt::format("vec4 vs_out_attr{};\n", i);
}
const auto semantic =
[&state = config.state](VSOutputAttributes::Semantic slot_semantic) -> std::string {
const auto semantic_maps = config.state.gs_state.GetSemanticMaps();
const auto semantic = [&state = config.state, &semantic_maps](
VSOutputAttributes::Semantic slot_semantic) -> std::string {
const u32 slot = static_cast<u32>(slot_semantic);
const u32 attrib = state.gs_state.semantic_maps[slot].attribute_index;
const u32 comp = state.gs_state.semantic_maps[slot].component_index;
if (attrib < state.gs_state.gs_output_attributes) {
const u32 attrib = semantic_maps[slot].attribute_index;
const u32 comp = semantic_maps[slot].component_index;
if (attrib < state.gs_state.gs_output_attributes_count) {
return fmt::format("vs_out_attr{}.{}", attrib, "xyzw"[comp]);
}
return "1.0";
@@ -242,7 +246,7 @@ std::string GenerateVertexShader(const ShaderSetup& setup, const PicaVSConfig& c
out += " vtx_pos.y = -vtx_pos.y;\n";
out += " }\n";
out += " gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n";
if (config.state.use_clip_planes) {
if (extra.use_clip_planes) {
out += " gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0
out += " if (enable_clip1) {\n";
out += " gl_ClipDistance[1] = dot(clip_coef, vtx_pos);\n";
@@ -279,7 +283,7 @@ std::string GenerateVertexShader(const ShaderSetup& setup, const PicaVSConfig& c
for (std::size_t i = 0; i < used_regs.size(); ++i) {
if (used_regs[i]) {
out += fmt::format("vs_in_reg{0} = vec4(vs_in_typed_reg{0});\n", i);
if (True(config.state.load_flags[i] & AttribLoadFlags::ZeroW)) {
if (True(extra.load_flags[i] & AttribLoadFlags::ZeroW)) {
out += fmt::format("vs_in_reg{0}.w = 0;\n", i);
}
}
@@ -294,13 +298,15 @@ std::string GenerateVertexShader(const ShaderSetup& setup, const PicaVSConfig& c
return out;
}
static std::string GetGSCommonSource(const PicaGSConfigState& state, bool separable_shader) {
std::string out = GetVertexInterfaceDeclaration(true, state.use_clip_planes, separable_shader);
static std::string GetGSCommonSource(const PicaGSConfigState& state,
const ExtraFixedGSConfig& extra) {
std::string out =
GetVertexInterfaceDeclaration(true, extra.use_clip_planes, extra.separable_shader);
out += VSUniformBlockDef;
out += '\n';
for (u32 i = 0; i < state.vs_output_attributes; ++i) {
if (separable_shader) {
for (u32 i = 0; i < state.vs_output_attributes_count; ++i) {
if (extra.separable_shader) {
out += fmt::format("layout(location = {}) ", i);
}
out += fmt::format("in vec4 vs_out_attr{}[];\n", i);
@@ -309,14 +315,17 @@ static std::string GetGSCommonSource(const PicaGSConfigState& state, bool separa
out += R"(
struct Vertex {
)";
out += fmt::format(" vec4 attributes[{}];\n", state.gs_output_attributes);
out += fmt::format(" vec4 attributes[{}];\n", state.gs_output_attributes_count);
out += "};\n\n";
const auto semantic = [&state](VSOutputAttributes::Semantic slot_semantic) -> std::string {
const auto semantic_maps = state.GetSemanticMaps();
const auto semantic =
[&state, &semantic_maps](VSOutputAttributes::Semantic slot_semantic) -> std::string {
const u32 slot = static_cast<u32>(slot_semantic);
const u32 attrib = state.semantic_maps[slot].attribute_index;
const u32 comp = state.semantic_maps[slot].component_index;
if (attrib < state.gs_output_attributes) {
const u32 attrib = semantic_maps[slot].attribute_index;
const u32 comp = semantic_maps[slot].component_index;
if (attrib < state.gs_output_attributes_count) {
return fmt::format("vtx.attributes[{}].{}", attrib, "xyzw"[comp]);
}
return "1.0";
@@ -339,7 +348,7 @@ struct Vertex {
out += " vtx_pos.y = -vtx_pos.y;\n";
out += " }\n";
out += " gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n";
if (state.use_clip_planes) {
if (extra.use_clip_planes) {
out += " gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0
out += " if (enable_clip1) {\n";
out += " gl_ClipDistance[1] = dot(clip_coef, vtx_pos);\n";
@@ -388,7 +397,10 @@ void EmitPrim(Vertex vtx0, Vertex vtx1, Vertex vtx2) {
return out;
};
std::string GenerateFixedGeometryShader(const PicaFixedGSConfig& config, bool separable_shader) {
std::string GenerateFixedGeometryShader(const PicaFixedGSConfig& config,
const ExtraFixedGSConfig& extra) {
const bool separable_shader = extra.separable_shader;
std::string out;
if (separable_shader) {
out += "#extension GL_ARB_separate_shader_objects : enable\n";
@@ -400,7 +412,7 @@ layout(triangle_strip, max_vertices = 3) out;
)";
out += GetGSCommonSource(config.state, separable_shader);
out += GetGSCommonSource(config.state, extra);
out += R"(
void main() {
@@ -408,8 +420,8 @@ void main() {
)";
for (u32 vtx = 0; vtx < 3; ++vtx) {
out += fmt::format(" prim_buffer[{}].attributes = vec4[{}](", vtx,
config.state.gs_output_attributes);
for (u32 i = 0; i < config.state.vs_output_attributes; ++i) {
config.state.gs_output_attributes_count);
for (u32 i = 0; i < config.state.vs_output_attributes_count; ++i) {
out += fmt::format("{}vs_out_attr{}[{}]", i == 0 ? "" : ", ", i, vtx);
}
out += ");\n";
@@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -27,7 +27,9 @@ struct ShaderSetup;
namespace Pica::Shader::Generator {
struct PicaVSConfig;
struct ExtraVSConfig;
struct PicaFixedGSConfig;
struct ExtraFixedGSConfig;
} // namespace Pica::Shader::Generator
namespace Pica::Shader::Generator::GLSL {
@@ -44,12 +46,13 @@ std::string GenerateTrivialVertexShader(bool use_clip_planes, bool separable_sha
* @returns String of the shader source code; empty on failure
*/
std::string GenerateVertexShader(const Pica::ShaderSetup& setup, const PicaVSConfig& config,
bool separable_shader);
const ExtraVSConfig& extra);
/**
* Generates the GLSL fixed geometry shader program source code for non-GS PICA pipeline
* @returns String of the shader source code
*/
std::string GenerateFixedGeometryShader(const PicaFixedGSConfig& config, bool separable_shader);
std::string GenerateFixedGeometryShader(const PicaFixedGSConfig& config,
const ExtraFixedGSConfig& extra_config);
} // namespace Pica::Shader::Generator::GLSL
@@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -6,7 +6,7 @@
namespace Pica::Shader {
FramebufferConfig::FramebufferConfig(const Pica::RegsInternal& regs, const Profile& profile) {
FramebufferConfig::FramebufferConfig(const Pica::RegsInternal& regs) {
const auto& output_merger = regs.framebuffer.output_merger;
scissor_test_mode.Assign(regs.rasterizer.scissor_test.mode);
depthmap_enable.Assign(regs.rasterizer.depthmap_enable);
@@ -15,31 +15,43 @@ FramebufferConfig::FramebufferConfig(const Pica::RegsInternal& regs, const Profi
? output_merger.alpha_test.func.Value()
: Pica::FramebufferRegs::CompareFunc::Always);
// Emulate logic op in the shader if needed and not supported.
alphablend_enable.Assign(output_merger.alphablend_enable);
requested_logic_op = output_merger.logic_op;
logic_op.Assign(Pica::FramebufferRegs::LogicOp::Copy);
if (!profile.has_logic_op && !regs.framebuffer.output_merger.alphablend_enable) {
logic_op.Assign(regs.framebuffer.output_merger.logic_op);
if (alphablend_enable) {
rgb_blend.eq = output_merger.alpha_blending.blend_equation_rgb.Value();
rgb_blend.src_factor = output_merger.alpha_blending.factor_source_rgb;
rgb_blend.dst_factor = output_merger.alpha_blending.factor_dest_rgb;
alpha_blend.eq = output_merger.alpha_blending.blend_equation_a.Value();
alpha_blend.src_factor = output_merger.alpha_blending.factor_source_a;
alpha_blend.dst_factor = output_merger.alpha_blending.factor_dest_a;
}
}
void FramebufferConfig::ApplyProfile(const Profile& profile) {
// Emulate logic op in the shader if needed and not supported.
if (!profile.has_logic_op && !alphablend_enable) {
logic_op.Assign(requested_logic_op);
}
const auto alpha_eq = output_merger.alpha_blending.blend_equation_a.Value();
const auto rgb_eq = output_merger.alpha_blending.blend_equation_rgb.Value();
if (!profile.has_blend_minmax_factor && output_merger.alphablend_enable) {
if (rgb_eq == Pica::FramebufferRegs::BlendEquation::Max ||
rgb_eq == Pica::FramebufferRegs::BlendEquation::Min) {
rgb_blend.eq = rgb_eq;
rgb_blend.src_factor = output_merger.alpha_blending.factor_source_rgb;
rgb_blend.dst_factor = output_merger.alpha_blending.factor_dest_rgb;
// Min/max blend emulation
if (!profile.has_blend_minmax_factor && alphablend_enable) {
if (rgb_blend.eq != Pica::FramebufferRegs::BlendEquation::Min &&
rgb_blend.eq != Pica::FramebufferRegs::BlendEquation::Max) {
rgb_blend = {};
}
if (alpha_eq == Pica::FramebufferRegs::BlendEquation::Max ||
alpha_eq == Pica::FramebufferRegs::BlendEquation::Min) {
alpha_blend.eq = alpha_eq;
alpha_blend.src_factor = output_merger.alpha_blending.factor_source_a;
alpha_blend.dst_factor = output_merger.alpha_blending.factor_dest_a;
if (alpha_blend.eq != Pica::FramebufferRegs::BlendEquation::Min &&
alpha_blend.eq != Pica::FramebufferRegs::BlendEquation::Max) {
alpha_blend = {};
}
}
}
TextureConfig::TextureConfig(const Pica::TexturingRegs& regs, const Profile& profile) {
TextureConfig::TextureConfig(const Pica::TexturingRegs& regs) {
texture0_type.Assign(regs.texture0.type);
texture2_use_coord1.Assign(regs.main_config.texture2_use_coord1 != 0);
combiner_buffer_input.Assign(regs.tev_combiner_buffer_input.update_mask_rgb.Value() |
@@ -48,16 +60,13 @@ TextureConfig::TextureConfig(const Pica::TexturingRegs& regs, const Profile& pro
fog_flip.Assign(regs.fog_flip != 0);
shadow_texture_orthographic.Assign(regs.shadow.orthographic != 0);
// Emulate custom border color if needed and not supported.
const auto pica_textures = regs.GetTextures();
for (u32 tex_index = 0; tex_index < 3; tex_index++) {
const auto& config = pica_textures[tex_index].config;
texture_border_color[tex_index].enable_s.Assign(
!profile.has_custom_border_color &&
config.wrap_s == Pica::TexturingRegs::TextureConfig::WrapMode::ClampToBorder);
texture_border_color[tex_index].enable_t.Assign(
!profile.has_custom_border_color &&
config.wrap_t == Pica::TexturingRegs::TextureConfig::WrapMode::ClampToBorder);
requested_wrap[tex_index].s = pica_textures[tex_index].config.wrap_s;
requested_wrap[tex_index].t = pica_textures[tex_index].config.wrap_t;
texture_border_color[tex_index].enable_s.Assign(false);
texture_border_color[tex_index].enable_t.Assign(false);
}
const auto& stages = regs.GetTevStages();
@@ -75,6 +84,21 @@ TextureConfig::TextureConfig(const Pica::TexturingRegs& regs, const Profile& pro
}
}
void TextureConfig::ApplyProfile(const Profile& profile) {
// Emulate custom border color if needed and not supported.
if (profile.has_custom_border_color) {
return;
}
for (u32 i = 0; i < 3; i++) {
texture_border_color[i].enable_s.Assign(
requested_wrap[i].s == Pica::TexturingRegs::TextureConfig::WrapMode::ClampToBorder);
texture_border_color[i].enable_t.Assign(
requested_wrap[i].t == Pica::TexturingRegs::TextureConfig::WrapMode::ClampToBorder);
}
}
LightConfig::LightConfig(const Pica::LightingRegs& regs) {
if (regs.disable) {
return;
@@ -116,48 +140,48 @@ LightConfig::LightConfig(const Pica::LightingRegs& regs) {
if (lut_d0.enable) {
lut_d0.abs_input.Assign(regs.abs_lut_input.disable_d0 == 0);
lut_d0.type.Assign(regs.lut_input.d0.Value());
lut_d0.scale = regs.lut_scale.GetScale(regs.lut_scale.d0);
lut_d0.SetScale(regs.lut_scale.GetScale(regs.lut_scale.d0));
}
lut_d1.enable.Assign(regs.config1.disable_lut_d1 == 0);
if (lut_d1.enable) {
lut_d1.abs_input.Assign(regs.abs_lut_input.disable_d1 == 0);
lut_d1.type.Assign(regs.lut_input.d1.Value());
lut_d1.scale = regs.lut_scale.GetScale(regs.lut_scale.d1);
lut_d1.SetScale(regs.lut_scale.GetScale(regs.lut_scale.d1));
}
// This is a dummy field due to lack of the corresponding register
lut_sp.enable.Assign(1);
lut_sp.abs_input.Assign(regs.abs_lut_input.disable_sp == 0);
lut_sp.type.Assign(regs.lut_input.sp.Value());
lut_sp.scale = regs.lut_scale.GetScale(regs.lut_scale.sp);
lut_sp.SetScale(regs.lut_scale.GetScale(regs.lut_scale.sp));
lut_fr.enable.Assign(regs.config1.disable_lut_fr == 0);
if (lut_fr.enable) {
lut_fr.abs_input.Assign(regs.abs_lut_input.disable_fr == 0);
lut_fr.type.Assign(regs.lut_input.fr.Value());
lut_fr.scale = regs.lut_scale.GetScale(regs.lut_scale.fr);
lut_fr.SetScale(regs.lut_scale.GetScale(regs.lut_scale.fr));
}
lut_rr.enable.Assign(regs.config1.disable_lut_rr == 0);
if (lut_rr.enable) {
lut_rr.abs_input.Assign(regs.abs_lut_input.disable_rr == 0);
lut_rr.type.Assign(regs.lut_input.rr.Value());
lut_rr.scale = regs.lut_scale.GetScale(regs.lut_scale.rr);
lut_rr.SetScale(regs.lut_scale.GetScale(regs.lut_scale.rr));
}
lut_rg.enable.Assign(regs.config1.disable_lut_rg == 0);
if (lut_rg.enable) {
lut_rg.abs_input.Assign(regs.abs_lut_input.disable_rg == 0);
lut_rg.type.Assign(regs.lut_input.rg.Value());
lut_rg.scale = regs.lut_scale.GetScale(regs.lut_scale.rg);
lut_rg.SetScale(regs.lut_scale.GetScale(regs.lut_scale.rg));
}
lut_rb.enable.Assign(regs.config1.disable_lut_rb == 0);
if (lut_rb.enable) {
lut_rb.abs_input.Assign(regs.abs_lut_input.disable_rb == 0);
lut_rb.type.Assign(regs.lut_input.rb.Value());
lut_rb.scale = regs.lut_scale.GetScale(regs.lut_scale.rb);
lut_rb.SetScale(regs.lut_scale.GetScale(regs.lut_scale.rb));
}
}
@@ -186,8 +210,8 @@ ProcTexConfig::ProcTexConfig(const Pica::TexturingRegs& regs) {
lut_filter.Assign(regs.proctex_lut.filter);
}
FSConfig::FSConfig(const Pica::RegsInternal& regs, const UserConfig& user_, const Profile& profile)
: framebuffer{regs, profile}, texture{regs.texturing, profile}, lighting{regs.lighting},
proctex{regs.texturing}, user{user_} {}
FSConfig::FSConfig(const Pica::RegsInternal& regs)
: framebuffer{regs}, texture{regs.texturing}, lighting{regs.lighting}, proctex{regs.texturing} {
}
} // namespace Pica::Shader
@@ -8,16 +8,46 @@
#include "video_core/pica/regs_internal.h"
#include "video_core/shader/generator/profile.h"
#define LAYOUT_HASH static_cast<u64>(sizeof(T)), static_cast<u64>(alignof(T))
#define FIELD_HASH(x) static_cast<u64>(offsetof(T, x)), static_cast<u64>(sizeof(x))
namespace Pica::Shader {
/**
* WARNING!
*
* The following structs are saved to the disk as cache entries!
* Any modification to their members will invalidate the cache, breaking their
* transferable properties.
*
* Only modify the entries if such modifications are justified.
* If the struct is modified in a way that results in the exact same layout
* (for example, replacing an u8 with another u8 in the same place), then bump
* the struct's STRUCT_VERSION value.
*/
struct BlendConfig {
Pica::FramebufferRegs::BlendEquation eq;
Pica::FramebufferRegs::BlendFactor src_factor;
Pica::FramebufferRegs::BlendFactor dst_factor;
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = BlendConfig;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(eq), FIELD_HASH(src_factor), FIELD_HASH(dst_factor));
}
};
static_assert(std::has_unique_object_representations_v<BlendConfig>);
struct FramebufferConfig {
explicit FramebufferConfig(const Pica::RegsInternal& regs, const Profile& profile);
explicit FramebufferConfig(const Pica::RegsInternal& regs);
union {
u32 raw{};
@@ -26,9 +56,33 @@ struct FramebufferConfig {
BitField<5, 1, Pica::RasterizerRegs::DepthBuffering> depthmap_enable;
BitField<6, 4, Pica::FramebufferRegs::LogicOp> logic_op;
BitField<10, 1, u32> shadow_rendering;
BitField<11, 1, u32> alphablend_enable;
};
BlendConfig rgb_blend{};
BlendConfig alpha_blend{};
Pica::FramebufferRegs::LogicOp requested_logic_op{};
void ApplyProfile(const Profile& profile);
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = FramebufferConfig;
return Common::HashCombine(
STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(alpha_test_func), FIELD_HASH(scissor_test_mode), FIELD_HASH(depthmap_enable),
FIELD_HASH(logic_op), FIELD_HASH(shadow_rendering), FIELD_HASH(alphablend_enable),
FIELD_HASH(rgb_blend), FIELD_HASH(alpha_blend), FIELD_HASH(requested_logic_op),
// nested layout
BlendConfig::StructHash());
}
};
static_assert(std::has_unique_object_representations_v<FramebufferConfig>);
@@ -46,15 +100,48 @@ struct TevStageConfigRaw {
.scales_raw = scales_raw,
};
}
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = TevStageConfigRaw;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(sources_raw), FIELD_HASH(modifiers_raw),
FIELD_HASH(ops_raw), FIELD_HASH(scales_raw));
}
};
static_assert(std::has_unique_object_representations_v<TevStageConfigRaw>);
union TextureBorder {
u32 raw{};
BitField<0, 1, u32> enable_s;
BitField<1, 1, u32> enable_t;
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = TextureBorder;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(enable_s), FIELD_HASH(enable_t),
// nested layout
BlendConfig::StructHash());
}
};
static_assert(std::has_unique_object_representations_v<TextureBorder>);
struct TextureConfig {
explicit TextureConfig(const Pica::TexturingRegs& regs, const Profile& profile);
explicit TextureConfig(const Pica::TexturingRegs& regs);
union {
u32 raw{};
@@ -67,6 +154,48 @@ struct TextureConfig {
};
std::array<TextureBorder, 3> texture_border_color{};
std::array<TevStageConfigRaw, 6> tev_stages{};
struct TextureWrap {
Pica::TexturingRegs::TextureConfig::WrapMode s;
Pica::TexturingRegs::TextureConfig::WrapMode t;
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = TextureWrap;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(s), FIELD_HASH(t));
}
};
std::array<TextureWrap, 3> requested_wrap{};
void ApplyProfile(const Profile& profile);
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = TextureConfig;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(texture0_type), FIELD_HASH(texture2_use_coord1),
FIELD_HASH(combiner_buffer_input), FIELD_HASH(fog_mode),
FIELD_HASH(fog_flip), FIELD_HASH(shadow_texture_orthographic),
FIELD_HASH(texture_border_color), FIELD_HASH(tev_stages),
FIELD_HASH(requested_wrap),
// nested layout
TextureBorder::StructHash(), TevStageConfigRaw::StructHash(),
TextureWrap::StructHash());
}
};
static_assert(std::has_unique_object_representations_v<TextureConfig>);
@@ -80,6 +209,22 @@ union Light {
BitField<7, 1, u16> geometric_factor_0;
BitField<8, 1, u16> geometric_factor_1;
BitField<9, 1, u16> shadow_enable;
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = Light;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(num), FIELD_HASH(directional),
FIELD_HASH(two_sided_diffuse), FIELD_HASH(dist_atten_enable),
FIELD_HASH(spot_atten_enable), FIELD_HASH(geometric_factor_0),
FIELD_HASH(geometric_factor_1), FIELD_HASH(shadow_enable));
}
};
static_assert(std::has_unique_object_representations_v<Light>);
@@ -90,8 +235,31 @@ struct LutConfig {
BitField<1, 1, u32> abs_input;
BitField<2, 3, Pica::LightingRegs::LightingLutInput> type;
};
f32 scale;
// Needed for std::has_unique_object_representations_v
u32 scale_bits;
inline f32 GetScale() const noexcept {
return std::bit_cast<f32>(scale_bits);
}
inline void SetScale(f32 value) noexcept {
scale_bits = std::bit_cast<u32>(value);
}
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = LutConfig;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(enable), FIELD_HASH(abs_input), FIELD_HASH(type),
FIELD_HASH(scale_bits));
}
};
static_assert(std::has_unique_object_representations_v<LutConfig>);
struct LightConfig {
explicit LightConfig(const Pica::LightingRegs& regs);
@@ -122,7 +290,32 @@ struct LightConfig {
LutConfig lut_rg{};
LutConfig lut_rb{};
std::array<Light, 8> lights{};
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = LightConfig;
return Common::HashCombine(
STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(enable), FIELD_HASH(src_num), FIELD_HASH(bump_mode),
FIELD_HASH(bump_selector), FIELD_HASH(bump_renorm), FIELD_HASH(clamp_highlights),
FIELD_HASH(config), FIELD_HASH(enable_primary_alpha),
FIELD_HASH(enable_secondary_alpha), FIELD_HASH(enable_shadow),
FIELD_HASH(shadow_primary), FIELD_HASH(shadow_secondary), FIELD_HASH(shadow_invert),
FIELD_HASH(shadow_alpha), FIELD_HASH(shadow_selector), FIELD_HASH(lut_d0),
FIELD_HASH(lut_d1), FIELD_HASH(lut_sp), FIELD_HASH(lut_fr), FIELD_HASH(lut_rr),
FIELD_HASH(lut_rg), FIELD_HASH(lut_rb), FIELD_HASH(lights),
// nested layout
LutConfig::StructHash(), Light::StructHash());
}
};
static_assert(std::has_unique_object_representations_v<LightConfig>);
struct ProcTexConfig {
explicit ProcTexConfig(const Pica::TexturingRegs& regs);
@@ -148,18 +341,43 @@ struct ProcTexConfig {
s32 lut_offset3{};
u16 lod_min{};
u16 lod_max{};
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = ProcTexConfig;
return Common::HashCombine(
STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(enable), FIELD_HASH(coord), FIELD_HASH(u_clamp), FIELD_HASH(v_clamp),
FIELD_HASH(color_combiner), FIELD_HASH(alpha_combiner), FIELD_HASH(lut_filter),
FIELD_HASH(separate_alpha), FIELD_HASH(noise_enable), FIELD_HASH(u_shift),
FIELD_HASH(v_shift), FIELD_HASH(lut_width), FIELD_HASH(lut_offset0),
FIELD_HASH(lut_offset1), FIELD_HASH(lut_offset2), FIELD_HASH(lut_offset3),
FIELD_HASH(lod_min), FIELD_HASH(lod_max));
}
};
static_assert(std::has_unique_object_representations_v<ProcTexConfig>);
union UserConfig {
u32 raw{};
BitField<0, 1, u32> use_custom_normal;
// Whether a FSConfig + UserConfig combination can be
// cached to disk. Right now, this is true if the
// UserConfig was default constructed
bool IsCacheable() const {
return raw == u32{};
}
};
static_assert(std::has_unique_object_representations_v<UserConfig>);
struct FSConfig {
explicit FSConfig(const Pica::RegsInternal& regs, const UserConfig& user,
const Profile& profile);
explicit FSConfig(const Pica::RegsInternal& regs);
[[nodiscard]] bool TevStageUpdatesCombinerBufferColor(u32 stage_index) const {
return (stage_index < 4) && (texture.combiner_buffer_input & (1 << stage_index));
@@ -180,6 +398,11 @@ struct FSConfig {
framebuffer.shadow_rendering.Value();
}
void ApplyProfile(const Profile& profile) {
framebuffer.ApplyProfile(profile);
texture.ApplyProfile(profile);
}
bool operator==(const FSConfig& other) const noexcept {
return std::memcmp(this, &other, sizeof(FSConfig)) == 0;
}
@@ -192,8 +415,27 @@ struct FSConfig {
TextureConfig texture;
LightConfig lighting;
ProcTexConfig proctex;
UserConfig user;
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = FSConfig;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(framebuffer), FIELD_HASH(texture),
FIELD_HASH(lighting), FIELD_HASH(proctex),
// nested layout
FramebufferConfig::StructHash(), TextureConfig::StructHash(),
LightConfig::StructHash(), ProcTexConfig::StructHash());
}
};
static_assert(std::has_unique_object_representations_v<FSConfig>);
static_assert(std::is_trivially_copyable_v<FSConfig>);
} // namespace Pica::Shader
@@ -205,3 +447,6 @@ struct hash<Pica::Shader::FSConfig> {
}
};
} // namespace std
#undef FIELD_HASH
#undef LAYOUT_HASH
+42 -16
View File
@@ -1,27 +1,53 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "common/common_types.h"
namespace Pica::Shader {
struct VKFormatTraits {
u8 transfer_support{};
u8 blit_support{};
u8 attachment_support{};
u8 storage_support{};
u8 needs_conversion{};
u8 needs_emulation{};
u32 usage_flags{};
u32 aspect_flags{};
u32 native_format{};
auto operator<=>(const VKFormatTraits&) const = default;
};
struct Profile {
bool has_separable_shaders{};
bool has_clip_planes{};
bool has_geometry_shader{};
bool has_custom_border_color{};
bool has_fragment_shader_interlock{};
bool has_fragment_shader_barycentric{};
bool has_blend_minmax_factor{};
bool has_minus_one_to_one_range{};
bool has_logic_op{};
bool has_gl_ext_framebuffer_fetch{};
bool has_gl_arm_framebuffer_fetch{};
bool has_gl_nv_fragment_shader_interlock{};
bool has_gl_intel_fragment_shader_ordering{};
bool has_gl_nv_fragment_shader_barycentric{};
bool is_vulkan{};
u8 enable_accurate_mul{};
u8 has_separable_shaders{};
u8 has_clip_planes{};
u8 has_geometry_shader{};
u8 has_custom_border_color{};
u8 has_fragment_shader_interlock{};
u8 has_fragment_shader_barycentric{};
u8 has_blend_minmax_factor{};
u8 has_minus_one_to_one_range{};
u8 has_logic_op{};
u8 has_gl_ext_framebuffer_fetch{};
u8 has_gl_arm_framebuffer_fetch{};
u8 has_gl_nv_fragment_shader_interlock{};
u8 has_gl_intel_fragment_shader_ordering{};
u8 has_gl_nv_fragment_shader_barycentric{};
u8 vk_disable_spirv_optimizer{};
u8 vk_use_spirv_generator{};
std::array<VKFormatTraits, 16> vk_format_traits{};
u8 is_vulkan{};
auto operator<=>(const Profile&) const = default;
};
} // namespace Pica::Shader
+25 -24
View File
@@ -11,19 +11,25 @@
namespace Pica::Shader::Generator {
void PicaGSConfigState::Init(const Pica::RegsInternal& regs, bool use_clip_planes_) {
use_clip_planes = use_clip_planes_;
void PicaGSConfigState::Init(const Pica::RegsInternal& regs) {
vs_output_attributes_count = Common::BitSet<u32>(regs.vs.output_mask).Count();
gs_output_attributes_count = vs_output_attributes_count;
vs_output_total = regs.rasterizer.vs_output_total;
vs_output_attributes = Common::BitSet<u32>(regs.vs.output_mask).Count();
gs_output_attributes = vs_output_attributes;
memcpy(vs_output_attributes.data(), regs.rasterizer.vs_output_attributes,
vs_output_total * sizeof(Pica::RasterizerRegs::VSOutputAttributes));
}
std::array<PicaGSConfigState::SemanticMap, 24> PicaGSConfigState::GetSemanticMaps() const {
std::array<SemanticMap, 24> semantic_maps{};
semantic_maps.fill({16, 0});
for (u32 attrib = 0; attrib < regs.rasterizer.vs_output_total; ++attrib) {
for (u32 attrib = 0; attrib < vs_output_total; ++attrib) {
const std::array semantics{
regs.rasterizer.vs_output_attributes[attrib].map_x.Value(),
regs.rasterizer.vs_output_attributes[attrib].map_y.Value(),
regs.rasterizer.vs_output_attributes[attrib].map_z.Value(),
regs.rasterizer.vs_output_attributes[attrib].map_w.Value(),
vs_output_attributes[attrib].map_x.Value(),
vs_output_attributes[attrib].map_y.Value(),
vs_output_attributes[attrib].map_z.Value(),
vs_output_attributes[attrib].map_w.Value(),
};
for (u32 comp = 0; comp < 4; ++comp) {
const auto semantic = semantics[comp];
@@ -34,39 +40,34 @@ void PicaGSConfigState::Init(const Pica::RegsInternal& regs, bool use_clip_plane
}
}
}
return semantic_maps;
}
void PicaVSConfigState::Init(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup,
bool use_clip_planes_, bool use_geometry_shader_, bool accurate_mul_) {
use_clip_planes = use_clip_planes_;
use_geometry_shader = use_geometry_shader_;
sanitize_mul = accurate_mul_;
void PicaVSConfigState::Init(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup) {
setup.DoProgramCodeFixup();
program_hash = setup.GetProgramCodeHash();
swizzle_hash = setup.GetSwizzleDataHash();
main_offset = regs.vs.main_offset;
lighting_disable = regs.lighting.disable;
num_outputs = 0;
load_flags.fill(AttribLoadFlags::Float);
output_map.fill(16);
for (u32 reg : Common::BitSet<u32>(regs.vs.output_mask)) {
output_map[reg] = num_outputs++;
}
if (!use_geometry_shader_) {
gs_state.Init(regs, use_clip_planes_);
}
gs_state.Init(regs);
}
PicaVSConfig::PicaVSConfig(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup,
bool use_clip_planes_, bool use_geometry_shader_, bool accurate_mul_) {
state.Init(regs, setup, use_clip_planes_, use_geometry_shader_, accurate_mul_);
PicaVSConfig::PicaVSConfig(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup) {
state.Init(regs, setup);
}
PicaFixedGSConfig::PicaFixedGSConfig(const Pica::RegsInternal& regs, bool use_clip_planes_) {
state.Init(regs, use_clip_planes_);
PicaFixedGSConfig::PicaFixedGSConfig(const Pica::RegsInternal& regs) {
state.Init(regs);
}
} // namespace Pica::Shader::Generator
+95 -19
View File
@@ -1,10 +1,14 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/hash.h"
#include "video_core/pica/regs_rasterizer.h"
#define LAYOUT_HASH static_cast<u64>(sizeof(T)), static_cast<u64>(alignof(T))
#define FIELD_HASH(x) static_cast<u64>(offsetof(T, x)), static_cast<u64>(sizeof(x))
namespace Pica {
struct RegsInternal;
@@ -31,7 +35,7 @@ enum Attributes {
ATTRIBUTE_VIEW,
};
enum class AttribLoadFlags {
enum class AttribLoadFlags : u32 {
Float = 1 << 0,
Sint = 1 << 1,
Uint = 1 << 2,
@@ -39,25 +43,53 @@ enum class AttribLoadFlags {
};
DECLARE_ENUM_FLAG_OPERATORS(AttribLoadFlags)
/**
* WARNING!
*
* The following structs are saved to the disk as cache entries!
* Any modification to their members will invalidate the cache, breaking their
* transferable properties.
*
* Only modify the entries if such modifications are justified.
* If the struct is modified in a way that results in the exact same layout
* (for example, replacing an u8 with another u8 in the same place), then bump
* the struct's STRUCT_VERSION value.
*/
/**
* This struct contains common information to identify a GLSL geometry shader generated from
* PICA geometry shader.
*/
struct PicaGSConfigState {
void Init(const Pica::RegsInternal& regs, bool use_clip_planes_);
void Init(const Pica::RegsInternal& regs);
bool use_clip_planes;
u32 vs_output_attributes_count;
u32 gs_output_attributes_count;
u32 vs_output_total;
u32 vs_output_attributes;
u32 gs_output_attributes;
std::array<Pica::RasterizerRegs::VSOutputAttributes, 7> vs_output_attributes;
// semantic_maps[semantic name] -> GS output attribute index + component index
struct SemanticMap {
u32 attribute_index;
u32 component_index;
};
std::array<SemanticMap, 24> GetSemanticMaps() const;
// semantic_maps[semantic name] -> GS output attribute index + component index
std::array<SemanticMap, 24> semantic_maps;
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = PicaGSConfigState;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(vs_output_attributes_count),
FIELD_HASH(gs_output_attributes_count),
FIELD_HASH(vs_output_total), FIELD_HASH(vs_output_attributes));
}
};
/**
@@ -65,25 +97,48 @@ struct PicaGSConfigState {
* PICA vertex shader.
*/
struct PicaVSConfigState {
void Init(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup, bool use_clip_planes_,
bool use_geometry_shader_, bool accurate_mul_);
bool use_clip_planes;
bool use_geometry_shader;
void Init(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup);
u8 lighting_disable;
u64 program_hash;
u64 swizzle_hash;
u32 main_offset;
bool sanitize_mul;
u32 num_outputs;
// Load operations to apply to the input vertex data
std::array<AttribLoadFlags, 16> load_flags;
// output_map[output register index] -> output attribute index
std::array<u32, 16> output_map;
// These represent relevant input vertex attributes
struct VAttr {
u8 location;
u8 type;
u8 size;
};
u8 used_input_vertex_attributes;
std::array<VAttr, 16> input_vertex_attributes;
PicaGSConfigState gs_state;
static consteval u64 StructHash() {
constexpr u64 STRUCT_VERSION = 0;
using T = PicaVSConfigState;
return Common::HashCombine(STRUCT_VERSION,
// layout
LAYOUT_HASH,
// fields
FIELD_HASH(lighting_disable), FIELD_HASH(program_hash),
FIELD_HASH(swizzle_hash), FIELD_HASH(main_offset),
FIELD_HASH(num_outputs), FIELD_HASH(output_map),
FIELD_HASH(used_input_vertex_attributes),
FIELD_HASH(input_vertex_attributes), FIELD_HASH(gs_state),
// nested layout
PicaGSConfigState::StructHash());
}
};
/**
@@ -91,8 +146,21 @@ struct PicaVSConfigState {
* shader.
*/
struct PicaVSConfig : Common::HashableStruct<PicaVSConfigState> {
explicit PicaVSConfig(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup,
bool use_clip_planes_, bool use_geometry_shader_, bool accurate_mul_);
PicaVSConfig() = default;
explicit PicaVSConfig(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup);
};
/**
* This struct contains complementary user/driver information to generate a vertex shader.
*/
struct ExtraVSConfig {
u8 use_clip_planes;
u8 use_geometry_shader;
u8 sanitize_mul;
u8 separable_shader;
// Load operations to apply to the input vertex data
std::array<AttribLoadFlags, 16> load_flags;
};
/**
@@ -100,7 +168,12 @@ struct PicaVSConfig : Common::HashableStruct<PicaVSConfigState> {
* shader pipeline
*/
struct PicaFixedGSConfig : Common::HashableStruct<PicaGSConfigState> {
explicit PicaFixedGSConfig(const Pica::RegsInternal& regs, bool use_clip_planes_);
explicit PicaFixedGSConfig(const Pica::RegsInternal& regs);
};
struct ExtraFixedGSConfig {
u8 use_clip_planes;
u8 separable_shader;
};
} // namespace Pica::Shader::Generator
@@ -120,3 +193,6 @@ struct hash<Pica::Shader::Generator::PicaFixedGSConfig> {
}
};
} // namespace std
#undef FIELD_HASH
#undef LAYOUT_HASH
@@ -21,6 +21,7 @@ FragmentModule::FragmentModule(const FSConfig& config_, const Profile& profile_)
: Sirit::Module{SPIRV_VERSION_1_3}, config{config_}, profile{profile_},
use_fragment_shader_barycentric{profile.has_fragment_shader_barycentric &&
config.lighting.enable} {
config.ApplyProfile(profile_);
DefineArithmeticTypes();
DefineUniformStructs();
DefineInterface();
@@ -454,7 +455,7 @@ void FragmentModule::WriteLighting() {
const Id value{
get_lut_value(LightingRegs::SpotlightAttenuationSampler(light_config.num),
light_config.num, lighting.lut_sp.type, lighting.lut_sp.abs_input)};
spot_atten = OpFMul(f32_id, ConstF32(lighting.lut_sp.scale), value);
spot_atten = OpFMul(f32_id, ConstF32(lighting.lut_sp.GetScale()), value);
}
// If enabled, compute distance attenuation value
@@ -486,7 +487,7 @@ void FragmentModule::WriteLighting() {
const Id value{get_lut_value(LightingRegs::LightingSampler::Distribution0,
light_config.num, lighting.lut_d0.type,
lighting.lut_d0.abs_input)};
d0_lut_value = OpFMul(f32_id, ConstF32(lighting.lut_d0.scale), value);
d0_lut_value = OpFMul(f32_id, ConstF32(lighting.lut_d0.GetScale()), value);
}
Id specular_0{OpVectorTimesScalar(vec_ids.Get(3), GetLightMember(0), d0_lut_value)};
@@ -503,7 +504,7 @@ void FragmentModule::WriteLighting() {
light_config.num, lighting.lut_rr.type,
lighting.lut_rr.abs_input)};
refl_value_r = OpFMul(f32_id, ConstF32(lighting.lut_rr.scale), value);
refl_value_r = OpFMul(f32_id, ConstF32(lighting.lut_rr.GetScale()), value);
}
// If enabled, lookup ReflectGreen value, otherwise, ReflectRed value is used
@@ -515,7 +516,7 @@ void FragmentModule::WriteLighting() {
light_config.num, lighting.lut_rg.type,
lighting.lut_rg.abs_input)};
refl_value_g = OpFMul(f32_id, ConstF32(lighting.lut_rg.scale), value);
refl_value_g = OpFMul(f32_id, ConstF32(lighting.lut_rg.GetScale()), value);
}
// If enabled, lookup ReflectBlue value, otherwise, ReflectRed value is used
@@ -526,7 +527,7 @@ void FragmentModule::WriteLighting() {
const Id value{get_lut_value(LightingRegs::LightingSampler::ReflectBlue,
light_config.num, lighting.lut_rb.type,
lighting.lut_rb.abs_input)};
refl_value_b = OpFMul(f32_id, ConstF32(lighting.lut_rb.scale), value);
refl_value_b = OpFMul(f32_id, ConstF32(lighting.lut_rb.GetScale()), value);
}
// Specular 1 component
@@ -538,7 +539,7 @@ void FragmentModule::WriteLighting() {
const Id value{get_lut_value(LightingRegs::LightingSampler::Distribution1,
light_config.num, lighting.lut_d1.type,
lighting.lut_d1.abs_input)};
d1_lut_value = OpFMul(f32_id, ConstF32(lighting.lut_d1.scale), value);
d1_lut_value = OpFMul(f32_id, ConstF32(lighting.lut_d1.GetScale()), value);
}
const Id refl_value{
@@ -559,7 +560,7 @@ void FragmentModule::WriteLighting() {
// Lookup fresnel LUT value
Id value{get_lut_value(LightingRegs::LightingSampler::Fresnel, light_config.num,
lighting.lut_fr.type, lighting.lut_fr.abs_input)};
value = OpFMul(f32_id, ConstF32(lighting.lut_fr.scale), value);
value = OpFMul(f32_id, ConstF32(lighting.lut_fr.GetScale()), value);
// Enabled for diffuse lighting alpha component
if (lighting.enable_primary_alpha) {
@@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -220,7 +220,7 @@ private:
Id CompareShadow(Id pixel, Id z);
private:
const FSConfig& config;
FSConfig config;
const Profile& profile;
bool use_fragment_shader_barycentric{};