diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index fce2a244c..f2210f225 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -264,6 +264,8 @@ void Config::ReadValues() { } // Web Service + NetSettings::values.enable_telemetry = + sdl2_config->GetBoolean("WebService", "enable_telemetry", false); NetSettings::values.web_api_url = sdl2_config->GetString("WebService", "web_api_url", "https://api.citra-emu.org"); NetSettings::values.citra_username = sdl2_config->GetString("WebService", "citra_username", ""); diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index c46395fea..31df12c5f 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -353,6 +353,9 @@ gdbstub_port=24689 # To LLE a service module add "LLE\=true" [WebService] +# Whether or not to enable telemetry +# 0 (default): No, 1: Yes +enable_telemetry = # URL for Web API web_api_url = https://api.citra-emu.org # Username and token for Citra Web Service diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 4010a1b81..9ffee4211 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -38,6 +38,7 @@ #include "core/hle/service/nfc/nfc.h" #include "core/loader/loader.h" #include "core/savestate.h" +#include "core/telemetry_session.h" #include "jni/android_common/android_common.h" #include "jni/applets/mii_selector.h" #include "jni/applets/swkbd.h" @@ -165,7 +166,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { #elif ENABLE_VULKAN window = std::make_unique(s_surf, vulkan_library); #else - // TODO: Add a null renderer backend for this, perhaps. +// TODO: Add a null renderer backend for this, perhaps. #error "At least one renderer must be enabled." #endif break; @@ -207,6 +208,9 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { return load_result; } + auto& telemetry_session = system.TelemetrySession(); + telemetry_session.AddField(Common::Telemetry::FieldType::App, "Frontend", "Android"); + stop_run = false; pause_emulation = false; diff --git a/src/common/detached_tasks.h b/src/common/detached_tasks.h index 251cbab4f..5dd8fc27b 100644 --- a/src/common/detached_tasks.h +++ b/src/common/detached_tasks.h @@ -12,10 +12,10 @@ namespace Common { /** * A background manager which ensures that all detached task is finished before program exits. * - * Some tasks prefer executing asynchronously and don't care - * about the result. These tasks are suitable for std::thread::detach(). - * However, this is unsafe if the task is launched just before the program exits - * so we need to block on these tasks on program exit. + * Some tasks, telemetry submission for example, prefer executing asynchronously and don't care + * about the result. These tasks are suitable for std::thread::detach(). However, this is unsafe if + * the task is launched just before the program exits (which is a common case for telemetry), so we + * need to block on these tasks on program exit. * * To make detached task safe, a single DetachedTasks object should be placed in the main(), and * call WaitForAllTasks() after all program execution but before global/static variable destruction. diff --git a/src/core/core.cpp b/src/core/core.cpp index 6acaf6048..7e969f20a 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -310,6 +310,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st restore_plugin_context.reset(); } + telemetry_session->AddInitialInfo(*app_loader); std::shared_ptr process; const Loader::ResultStatus load_result{app_loader->Load(process)}; if (Loader::ResultStatus::Success != load_result) { @@ -565,6 +566,17 @@ void System::RegisterImageInterface(std::shared_ptr im } void System::Shutdown(bool is_deserializing) { + // Log last frame performance stats + const auto perf_results = GetAndResetPerfStats(); + constexpr auto performance = Common::Telemetry::FieldType::Performance; + + telemetry_session->AddField(performance, "Shutdown_EmulationSpeed", + perf_results.emulation_speed * 100.0); + telemetry_session->AddField(performance, "Shutdown_Framerate", perf_results.game_fps); + telemetry_session->AddField(performance, "Shutdown_Frametime", perf_results.frametime * 1000.0); + telemetry_session->AddField(performance, "Mean_Frametime_MS", + perf_stats ? perf_stats->GetMeanFrametime() : 0); + // Shutdown emulation session is_powered_on = false; diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 3e552dfc4..a3befffd8 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -34,6 +34,7 @@ #include "core/hw/aes/ccm.h" #include "core/hw/aes/key.h" #include "core/loader/loader.h" +#include "core/telemetry_session.h" SERVICE_CONSTRUCT_IMPL(Service::APT::Module) @@ -273,6 +274,10 @@ void Module::APTInterface::GetSharedFont(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + // Log in telemetry if the game uses the shared font + apt->system.TelemetrySession().AddField(Common::Telemetry::FieldType::Session, + "RequiresSharedFont", true); + if (!apt->shared_font_loaded) { // On real 3DS, font loading happens on booting. However, we load it on demand to coordinate // with CFG region auto configuration, which happens later than APT initialization. diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 29ad40cac..337177a88 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -26,6 +26,7 @@ #include "core/loader/smdh.h" #include "core/memory.h" #include "core/system_titles.h" +#include "core/telemetry_session.h" #include "network/network.h" namespace Loader { @@ -273,6 +274,9 @@ ResultStatus AppLoader_NCCH::Load(std::shared_ptr& process) { overlay_ncch = &update_ncch; } + system.TelemetrySession().AddField(Common::Telemetry::FieldType::Session, "ProgramId", + program_id); + if (auto room_member = Network::GetRoomMember().lock()) { Network::GameInfo game_info; ReadTitle(game_info.name); diff --git a/src/lime/config.cpp b/src/lime/config.cpp index dec7acbd1..cfd0f57b9 100644 --- a/src/lime/config.cpp +++ b/src/lime/config.cpp @@ -326,6 +326,8 @@ void Config::ReadValues() { } // Web Service + NetSettings::values.enable_telemetry = + sdl2_config->GetBoolean("WebService", "enable_telemetry", false); NetSettings::values.web_api_url = sdl2_config->GetString("WebService", "web_api_url", "https://api.citra-emu.org"); NetSettings::values.citra_username = sdl2_config->GetString("WebService", "citra_username", ""); diff --git a/src/lime/default_ini.h b/src/lime/default_ini.h index b3cdf9293..be3c67948 100644 --- a/src/lime/default_ini.h +++ b/src/lime/default_ini.h @@ -361,6 +361,9 @@ renderer_debug = # To LLE a service module add "LLE\=true" [WebService] +# Whether or not to enable telemetry +# 0 (default): No, 1: Yes +enable_telemetry = # URL for Web API web_api_url = https://api.citra-emu.org # Username and token for Citra Web Service diff --git a/src/lime/lime.cpp b/src/lime/lime.cpp index 5e882f6b0..a3f57b34c 100644 --- a/src/lime/lime.cpp +++ b/src/lime/lime.cpp @@ -39,6 +39,7 @@ #include "core/hle/service/am/am.h" #include "core/hle/service/cfg/cfg.h" #include "core/movie.h" +#include "core/telemetry_session.h" #include "input_common/main.h" #include "network/network.h" #include "video_core/gpu.h" @@ -385,7 +386,7 @@ int main(int argc, char** argv) { #elif ENABLE_SOFTWARE_RENDERER return std::make_unique(system, fullscreen, is_secondary); #else - // TODO: Add a null renderer backend for this, perhaps. +// TODO: Add a null renderer backend for this, perhaps. #error "At least one renderer must be enabled." #endif } @@ -435,6 +436,8 @@ int main(int argc, char** argv) { break; } + system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "SDL"); + if (use_multiplayer) { if (auto member = Network::GetRoomMember().lock()) { member->BindOnChatMessageRecieved(OnMessageReceived); diff --git a/src/lime_qt/compatdb.cpp b/src/lime_qt/compatdb.cpp index 506706fe9..fe51b264a 100644 --- a/src/lime_qt/compatdb.cpp +++ b/src/lime_qt/compatdb.cpp @@ -6,13 +6,15 @@ #include #include #include +#include "common/telemetry.h" #include "core/core.h" +#include "core/telemetry_session.h" #include "lime_qt/compatdb.h" #include "ui_compatdb.h" -CompatDB::CompatDB(QWidget* parent) +CompatDB::CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent) : QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), - ui{std::make_unique()} { + ui{std::make_unique()}, telemetry_session{telemetry_session_} { ui->setupUi(this); connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext); connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext); @@ -50,11 +52,15 @@ void CompatDB::Submit() { case CompatDBPage::Final: back(); LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId()); + telemetry_session.AddField(Common::Telemetry::FieldType::UserFeedback, "Compatibility", + compatibility->checkedId()); button(NextButton)->setEnabled(false); button(NextButton)->setText(tr("Submitting")); button(CancelButton)->setVisible(false); + testcase_watcher.setFuture( + QtConcurrent::run([this] { return telemetry_session.SubmitTestcase(); })); break; default: LOG_ERROR(Frontend, "Unexpected page: {}", currentId()); diff --git a/src/lime_qt/compatdb.h b/src/lime_qt/compatdb.h index 5381f67f7..b8ae5e209 100644 --- a/src/lime_qt/compatdb.h +++ b/src/lime_qt/compatdb.h @@ -8,6 +8,10 @@ #include #include +namespace Core { +class TelemetrySession; +} + namespace Ui { class CompatDB; } @@ -16,7 +20,7 @@ class CompatDB : public QWizard { Q_OBJECT public: - explicit CompatDB(QWidget* parent = nullptr); + explicit CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent = nullptr); ~CompatDB(); private: @@ -27,4 +31,6 @@ private: void Submit(); void OnTestcaseSubmitted(); void EnableNext(); + + Core::TelemetrySession& telemetry_session; }; diff --git a/src/lime_qt/configuration/config.cpp b/src/lime_qt/configuration/config.cpp index 3cadfb8ca..1ca4b3fe0 100644 --- a/src/lime_qt/configuration/config.cpp +++ b/src/lime_qt/configuration/config.cpp @@ -836,6 +836,8 @@ void Config::ReadUpdaterValues() { void Config::ReadWebServiceValues() { qt_config->beginGroup(QStringLiteral("WebService")); + NetSettings::values.enable_telemetry = + ReadSetting(QStringLiteral("enable_telemetry"), false).toBool(); NetSettings::values.web_api_url = ReadSetting(QStringLiteral("web_api_url"), QStringLiteral("https://api.citra-emu.org")) .toString() @@ -1315,6 +1317,7 @@ void Config::SaveUpdaterValues() { void Config::SaveWebServiceValues() { qt_config->beginGroup(QStringLiteral("WebService")); + WriteSetting(QStringLiteral("enable_telemetry"), NetSettings::values.enable_telemetry, false); WriteSetting(QStringLiteral("web_api_url"), QString::fromStdString(NetSettings::values.web_api_url), QStringLiteral("https://api.citra-emu.org")); diff --git a/src/lime_qt/configuration/configure_dialog.cpp b/src/lime_qt/configuration/configure_dialog.cpp index 46e7c9bc4..4e967ebb0 100644 --- a/src/lime_qt/configuration/configure_dialog.cpp +++ b/src/lime_qt/configuration/configure_dialog.cpp @@ -57,6 +57,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor ui->tabWidget->addTab(ui_tab.get(), tr("UI")); hotkeys_tab->Populate(registry); + web_tab->SetWebServiceConfigEnabled(enable_web_config); PopulateSelectionList(); diff --git a/src/lime_qt/configuration/configure_web.cpp b/src/lime_qt/configuration/configure_web.cpp index a707abd62..da4796382 100644 --- a/src/lime_qt/configuration/configure_web.cpp +++ b/src/lime_qt/configuration/configure_web.cpp @@ -11,12 +11,41 @@ #include "network/network_settings.h" #include "ui_configure_web.h" +static constexpr char token_delimiter{':'}; + +static std::string GenerateDisplayToken(const std::string& username, const std::string& token) { + if (username.empty() || token.empty()) { + return {}; + } + + const std::string unencoded_display_token{username + token_delimiter + token}; + QByteArray b{unencoded_display_token.c_str()}; + QByteArray b64 = b.toBase64(); + return b64.toStdString(); +} + +static std::string UsernameFromDisplayToken(const std::string& display_token) { + const std::string unencoded_display_token{ + QByteArray::fromBase64(display_token.c_str()).toStdString()}; + return unencoded_display_token.substr(0, unencoded_display_token.find(token_delimiter)); +} + +static std::string TokenFromDisplayToken(const std::string& display_token) { + const std::string unencoded_display_token{ + QByteArray::fromBase64(display_token.c_str()).toStdString()}; + return unencoded_display_token.substr(unencoded_display_token.find(token_delimiter) + 1); +} + ConfigureWeb::ConfigureWeb(QWidget* parent) : QWidget(parent), ui(std::make_unique()) { ui->setupUi(this); + connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this, + &ConfigureWeb::RefreshTelemetryID); + connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin); + connect(&verify_watcher, &QFutureWatcher::finished, this, &ConfigureWeb::OnLoginVerified); #ifndef USE_DISCORD_PRESENCE - ui->discord_group->setEnabled(false); + ui->discord_group->setVisible(false); #endif SetConfiguration(); } @@ -24,13 +53,113 @@ ConfigureWeb::ConfigureWeb(QWidget* parent) ConfigureWeb::~ConfigureWeb() = default; void ConfigureWeb::SetConfiguration() { + ui->web_credentials_disclaimer->setWordWrap(true); + ui->telemetry_learn_more->setOpenExternalLinks(true); + ui->telemetry_learn_more->setText(tr("Learn more")); + + ui->web_signup_link->setOpenExternalLinks(true); + ui->web_signup_link->setText( + tr("Sign up")); + ui->web_token_info_link->setOpenExternalLinks(true); + ui->web_token_info_link->setText( + tr("What is my token?")); + + ui->toggle_telemetry->setChecked(NetSettings::values.enable_telemetry); + + if (NetSettings::values.citra_username.empty()) { + ui->username->setText(tr("Unspecified")); + } else { + ui->username->setText(QString::fromStdString(NetSettings::values.citra_username)); + } + + ui->edit_token->setText(QString::fromStdString( + GenerateDisplayToken(NetSettings::values.citra_username, NetSettings::values.citra_token))); + + // Connect after setting the values, to avoid calling OnLoginChanged now + connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); + ui->label_telemetry_id->setText( + tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper())); + user_verified = true; + ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence.GetValue()); } void ConfigureWeb::ApplyConfiguration() { + NetSettings::values.enable_telemetry = ui->toggle_telemetry->isChecked(); UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked(); + if (user_verified) { + NetSettings::values.citra_username = + UsernameFromDisplayToken(ui->edit_token->text().toStdString()); + NetSettings::values.citra_token = + TokenFromDisplayToken(ui->edit_token->text().toStdString()); + } else { + QMessageBox::warning( + this, tr("Token not verified"), + tr("Token was not verified. The change to your token has not been saved.")); + } +} + +void ConfigureWeb::RefreshTelemetryID() { + const u64 new_telemetry_id{Core::RegenerateTelemetryId()}; + ui->label_telemetry_id->setText( + tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper())); +} + +void ConfigureWeb::OnLoginChanged() { + if (ui->edit_token->text().isEmpty()) { + user_verified = true; + + const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16); + ui->label_token_verified->setPixmap(pixmap); + } else { + user_verified = false; + + const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16); + ui->label_token_verified->setPixmap(pixmap); + } +} + +void ConfigureWeb::VerifyLogin() { + ui->button_verify_login->setDisabled(true); + ui->button_verify_login->setText(tr("Verifying...")); + verify_watcher.setFuture(QtConcurrent::run( + [username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()), + token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] { + return Core::VerifyLogin(username, token); + })); +} + +void ConfigureWeb::OnLoginVerified() { + ui->button_verify_login->setEnabled(true); + ui->button_verify_login->setText(tr("Verify")); + if (verify_watcher.result()) { + user_verified = true; + + const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16); + ui->label_token_verified->setPixmap(pixmap); + ui->username->setText( + QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString()))); + } else { + const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16); + ui->label_token_verified->setPixmap(pixmap); + ui->username->setText(tr("Unspecified")); + QMessageBox::critical(this, tr("Verification failed"), + tr("Verification failed. Check that you have entered your token " + "correctly, and that your internet connection is working.")); + } } void ConfigureWeb::RetranslateUI() { ui->retranslateUi(this); } + +void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) { + ui->label_disable_info->setVisible(!enabled); + ui->groupBoxWebConfig->setEnabled(enabled); +} diff --git a/src/lime_qt/configuration/configure_web.h b/src/lime_qt/configuration/configure_web.h index 46f29bd2c..a9b2566f8 100644 --- a/src/lime_qt/configuration/configure_web.h +++ b/src/lime_qt/configuration/configure_web.h @@ -22,6 +22,7 @@ public: void ApplyConfiguration(); void RetranslateUI(); void SetConfiguration(); + void SetWebServiceConfigEnabled(bool enabled); private: void RefreshTelemetryID(); diff --git a/src/lime_qt/configuration/configure_web.ui b/src/lime_qt/configuration/configure_web.ui index a68936c59..fdae590bf 100644 --- a/src/lime_qt/configuration/configure_web.ui +++ b/src/lime_qt/configuration/configure_web.ui @@ -14,6 +14,163 @@ Form + + + + + + Lime3DS Web Service + + + + + + Currently not supported by Lime. By providing your username and token, you agree to allow Citra to collect additional usage data, which may include user identifying information. + + + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Verify + + + + + + + Sign up + + + + + + + + + + Token: + + + + + + + + + + Username: + + + + + + + 80 + + + QLineEdit::Password + + + + + + + What is my token? + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Web Service configuration can only be changed when a public room isn't being hosted. + + + true + + + + + + + Telemetry + + + + + + Share anonymous usage data with the Lime3DS team + + + + + + + Learn more + + + + + + + + + Telemetry ID: + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Regenerate + + + + + + + + + + @@ -46,6 +203,10 @@ + edit_token + button_verify_login + toggle_telemetry + button_regenerate_telemetry_id toggle_discordrpc diff --git a/src/lime_qt/main.cpp b/src/lime_qt/main.cpp index a706bc1b9..ddd2860bc 100644 --- a/src/lime_qt/main.cpp +++ b/src/lime_qt/main.cpp @@ -15,6 +15,7 @@ #include #include #include +#include "core/telemetry_session.h" #ifdef __APPLE__ #include // for chdir #endif @@ -127,6 +128,27 @@ constexpr int default_mouse_timeout = 2500; * is a bitfield "callout_flags" options, used to track if a message has already been shown to the * user. This is 32-bits - if we have more than 32 callouts, we should retire and recycle old ones. */ +enum class CalloutFlag : uint32_t { + Telemetry = 0x1, +}; + +void GMainWindow::ShowTelemetryCallout() { + if (UISettings::values.callout_flags.GetValue() & + static_cast(CalloutFlag::Telemetry)) { + return; + } + + UISettings::values.callout_flags = + UISettings::values.callout_flags.GetValue() | static_cast(CalloutFlag::Telemetry); + const QString telemetry_message = + tr("Anonymous " + "data is collected to help improve Citra. " + "

Would you like to share your usage data with us?"); + if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) == QMessageBox::Yes) { + NetSettings::values.enable_telemetry = true; + system.ApplySettings(); + } +} const int GMainWindow::max_recent_files_item; @@ -241,6 +263,9 @@ GMainWindow::GMainWindow(Core::System& system_) game_list->LoadCompatibilityList(); game_list->PopulateAsync(UISettings::values.game_dirs); + // Show one-time "callout" messages to the user + ShowTelemetryCallout(); + mouse_hide_timer.setInterval(default_mouse_timeout); connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor); connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::OnMouseActivity); @@ -1222,6 +1247,7 @@ bool GMainWindow::LoadROM(const QString& filename) { game_path = filename; + system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "Qt"); return true; } @@ -1967,7 +1993,7 @@ void GMainWindow::OnLoadComplete() { void GMainWindow::OnMenuReportCompatibility() { if (!NetSettings::values.citra_token.empty() && !NetSettings::values.citra_username.empty()) { - CompatDB compatdb{this}; + CompatDB compatdb{system.TelemetrySession(), this}; compatdb.exec(); } else { QMessageBox::critical(this, tr("Missing Citra Account"), diff --git a/src/lime_qt/main.h b/src/lime_qt/main.h index 4188ed0d8..589c5f5d0 100644 --- a/src/lime_qt/main.h +++ b/src/lime_qt/main.h @@ -152,6 +152,7 @@ private: void BootGame(const QString& filename); void ShutdownGame(); + void ShowTelemetryCallout(); void SetDiscordEnabled(bool state); void LoadAmiibo(const QString& filename); diff --git a/src/video_core/renderer_opengl/gl_driver.cpp b/src/video_core/renderer_opengl/gl_driver.cpp index 63b05bd85..ceb018f32 100644 --- a/src/video_core/renderer_opengl/gl_driver.cpp +++ b/src/video_core/renderer_opengl/gl_driver.cpp @@ -5,6 +5,7 @@ #include #include "common/assert.h" #include "common/settings.h" +#include "core/telemetry_session.h" #include "video_core/custom_textures/custom_format.h" #include "video_core/renderer_opengl/gl_driver.h" #include "video_core/renderer_opengl/gl_vars.h" @@ -69,11 +70,12 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum level = Common::Log::Level::Debug; break; } + LOG_GENERIC(Common::Log::Class::Render_OpenGL, level, "{} {} {}: {}", GetSource(source), GetType(type), id, message); } -Driver::Driver() { +Driver::Driver(Core::TelemetrySession& telemetry_session_) : telemetry_session{telemetry_session_} { const bool enable_debug = Settings::values.renderer_debug.GetValue(); if (enable_debug) { glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); @@ -133,6 +135,12 @@ void Driver::ReportDriverInfo() { LOG_INFO(Render_OpenGL, "GL_VERSION: {}", gl_version); LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor); LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model); + + // Add the information to the telemetry system + constexpr auto user_system = Common::Telemetry::FieldType::UserSystem; + telemetry_session.AddField(user_system, "GPU_Vendor", std::string{gpu_vendor}); + telemetry_session.AddField(user_system, "GPU_Model", std::string{gpu_model}); + telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string{gl_version}); } void Driver::DeduceGLES() { diff --git a/src/video_core/renderer_opengl/gl_driver.h b/src/video_core/renderer_opengl/gl_driver.h index 2d67c3dc9..430469da7 100644 --- a/src/video_core/renderer_opengl/gl_driver.h +++ b/src/video_core/renderer_opengl/gl_driver.h @@ -7,6 +7,10 @@ #include #include "common/common_types.h" +namespace Core { +class TelemetrySession; +} + namespace VideoCore { enum class CustomPixelFormat : u32; } @@ -44,7 +48,7 @@ enum class DriverBug { */ class Driver { public: - Driver(); + Driver(Core::TelemetrySession& telemetry_session); ~Driver(); /// Returns true of the driver has a particular bug stated in the DriverBug enum @@ -139,6 +143,7 @@ private: void FindBugs(); private: + Core::TelemetrySession& telemetry_session; Vendor vendor = Vendor::Unknown; DriverBug bugs{}; bool is_suitable{}; diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h index 162610c55..fa9409465 100644 --- a/src/video_core/renderer_opengl/pica_to_gl.h +++ b/src/video_core/renderer_opengl/pica_to_gl.h @@ -9,6 +9,7 @@ #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" +#include "core/telemetry_session.h" #include "video_core/pica/regs_framebuffer.h" #include "video_core/pica/regs_lighting.h" #include "video_core/pica/regs_texturing.h" @@ -75,6 +76,9 @@ inline GLenum WrapMode(Pica::TexturingRegs::TextureConfig::WrapMode mode) { } if (index > 3) { + Core::System::GetInstance().TelemetrySession().AddField( + Common::Telemetry::FieldType::Session, "VideoCore_Pica_UnsupportedTextureWrapMode", + static_cast(index)); LOG_WARNING(Render_OpenGL, "Using texture wrap mode {}", index); } diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index ce6cf5386..e9423fc6d 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -75,9 +75,9 @@ static std::array MakeOrthographicMatrix(const float width, cons RendererOpenGL::RendererOpenGL(Core::System& system, Pica::PicaCore& pica_, Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window) : VideoCore::RendererBase{system, window, secondary_window}, pica{pica_}, - rasterizer{system.Memory(), pica, system.CustomTexManager(), *this, driver}, frame_dumper{ - system, - window} { + driver{system.TelemetrySession()}, rasterizer{system.Memory(), pica, + system.CustomTexManager(), *this, driver}, + frame_dumper{system, window} { const bool has_debug_tool = driver.HasDebugTool(); window.mailbox = std::make_unique(has_debug_tool); if (secondary_window) { diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 98f8b23f2..5a18b305d 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -53,9 +53,9 @@ constexpr static std::array PRESENT_BINDINGS RendererVulkan::RendererVulkan(Core::System& system, Pica::PicaCore& pica_, Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window) : RendererBase{system, window, secondary_window}, memory{system.Memory()}, pica{pica_}, - instance{window, Settings::values.physical_device.GetValue()}, scheduler{instance}, - renderpass_cache{instance, scheduler}, pool{instance}, main_window{window, instance, - scheduler}, + instance{system.TelemetrySession(), window, Settings::values.physical_device.GetValue()}, + scheduler{instance}, renderpass_cache{instance, scheduler}, pool{instance}, + main_window{window, instance, scheduler}, vertex_buffer{instance, scheduler, vk::BufferUsageFlagBits::eVertexBuffer, VERTEX_BUFFER_SIZE}, rasterizer{memory, diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index b4fde6a5c..c0e14e4a2 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -9,6 +9,7 @@ #include "common/assert.h" #include "common/settings.h" #include "core/frontend/emu_window.h" +#include "core/telemetry_session.h" #include "video_core/custom_textures/custom_format.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_platform.h" @@ -137,7 +138,8 @@ Instance::Instance(bool enable_validation, bool dump_command_buffers) enable_validation, dump_command_buffers)}, physical_devices{instance->enumeratePhysicalDevices()} {} -Instance::Instance(Frontend::EmuWindow& window, u32 physical_device_index) +Instance::Instance(Core::TelemetrySession& telemetry, Frontend::EmuWindow& window, + u32 physical_device_index) : library{OpenLibrary(&window)}, instance{CreateInstance( *library, window.GetWindowInfo().type, Settings::values.renderer_debug.GetValue(), @@ -159,9 +161,10 @@ Instance::Instance(Frontend::EmuWindow& window, u32 physical_device_index) VK_VERSION_MAJOR(properties.apiVersion), VK_VERSION_MINOR(properties.apiVersion))); } + CollectTelemetryParameters(telemetry); CreateDevice(); - CreateFormatTable(); CollectToolingInfo(); + CreateFormatTable(); CreateCustomFormatTable(); CreateAttribTable(); } @@ -642,10 +645,7 @@ void Instance::CreateAllocator() { } } -void Instance::CollectToolingInfo() { - if (!tooling_info) { - return; - } +void Instance::CollectTelemetryParameters(Core::TelemetrySession& telemetry) { const vk::StructureChain property_chain = physical_device .getProperties2(); @@ -664,6 +664,19 @@ void Instance::CollectToolingInfo() { LOG_INFO(Render_Vulkan, "VK_DRIVER: {}", driver_name); LOG_INFO(Render_Vulkan, "VK_DEVICE: {}", model_name); LOG_INFO(Render_Vulkan, "VK_VERSION: {}", api_version); + + static constexpr auto field = Common::Telemetry::FieldType::UserSystem; + telemetry.AddField(field, "GPU_Vendor", vendor_name); + telemetry.AddField(field, "GPU_Model", model_name); + telemetry.AddField(field, "GPU_Vulkan_Driver", driver_name); + telemetry.AddField(field, "GPU_Vulkan_Version", api_version); + telemetry.AddField(field, "GPU_Vulkan_Extensions", extensions); +} + +void Instance::CollectToolingInfo() { + if (!tooling_info) { + return; + } const auto tools = physical_device.getToolPropertiesEXT(); for (const vk::PhysicalDeviceToolProperties& tool : tools) { const std::string_view name = tool.name; diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 1a01040b7..4bdadb7ca 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -10,6 +10,10 @@ #include "video_core/rasterizer_cache/pixel_format.h" #include "video_core/renderer_vulkan/vk_platform.h" +namespace Core { +class TelemetrySession; +} + namespace Frontend { class EmuWindow; } @@ -37,7 +41,8 @@ struct FormatTraits { class Instance { public: explicit Instance(bool validation = false, bool dump_command_buffers = false); - explicit Instance(Frontend::EmuWindow& window, u32 physical_device_index); + explicit Instance(Core::TelemetrySession& telemetry, Frontend::EmuWindow& window, + u32 physical_device_index); ~Instance(); /// Returns the FormatTraits struct for the provided pixel format @@ -280,7 +285,8 @@ private: /// Creates the VMA allocator handle void CreateAllocator(); - // Collects logging gpu info + /// Collects telemetry information from the device. + void CollectTelemetryParameters(Core::TelemetrySession& telemetry); void CollectToolingInfo(); private: @@ -323,4 +329,4 @@ private: bool has_renderdoc{}; }; -} // namespace Vulkan \ No newline at end of file +} // namespace Vulkan