diff options
70 files changed, 1640 insertions, 960 deletions
| diff --git a/.travis-upload.sh b/.travis-upload.sh index 3a15e8f6a..e508386dd 100755 --- a/.travis-upload.sh +++ b/.travis-upload.sh @@ -24,5 +24,5 @@ if [ "$TRAVIS_BRANCH" = "master" ]; then      ARCHIVE_NAME="${REV_NAME}.tar.xz"      tar -cJvf "$ARCHIVE_NAME" "$REV_NAME" -    lftp -c "open -u citra-builds,$BUILD_PASSWORD sftp://builds.citra-emu.org; put -O '$UPLOAD_DIR' '$ARCHIVE_NAME'" +    lftp -c "open -u citra-builds,$BUILD_PASSWORD sftp://builds.citra-emu.org; set sftp:auto-confirm yes; put -O '$UPLOAD_DIR' '$ARCHIVE_NAME'"  fi diff --git a/appveyor.yml b/appveyor.yml index 6e073ece7..d05cc2213 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,6 @@  clone_depth: 5  environment: -  QTDIR: C:\Qt\5.4\msvc2013_64_opengl    BUILD_PASSWORD:      secure: EXGNlWKJsCtbeImEJ5EP9qrxZ+EqUFfNy+CP61nDOMA= @@ -18,7 +17,7 @@ install:  before_build:    - mkdir build    - cd build -  - cmake -G "Visual Studio 12 Win64" -DCITRA_USE_BUNDLED_GLFW=1 -DQt5_DIR=%QTDIR%/lib/cmake/Qt5 .. +  - cmake -G "Visual Studio 14 Win64" -DCITRA_USE_BUNDLED_GLFW=1 -DCITRA_USE_BUNDLED_QT=1 ..    - cd ..  after_build: diff --git a/dist/citra.icns b/dist/citra.icnsBinary files differ new file mode 100644 index 000000000..9d3dcca83 --- /dev/null +++ b/dist/citra.icns 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 | 
