diff options
Diffstat (limited to 'src')
67 files changed, 1638 insertions, 957 deletions
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index a82e8a85b..bbf6ae001 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -17,12 +17,14 @@ set(SRCS debugger/profiler.cpp debugger/ramview.cpp debugger/registers.cpp + game_list.cpp util/spinbox.cpp util/util.cpp bootmanager.cpp hotkeys.cpp main.cpp citra-qt.rc + Info.plist ) set(HEADERS @@ -42,6 +44,7 @@ set(HEADERS debugger/profiler.h debugger/ramview.h debugger/registers.h + game_list.h util/spinbox.h util/util.h bootmanager.h @@ -69,7 +72,10 @@ else() endif() if (APPLE) - add_executable(citra-qt MACOSX_BUNDLE ${SRCS} ${HEADERS} ${UI_HDRS}) + set(MACOSX_ICON "../../dist/citra.icns") + set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + add_executable(citra-qt MACOSX_BUNDLE ${SRCS} ${HEADERS} ${UI_HDRS} ${MACOSX_ICON}) + set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) else() add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) endif() diff --git a/src/citra_qt/Info.plist b/src/citra_qt/Info.plist new file mode 100644 index 000000000..4c89e128b --- /dev/null +++ b/src/citra_qt/Info.plist @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundleGetInfoString</key> + <string></string> + <key>CFBundleIconFile</key> + <string>citra.icns</string> + <key>CFBundleIdentifier</key> + <string>com.citra-emu.citra</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleLongVersionString</key> + <string></string> + <key>CFBundleName</key> + <string>Citra</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string></string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string></string> + <key>CSResourcesFileMapped</key> + <true/> + <key>LSRequiresCarbon</key> + <true/> + <key>NSHumanReadableCopyright</key> + <string></string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> + <key>NSHighResolutionCapable</key> + <string>True</string> +</dict> +</plist> diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 7a1360d34..8e60b9cad 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -19,8 +19,8 @@ #include "core/settings.h" #include "core/system.h" -#include "video_core/video_core.h" #include "video_core/debug_utils/debug_utils.h" +#include "video_core/video_core.h" #define APP_NAME "citra" #define APP_VERSION "0.1-" VERSION @@ -86,6 +86,9 @@ public: } void paintEvent(QPaintEvent* ev) override { + if (do_painting) { + QPainter painter(this); + } } void resizeEvent(QResizeEvent* ev) override { @@ -93,8 +96,12 @@ public: parent->OnFramebufferSizeChanged(); } + void DisablePainting() { do_painting = false; } + void EnablePainting() { do_painting = true; } + private: GRenderWindow* parent; + bool do_painting; }; GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) : @@ -128,9 +135,6 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) : BackupGeometry(); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - connect(this->windowHandle(), SIGNAL(screenChanged(QScreen*)), this, SLOT(OnFramebufferSizeChanged())); -#endif } void GRenderWindow::moveContext() @@ -177,16 +181,9 @@ void GRenderWindow::PollEvents() { void GRenderWindow::OnFramebufferSizeChanged() { // Screen changes potentially incur a change in screen DPI, hence we should update the framebuffer size -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - // windowHandle() might not be accessible until the window is displayed to screen. - auto pixel_ratio = windowHandle() ? (windowHandle()->screen()->devicePixelRatio()) : 1.0; - - unsigned width = child->QPaintDevice::width() * pixel_ratio; - unsigned height = child->QPaintDevice::height() * pixel_ratio; -#else - unsigned width = child->QPaintDevice::width(); - unsigned height = child->QPaintDevice::height(); -#endif + qreal pixelRatio = windowPixelRatio(); + unsigned width = child->QPaintDevice::width() * pixelRatio; + unsigned height = child->QPaintDevice::height() * pixelRatio; NotifyFramebufferLayoutChanged(EmuWindow::FramebufferLayout::DefaultScreenLayout(width, height)); } @@ -219,6 +216,16 @@ QByteArray GRenderWindow::saveGeometry() return geometry; } +qreal GRenderWindow::windowPixelRatio() +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + // windowHandle() might not be accessible until the window is displayed to screen. + return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; +#else + return 1.0f; +#endif +} + void GRenderWindow::closeEvent(QCloseEvent* event) { emit Closed(); QWidget::closeEvent(event); @@ -239,14 +246,18 @@ void GRenderWindow::mousePressEvent(QMouseEvent *event) if (event->button() == Qt::LeftButton) { auto pos = event->pos(); - this->TouchPressed(static_cast<unsigned>(pos.x()), static_cast<unsigned>(pos.y())); + qreal pixelRatio = windowPixelRatio(); + this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio), + static_cast<unsigned>(pos.y() * pixelRatio)); } } void GRenderWindow::mouseMoveEvent(QMouseEvent *event) { auto pos = event->pos(); - this->TouchMoved(static_cast<unsigned>(std::max(pos.x(), 0)), static_cast<unsigned>(std::max(pos.y(), 0))); + qreal pixelRatio = windowPixelRatio(); + this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u), + std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u)); } void GRenderWindow::mouseReleaseEvent(QMouseEvent *event) @@ -273,8 +284,19 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(const std::pair<unsigned,un void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { this->emu_thread = emu_thread; + child->DisablePainting(); } void GRenderWindow::OnEmulationStopping() { emu_thread = nullptr; + child->EnablePainting(); +} + +void GRenderWindow::showEvent(QShowEvent * event) { + QWidget::showEvent(event); + + // windowHandle() is not initialized until the Window is shown, so we connect it here. + #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + connect(this->windowHandle(), SIGNAL(screenChanged(QScreen*)), this, SLOT(OnFramebufferSizeChanged()), Qt::UniqueConnection); + #endif } diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index dc422358e..0dcf3e5eb 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -12,11 +12,12 @@ #include "common/emu_window.h" #include "common/thread.h" -class QScreen; class QKeyEvent; +class QScreen; -class GRenderWindow; +class GGLWidgetInternal; class GMainWindow; +class GRenderWindow; class EmuThread : public QThread { @@ -110,6 +111,8 @@ public: void restoreGeometry(const QByteArray& geometry); // overridden QByteArray saveGeometry(); // overridden + qreal windowPixelRatio(); + void closeEvent(QCloseEvent* event) override; void keyPressEvent(QKeyEvent* event) override; @@ -123,13 +126,12 @@ public: void OnClientAreaResized(unsigned width, unsigned height); - void OnFramebufferSizeChanged(); - public slots: void moveContext(); // overridden void OnEmulationStarting(EmuThread* emu_thread); void OnEmulationStopping(); + void OnFramebufferSizeChanged(); signals: /// Emitted when the window is closed @@ -138,7 +140,7 @@ signals: private: void OnMinimalClientAreaChangeRequest(const std::pair<unsigned,unsigned>& minimal_size) override; - QGLWidget* child; + GGLWidgetInternal* child; QByteArray geometry; @@ -146,4 +148,7 @@ private: int keyboard_id; EmuThread* emu_thread; + +protected: + void showEvent(QShowEvent* event) override; }; diff --git a/src/citra_qt/debugger/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics_vertex_shader.cpp index f915d2bab..a5a5fe6b0 100644 --- a/src/citra_qt/debugger/graphics_vertex_shader.cpp +++ b/src/citra_qt/debugger/graphics_vertex_shader.cpp @@ -294,16 +294,16 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con { // Highlight current instruction int current_record_index = par->cycle_index->value(); - if (current_record_index < par->debug_data.records.size()) { + if (current_record_index < static_cast<int>(par->debug_data.records.size())) { const auto& current_record = par->debug_data.records[current_record_index]; - if (index.row() == current_record.instruction_offset) { + if (index.row() == static_cast<int>(current_record.instruction_offset)) { return QColor(255, 255, 63); } } // Use a grey background for instructions which have no debug data associated to them for (const auto& record : par->debug_data.records) - if (index.row() == record.instruction_offset) + if (index.row() == static_cast<int>(record.instruction_offset)) return QVariant(); return QBrush(QColor(192, 192, 192)); @@ -494,7 +494,7 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d debug_data = Pica::Shader::ProduceDebugInfo(input_vertex, num_attributes, shader_config, shader_setup); // Reload widget state - for (unsigned int attr = 0; attr < num_attributes; ++attr) { + for (int attr = 0; attr < num_attributes; ++attr) { unsigned source_attr = shader_config.input_register_map.GetRegisterForAttribute(attr); input_data_mapping[source_attr]->setText(QString("-> v%1").arg(attr)); input_data_container[source_attr]->setVisible(true); diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp new file mode 100644 index 000000000..e925f08a7 --- /dev/null +++ b/src/citra_qt/game_list.cpp @@ -0,0 +1,171 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QHeaderView> +#include <QThreadPool> +#include <QVBoxLayout> + +#include "game_list.h" +#include "game_list_p.h" + +#include "core/loader/loader.h" + +#include "common/common_paths.h" +#include "common/logging/log.h" +#include "common/string_util.h" + +GameList::GameList(QWidget* parent) +{ + QVBoxLayout* layout = new QVBoxLayout; + + tree_view = new QTreeView; + item_model = new QStandardItemModel(tree_view); + tree_view->setModel(item_model); + + tree_view->setAlternatingRowColors(true); + tree_view->setSelectionMode(QHeaderView::SingleSelection); + tree_view->setSelectionBehavior(QHeaderView::SelectRows); + tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setSortingEnabled(true); + tree_view->setEditTriggers(QHeaderView::NoEditTriggers); + tree_view->setUniformRowHeights(true); + + item_model->insertColumns(0, COLUMN_COUNT); + item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type"); + item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name"); + item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size"); + + connect(tree_view, SIGNAL(activated(const QModelIndex&)), this, SLOT(ValidateEntry(const QModelIndex&))); + + // We must register all custom types with the Qt Automoc system so that we are able to use it with + // signals/slots. In this case, QList falls under the umbrells of custom types. + qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); + + layout->addWidget(tree_view); + setLayout(layout); +} + +GameList::~GameList() +{ + emit ShouldCancelWorker(); +} + +void GameList::AddEntry(QList<QStandardItem*> entry_items) +{ + item_model->invisibleRootItem()->appendRow(entry_items); +} + +void GameList::ValidateEntry(const QModelIndex& item) +{ + // We don't care about the individual QStandardItem that was selected, but its row. + int row = item_model->itemFromIndex(item)->row(); + QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); + QString file_path = child_file->data(GameListItemPath::FullPathRole).toString(); + + if (file_path.isEmpty()) + return; + std::string std_file_path = file_path.toStdString(); + if (!FileUtil::Exists(std_file_path) || FileUtil::IsDirectory(std_file_path)) + return; + emit GameChosen(file_path); +} + +void GameList::DonePopulating() +{ + tree_view->setEnabled(true); +} + +void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) +{ + if (!FileUtil::Exists(dir_path.toStdString()) || !FileUtil::IsDirectory(dir_path.toStdString())) { + LOG_ERROR(Frontend, "Could not find game list folder at %s", dir_path.toLocal8Bit().data()); + return; + } + + tree_view->setEnabled(false); + // Delete any rows that might already exist if we're repopulating + item_model->removeRows(0, item_model->rowCount()); + + emit ShouldCancelWorker(); + GameListWorker* worker = new GameListWorker(dir_path, deep_scan); + + connect(worker, SIGNAL(EntryReady(QList<QStandardItem*>)), this, SLOT(AddEntry(QList<QStandardItem*>)), Qt::QueuedConnection); + connect(worker, SIGNAL(Finished()), this, SLOT(DonePopulating()), Qt::QueuedConnection); + // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to cancel without delay. + connect(this, SIGNAL(ShouldCancelWorker()), worker, SLOT(Cancel()), Qt::DirectConnection); + + QThreadPool::globalInstance()->start(worker); + current_worker = std::move(worker); +} + +void GameList::SaveInterfaceLayout(QSettings& settings) +{ + settings.beginGroup("UILayout"); + settings.setValue("gameListHeaderState", tree_view->header()->saveState()); + settings.endGroup(); +} + +void GameList::LoadInterfaceLayout(QSettings& settings) +{ + auto header = tree_view->header(); + settings.beginGroup("UILayout"); + header->restoreState(settings.value("gameListHeaderState").toByteArray()); + settings.endGroup(); + + item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); +} + +void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool deep_scan) +{ + const auto callback = [&](const std::string& directory, + const std::string& virtual_name) -> int { + + std::string physical_name = directory + DIR_SEP + virtual_name; + + if (stop_processing) + return -1; // A negative return value breaks the callback loop. + + if (deep_scan && FileUtil::IsDirectory(physical_name)) { + AddFstEntriesToGameList(physical_name, true); + } else { + std::string filename_filename, filename_extension; + Common::SplitPath(physical_name, nullptr, &filename_filename, &filename_extension); + + Loader::FileType guessed_filetype = Loader::GuessFromExtension(filename_extension); + if (guessed_filetype == Loader::FileType::Unknown) + return 0; + Loader::FileType filetype = Loader::IdentifyFile(physical_name); + if (filetype == Loader::FileType::Unknown) { + LOG_WARNING(Frontend, "File %s is of indeterminate type and is possibly corrupted.", physical_name.c_str()); + return 0; + } + if (guessed_filetype != filetype) { + LOG_WARNING(Frontend, "Filetype and extension of file %s do not match.", physical_name.c_str()); + } + + emit EntryReady({ + new GameListItem(QString::fromStdString(Loader::GetFileTypeString(filetype))), + new GameListItemPath(QString::fromStdString(physical_name)), + new GameListItemSize(FileUtil::GetSize(physical_name)), + }); + } + + return 0; // We don't care about the found entries + }; + FileUtil::ScanDirectoryTreeAndCallback(dir_path, callback); +} + +void GameListWorker::run() +{ + stop_processing = false; + AddFstEntriesToGameList(dir_path.toStdString(), deep_scan); + emit Finished(); +} + +void GameListWorker::Cancel() +{ + disconnect(this, 0, 0, 0); + stop_processing = true; +} diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h new file mode 100644 index 000000000..0950d9622 --- /dev/null +++ b/src/citra_qt/game_list.h @@ -0,0 +1,52 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QModelIndex> +#include <QSettings> +#include <QStandardItem> +#include <QStandardItemModel> +#include <QString> +#include <QTreeView> +#include <QWidget> + +class GameListWorker; + + +class GameList : public QWidget { + Q_OBJECT + +public: + enum { + COLUMN_FILE_TYPE, + COLUMN_NAME, + COLUMN_SIZE, + COLUMN_COUNT, // Number of columns + }; + + GameList(QWidget* parent = nullptr); + ~GameList() override; + + void PopulateAsync(const QString& dir_path, bool deep_scan); + + void SaveInterfaceLayout(QSettings& settings); + void LoadInterfaceLayout(QSettings& settings); + +public slots: + void AddEntry(QList<QStandardItem*> entry_items); + +private slots: + void ValidateEntry(const QModelIndex& item); + void DonePopulating(); + +signals: + void GameChosen(QString game_path); + void ShouldCancelWorker(); + +private: + QTreeView* tree_view = nullptr; + QStandardItemModel* item_model = nullptr; + GameListWorker* current_worker = nullptr; +}; diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h new file mode 100644 index 000000000..820012bce --- /dev/null +++ b/src/citra_qt/game_list_p.h @@ -0,0 +1,130 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> + +#include <QRunnable> +#include <QStandardItem> +#include <QString> + +#include "citra_qt/util/util.h" +#include "common/string_util.h" + + +class GameListItem : public QStandardItem { + +public: + GameListItem(): QStandardItem() {} + GameListItem(const QString& string): QStandardItem(string) {} + virtual ~GameListItem() override {} +}; + + +/** + * A specialization of GameListItem for path values. + * This class ensures that for every full path value it holds, a correct string representation + * of just the filename (with no extension) will be displayed to the user. + */ +class GameListItemPath : public GameListItem { + +public: + static const int FullPathRole = Qt::UserRole + 1; + + GameListItemPath(): GameListItem() {} + GameListItemPath(const QString& game_path): GameListItem() + { + setData(game_path, FullPathRole); + } + + void setData(const QVariant& value, int role) override + { + // By specializing setData for FullPathRole, we can ensure that the two string + // representations of the data are always accurate and in the correct format. + if (role == FullPathRole) { + std::string filename; + Common::SplitPath(value.toString().toStdString(), nullptr, &filename, nullptr); + GameListItem::setData(QString::fromStdString(filename), Qt::DisplayRole); + GameListItem::setData(value, FullPathRole); + } else { + GameListItem::setData(value, role); + } + } +}; + + +/** + * A specialization of GameListItem for size values. + * This class ensures that for every numerical size value it holds (in bytes), a correct + * human-readable string representation will be displayed to the user. + */ +class GameListItemSize : public GameListItem { + +public: + static const int SizeRole = Qt::UserRole + 1; + + GameListItemSize(): GameListItem() {} + GameListItemSize(const qulonglong size_bytes): GameListItem() + { + setData(size_bytes, SizeRole); + } + + void setData(const QVariant& value, int role) override + { + // By specializing setData for SizeRole, we can ensure that the numerical and string + // representations of the data are always accurate and in the correct format. + if (role == SizeRole) { + qulonglong size_bytes = value.toULongLong(); + GameListItem::setData(ReadableByteSize(size_bytes), Qt::DisplayRole); + GameListItem::setData(value, SizeRole); + } else { + GameListItem::setData(value, role); + } + } + + /** + * This operator is, in practice, only used by the TreeView sorting systems. + * Override it so that it will correctly sort by numerical value instead of by string representation. + */ + bool operator<(const QStandardItem& other) const override + { + return data(SizeRole).toULongLong() < other.data(SizeRole).toULongLong(); + } +}; + + +/** + * Asynchronous worker object for populating the game list. + * Communicates with other threads through Qt's signal/slot system. + */ +class GameListWorker : public QObject, public QRunnable { + Q_OBJECT + +public: + GameListWorker(QString dir_path, bool deep_scan): + QObject(), QRunnable(), dir_path(dir_path), deep_scan(deep_scan) {} + +public slots: + /// Starts the processing of directory tree information. + void run() override; + /// Tells the worker that it should no longer continue processing. Thread-safe. + void Cancel(); + +signals: + /** + * The `EntryReady` signal is emitted once an entry has been prepared and is ready + * to be added to the game list. + * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. + */ + void EntryReady(QList<QStandardItem*> entry_items); + void Finished(); + +private: + QString dir_path; + bool deep_scan; + std::atomic_bool stop_processing; + + void AddFstEntriesToGameList(const std::string& dir_path, bool deep_scan); +}; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 01841b33c..bcff6be64 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -12,6 +12,7 @@ #include "citra_qt/bootmanager.h" #include "citra_qt/config.h" +#include "citra_qt/game_list.h" #include "citra_qt/hotkeys.h" #include "citra_qt/main.h" @@ -59,6 +60,9 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) render_window = new GRenderWindow(this, emu_thread.get()); render_window->hide(); + game_list = new GameList(); + ui.horizontalLayout->addWidget(game_list); + profilerWidget = new ProfilerWidget(this); addDockWidget(Qt::BottomDockWidgetArea, profilerWidget); profilerWidget->hide(); @@ -137,6 +141,8 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) microProfileDialog->setVisible(settings.value("microProfileDialogVisible").toBool()); settings.endGroup(); + game_list->LoadInterfaceLayout(settings); + ui.action_Use_Hardware_Renderer->setChecked(Settings::values.use_hw_renderer); SetHardwareRendererEnabled(ui.action_Use_Hardware_Renderer->isChecked()); @@ -160,8 +166,10 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) UpdateRecentFiles(); // Setup connections + connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString))); connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile())); connect(ui.action_Load_Symbol_Map, SIGNAL(triggered()), this, SLOT(OnMenuLoadSymbolMap())); + connect(ui.action_Select_Game_List_Root, SIGNAL(triggered()), this, SLOT(OnMenuSelectGameListRoot())); connect(ui.action_Start, SIGNAL(triggered()), this, SLOT(OnStartGame())); connect(ui.action_Pause, SIGNAL(triggered()), this, SLOT(OnPauseGame())); connect(ui.action_Stop, SIGNAL(triggered()), this, SLOT(OnStopGame())); @@ -193,6 +201,8 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) show(); + game_list->PopulateAsync(settings.value("gameListRootDir").toString(), settings.value("gameListDeepScan").toBool()); + QStringList args = QApplication::arguments(); if (args.length() >= 2) { BootGame(args[1].toStdString()); @@ -230,7 +240,7 @@ void GMainWindow::OnDisplayTitleBars(bool show) } void GMainWindow::BootGame(const std::string& filename) { - LOG_INFO(Frontend, "Citra starting...\n"); + LOG_INFO(Frontend, "Citra starting..."); // Shutdown previous session if the emu thread is still active... if (emu_thread != nullptr) @@ -264,8 +274,12 @@ void GMainWindow::BootGame(const std::string& filename) { // Update the GUI registersWidget->OnDebugModeEntered(); callstackWidget->OnDebugModeEntered(); + if (ui.action_Single_Window_Mode->isChecked()) { + game_list->hide(); + } render_window->show(); + emulation_running = true; OnStartGame(); } @@ -294,6 +308,9 @@ void GMainWindow::ShutdownGame() { ui.action_Pause->setEnabled(false); ui.action_Stop->setEnabled(false); render_window->hide(); + game_list->show(); + + emulation_running = false; } void GMainWindow::StoreRecentFile(const QString& filename) @@ -337,16 +354,20 @@ void GMainWindow::UpdateRecentFiles() { } } +void GMainWindow::OnGameListLoadFile(QString game_path) { + BootGame(game_path.toLocal8Bit().data()); +} + void GMainWindow::OnMenuLoadFile() { QSettings settings; QString rom_path = settings.value("romsPath", QString()).toString(); QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), rom_path, tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.cci *.cxi)")); - if (filename.size()) { + if (!filename.isEmpty()) { settings.setValue("romsPath", QFileInfo(filename).path()); StoreRecentFile(filename); - BootGame(filename.toLatin1().data()); + BootGame(filename.toLocal8Bit().data()); } } @@ -355,10 +376,20 @@ void GMainWindow::OnMenuLoadSymbolMap() { QString symbol_path = settings.value("symbolsPath", QString()).toString(); QString filename = QFileDialog::getOpenFileName(this, tr("Load Symbol Map"), symbol_path, tr("Symbol map (*)")); - if (filename.size()) { + if (!filename.isEmpty()) { settings.setValue("symbolsPath", QFileInfo(filename).path()); - LoadSymbolMap(filename.toLatin1().data()); + LoadSymbolMap(filename.toLocal8Bit().data()); + } +} + +void GMainWindow::OnMenuSelectGameListRoot() { + QSettings settings; + + QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); + if (!dir_path.isEmpty()) { + settings.setValue("gameListRootDir", dir_path); + game_list->PopulateAsync(dir_path, settings.value("gameListDeepScan").toBool()); } } @@ -369,7 +400,7 @@ void GMainWindow::OnMenuRecentFile() { QString filename = action->data().toString(); QFileInfo file_info(filename); if (file_info.exists()) { - BootGame(filename.toLatin1().data()); + BootGame(filename.toLocal8Bit().data()); StoreRecentFile(filename); // Put the filename on top of the list } else { // Display an error message and remove the file from the list. @@ -412,10 +443,18 @@ void GMainWindow::OnOpenHotkeysDialog() { void GMainWindow::SetHardwareRendererEnabled(bool enabled) { VideoCore::g_hw_renderer_enabled = enabled; + + Config config; + Settings::values.use_hw_renderer = enabled; + config.Save(); } void GMainWindow::SetShaderJITEnabled(bool enabled) { VideoCore::g_shader_jit_enabled = enabled; + + Config config; + Settings::values.use_shader_jit = enabled; + config.Save(); } void GMainWindow::ToggleWindowMode() { @@ -423,17 +462,23 @@ void GMainWindow::ToggleWindowMode() { // Render in the main window... render_window->BackupGeometry(); ui.horizontalLayout->addWidget(render_window); - render_window->setVisible(true); render_window->setFocusPolicy(Qt::ClickFocus); - render_window->setFocus(); + if (emulation_running) { + render_window->setVisible(true); + render_window->setFocus(); + game_list->hide(); + } } else { // Render in a separate window... ui.horizontalLayout->removeWidget(render_window); render_window->setParent(nullptr); - render_window->setVisible(true); - render_window->RestoreGeometry(); render_window->setFocusPolicy(Qt::NoFocus); + if (emulation_running) { + render_window->setVisible(true); + render_window->RestoreGeometry(); + game_list->show(); + } } } @@ -456,6 +501,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { settings.setValue("singleWindowMode", ui.action_Single_Window_Mode->isChecked()); settings.setValue("displayTitleBars", ui.actionDisplay_widget_title_bars->isChecked()); settings.setValue("firstStart", false); + game_list->SaveInterfaceLayout(settings); SaveHotkeys(settings); // Shutdown session if the emu thread is active... diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 32523fded..6d27ce6a9 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -10,6 +10,7 @@ #include "ui_main.h" +class GameList; class GImageInfo; class GRenderWindow; class EmuThread; @@ -87,8 +88,12 @@ private slots: void OnStartGame(); void OnPauseGame(); void OnStopGame(); + /// Called whenever a user selects a game in the game list widget. + void OnGameListLoadFile(QString game_path); void OnMenuLoadFile(); void OnMenuLoadSymbolMap(); + /// Called whenever a user selects the "File->Select Game List Root" menu item + void OnMenuSelectGameListRoot(); void OnMenuRecentFile(); void OnOpenHotkeysDialog(); void OnConfigure(); @@ -101,7 +106,10 @@ private: Ui::MainWindow ui; GRenderWindow* render_window; + GameList* game_list; + // Whether emulation is currently running in Citra. + bool emulation_running = false; std::unique_ptr<EmuThread> emu_thread; ProfilerWidget* profilerWidget; diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index 1ba700a3a..997597642 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -45,7 +45,7 @@ <x>0</x> <y>0</y> <width>1081</width> - <height>21</height> + <height>22</height> </rect> </property> <widget class="QMenu" name="menu_File"> @@ -60,6 +60,7 @@ <addaction name="action_Load_File"/> <addaction name="action_Load_Symbol_Map"/> <addaction name="separator"/> + <addaction name="action_Select_Game_List_Root"/> <addaction name="menu_recent_files"/> <addaction name="separator"/> <addaction name="action_Exit"/> @@ -182,6 +183,14 @@ <string>Display Dock Widget Headers</string> </property> </action> + <action name="action_Select_Game_List_Root"> + <property name="text"> + <string>Select Game Directory...</string> + </property> + <property name="toolTip"> + <string>Selects a folder to display in the game list</string> + </property> + </action> </widget> <resources/> <connections> diff --git a/src/citra_qt/util/util.cpp b/src/citra_qt/util/util.cpp index f292046b7..8734a8efd 100644 --- a/src/citra_qt/util/util.cpp +++ b/src/citra_qt/util/util.cpp @@ -2,6 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> +#include <cmath> + #include "citra_qt/util/util.h" QFont GetMonospaceFont() { @@ -11,3 +14,12 @@ QFont GetMonospaceFont() { font.setFixedPitch(true); return font; } + +QString ReadableByteSize(qulonglong size) { + static const std::array<const char*, 6> units = { "B", "KiB", "MiB", "GiB", "TiB", "PiB" }; + if (size == 0) + return "0"; + int digit_groups = std::min<int>((int)(std::log10(size) / std::log10(1024)), units.size()); + return QString("%L1 %2").arg(size / std::pow(1024, digit_groups), 0, 'f', 1) + .arg(units[digit_groups]); +} diff --git a/src/citra_qt/util/util.h b/src/citra_qt/util/util.h index 98a944047..ab443ef9b 100644 --- a/src/citra_qt/util/util.h +++ b/src/citra_qt/util/util.h @@ -5,6 +5,10 @@ #pragma once #include <QFont> +#include <QString> /// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc. QFont GetMonospaceFont(); + +/// Convert a size in bytes into a readable format (KiB, MiB, etc.) +QString ReadableByteSize(qulonglong size); diff --git a/src/common/bit_field.h b/src/common/bit_field.h index d306ce9a9..66689f398 100644 --- a/src/common/bit_field.h +++ b/src/common/bit_field.h @@ -125,21 +125,10 @@ public: // so that we can use this within unions BitField() = default; -#ifndef _WIN32 // We explicitly delete the copy assigment operator here, because the // default copy assignment would copy the full storage value, rather than // just the bits relevant to this particular bit field. - // Ideally, we would just implement the copy assignment to copy only the - // relevant bits, but this requires compiler support for unrestricted - // unions. - // MSVC 2013 has no support for this, hence we disable this code on - // Windows (so that the default copy assignment operator will be used). - // For any C++11 conformant compiler we delete the operator to make sure - // we never use this inappropriate operator to begin with. - // TODO: Implement this operator properly once all target compilers - // support unrestricted unions. BitField& operator=(const BitField&) = delete; -#endif FORCE_INLINE BitField& operator=(T val) { diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 836b58d52..1e0d33313 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -420,28 +420,23 @@ bool CreateEmptyFile(const std::string &filename) } -// Scans the directory tree gets, starting from _Directory and adds the -// results into parentEntry. Returns the number of files+directories found -u32 ScanDirectoryTree(const std::string &directory, FSTEntry& parentEntry) +int ScanDirectoryTreeAndCallback(const std::string &directory, std::function<int(const std::string&, const std::string&)> callback) { LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str()); // How many files + directories we found - u32 foundEntries = 0; + int found_entries = 0; #ifdef _WIN32 // Find the first file in the directory. WIN32_FIND_DATA ffd; - HANDLE hFind = FindFirstFile(Common::UTF8ToTStr(directory + "\\*").c_str(), &ffd); - if (hFind == INVALID_HANDLE_VALUE) - { - FindClose(hFind); - return foundEntries; + HANDLE handle_find = FindFirstFile(Common::UTF8ToTStr(directory + "\\*").c_str(), &ffd); + if (handle_find == INVALID_HANDLE_VALUE) { + FindClose(handle_find); + return found_entries; } // windows loop - do - { - FSTEntry entry; - const std::string virtualName(Common::TStrToUTF8(ffd.cFileName)); + do { + const std::string virtual_name(Common::TStrToUTF8(ffd.cFileName)); #else struct dirent dirent, *result = nullptr; @@ -450,115 +445,80 @@ u32 ScanDirectoryTree(const std::string &directory, FSTEntry& parentEntry) return 0; // non windows loop - while (!readdir_r(dirp, &dirent, &result) && result) - { - FSTEntry entry; - const std::string virtualName(result->d_name); + while (!readdir_r(dirp, &dirent, &result) && result) { + const std::string virtual_name(result->d_name); #endif // check for "." and ".." - if (((virtualName[0] == '.') && (virtualName[1] == '\0')) || - ((virtualName[0] == '.') && (virtualName[1] == '.') && - (virtualName[2] == '\0'))) + if (((virtual_name[0] == '.') && (virtual_name[1] == '\0')) || + ((virtual_name[0] == '.') && (virtual_name[1] == '.') && + (virtual_name[2] == '\0'))) continue; - entry.virtualName = virtualName; - entry.physicalName = directory; - entry.physicalName += DIR_SEP + entry.virtualName; - if (IsDirectory(entry.physicalName.c_str())) - { - entry.isDirectory = true; - // is a directory, lets go inside - entry.size = ScanDirectoryTree(entry.physicalName, entry); - foundEntries += (u32)entry.size; - } - else - { // is a file - entry.isDirectory = false; - entry.size = GetSize(entry.physicalName.c_str()); + int ret = callback(directory, virtual_name); + if (ret < 0) { + if (ret != -1) + found_entries = ret; + break; } - ++foundEntries; - // Push into the tree - parentEntry.children.push_back(entry); + found_entries += ret; + #ifdef _WIN32 - } while (FindNextFile(hFind, &ffd) != 0); - FindClose(hFind); + } while (FindNextFile(handle_find, &ffd) != 0); + FindClose(handle_find); #else } closedir(dirp); #endif // Return number of entries found. - return foundEntries; + return found_entries; } - -// Deletes the given directory and anything under it. Returns true on success. -bool DeleteDirRecursively(const std::string &directory) +int ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry) { - LOG_TRACE(Common_Filesystem, "%s", directory.c_str()); -#ifdef _WIN32 - // Find the first file in the directory. - WIN32_FIND_DATA ffd; - HANDLE hFind = FindFirstFile(Common::UTF8ToTStr(directory + "\\*").c_str(), &ffd); - - if (hFind == INVALID_HANDLE_VALUE) - { - FindClose(hFind); - return false; - } - - // windows loop - do - { - const std::string virtualName(Common::TStrToUTF8(ffd.cFileName)); -#else - struct dirent dirent, *result = nullptr; - DIR *dirp = opendir(directory.c_str()); - if (!dirp) - return false; - - // non windows loop - while (!readdir_r(dirp, &dirent, &result) && result) - { - const std::string virtualName = result->d_name; -#endif + const auto callback = [&parent_entry](const std::string& directory, + const std::string& virtual_name) -> int { + FSTEntry entry; + int found_entries = 0; + entry.virtualName = virtual_name; + entry.physicalName = directory + DIR_SEP + virtual_name; - // check for "." and ".." - if (((virtualName[0] == '.') && (virtualName[1] == '\0')) || - ((virtualName[0] == '.') && (virtualName[1] == '.') && - (virtualName[2] == '\0'))) - continue; + if (IsDirectory(entry.physicalName)) { + entry.isDirectory = true; + // is a directory, lets go inside + entry.size = ScanDirectoryTree(entry.physicalName, entry); + found_entries += (int)entry.size; + } else { // is a file + entry.isDirectory = false; + entry.size = GetSize(entry.physicalName); + } + ++found_entries; + // Push into the tree + parent_entry.children.push_back(entry); + return found_entries; + }; - std::string newPath = directory + DIR_SEP_CHR + virtualName; - if (IsDirectory(newPath)) - { - if (!DeleteDirRecursively(newPath)) - { - #ifndef _WIN32 - closedir(dirp); - #endif + return ScanDirectoryTreeAndCallback(directory, callback); +} - return false; - } - } - else - { - if (!FileUtil::Delete(newPath)) - { - #ifndef _WIN32 - closedir(dirp); - #endif - return false; +bool DeleteDirRecursively(const std::string &directory) +{ + const static auto callback = [](const std::string& directory, + const std::string& virtual_name) -> int { + std::string new_path = directory + DIR_SEP_CHR + virtual_name; + if (IsDirectory(new_path)) { + if (!DeleteDirRecursively(new_path)) { + return -2; } + } else if (!Delete(new_path)) { + return -2; } + return 0; + }; -#ifdef _WIN32 - } while (FindNextFile(hFind, &ffd) != 0); - FindClose(hFind); -#else + if (ScanDirectoryTreeAndCallback(directory, callback) == -2) { + return false; } - closedir(dirp); -#endif FileUtil::DeleteDir(directory); return true; @@ -861,8 +821,8 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam const std::string forbidden_characters = ".\"/\\[]:;=, "; // On a FAT32 partition, 8.3 names are stored as a 11 bytes array, filled with spaces. - short_name = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\0'}; - extension = {' ', ' ', ' ', '\0'}; + short_name = {{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\0'}}; + extension = {{' ', ' ', ' ', '\0'}}; std::string::size_type point = filename.rfind('.'); if (point == filename.size() - 1) diff --git a/src/common/file_util.h b/src/common/file_util.h index e71a9b2fa..3d617f573 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -6,6 +6,7 @@ #include <array> #include <fstream> +#include <functional> #include <cstddef> #include <cstdio> #include <string> @@ -96,9 +97,28 @@ bool Copy(const std::string &srcFilename, const std::string &destFilename); // creates an empty file filename, returns true on success bool CreateEmptyFile(const std::string &filename); -// Scans the directory tree gets, starting from _Directory and adds the -// results into parentEntry. Returns the number of files+directories found -u32 ScanDirectoryTree(const std::string &directory, FSTEntry& parentEntry); +/** + * Scans the directory tree, calling the callback for each file/directory found. + * The callback must return the number of files and directories which the provided path contains. + * If the callback's return value is -1, the callback loop is broken immediately. + * If the callback's return value is otherwise negative, the callback loop is broken immediately + * and the callback's return value is returned from this function (to allow for error handling). + * @param directory the parent directory to start scanning from + * @param callback The callback which will be called for each file/directory. It is called + * with the arguments (const std::string& directory, const std::string& virtual_name). + * The `directory `parameter is the path to the directory which contains the file/directory. + * The `virtual_name` parameter is the incomplete file path, without any directory info. + * @return the total number of files/directories found + */ +int ScanDirectoryTreeAndCallback(const std::string &directory, std::function<int(const std::string&, const std::string&)> callback); + +/** + * Scans the directory tree, storing the results. + * @param directory the parent directory to start scanning from + * @param parent_entry FSTEntry where the filesystem tree results will be stored. + * @return the total number of files/directories found + */ +int ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry); // deletes the given directory and anything under it. Returns true on success. bool DeleteDirRecursively(const std::string &directory); diff --git a/src/common/hash.cpp b/src/common/hash.cpp index 413e9c6f1..c49c2f60e 100644 --- a/src/common/hash.cpp +++ b/src/common/hash.cpp @@ -17,27 +17,11 @@ namespace Common { // Block read - if your platform needs to do endian-swapping or can only handle aligned reads, do // the conversion here - -static FORCE_INLINE u32 getblock32(const u32* p, int i) { - return p[i]; -} - static FORCE_INLINE u64 getblock64(const u64* p, int i) { return p[i]; } // Finalization mix - force all bits of a hash block to avalanche - -static FORCE_INLINE u32 fmix32(u32 h) { - h ^= h >> 16; - h *= 0x85ebca6b; - h ^= h >> 13; - h *= 0xc2b2ae35; - h ^= h >> 16; - - return h; -} - static FORCE_INLINE u64 fmix64(u64 k) { k ^= k >> 33; k *= 0xff51afd7ed558ccdllu; diff --git a/src/common/symbols.cpp b/src/common/symbols.cpp index f23e51c9d..db8340043 100644 --- a/src/common/symbols.cpp +++ b/src/common/symbols.cpp @@ -8,46 +8,43 @@ TSymbolsMap g_symbols; namespace Symbols { - bool HasSymbol(u32 _address) + bool HasSymbol(u32 address) { - return g_symbols.find(_address) != g_symbols.end(); + return g_symbols.find(address) != g_symbols.end(); } - void Add(u32 _address, const std::string& _name, u32 _size, u32 _type) + void Add(u32 address, const std::string& name, u32 size, u32 type) { - if (!HasSymbol(_address)) + if (!HasSymbol(address)) { TSymbol symbol; - symbol.address = _address; - symbol.name = _name; - symbol.size = _size; - symbol.type = _type; + symbol.address = address; + symbol.name = name; + symbol.size = size; + symbol.type = type; - g_symbols.insert(TSymbolsPair(_address, symbol)); + g_symbols.emplace(address, symbol); } } - TSymbol GetSymbol(u32 _address) + TSymbol GetSymbol(u32 address) { - TSymbolsMap::iterator foundSymbolItr; - TSymbol symbol; + const auto iter = g_symbols.find(address); - foundSymbolItr = g_symbols.find(_address); - if (foundSymbolItr != g_symbols.end()) - { - symbol = (*foundSymbolItr).second; - } + if (iter != g_symbols.end()) + return iter->second; - return symbol; + return {}; } - const std::string GetName(u32 _address) + + const std::string GetName(u32 address) { - return GetSymbol(_address).name; + return GetSymbol(address).name; } - void Remove(u32 _address) + void Remove(u32 address) { - g_symbols.erase(_address); + g_symbols.erase(address); } void Clear() diff --git a/src/common/symbols.h b/src/common/symbols.h index 6b62b011e..5ed16009c 100644 --- a/src/common/symbols.h +++ b/src/common/symbols.h @@ -12,15 +12,10 @@ struct TSymbol { - TSymbol() : - address(0), - size(0), - type(0) - {} - u32 address; + u32 address = 0; std::string name; - u32 size; - u32 type; + u32 size = 0; + u32 type = 0; }; typedef std::map<u32, TSymbol> TSymbolsMap; @@ -28,12 +23,12 @@ typedef std::pair<u32, TSymbol> TSymbolsPair; namespace Symbols { - bool HasSymbol(u32 _address); + bool HasSymbol(u32 address); - void Add(u32 _address, const std::string& _name, u32 _size, u32 _type); - TSymbol GetSymbol(u32 _address); - const std::string GetName(u32 _address); - void Remove(u32 _address); + void Add(u32 address, const std::string& name, u32 size, u32 type); + TSymbol GetSymbol(u32 address); + const std::string GetName(u32 address); + void Remove(u32 address); void Clear(); } diff --git a/src/core/arm/skyeye_common/armstate.h b/src/core/arm/skyeye_common/armstate.h index b364e2621..ceb159d14 100644 --- a/src/core/arm/skyeye_common/armstate.h +++ b/src/core/arm/skyeye_common/armstate.h @@ -247,6 +247,5 @@ private: static const u32 RESERVATION_GRANULE_MASK = 0xFFFFFFF8; u32 exclusive_tag; // The address for which the local monitor is in exclusive access mode - u32 exclusive_result; bool exclusive_state; }; diff --git a/src/core/arm/skyeye_common/vfp/vfp.cpp b/src/core/arm/skyeye_common/vfp/vfp.cpp index 0537135e2..a27a7e194 100644 --- a/src/core/arm/skyeye_common/vfp/vfp.cpp +++ b/src/core/arm/skyeye_common/vfp/vfp.cpp @@ -113,26 +113,26 @@ void VMOVR(ARMul_State* state, u32 single, u32 d, u32 m) /* Miscellaneous functions */ s32 vfp_get_float(ARMul_State* state, unsigned int reg) { - LOG_TRACE(Core_ARM11, "VFP get float: s%d=[%08x]\n", reg, state->ExtReg[reg]); + LOG_TRACE(Core_ARM11, "VFP get float: s%d=[%08x]", reg, state->ExtReg[reg]); return state->ExtReg[reg]; } void vfp_put_float(ARMul_State* state, s32 val, unsigned int reg) { - LOG_TRACE(Core_ARM11, "VFP put float: s%d <= [%08x]\n", reg, val); + LOG_TRACE(Core_ARM11, "VFP put float: s%d <= [%08x]", reg, val); state->ExtReg[reg] = val; } u64 vfp_get_double(ARMul_State* state, unsigned int reg) { u64 result = ((u64) state->ExtReg[reg*2+1])<<32 | state->ExtReg[reg*2]; - LOG_TRACE(Core_ARM11, "VFP get double: s[%d-%d]=[%016llx]\n", reg * 2 + 1, reg * 2, result); + LOG_TRACE(Core_ARM11, "VFP get double: s[%d-%d]=[%016llx]", reg * 2 + 1, reg * 2, result); return result; } void vfp_put_double(ARMul_State* state, u64 val, unsigned int reg) { - LOG_TRACE(Core_ARM11, "VFP put double: s[%d-%d] <= [%08x-%08x]\n", reg * 2 + 1, reg * 2, (u32)(val >> 32), (u32)(val & 0xffffffff)); + LOG_TRACE(Core_ARM11, "VFP put double: s[%d-%d] <= [%08x-%08x]", reg * 2 + 1, reg * 2, (u32)(val >> 32), (u32)(val & 0xffffffff)); state->ExtReg[reg*2] = (u32) (val & 0xffffffff); state->ExtReg[reg*2+1] = (u32) (val>>32); } @@ -142,10 +142,10 @@ void vfp_put_double(ARMul_State* state, u64 val, unsigned int reg) */ void vfp_raise_exceptions(ARMul_State* state, u32 exceptions, u32 inst, u32 fpscr) { - LOG_TRACE(Core_ARM11, "VFP: raising exceptions %08x\n", exceptions); + LOG_TRACE(Core_ARM11, "VFP: raising exceptions %08x", exceptions); if (exceptions == VFP_EXCEPTION_ERROR) { - LOG_CRITICAL(Core_ARM11, "unhandled bounce %x\n", inst); + LOG_CRITICAL(Core_ARM11, "unhandled bounce %x", inst); Crash(); } diff --git a/src/core/arm/skyeye_common/vfp/vfp.h b/src/core/arm/skyeye_common/vfp/vfp.h index 88908da9f..60a63e6de 100644 --- a/src/core/arm/skyeye_common/vfp/vfp.h +++ b/src/core/arm/skyeye_common/vfp/vfp.h @@ -22,7 +22,7 @@ #include "core/arm/skyeye_common/vfp/vfp_helper.h" /* for references to cdp SoftFloat functions */ -#define VFP_DEBUG_UNTESTED(x) LOG_TRACE(Core_ARM11, "in func %s, " #x " untested\n", __FUNCTION__); +#define VFP_DEBUG_UNTESTED(x) LOG_TRACE(Core_ARM11, "in func %s, " #x " untested", __FUNCTION__); #define CHECK_VFP_ENABLED #define CHECK_VFP_CDP_RET vfp_raise_exceptions(cpu, ret, inst_cream->instr, cpu->VFP[VFP_FPSCR]); diff --git a/src/core/arm/skyeye_common/vfp/vfpdouble.cpp b/src/core/arm/skyeye_common/vfp/vfpdouble.cpp index 857e6ce45..45914d479 100644 --- a/src/core/arm/skyeye_common/vfp/vfpdouble.cpp +++ b/src/core/arm/skyeye_common/vfp/vfpdouble.cpp @@ -65,7 +65,7 @@ static struct vfp_double vfp_double_default_qnan = { static void vfp_double_dump(const char *str, struct vfp_double *d) { - LOG_TRACE(Core_ARM11, "VFP: %s: sign=%d exponent=%d significand=%016llx\n", + LOG_TRACE(Core_ARM11, "VFP: %s: sign=%d exponent=%d significand=%016llx", str, d->sign != 0, d->exponent, d->significand); } @@ -155,7 +155,7 @@ u32 vfp_double_normaliseround(ARMul_State* state, int dd, struct vfp_double *vd, } else if ((rmode == FPSCR_ROUND_PLUSINF) ^ (vd->sign != 0)) incr = (1ULL << (VFP_DOUBLE_LOW_BITS + 1)) - 1; - LOG_TRACE(Core_ARM11, "VFP: rounding increment = 0x%08llx\n", incr); + LOG_TRACE(Core_ARM11, "VFP: rounding increment = 0x%08llx", incr); /* * Is our rounding going to overflow? @@ -210,7 +210,7 @@ pack: vfp_double_dump("pack: final", vd); { s64 d = vfp_double_pack(vd); - LOG_TRACE(Core_ARM11, "VFP: %s: d(d%d)=%016llx exceptions=%08x\n", func, + LOG_TRACE(Core_ARM11, "VFP: %s: d(d%d)=%016llx exceptions=%08x", func, dd, d, exceptions); vfp_put_double(state, d, dd); } @@ -267,28 +267,28 @@ vfp_propagate_nan(struct vfp_double *vdd, struct vfp_double *vdn, */ static u32 vfp_double_fabs(ARMul_State* state, int dd, int unused, int dm, u32 fpscr) { - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vfp_put_double(state, vfp_double_packed_abs(vfp_get_double(state, dm)), dd); return 0; } static u32 vfp_double_fcpy(ARMul_State* state, int dd, int unused, int dm, u32 fpscr) { - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vfp_put_double(state, vfp_get_double(state, dm), dd); return 0; } static u32 vfp_double_fneg(ARMul_State* state, int dd, int unused, int dm, u32 fpscr) { - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vfp_put_double(state, vfp_double_packed_negate(vfp_get_double(state, dm)), dd); return 0; } static u32 vfp_double_fsqrt(ARMul_State* state, int dd, int unused, int dm, u32 fpscr) { - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vfp_double vdm, vdd, *vdp; int ret, tm; @@ -383,7 +383,7 @@ static u32 vfp_compare(ARMul_State* state, int dd, int signal_on_qnan, int dm, u s64 d, m; u32 ret = 0; - LOG_TRACE(Core_ARM11, "In %s, state=0x%p, fpscr=0x%x\n", __FUNCTION__, state, fpscr); + LOG_TRACE(Core_ARM11, "In %s, state=0x%p, fpscr=0x%x", __FUNCTION__, state, fpscr); m = vfp_get_double(state, dm); if (vfp_double_packed_exponent(m) == 2047 && vfp_double_packed_mantissa(m)) { ret |= FPSCR_CFLAG | FPSCR_VFLAG; @@ -438,32 +438,32 @@ static u32 vfp_compare(ARMul_State* state, int dd, int signal_on_qnan, int dm, u ret |= FPSCR_CFLAG; } } - LOG_TRACE(Core_ARM11, "In %s, state=0x%p, ret=0x%x\n", __FUNCTION__, state, ret); + LOG_TRACE(Core_ARM11, "In %s, state=0x%p, ret=0x%x", __FUNCTION__, state, ret); return ret; } static u32 vfp_double_fcmp(ARMul_State* state, int dd, int unused, int dm, u32 fpscr) { - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); return vfp_compare(state, dd, 0, dm, fpscr); } static u32 vfp_double_fcmpe(ARMul_State* state, int dd, int unused, int dm, u32 fpscr) { - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); return vfp_compare(state, dd, 1, dm, fpscr); } static u32 vfp_double_fcmpz(ARMul_State* state, int dd, int unused, int dm, u32 fpscr) { - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); return vfp_compare(state, dd, 0, VFP_REG_ZERO, fpscr); } static u32 vfp_double_fcmpez(ARMul_State* state, int dd, int unused, int dm, u32 fpscr) { - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); return vfp_compare(state, dd, 1, VFP_REG_ZERO, fpscr); } @@ -474,7 +474,7 @@ static u32 vfp_double_fcvts(ARMul_State* state, int sd, int unused, int dm, u32 int tm; u32 exceptions = 0; - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); tm = vfp_double_type(&vdm); @@ -516,7 +516,7 @@ static u32 vfp_double_fuito(ARMul_State* state, int dd, int unused, int dm, u32 struct vfp_double vdm; u32 m = vfp_get_float(state, dm); - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vdm.sign = 0; vdm.exponent = 1023 + 63 - 1; vdm.significand = (u64)m; @@ -529,7 +529,7 @@ static u32 vfp_double_fsito(ARMul_State* state, int dd, int unused, int dm, u32 struct vfp_double vdm; u32 m = vfp_get_float(state, dm); - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vdm.sign = (m & 0x80000000) >> 16; vdm.exponent = 1023 + 63 - 1; vdm.significand = vdm.sign ? (~m + 1) : m; @@ -544,7 +544,7 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32 int rmode = fpscr & FPSCR_RMODE_MASK; int tm; - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); /* @@ -605,7 +605,7 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32 } } - LOG_TRACE(Core_ARM11, "VFP: ftoui: d(s%d)=%08x exceptions=%08x\n", sd, d, exceptions); + LOG_TRACE(Core_ARM11, "VFP: ftoui: d(s%d)=%08x exceptions=%08x", sd, d, exceptions); vfp_put_float(state, d, sd); @@ -614,7 +614,7 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32 static u32 vfp_double_ftouiz(ARMul_State* state, int sd, int unused, int dm, u32 fpscr) { - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); return vfp_double_ftoui(state, sd, unused, dm, FPSCR_ROUND_TOZERO); } @@ -625,7 +625,7 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32 int rmode = fpscr & FPSCR_RMODE_MASK; int tm; - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); vfp_double_dump("VDM", &vdm); @@ -682,7 +682,7 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32 } } - LOG_TRACE(Core_ARM11, "VFP: ftosi: d(s%d)=%08x exceptions=%08x\n", sd, d, exceptions); + LOG_TRACE(Core_ARM11, "VFP: ftosi: d(s%d)=%08x exceptions=%08x", sd, d, exceptions); vfp_put_float(state, (s32)d, sd); @@ -691,7 +691,7 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32 static u32 vfp_double_ftosiz(ARMul_State* state, int dd, int unused, int dm, u32 fpscr) { - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); return vfp_double_ftosi(state, dd, unused, dm, FPSCR_ROUND_TOZERO); } @@ -775,7 +775,7 @@ u32 vfp_double_add(struct vfp_double *vdd, struct vfp_double *vdn,struct vfp_dou if (vdn->significand & (1ULL << 63) || vdm->significand & (1ULL << 63)) { - LOG_INFO(Core_ARM11, "VFP: bad FP values in %s\n", __func__); + LOG_INFO(Core_ARM11, "VFP: bad FP values in %s", __func__); vfp_double_dump("VDN", vdn); vfp_double_dump("VDM", vdm); } @@ -843,7 +843,7 @@ vfp_double_multiply(struct vfp_double *vdd, struct vfp_double *vdn, */ if (vdn->exponent < vdm->exponent) { std::swap(vdm, vdn); - LOG_TRACE(Core_ARM11, "VFP: swapping M <-> N\n"); + LOG_TRACE(Core_ARM11, "VFP: swapping M <-> N"); } vdd->sign = vdn->sign ^ vdm->sign; @@ -927,7 +927,7 @@ vfp_double_multiply_accumulate(ARMul_State* state, int dd, int dn, int dm, u32 f */ static u32 vfp_double_fmac(ARMul_State* state, int dd, int dn, int dm, u32 fpscr) { - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); return vfp_double_multiply_accumulate(state, dd, dn, dm, fpscr, 0, "fmac"); } @@ -936,7 +936,7 @@ static u32 vfp_double_fmac(ARMul_State* state, int dd, int dn, int dm, u32 fpscr */ static u32 vfp_double_fnmac(ARMul_State* state, int dd, int dn, int dm, u32 fpscr) { - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); return vfp_double_multiply_accumulate(state, dd, dn, dm, fpscr, NEG_MULTIPLY, "fnmac"); } @@ -945,7 +945,7 @@ static u32 vfp_double_fnmac(ARMul_State* state, int dd, int dn, int dm, u32 fpsc */ static u32 vfp_double_fmsc(ARMul_State* state, int dd, int dn, int dm, u32 fpscr) { - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); return vfp_double_multiply_accumulate(state, dd, dn, dm, fpscr, NEG_SUBTRACT, "fmsc"); } @@ -954,7 +954,7 @@ static u32 vfp_double_fmsc(ARMul_State* state, int dd, int dn, int dm, u32 fpscr */ static u32 vfp_double_fnmsc(ARMul_State* state, int dd, int dn, int dm, u32 fpscr) { - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); return vfp_double_multiply_accumulate(state, dd, dn, dm, fpscr, NEG_SUBTRACT | NEG_MULTIPLY, "fnmsc"); } @@ -966,7 +966,7 @@ static u32 vfp_double_fmul(ARMul_State* state, int dd, int dn, int dm, u32 fpscr struct vfp_double vdd, vdn, vdm; u32 exceptions; - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr); if (vdn.exponent == 0 && vdn.significand) vfp_double_normalise_denormal(&vdn); @@ -987,7 +987,7 @@ static u32 vfp_double_fnmul(ARMul_State* state, int dd, int dn, int dm, u32 fpsc struct vfp_double vdd, vdn, vdm; u32 exceptions; - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr); if (vdn.exponent == 0 && vdn.significand) vfp_double_normalise_denormal(&vdn); @@ -1010,7 +1010,7 @@ static u32 vfp_double_fadd(ARMul_State* state, int dd, int dn, int dm, u32 fpscr struct vfp_double vdd, vdn, vdm; u32 exceptions; - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr); if (vdn.exponent == 0 && vdn.significand) vfp_double_normalise_denormal(&vdn); @@ -1032,7 +1032,7 @@ static u32 vfp_double_fsub(ARMul_State* state, int dd, int dn, int dm, u32 fpscr struct vfp_double vdd, vdn, vdm; u32 exceptions; - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr); if (vdn.exponent == 0 && vdn.significand) vfp_double_normalise_denormal(&vdn); @@ -1060,7 +1060,7 @@ static u32 vfp_double_fdiv(ARMul_State* state, int dd, int dn, int dm, u32 fpscr u32 exceptions = 0; int tm, tn; - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr); vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); @@ -1185,7 +1185,7 @@ u32 vfp_double_cpdo(ARMul_State* state, u32 inst, u32 fpscr) unsigned int vecitr, veclen, vecstride; struct op *fop; - LOG_TRACE(Core_ARM11, "In %s\n", __FUNCTION__); + LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vecstride = (1 + ((fpscr & FPSCR_STRIDE_MASK) == FPSCR_STRIDE_MASK)); fop = (op == FOP_EXT) ? &fops_ext[FEXT_TO_IDX(inst)] : &fops[FOP_TO_IDX(op)]; @@ -1216,7 +1216,7 @@ u32 vfp_double_cpdo(ARMul_State* state, u32 inst, u32 fpscr) else veclen = fpscr & FPSCR_LENGTH_MASK; - LOG_TRACE(Core_ARM11, "VFP: vecstride=%u veclen=%u\n", vecstride, + LOG_TRACE(Core_ARM11, "VFP: vecstride=%u veclen=%u", vecstride, (veclen >> FPSCR_LENGTH_BIT) + 1); if (!fop->fn) { @@ -1230,16 +1230,16 @@ u32 vfp_double_cpdo(ARMul_State* state, u32 inst, u32 fpscr) type = (fop->flags & OP_SD) ? 's' : 'd'; if (op == FOP_EXT) - LOG_TRACE(Core_ARM11, "VFP: itr%d (%c%u) = op[%u] (d%u)\n", + LOG_TRACE(Core_ARM11, "VFP: itr%d (%c%u) = op[%u] (d%u)", vecitr >> FPSCR_LENGTH_BIT, type, dest, dn, dm); else - LOG_TRACE(Core_ARM11, "VFP: itr%d (%c%u) = (d%u) op[%u] (d%u)\n", + LOG_TRACE(Core_ARM11, "VFP: itr%d (%c%u) = (d%u) op[%u] (d%u)", vecitr >> FPSCR_LENGTH_BIT, type, dest, dn, FOP_TO_IDX(op), dm); except = fop->fn(state, dest, dn, dm, fpscr); - LOG_TRACE(Core_ARM11, "VFP: itr%d: exceptions=%08x\n", + LOG_TRACE(Core_ARM11, "VFP: itr%d: exceptions=%08x", vecitr >> FPSCR_LENGTH_BIT, except); exceptions |= except; diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 56615502c..aba22cdd1 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <atomic> +#include <cinttypes> #include <mutex> #include <vector> @@ -530,7 +531,7 @@ void Idle(int max_idle) { } } - LOG_TRACE(Core_Timing, "Idle for %i cycles! (%f ms)", cycles_down, cycles_down / (float)(g_clock_rate_arm11 * 0.001f)); + LOG_TRACE(Core_Timing, "Idle for %" PRId64 " cycles! (%f ms)", cycles_down, cycles_down / (float)(g_clock_rate_arm11 * 0.001f)); idled_cycles += cycles_down; Core::g_app_core->down_count -= cycles_down; diff --git a/src/core/file_sys/archive_backend.h b/src/core/file_sys/archive_backend.h index c6a1be79d..e7a59a1ed 100644 --- a/src/core/file_sys/archive_backend.h +++ b/src/core/file_sys/archive_backend.h @@ -131,6 +131,12 @@ public: * @return Opened directory, or nullptr */ virtual std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const = 0; + + /** + * Get the free space + * @return The number of free bytes in the archive + */ + virtual u64 GetFreeBytes() const = 0; }; class ArchiveFactory : NonCopyable { diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp index e9ecd2b1c..0ba502200 100644 --- a/src/core/file_sys/disk_archive.cpp +++ b/src/core/file_sys/disk_archive.cpp @@ -74,6 +74,11 @@ std::unique_ptr<DirectoryBackend> DiskArchive::OpenDirectory(const Path& path) c return std::move(directory); } +u64 DiskArchive::GetFreeBytes() const { + // TODO: Stubbed to return 1GiB + return 1024 * 1024 * 1024; +} + //////////////////////////////////////////////////////////////////////////////////////////////////// DiskFile::DiskFile(const DiskArchive& archive, const Path& path, const Mode mode) { diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h index aaac65b17..ef9a98057 100644 --- a/src/core/file_sys/disk_archive.h +++ b/src/core/file_sys/disk_archive.h @@ -41,6 +41,7 @@ public: bool CreateDirectory(const Path& path) const override; bool RenameDirectory(const Path& src_path, const Path& dest_path) const override; std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override; + u64 GetFreeBytes() const override; protected: friend class DiskFile; diff --git a/src/core/file_sys/ivfc_archive.cpp b/src/core/file_sys/ivfc_archive.cpp index e16aa1491..2efc31a8c 100644 --- a/src/core/file_sys/ivfc_archive.cpp +++ b/src/core/file_sys/ivfc_archive.cpp @@ -59,10 +59,15 @@ std::unique_ptr<DirectoryBackend> IVFCArchive::OpenDirectory(const Path& path) c return Common::make_unique<IVFCDirectory>(); } +u64 IVFCArchive::GetFreeBytes() const { + LOG_WARNING(Service_FS, "Attempted to get the free space in an IVFC archive"); + return 0; +} + //////////////////////////////////////////////////////////////////////////////////////////////////// size_t IVFCFile::Read(const u64 offset, const size_t length, u8* buffer) const { - LOG_TRACE(Service_FS, "called offset=%llu, length=%d", offset, length); + LOG_TRACE(Service_FS, "called offset=%llu, length=%zu", offset, length); romfs_file->Seek(data_offset + offset, SEEK_SET); size_t read_length = (size_t)std::min((u64)length, data_size - offset); diff --git a/src/core/file_sys/ivfc_archive.h b/src/core/file_sys/ivfc_archive.h index c15a6c4ae..f3fd82de4 100644 --- a/src/core/file_sys/ivfc_archive.h +++ b/src/core/file_sys/ivfc_archive.h @@ -42,6 +42,7 @@ public: bool CreateDirectory(const Path& path) const override; bool RenameDirectory(const Path& src_path, const Path& dest_path) const override; std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override; + u64 GetFreeBytes() const override; protected: std::shared_ptr<FileUtil::IOFile> romfs_file; diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index c10126513..00fa995f6 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -220,7 +220,7 @@ static void SwitchContext(Thread* new_thread) { // Clean up the thread's wait_objects, they'll be restored if needed during // the svcWaitSynchronization call - for (int i = 0; i < new_thread->wait_objects.size(); ++i) { + for (size_t i = 0; i < new_thread->wait_objects.size(); ++i) { SharedPtr<WaitObject> object = new_thread->wait_objects[i]; object->RemoveWaitingThread(new_thread); } diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index 8aa4110a6..08b3ea8c0 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> + #include "common/assert.h" #include "common/logging/log.h" @@ -71,11 +73,11 @@ static void TimerCallback(u64 timer_handle, int cycles_late) { SharedPtr<Timer> timer = timer_callback_handle_table.Get<Timer>(static_cast<Handle>(timer_handle)); if (timer == nullptr) { - LOG_CRITICAL(Kernel, "Callback fired for invalid timer %08lX", timer_handle); + LOG_CRITICAL(Kernel, "Callback fired for invalid timer %08" PRIx64, timer_handle); return; } - LOG_TRACE(Kernel, "Timer %u fired", timer_handle); + LOG_TRACE(Kernel, "Timer %08" PRIx64 " fired", timer_handle); timer->signaled = true; diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 6f2cf0190..56986a49e 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -77,10 +77,10 @@ static const ConsoleCountryInfo COUNTRY_INFO = { { 0, 0, 0 }, UNITED_STATES_COUN * for example Nintendo Zone * Thanks Normmatt for providing this information */ -static const std::array<float, 8> STEREO_CAMERA_SETTINGS = { +static const std::array<float, 8> STEREO_CAMERA_SETTINGS = {{ 62.0f, 289.0f, 76.80000305175781f, 46.08000183105469f, 10.0f, 5.0f, 55.58000183105469f, 21.56999969482422f -}; +}}; static_assert(sizeof(STEREO_CAMERA_SETTINGS) == 0x20, "STEREO_CAMERA_SETTINGS must be exactly 0x20 bytes"); static const u32 CONFIG_SAVEFILE_SIZE = 0x8000; @@ -345,7 +345,7 @@ ResultCode FormatConfig() { char16_t country_name_buffer[16][0x40] = {}; for (size_t i = 0; i < 16; ++i) { - auto size = Common::UTF8ToUTF16("Gensokyo").copy(country_name_buffer[i], 0x40); + Common::UTF8ToUTF16("Gensokyo").copy(country_name_buffer[i], 0x40); } // 0x000B0001 - Localized names for the profile Country res = CreateConfigInfoBlk(0x000B0001, sizeof(country_name_buffer), 0xE, country_name_buffer); diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index 7b7a76b08..fc2a16a04 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -41,10 +41,11 @@ struct SaveConfigBlockEntry { u16 flags; ///< The flags of the block, possibly used for access control }; -// TODO(Link Mauve): use a constexpr once MSVC starts supporting it. -#define C(code) (u16)((code)[0] | ((code)[1] << 8)) +static constexpr u16 C(const char code[2]) { + return code[0] | (code[1] << 8); +} -static const std::array<u16, 187> country_codes = { +static const std::array<u16, 187> country_codes = {{ 0, C("JP"), 0, 0, 0, 0, 0, 0, // 0-7 C("AI"), C("AG"), C("AR"), C("AW"), C("BS"), C("BB"), C("BZ"), C("BO"), // 8-15 C("BR"), C("VG"), C("CA"), C("KY"), C("CL"), C("CO"), C("CR"), C("DM"), // 16-23 @@ -69,9 +70,7 @@ static const std::array<u16, 187> country_codes = { C("AE"), C("IN"), C("EG"), C("OM"), C("QA"), C("KW"), C("SA"), C("SY"), // 168-175 C("BH"), C("JO"), 0, 0, 0, 0, 0, 0, // 176-183 C("SM"), C("VA"), C("BM") // 184-186 -}; - -#undef C +}}; /** * CFG::GetCountryCodeString service function diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp index a8cb15d60..ce5619069 100644 --- a/src/core/hle/service/dsp_dsp.cpp +++ b/src/core/hle/service/dsp_dsp.cpp @@ -212,10 +212,10 @@ static void ReadPipeIfPossible(Service::Interface* self) { // Canned DSP responses that games expect. These were taken from HW by 3dmoo team. // TODO: Remove this hack :) - static const std::array<u16, 16> canned_read_pipe = { + static const std::array<u16, 16> canned_read_pipe = {{ 0x000F, 0xBFFF, 0x9E8E, 0x8680, 0xA78E, 0x9430, 0x8400, 0x8540, 0x948E, 0x8710, 0x8410, 0xA90E, 0xAA0E, 0xAACE, 0xAC4E, 0xAC58 - }; + }}; u32 initial_size = read_pipe_count; diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 6c0df67c3..d64b3656a 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -403,6 +403,13 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a return MakeResult<Kernel::SharedPtr<Directory>>(std::move(directory)); } +ResultVal<u64> GetFreeBytesInArchive(ArchiveHandle archive_handle) { + ArchiveBackend* archive = GetArchive(archive_handle); + if (archive == nullptr) + return ERR_INVALID_HANDLE; + return MakeResult<u64>(archive->GetFreeBytes()); +} + ResultCode FormatArchive(ArchiveIdCode id_code, const FileSys::Path& path) { auto archive_itr = id_code_map.find(id_code); if (archive_itr == id_code_map.end()) { diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index 6f7048710..952deb4d4 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -167,6 +167,13 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a const FileSys::Path& path); /** + * Get the free space in an Archive + * @param archive_handle Handle to an open Archive object + * @return The number of free bytes in the archive + */ +ResultVal<u64> GetFreeBytesInArchive(ArchiveHandle archive_handle); + +/** * Erases the contents of the physical folder that contains the archive * identified by the specified id code and path * @param id_code The id of the archive to format diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index ae52083f9..b3fa89302 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -497,6 +497,33 @@ static void FormatThisUserSaveData(Service::Interface* self) { } /** + * FS_User::GetFreeBytes service function + * Inputs: + * 0: 0x08120080 + * 1: Archive handle low word + * 2: Archive handle high word + * Outputs: + * 1: Result of function, 0 on success, otherwise error code + * 2: Free byte count low word + * 3: Free byte count high word + */ +static void GetFreeBytes(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + ArchiveHandle archive_handle = MakeArchiveHandle(cmd_buff[1], cmd_buff[2]); + ResultVal<u64> bytes_res = GetFreeBytesInArchive(archive_handle); + + cmd_buff[1] = bytes_res.Code().raw; + if (bytes_res.Succeeded()) { + cmd_buff[2] = (u32)*bytes_res; + cmd_buff[3] = *bytes_res >> 32; + } else { + cmd_buff[2] = 0; + cmd_buff[3] = 0; + } +} + +/** * FS_User::CreateExtSaveData service function * Inputs: * 0 : 0x08510242 @@ -700,7 +727,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x080F0180, FormatThisUserSaveData,"FormatThisUserSaveData"}, {0x08100200, nullptr, "CreateSystemSaveData"}, {0x08110040, nullptr, "DeleteSystemSaveData"}, - {0x08120080, nullptr, "GetFreeBytes"}, + {0x08120080, GetFreeBytes, "GetFreeBytes"}, {0x08130000, nullptr, "GetCardType"}, {0x08140000, nullptr, "GetSdmcArchiveResource"}, {0x08150000, nullptr, "GetNandArchiveResource"}, diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index c35b13b25..2e1e5c3e9 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -35,14 +35,14 @@ static Kernel::SharedPtr<Kernel::Event> event_debug_pad; static u32 next_pad_index; static u32 next_touch_index; -const std::array<Service::HID::PadState, Settings::NativeInput::NUM_INPUTS> pad_mapping = { +const std::array<Service::HID::PadState, Settings::NativeInput::NUM_INPUTS> pad_mapping = {{ Service::HID::PAD_A, Service::HID::PAD_B, Service::HID::PAD_X, Service::HID::PAD_Y, Service::HID::PAD_L, Service::HID::PAD_R, Service::HID::PAD_ZL, Service::HID::PAD_ZR, Service::HID::PAD_START, Service::HID::PAD_SELECT, Service::HID::PAD_NONE, Service::HID::PAD_UP, Service::HID::PAD_DOWN, Service::HID::PAD_LEFT, Service::HID::PAD_RIGHT, Service::HID::PAD_CIRCLE_UP, Service::HID::PAD_CIRCLE_DOWN, Service::HID::PAD_CIRCLE_LEFT, Service::HID::PAD_CIRCLE_RIGHT, Service::HID::PAD_C_UP, Service::HID::PAD_C_DOWN, Service::HID::PAD_C_LEFT, Service::HID::PAD_C_RIGHT -}; +}}; // TODO(peachum): diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index 2c7d49c9f..22c1093ff 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -68,6 +68,18 @@ void GetBatteryChargeState(Service::Interface* self) { LOG_WARNING(Service_PTM, "(STUBBED) called"); } +void GetTotalStepCount(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + // TODO: This function is only a stub, + // it returns 0 as the total step count + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = 0; + + LOG_WARNING(Service_PTM, "(STUBBED) called"); +} + void IsLegacyPowerOff(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h index b690003cb..f2e76441f 100644 --- a/src/core/hle/service/ptm/ptm.h +++ b/src/core/hle/service/ptm/ptm.h @@ -72,6 +72,14 @@ void GetBatteryLevel(Interface* self); void GetBatteryChargeState(Interface* self); /** + * PTM::GetTotalStepCount service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Output of function, * = total step count + */ +void GetTotalStepCount(Interface* self); + +/** * PTM::IsLegacyPowerOff service function * Outputs: * 1: Result code, 0 on success, otherwise error code diff --git a/src/core/hle/service/ptm/ptm_u.cpp b/src/core/hle/service/ptm/ptm_u.cpp index 3f5e9c7c1..09dc38c3e 100644 --- a/src/core/hle/service/ptm/ptm_u.cpp +++ b/src/core/hle/service/ptm/ptm_u.cpp @@ -23,7 +23,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00090000, nullptr, "GetPedometerState"}, {0x000A0042, nullptr, "GetStepHistoryEntry"}, {0x000B00C2, nullptr, "GetStepHistory"}, - {0x000C0000, nullptr, "GetTotalStepCount"}, + {0x000C0000, GetTotalStepCount, "GetTotalStepCount"}, {0x000D0040, nullptr, "SetPedometerRecordingMode"}, {0x000E0000, nullptr, "GetPedometerRecordingMode"}, {0x000F0084, nullptr, "GetStepHistoryAll"}, diff --git a/src/core/hw/y2r.cpp b/src/core/hw/y2r.cpp index 15f96ced8..48c45564f 100644 --- a/src/core/hw/y2r.cpp +++ b/src/core/hw/y2r.cpp @@ -324,7 +324,7 @@ void PerformConversion(ConversionConfiguration& cvt) { u32* output_buffer = reinterpret_cast<u32*>(data_buffer.get()); - for (int i = 0; i < num_tiles; ++i) { + for (size_t i = 0; i < num_tiles; ++i) { int image_strip_width = 0; int output_stride = 0; diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 530837d08..8eed6a50a 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -62,6 +62,10 @@ struct THREEDSX_Header // Sizes of the code, rodata and data segments + // size of the BSS section (uninitialized latter half of the data segment) u32 code_seg_size, rodata_seg_size, data_seg_size, bss_size; + // offset and size of smdh + u32 smdh_offset, smdh_size; + // offset to filesystem + u32 fs_offset; }; // Relocation header: all fields (even extra unknown fields) are guaranteed to be relocation counts. @@ -177,14 +181,14 @@ static THREEDSX_Error Load3DSXFile(FileUtil::IOFile& file, u32 base_addr, Shared for (unsigned current_inprogress = 0; current_inprogress < remaining && pos < end_pos; current_inprogress++) { const auto& table = reloc_table[current_inprogress]; - LOG_TRACE(Loader, "(t=%d,skip=%u,patch=%u)\n", current_segment_reloc_table, + LOG_TRACE(Loader, "(t=%d,skip=%u,patch=%u)", current_segment_reloc_table, (u32)table.skip, (u32)table.patch); pos += table.skip; s32 num_patches = table.patch; while (0 < num_patches && pos < end_pos) { u32 in_addr = (u8*)pos - program_image.data(); u32 addr = TranslateAddr(*pos, &loadinfo, offsets); - LOG_TRACE(Loader, "Patching %08X <-- rel(%08X,%d) (%08X)\n", + LOG_TRACE(Loader, "Patching %08X <-- rel(%08X,%d) (%08X)", base_addr + in_addr, addr, current_segment_reloc_table, *pos); switch (current_segment_reloc_table) { case 0: @@ -267,4 +271,40 @@ ResultStatus AppLoader_THREEDSX::Load() { return ResultStatus::Success; } +ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, u64& size) { + if (!file.IsOpen()) + return ResultStatus::Error; + + // Reset read pointer in case this file has been read before. + file.Seek(0, SEEK_SET); + + THREEDSX_Header hdr; + if (file.ReadBytes(&hdr, sizeof(THREEDSX_Header)) != sizeof(THREEDSX_Header)) + return ResultStatus::Error; + + if (hdr.header_size != sizeof(THREEDSX_Header)) + return ResultStatus::Error; + + // Check if the 3DSX has a RomFS... + if (hdr.fs_offset != 0) { + u32 romfs_offset = hdr.fs_offset; + u32 romfs_size = file.GetSize() - hdr.fs_offset; + + LOG_DEBUG(Loader, "RomFS offset: 0x%08X", romfs_offset); + LOG_DEBUG(Loader, "RomFS size: 0x%08X", romfs_size); + + // We reopen the file, to allow its position to be independent from file's + romfs_file = std::make_shared<FileUtil::IOFile>(filepath, "rb"); + if (!romfs_file->IsOpen()) + return ResultStatus::Error; + + offset = romfs_offset; + size = romfs_size; + + return ResultStatus::Success; + } + LOG_DEBUG(Loader, "3DSX has no RomFS"); + return ResultStatus::ErrorNotUsed; +} + } // namespace Loader diff --git a/src/core/loader/3dsx.h b/src/core/loader/3dsx.h index a0aa0c533..365ddb7a5 100644 --- a/src/core/loader/3dsx.h +++ b/src/core/loader/3dsx.h @@ -17,8 +17,8 @@ namespace Loader { /// Loads an 3DSX file class AppLoader_THREEDSX final : public AppLoader { public: - AppLoader_THREEDSX(FileUtil::IOFile&& file, std::string filename) - : AppLoader(std::move(file)), filename(std::move(filename)) {} + AppLoader_THREEDSX(FileUtil::IOFile&& file, std::string filename, const std::string& filepath) + : AppLoader(std::move(file)), filename(std::move(filename)), filepath(filepath) {} /** * Returns the type of the file @@ -33,8 +33,18 @@ public: */ ResultStatus Load() override; + /** + * Get the RomFS of the application + * @param romfs_file Reference to buffer to store data + * @param offset Offset in the file to the RomFS + * @param size Size of the RomFS in bytes + * @return ResultStatus result of function + */ + ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, u64& size) override; + private: std::string filename; + std::string filepath; }; } // namespace Loader diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 74eb6e871..6b88169e1 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -26,12 +26,7 @@ const std::initializer_list<Kernel::AddressMapping> default_address_mappings = { { 0x1F000000, 0x600000, false }, // entire VRAM }; -/** - * Identifies the type of a bootable file - * @param file open file - * @return FileType of file - */ -static FileType IdentifyFile(FileUtil::IOFile& file) { +FileType IdentifyFile(FileUtil::IOFile& file) { FileType type; #define CHECK_TYPE(loader) \ @@ -48,12 +43,17 @@ static FileType IdentifyFile(FileUtil::IOFile& file) { return FileType::Unknown; } -/** - * Guess the type of a bootable file from its extension - * @param extension_ String extension of bootable file - * @return FileType of file - */ -static FileType GuessFromExtension(const std::string& extension_) { +FileType IdentifyFile(const std::string& file_name) { + FileUtil::IOFile file(file_name, "rb"); + if (!file.IsOpen()) { + LOG_ERROR(Loader, "Failed to load file %s", file_name.c_str()); + return FileType::Unknown; + } + + return IdentifyFile(file); +} + +FileType GuessFromExtension(const std::string& extension_) { std::string extension = Common::ToLower(extension_); if (extension == ".elf" || extension == ".axf") @@ -71,7 +71,7 @@ static FileType GuessFromExtension(const std::string& extension_) { return FileType::Unknown; } -static const char* GetFileTypeString(FileType type) { +const char* GetFileTypeString(FileType type) { switch (type) { case FileType::CCI: return "NCSD"; @@ -116,7 +116,15 @@ ResultStatus LoadFile(const std::string& filename) { //3DSX file format... case FileType::THREEDSX: - return AppLoader_THREEDSX(std::move(file), filename_filename).Load(); + { + AppLoader_THREEDSX app_loader(std::move(file), filename_filename, filename); + // Load application and RomFS + if (ResultStatus::Success == app_loader.Load()) { + Service::FS::RegisterArchiveType(Common::make_unique<FileSys::ArchiveFactory_RomFS>(app_loader), Service::FS::ArchiveIdCode::RomFS); + return ResultStatus::Success; + } + break; + } // Standard ELF file format... case FileType::ELF: diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index a37d3348c..a7f2715ba 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -33,6 +33,34 @@ enum class FileType { THREEDSX, //3DSX }; +/** + * Identifies the type of a bootable file based on the magic value in its header. + * @param file open file + * @return FileType of file + */ +FileType IdentifyFile(FileUtil::IOFile& file); + +/** + * Identifies the type of a bootable file based on the magic value in its header. + * @param file_name path to file + * @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine + * a filetype, and will never return FileType::Error. + */ +FileType IdentifyFile(const std::string& file_name); + +/** + * Guess the type of a bootable file from its extension + * @param extension String extension of bootable file + * @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine + * a filetype, and will never return FileType::Error. + */ +FileType GuessFromExtension(const std::string& extension_); + +/** + * Convert a FileType into a string which can be displayed to the user. + */ +const char* GetFileTypeString(FileType type); + /// Return type for functions in Loader namespace enum class ResultStatus { Success, @@ -43,6 +71,7 @@ enum class ResultStatus { ErrorNotUsed, ErrorAlreadyLoaded, ErrorMemoryAllocationFailed, + ErrorEncrypted, }; static inline u32 MakeMagic(char a, char b, char c, char d) { diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 094d74100..68b3f546e 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -128,9 +128,8 @@ ResultStatus AppLoader_NCCH::LoadExec() { if (ResultStatus::Success == ReadCode(code)) { std::string process_name = Common::StringFromFixedZeroTerminatedBuffer( (const char*)exheader_header.codeset_info.name, 8); - u64 program_id = *reinterpret_cast<u64_le const*>(&ncch_header.program_id[0]); - SharedPtr<CodeSet> codeset = CodeSet::Create(process_name, program_id); + SharedPtr<CodeSet> codeset = CodeSet::Create(process_name, ncch_header.program_id); codeset->code.offset = 0; codeset->code.addr = exheader_header.codeset_info.text.address; @@ -266,6 +265,11 @@ ResultStatus AppLoader_NCCH::Load() { LOG_DEBUG(Loader, "Thread priority: 0x%X" , priority); LOG_DEBUG(Loader, "Resource limit category: %d" , resource_limit_category); + if (exheader_header.arm11_system_local_caps.program_id != ncch_header.program_id) { + LOG_ERROR(Loader, "ExHeader Program ID mismatch: the ROM is probably encrypted."); + return ResultStatus::ErrorEncrypted; + } + // Read ExeFS... exefs_offset = ncch_header.exefs_offset * kBlockSize; diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index d875e4cf3..ca6772a78 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -17,31 +17,31 @@ struct NCCH_Header { u8 signature[0x100]; - u32 magic; - u32 content_size; + u32_le magic; + u32_le content_size; u8 partition_id[8]; - u16 maker_code; - u16 version; + u16_le maker_code; + u16_le version; u8 reserved_0[4]; - u8 program_id[8]; + u64_le program_id; u8 reserved_1[0x10]; u8 logo_region_hash[0x20]; u8 product_code[0x10]; u8 extended_header_hash[0x20]; - u32 extended_header_size; + u32_le extended_header_size; u8 reserved_2[4]; u8 flags[8]; - u32 plain_region_offset; - u32 plain_region_size; - u32 logo_region_offset; - u32 logo_region_size; - u32 exefs_offset; - u32 exefs_size; - u32 exefs_hash_region_size; + u32_le plain_region_offset; + u32_le plain_region_size; + u32_le logo_region_offset; + u32_le logo_region_size; + u32_le exefs_offset; + u32_le exefs_size; + u32_le exefs_hash_region_size; u8 reserved_3[4]; - u32 romfs_offset; - u32 romfs_size; - u32 romfs_hash_region_size; + u32_le romfs_offset; + u32_le romfs_size; + u32_le romfs_hash_region_size; u8 reserved_4[4]; u8 exefs_super_block_hash[0x20]; u8 romfs_super_block_hash[0x20]; @@ -109,8 +109,8 @@ struct ExHeader_StorageInfo { }; struct ExHeader_ARM11_SystemLocalCaps { - u8 program_id[8]; - u32 core_version; + u64_le program_id; + u32_le core_version; u8 reserved_flags[2]; union { u8 flags0; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index b80795e0c..fc79c3ee9 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -26,9 +26,9 @@ enum class PageType { }; /** - * A (reasonably) fast way of allowing switchable and remmapable process address spaces. It loosely + * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely * mimics the way a real CPU page table works, but instead is optimized for minimal decoding and - * fetching requirements when acessing. In the usual case of an access to regular memory, it only + * fetching requirements when accessing. In the usual case of an access to regular memory, it only * requires an indexed fetch and a check for NULL. */ struct PageTable { diff --git a/src/core/settings.h b/src/core/settings.h index 6ca0e1afc..0b05e5bee 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -19,22 +19,22 @@ enum Values { CUP, CDOWN, CLEFT, CRIGHT, NUM_INPUTS }; -static const std::array<const char*, NUM_INPUTS> Mapping = { +static const std::array<const char*, NUM_INPUTS> Mapping = {{ "pad_a", "pad_b", "pad_x", "pad_y", "pad_l", "pad_r", "pad_zl", "pad_zr", "pad_start", "pad_select", "pad_home", "pad_dup", "pad_ddown", "pad_dleft", "pad_dright", "pad_sup", "pad_sdown", "pad_sleft", "pad_sright", "pad_cup", "pad_cdown", "pad_cleft", "pad_cright" -}; -static const std::array<Values, NUM_INPUTS> All = { +}}; +static const std::array<Values, NUM_INPUTS> All = {{ A, B, X, Y, L, R, ZL, ZR, START, SELECT, HOME, DUP, DDOWN, DLEFT, DRIGHT, SUP, SDOWN, SLEFT, SRIGHT, CUP, CDOWN, CLEFT, CRIGHT -}; +}}; } diff --git a/src/core/tracer/recorder.cpp b/src/core/tracer/recorder.cpp index 656706c0c..c6dc35c83 100644 --- a/src/core/tracer/recorder.cpp +++ b/src/core/tracer/recorder.cpp @@ -143,11 +143,11 @@ void Recorder::Finish(const std::string& filename) { } void Recorder::FrameFinished() { - stream.push_back( { FrameMarker } ); + stream.push_back( { { FrameMarker } } ); } void Recorder::MemoryAccessed(const u8* data, u32 size, u32 physical_address) { - StreamElement element = { MemoryLoad }; + StreamElement element = { { MemoryLoad } }; element.data.memory_load.size = size; element.data.memory_load.physical_address = physical_address; @@ -168,7 +168,7 @@ void Recorder::MemoryAccessed(const u8* data, u32 size, u32 physical_address) { template<typename T> void Recorder::RegisterWritten(u32 physical_address, T value) { - StreamElement element = { RegisterWrite }; + StreamElement element = { { RegisterWrite } }; element.data.register_write.size = (sizeof(T) == 1) ? CTRegisterWrite::SIZE_8 : (sizeof(T) == 2) ? CTRegisterWrite::SIZE_16 : (sizeof(T) == 4) ? CTRegisterWrite::SIZE_32 diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 8c9d76ab4..2a924f4ad 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,6 +1,7 @@ set(SRCS renderer_opengl/gl_rasterizer.cpp renderer_opengl/gl_rasterizer_cache.cpp + renderer_opengl/gl_shader_gen.cpp renderer_opengl/gl_shader_util.cpp renderer_opengl/gl_state.cpp renderer_opengl/renderer_opengl.cpp @@ -21,8 +22,8 @@ set(HEADERS renderer_opengl/gl_rasterizer.h renderer_opengl/gl_rasterizer_cache.h renderer_opengl/gl_resource_manager.h + renderer_opengl/gl_shader_gen.h renderer_opengl/gl_shader_util.h - renderer_opengl/gl_shaders.h renderer_opengl/gl_state.h renderer_opengl/pica_to_gl.h renderer_opengl/renderer_opengl.h diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 77a4fe272..f1cfa9361 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -92,7 +92,7 @@ void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) { vertices.push_back(v2); int num_vertices = (int)vertices.size(); - faces.push_back({ num_vertices-3, num_vertices-2, num_vertices-1 }); + faces.push_back({{ num_vertices-3, num_vertices-2, num_vertices-1 }}); } void GeometryDumper::Dump() { @@ -576,8 +576,8 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture unsigned table_index = static_cast<int>((x < 2) ? table_index_1.Value() : table_index_2.Value()); static const std::array<std::array<u8, 2>, 8> etc1_modifier_table = {{ - { 2, 8 }, { 5, 17 }, { 9, 29 }, { 13, 42 }, - { 18, 60 }, { 24, 80 }, { 33, 106 }, { 47, 183 } + {{ 2, 8 }}, {{ 5, 17 }}, {{ 9, 29 }}, {{ 13, 42 }}, + {{ 18, 60 }}, {{ 24, 80 }}, {{ 33, 106 }}, {{ 47, 183 }} }}; int modifier = etc1_modifier_table.at(table_index).at(GetTableSubIndex(texel)); @@ -641,7 +641,7 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { // Initialize write structure png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (png_ptr == nullptr) { - LOG_ERROR(Debug_GPU, "Could not allocate write struct\n"); + LOG_ERROR(Debug_GPU, "Could not allocate write struct"); goto finalise; } @@ -649,13 +649,13 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { // Initialize info structure info_ptr = png_create_info_struct(png_ptr); if (info_ptr == nullptr) { - LOG_ERROR(Debug_GPU, "Could not allocate info struct\n"); + LOG_ERROR(Debug_GPU, "Could not allocate info struct"); goto finalise; } // Setup Exception handling if (setjmp(png_jmpbuf(png_ptr))) { - LOG_ERROR(Debug_GPU, "Error during png creation\n"); + LOG_ERROR(Debug_GPU, "Error during png creation"); goto finalise; } diff --git a/src/video_core/gpu_debugger.h b/src/video_core/gpu_debugger.h index fae5de7d1..a3aab216c 100644 --- a/src/video_core/gpu_debugger.h +++ b/src/video_core/gpu_debugger.h @@ -45,7 +45,6 @@ public: private: GraphicsDebugger* observed; - bool in_destruction; friend class GraphicsDebugger; }; diff --git a/src/video_core/pica.h b/src/video_core/pica.h index ff81b409d..2f1b2dec4 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -317,6 +317,7 @@ struct Regs { }; union { + u32 sources_raw; BitField< 0, 4, Source> color_source1; BitField< 4, 4, Source> color_source2; BitField< 8, 4, Source> color_source3; @@ -326,6 +327,7 @@ struct Regs { }; union { + u32 modifiers_raw; BitField< 0, 4, ColorModifier> color_modifier1; BitField< 4, 4, ColorModifier> color_modifier2; BitField< 8, 4, ColorModifier> color_modifier3; @@ -335,6 +337,7 @@ struct Regs { }; union { + u32 ops_raw; BitField< 0, 4, Operation> color_op; BitField<16, 4, Operation> alpha_op; }; @@ -348,6 +351,7 @@ struct Regs { }; union { + u32 scales_raw; BitField< 0, 2, u32> color_scale; BitField<16, 2, u32> alpha_scale; }; diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp index a90ff5fef..226fad783 100644 --- a/src/video_core/rasterizer.cpp +++ b/src/video_core/rasterizer.cpp @@ -462,7 +462,7 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, } default: - LOG_ERROR(HW_GPU, "Unknown texture coordinate wrapping mode %x\n", (int)mode); + LOG_ERROR(HW_GPU, "Unknown texture coordinate wrapping mode %x", (int)mode); UNIMPLEMENTED(); return 0; } @@ -541,7 +541,7 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, return combiner_output; default: - LOG_ERROR(HW_GPU, "Unknown color combiner source %d\n", (int)source); + LOG_ERROR(HW_GPU, "Unknown color combiner source %d", (int)source); UNIMPLEMENTED(); return {0, 0, 0, 0}; } @@ -679,7 +679,7 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, return { (u8)result, (u8)result, (u8)result }; } default: - LOG_ERROR(HW_GPU, "Unknown color combiner operation %d\n", (int)op); + LOG_ERROR(HW_GPU, "Unknown color combiner operation %d", (int)op); UNIMPLEMENTED(); return {0, 0, 0}; } @@ -716,7 +716,7 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, return (std::min(255, (input[0] + input[1])) * input[2]) / 255; default: - LOG_ERROR(HW_GPU, "Unknown alpha combiner operation %d\n", (int)op); + LOG_ERROR(HW_GPU, "Unknown alpha combiner operation %d", (int)op); UNIMPLEMENTED(); return 0; } @@ -735,11 +735,11 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, auto color_output = ColorCombine(tev_stage.color_op, color_result); // alpha combiner - std::array<u8,3> alpha_result = { + std::array<u8,3> alpha_result = {{ GetAlphaModifier(tev_stage.alpha_modifier1, GetSource(tev_stage.alpha_source1)), GetAlphaModifier(tev_stage.alpha_modifier2, GetSource(tev_stage.alpha_source2)), GetAlphaModifier(tev_stage.alpha_modifier3, GetSource(tev_stage.alpha_source3)) - }; + }}; auto alpha_output = AlphaCombine(tev_stage.alpha_op, alpha_result); combiner_output[0] = std::min((unsigned)255, color_output.r() * tev_stage.GetColorMultiplier()); @@ -967,6 +967,8 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, UNIMPLEMENTED(); break; } + + return {}; }; auto LookupFactorA = [&](Regs::BlendFactor factor) -> u8 { @@ -1000,6 +1002,8 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, UNIMPLEMENTED(); break; } + + return {}; }; static auto EvaluateBlendEquation = [](const Math::Vec4<u8>& src, const Math::Vec4<u8>& srcfactor, diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 50eb157a5..d1def2f3b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -8,6 +8,8 @@ #include <glad/glad.h> #include "common/color.h" +#include "common/file_util.h" +#include "common/make_unique.h" #include "common/math_util.h" #include "common/microprofile.h" #include "common/profiler.h" @@ -19,7 +21,7 @@ #include "video_core/pica.h" #include "video_core/utils.h" #include "video_core/renderer_opengl/gl_rasterizer.h" -#include "video_core/renderer_opengl/gl_shaders.h" +#include "video_core/renderer_opengl/gl_shader_gen.h" #include "video_core/renderer_opengl/gl_shader_util.h" #include "video_core/renderer_opengl/pica_to_gl.h" @@ -38,38 +40,8 @@ RasterizerOpenGL::RasterizerOpenGL() : last_fb_color_addr(0), last_fb_depth_addr RasterizerOpenGL::~RasterizerOpenGL() { } void RasterizerOpenGL::InitObjects() { - // Create the hardware shader program and get attrib/uniform locations - shader.Create(GLShaders::g_vertex_shader_hw, GLShaders::g_fragment_shader_hw); - attrib_position = glGetAttribLocation(shader.handle, "vert_position"); - attrib_color = glGetAttribLocation(shader.handle, "vert_color"); - attrib_texcoords = glGetAttribLocation(shader.handle, "vert_texcoords"); - - uniform_alphatest_enabled = glGetUniformLocation(shader.handle, "alphatest_enabled"); - uniform_alphatest_func = glGetUniformLocation(shader.handle, "alphatest_func"); - uniform_alphatest_ref = glGetUniformLocation(shader.handle, "alphatest_ref"); - - uniform_tex = glGetUniformLocation(shader.handle, "tex"); - - uniform_tev_combiner_buffer_color = glGetUniformLocation(shader.handle, "tev_combiner_buffer_color"); - - const auto tev_stages = Pica::g_state.regs.GetTevStages(); - for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); ++tev_stage_index) { - auto& uniform_tev_cfg = uniform_tev_cfgs[tev_stage_index]; - - std::string tev_ref_str = "tev_cfgs[" + std::to_string(tev_stage_index) + "]"; - uniform_tev_cfg.enabled = glGetUniformLocation(shader.handle, (tev_ref_str + ".enabled").c_str()); - uniform_tev_cfg.color_sources = glGetUniformLocation(shader.handle, (tev_ref_str + ".color_sources").c_str()); - uniform_tev_cfg.alpha_sources = glGetUniformLocation(shader.handle, (tev_ref_str + ".alpha_sources").c_str()); - uniform_tev_cfg.color_modifiers = glGetUniformLocation(shader.handle, (tev_ref_str + ".color_modifiers").c_str()); - uniform_tev_cfg.alpha_modifiers = glGetUniformLocation(shader.handle, (tev_ref_str + ".alpha_modifiers").c_str()); - uniform_tev_cfg.color_alpha_op = glGetUniformLocation(shader.handle, (tev_ref_str + ".color_alpha_op").c_str()); - uniform_tev_cfg.color_alpha_multiplier = glGetUniformLocation(shader.handle, (tev_ref_str + ".color_alpha_multiplier").c_str()); - uniform_tev_cfg.const_color = glGetUniformLocation(shader.handle, (tev_ref_str + ".const_color").c_str()); - uniform_tev_cfg.updates_combiner_buffer_color_alpha = glGetUniformLocation(shader.handle, (tev_ref_str + ".updates_combiner_buffer_color_alpha").c_str()); - } - // Create sampler objects - for (int i = 0; i < texture_samplers.size(); ++i) { + for (size_t i = 0; i < texture_samplers.size(); ++i) { texture_samplers[i].Create(); state.texture_units[i].sampler = texture_samplers[i].sampler.handle; } @@ -78,29 +50,25 @@ void RasterizerOpenGL::InitObjects() { vertex_buffer.Create(); vertex_array.Create(); - // Update OpenGL state state.draw.vertex_array = vertex_array.handle; state.draw.vertex_buffer = vertex_buffer.handle; - state.draw.shader_program = shader.handle; - state.Apply(); - // Set the texture samplers to correspond to different texture units - glUniform1i(uniform_tex, 0); - glUniform1i(uniform_tex + 1, 1); - glUniform1i(uniform_tex + 2, 2); - // Set vertex attributes - glVertexAttribPointer(attrib_position, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, position)); - glVertexAttribPointer(attrib_color, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, color)); - glVertexAttribPointer(attrib_texcoords, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord0)); - glVertexAttribPointer(attrib_texcoords + 1, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord1)); - glVertexAttribPointer(attrib_texcoords + 2, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord2)); - glEnableVertexAttribArray(attrib_position); - glEnableVertexAttribArray(attrib_color); - glEnableVertexAttribArray(attrib_texcoords); - glEnableVertexAttribArray(attrib_texcoords + 1); - glEnableVertexAttribArray(attrib_texcoords + 2); + glVertexAttribPointer(GLShader::ATTRIBUTE_POSITION, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, position)); + glEnableVertexAttribArray(GLShader::ATTRIBUTE_POSITION); + + glVertexAttribPointer(GLShader::ATTRIBUTE_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, color)); + glEnableVertexAttribArray(GLShader::ATTRIBUTE_COLOR); + + glVertexAttribPointer(GLShader::ATTRIBUTE_TEXCOORD0, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord0)); + glVertexAttribPointer(GLShader::ATTRIBUTE_TEXCOORD1, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord1)); + glVertexAttribPointer(GLShader::ATTRIBUTE_TEXCOORD2, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord2)); + glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORD0); + glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORD1); + glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORD2); + + SetShader(); // Create textures for OGL framebuffer that will be rendered to, initially 1x1 to succeed in framebuffer creation fb_color_texture.texture.Create(); @@ -150,61 +118,15 @@ void RasterizerOpenGL::InitObjects() { } void RasterizerOpenGL::Reset() { - const auto& regs = Pica::g_state.regs; - SyncCullMode(); SyncBlendEnabled(); SyncBlendFuncs(); SyncBlendColor(); - SyncAlphaTest(); SyncLogicOp(); SyncStencilTest(); SyncDepthTest(); - // TEV stage 0 - SyncTevSources(0, regs.tev_stage0); - SyncTevModifiers(0, regs.tev_stage0); - SyncTevOps(0, regs.tev_stage0); - SyncTevColor(0, regs.tev_stage0); - SyncTevMultipliers(0, regs.tev_stage0); - - // TEV stage 1 - SyncTevSources(1, regs.tev_stage1); - SyncTevModifiers(1, regs.tev_stage1); - SyncTevOps(1, regs.tev_stage1); - SyncTevColor(1, regs.tev_stage1); - SyncTevMultipliers(1, regs.tev_stage1); - - // TEV stage 2 - SyncTevSources(2, regs.tev_stage2); - SyncTevModifiers(2, regs.tev_stage2); - SyncTevOps(2, regs.tev_stage2); - SyncTevColor(2, regs.tev_stage2); - SyncTevMultipliers(2, regs.tev_stage2); - - // TEV stage 3 - SyncTevSources(3, regs.tev_stage3); - SyncTevModifiers(3, regs.tev_stage3); - SyncTevOps(3, regs.tev_stage3); - SyncTevColor(3, regs.tev_stage3); - SyncTevMultipliers(3, regs.tev_stage3); - - // TEV stage 4 - SyncTevSources(4, regs.tev_stage4); - SyncTevModifiers(4, regs.tev_stage4); - SyncTevOps(4, regs.tev_stage4); - SyncTevColor(4, regs.tev_stage4); - SyncTevMultipliers(4, regs.tev_stage4); - - // TEV stage 5 - SyncTevSources(5, regs.tev_stage5); - SyncTevModifiers(5, regs.tev_stage5); - SyncTevOps(5, regs.tev_stage5); - SyncTevColor(5, regs.tev_stage5); - SyncTevMultipliers(5, regs.tev_stage5); - - SyncCombinerColor(); - SyncCombinerWriteFlags(); + SetShader(); res_cache.FullFlush(); } @@ -221,6 +143,11 @@ void RasterizerOpenGL::DrawTriangles() { SyncFramebuffer(); SyncDrawState(); + if (state.draw.shader_dirty) { + SetShader(); + state.draw.shader_dirty = false; + } + glBufferData(GL_ARRAY_BUFFER, vertex_batch.size() * sizeof(HardwareVertex), vertex_batch.data(), GL_STREAM_DRAW); glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertex_batch.size()); @@ -272,6 +199,7 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { // Alpha test case PICA_REG_INDEX(output_merger.alpha_test): SyncAlphaTest(); + state.draw.shader_dirty = true; break; // Stencil test @@ -290,117 +218,57 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { SyncLogicOp(); break; - // TEV stage 0 + // TEV stages case PICA_REG_INDEX(tev_stage0.color_source1): - SyncTevSources(0, regs.tev_stage0); - break; case PICA_REG_INDEX(tev_stage0.color_modifier1): - SyncTevModifiers(0, regs.tev_stage0); - break; case PICA_REG_INDEX(tev_stage0.color_op): - SyncTevOps(0, regs.tev_stage0); - break; - case PICA_REG_INDEX(tev_stage0.const_r): - SyncTevColor(0, regs.tev_stage0); - break; case PICA_REG_INDEX(tev_stage0.color_scale): - SyncTevMultipliers(0, regs.tev_stage0); - break; - - // TEV stage 1 case PICA_REG_INDEX(tev_stage1.color_source1): - SyncTevSources(1, regs.tev_stage1); - break; case PICA_REG_INDEX(tev_stage1.color_modifier1): - SyncTevModifiers(1, regs.tev_stage1); - break; case PICA_REG_INDEX(tev_stage1.color_op): - SyncTevOps(1, regs.tev_stage1); - break; - case PICA_REG_INDEX(tev_stage1.const_r): - SyncTevColor(1, regs.tev_stage1); - break; case PICA_REG_INDEX(tev_stage1.color_scale): - SyncTevMultipliers(1, regs.tev_stage1); - break; - - // TEV stage 2 case PICA_REG_INDEX(tev_stage2.color_source1): - SyncTevSources(2, regs.tev_stage2); - break; case PICA_REG_INDEX(tev_stage2.color_modifier1): - SyncTevModifiers(2, regs.tev_stage2); - break; case PICA_REG_INDEX(tev_stage2.color_op): - SyncTevOps(2, regs.tev_stage2); - break; - case PICA_REG_INDEX(tev_stage2.const_r): - SyncTevColor(2, regs.tev_stage2); - break; case PICA_REG_INDEX(tev_stage2.color_scale): - SyncTevMultipliers(2, regs.tev_stage2); - break; - - // TEV stage 3 case PICA_REG_INDEX(tev_stage3.color_source1): - SyncTevSources(3, regs.tev_stage3); - break; case PICA_REG_INDEX(tev_stage3.color_modifier1): - SyncTevModifiers(3, regs.tev_stage3); - break; case PICA_REG_INDEX(tev_stage3.color_op): - SyncTevOps(3, regs.tev_stage3); - break; - case PICA_REG_INDEX(tev_stage3.const_r): - SyncTevColor(3, regs.tev_stage3); - break; case PICA_REG_INDEX(tev_stage3.color_scale): - SyncTevMultipliers(3, regs.tev_stage3); - break; - - // TEV stage 4 case PICA_REG_INDEX(tev_stage4.color_source1): - SyncTevSources(4, regs.tev_stage4); - break; case PICA_REG_INDEX(tev_stage4.color_modifier1): - SyncTevModifiers(4, regs.tev_stage4); - break; case PICA_REG_INDEX(tev_stage4.color_op): - SyncTevOps(4, regs.tev_stage4); + case PICA_REG_INDEX(tev_stage4.color_scale): + case PICA_REG_INDEX(tev_stage5.color_source1): + case PICA_REG_INDEX(tev_stage5.color_modifier1): + case PICA_REG_INDEX(tev_stage5.color_op): + case PICA_REG_INDEX(tev_stage5.color_scale): + case PICA_REG_INDEX(tev_combiner_buffer_input): + state.draw.shader_dirty = true; break; - case PICA_REG_INDEX(tev_stage4.const_r): - SyncTevColor(4, regs.tev_stage4); + case PICA_REG_INDEX(tev_stage0.const_r): + SyncTevConstColor(0, regs.tev_stage0); break; - case PICA_REG_INDEX(tev_stage4.color_scale): - SyncTevMultipliers(4, regs.tev_stage4); + case PICA_REG_INDEX(tev_stage1.const_r): + SyncTevConstColor(1, regs.tev_stage1); break; - - // TEV stage 5 - case PICA_REG_INDEX(tev_stage5.color_source1): - SyncTevSources(5, regs.tev_stage5); + case PICA_REG_INDEX(tev_stage2.const_r): + SyncTevConstColor(2, regs.tev_stage2); break; - case PICA_REG_INDEX(tev_stage5.color_modifier1): - SyncTevModifiers(5, regs.tev_stage5); + case PICA_REG_INDEX(tev_stage3.const_r): + SyncTevConstColor(3, regs.tev_stage3); break; - case PICA_REG_INDEX(tev_stage5.color_op): - SyncTevOps(5, regs.tev_stage5); + case PICA_REG_INDEX(tev_stage4.const_r): + SyncTevConstColor(4, regs.tev_stage4); break; case PICA_REG_INDEX(tev_stage5.const_r): - SyncTevColor(5, regs.tev_stage5); - break; - case PICA_REG_INDEX(tev_stage5.color_scale): - SyncTevMultipliers(5, regs.tev_stage5); + SyncTevConstColor(5, regs.tev_stage5); break; // TEV combiner buffer color case PICA_REG_INDEX(tev_combiner_buffer_color): SyncCombinerColor(); break; - - // TEV combiner buffer write flags - case PICA_REG_INDEX(tev_combiner_buffer_input): - SyncCombinerWriteFlags(); - break; } } @@ -592,6 +460,41 @@ void RasterizerOpenGL::ReconfigureDepthTexture(DepthTextureInfo& texture, Pica:: state.Apply(); } +void RasterizerOpenGL::SetShader() { + PicaShaderConfig config = PicaShaderConfig::CurrentConfig(); + std::unique_ptr<PicaShader> shader = Common::make_unique<PicaShader>(); + + // Find (or generate) the GLSL shader for the current TEV state + auto cached_shader = shader_cache.find(config); + if (cached_shader != shader_cache.end()) { + current_shader = cached_shader->second.get(); + + state.draw.shader_program = current_shader->shader.handle; + state.Apply(); + } else { + LOG_DEBUG(Render_OpenGL, "Creating new shader"); + + shader->shader.Create(GLShader::GenerateVertexShader().c_str(), GLShader::GenerateFragmentShader(config).c_str()); + + state.draw.shader_program = shader->shader.handle; + state.Apply(); + + // Set the texture samplers to correspond to different texture units + glUniform1i(PicaShader::Uniform::Texture0, 0); + glUniform1i(PicaShader::Uniform::Texture1, 1); + glUniform1i(PicaShader::Uniform::Texture2, 2); + + current_shader = shader_cache.emplace(config, std::move(shader)).first->second.get(); + } + + // Update uniforms + SyncAlphaTest(); + SyncCombinerColor(); + auto& tev_stages = Pica::g_state.regs.GetTevStages(); + for (int index = 0; index < tev_stages.size(); ++index) + SyncTevConstColor(index, tev_stages[index]); +} + void RasterizerOpenGL::SyncFramebuffer() { const auto& regs = Pica::g_state.regs; @@ -601,8 +504,8 @@ void RasterizerOpenGL::SyncFramebuffer() { PAddr cur_fb_depth_addr = regs.framebuffer.GetDepthBufferPhysicalAddress(); Pica::Regs::DepthFormat new_fb_depth_format = regs.framebuffer.depth_format; - bool fb_size_changed = fb_color_texture.width != regs.framebuffer.GetWidth() || - fb_color_texture.height != regs.framebuffer.GetHeight(); + bool fb_size_changed = fb_color_texture.width != static_cast<GLsizei>(regs.framebuffer.GetWidth()) || + fb_color_texture.height != static_cast<GLsizei>(regs.framebuffer.GetHeight()); bool color_fb_prop_changed = fb_color_texture.format != new_fb_color_format || fb_size_changed; @@ -712,9 +615,7 @@ void RasterizerOpenGL::SyncBlendColor() { void RasterizerOpenGL::SyncAlphaTest() { const auto& regs = Pica::g_state.regs; - glUniform1i(uniform_alphatest_enabled, regs.output_merger.alpha_test.enable); - glUniform1i(uniform_alphatest_func, (GLint)regs.output_merger.alpha_test.func.Value()); - glUniform1f(uniform_alphatest_ref, regs.output_merger.alpha_test.ref / 255.0f); + glUniform1i(PicaShader::Uniform::AlphaTestRef, regs.output_merger.alpha_test.ref); } void RasterizerOpenGL::SyncLogicOp() { @@ -744,56 +645,14 @@ void RasterizerOpenGL::SyncDepthTest() { state.depth.write_mask = regs.output_merger.depth_write_enable ? GL_TRUE : GL_FALSE; } -void RasterizerOpenGL::SyncTevSources(unsigned stage_index, const Pica::Regs::TevStageConfig& config) { - GLint color_srcs[3] = { (GLint)config.color_source1.Value(), - (GLint)config.color_source2.Value(), - (GLint)config.color_source3.Value() }; - GLint alpha_srcs[3] = { (GLint)config.alpha_source1.Value(), - (GLint)config.alpha_source2.Value(), - (GLint)config.alpha_source3.Value() }; - - glUniform3iv(uniform_tev_cfgs[stage_index].color_sources, 1, color_srcs); - glUniform3iv(uniform_tev_cfgs[stage_index].alpha_sources, 1, alpha_srcs); -} - -void RasterizerOpenGL::SyncTevModifiers(unsigned stage_index, const Pica::Regs::TevStageConfig& config) { - GLint color_mods[3] = { (GLint)config.color_modifier1.Value(), - (GLint)config.color_modifier2.Value(), - (GLint)config.color_modifier3.Value() }; - GLint alpha_mods[3] = { (GLint)config.alpha_modifier1.Value(), - (GLint)config.alpha_modifier2.Value(), - (GLint)config.alpha_modifier3.Value() }; - - glUniform3iv(uniform_tev_cfgs[stage_index].color_modifiers, 1, color_mods); - glUniform3iv(uniform_tev_cfgs[stage_index].alpha_modifiers, 1, alpha_mods); -} - -void RasterizerOpenGL::SyncTevOps(unsigned stage_index, const Pica::Regs::TevStageConfig& config) { - glUniform2i(uniform_tev_cfgs[stage_index].color_alpha_op, (GLint)config.color_op.Value(), (GLint)config.alpha_op.Value()); -} - -void RasterizerOpenGL::SyncTevColor(unsigned stage_index, const Pica::Regs::TevStageConfig& config) { - auto const_color = PicaToGL::ColorRGBA8(config.const_color); - glUniform4fv(uniform_tev_cfgs[stage_index].const_color, 1, const_color.data()); -} - -void RasterizerOpenGL::SyncTevMultipliers(unsigned stage_index, const Pica::Regs::TevStageConfig& config) { - glUniform2i(uniform_tev_cfgs[stage_index].color_alpha_multiplier, config.GetColorMultiplier(), config.GetAlphaMultiplier()); -} - void RasterizerOpenGL::SyncCombinerColor() { auto combiner_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.tev_combiner_buffer_color.raw); - glUniform4fv(uniform_tev_combiner_buffer_color, 1, combiner_color.data()); + glUniform4fv(PicaShader::Uniform::TevCombinerBufferColor, 1, combiner_color.data()); } -void RasterizerOpenGL::SyncCombinerWriteFlags() { - const auto& regs = Pica::g_state.regs; - const auto tev_stages = regs.GetTevStages(); - for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); ++tev_stage_index) { - glUniform2i(uniform_tev_cfgs[tev_stage_index].updates_combiner_buffer_color_alpha, - regs.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferColor(tev_stage_index), - regs.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferAlpha(tev_stage_index)); - } +void RasterizerOpenGL::SyncTevConstColor(int stage_index, const Pica::Regs::TevStageConfig& tev_stage) { + auto const_color = PicaToGL::ColorRGBA8(tev_stage.const_color); + glUniform4fv(PicaShader::Uniform::TevConstColors + stage_index, 1, const_color.data()); } void RasterizerOpenGL::SyncDrawState() { @@ -824,12 +683,6 @@ void RasterizerOpenGL::SyncDrawState() { } } - // Skip processing TEV stages that simply pass the previous stage results through - const auto tev_stages = regs.GetTevStages(); - for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); ++tev_stage_index) { - glUniform1i(uniform_tev_cfgs[tev_stage_index].enabled, !IsPassThroughTevStage(tev_stages[tev_stage_index])); - } - state.Apply(); } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 1fe307846..872cae7da 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -4,15 +4,104 @@ #pragma once +#include <cstddef> +#include <cstring> +#include <memory> #include <vector> +#include <unordered_map> #include "common/common_types.h" +#include "common/hash.h" +#include "video_core/pica.h" #include "video_core/hwrasterizer_base.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_state.h" #include "video_core/shader/shader_interpreter.h" +/** + * This struct contains all state used to generate the GLSL shader program that emulates the current + * Pica register configuration. This struct is used as a cache key for generated GLSL shader + * programs. The functions in gl_shader_gen.cpp should retrieve state from this struct only, not by + * directly accessing Pica registers. This should reduce the risk of bugs in shader generation where + * Pica state is not being captured in the shader cache key, thereby resulting in (what should be) + * two separate shaders sharing the same key. + */ +struct PicaShaderConfig { + /// Construct a PicaShaderConfig with the current Pica register configuration. + static PicaShaderConfig CurrentConfig() { + PicaShaderConfig res; + const auto& regs = Pica::g_state.regs; + + res.alpha_test_func = regs.output_merger.alpha_test.enable ? + regs.output_merger.alpha_test.func.Value() : Pica::Regs::CompareFunc::Always; + + // Copy relevant TevStageConfig fields only. We're doing this manually (instead of calling + // the GetTevStages() function) because BitField explicitly disables copies. + + res.tev_stages[0].sources_raw = regs.tev_stage0.sources_raw; + res.tev_stages[1].sources_raw = regs.tev_stage1.sources_raw; + res.tev_stages[2].sources_raw = regs.tev_stage2.sources_raw; + res.tev_stages[3].sources_raw = regs.tev_stage3.sources_raw; + res.tev_stages[4].sources_raw = regs.tev_stage4.sources_raw; + res.tev_stages[5].sources_raw = regs.tev_stage5.sources_raw; + + res.tev_stages[0].modifiers_raw = regs.tev_stage0.modifiers_raw; + res.tev_stages[1].modifiers_raw = regs.tev_stage1.modifiers_raw; + res.tev_stages[2].modifiers_raw = regs.tev_stage2.modifiers_raw; + res.tev_stages[3].modifiers_raw = regs.tev_stage3.modifiers_raw; + res.tev_stages[4].modifiers_raw = regs.tev_stage4.modifiers_raw; + res.tev_stages[5].modifiers_raw = regs.tev_stage5.modifiers_raw; + + res.tev_stages[0].ops_raw = regs.tev_stage0.ops_raw; + res.tev_stages[1].ops_raw = regs.tev_stage1.ops_raw; + res.tev_stages[2].ops_raw = regs.tev_stage2.ops_raw; + res.tev_stages[3].ops_raw = regs.tev_stage3.ops_raw; + res.tev_stages[4].ops_raw = regs.tev_stage4.ops_raw; + res.tev_stages[5].ops_raw = regs.tev_stage5.ops_raw; + + res.tev_stages[0].scales_raw = regs.tev_stage0.scales_raw; + res.tev_stages[1].scales_raw = regs.tev_stage1.scales_raw; + res.tev_stages[2].scales_raw = regs.tev_stage2.scales_raw; + res.tev_stages[3].scales_raw = regs.tev_stage3.scales_raw; + res.tev_stages[4].scales_raw = regs.tev_stage4.scales_raw; + res.tev_stages[5].scales_raw = regs.tev_stage5.scales_raw; + + res.combiner_buffer_input = + regs.tev_combiner_buffer_input.update_mask_rgb.Value() | + regs.tev_combiner_buffer_input.update_mask_a.Value() << 4; + + return res; + } + + bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const { + return (stage_index < 4) && (combiner_buffer_input & (1 << stage_index)); + } + + bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const { + return (stage_index < 4) && ((combiner_buffer_input >> 4) & (1 << stage_index)); + } + + bool operator ==(const PicaShaderConfig& o) const { + return std::memcmp(this, &o, sizeof(PicaShaderConfig)) == 0; + }; + + Pica::Regs::CompareFunc alpha_test_func; + std::array<Pica::Regs::TevStageConfig, 6> tev_stages = {}; + u8 combiner_buffer_input; +}; + +namespace std { + +template <> +struct hash<PicaShaderConfig> { + size_t operator()(const PicaShaderConfig& k) const { + return Common::ComputeHash64(&k, sizeof(PicaShaderConfig)); + } +}; + +} // namespace std + class RasterizerOpenGL : public HWRasterizer { public: @@ -45,20 +134,24 @@ public: /// Notify rasterizer that a 3DS memory region has been changed void NotifyFlush(PAddr addr, u32 size) override; -private: - /// Structure used for managing texture environment states - struct TEVConfigUniforms { - GLuint enabled; - GLuint color_sources; - GLuint alpha_sources; - GLuint color_modifiers; - GLuint alpha_modifiers; - GLuint color_alpha_op; - GLuint color_alpha_multiplier; - GLuint const_color; - GLuint updates_combiner_buffer_color_alpha; + /// OpenGL shader generated for a given Pica register state + struct PicaShader { + /// OpenGL shader resource + OGLShader shader; + + /// Fragment shader uniforms + enum Uniform : GLuint { + AlphaTestRef = 0, + TevConstColors = 1, + Texture0 = 7, + Texture1 = 8, + Texture2 = 9, + TevCombinerBufferColor = 10, + }; }; +private: + /// Structure used for storing information about color textures struct TextureInfo { OGLTexture texture; @@ -129,6 +222,9 @@ private: /// Reconfigure the OpenGL depth texture to use the given format and dimensions void ReconfigureDepthTexture(DepthTextureInfo& texture, Pica::Regs::DepthFormat format, u32 width, u32 height); + /// Sets the OpenGL shader in accordance with the current PICA register state + void SetShader(); + /// Syncs the state and contents of the OpenGL framebuffer to match the current PICA framebuffer void SyncFramebuffer(); @@ -156,27 +252,12 @@ private: /// Syncs the depth test states to match the PICA register void SyncDepthTest(); - /// Syncs the specified TEV stage's color and alpha sources to match the PICA register - void SyncTevSources(unsigned stage_index, const Pica::Regs::TevStageConfig& config); - - /// Syncs the specified TEV stage's color and alpha modifiers to match the PICA register - void SyncTevModifiers(unsigned stage_index, const Pica::Regs::TevStageConfig& config); - - /// Syncs the specified TEV stage's color and alpha combiner operations to match the PICA register - void SyncTevOps(unsigned stage_index, const Pica::Regs::TevStageConfig& config); - - /// Syncs the specified TEV stage's constant color to match the PICA register - void SyncTevColor(unsigned stage_index, const Pica::Regs::TevStageConfig& config); - - /// Syncs the specified TEV stage's color and alpha multipliers to match the PICA register - void SyncTevMultipliers(unsigned stage_index, const Pica::Regs::TevStageConfig& config); + /// Syncs the TEV constant color to match the PICA register + void SyncTevConstColor(int tev_index, const Pica::Regs::TevStageConfig& tev_stage); /// Syncs the TEV combiner color buffer to match the PICA register void SyncCombinerColor(); - /// Syncs the TEV combiner write flags to match the PICA register - void SyncCombinerWriteFlags(); - /// Syncs the remaining OpenGL drawing state to match the current PICA state void SyncDrawState(); @@ -213,21 +294,11 @@ private: std::array<SamplerInfo, 3> texture_samplers; TextureInfo fb_color_texture; DepthTextureInfo fb_depth_texture; - OGLShader shader; + + std::unordered_map<PicaShaderConfig, std::unique_ptr<PicaShader>> shader_cache; + const PicaShader* current_shader = nullptr; + OGLVertexArray vertex_array; OGLBuffer vertex_buffer; OGLFramebuffer framebuffer; - - // Hardware vertex shader - GLuint attrib_position; - GLuint attrib_color; - GLuint attrib_texcoords; - - // Hardware fragment shader - GLuint uniform_alphatest_enabled; - GLuint uniform_alphatest_func; - GLuint uniform_alphatest_ref; - GLuint uniform_tex; - GLuint uniform_tev_combiner_buffer_color; - TEVConfigUniforms uniform_tev_cfgs[6]; }; diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h index 65034d40d..eb128966c 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.h +++ b/src/video_core/renderer_opengl/gl_resource_manager.h @@ -71,7 +71,7 @@ public: /// Creates a new internal OpenGL resource and stores the handle void Create(const char* vert_shader, const char* frag_shader) { if (handle != 0) return; - handle = ShaderUtil::LoadShaders(vert_shader, frag_shader); + handle = GLShader::LoadProgram(vert_shader, frag_shader); } /// Deletes the internal OpenGL resource diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp new file mode 100644 index 000000000..d19d15e75 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -0,0 +1,388 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "video_core/pica.h" +#include "video_core/renderer_opengl/gl_rasterizer.h" +#include "video_core/renderer_opengl/gl_shader_gen.h" + +using Pica::Regs; +using TevStageConfig = Regs::TevStageConfig; + +namespace GLShader { + +/// Detects if a TEV stage is configured to be skipped (to avoid generating unnecessary code) +static bool IsPassThroughTevStage(const TevStageConfig& stage) { + return (stage.color_op == TevStageConfig::Operation::Replace && + stage.alpha_op == TevStageConfig::Operation::Replace && + stage.color_source1 == TevStageConfig::Source::Previous && + stage.alpha_source1 == TevStageConfig::Source::Previous && + stage.color_modifier1 == TevStageConfig::ColorModifier::SourceColor && + stage.alpha_modifier1 == TevStageConfig::AlphaModifier::SourceAlpha && + stage.GetColorMultiplier() == 1 && + stage.GetAlphaMultiplier() == 1); +} + +/// Writes the specified TEV stage source component(s) +static void AppendSource(std::string& out, TevStageConfig::Source source, + const std::string& index_name) { + using Source = TevStageConfig::Source; + switch (source) { + case Source::PrimaryColor: + out += "primary_color"; + break; + case Source::PrimaryFragmentColor: + // HACK: Until we implement fragment lighting, use primary_color + out += "primary_color"; + break; + case Source::SecondaryFragmentColor: + // HACK: Until we implement fragment lighting, use zero + out += "vec4(0.0)"; + break; + case Source::Texture0: + out += "texture(tex[0], texcoord[0])"; + break; + case Source::Texture1: + out += "texture(tex[1], texcoord[1])"; + break; + case Source::Texture2: + out += "texture(tex[2], texcoord[2])"; + break; + case Source::PreviousBuffer: + out += "combiner_buffer"; + break; + case Source::Constant: + ((out += "const_color[") += index_name) += ']'; + break; + case Source::Previous: + out += "last_tex_env_out"; + break; + default: + out += "vec4(0.0)"; + LOG_CRITICAL(Render_OpenGL, "Unknown source op %u", source); + break; + } +} + +/// Writes the color components to use for the specified TEV stage color modifier +static void AppendColorModifier(std::string& out, TevStageConfig::ColorModifier modifier, + TevStageConfig::Source source, const std::string& index_name) { + using ColorModifier = TevStageConfig::ColorModifier; + switch (modifier) { + case ColorModifier::SourceColor: + AppendSource(out, source, index_name); + out += ".rgb"; + break; + case ColorModifier::OneMinusSourceColor: + out += "vec3(1.0) - "; + AppendSource(out, source, index_name); + out += ".rgb"; + break; + case ColorModifier::SourceAlpha: + AppendSource(out, source, index_name); + out += ".aaa"; + break; + case ColorModifier::OneMinusSourceAlpha: + out += "vec3(1.0) - "; + AppendSource(out, source, index_name); + out += ".aaa"; + break; + case ColorModifier::SourceRed: + AppendSource(out, source, index_name); + out += ".rrr"; + break; + case ColorModifier::OneMinusSourceRed: + out += "vec3(1.0) - "; + AppendSource(out, source, index_name); + out += ".rrr"; + break; + case ColorModifier::SourceGreen: + AppendSource(out, source, index_name); + out += ".ggg"; + break; + case ColorModifier::OneMinusSourceGreen: + out += "vec3(1.0) - "; + AppendSource(out, source, index_name); + out += ".ggg"; + break; + case ColorModifier::SourceBlue: + AppendSource(out, source, index_name); + out += ".bbb"; + break; + case ColorModifier::OneMinusSourceBlue: + out += "vec3(1.0) - "; + AppendSource(out, source, index_name); + out += ".bbb"; + break; + default: + out += "vec3(0.0)"; + LOG_CRITICAL(Render_OpenGL, "Unknown color modifier op %u", modifier); + break; + } +} + +/// Writes the alpha component to use for the specified TEV stage alpha modifier +static void AppendAlphaModifier(std::string& out, TevStageConfig::AlphaModifier modifier, + TevStageConfig::Source source, const std::string& index_name) { + using AlphaModifier = TevStageConfig::AlphaModifier; + switch (modifier) { + case AlphaModifier::SourceAlpha: + AppendSource(out, source, index_name); + out += ".a"; + break; + case AlphaModifier::OneMinusSourceAlpha: + out += "1.0 - "; + AppendSource(out, source, index_name); + out += ".a"; + break; + case AlphaModifier::SourceRed: + AppendSource(out, source, index_name); + out += ".r"; + break; + case AlphaModifier::OneMinusSourceRed: + out += "1.0 - "; + AppendSource(out, source, index_name); + out += ".r"; + break; + case AlphaModifier::SourceGreen: + AppendSource(out, source, index_name); + out += ".g"; + break; + case AlphaModifier::OneMinusSourceGreen: + out += "1.0 - "; + AppendSource(out, source, index_name); + out += ".g"; + break; + case AlphaModifier::SourceBlue: + AppendSource(out, source, index_name); + out += ".b"; + break; + case AlphaModifier::OneMinusSourceBlue: + out += "1.0 - "; + AppendSource(out, source, index_name); + out += ".b"; + break; + default: + out += "0.0"; + LOG_CRITICAL(Render_OpenGL, "Unknown alpha modifier op %u", modifier); + break; + } +} + +/// Writes the combiner function for the color components for the specified TEV stage operation +static void AppendColorCombiner(std::string& out, TevStageConfig::Operation operation, + const std::string& variable_name) { + out += "clamp("; + using Operation = TevStageConfig::Operation; + switch (operation) { + case Operation::Replace: + out += variable_name + "[0]"; + break; + case Operation::Modulate: + out += variable_name + "[0] * " + variable_name + "[1]"; + break; + case Operation::Add: + out += variable_name + "[0] + " + variable_name + "[1]"; + break; + case Operation::AddSigned: + out += variable_name + "[0] + " + variable_name + "[1] - vec3(0.5)"; + break; + case Operation::Lerp: + // TODO(bunnei): Verify if HW actually does this per-component, otherwise we can just use builtin lerp + out += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (vec3(1.0) - " + variable_name + "[2])"; + break; + case Operation::Subtract: + out += variable_name + "[0] - " + variable_name + "[1]"; + break; + case Operation::MultiplyThenAdd: + out += variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2]"; + break; + case Operation::AddThenMultiply: + out += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0)) * " + variable_name + "[2]"; + break; + default: + out += "vec3(0.0)"; + LOG_CRITICAL(Render_OpenGL, "Unknown color combiner operation: %u", operation); + break; + } + out += ", vec3(0.0), vec3(1.0))"; // Clamp result to 0.0, 1.0 +} + +/// Writes the combiner function for the alpha component for the specified TEV stage operation +static void AppendAlphaCombiner(std::string& out, TevStageConfig::Operation operation, + const std::string& variable_name) { + out += "clamp("; + using Operation = TevStageConfig::Operation; + switch (operation) { + case Operation::Replace: + out += variable_name + "[0]"; + break; + case Operation::Modulate: + out += variable_name + "[0] * " + variable_name + "[1]"; + break; + case Operation::Add: + out += variable_name + "[0] + " + variable_name + "[1]"; + break; + case Operation::AddSigned: + out += variable_name + "[0] + " + variable_name + "[1] - 0.5"; + break; + case Operation::Lerp: + out += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (1.0 - " + variable_name + "[2])"; + break; + case Operation::Subtract: + out += variable_name + "[0] - " + variable_name + "[1]"; + break; + case Operation::MultiplyThenAdd: + out += variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2]"; + break; + case Operation::AddThenMultiply: + out += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0) * " + variable_name + "[2]"; + break; + default: + out += "0.0"; + LOG_CRITICAL(Render_OpenGL, "Unknown alpha combiner operation: %u", operation); + break; + } + out += ", 0.0, 1.0)"; +} + +/// Writes the if-statement condition used to evaluate alpha testing +static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) { + using CompareFunc = Regs::CompareFunc; + switch (func) { + case CompareFunc::Never: + out += "true"; + break; + case CompareFunc::Always: + out += "false"; + break; + case CompareFunc::Equal: + case CompareFunc::NotEqual: + case CompareFunc::LessThan: + case CompareFunc::LessThanOrEqual: + case CompareFunc::GreaterThan: + case CompareFunc::GreaterThanOrEqual: + { + static const char* op[] = { "!=", "==", ">=", ">", "<=", "<", }; + unsigned index = (unsigned)func - (unsigned)CompareFunc::Equal; + out += "int(last_tex_env_out.a * 255.0f) " + std::string(op[index]) + " alphatest_ref"; + break; + } + + default: + out += "false"; + LOG_CRITICAL(Render_OpenGL, "Unknown alpha test condition %u", func); + break; + } +} + +/// Writes the code to emulate the specified TEV stage +static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsigned index) { + auto& stage = config.tev_stages[index]; + if (!IsPassThroughTevStage(stage)) { + std::string index_name = std::to_string(index); + + out += "vec3 color_results_" + index_name + "[3] = vec3[3]("; + AppendColorModifier(out, stage.color_modifier1, stage.color_source1, index_name); + out += ", "; + AppendColorModifier(out, stage.color_modifier2, stage.color_source2, index_name); + out += ", "; + AppendColorModifier(out, stage.color_modifier3, stage.color_source3, index_name); + out += ");\n"; + + out += "vec3 color_output_" + index_name + " = "; + AppendColorCombiner(out, stage.color_op, "color_results_" + index_name); + out += ";\n"; + + out += "float alpha_results_" + index_name + "[3] = float[3]("; + AppendAlphaModifier(out, stage.alpha_modifier1, stage.alpha_source1, index_name); + out += ", "; + AppendAlphaModifier(out, stage.alpha_modifier2, stage.alpha_source2, index_name); + out += ", "; + AppendAlphaModifier(out, stage.alpha_modifier3, stage.alpha_source3, index_name); + out += ");\n"; + + out += "float alpha_output_" + index_name + " = "; + AppendAlphaCombiner(out, stage.alpha_op, "alpha_results_" + index_name); + out += ";\n"; + + out += "last_tex_env_out = vec4(" + "clamp(color_output_" + index_name + " * " + std::to_string(stage.GetColorMultiplier()) + ".0, vec3(0.0), vec3(1.0))," + "clamp(alpha_output_" + index_name + " * " + std::to_string(stage.GetAlphaMultiplier()) + ".0, 0.0, 1.0));\n"; + } + + if (config.TevStageUpdatesCombinerBufferColor(index)) + out += "combiner_buffer.rgb = last_tex_env_out.rgb;\n"; + + if (config.TevStageUpdatesCombinerBufferAlpha(index)) + out += "combiner_buffer.a = last_tex_env_out.a;\n"; +} + +std::string GenerateFragmentShader(const PicaShaderConfig& config) { + std::string out = R"( +#version 330 +#extension GL_ARB_explicit_uniform_location : require + +#define NUM_TEV_STAGES 6 + +in vec4 primary_color; +in vec2 texcoord[3]; + +out vec4 color; +)"; + + using Uniform = RasterizerOpenGL::PicaShader::Uniform; + out += "layout(location = " + std::to_string((int)Uniform::AlphaTestRef) + ") uniform int alphatest_ref;\n"; + out += "layout(location = " + std::to_string((int)Uniform::TevConstColors) + ") uniform vec4 const_color[NUM_TEV_STAGES];\n"; + out += "layout(location = " + std::to_string((int)Uniform::Texture0) + ") uniform sampler2D tex[3];\n"; + out += "layout(location = " + std::to_string((int)Uniform::TevCombinerBufferColor) + ") uniform vec4 tev_combiner_buffer_color;\n"; + + out += "void main() {\n"; + out += "vec4 combiner_buffer = tev_combiner_buffer_color;\n"; + out += "vec4 last_tex_env_out = vec4(0.0);\n"; + + // Do not do any sort of processing if it's obvious we're not going to pass the alpha test + if (config.alpha_test_func == Regs::CompareFunc::Never) { + out += "discard; }"; + return out; + } + + for (size_t index = 0; index < config.tev_stages.size(); ++index) + WriteTevStage(out, config, (unsigned)index); + + if (config.alpha_test_func != Regs::CompareFunc::Always) { + out += "if ("; + AppendAlphaTestCondition(out, config.alpha_test_func); + out += ") discard;\n"; + } + + out += "color = last_tex_env_out;\n}"; + + return out; +} + +std::string GenerateVertexShader() { + std::string out = "#version 330\n"; + out += "layout(location = " + std::to_string((int)ATTRIBUTE_POSITION) + ") in vec4 vert_position;\n"; + out += "layout(location = " + std::to_string((int)ATTRIBUTE_COLOR) + ") in vec4 vert_color;\n"; + out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD0) + ") in vec2 vert_texcoord0;\n"; + out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD1) + ") in vec2 vert_texcoord1;\n"; + out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD2) + ") in vec2 vert_texcoord2;\n"; + + out += R"( +out vec4 primary_color; +out vec2 texcoord[3]; + +void main() { + primary_color = vert_color; + texcoord[0] = vert_texcoord0; + texcoord[1] = vert_texcoord1; + texcoord[2] = vert_texcoord2; + gl_Position = vec4(vert_position.x, -vert_position.y, -vert_position.z, vert_position.w); +} +)"; + + return out; +} + +} // namespace GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h new file mode 100644 index 000000000..0ca9d2879 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -0,0 +1,27 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> + +#include "video_core/renderer_opengl/gl_rasterizer.h" + +namespace GLShader { + +/** + * Generates the GLSL vertex shader program source code for the current Pica state + * @returns String of the shader source code + */ +std::string GenerateVertexShader(); + +/** + * Generates the GLSL fragment shader program source code for the current Pica state + * @param config ShaderCacheKey object generated for the current Pica state, used for the shader + * configuration (NOTE: Use state in this struct only, not the Pica registers!) + * @returns String of the shader source code + */ +std::string GenerateFragmentShader(const PicaShaderConfig& config); + +} // namespace GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp index 4cf246c06..e3f7a5868 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.cpp +++ b/src/video_core/renderer_opengl/gl_shader_util.cpp @@ -8,9 +8,9 @@ #include "common/logging/log.h" #include "video_core/renderer_opengl/gl_shader_util.h" -namespace ShaderUtil { +namespace GLShader { -GLuint LoadShaders(const char* vertex_shader, const char* fragment_shader) { +GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader) { // Create the shaders GLuint vertex_shader_id = glCreateShader(GL_VERTEX_SHADER); @@ -65,6 +65,7 @@ GLuint LoadShaders(const char* vertex_shader, const char* fragment_shader) { GLuint program_id = glCreateProgram(); glAttachShader(program_id, vertex_shader_id); glAttachShader(program_id, fragment_shader_id); + glLinkProgram(program_id); // Check the program @@ -87,4 +88,4 @@ GLuint LoadShaders(const char* vertex_shader, const char* fragment_shader) { return program_id; } -} +} // namespace GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h index c9d7cc380..046aae14f 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.h +++ b/src/video_core/renderer_opengl/gl_shader_util.h @@ -6,8 +6,22 @@ #include <glad/glad.h> -namespace ShaderUtil { +namespace GLShader { -GLuint LoadShaders(const char* vertex_file_path, const char* fragment_file_path); +enum Attributes { + ATTRIBUTE_POSITION, + ATTRIBUTE_COLOR, + ATTRIBUTE_TEXCOORD0, + ATTRIBUTE_TEXCOORD1, + ATTRIBUTE_TEXCOORD2, +}; -} +/** + * Utility function to create and compile an OpenGL GLSL shader program (vertex + fragment shader) + * @param vertex_shader String of the GLSL vertex shader program + * @param fragment_shader String of the GLSL fragment shader program + * @returns Handle of the newly created OpenGL shader object + */ +GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader); + +} // namespace diff --git a/src/video_core/renderer_opengl/gl_shaders.h b/src/video_core/renderer_opengl/gl_shaders.h deleted file mode 100644 index a8cb2f595..000000000 --- a/src/video_core/renderer_opengl/gl_shaders.h +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -namespace GLShaders { - -const char g_vertex_shader[] = R"( -#version 150 core - -in vec2 vert_position; -in vec2 vert_tex_coord; -out vec2 frag_tex_coord; - -// This is a truncated 3x3 matrix for 2D transformations: -// The upper-left 2x2 submatrix performs scaling/rotation/mirroring. -// The third column performs translation. -// The third row could be used for projection, which we don't need in 2D. It hence is assumed to -// implicitly be [0, 0, 1] -uniform mat3x2 modelview_matrix; - -void main() { - // Multiply input position by the rotscale part of the matrix and then manually translate by - // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector - // to `vec3(vert_position.xy, 1.0)` - gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0); - frag_tex_coord = vert_tex_coord; -} -)"; - -const char g_fragment_shader[] = R"( -#version 150 core - -in vec2 frag_tex_coord; -out vec4 color; - -uniform sampler2D color_texture; - -void main() { - color = texture(color_texture, frag_tex_coord); -} -)"; - -const char g_vertex_shader_hw[] = R"( -#version 150 core - -#define NUM_VTX_ATTR 7 - -in vec4 vert_position; -in vec4 vert_color; -in vec2 vert_texcoords[3]; - -out vec4 o[NUM_VTX_ATTR]; - -void main() { - o[2] = vert_color; - o[3] = vec4(vert_texcoords[0].xy, vert_texcoords[1].xy); - o[5] = vec4(0.0, 0.0, vert_texcoords[2].xy); - - gl_Position = vec4(vert_position.x, -vert_position.y, -vert_position.z, vert_position.w); -} -)"; - -// TODO: Create a shader constructor and cache that builds this program with minimal conditionals instead of using tev_cfg uniforms -const char g_fragment_shader_hw[] = R"( -#version 150 core - -#define NUM_VTX_ATTR 7 -#define NUM_TEV_STAGES 6 - -#define SOURCE_PRIMARYCOLOR 0x0 -#define SOURCE_PRIMARYFRAGMENTCOLOR 0x1 -#define SOURCE_SECONDARYFRAGMENTCOLOR 0x2 -#define SOURCE_TEXTURE0 0x3 -#define SOURCE_TEXTURE1 0x4 -#define SOURCE_TEXTURE2 0x5 -#define SOURCE_TEXTURE3 0x6 -#define SOURCE_PREVIOUSBUFFER 0xd -#define SOURCE_CONSTANT 0xe -#define SOURCE_PREVIOUS 0xf - -#define COLORMODIFIER_SOURCECOLOR 0x0 -#define COLORMODIFIER_ONEMINUSSOURCECOLOR 0x1 -#define COLORMODIFIER_SOURCEALPHA 0x2 -#define COLORMODIFIER_ONEMINUSSOURCEALPHA 0x3 -#define COLORMODIFIER_SOURCERED 0x4 -#define COLORMODIFIER_ONEMINUSSOURCERED 0x5 -#define COLORMODIFIER_SOURCEGREEN 0x8 -#define COLORMODIFIER_ONEMINUSSOURCEGREEN 0x9 -#define COLORMODIFIER_SOURCEBLUE 0xc -#define COLORMODIFIER_ONEMINUSSOURCEBLUE 0xd - -#define ALPHAMODIFIER_SOURCEALPHA 0x0 -#define ALPHAMODIFIER_ONEMINUSSOURCEALPHA 0x1 -#define ALPHAMODIFIER_SOURCERED 0x2 -#define ALPHAMODIFIER_ONEMINUSSOURCERED 0x3 -#define ALPHAMODIFIER_SOURCEGREEN 0x4 -#define ALPHAMODIFIER_ONEMINUSSOURCEGREEN 0x5 -#define ALPHAMODIFIER_SOURCEBLUE 0x6 -#define ALPHAMODIFIER_ONEMINUSSOURCEBLUE 0x7 - -#define OPERATION_REPLACE 0 -#define OPERATION_MODULATE 1 -#define OPERATION_ADD 2 -#define OPERATION_ADDSIGNED 3 -#define OPERATION_LERP 4 -#define OPERATION_SUBTRACT 5 -#define OPERATION_MULTIPLYTHENADD 8 -#define OPERATION_ADDTHENMULTIPLY 9 - -#define COMPAREFUNC_NEVER 0 -#define COMPAREFUNC_ALWAYS 1 -#define COMPAREFUNC_EQUAL 2 -#define COMPAREFUNC_NOTEQUAL 3 -#define COMPAREFUNC_LESSTHAN 4 -#define COMPAREFUNC_LESSTHANOREQUAL 5 -#define COMPAREFUNC_GREATERTHAN 6 -#define COMPAREFUNC_GREATERTHANOREQUAL 7 - -in vec4 o[NUM_VTX_ATTR]; -out vec4 color; - -uniform bool alphatest_enabled; -uniform int alphatest_func; -uniform float alphatest_ref; - -uniform sampler2D tex[3]; - -uniform vec4 tev_combiner_buffer_color; - -struct TEVConfig -{ - bool enabled; - ivec3 color_sources; - ivec3 alpha_sources; - ivec3 color_modifiers; - ivec3 alpha_modifiers; - ivec2 color_alpha_op; - ivec2 color_alpha_multiplier; - vec4 const_color; - bvec2 updates_combiner_buffer_color_alpha; -}; - -uniform TEVConfig tev_cfgs[NUM_TEV_STAGES]; - -vec4 g_combiner_buffer; -vec4 g_last_tex_env_out; -vec4 g_const_color; - -vec4 GetSource(int source) { - if (source == SOURCE_PRIMARYCOLOR) { - return o[2]; - } else if (source == SOURCE_PRIMARYFRAGMENTCOLOR) { - // HACK: Until we implement fragment lighting, use primary_color - return o[2]; - } else if (source == SOURCE_SECONDARYFRAGMENTCOLOR) { - // HACK: Until we implement fragment lighting, use zero - return vec4(0.0, 0.0, 0.0, 0.0); - } else if (source == SOURCE_TEXTURE0) { - return texture(tex[0], o[3].xy); - } else if (source == SOURCE_TEXTURE1) { - return texture(tex[1], o[3].zw); - } else if (source == SOURCE_TEXTURE2) { - // TODO: Unverified - return texture(tex[2], o[5].zw); - } else if (source == SOURCE_TEXTURE3) { - // TODO: no 4th texture? - } else if (source == SOURCE_PREVIOUSBUFFER) { - return g_combiner_buffer; - } else if (source == SOURCE_CONSTANT) { - return g_const_color; - } else if (source == SOURCE_PREVIOUS) { - return g_last_tex_env_out; - } - - return vec4(0.0); -} - -vec3 GetColorModifier(int factor, vec4 color) { - if (factor == COLORMODIFIER_SOURCECOLOR) { - return color.rgb; - } else if (factor == COLORMODIFIER_ONEMINUSSOURCECOLOR) { - return vec3(1.0) - color.rgb; - } else if (factor == COLORMODIFIER_SOURCEALPHA) { - return color.aaa; - } else if (factor == COLORMODIFIER_ONEMINUSSOURCEALPHA) { - return vec3(1.0) - color.aaa; - } else if (factor == COLORMODIFIER_SOURCERED) { - return color.rrr; - } else if (factor == COLORMODIFIER_ONEMINUSSOURCERED) { - return vec3(1.0) - color.rrr; - } else if (factor == COLORMODIFIER_SOURCEGREEN) { - return color.ggg; - } else if (factor == COLORMODIFIER_ONEMINUSSOURCEGREEN) { - return vec3(1.0) - color.ggg; - } else if (factor == COLORMODIFIER_SOURCEBLUE) { - return color.bbb; - } else if (factor == COLORMODIFIER_ONEMINUSSOURCEBLUE) { - return vec3(1.0) - color.bbb; - } - - return vec3(0.0); -} - -float GetAlphaModifier(int factor, vec4 color) { - if (factor == ALPHAMODIFIER_SOURCEALPHA) { - return color.a; - } else if (factor == ALPHAMODIFIER_ONEMINUSSOURCEALPHA) { - return 1.0 - color.a; - } else if (factor == ALPHAMODIFIER_SOURCERED) { - return color.r; - } else if (factor == ALPHAMODIFIER_ONEMINUSSOURCERED) { - return 1.0 - color.r; - } else if (factor == ALPHAMODIFIER_SOURCEGREEN) { - return color.g; - } else if (factor == ALPHAMODIFIER_ONEMINUSSOURCEGREEN) { - return 1.0 - color.g; - } else if (factor == ALPHAMODIFIER_SOURCEBLUE) { - return color.b; - } else if (factor == ALPHAMODIFIER_ONEMINUSSOURCEBLUE) { - return 1.0 - color.b; - } - - return 0.0; -} - -vec3 ColorCombine(int op, vec3 color[3]) { - if (op == OPERATION_REPLACE) { - return color[0]; - } else if (op == OPERATION_MODULATE) { - return color[0] * color[1]; - } else if (op == OPERATION_ADD) { - return min(color[0] + color[1], 1.0); - } else if (op == OPERATION_ADDSIGNED) { - return clamp(color[0] + color[1] - vec3(0.5), 0.0, 1.0); - } else if (op == OPERATION_LERP) { - return color[0] * color[2] + color[1] * (vec3(1.0) - color[2]); - } else if (op == OPERATION_SUBTRACT) { - return max(color[0] - color[1], 0.0); - } else if (op == OPERATION_MULTIPLYTHENADD) { - return min(color[0] * color[1] + color[2], 1.0); - } else if (op == OPERATION_ADDTHENMULTIPLY) { - return min(color[0] + color[1], 1.0) * color[2]; - } - - return vec3(0.0); -} - -float AlphaCombine(int op, float alpha[3]) { - if (op == OPERATION_REPLACE) { - return alpha[0]; - } else if (op == OPERATION_MODULATE) { - return alpha[0] * alpha[1]; - } else if (op == OPERATION_ADD) { - return min(alpha[0] + alpha[1], 1.0); - } else if (op == OPERATION_ADDSIGNED) { - return clamp(alpha[0] + alpha[1] - 0.5, 0.0, 1.0); - } else if (op == OPERATION_LERP) { - return alpha[0] * alpha[2] + alpha[1] * (1.0 - alpha[2]); - } else if (op == OPERATION_SUBTRACT) { - return max(alpha[0] - alpha[1], 0.0); - } else if (op == OPERATION_MULTIPLYTHENADD) { - return min(alpha[0] * alpha[1] + alpha[2], 1.0); - } else if (op == OPERATION_ADDTHENMULTIPLY) { - return min(alpha[0] + alpha[1], 1.0) * alpha[2]; - } - - return 0.0; -} - -void main(void) { - g_combiner_buffer = tev_combiner_buffer_color; - - for (int tex_env_idx = 0; tex_env_idx < NUM_TEV_STAGES; ++tex_env_idx) { - if (tev_cfgs[tex_env_idx].enabled) { - g_const_color = tev_cfgs[tex_env_idx].const_color; - - vec3 color_results[3] = vec3[3](GetColorModifier(tev_cfgs[tex_env_idx].color_modifiers.x, GetSource(tev_cfgs[tex_env_idx].color_sources.x)), - GetColorModifier(tev_cfgs[tex_env_idx].color_modifiers.y, GetSource(tev_cfgs[tex_env_idx].color_sources.y)), - GetColorModifier(tev_cfgs[tex_env_idx].color_modifiers.z, GetSource(tev_cfgs[tex_env_idx].color_sources.z))); - vec3 color_output = ColorCombine(tev_cfgs[tex_env_idx].color_alpha_op.x, color_results); - - float alpha_results[3] = float[3](GetAlphaModifier(tev_cfgs[tex_env_idx].alpha_modifiers.x, GetSource(tev_cfgs[tex_env_idx].alpha_sources.x)), - GetAlphaModifier(tev_cfgs[tex_env_idx].alpha_modifiers.y, GetSource(tev_cfgs[tex_env_idx].alpha_sources.y)), - GetAlphaModifier(tev_cfgs[tex_env_idx].alpha_modifiers.z, GetSource(tev_cfgs[tex_env_idx].alpha_sources.z))); - float alpha_output = AlphaCombine(tev_cfgs[tex_env_idx].color_alpha_op.y, alpha_results); - - g_last_tex_env_out = vec4(min(color_output * tev_cfgs[tex_env_idx].color_alpha_multiplier.x, 1.0), min(alpha_output * tev_cfgs[tex_env_idx].color_alpha_multiplier.y, 1.0)); - } - - if (tev_cfgs[tex_env_idx].updates_combiner_buffer_color_alpha.x) { - g_combiner_buffer.rgb = g_last_tex_env_out.rgb; - } - - if (tev_cfgs[tex_env_idx].updates_combiner_buffer_color_alpha.y) { - g_combiner_buffer.a = g_last_tex_env_out.a; - } - } - - if (alphatest_enabled) { - if (alphatest_func == COMPAREFUNC_NEVER) { - discard; - } else if (alphatest_func == COMPAREFUNC_ALWAYS) { - - } else if (alphatest_func == COMPAREFUNC_EQUAL) { - if (g_last_tex_env_out.a != alphatest_ref) { - discard; - } - } else if (alphatest_func == COMPAREFUNC_NOTEQUAL) { - if (g_last_tex_env_out.a == alphatest_ref) { - discard; - } - } else if (alphatest_func == COMPAREFUNC_LESSTHAN) { - if (g_last_tex_env_out.a >= alphatest_ref) { - discard; - } - } else if (alphatest_func == COMPAREFUNC_LESSTHANOREQUAL) { - if (g_last_tex_env_out.a > alphatest_ref) { - discard; - } - } else if (alphatest_func == COMPAREFUNC_GREATERTHAN) { - if (g_last_tex_env_out.a <= alphatest_ref) { - discard; - } - } else if (alphatest_func == COMPAREFUNC_GREATERTHANOREQUAL) { - if (g_last_tex_env_out.a < alphatest_ref) { - discard; - } - } - } - - color = g_last_tex_env_out; -} -)"; - -} diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index 6ecbedbb4..668b04259 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -65,6 +65,7 @@ public: GLuint vertex_array; // GL_VERTEX_ARRAY_BINDING GLuint vertex_buffer; // GL_ARRAY_BUFFER_BINDING GLuint shader_program; // GL_CURRENT_PROGRAM + bool shader_dirty; } draw; OpenGLState(); diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 22f261c68..ac0a058db 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -21,9 +21,44 @@ #include "video_core/debug_utils/debug_utils.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_util.h" -#include "video_core/renderer_opengl/gl_shaders.h" #include "video_core/renderer_opengl/renderer_opengl.h" +static const char vertex_shader[] = R"( +#version 150 core + +in vec2 vert_position; +in vec2 vert_tex_coord; +out vec2 frag_tex_coord; + +// This is a truncated 3x3 matrix for 2D transformations: +// The upper-left 2x2 submatrix performs scaling/rotation/mirroring. +// The third column performs translation. +// The third row could be used for projection, which we don't need in 2D. It hence is assumed to +// implicitly be [0, 0, 1] +uniform mat3x2 modelview_matrix; + +void main() { + // Multiply input position by the rotscale part of the matrix and then manually translate by + // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector + // to `vec3(vert_position.xy, 1.0)` + gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0); + frag_tex_coord = vert_tex_coord; +} +)"; + +static const char fragment_shader[] = R"( +#version 150 core + +in vec2 frag_tex_coord; +out vec4 color; + +uniform sampler2D color_texture; + +void main() { + color = texture(color_texture, frag_tex_coord); +} +)"; + /** * Vertex structure that the drawn screen rectangles are composed of. */ @@ -207,7 +242,7 @@ void RendererOpenGL::InitOpenGLObjects() { glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, 0.0f); // Link shaders and get variable locations - program_id = ShaderUtil::LoadShaders(GLShaders::g_vertex_shader, GLShaders::g_fragment_shader); + program_id = GLShader::LoadProgram(vertex_shader, fragment_shader); uniform_modelview_matrix = glGetUniformLocation(program_id, "modelview_matrix"); uniform_color_texture = glGetUniformLocation(program_id, "color_texture"); attrib_position = glGetAttribLocation(program_id, "vert_position"); @@ -314,12 +349,12 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, * Draws a single texture to the emulator window, rotating the texture to correct for the 3DS's LCD rotation. */ void RendererOpenGL::DrawSingleScreenRotated(const TextureInfo& texture, float x, float y, float w, float h) { - std::array<ScreenRectVertex, 4> vertices = { + std::array<ScreenRectVertex, 4> vertices = {{ ScreenRectVertex(x, y, 1.f, 0.f), ScreenRectVertex(x+w, y, 1.f, 1.f), ScreenRectVertex(x, y+h, 0.f, 0.f), ScreenRectVertex(x+w, y+h, 0.f, 1.f), - }; + }}; state.texture_units[0].texture_2d = texture.handle; state.Apply(); diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 5677e538b..b42df7654 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -62,7 +62,6 @@ private: const TextureInfo& texture); EmuWindow* render_window; ///< Handle to render window - u32 last_mode; ///< Last render mode int resolution_width; ///< Current resolution width int resolution_height; ///< Current resolution height |