diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss index a5eee211e..094c7629a 100644 --- a/dist/qt_themes/qdarkstyle/style.qss +++ b/dist/qt_themes/qdarkstyle/style.qss @@ -1,3 +1,31 @@ +QPushButton#TogglableStatusBarButton { + color: #959595; + border: 1px solid transparent; + background-color: transparent; + padding: 0px 3px 0px 3px; + text-align: center; +} + +QPushButton#TogglableStatusBarButton:checked { + color: palette(text); +} + +QPushButton#TogglableStatusBarButton:hover { + border: 1px solid #76797C; +} + +QPushButton#GraphicsAPIStatusBarButton { + color: #656565; + border: 1px solid transparent; + background-color: transparent; + padding: 0px 3px 0px 3px; + text-align: center; +} + +QPushButton#GraphicsAPIStatusBarButton:hover { + border: 1px solid #76797C; +} + QToolTip { border: 1px solid #76797C; background-color: #5A7566; diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 2efd618f9..4711bf8f5 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -164,3 +164,8 @@ if(ANDROID) add_subdirectory(libyuv) target_include_directories(yuv INTERFACE ./libyuv/include) endif() + +# VMA +add_library(vma INTERFACE) +target_include_directories(vma INTERFACE ./vma) + diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 01496953e..9264b7769 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -266,7 +266,7 @@ endif() create_target_directory_groups(citra-qt) target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core) -target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::Widgets Qt5::Multimedia) +target_link_libraries(citra-qt PRIVATE Boost::boost glad vma nihstro-headers Qt5::Widgets Qt5::Multimedia) target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) if (NOT WIN32) diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index f510a3c3c..e5d88a743 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -601,6 +601,7 @@ void Config::ReadPathValues() { void Config::ReadRendererValues() { qt_config->beginGroup(QStringLiteral("Renderer")); + ReadGlobalSetting(Settings::values.physical_device); ReadGlobalSetting(Settings::values.graphics_api); ReadGlobalSetting(Settings::values.use_hw_renderer); ReadGlobalSetting(Settings::values.use_hw_shader); @@ -1080,6 +1081,7 @@ void Config::SaveRendererValues() { qt_config->beginGroup(QStringLiteral("Renderer")); WriteGlobalSetting(Settings::values.graphics_api); + WriteGlobalSetting(Settings::values.physical_device); WriteGlobalSetting(Settings::values.use_hw_renderer); WriteGlobalSetting(Settings::values.use_hw_shader); #ifdef __APPLE__ diff --git a/src/citra_qt/configuration/configure_graphics.cpp b/src/citra_qt/configuration/configure_graphics.cpp index 7f83d3a90..56f1fd530 100644 --- a/src/citra_qt/configuration/configure_graphics.cpp +++ b/src/citra_qt/configuration/configure_graphics.cpp @@ -11,11 +11,13 @@ #include "common/settings.h" #include "core/core.h" #include "ui_configure_graphics.h" +#include "video_core/renderer_vulkan/vk_instance.h" ConfigureGraphics::ConfigureGraphics(QWidget* parent) : QWidget(parent), ui(std::make_unique()) { ui->setupUi(this); + DiscoverPhysicalDevices(); SetupPerGameUI(); SetConfiguration(); @@ -27,6 +29,10 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) ui->graphics_api_combo->setEnabled(not_running); ui->toggle_shader_jit->setEnabled(not_running); ui->toggle_disk_shader_cache->setEnabled(hw_renderer_enabled && not_running); + SetPhysicalDeviceComboVisibility(ui->graphics_api_combo->currentIndex()); + + connect(ui->graphics_api_combo, qOverload(&QComboBox::currentIndexChanged), this, + &ConfigureGraphics::SetPhysicalDeviceComboVisibility); connect(ui->toggle_hw_renderer, &QCheckBox::toggled, this, [this] { const bool checked = ui->toggle_hw_renderer->isChecked(); @@ -77,6 +83,7 @@ void ConfigureGraphics::SetConfiguration() { ui->toggle_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue()); ui->toggle_vsync_new->setChecked(Settings::values.use_vsync_new.GetValue()); ui->graphics_api_combo->setCurrentIndex(static_cast(Settings::values.graphics_api.GetValue())); + ui->physical_device_combo->setCurrentIndex(static_cast(Settings::values.physical_device.GetValue())); if (Settings::IsConfiguringGlobal()) { ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit.GetValue()); @@ -97,6 +104,7 @@ void ConfigureGraphics::ApplyConfiguration() { ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync_new, ui->toggle_vsync_new, use_vsync_new); ConfigurationShared::ApplyPerGameSetting(&Settings::values.graphics_api, ui->graphics_api_combo); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.physical_device, ui->physical_device_combo); if (Settings::IsConfiguringGlobal()) { Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked(); @@ -136,3 +144,21 @@ void ConfigureGraphics::SetupPerGameUI() { ConfigurationShared::SetColoredTristate(ui->toggle_vsync_new, Settings::values.use_vsync_new, use_vsync_new); } + +void ConfigureGraphics::DiscoverPhysicalDevices() { + Vulkan::Instance instance{}; + const auto physical_devices = instance.GetPhysicalDevices(); + + ui->physical_device_combo->clear(); + for (const vk::PhysicalDevice& physical_device : physical_devices) { + const QString name = QString::fromLocal8Bit(physical_device.getProperties().deviceName); + ui->physical_device_combo->addItem(name); + } +} + +void ConfigureGraphics::SetPhysicalDeviceComboVisibility(int index) { + const auto graphics_api = static_cast(index); + const bool is_visible = graphics_api == Settings::GraphicsAPI::Vulkan; + ui->physical_device_label->setVisible(is_visible); + ui->physical_device_combo->setVisible(is_visible); +} \ No newline at end of file diff --git a/src/citra_qt/configuration/configure_graphics.h b/src/citra_qt/configuration/configure_graphics.h index 2517e1553..c3f5acb0b 100644 --- a/src/citra_qt/configuration/configure_graphics.h +++ b/src/citra_qt/configuration/configure_graphics.h @@ -30,6 +30,10 @@ public: void SetupPerGameUI(); +private: + void DiscoverPhysicalDevices(); + void SetPhysicalDeviceComboVisibility(int index); + ConfigurationShared::CheckState use_hw_renderer; ConfigurationShared::CheckState use_hw_shader; ConfigurationShared::CheckState separable_shader; diff --git a/src/citra_qt/configuration/configure_graphics.ui b/src/citra_qt/configuration/configure_graphics.ui index 36a105452..0dda1f100 100644 --- a/src/citra_qt/configuration/configure_graphics.ui +++ b/src/citra_qt/configuration/configure_graphics.ui @@ -21,15 +21,15 @@ - + - Renderer + API Settings - + - + Graphics API @@ -56,6 +56,29 @@ + + + + + + Physical device + + + + + + + + + + + + + + + Renderer + + diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index d8fdb4b3a..14590e590 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -299,6 +299,23 @@ void GMainWindow::InitializeWidgets() { message_label->setAlignment(Qt::AlignLeft); statusBar()->addPermanentWidget(message_label, 1); + // Setup Graphics API button + graphics_api_button = new QPushButton(); + graphics_api_button->setCheckable(true); + graphics_api_button->setObjectName(QStringLiteral("GraphicsAPIStatusBarButton")); + graphics_api_button->setFocusPolicy(Qt::NoFocus); + UpdateAPIIndicator(false); + + connect(graphics_api_button, &QPushButton::clicked, this, [this] { + if (emulation_running) { + return; + } + + UpdateAPIIndicator(true); + }); + + statusBar()->addPermanentWidget(graphics_api_button); + progress_bar = new QProgressBar(); progress_bar->hide(); statusBar()->addPermanentWidget(progress_bar); @@ -318,8 +335,9 @@ void GMainWindow::InitializeWidgets() { label->setVisible(false); label->setFrameStyle(QFrame::NoFrame); label->setContentsMargins(4, 0, 4, 0); - statusBar()->addPermanentWidget(label, 0); + statusBar()->addPermanentWidget(label); } + statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0); statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0); statusBar()->setVisible(true); @@ -1919,6 +1937,7 @@ void GMainWindow::OnConfigure() { setMouseTracking(false); } UpdateSecondaryWindowVisibility(); + UpdateAPIIndicator(false); } else { Settings::values.input_profiles = old_input_profiles; Settings::values.touch_from_button_maps = old_touch_from_button_maps; @@ -2230,6 +2249,33 @@ void GMainWindow::ShowMouseCursor() { } } +void GMainWindow::UpdateAPIIndicator(bool override) { + static std::array graphics_apis = { + QStringLiteral("OPENGL"), + QStringLiteral("OPENGLES"), + QStringLiteral("VULKAN") + }; + + static std::array graphics_api_colors = { + QStringLiteral("#00ccdd"), + QStringLiteral("#ba2a8d"), + QStringLiteral("#91242a") + }; + + u32 api_index = static_cast(Settings::values.graphics_api); + if (override) { + api_index = (api_index + 1) % graphics_apis.size(); + Settings::values.graphics_api = + static_cast(api_index); + } + + const QString style_sheet = + QStringLiteral("QPushButton { font-weight: bold; color: %0; }").arg(graphics_api_colors[api_index]); + + graphics_api_button->setText(graphics_apis[api_index]); + graphics_api_button->setStyleSheet(style_sheet); +} + void GMainWindow::OnMouseActivity() { ShowMouseCursor(); } diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 03689cc5f..e1bcf1b02 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "citra_qt/compatibility_list.h" #include "citra_qt/hotkeys.h" #include "common/announce_multiplayer_room.h" @@ -241,6 +242,7 @@ private: void HideMouseCursor(); void ShowMouseCursor(); void OpenPerGameConfiguration(u64 title_id, const QString& file_name); + void UpdateAPIIndicator(bool override); std::unique_ptr ui; @@ -256,6 +258,7 @@ private: QLabel* emu_speed_label = nullptr; QLabel* game_fps_label = nullptr; QLabel* emu_frametime_label = nullptr; + QPushButton* graphics_api_button = nullptr; QTimer status_bar_update_timer; bool message_label_used_for_movie = false; diff --git a/src/common/settings.h b/src/common/settings.h index 5d7ed0ed4..fb5fd9408 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -447,6 +447,7 @@ struct Values { // Renderer SwitchableSetting graphics_api{GraphicsAPI::OpenGL, "graphics_api"}; + SwitchableSetting physical_device{0, "physical_device"}; Setting renderer_debug{false, "renderer_debug"}; SwitchableSetting use_hw_renderer{true, "use_hw_renderer"}; SwitchableSetting use_hw_shader{true, "use_hw_shader"}; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 657e25d44..3a0f4e23d 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -191,9 +191,8 @@ create_target_directory_groups(video_core) # Include Vulkan headers target_include_directories(video_core PRIVATE ../../externals/vulkan-headers/include) -target_include_directories(video_core PRIVATE ../../externals/vma) target_link_libraries(video_core PUBLIC common core) -target_link_libraries(video_core PRIVATE glad glm::glm SPIRV glslang nihstro-headers Boost::serialization) +target_link_libraries(video_core PRIVATE glad vma glm::glm SPIRV glslang nihstro-headers Boost::serialization) set_target_properties(video_core PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO}) if (ARCHITECTURE_x86_64) diff --git a/src/video_core/rasterizer_cache/rasterizer_cache.h b/src/video_core/rasterizer_cache/rasterizer_cache.h index 020f5017d..950baa60b 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache.h @@ -1077,44 +1077,7 @@ bool RasterizerCache::ValidateByReinterpretation(const Surface& surface, cons auto src_rect = reinterpret_surface->GetScaledSubRect(reinterpret_params); auto dest_rect = surface->GetScaledSubRect(reinterpret_params); - if (!texture_filterer->IsNull() && reinterpret_surface->res_scale == 1 && - surface->res_scale == resolution_scale_factor) { - // The destination surface is either a framebuffer, or a filtered texture. - // Create an intermediate surface to convert to before blitting to the - // destination. - const u32 width = dest_rect.GetHeight() / resolution_scale_factor; - const u32 height = dest_rect.GetWidth() / resolution_scale_factor; - const Common::Rectangle tmp_rect{0, width, height, 0}; - - OGLTexture tmp_tex = AllocateSurfaceTexture(dst_format, height, width); - reinterpreter->Reinterpret(reinterpret_surface->texture, src_rect, tmp_tex, - tmp_rect); - - if (!texture_filterer->Filter(tmp_tex, tmp_rect, surface->texture, dest_rect, type)) { - const TextureBlit texture_blit = { - .surface_type = type, - .src_level = 0, - .dst_level = 0, - .src_layer = 0, - .dst_layer = 0, - .src_region = Region2D{ - .start = {0, 0}, - .end = {width, height} - }, - .dst_region = Region2D{ - .start = {dest_rect.left, dest_rect.bottom}, - .end = {dest_rect.right, dest_rect.top} - } - }; - - runtime.BlitTextures(tmp_tex, surface->texture, texture_blit); - } - - } else { - reinterpreter->Reinterpret(reinterpret_surface->texture, src_rect, surface->texture, - dest_rect); - } - + reinterpreter->Reinterpret(reinterpret_surface->texture, src_rect, surface->texture, dest_rect); return true; } }*/ diff --git a/src/video_core/renderer_opengl/gl_texture_runtime.cpp b/src/video_core/renderer_opengl/gl_texture_runtime.cpp index ae6ed6f84..c714c3828 100644 --- a/src/video_core/renderer_opengl/gl_texture_runtime.cpp +++ b/src/video_core/renderer_opengl/gl_texture_runtime.cpp @@ -129,9 +129,9 @@ void TextureRuntime::FormatConvert(const Surface& surface, bool upload, std::span source, std::span dest) { const VideoCore::PixelFormat format = surface.pixel_format; if (format == VideoCore::PixelFormat::RGBA8 && driver.IsOpenGLES()) { - Pica::Texture::ConvertABGRToRGBA(source, dest); + return Pica::Texture::ConvertABGRToRGBA(source, dest); } else if (format == VideoCore::PixelFormat::RGB8 && driver.IsOpenGLES()) { - Pica::Texture::ConvertBGRToRGB(source, dest); + return Pica::Texture::ConvertBGRToRGB(source, dest); } else { ASSERT(dest.size() >= source.size()); std::memcpy(dest.data(), source.data(), source.size()); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 4c9b44b9e..83e88f159 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -183,7 +183,8 @@ static std::array MakeOrthographicMatrix(float width, float height } RendererVulkan::RendererVulkan(Frontend::EmuWindow& window) - : RendererBase{window}, instance{window, Settings::values.renderer_debug}, scheduler{instance, *this}, + : RendererBase{window}, instance{window, Settings::values.physical_device, Settings::values.renderer_debug}, + scheduler{instance, *this}, renderpass_cache{instance, scheduler}, runtime{instance, scheduler, renderpass_cache}, swapchain{instance, renderpass_cache}, vertex_buffer{instance, scheduler, VERTEX_BUFFER_SIZE, vk::BufferUsageFlagBits::eVertexBuffer, {}} { diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 9c7172852..d8414ccea 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -38,7 +38,38 @@ vk::Format ToVkFormat(VideoCore::PixelFormat format) { } } -Instance::Instance(Frontend::EmuWindow& window, bool enable_validation) { +Instance::Instance() { + // Fetch instance independant function pointers + vk::DynamicLoader dl; + auto vkGetInstanceProcAddr = dl.getProcAddress("vkGetInstanceProcAddr"); + VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); + + const vk::ApplicationInfo application_info = { + .pApplicationName = "Citra", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "Citra Vulkan", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = VK_API_VERSION_1_0 + }; + + const vk::InstanceCreateInfo instance_info = { + .pApplicationInfo = &application_info + }; + + instance = vk::createInstance(instance_info); + + // Load required function pointers for querying the physical device + VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumeratePhysicalDevices = + PFN_vkEnumeratePhysicalDevices(vkGetInstanceProcAddr(instance, "vkEnumeratePhysicalDevices")); + VULKAN_HPP_DEFAULT_DISPATCHER.vkGetPhysicalDeviceProperties = + PFN_vkGetPhysicalDeviceProperties(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties")); + VULKAN_HPP_DEFAULT_DISPATCHER.vkDestroyInstance = + PFN_vkDestroyInstance(vkGetInstanceProcAddr(instance, "vkDestroyInstance")); + + physical_devices = instance.enumeratePhysicalDevices(); +} + +Instance::Instance(Frontend::EmuWindow& window, u32 physical_device_index, bool enable_validation) { auto window_info = window.GetWindowInfo(); // Fetch instance independant function pointers @@ -53,6 +84,7 @@ Instance::Instance(Frontend::EmuWindow& window, bool enable_validation) { const u32 available_version = vk::enumerateInstanceVersion(); if (available_version < VK_API_VERSION_1_1) { LOG_CRITICAL(Render_Vulkan, "Vulkan 1.0 is not supported, 1.1 is required!"); + return; } const vk::ApplicationInfo application_info = { @@ -76,9 +108,16 @@ Instance::Instance(Frontend::EmuWindow& window, bool enable_validation) { instance = vk::createInstance(instance_info); surface = CreateSurface(instance, window); - // TODO: GPU select dialog - auto physical_devices = instance.enumeratePhysicalDevices(); - physical_device = physical_devices[1]; + // Pick physical device + physical_devices = instance.enumeratePhysicalDevices(); + if (const u16 physical_device_count = static_cast(physical_devices.size()); + physical_device_index >= physical_devices.size()) { + LOG_CRITICAL(Render_Vulkan, "Invalid physical device index {} provided when only {} devices exist", + physical_device_index, physical_device_count); + UNREACHABLE(); + } + + physical_device = physical_devices[physical_device_index]; device_properties = physical_device.getProperties(); CreateDevice(); @@ -86,10 +125,12 @@ Instance::Instance(Frontend::EmuWindow& window, bool enable_validation) { } Instance::~Instance() { - device.waitIdle(); - vmaDestroyAllocator(allocator); - device.destroy(); - instance.destroySurfaceKHR(surface); + if (device) { + vmaDestroyAllocator(allocator); + device.destroy(); + instance.destroySurfaceKHR(surface); + } + instance.destroy(); } diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index f5476c7fe..09f490beb 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -5,7 +5,8 @@ #pragma once #include -#include +#include +#include #include "video_core/rasterizer_cache/pixel_format.h" #include "video_core/renderer_vulkan/vk_common.h" @@ -27,7 +28,8 @@ struct FormatTraits { /// The global Vulkan instance class Instance { public: - Instance(Frontend::EmuWindow& window, bool enable_validation); + Instance(); ///< Portable constructor used to query physical devices + Instance(Frontend::EmuWindow& window, u32 physical_device_index, bool enable_validation); ~Instance(); /// Returns the FormatTraits struct for the provided pixel format @@ -53,10 +55,16 @@ public: return device; } + /// Returns the VMA allocator handle VmaAllocator GetAllocator() const { return allocator; } + /// Returns a list of the available physical devices + std::span GetPhysicalDevices() const { + return physical_devices; + } + /// Retrieve queue information u32 GetGraphicsQueueFamilyIndex() const { return graphics_queue_family_index; @@ -131,6 +139,7 @@ private: VmaAllocator allocator; vk::Queue present_queue; vk::Queue graphics_queue; + std::vector physical_devices; std::array format_table; u32 present_queue_family_index = 0; u32 graphics_queue_family_index = 0; diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp index cacfcf651..c1a857201 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp @@ -661,10 +661,6 @@ bool Surface::NeedsConvert() const { } u32 Surface::GetInternalBytesPerPixel() const { - if (alloc.format == vk::Format::eD32SfloatS8Uint) { - return 8; - } - return vk::blockSize(alloc.format); } diff --git a/src/video_core/texture/texture_decode.cpp b/src/video_core/texture/texture_decode.cpp index fd38cc093..8c49c5828 100644 --- a/src/video_core/texture/texture_decode.cpp +++ b/src/video_core/texture/texture_decode.cpp @@ -228,7 +228,7 @@ void ConvertBGRToRGB(std::span source, std::span des u32 bgr{}; std::memcpy(&bgr, source.data() + i, 3); const u32 rgb = Common::swap32(bgr << 8); - std::memcpy(dest.data(), &rgb, 3); + std::memcpy(dest.data() + i, &rgb, 3); } }