mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-07-11 01:34:03 +02:00
video_core: vulkan: Add disk shader cache (#1725)
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{};
|
||||
|
||||
Reference in New Issue
Block a user