Merge master
This commit is contained in:
commit
9854cf63dc
@ -7,13 +7,6 @@ REV_NAME="citra-${OS}-${TARGET}-${GITDATE}-${GITREV}"
|
||||
# Find out what release we are building
|
||||
if [[ "$GITHUB_REF_NAME" =~ ^canary- ]] || [[ "$GITHUB_REF_NAME" =~ ^nightly- ]]; then
|
||||
RELEASE_NAME=$(echo $GITHUB_REF_NAME | cut -d- -f1)
|
||||
# For compatibility with existing installs, use mingw/osx in the archive and target names.
|
||||
if [ "$TARGET" = "msys2" ]; then
|
||||
REV_NAME="citra-${OS}-mingw-${GITDATE}-${GITREV}"
|
||||
RELEASE_NAME="${RELEASE_NAME}-mingw"
|
||||
elif [ "$OS" = "macos" ]; then
|
||||
REV_NAME="citra-osx-${TARGET}-${GITDATE}-${GITREV}"
|
||||
fi
|
||||
else
|
||||
RELEASE_NAME=head
|
||||
fi
|
||||
|
21
.github/workflows/ci.yml
vendored
21
.github/workflows/ci.yml
vendored
@ -161,9 +161,16 @@ jobs:
|
||||
- name: Set up MSVC
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
if: ${{ matrix.target == 'msvc' }}
|
||||
- name: Install MSVC extra tools
|
||||
- name: Install extra tools (MSVC)
|
||||
run: choco install ccache ninja wget
|
||||
if: ${{ matrix.target == 'msvc' }}
|
||||
- name: Set up Vulkan SDK (MSVC)
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
if: ${{ matrix.target == 'msvc' }}
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-components: Glslang
|
||||
vulkan-use-cache: true
|
||||
- name: Set up MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
if: ${{ matrix.target == 'msys2' }}
|
||||
@ -172,16 +179,10 @@ jobs:
|
||||
update: true
|
||||
install: git make p7zip
|
||||
pacboy: >-
|
||||
toolchain:p ccache:p cmake:p ninja:p
|
||||
toolchain:p ccache:p cmake:p ninja:p glslang:p
|
||||
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-components: Glslang
|
||||
vulkan-use-cache: true
|
||||
- name: Test glslangValidator
|
||||
run: glslangValidator --version
|
||||
- name: Test glslang
|
||||
run: glslang --version || glslangValidator --version
|
||||
- name: Disable line ending translation
|
||||
run: git config --global core.autocrlf input
|
||||
- name: Build
|
||||
|
@ -57,6 +57,7 @@ CMAKE_DEPENDENT_OPTION(ENABLE_TESTS "Enable generating tests executable" ON "NOT
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_DEDICATED_ROOM "Enable generating dedicated room executable" ON "NOT ANDROID AND NOT IOS" OFF)
|
||||
|
||||
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||
option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
|
||||
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
|
||||
@ -68,6 +69,8 @@ option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_MF "Use Media Foundation decoder (preferred over FFmpeg)" ON "WIN32" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_AUDIOTOOLBOX "Use AudioToolbox decoder (preferred over FFmpeg)" ON "APPLE" OFF)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(CITRA_ENABLE_BUNDLE_TARGET "Enable the distribution bundling target." ON "NOT ANDROID AND NOT IOS" OFF)
|
||||
|
||||
# Compile options
|
||||
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
|
||||
option(ENABLE_LTO "Enable link time optimization" OFF)
|
||||
@ -219,7 +222,7 @@ find_package(Threads REQUIRED)
|
||||
|
||||
if (ENABLE_QT)
|
||||
if (NOT USE_SYSTEM_QT)
|
||||
download_qt(6.5.0)
|
||||
download_qt(6.5.1)
|
||||
endif()
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
||||
@ -341,13 +344,6 @@ function(get_timestamp _var)
|
||||
set(${_var} "${timestamp}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Prevent boost from linking against libs when building
|
||||
add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY
|
||||
-DBOOST_SYSTEM_NO_LIB
|
||||
-DBOOST_DATE_TIME_NO_LIB
|
||||
-DBOOST_REGEX_NO_LIB
|
||||
)
|
||||
|
||||
# generate git/build information
|
||||
include(GetGitRevisionDescription)
|
||||
get_git_head_revision(GIT_REF_SPEC GIT_REV)
|
||||
@ -355,17 +351,23 @@ git_describe(GIT_DESC --always --long --dirty)
|
||||
git_branch_name(GIT_BRANCH)
|
||||
get_timestamp(BUILD_DATE)
|
||||
|
||||
if (NOT USE_SYSTEM_BOOST)
|
||||
add_definitions( -DBOOST_ALL_NO_LIB )
|
||||
# Boost
|
||||
# Prevent boost from linking against libs when building
|
||||
add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY
|
||||
-DBOOST_SYSTEM_NO_LIB
|
||||
-DBOOST_DATE_TIME_NO_LIB
|
||||
-DBOOST_REGEX_NO_LIB
|
||||
)
|
||||
if (USE_SYSTEM_BOOST)
|
||||
find_package(Boost 1.70.0 COMPONENTS container locale serialization iostreams REQUIRED)
|
||||
endif()
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(externals)
|
||||
|
||||
# Boost
|
||||
if (USE_SYSTEM_BOOST)
|
||||
find_package(Boost 1.70.0 COMPONENTS serialization iostreams REQUIRED)
|
||||
else()
|
||||
# Boost (bundled)
|
||||
if (NOT USE_SYSTEM_BOOST)
|
||||
add_definitions( -DBOOST_ALL_NO_LIB )
|
||||
add_library(Boost::boost ALIAS boost)
|
||||
add_library(Boost::serialization ALIAS boost_serialization)
|
||||
add_library(Boost::iostreams ALIAS boost_iostreams)
|
||||
@ -397,7 +399,7 @@ else()
|
||||
endif()
|
||||
|
||||
# Create target for outputting distributable bundles.
|
||||
if (NOT ANDROID AND NOT IOS)
|
||||
if (CITRA_ENABLE_BUNDLE_TARGET)
|
||||
include(BundleTarget)
|
||||
if (ENABLE_SDL2_FRONTEND)
|
||||
bundle_target(citra)
|
||||
|
@ -189,6 +189,12 @@ else()
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/dist/")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dist/icon.png" "${CMAKE_BINARY_DIR}/bundle/dist/citra.png")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/license.txt" "${CMAKE_BINARY_DIR}/bundle/")
|
||||
|
@ -54,7 +54,8 @@ function(download_qt target)
|
||||
set(host_flag "--autodesktop")
|
||||
set(host_prefix "${base_path}/${target}/${host_arch_path}")
|
||||
endif()
|
||||
set(install_args install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} ${host_flag} -m qtmultimedia)
|
||||
set(install_args install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} ${host_flag}
|
||||
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase)
|
||||
endif()
|
||||
|
||||
if (NOT EXISTS "${prefix}")
|
||||
|
@ -12,8 +12,16 @@ set(HASH_FILES
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_gen.h"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.h"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_gen.cpp"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_gen.h"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_gen_spv.cpp"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_gen_spv.h"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.h"
|
||||
"${VIDEO_CORE}/shader/shader.cpp"
|
||||
"${VIDEO_CORE}/shader/shader.h"
|
||||
"${VIDEO_CORE}/shader/shader_uniforms.cpp"
|
||||
"${VIDEO_CORE}/shader/shader_uniforms.h"
|
||||
"${VIDEO_CORE}/pica.cpp"
|
||||
"${VIDEO_CORE}/pica.h"
|
||||
"${VIDEO_CORE}/regs_framebuffer.h"
|
||||
|
5
dist/qt_themes/qdarkstyle/style.qss
vendored
5
dist/qt_themes/qdarkstyle/style.qss
vendored
@ -298,6 +298,11 @@ QAbstractItemView:read-only {
|
||||
alternate-background-color: #232629;
|
||||
}
|
||||
|
||||
/* Workaround for https://bugreports.qt.io/browse/QTBUG-115529 */
|
||||
QAbstractItemView:item {
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
QWidget:focus {
|
||||
border: 1px solid #3daee9;
|
||||
}
|
||||
|
@ -481,6 +481,11 @@ QAbstractItemView QLineEdit {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
/* Workaround for https://bugreports.qt.io/browse/QTBUG-115529 */
|
||||
QAbstractItemView:item {
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
/* QAbstractScrollArea ----------------------------------------------------
|
||||
|
||||
https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qabstractscrollarea
|
||||
|
17
externals/CMakeLists.txt
vendored
17
externals/CMakeLists.txt
vendored
@ -12,6 +12,8 @@ include(DownloadExternals)
|
||||
include(ExternalProject)
|
||||
|
||||
# Boost
|
||||
if (NOT USE_SYSTEM_BOOST)
|
||||
message(STATUS "Including vendored Boost library")
|
||||
set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/externals/boost" CACHE STRING "")
|
||||
set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/externals/boost" CACHE STRING "")
|
||||
set(Boost_NO_SYSTEM_PATHS ON CACHE BOOL "")
|
||||
@ -31,8 +33,8 @@ add_library(
|
||||
${CMAKE_SOURCE_DIR}/externals/boost/libs/iostreams/src/mapped_file.cpp
|
||||
)
|
||||
target_link_libraries(boost_iostreams PUBLIC boost)
|
||||
|
||||
# Add additional boost libs here; remember to ALIAS them in the root CMakeLists!
|
||||
endif()
|
||||
|
||||
# Catch2
|
||||
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
|
||||
@ -95,6 +97,7 @@ set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "")
|
||||
set(ENABLE_SPVREMAPPER OFF CACHE BOOL "")
|
||||
set(ENABLE_CTEST OFF CACHE BOOL "")
|
||||
set(ENABLE_HLSL OFF CACHE BOOL "")
|
||||
set(BUILD_EXTERNAL OFF CACHE BOOL "")
|
||||
add_subdirectory(glslang)
|
||||
|
||||
# inih
|
||||
@ -171,7 +174,7 @@ endif()
|
||||
add_library(json-headers INTERFACE)
|
||||
target_include_directories(json-headers INTERFACE ./json)
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
# OpenSSL
|
||||
if (USE_SYSTEM_OPENSSL)
|
||||
find_package(OpenSSL 1.1)
|
||||
if (OPENSSL_FOUND)
|
||||
@ -191,17 +194,19 @@ if (ENABLE_WEB_SERVICE)
|
||||
DEFINITION OPENSSL_LIBS)
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
add_subdirectory(android-ifaddrs)
|
||||
endif()
|
||||
|
||||
# httplib
|
||||
add_library(httplib INTERFACE)
|
||||
target_include_directories(httplib INTERFACE ./httplib)
|
||||
target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT -DCPPHTTPLIB_NO_DEFAULT_USER_AGENT)
|
||||
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
|
||||
|
||||
if(ANDROID)
|
||||
add_subdirectory(android-ifaddrs)
|
||||
target_link_libraries(httplib INTERFACE ifaddrs)
|
||||
endif()
|
||||
|
||||
# cpp-jwt
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
add_library(cpp-jwt INTERFACE)
|
||||
target_include_directories(cpp-jwt INTERFACE ./cpp-jwt/include)
|
||||
target_compile_definitions(cpp-jwt INTERFACE CPP_JWT_USE_VENDORED_NLOHMANN_JSON)
|
||||
|
2
externals/boost
vendored
2
externals/boost
vendored
@ -1 +1 @@
|
||||
Subproject commit 700ae2eff3134792f09cea2b051666688b1d5b97
|
||||
Subproject commit 3c27c785ad0f8a742af02e620dc225673f3a12d8
|
@ -47,7 +47,7 @@ add_library(audio_core STATIC
|
||||
|
||||
create_target_directory_groups(audio_core)
|
||||
|
||||
target_link_libraries(audio_core PUBLIC citra_common)
|
||||
target_link_libraries(audio_core PUBLIC citra_common citra_core)
|
||||
target_link_libraries(audio_core PRIVATE SoundTouch teakra)
|
||||
set_target_properties(audio_core PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO})
|
||||
add_definitions(-DSOUNDTOUCH_INTEGER_SAMPLES)
|
||||
|
@ -342,7 +342,7 @@ if (USE_DISCORD_PRESENCE)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
target_compile_definitions(citra-qt PRIVATE -DENABLE_WEB_SERVICE)
|
||||
target_link_libraries(citra-qt PRIVATE web_service)
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
|
@ -13,6 +13,7 @@ ConfigureGraphics::ConfigureGraphics(bool is_powered_on, QWidget* parent)
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->toggle_vsync_new->setEnabled(!is_powered_on);
|
||||
ui->graphics_api_combo->setEnabled(!is_powered_on);
|
||||
// Set the index to -1 to ensure the below lambda is called with setCurrentIndex
|
||||
ui->graphics_api_combo->setCurrentIndex(-1);
|
||||
|
||||
@ -28,9 +29,10 @@ ConfigureGraphics::ConfigureGraphics(bool is_powered_on, QWidget* parent)
|
||||
});
|
||||
|
||||
connect(ui->toggle_hw_shader, &QCheckBox::toggled, this, [this] {
|
||||
const bool enabled = ui->toggle_hw_shader->isEnabled();
|
||||
const bool checked = ui->toggle_hw_shader->isChecked();
|
||||
ui->hw_shader_group->setEnabled(checked);
|
||||
ui->toggle_disk_shader_cache->setEnabled(checked);
|
||||
ui->hw_shader_group->setEnabled(checked && enabled);
|
||||
ui->toggle_disk_shader_cache->setEnabled(checked && enabled);
|
||||
});
|
||||
|
||||
SetupPerGameUI();
|
||||
|
@ -13,7 +13,7 @@
|
||||
<property name="windowTitle">
|
||||
<string>ConfigureInput</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<layout class="QVBoxLayout" name="rootLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
@ -62,6 +62,13 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaContents">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_7">
|
||||
<item row="0" column="0">
|
||||
@ -712,6 +719,10 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
|
@ -551,7 +551,6 @@ void ConfigureSystem::SetupPerGameUI() {
|
||||
}
|
||||
|
||||
void ConfigureSystem::DownloadFromNUS() {
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
ui->button_start_download->setEnabled(false);
|
||||
|
||||
const auto mode =
|
||||
@ -590,5 +589,4 @@ void ConfigureSystem::DownloadFromNUS() {
|
||||
}
|
||||
|
||||
ui->button_start_download->setEnabled(true);
|
||||
#endif
|
||||
}
|
||||
|
@ -64,15 +64,22 @@ void GPUCommandStreamItemModel::OnGXCommandFinishedInternal(int total_command_co
|
||||
}
|
||||
|
||||
GPUCommandStreamWidget::GPUCommandStreamWidget(QWidget* parent)
|
||||
: QDockWidget(tr("Graphics Debugger"), parent) {
|
||||
: QDockWidget(tr("Graphics Debugger"), parent), model(this) {
|
||||
setObjectName(QStringLiteral("GraphicsDebugger"));
|
||||
|
||||
GPUCommandStreamItemModel* command_model = new GPUCommandStreamItemModel(this);
|
||||
g_debugger.RegisterObserver(command_model);
|
||||
|
||||
QListView* command_list = new QListView;
|
||||
command_list->setModel(command_model);
|
||||
auto* command_list = new QListView;
|
||||
command_list->setModel(&model);
|
||||
command_list->setFont(GetMonospaceFont());
|
||||
|
||||
setWidget(command_list);
|
||||
}
|
||||
|
||||
void GPUCommandStreamWidget::showEvent(QShowEvent* event) {
|
||||
g_debugger.RegisterObserver(&model);
|
||||
QDockWidget::showEvent(event);
|
||||
}
|
||||
|
||||
void GPUCommandStreamWidget::hideEvent(QHideEvent* event) {
|
||||
g_debugger.UnregisterObserver(&model);
|
||||
QDockWidget::hideEvent(event);
|
||||
}
|
||||
|
@ -37,5 +37,10 @@ class GPUCommandStreamWidget : public QDockWidget {
|
||||
public:
|
||||
GPUCommandStreamWidget(QWidget* parent = nullptr);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event) override;
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
|
||||
private:
|
||||
GPUCommandStreamItemModel model;
|
||||
};
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "citra_qt/debugger/graphics/graphics_cmdlists.h"
|
||||
#include "citra_qt/util/util.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "core/core.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/pica_state.h"
|
||||
@ -166,7 +167,7 @@ void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) {
|
||||
const auto format = texture.format;
|
||||
|
||||
const auto info = Pica::Texture::TextureInfo::FromPicaRegister(config, format);
|
||||
const u8* src = memory.GetPhysicalPointer(config.GetPhysicalAddress());
|
||||
const u8* src = system.Memory().GetPhysicalPointer(config.GetPhysicalAddress());
|
||||
new_info_widget = new TextureInfoWidget(src, info);
|
||||
}
|
||||
if (command_info_widget) {
|
||||
@ -180,8 +181,8 @@ void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) {
|
||||
}
|
||||
#undef COMMAND_IN_RANGE
|
||||
|
||||
GPUCommandListWidget::GPUCommandListWidget(Memory::MemorySystem& memory_, QWidget* parent)
|
||||
: QDockWidget(tr("Pica Command List"), parent), memory{memory_} {
|
||||
GPUCommandListWidget::GPUCommandListWidget(Core::System& system_, QWidget* parent)
|
||||
: QDockWidget(tr("Pica Command List"), parent), system{system_} {
|
||||
setObjectName(QStringLiteral("Pica Command List"));
|
||||
GPUCommandListModel* model = new GPUCommandListModel(this);
|
||||
|
||||
|
@ -11,8 +11,8 @@
|
||||
class QPushButton;
|
||||
class QTreeView;
|
||||
|
||||
namespace Memory {
|
||||
class MemorySystem;
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
class GPUCommandListModel : public QAbstractListModel {
|
||||
@ -42,7 +42,7 @@ class GPUCommandListWidget : public QDockWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GPUCommandListWidget(Memory::MemorySystem& memory, QWidget* parent = nullptr);
|
||||
explicit GPUCommandListWidget(Core::System& system, QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void OnToggleTracing();
|
||||
@ -57,7 +57,7 @@ signals:
|
||||
|
||||
private:
|
||||
std::unique_ptr<Pica::DebugUtils::PicaTrace> pica_trace;
|
||||
Memory::MemorySystem& memory;
|
||||
Core::System& system;
|
||||
QTreeView* list_widget;
|
||||
QWidget* command_info_widget;
|
||||
QPushButton* toggle_tracing;
|
||||
|
@ -448,7 +448,7 @@ void GMainWindow::InitializeDebugWidgets() {
|
||||
graphicsWidget->hide();
|
||||
debug_menu->addAction(graphicsWidget->toggleViewAction());
|
||||
|
||||
graphicsCommandsWidget = new GPUCommandListWidget(system.Memory(), this);
|
||||
graphicsCommandsWidget = new GPUCommandListWidget(system, this);
|
||||
addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget);
|
||||
graphicsCommandsWidget->hide();
|
||||
debug_menu->addAction(graphicsCommandsWidget->toggleViewAction());
|
||||
@ -1071,8 +1071,10 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
ShutdownGame();
|
||||
}
|
||||
|
||||
render_window->InitRenderTarget();
|
||||
secondary_window->InitRenderTarget();
|
||||
if (!render_window->InitRenderTarget() || !secondary_window->InitRenderTarget()) {
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize render targets!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto scope = render_window->Acquire();
|
||||
|
||||
@ -1420,10 +1422,17 @@ void GMainWindow::UpdateSaveStates() {
|
||||
actions_save_state[i]->setText(tr("Slot %1").arg(i + 1));
|
||||
}
|
||||
for (const auto& savestate : savestates) {
|
||||
const auto text = tr("Slot %1 - %2")
|
||||
const bool display_name =
|
||||
savestate.status == Core::SaveStateInfo::ValidationStatus::RevisionDismatch &&
|
||||
!savestate.build_name.empty();
|
||||
const auto text =
|
||||
tr("Slot %1 - %2 %3")
|
||||
.arg(savestate.slot)
|
||||
.arg(QDateTime::fromSecsSinceEpoch(savestate.time)
|
||||
.toString(QStringLiteral("yyyy-MM-dd hh:mm:ss")));
|
||||
.toString(QStringLiteral("yyyy-MM-dd hh:mm:ss")))
|
||||
.arg(display_name ? QString::fromStdString(savestate.build_name) : QLatin1String())
|
||||
.trimmed();
|
||||
|
||||
actions_load_state[savestate.slot - 1]->setEnabled(true);
|
||||
actions_load_state[savestate.slot - 1]->setText(text);
|
||||
actions_save_state[savestate.slot - 1]->setText(text);
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -324,31 +325,32 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
|
||||
return AndroidStorage::CopyFile(srcFilename, std::string(GetParentPath(destFilename)),
|
||||
std::string(GetFilename(destFilename)));
|
||||
#else
|
||||
using CFilePointer = std::unique_ptr<FILE, decltype(&std::fclose)>;
|
||||
|
||||
// Open input file
|
||||
CFilePointer input{fopen(srcFilename.c_str(), "rb"), std::fclose};
|
||||
FILE* input = fopen(srcFilename.c_str(), "rb");
|
||||
if (!input) {
|
||||
LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename,
|
||||
destFilename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
SCOPE_EXIT({ fclose(input); });
|
||||
|
||||
// open output file
|
||||
CFilePointer output{fopen(destFilename.c_str(), "wb"), std::fclose};
|
||||
FILE* output = fopen(destFilename.c_str(), "wb");
|
||||
if (!output) {
|
||||
LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename,
|
||||
destFilename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
SCOPE_EXIT({ fclose(output); });
|
||||
|
||||
// copy loop
|
||||
std::array<char, 1024> buffer;
|
||||
while (!feof(input.get())) {
|
||||
while (!feof(input)) {
|
||||
// read input
|
||||
std::size_t rnum = fread(buffer.data(), sizeof(char), buffer.size(), input.get());
|
||||
std::size_t rnum = fread(buffer.data(), sizeof(char), buffer.size(), input);
|
||||
if (rnum != buffer.size()) {
|
||||
if (ferror(input.get()) != 0) {
|
||||
if (ferror(input) != 0) {
|
||||
LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}",
|
||||
srcFilename, destFilename, GetLastErrorMsg());
|
||||
return false;
|
||||
@ -356,7 +358,7 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
|
||||
}
|
||||
|
||||
// write output
|
||||
std::size_t wnum = fwrite(buffer.data(), sizeof(char), rnum, output.get());
|
||||
std::size_t wnum = fwrite(buffer.data(), sizeof(char), rnum, output);
|
||||
if (wnum != rnum) {
|
||||
LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename,
|
||||
destFilename, GetLastErrorMsg());
|
||||
|
@ -458,19 +458,14 @@ add_library(citra_core STATIC
|
||||
mmio.h
|
||||
movie.cpp
|
||||
movie.h
|
||||
nus_download.cpp
|
||||
nus_download.h
|
||||
perf_stats.cpp
|
||||
perf_stats.h
|
||||
precompiled_headers.h
|
||||
rpc/packet.cpp
|
||||
rpc/packet.h
|
||||
rpc/rpc_server.cpp
|
||||
rpc/rpc_server.h
|
||||
rpc/server.cpp
|
||||
rpc/server.h
|
||||
rpc/udp_server.cpp
|
||||
rpc/udp_server.h
|
||||
savestate.cpp
|
||||
savestate.h
|
||||
savestate_data.h
|
||||
system_titles.cpp
|
||||
system_titles.h
|
||||
telemetry_session.cpp
|
||||
@ -483,16 +478,26 @@ add_library(citra_core STATIC
|
||||
create_target_directory_groups(citra_core)
|
||||
|
||||
target_link_libraries(citra_core PUBLIC citra_common PRIVATE audio_core network video_core)
|
||||
target_link_libraries(citra_core PRIVATE Boost::boost Boost::serialization Boost::iostreams)
|
||||
target_link_libraries(citra_core PRIVATE Boost::boost Boost::serialization Boost::iostreams httplib)
|
||||
target_link_libraries(citra_core PUBLIC dds-ktx PRIVATE cryptopp fmt::fmt lodepng open_source_archives)
|
||||
set_target_properties(citra_core PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO})
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
target_compile_definitions(citra_core PRIVATE -DENABLE_WEB_SERVICE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
target_link_libraries(citra_core PRIVATE web_service ${OPENSSL_LIBS} httplib)
|
||||
if (ANDROID)
|
||||
target_link_libraries(citra_core PRIVATE ifaddrs)
|
||||
target_link_libraries(citra_core PRIVATE web_service)
|
||||
endif()
|
||||
|
||||
if (ENABLE_SCRIPTING)
|
||||
target_compile_definitions(citra_core PUBLIC -DENABLE_SCRIPTING)
|
||||
target_sources(citra_core PRIVATE
|
||||
rpc/packet.cpp
|
||||
rpc/packet.h
|
||||
rpc/rpc_server.cpp
|
||||
rpc/rpc_server.h
|
||||
rpc/server.cpp
|
||||
rpc/server.h
|
||||
rpc/udp_server.cpp
|
||||
rpc/udp_server.h
|
||||
)
|
||||
endif()
|
||||
|
||||
if ("x86_64" IN_LIST ARCHITECTURE OR "arm64" IN_LIST ARCHITECTURE)
|
||||
|
@ -45,7 +45,9 @@
|
||||
#include "core/hw/lcd.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/movie.h"
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
#include "core/rpc/server.h"
|
||||
#endif
|
||||
#include "core/telemetry_session.h"
|
||||
#include "network/network.h"
|
||||
#include "video_core/custom_textures/custom_tex_manager.h"
|
||||
@ -418,7 +420,9 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
|
||||
|
||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
rpc_server = std::make_unique<RPC::Server>(*this);
|
||||
#endif
|
||||
|
||||
service_manager = std::make_unique<Service::SM::ServiceManager>(*this);
|
||||
archive_manager = std::make_unique<Service::FS::ArchiveManager>(*this);
|
||||
@ -555,7 +559,9 @@ void System::Shutdown(bool is_deserializing) {
|
||||
}
|
||||
custom_tex_manager.reset();
|
||||
telemetry_session.reset();
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
rpc_server.reset();
|
||||
#endif
|
||||
archive_manager.reset();
|
||||
service_manager.reset();
|
||||
dsp_core.reset();
|
||||
|
@ -405,8 +405,10 @@ private:
|
||||
/// Image interface
|
||||
std::shared_ptr<Frontend::ImageInterface> registered_image_interface;
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
/// RPC Server for scripting support
|
||||
std::unique_ptr<RPC::Server> rpc_server;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<Service::FS::ArchiveManager> archive_manager;
|
||||
|
||||
|
@ -151,6 +151,17 @@ void Module::Interface::RegisterDisconnectEvent(Kernel::HLERequestContext& ctx)
|
||||
LOG_WARNING(Service_AC, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void Module::Interface::GetConnectingProxyEnable(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
constexpr bool proxy_enabled = false;
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(proxy_enabled);
|
||||
|
||||
LOG_WARNING(Service_AC, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void Module::Interface::IsConnected(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
u32 unk = rp.Pop<u32>();
|
||||
|
@ -120,6 +120,14 @@ public:
|
||||
*/
|
||||
void RegisterDisconnectEvent(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* AC::GetConnectingProxyEnable service function
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 2 : bool, is proxy enabled
|
||||
*/
|
||||
void GetConnectingProxyEnable(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* AC::IsConnected service function
|
||||
* Outputs:
|
||||
|
@ -27,6 +27,7 @@ AC_I::AC_I(std::shared_ptr<Module> ac) : Module::Interface(std::move(ac), "ac:i"
|
||||
{0x0027, &AC_I::GetInfraPriority, "GetInfraPriority"},
|
||||
{0x002D, &AC_I::SetRequestEulaVersion, "SetRequestEulaVersion"},
|
||||
{0x0030, &AC_I::RegisterDisconnectEvent, "RegisterDisconnectEvent"},
|
||||
{0x0036, &AC_I::GetConnectingProxyEnable, "GetConnectingProxyEnable"},
|
||||
{0x003C, nullptr, "GetAPSSIDList"},
|
||||
{0x003E, &AC_I::IsConnected, "IsConnected"},
|
||||
{0x0040, &AC_I::SetClientVersion, "SetClientVersion"},
|
||||
|
@ -27,6 +27,7 @@ AC_U::AC_U(std::shared_ptr<Module> ac) : Module::Interface(std::move(ac), "ac:u"
|
||||
{0x0027, &AC_U::GetInfraPriority, "GetInfraPriority"},
|
||||
{0x002D, &AC_U::SetRequestEulaVersion, "SetRequestEulaVersion"},
|
||||
{0x0030, &AC_U::RegisterDisconnectEvent, "RegisterDisconnectEvent"},
|
||||
{0x0036, &AC_U::GetConnectingProxyEnable, "GetConnectingProxyEnable"},
|
||||
{0x003C, nullptr, "GetAPSSIDList"},
|
||||
{0x003E, &AC_U::IsConnected, "IsConnected"},
|
||||
{0x0040, &AC_U::SetClientVersion, "SetClientVersion"},
|
||||
|
@ -31,9 +31,7 @@
|
||||
#include "core/hle/service/fs/fs_user.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/loader/smdh.h"
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
#include "web_service/nus_download.h"
|
||||
#endif
|
||||
#include "core/nus_download.h"
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
@ -463,7 +461,6 @@ InstallStatus InstallCIA(const std::string& path,
|
||||
}
|
||||
|
||||
InstallStatus InstallFromNus(u64 title_id, int version) {
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
LOG_DEBUG(Service_AM, "Downloading {:X}", title_id);
|
||||
|
||||
CIAFile install_file{GetTitleMediaType(title_id)};
|
||||
@ -472,7 +469,7 @@ InstallStatus InstallFromNus(u64 title_id, int version) {
|
||||
if (version != -1) {
|
||||
path += fmt::format(".{}", version);
|
||||
}
|
||||
auto tmd_response = WebService::NUS::Download(path);
|
||||
auto tmd_response = Core::NUS::Download(path);
|
||||
if (!tmd_response) {
|
||||
LOG_ERROR(Service_AM, "Failed to download tmd for {:016X}", title_id);
|
||||
return InstallStatus::ErrorFileNotFound;
|
||||
@ -481,7 +478,7 @@ InstallStatus InstallFromNus(u64 title_id, int version) {
|
||||
tmd.Load(*tmd_response);
|
||||
|
||||
path = fmt::format("/ccs/download/{:016X}/cetk", title_id);
|
||||
auto cetk_response = WebService::NUS::Download(path);
|
||||
auto cetk_response = Core::NUS::Download(path);
|
||||
if (!cetk_response) {
|
||||
LOG_ERROR(Service_AM, "Failed to download cetk for {:016X}", title_id);
|
||||
return InstallStatus::ErrorFileNotFound;
|
||||
@ -492,7 +489,7 @@ InstallStatus InstallFromNus(u64 title_id, int version) {
|
||||
for (std::size_t i = 0; i < content_count; ++i) {
|
||||
const std::string filename = fmt::format("{:08x}", tmd.GetContentIDByIndex(i));
|
||||
path = fmt::format("/ccs/download/{:016X}/{}", title_id, filename);
|
||||
const auto temp_response = WebService::NUS::Download(path);
|
||||
const auto temp_response = Core::NUS::Download(path);
|
||||
if (!temp_response) {
|
||||
LOG_ERROR(Service_AM, "Failed to download content for {:016X}", title_id);
|
||||
return InstallStatus::ErrorFileNotFound;
|
||||
@ -550,9 +547,6 @@ InstallStatus InstallFromNus(u64 title_id, int version) {
|
||||
return result;
|
||||
}
|
||||
return InstallStatus::Success;
|
||||
#else
|
||||
return InstallStatus::ErrorFileNotFound;
|
||||
#endif
|
||||
}
|
||||
|
||||
u64 GetTitleUpdateId(u64 title_id) {
|
||||
|
@ -620,9 +620,9 @@ void Module::Interface::SetData(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
void Module::Interface::ReadData(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
const u32 dest_buffer_size = rp.Pop<u32>();
|
||||
const CecSystemInfoType info_type = rp.PopEnum<CecSystemInfoType>();
|
||||
const u32 param_buffer_size = rp.Pop<u32>();
|
||||
const auto dest_buffer_size = rp.Pop<u32>();
|
||||
const auto info_type = rp.PopEnum<CecSystemInfoType>();
|
||||
const auto param_buffer_size = rp.Pop<u32>();
|
||||
auto& param_buffer = rp.PopMappedBuffer();
|
||||
auto& dest_buffer = rp.PopMappedBuffer();
|
||||
|
||||
@ -631,22 +631,23 @@ void Module::Interface::ReadData(Kernel::HLERequestContext& ctx) {
|
||||
std::vector<u8> buffer;
|
||||
switch (info_type) {
|
||||
case CecSystemInfoType::EulaVersion: {
|
||||
auto cfg = Service::CFG::GetModule(cecd->system);
|
||||
Service::CFG::EULAVersion version = cfg->GetEULAVersion();
|
||||
dest_buffer.Write(&version, 0, sizeof(version));
|
||||
const auto cfg = Service::CFG::GetModule(cecd->system);
|
||||
const auto version = cfg->GetEULAVersion();
|
||||
buffer = {version.minor, version.major};
|
||||
break;
|
||||
}
|
||||
case CecSystemInfoType::Eula:
|
||||
buffer = {0x01}; // Eula agreed
|
||||
dest_buffer.Write(buffer.data(), 0, buffer.size());
|
||||
buffer = {true}; // Eula agreed
|
||||
break;
|
||||
case CecSystemInfoType::ParentControl:
|
||||
buffer = {0x00}; // No parent control
|
||||
dest_buffer.Write(buffer.data(), 0, buffer.size());
|
||||
buffer = {false}; // No parent control
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Service_CECD, "Unknown system info type={:#x}", info_type);
|
||||
buffer = {};
|
||||
}
|
||||
dest_buffer.Write(buffer.data(), 0,
|
||||
std::min(static_cast<size_t>(dest_buffer_size), buffer.size()));
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushMappedBuffer(param_buffer);
|
||||
|
@ -314,6 +314,7 @@ void HTTP_C::ReceiveDataImpl(Kernel::HLERequestContext& ctx, bool timeout) {
|
||||
} else {
|
||||
LOG_WARNING(Service_HTTP, "(STUBBED) called");
|
||||
}
|
||||
[[maybe_unused]] Kernel::MappedBuffer& buffer = rp.PopMappedBuffer();
|
||||
|
||||
Kernel::MappedBuffer& buffer = rp.PopMappedBuffer();
|
||||
|
||||
|
@ -17,12 +17,10 @@
|
||||
#include <boost/serialization/unordered_map.hpp>
|
||||
#include <boost/serialization/vector.hpp>
|
||||
#include <boost/serialization/weak_ptr.hpp>
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
#if defined(__ANDROID__)
|
||||
#include <ifaddrs.h>
|
||||
#endif
|
||||
#include <httplib.h>
|
||||
#endif
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
#include "core/hle/service/service.h"
|
||||
@ -214,7 +212,6 @@ public:
|
||||
bool uses_default_client_cert{};
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
httplib::Response response;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
|
||||
|
@ -316,7 +316,7 @@ void Module::Interface::GetTagInfo2(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
if (nfc->nfc_mode == CommunicationMode::TrainTag) {
|
||||
LOG_ERROR(Service_NFC, "CommunicationMode {} not implemented", nfc->nfc_mode);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(26, 0);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(25, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw<TagInfo2>({});
|
||||
return;
|
||||
@ -324,7 +324,7 @@ void Module::Interface::GetTagInfo2(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
TagInfo2 tag_info{};
|
||||
const auto result = nfc->device->GetTagInfo2(tag_info);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(26, 0);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(25, 0);
|
||||
rb.Push(result);
|
||||
rb.PushRaw<TagInfo2>(tag_info);
|
||||
}
|
||||
@ -383,10 +383,14 @@ void Module::Interface::OpenApplicationArea(Kernel::HLERequestContext& ctx) {
|
||||
void Module::Interface::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
u32 access_id = rp.Pop<u32>();
|
||||
[[maybe_unused]] u32 size = rp.Pop<u32>();
|
||||
u32 size = rp.Pop<u32>();
|
||||
std::vector<u8> buffer = rp.PopStaticBuffer();
|
||||
|
||||
LOG_CRITICAL(Service_NFC, "called, size={}", size);
|
||||
LOG_INFO(Service_NFC, "called, size={}", size);
|
||||
|
||||
if (buffer.size() > size) {
|
||||
buffer.resize(size);
|
||||
}
|
||||
|
||||
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
@ -402,8 +406,9 @@ void Module::Interface::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
void Module::Interface::ReadApplicationArea(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
u32 size = rp.Pop<u32>();
|
||||
|
||||
LOG_INFO(Service_NFC, "called");
|
||||
LOG_INFO(Service_NFC, "called, size={}", size);
|
||||
|
||||
nfc->device->RescheduleTagRemoveEvent();
|
||||
|
||||
@ -413,7 +418,7 @@ void Module::Interface::ReadApplicationArea(Kernel::HLERequestContext& ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<u8> buffer(sizeof(ApplicationArea));
|
||||
std::vector<u8> buffer(size);
|
||||
const auto result = nfc->device->GetApplicationArea(buffer);
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||
@ -423,11 +428,15 @@ void Module::Interface::ReadApplicationArea(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
void Module::Interface::WriteApplicationArea(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
[[maybe_unused]] u32 size = rp.Pop<u32>();
|
||||
u32 size = rp.Pop<u32>();
|
||||
std::vector<u8> tag_uuid_info = rp.PopStaticBuffer();
|
||||
std::vector<u8> buffer = rp.PopStaticBuffer();
|
||||
|
||||
LOG_CRITICAL(Service_NFC, "called, size={}", size);
|
||||
LOG_INFO(Service_NFC, "called, size={}", size);
|
||||
|
||||
if (buffer.size() > size) {
|
||||
buffer.resize(size);
|
||||
}
|
||||
|
||||
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
@ -540,7 +549,7 @@ void Module::Interface::GetIdentificationBlock(Kernel::HLERequestContext& ctx) {
|
||||
ModelInfo model_info{};
|
||||
const auto result = nfc->device->GetModelInfo(model_info);
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(0x1F, 0);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(14, 0);
|
||||
rb.Push(result);
|
||||
rb.PushRaw<ModelInfo>(model_info);
|
||||
}
|
||||
|
@ -1101,8 +1101,8 @@ void NfcDevice::BuildAmiiboWithoutKeys() {
|
||||
}
|
||||
|
||||
void NfcDevice::RescheduleTagRemoveEvent() {
|
||||
/// The interval at which the amiibo will be removed automatically 1.5s
|
||||
static constexpr u64 amiibo_removal_interval = nsToCycles(1500 * 1000 * 1000);
|
||||
/// The interval at which the amiibo will be removed automatically 3s
|
||||
static constexpr u64 amiibo_removal_interval = msToCycles(3 * 1000);
|
||||
|
||||
system.CoreTiming().UnscheduleEvent(remove_amiibo_event, 0);
|
||||
|
||||
|
@ -5,9 +5,9 @@
|
||||
#include <memory>
|
||||
#include <httplib.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "web_service/nus_download.h"
|
||||
#include "core/nus_download.h"
|
||||
|
||||
namespace WebService::NUS {
|
||||
namespace Core::NUS {
|
||||
|
||||
std::optional<std::vector<u8>> Download(const std::string& path) {
|
||||
constexpr auto HOST = "http://nus.cdn.c.shop.nintendowifi.net";
|
||||
@ -46,4 +46,4 @@ std::optional<std::vector<u8>> Download(const std::string& path) {
|
||||
return std::vector<u8>(response.body.begin(), response.body.end());
|
||||
}
|
||||
|
||||
} // namespace WebService::NUS
|
||||
} // namespace Core::NUS
|
@ -8,8 +8,8 @@
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace WebService::NUS {
|
||||
namespace Core::NUS {
|
||||
|
||||
std::optional<std::vector<u8>> Download(const std::string& path);
|
||||
|
||||
}
|
||||
} // namespace Core::NUS
|
@ -15,6 +15,7 @@
|
||||
#include "core/core.h"
|
||||
#include "core/movie.h"
|
||||
#include "core/savestate.h"
|
||||
#include "core/savestate_data.h"
|
||||
#include "network/network.h"
|
||||
|
||||
namespace Core {
|
||||
@ -25,8 +26,10 @@ struct CSTHeader {
|
||||
u64_le program_id; /// ID of the ROM being executed. Also called title_id
|
||||
std::array<u8, 20> revision; /// Git hash of the revision this savestate was created with
|
||||
u64_le time; /// The time when this save state was created
|
||||
std::array<u8, 20> build_name; /// The build name (Canary/Nightly) with the version number
|
||||
u32_le zero = 0; /// Should be zero, just in case.
|
||||
|
||||
std::array<u8, 216> reserved{}; /// Make heading 256 bytes so it has consistent size
|
||||
std::array<u8, 192> reserved{}; /// Make heading 256 bytes so it has consistent size
|
||||
};
|
||||
static_assert(sizeof(CSTHeader) == 256, "CSTHeader should be 256 bytes");
|
||||
#pragma pack(pop)
|
||||
@ -58,11 +61,26 @@ static bool ValidateSaveState(const CSTHeader& header, SaveStateInfo& info, u64
|
||||
return false;
|
||||
}
|
||||
const std::string revision = fmt::format("{:02x}", fmt::join(header.revision, ""));
|
||||
const std::string build_name =
|
||||
header.zero == 0 ? reinterpret_cast<const char*>(header.build_name.data()) : "";
|
||||
|
||||
if (revision == Common::g_scm_rev) {
|
||||
info.status = SaveStateInfo::ValidationStatus::OK;
|
||||
} else {
|
||||
if (!build_name.empty()) {
|
||||
info.build_name = build_name;
|
||||
} else if (hash_to_version.find(revision) != hash_to_version.end()) {
|
||||
info.build_name = hash_to_version.at(revision);
|
||||
}
|
||||
if (info.build_name.empty()) {
|
||||
LOG_WARNING(Core, "Save state file {} created from a different revision {}", path,
|
||||
revision);
|
||||
} else {
|
||||
LOG_WARNING(Core,
|
||||
"Save state file {} created from a different build {} with revision {}",
|
||||
path, info.build_name, revision);
|
||||
}
|
||||
|
||||
info.status = SaveStateInfo::ValidationStatus::RevisionDismatch;
|
||||
}
|
||||
return true;
|
||||
@ -134,6 +152,10 @@ void System::SaveState(u32 slot) const {
|
||||
header.time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
const std::string build_fullname = Common::g_build_fullname;
|
||||
std::memset(header.build_name.data(), 0, sizeof(header.build_name));
|
||||
std::memcpy(header.build_name.data(), build_fullname.c_str(),
|
||||
std::min(build_fullname.length(), sizeof(header.build_name) - 1));
|
||||
|
||||
if (file.WriteBytes(&header, sizeof(header)) != sizeof(header) ||
|
||||
file.WriteBytes(buffer.data(), buffer.size()) != buffer.size()) {
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
@ -16,6 +17,7 @@ struct SaveStateInfo {
|
||||
OK,
|
||||
RevisionDismatch,
|
||||
} status;
|
||||
std::string build_name;
|
||||
};
|
||||
|
||||
constexpr u32 SaveStateSlotCount = 10; // Maximum count of savestate slots
|
||||
|
1427
src/core/savestate_data.h
Normal file
1427
src/core/savestate_data.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,6 @@ create_target_directory_groups(citra-room)
|
||||
|
||||
target_link_libraries(citra-room PRIVATE citra_common network)
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
target_compile_definitions(citra-room PRIVATE -DENABLE_WEB_SERVICE)
|
||||
target_link_libraries(citra-room PRIVATE web_service)
|
||||
endif()
|
||||
|
||||
|
@ -21,14 +21,10 @@ add_library(network STATIC
|
||||
create_target_directory_groups(network)
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
target_compile_definitions(network PRIVATE -DENABLE_WEB_SERVICE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
target_link_libraries(network PRIVATE web_service httplib)
|
||||
if (ANDROID)
|
||||
target_link_libraries(network PRIVATE ifaddrs)
|
||||
endif()
|
||||
target_link_libraries(network PRIVATE web_service)
|
||||
endif()
|
||||
|
||||
target_link_libraries(network PRIVATE citra_common enet Boost::serialization cryptopp)
|
||||
target_link_libraries(network PRIVATE citra_common enet Boost::serialization cryptopp httplib)
|
||||
set_target_properties(network PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO})
|
||||
|
||||
if (CITRA_USE_PRECOMPILED_HEADERS)
|
||||
|
@ -170,9 +170,13 @@ bool CustomTexManager::ParseFilename(const FileUtil::FSTEntry& file, CustomTextu
|
||||
}
|
||||
|
||||
void CustomTexManager::PrepareDumping(u64 title_id) {
|
||||
// If a pack exists in the load folder that uses the old hash
|
||||
// dump textures using the old hash.
|
||||
ReadConfig(title_id, true);
|
||||
// If a pack exists in the load folder that uses the old hash, dump textures using the old hash.
|
||||
// This occurs either if a configuration file doesn't exist or that file sets the old hash.
|
||||
const std::string load_path =
|
||||
fmt::format("{}textures/{:016X}/", GetUserPath(FileUtil::UserPath::LoadDir), title_id);
|
||||
if (FileUtil::Exists(load_path) && !ReadConfig(title_id, true)) {
|
||||
use_new_hash = false;
|
||||
}
|
||||
|
||||
// Write template config file
|
||||
const std::string dump_path =
|
||||
|
@ -27,9 +27,12 @@ set(SHADER_FILES
|
||||
vulkan_blit_depth_stencil.frag
|
||||
)
|
||||
|
||||
find_program(GLSLANGVALIDATOR "glslangValidator")
|
||||
if ("${GLSLANGVALIDATOR}" STREQUAL "GLSLANGVALIDATOR-NOTFOUND")
|
||||
message(FATAL_ERROR "Required program `glslangValidator` not found.")
|
||||
find_program(GLSLANG "glslang")
|
||||
if ("${GLSLANG}" STREQUAL "GLSLANG-NOTFOUND")
|
||||
find_program(GLSLANG "glslangValidator")
|
||||
if ("${GLSLANG}" STREQUAL "GLSLANG-NOTFOUND")
|
||||
message(FATAL_ERROR "Required program `glslang` (or `glslangValidator`) not found.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(MACROS "-Dgl_VertexID=gl_VertexIndex")
|
||||
@ -42,11 +45,11 @@ set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE)
|
||||
set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in)
|
||||
set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake)
|
||||
|
||||
# Check if `--quiet` is available on host's glslangValidator version
|
||||
# glslangValidator prints to STDERR iff an unrecognized flag is passed to it
|
||||
# Check if `--quiet` is available on host's glslang version
|
||||
# glslang prints to STDERR iff an unrecognized flag is passed to it
|
||||
execute_process(
|
||||
COMMAND
|
||||
${GLSLANGVALIDATOR} ${QUIET_FLAG}
|
||||
${GLSLANG} ${QUIET_FLAG}
|
||||
ERROR_VARIABLE
|
||||
GLSLANG_ERROR
|
||||
# STDOUT variable defined to silence unnecessary output during CMake configuration
|
||||
@ -55,7 +58,7 @@ execute_process(
|
||||
)
|
||||
|
||||
if (NOT GLSLANG_ERROR STREQUAL "")
|
||||
message(WARNING "Refusing to use unavailable flag `${QUIET_FLAG}` on `${GLSLANGVALIDATOR}`")
|
||||
message(WARNING "Refusing to use unavailable flag `${QUIET_FLAG}` on `${GLSLANG}`")
|
||||
set(QUIET_FLAG "")
|
||||
endif()
|
||||
|
||||
@ -87,7 +90,7 @@ foreach(FILENAME IN ITEMS ${SHADER_FILES})
|
||||
OUTPUT
|
||||
${SPIRV_HEADER_FILE}
|
||||
COMMAND
|
||||
${GLSLANGVALIDATOR} --target-env vulkan1.1 --glsl-version 450 ${QUIET_FLAG} ${MACROS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE}
|
||||
${GLSLANG} --target-env vulkan1.1 --glsl-version 450 ${QUIET_FLAG} ${MACROS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE}
|
||||
MAIN_DEPENDENCY
|
||||
${SOURCE_FILE}
|
||||
)
|
||||
|
@ -599,6 +599,17 @@ void RasterizerAccelerated::NotifyPicaRegisterChanged(u32 id) {
|
||||
SyncTextureLodBias(2);
|
||||
break;
|
||||
|
||||
// Texture borders
|
||||
case PICA_REG_INDEX(texturing.texture0.border_color):
|
||||
SyncTextureBorderColor(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.texture1.border_color):
|
||||
SyncTextureBorderColor(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.texture2.border_color):
|
||||
SyncTextureBorderColor(2);
|
||||
break;
|
||||
|
||||
// Clipping plane
|
||||
case PICA_REG_INDEX(rasterizer.clip_coef[0]):
|
||||
case PICA_REG_INDEX(rasterizer.clip_coef[1]):
|
||||
@ -821,6 +832,16 @@ void RasterizerAccelerated::SyncTextureLodBias(int tex_index) {
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncTextureBorderColor(int tex_index) {
|
||||
const auto pica_textures = regs.texturing.GetTextures();
|
||||
const auto params = pica_textures[tex_index].config;
|
||||
const Common::Vec4f border_color = ColorRGBA8(params.border_color.raw);
|
||||
if (border_color != uniform_block_data.data.tex_border_color[tex_index]) {
|
||||
uniform_block_data.data.tex_border_color[tex_index] = border_color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncClipCoef() {
|
||||
const auto raw_clip_coef = regs.rasterizer.GetClipCoef();
|
||||
const Common::Vec4f new_clip_coef = {raw_clip_coef.x.ToFloat32(), raw_clip_coef.y.ToFloat32(),
|
||||
|
@ -97,6 +97,9 @@ protected:
|
||||
/// Syncs the texture LOD bias to match the PICA register
|
||||
void SyncTextureLodBias(int tex_index);
|
||||
|
||||
/// Syncs the texture border color to match the PICA registers
|
||||
void SyncTextureBorderColor(int tex_index);
|
||||
|
||||
/// Syncs the clip coefficients to match the PICA register
|
||||
void SyncClipCoef();
|
||||
|
||||
|
@ -127,6 +127,29 @@ void RasterizerCache<T>::RemoveFramebuffers(SurfaceId surface_id) {
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void RasterizerCache<T>::RemoveTextureCubeFace(SurfaceId surface_id) {
|
||||
if (False(slot_surfaces[surface_id].flags & SurfaceFlagBits::Tracked)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto it = texture_cube_cache.begin(); it != texture_cube_cache.end();) {
|
||||
TextureCube& cube = it->second;
|
||||
for (SurfaceId& face_id : cube.face_ids) {
|
||||
if (face_id == surface_id) {
|
||||
face_id = SurfaceId{};
|
||||
}
|
||||
}
|
||||
if (std::none_of(cube.face_ids.begin(), cube.face_ids.end(),
|
||||
[](SurfaceId id) { return id; })) {
|
||||
sentenced.emplace_back(cube.surface_id, frame_tick);
|
||||
it = texture_cube_cache.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool RasterizerCache<T>::AccelerateTextureCopy(const GPU::Regs::DisplayTransferConfig& config) {
|
||||
const DebugScope scope{runtime, Common::Vec4f{0.f, 0.f, 1.f, 1.f},
|
||||
@ -866,39 +889,6 @@ SurfaceId RasterizerCache<T>::FindMatch(const SurfaceParams& params, ScaleMatch
|
||||
return match_id;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void RasterizerCache<T>::DuplicateSurface(SurfaceId src_id, SurfaceId dst_id) {
|
||||
Surface& src_surface = slot_surfaces[src_id];
|
||||
Surface& dst_surface = slot_surfaces[dst_id];
|
||||
ASSERT(dst_surface.addr <= src_surface.addr && dst_surface.end >= src_surface.end);
|
||||
|
||||
const auto src_rect = src_surface.GetScaledRect();
|
||||
const auto dst_rect = dst_surface.GetScaledSubRect(src_surface);
|
||||
ASSERT(src_rect.GetWidth() == dst_rect.GetWidth());
|
||||
|
||||
const TextureCopy copy = {
|
||||
.src_level = 0,
|
||||
.dst_level = 0,
|
||||
.src_offset = {src_rect.left, src_rect.bottom},
|
||||
.dst_offset = {dst_rect.left, dst_rect.bottom},
|
||||
.extent = {src_rect.GetWidth(), src_rect.GetHeight()},
|
||||
};
|
||||
runtime.CopyTextures(src_surface, dst_surface, copy);
|
||||
|
||||
dst_surface.invalid_regions -= src_surface.GetInterval();
|
||||
dst_surface.invalid_regions += src_surface.invalid_regions;
|
||||
|
||||
SurfaceRegions regions;
|
||||
for (const auto& pair : RangeFromInterval(dirty_regions, src_surface.GetInterval())) {
|
||||
if (pair.second == src_id) {
|
||||
regions += pair.first;
|
||||
}
|
||||
}
|
||||
for (const auto& interval : regions) {
|
||||
dirty_regions.set({interval, dst_id});
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void RasterizerCache<T>::ValidateSurface(SurfaceId surface_id, PAddr addr, u32 size) {
|
||||
if (size == 0) [[unlikely]] {
|
||||
@ -1051,15 +1041,16 @@ bool RasterizerCache<T>::UploadCustomSurface(SurfaceId surface_id, SurfaceInterv
|
||||
surface.flags |= SurfaceFlagBits::Custom;
|
||||
|
||||
const auto upload = [this, level, surface_id, material]() -> bool {
|
||||
Surface& surface = slot_surfaces[surface_id];
|
||||
ASSERT_MSG(True(surface.flags & SurfaceFlagBits::Custom),
|
||||
ASSERT_MSG(True(slot_surfaces[surface_id].flags & SurfaceFlagBits::Custom),
|
||||
"Surface is not suitable for custom upload, aborting!");
|
||||
if (!surface.IsCustom()) {
|
||||
const SurfaceBase old_surface{surface};
|
||||
if (!slot_surfaces[surface_id].IsCustom()) {
|
||||
const SurfaceBase old_surface{slot_surfaces[surface_id]};
|
||||
const SurfaceId old_id =
|
||||
slot_surfaces.swap_and_insert(surface_id, runtime, old_surface, material);
|
||||
slot_surfaces[old_id].flags &= ~SurfaceFlagBits::Registered;
|
||||
sentenced.emplace_back(old_id, frame_tick);
|
||||
}
|
||||
Surface& surface = slot_surfaces[surface_id];
|
||||
surface.UploadCustom(material, level);
|
||||
if (custom_tex_manager.SkipMipmaps()) {
|
||||
runtime.GenerateMipmaps(surface);
|
||||
@ -1203,7 +1194,6 @@ void RasterizerCache<T>::ClearAll(bool flush) {
|
||||
cached_pages -= flush_interval;
|
||||
dirty_regions.clear();
|
||||
page_table.clear();
|
||||
remove_surfaces.clear();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@ -1232,7 +1222,7 @@ void RasterizerCache<T>::FlushRegion(PAddr addr, u32 size, SurfaceId flush_surfa
|
||||
interval.lower(), interval.upper()};
|
||||
|
||||
SCOPE_EXIT({ flushed_intervals += interval; });
|
||||
if (surface.IsFill()) {
|
||||
if (surface.type == SurfaceType::Fill) {
|
||||
DownloadFillSurface(surface, interval);
|
||||
continue;
|
||||
}
|
||||
@ -1274,6 +1264,7 @@ void RasterizerCache<T>::InvalidateRegion(PAddr addr, u32 size, SurfaceId region
|
||||
region_owner.MarkValid(invalid_interval);
|
||||
}
|
||||
|
||||
boost::container::small_vector<SurfaceId, 4> remove_surfaces;
|
||||
ForEachSurfaceInRegion(addr, size, [&](SurfaceId surface_id, Surface& surface) {
|
||||
if (surface_id == region_owner_id) {
|
||||
return;
|
||||
@ -1301,13 +1292,12 @@ void RasterizerCache<T>::InvalidateRegion(PAddr addr, u32 size, SurfaceId region
|
||||
|
||||
for (const SurfaceId surface_id : remove_surfaces) {
|
||||
UnregisterSurface(surface_id);
|
||||
if (!slot_surfaces[surface_id].IsFill()) {
|
||||
if (slot_surfaces[surface_id].type != SurfaceType::Fill) {
|
||||
sentenced.emplace_back(surface_id, frame_tick);
|
||||
} else {
|
||||
slot_surfaces.erase(surface_id);
|
||||
}
|
||||
}
|
||||
remove_surfaces.clear();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@ -1316,14 +1306,17 @@ SurfaceId RasterizerCache<T>::CreateSurface(const SurfaceParams& params) {
|
||||
const auto it = std::find_if(sentenced.begin(), sentenced.end(), [&](const auto& pair) {
|
||||
return slot_surfaces[pair.first] == params;
|
||||
});
|
||||
if (it != sentenced.end()) {
|
||||
if (it == sentenced.end()) {
|
||||
return slot_surfaces.insert(runtime, params);
|
||||
}
|
||||
const SurfaceId surface_id = it->first;
|
||||
sentenced.erase(it);
|
||||
return surface_id;
|
||||
}
|
||||
return slot_surfaces.insert(runtime, params);
|
||||
}();
|
||||
Surface& surface = slot_surfaces[surface_id];
|
||||
if (params.res_scale > surface.res_scale) {
|
||||
surface.ScaleUp(params.res_scale);
|
||||
}
|
||||
surface.MarkInvalid(surface.GetInterval());
|
||||
return surface_id;
|
||||
}
|
||||
@ -1364,24 +1357,7 @@ void RasterizerCache<T>::UnregisterSurface(SurfaceId surface_id) {
|
||||
surfaces.erase(vector_it);
|
||||
});
|
||||
|
||||
if (False(surface.flags & SurfaceFlagBits::Tracked)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::erase_if(texture_cube_cache, [&](auto& pair) {
|
||||
TextureCube& cube = pair.second;
|
||||
for (SurfaceId& face_id : cube.face_ids) {
|
||||
if (face_id == surface_id) {
|
||||
face_id = SurfaceId{};
|
||||
}
|
||||
}
|
||||
if (std::none_of(cube.face_ids.begin(), cube.face_ids.end(),
|
||||
[](SurfaceId id) { return id; })) {
|
||||
sentenced.emplace_back(cube.surface_id, frame_tick);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
RemoveTextureCubeFace(surface_id);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@ -1393,7 +1369,6 @@ void RasterizerCache<T>::UnregisterAll() {
|
||||
}
|
||||
}
|
||||
texture_cube_cache.clear();
|
||||
remove_surfaces.clear();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
|
@ -165,8 +165,8 @@ private:
|
||||
/// Removes any framebuffers that reference the provided surface_id.
|
||||
void RemoveFramebuffers(SurfaceId surface_id);
|
||||
|
||||
/// Transfers ownership of a memory region from src_surface to dest_surface
|
||||
void DuplicateSurface(SurfaceId src_id, SurfaceId dst_id);
|
||||
/// Removes any references of the provided surface id from cached texture cubes.
|
||||
void RemoveTextureCubeFace(SurfaceId surface_id);
|
||||
|
||||
/// Computes the hash of the provided texture data.
|
||||
u64 ComputeHash(const SurfaceParams& load_info, std::span<u8> upload_data);
|
||||
@ -224,7 +224,6 @@ private:
|
||||
Common::SlotVector<Framebuffer> slot_framebuffers;
|
||||
SurfaceMap dirty_regions;
|
||||
PageMap cached_pages;
|
||||
std::vector<SurfaceId> remove_surfaces;
|
||||
u32 resolution_scale_factor;
|
||||
u64 frame_tick{};
|
||||
FramebufferParams fb_params;
|
||||
|
@ -46,10 +46,6 @@ public:
|
||||
/// Returns true if the surface contains a custom material with a normal map.
|
||||
bool HasNormalMap() const noexcept;
|
||||
|
||||
bool IsFill() const noexcept {
|
||||
return type == SurfaceType::Fill;
|
||||
}
|
||||
|
||||
bool Overlaps(PAddr overlap_addr, size_t overlap_size) const noexcept {
|
||||
const PAddr overlap_end = overlap_addr + static_cast<PAddr>(overlap_size);
|
||||
return addr < overlap_end && overlap_addr < end;
|
||||
|
@ -227,4 +227,11 @@ std::string SurfaceParams::DebugName(bool scaled, bool custom) const noexcept {
|
||||
custom ? "custom," : "", scaled ? "scaled" : "unscaled");
|
||||
}
|
||||
|
||||
bool SurfaceParams::operator==(const SurfaceParams& other) const noexcept {
|
||||
return std::tie(addr, end, width, height, stride, levels, is_tiled, texture_type, pixel_format,
|
||||
custom_format) ==
|
||||
std::tie(other.addr, other.end, other.width, other.height, other.stride, other.levels,
|
||||
other.is_tiled, other.texture_type, other.pixel_format, other.custom_format);
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
||||
|
@ -53,9 +53,7 @@ public:
|
||||
/// Returns a string identifier of the params object
|
||||
std::string DebugName(bool scaled, bool custom = false) const noexcept;
|
||||
|
||||
bool operator==(const SurfaceParams& other) const noexcept {
|
||||
return std::memcmp(this, &other, sizeof(SurfaceParams)) == 0;
|
||||
}
|
||||
bool operator==(const SurfaceParams& other) const noexcept;
|
||||
|
||||
[[nodiscard]] SurfaceInterval GetInterval() const noexcept {
|
||||
return SurfaceInterval{addr, end};
|
||||
|
@ -5,6 +5,9 @@
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/dumping/backend.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "video_core/renderer_opengl/frame_dumper_opengl.h"
|
||||
#include "video_core/renderer_opengl/gl_texture_mailbox.h"
|
||||
@ -12,13 +15,9 @@
|
||||
namespace OpenGL {
|
||||
|
||||
FrameDumperOpenGL::FrameDumperOpenGL(Core::System& system_, Frontend::EmuWindow& emu_window)
|
||||
: system(system_), context(emu_window.CreateSharedContext()) {}
|
||||
: system{system_}, context{emu_window.CreateSharedContext()} {}
|
||||
|
||||
FrameDumperOpenGL::~FrameDumperOpenGL() {
|
||||
if (present_thread.joinable()) {
|
||||
present_thread.join();
|
||||
}
|
||||
}
|
||||
FrameDumperOpenGL::~FrameDumperOpenGL() = default;
|
||||
|
||||
bool FrameDumperOpenGL::IsDumping() const {
|
||||
auto video_dumper = system.GetVideoDumper();
|
||||
@ -35,19 +34,19 @@ void FrameDumperOpenGL::StartDumping() {
|
||||
present_thread.join();
|
||||
}
|
||||
|
||||
present_thread = std::thread(&FrameDumperOpenGL::PresentLoop, this);
|
||||
present_thread = std::jthread([this](std::stop_token stop_token) { PresentLoop(stop_token); });
|
||||
}
|
||||
|
||||
void FrameDumperOpenGL::StopDumping() {
|
||||
stop_requested.store(true, std::memory_order_relaxed);
|
||||
present_thread.request_stop();
|
||||
}
|
||||
|
||||
void FrameDumperOpenGL::PresentLoop() {
|
||||
void FrameDumperOpenGL::PresentLoop(std::stop_token stop_token) {
|
||||
const auto scope = context->Acquire();
|
||||
InitializeOpenGLObjects();
|
||||
|
||||
const auto& layout = GetLayout();
|
||||
while (!stop_requested.exchange(false)) {
|
||||
while (!stop_token.stop_requested()) {
|
||||
auto frame = mailbox->TryGetPresentFrame(200);
|
||||
if (!frame) {
|
||||
continue;
|
||||
|
@ -6,9 +6,8 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include "core/core.h"
|
||||
#include "core/dumping/backend.h"
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
|
||||
@ -18,6 +17,10 @@ class GraphicsContext;
|
||||
class TextureMailbox;
|
||||
} // namespace Frontend
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class RendererOpenGL;
|
||||
@ -42,12 +45,12 @@ public:
|
||||
private:
|
||||
void InitializeOpenGLObjects();
|
||||
void CleanupOpenGLObjects();
|
||||
void PresentLoop();
|
||||
void PresentLoop(std::stop_token stop_token);
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
std::unique_ptr<Frontend::GraphicsContext> context;
|
||||
std::thread present_thread;
|
||||
std::atomic_bool stop_requested{false};
|
||||
std::jthread present_thread;
|
||||
|
||||
// PBOs used to dump frames faster
|
||||
std::array<OGLBuffer, 2> pbos;
|
||||
|
@ -188,6 +188,10 @@ void Driver::FindBugs() {
|
||||
if (vendor == Vendor::AMD || (vendor == Vendor::Intel && !is_linux)) {
|
||||
bugs |= DriverBug::BrokenTextureView;
|
||||
}
|
||||
|
||||
if (vendor == Vendor::Intel && !is_linux) {
|
||||
bugs |= DriverBug::BrokenClearTexture;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
|
@ -38,6 +38,8 @@ enum class DriverBug {
|
||||
VertexArrayOutOfBound = 1 << 1,
|
||||
// On AMD and Intel drivers on Windows glTextureView produces incorrect results
|
||||
BrokenTextureView = 1 << 2,
|
||||
// On Haswell and Broadwell Intel drivers glClearTexSubImage produces a black screen
|
||||
BrokenClearTexture = 1 << 3,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -109,8 +109,8 @@ PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs, bool use_normal
|
||||
<< 4;
|
||||
|
||||
// Fragment lighting
|
||||
|
||||
state.lighting.enable = !regs.lighting.disable;
|
||||
if (state.lighting.enable) {
|
||||
state.lighting.src_num = regs.lighting.max_light_index + 1;
|
||||
|
||||
for (unsigned light_index = 0; light_index < state.lighting.src_num; ++light_index) {
|
||||
@ -118,9 +118,12 @@ PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs, bool use_normal
|
||||
const auto& light = regs.lighting.light[num];
|
||||
state.lighting.light[light_index].num = num;
|
||||
state.lighting.light[light_index].directional = light.config.directional != 0;
|
||||
state.lighting.light[light_index].two_sided_diffuse = light.config.two_sided_diffuse != 0;
|
||||
state.lighting.light[light_index].geometric_factor_0 = light.config.geometric_factor_0 != 0;
|
||||
state.lighting.light[light_index].geometric_factor_1 = light.config.geometric_factor_1 != 0;
|
||||
state.lighting.light[light_index].two_sided_diffuse =
|
||||
light.config.two_sided_diffuse != 0;
|
||||
state.lighting.light[light_index].geometric_factor_0 =
|
||||
light.config.geometric_factor_0 != 0;
|
||||
state.lighting.light[light_index].geometric_factor_1 =
|
||||
light.config.geometric_factor_1 != 0;
|
||||
state.lighting.light[light_index].dist_atten_enable =
|
||||
!regs.lighting.IsDistAttenDisabled(num);
|
||||
state.lighting.light[light_index].spot_atten_enable =
|
||||
@ -129,14 +132,20 @@ PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs, bool use_normal
|
||||
}
|
||||
|
||||
state.lighting.lut_d0.enable = regs.lighting.config1.disable_lut_d0 == 0;
|
||||
if (state.lighting.lut_d0.enable) {
|
||||
state.lighting.lut_d0.abs_input = regs.lighting.abs_lut_input.disable_d0 == 0;
|
||||
state.lighting.lut_d0.type = regs.lighting.lut_input.d0.Value();
|
||||
state.lighting.lut_d0.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d0);
|
||||
state.lighting.lut_d0.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d0);
|
||||
}
|
||||
|
||||
state.lighting.lut_d1.enable = regs.lighting.config1.disable_lut_d1 == 0;
|
||||
if (state.lighting.lut_d1.enable) {
|
||||
state.lighting.lut_d1.abs_input = regs.lighting.abs_lut_input.disable_d1 == 0;
|
||||
state.lighting.lut_d1.type = regs.lighting.lut_input.d1.Value();
|
||||
state.lighting.lut_d1.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d1);
|
||||
state.lighting.lut_d1.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d1);
|
||||
}
|
||||
|
||||
// this is a dummy field due to lack of the corresponding register
|
||||
state.lighting.lut_sp.enable = true;
|
||||
@ -145,24 +154,36 @@ PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs, bool use_normal
|
||||
state.lighting.lut_sp.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.sp);
|
||||
|
||||
state.lighting.lut_fr.enable = regs.lighting.config1.disable_lut_fr == 0;
|
||||
if (state.lighting.lut_fr.enable) {
|
||||
state.lighting.lut_fr.abs_input = regs.lighting.abs_lut_input.disable_fr == 0;
|
||||
state.lighting.lut_fr.type = regs.lighting.lut_input.fr.Value();
|
||||
state.lighting.lut_fr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.fr);
|
||||
state.lighting.lut_fr.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.fr);
|
||||
}
|
||||
|
||||
state.lighting.lut_rr.enable = regs.lighting.config1.disable_lut_rr == 0;
|
||||
if (state.lighting.lut_rr.enable) {
|
||||
state.lighting.lut_rr.abs_input = regs.lighting.abs_lut_input.disable_rr == 0;
|
||||
state.lighting.lut_rr.type = regs.lighting.lut_input.rr.Value();
|
||||
state.lighting.lut_rr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rr);
|
||||
state.lighting.lut_rr.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rr);
|
||||
}
|
||||
|
||||
state.lighting.lut_rg.enable = regs.lighting.config1.disable_lut_rg == 0;
|
||||
if (state.lighting.lut_rg.enable) {
|
||||
state.lighting.lut_rg.abs_input = regs.lighting.abs_lut_input.disable_rg == 0;
|
||||
state.lighting.lut_rg.type = regs.lighting.lut_input.rg.Value();
|
||||
state.lighting.lut_rg.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rg);
|
||||
state.lighting.lut_rg.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rg);
|
||||
}
|
||||
|
||||
state.lighting.lut_rb.enable = regs.lighting.config1.disable_lut_rb == 0;
|
||||
if (state.lighting.lut_rb.enable) {
|
||||
state.lighting.lut_rb.abs_input = regs.lighting.abs_lut_input.disable_rb == 0;
|
||||
state.lighting.lut_rb.type = regs.lighting.lut_input.rb.Value();
|
||||
state.lighting.lut_rb.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rb);
|
||||
state.lighting.lut_rb.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rb);
|
||||
}
|
||||
|
||||
state.lighting.config = regs.lighting.config0.config;
|
||||
state.lighting.enable_primary_alpha = regs.lighting.config0.enable_primary_alpha;
|
||||
@ -173,11 +194,14 @@ PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs, bool use_normal
|
||||
state.lighting.clamp_highlights = regs.lighting.config0.clamp_highlights != 0;
|
||||
|
||||
state.lighting.enable_shadow = regs.lighting.config0.enable_shadow != 0;
|
||||
if (state.lighting.enable_shadow) {
|
||||
state.lighting.shadow_primary = regs.lighting.config0.shadow_primary != 0;
|
||||
state.lighting.shadow_secondary = regs.lighting.config0.shadow_secondary != 0;
|
||||
state.lighting.shadow_invert = regs.lighting.config0.shadow_invert != 0;
|
||||
state.lighting.shadow_alpha = regs.lighting.config0.shadow_alpha != 0;
|
||||
state.lighting.shadow_selector = regs.lighting.config0.shadow_selector;
|
||||
}
|
||||
}
|
||||
|
||||
state.proctex.enable = regs.texturing.main_config.texture3_enable;
|
||||
if (state.proctex.enable) {
|
||||
@ -1246,13 +1270,13 @@ float LookupLightingLUT(int lut_index, int index, float delta) {
|
||||
}
|
||||
|
||||
float LookupLightingLUTUnsigned(int lut_index, float pos) {
|
||||
int index = clamp(int(pos * 256.0), 0, 255);
|
||||
int index = int(clamp(floor(pos * 256.0), 0.f, 255.f));
|
||||
float delta = pos * 256.0 - float(index);
|
||||
return LookupLightingLUT(lut_index, index, delta);
|
||||
}
|
||||
|
||||
float LookupLightingLUTSigned(int lut_index, float pos) {
|
||||
int index = clamp(int(pos * 128.0), -128, 127);
|
||||
int index = int(clamp(floor(pos * 128.0), -128.f, 127.f));
|
||||
float delta = pos * 128.0 - float(index);
|
||||
if (index < 0) index += 256;
|
||||
return LookupLightingLUT(lut_index, index, delta);
|
||||
|
@ -187,7 +187,41 @@ bool TextureRuntime::Reinterpret(Surface& source, Surface& dest,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClear& clear) {
|
||||
bool TextureRuntime::ClearTextureWithoutFbo(Surface& surface,
|
||||
const VideoCore::TextureClear& clear) {
|
||||
if (!driver.HasArbClearTexture() || driver.HasBug(DriverBug::BrokenClearTexture)) {
|
||||
return false;
|
||||
}
|
||||
GLenum format{};
|
||||
GLenum type{};
|
||||
switch (surface.type) {
|
||||
case SurfaceType::Color:
|
||||
case SurfaceType::Texture:
|
||||
format = GL_RGBA;
|
||||
type = GL_FLOAT;
|
||||
break;
|
||||
case SurfaceType::Depth:
|
||||
format = GL_DEPTH_COMPONENT;
|
||||
type = GL_FLOAT;
|
||||
break;
|
||||
case SurfaceType::DepthStencil:
|
||||
format = GL_DEPTH_STENCIL;
|
||||
type = GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown surface type {}", surface.type);
|
||||
}
|
||||
glClearTexSubImage(surface.Handle(), clear.texture_level, clear.texture_rect.left,
|
||||
clear.texture_rect.bottom, 0, clear.texture_rect.GetWidth(),
|
||||
clear.texture_rect.GetHeight(), 1, format, type, &clear.value);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClear& clear) {
|
||||
if (ClearTextureWithoutFbo(surface, clear)) {
|
||||
return;
|
||||
}
|
||||
|
||||
OpenGLState state = OpenGLState::GetCurState();
|
||||
state.scissor.enabled = true;
|
||||
state.scissor.x = clear.texture_rect.left;
|
||||
@ -222,10 +256,7 @@ bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClea
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown surface type {}", surface.type);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
|
||||
|
@ -59,7 +59,7 @@ public:
|
||||
bool Reinterpret(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit);
|
||||
|
||||
/// Fills the rectangle of the texture with the clear value provided
|
||||
bool ClearTexture(Surface& surface, const VideoCore::TextureClear& clear);
|
||||
void ClearTexture(Surface& surface, const VideoCore::TextureClear& clear);
|
||||
|
||||
/// Copies a rectangle of source to another rectange of dest
|
||||
bool CopyTextures(Surface& source, Surface& dest, const VideoCore::TextureCopy& copy);
|
||||
@ -76,6 +76,9 @@ private:
|
||||
return driver;
|
||||
}
|
||||
|
||||
/// Fills the rectangle of the surface with the value provided, without an fbo.
|
||||
bool ClearTextureWithoutFbo(Surface& surface, const VideoCore::TextureClear& clear);
|
||||
|
||||
private:
|
||||
const Driver& driver;
|
||||
BlitHelper blit_helper;
|
||||
|
@ -41,9 +41,9 @@ constexpr char dolphin_shader_header[] = R"(
|
||||
#define lerp mix
|
||||
|
||||
// Output variable
|
||||
out float4 color;
|
||||
layout (location = 0) out float4 color;
|
||||
// Input coordinates
|
||||
in float2 frag_tex_coord;
|
||||
layout (location = 0) in float2 frag_tex_coord;
|
||||
// Resolution
|
||||
uniform float4 i_resolution;
|
||||
uniform float4 o_resolution;
|
||||
|
@ -409,7 +409,9 @@ bool Instance::CreateDevice() {
|
||||
const bool has_extended_dynamic_state =
|
||||
add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, is_arm || is_qualcomm,
|
||||
"it is broken on Qualcomm and ARM drivers");
|
||||
const bool has_custom_border_color = add_extension(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
|
||||
const bool has_custom_border_color =
|
||||
add_extension(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME, is_qualcomm,
|
||||
"it is broken on most Qualcomm driver versions");
|
||||
const bool has_index_type_uint8 = add_extension(VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME);
|
||||
const bool has_pipeline_creation_cache_control =
|
||||
add_extension(VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME);
|
||||
|
@ -69,6 +69,17 @@ PicaFSConfig::PicaFSConfig(const Pica::Regs& regs, const Instance& instance) {
|
||||
|
||||
state.texture2_use_coord1.Assign(regs.texturing.main_config.texture2_use_coord1 != 0);
|
||||
|
||||
const auto pica_textures = regs.texturing.GetTextures();
|
||||
for (u32 tex_index = 0; tex_index < 3; tex_index++) {
|
||||
const auto config = pica_textures[tex_index].config;
|
||||
state.texture_border_color[tex_index].enable_s.Assign(
|
||||
!instance.IsCustomBorderColorSupported() &&
|
||||
config.wrap_s == TexturingRegs::TextureConfig::WrapMode::ClampToBorder);
|
||||
state.texture_border_color[tex_index].enable_t.Assign(
|
||||
!instance.IsCustomBorderColorSupported() &&
|
||||
config.wrap_t == TexturingRegs::TextureConfig::WrapMode::ClampToBorder);
|
||||
}
|
||||
|
||||
// Emulate logic op in the shader if not supported. This is mostly for mobile GPUs
|
||||
const bool emulate_logic_op = instance.NeedsLogicOpEmulation() &&
|
||||
!Pica::g_state.regs.framebuffer.output_merger.alphablend_enable;
|
||||
@ -101,8 +112,8 @@ PicaFSConfig::PicaFSConfig(const Pica::Regs& regs, const Instance& instance) {
|
||||
regs.texturing.tev_combiner_buffer_input.update_mask_a.Value() << 4);
|
||||
|
||||
// Fragment lighting
|
||||
|
||||
state.lighting.enable.Assign(!regs.lighting.disable);
|
||||
if (state.lighting.enable) {
|
||||
state.lighting.src_num.Assign(regs.lighting.max_light_index + 1);
|
||||
|
||||
for (u32 light_index = 0; light_index < state.lighting.src_num; ++light_index) {
|
||||
@ -110,8 +121,8 @@ PicaFSConfig::PicaFSConfig(const Pica::Regs& regs, const Instance& instance) {
|
||||
const auto& light = regs.lighting.light[num];
|
||||
state.lighting.light[light_index].num.Assign(num);
|
||||
state.lighting.light[light_index].directional.Assign(light.config.directional != 0);
|
||||
state.lighting.light[light_index].two_sided_diffuse.Assign(light.config.two_sided_diffuse !=
|
||||
0);
|
||||
state.lighting.light[light_index].two_sided_diffuse.Assign(
|
||||
light.config.two_sided_diffuse != 0);
|
||||
state.lighting.light[light_index].geometric_factor_0.Assign(
|
||||
light.config.geometric_factor_0 != 0);
|
||||
state.lighting.light[light_index].geometric_factor_1.Assign(
|
||||
@ -125,14 +136,20 @@ PicaFSConfig::PicaFSConfig(const Pica::Regs& regs, const Instance& instance) {
|
||||
}
|
||||
|
||||
state.lighting.lut_d0.enable.Assign(regs.lighting.config1.disable_lut_d0 == 0);
|
||||
if (state.lighting.lut_d0.enable) {
|
||||
state.lighting.lut_d0.abs_input.Assign(regs.lighting.abs_lut_input.disable_d0 == 0);
|
||||
state.lighting.lut_d0.type.Assign(regs.lighting.lut_input.d0.Value());
|
||||
state.lighting.lut_d0.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d0);
|
||||
state.lighting.lut_d0.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d0);
|
||||
}
|
||||
|
||||
state.lighting.lut_d1.enable.Assign(regs.lighting.config1.disable_lut_d1 == 0);
|
||||
if (state.lighting.lut_d1.enable) {
|
||||
state.lighting.lut_d1.abs_input.Assign(regs.lighting.abs_lut_input.disable_d1 == 0);
|
||||
state.lighting.lut_d1.type.Assign(regs.lighting.lut_input.d1.Value());
|
||||
state.lighting.lut_d1.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d1);
|
||||
state.lighting.lut_d1.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d1);
|
||||
}
|
||||
|
||||
// this is a dummy field due to lack of the corresponding register
|
||||
state.lighting.lut_sp.enable.Assign(1);
|
||||
@ -141,24 +158,36 @@ PicaFSConfig::PicaFSConfig(const Pica::Regs& regs, const Instance& instance) {
|
||||
state.lighting.lut_sp.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.sp);
|
||||
|
||||
state.lighting.lut_fr.enable.Assign(regs.lighting.config1.disable_lut_fr == 0);
|
||||
if (state.lighting.lut_fr.enable) {
|
||||
state.lighting.lut_fr.abs_input.Assign(regs.lighting.abs_lut_input.disable_fr == 0);
|
||||
state.lighting.lut_fr.type.Assign(regs.lighting.lut_input.fr.Value());
|
||||
state.lighting.lut_fr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.fr);
|
||||
state.lighting.lut_fr.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.fr);
|
||||
}
|
||||
|
||||
state.lighting.lut_rr.enable.Assign(regs.lighting.config1.disable_lut_rr == 0);
|
||||
if (state.lighting.lut_rr.enable) {
|
||||
state.lighting.lut_rr.abs_input.Assign(regs.lighting.abs_lut_input.disable_rr == 0);
|
||||
state.lighting.lut_rr.type.Assign(regs.lighting.lut_input.rr.Value());
|
||||
state.lighting.lut_rr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rr);
|
||||
state.lighting.lut_rr.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rr);
|
||||
}
|
||||
|
||||
state.lighting.lut_rg.enable.Assign(regs.lighting.config1.disable_lut_rg == 0);
|
||||
if (state.lighting.lut_rg.enable) {
|
||||
state.lighting.lut_rg.abs_input.Assign(regs.lighting.abs_lut_input.disable_rg == 0);
|
||||
state.lighting.lut_rg.type.Assign(regs.lighting.lut_input.rg.Value());
|
||||
state.lighting.lut_rg.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rg);
|
||||
state.lighting.lut_rg.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rg);
|
||||
}
|
||||
|
||||
state.lighting.lut_rb.enable.Assign(regs.lighting.config1.disable_lut_rb == 0);
|
||||
if (state.lighting.lut_rb.enable) {
|
||||
state.lighting.lut_rb.abs_input.Assign(regs.lighting.abs_lut_input.disable_rb == 0);
|
||||
state.lighting.lut_rb.type.Assign(regs.lighting.lut_input.rb.Value());
|
||||
state.lighting.lut_rb.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rb);
|
||||
state.lighting.lut_rb.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rb);
|
||||
}
|
||||
|
||||
state.lighting.config.Assign(regs.lighting.config0.config);
|
||||
state.lighting.enable_primary_alpha.Assign(regs.lighting.config0.enable_primary_alpha);
|
||||
@ -169,11 +198,14 @@ PicaFSConfig::PicaFSConfig(const Pica::Regs& regs, const Instance& instance) {
|
||||
state.lighting.clamp_highlights.Assign(regs.lighting.config0.clamp_highlights != 0);
|
||||
|
||||
state.lighting.enable_shadow.Assign(regs.lighting.config0.enable_shadow != 0);
|
||||
if (state.lighting.enable_shadow) {
|
||||
state.lighting.shadow_primary.Assign(regs.lighting.config0.shadow_primary != 0);
|
||||
state.lighting.shadow_secondary.Assign(regs.lighting.config0.shadow_secondary != 0);
|
||||
state.lighting.shadow_invert.Assign(regs.lighting.config0.shadow_invert != 0);
|
||||
state.lighting.shadow_alpha.Assign(regs.lighting.config0.shadow_alpha != 0);
|
||||
state.lighting.shadow_selector.Assign(regs.lighting.config0.shadow_selector);
|
||||
}
|
||||
}
|
||||
|
||||
state.proctex.enable.Assign(regs.texturing.main_config.texture3_enable);
|
||||
if (state.proctex.enable) {
|
||||
@ -284,54 +316,6 @@ static bool IsPassThroughTevStage(const TevStageConfig& stage) {
|
||||
stage.GetColorMultiplier() == 1 && stage.GetAlphaMultiplier() == 1);
|
||||
}
|
||||
|
||||
static std::string SampleTexture(const PicaFSConfig& config, unsigned texture_unit) {
|
||||
const auto& state = config.state;
|
||||
switch (texture_unit) {
|
||||
case 0:
|
||||
// Only unit 0 respects the texturing type
|
||||
switch (state.texture0_type) {
|
||||
case TexturingRegs::TextureConfig::Texture2D:
|
||||
return "textureLod(tex0, texcoord0, getLod(texcoord0 * "
|
||||
"vec2(textureSize(tex0, 0))) + tex_lod_bias[0])";
|
||||
case TexturingRegs::TextureConfig::Projection2D:
|
||||
// TODO (wwylele): find the exact LOD formula for projection texture
|
||||
return "textureProj(tex0, vec3(texcoord0, texcoord0_w))";
|
||||
case TexturingRegs::TextureConfig::TextureCube:
|
||||
return "texture(tex_cube, vec3(texcoord0, texcoord0_w))";
|
||||
case TexturingRegs::TextureConfig::Shadow2D:
|
||||
return "shadowTexture(texcoord0, texcoord0_w)";
|
||||
case TexturingRegs::TextureConfig::ShadowCube:
|
||||
return "shadowTextureCube(texcoord0, texcoord0_w)";
|
||||
case TexturingRegs::TextureConfig::Disabled:
|
||||
return "vec4(0.0)";
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture type {:x}", state.texture0_type);
|
||||
UNIMPLEMENTED();
|
||||
return "texture(tex0, texcoord0)";
|
||||
}
|
||||
case 1:
|
||||
return "textureLod(tex1, texcoord1, getLod(texcoord1 * "
|
||||
"vec2(textureSize(tex1, 0))) + tex_lod_bias[1])";
|
||||
case 2:
|
||||
if (state.texture2_use_coord1)
|
||||
return "textureLod(tex2, texcoord1, getLod(texcoord1 * "
|
||||
"vec2(textureSize(tex2, 0))) + tex_lod_bias[2])";
|
||||
else
|
||||
return "textureLod(tex2, texcoord2, getLod(texcoord2 * "
|
||||
"vec2(textureSize(tex2, 0))) + tex_lod_bias[2])";
|
||||
case 3:
|
||||
if (state.proctex.enable) {
|
||||
return "ProcTex()";
|
||||
} else {
|
||||
LOG_DEBUG(Render_OpenGL, "Using Texture3 without enabling it");
|
||||
return "vec4(0.0)";
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the specified TEV stage source component(s)
|
||||
static void AppendSource(std::string& out, const PicaFSConfig& config,
|
||||
TevStageConfig::Source source, std::string_view index_name) {
|
||||
@ -347,16 +331,16 @@ static void AppendSource(std::string& out, const PicaFSConfig& config,
|
||||
out += "secondary_fragment_color";
|
||||
break;
|
||||
case Source::Texture0:
|
||||
out += SampleTexture(config, 0);
|
||||
out += "sampleTexUnit0()";
|
||||
break;
|
||||
case Source::Texture1:
|
||||
out += SampleTexture(config, 1);
|
||||
out += "sampleTexUnit1()";
|
||||
break;
|
||||
case Source::Texture2:
|
||||
out += SampleTexture(config, 2);
|
||||
out += "sampleTexUnit2()";
|
||||
break;
|
||||
case Source::Texture3:
|
||||
out += SampleTexture(config, 3);
|
||||
out += "sampleTexUnit3()";
|
||||
break;
|
||||
case Source::PreviousBuffer:
|
||||
out += "combiner_buffer";
|
||||
@ -656,7 +640,7 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
|
||||
|
||||
// Compute fragment normals and tangents
|
||||
const auto perturbation = [&] {
|
||||
return fmt::format("2.0 * ({}).rgb - 1.0", SampleTexture(config, lighting.bump_selector));
|
||||
return fmt::format("2.0 * (sampleTexUnit{}()).rgb - 1.0", lighting.bump_selector);
|
||||
};
|
||||
|
||||
switch (lighting.bump_mode) {
|
||||
@ -700,7 +684,7 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
|
||||
"vec3 tangent = quaternion_rotate(normalized_normquat, surface_tangent);\n";
|
||||
|
||||
if (lighting.enable_shadow) {
|
||||
std::string shadow_texture = SampleTexture(config, lighting.shadow_selector);
|
||||
std::string shadow_texture = fmt::format("sampleTexUnit{}()", lighting.shadow_selector);
|
||||
if (lighting.shadow_invert) {
|
||||
out += fmt::format("vec4 shadow = vec4(1.0) - {};\n", shadow_texture);
|
||||
} else {
|
||||
@ -1247,13 +1231,13 @@ float LookupLightingLUT(int lut_index, int index, float delta) {
|
||||
}
|
||||
|
||||
float LookupLightingLUTUnsigned(int lut_index, float pos) {
|
||||
int index = clamp(int(pos * 256.0), 0, 255);
|
||||
int index = int(clamp(floor(pos * 256.0), 0.f, 255.f));
|
||||
float delta = pos * 256.0 - float(index);
|
||||
return LookupLightingLUT(lut_index, index, delta);
|
||||
}
|
||||
|
||||
float LookupLightingLUTSigned(int lut_index, float pos) {
|
||||
int index = clamp(int(pos * 128.0), -128, 127);
|
||||
int index = int(clamp(floor(pos * 128.0), -128.f, 127.f));
|
||||
float delta = pos * 128.0 - float(index);
|
||||
if (index < 0) index += 256;
|
||||
return LookupLightingLUT(lut_index, index, delta);
|
||||
@ -1310,6 +1294,7 @@ float mix2(vec4 s, vec2 a) {
|
||||
|
||||
vec4 shadowTexture(vec2 uv, float w) {
|
||||
)";
|
||||
|
||||
if (!config.state.shadow_texture_orthographic) {
|
||||
out += "uv /= w;";
|
||||
}
|
||||
@ -1344,9 +1329,7 @@ vec4 shadowTextureCube(vec2 uv, float w) {
|
||||
uv = -c.xy;
|
||||
if (c.z > 0.0) uv.x = -uv.x;
|
||||
}
|
||||
)";
|
||||
out += "uint z = uint(max(0, int(min(w, 1.0) * float(0xFFFFFF)) - shadow_texture_bias));";
|
||||
out += R"(
|
||||
uint z = uint(max(0, int(min(w, 1.0) * float(0xFFFFFF)) - shadow_texture_bias));
|
||||
vec2 coord = vec2(size) * (uv / w * vec2(0.5) + vec2(0.5)) - vec2(0.5);
|
||||
vec2 coord_floor = floor(coord);
|
||||
vec2 f = coord - coord_floor;
|
||||
@ -1411,8 +1394,90 @@ vec4 shadowTextureCube(vec2 uv, float w) {
|
||||
}
|
||||
)";
|
||||
|
||||
if (config.state.proctex.enable)
|
||||
if (config.state.proctex.enable) {
|
||||
AppendProcTexSampler(out, config);
|
||||
}
|
||||
|
||||
for (u32 texture_unit = 0; texture_unit < 4; texture_unit++) {
|
||||
out += fmt::format("vec4 sampleTexUnit{}() {{", texture_unit);
|
||||
if (texture_unit == 0 && state.texture0_type == TexturingRegs::TextureConfig::Disabled) {
|
||||
out += "return vec4(0.0);}";
|
||||
continue;
|
||||
} else if (texture_unit == 3) {
|
||||
if (state.proctex.enable) {
|
||||
out += "return ProcTex();}";
|
||||
} else {
|
||||
out += "return vec4(0.0);}";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 texcoord_num = texture_unit == 2 && state.texture2_use_coord1 ? 1 : texture_unit;
|
||||
if (config.state.texture_border_color[texture_unit].enable_s) {
|
||||
out += fmt::format(R"(
|
||||
if (texcoord{}.x < 0 || texcoord{}.x > 1) {{
|
||||
return tex_border_color[{}];
|
||||
}}
|
||||
)",
|
||||
texcoord_num, texcoord_num, texture_unit);
|
||||
}
|
||||
if (config.state.texture_border_color[texture_unit].enable_t) {
|
||||
out += fmt::format(R"(
|
||||
if (texcoord{}.y < 0 || texcoord{}.y > 1) {{
|
||||
return tex_border_color[{}];
|
||||
}}
|
||||
)",
|
||||
texcoord_num, texcoord_num, texture_unit);
|
||||
}
|
||||
// TODO: 3D border?
|
||||
|
||||
switch (texture_unit) {
|
||||
case 0:
|
||||
// Only unit 0 respects the texturing type
|
||||
switch (state.texture0_type) {
|
||||
case TexturingRegs::TextureConfig::Texture2D:
|
||||
out += "return textureLod(tex0, texcoord0, getLod(texcoord0 * "
|
||||
"vec2(textureSize(tex0, 0))) + tex_lod_bias[0]);";
|
||||
break;
|
||||
case TexturingRegs::TextureConfig::Projection2D:
|
||||
// TODO (wwylele): find the exact LOD formula for projection texture
|
||||
out += "return textureProj(tex0, vec3(texcoord0, texcoord0_w));";
|
||||
break;
|
||||
case TexturingRegs::TextureConfig::TextureCube:
|
||||
out += "return texture(tex_cube, vec3(texcoord0, texcoord0_w));";
|
||||
break;
|
||||
case TexturingRegs::TextureConfig::Shadow2D:
|
||||
out += "return shadowTexture(texcoord0, texcoord0_w);";
|
||||
break;
|
||||
case TexturingRegs::TextureConfig::ShadowCube:
|
||||
out += "return shadowTextureCube(texcoord0, texcoord0_w);";
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture type {:x}", state.texture0_type);
|
||||
UNIMPLEMENTED();
|
||||
out += "return texture(tex0, texcoord0);";
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
out += "return textureLod(tex1, texcoord1, getLod(texcoord1 * vec2(textureSize(tex1, "
|
||||
"0))) + tex_lod_bias[1]);";
|
||||
break;
|
||||
case 2:
|
||||
if (state.texture2_use_coord1) {
|
||||
out += "return textureLod(tex2, texcoord1, getLod(texcoord1 * "
|
||||
"vec2(textureSize(tex2, 0))) + tex_lod_bias[1]);";
|
||||
} else {
|
||||
out += "return textureLod(tex2, texcoord2, getLod(texcoord2 * "
|
||||
"vec2(textureSize(tex2, 0))) + tex_lod_bias[2]);";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
|
||||
out += "}";
|
||||
}
|
||||
|
||||
// We round the interpolated primary color to the nearest 1/255th
|
||||
// This maintains the PICA's 8 bits of precision
|
||||
|
@ -57,6 +57,11 @@ struct PicaFSConfigState {
|
||||
BitField<28, 1, u32> shadow_texture_orthographic;
|
||||
};
|
||||
|
||||
union {
|
||||
BitField<0, 1, u32> enable_s;
|
||||
BitField<1, 1, u32> enable_t;
|
||||
} texture_border_color[3];
|
||||
|
||||
std::array<TevStageConfigRaw, 6> tev_stages;
|
||||
|
||||
struct {
|
||||
|
@ -21,8 +21,8 @@ FragmentModule::FragmentModule(Core::TelemetrySession& telemetry_, const PicaFSC
|
||||
DefineArithmeticTypes();
|
||||
DefineUniformStructs();
|
||||
DefineInterface();
|
||||
if (config.state.proctex.enable) {
|
||||
DefineProcTexSampler();
|
||||
for (u32 i = 0; i < NUM_TEX_UNITS; i++) {
|
||||
DefineTexSampler(i);
|
||||
}
|
||||
DefineEntryPoint();
|
||||
}
|
||||
@ -225,7 +225,8 @@ void FragmentModule::WriteLighting() {
|
||||
|
||||
// Compute fragment normals and tangents
|
||||
const auto perturbation = [&]() -> Id {
|
||||
const Id texel{SampleTexture(lighting.bump_selector)};
|
||||
const Id texel{
|
||||
OpFunctionCall(vec_ids.Get(4), sample_tex_unit_func[lighting.bump_selector])};
|
||||
const Id texel_rgb{OpVectorShuffle(vec_ids.Get(3), texel, texel, 0, 1, 2)};
|
||||
const Id rgb_mul_two{OpVectorTimesScalar(vec_ids.Get(3), texel_rgb, ConstF32(2.f))};
|
||||
return OpFSub(vec_ids.Get(3), rgb_mul_two, ConstF32(1.f, 1.f, 1.f));
|
||||
@ -284,23 +285,25 @@ void FragmentModule::WriteLighting() {
|
||||
|
||||
Id shadow{ConstF32(1.f, 1.f, 1.f, 1.f)};
|
||||
if (lighting.enable_shadow) {
|
||||
shadow = SampleTexture(lighting.shadow_selector);
|
||||
shadow = OpFunctionCall(vec_ids.Get(4), sample_tex_unit_func[lighting.shadow_selector]);
|
||||
if (lighting.shadow_invert) {
|
||||
shadow = OpFSub(vec_ids.Get(4), ConstF32(1.f, 1.f, 1.f, 1.f), shadow);
|
||||
}
|
||||
}
|
||||
|
||||
const auto lookup_lighting_lut_unsigned = [this](Id lut_index, Id pos) -> Id {
|
||||
const Id pos_int{OpConvertFToS(i32_id, OpFMul(f32_id, pos, ConstF32(256.f)))};
|
||||
const Id index{OpSClamp(i32_id, pos_int, ConstS32(0), ConstS32(255))};
|
||||
const Id pos_floor{OpFloor(f32_id, OpFMul(f32_id, pos, ConstF32(256.f)))};
|
||||
const Id index_float{OpFClamp(f32_id, pos_floor, ConstF32(0.f), ConstF32(255.f))};
|
||||
const Id index{OpConvertFToS(i32_id, index_float)};
|
||||
const Id neg_index{OpFNegate(f32_id, OpConvertSToF(f32_id, index))};
|
||||
const Id delta{OpFma(f32_id, pos, ConstF32(256.f), neg_index)};
|
||||
return LookupLightingLUT(lut_index, index, delta);
|
||||
};
|
||||
|
||||
const auto lookup_lighting_lut_signed = [this](Id lut_index, Id pos) -> Id {
|
||||
const Id pos_int{OpConvertFToS(i32_id, OpFMul(f32_id, pos, ConstF32(128.f)))};
|
||||
const Id index{OpSClamp(i32_id, pos_int, ConstS32(-128), ConstS32(127))};
|
||||
const Id pos_floor{OpFloor(f32_id, OpFMul(f32_id, pos, ConstF32(128.f)))};
|
||||
const Id index_float{OpFClamp(f32_id, pos_floor, ConstF32(-128.f), ConstF32(127.f))};
|
||||
const Id index{OpConvertFToS(i32_id, index_float)};
|
||||
const Id neg_index{OpFNegate(f32_id, OpConvertSToF(f32_id, index))};
|
||||
const Id delta{OpFma(f32_id, pos, ConstF32(128.f), neg_index)};
|
||||
const Id increment{
|
||||
@ -708,89 +711,6 @@ void FragmentModule::WriteAlphaTestCondition(FramebufferRegs::CompareFunc func)
|
||||
}
|
||||
}
|
||||
|
||||
Id FragmentModule::SampleTexture(u32 texture_unit) {
|
||||
const PicaFSConfigState& state = config.state;
|
||||
const Id zero_vec{ConstF32(0.f, 0.f, 0.f, 0.f)};
|
||||
|
||||
// PICA's LOD formula for 2D textures.
|
||||
// This LOD formula is the same as the LOD lower limit defined in OpenGL.
|
||||
// f(x, y) >= max{m_u, m_v, m_w}
|
||||
// (See OpenGL 4.6 spec, 8.14.1 - Scale Factor and Level-of-Detail)
|
||||
const auto sample_lod = [this, texture_unit](Id tex_id, Id texcoord_id) {
|
||||
const Id sampled_image{OpLoad(TypeSampledImage(image2d_id), tex_id)};
|
||||
const Id tex_image{OpImage(image2d_id, sampled_image)};
|
||||
const Id tex_size{OpImageQuerySizeLod(ivec_ids.Get(2), tex_image, ConstS32(0))};
|
||||
const Id texcoord{OpLoad(vec_ids.Get(2), texcoord_id)};
|
||||
const Id coord{OpFMul(vec_ids.Get(2), texcoord, OpConvertSToF(vec_ids.Get(2), tex_size))};
|
||||
const Id abs_dfdx_coord{OpFAbs(vec_ids.Get(2), OpDPdx(vec_ids.Get(2), coord))};
|
||||
const Id abs_dfdy_coord{OpFAbs(vec_ids.Get(2), OpDPdy(vec_ids.Get(2), coord))};
|
||||
const Id d{OpFMax(vec_ids.Get(2), abs_dfdx_coord, abs_dfdy_coord)};
|
||||
const Id dx_dy_max{
|
||||
OpFMax(f32_id, OpCompositeExtract(f32_id, d, 0), OpCompositeExtract(f32_id, d, 1))};
|
||||
const Id lod{OpLog2(f32_id, dx_dy_max)};
|
||||
const Id lod_bias{GetShaderDataMember(f32_id, ConstS32(28), ConstU32(texture_unit))};
|
||||
const Id biased_lod{OpFAdd(f32_id, lod, lod_bias)};
|
||||
return OpImageSampleExplicitLod(vec_ids.Get(4), sampled_image, texcoord,
|
||||
spv::ImageOperandsMask::Lod, biased_lod);
|
||||
};
|
||||
|
||||
const auto sample = [this](Id tex_id, bool projection) {
|
||||
const Id image_type = tex_id.value == tex_cube_id.value ? image_cube_id : image2d_id;
|
||||
const Id sampled_image{OpLoad(TypeSampledImage(image_type), tex_id)};
|
||||
const Id texcoord0{OpLoad(vec_ids.Get(2), texcoord0_id)};
|
||||
const Id texcoord0_w{OpLoad(f32_id, texcoord0_w_id)};
|
||||
const Id coord{OpCompositeConstruct(vec_ids.Get(3),
|
||||
OpCompositeExtract(f32_id, texcoord0, 0),
|
||||
OpCompositeExtract(f32_id, texcoord0, 1), texcoord0_w)};
|
||||
if (projection) {
|
||||
return OpImageSampleProjImplicitLod(vec_ids.Get(4), sampled_image, coord);
|
||||
} else {
|
||||
return OpImageSampleImplicitLod(vec_ids.Get(4), sampled_image, coord);
|
||||
}
|
||||
};
|
||||
|
||||
switch (texture_unit) {
|
||||
case 0:
|
||||
// Only unit 0 respects the texturing type
|
||||
switch (state.texture0_type) {
|
||||
case Pica::TexturingRegs::TextureConfig::Texture2D:
|
||||
return sample_lod(tex0_id, texcoord0_id);
|
||||
case Pica::TexturingRegs::TextureConfig::Projection2D:
|
||||
return sample(tex0_id, true);
|
||||
case Pica::TexturingRegs::TextureConfig::TextureCube:
|
||||
return sample(tex_cube_id, false);
|
||||
case Pica::TexturingRegs::TextureConfig::Shadow2D:
|
||||
return SampleShadow();
|
||||
// case Pica::TexturingRegs::TextureConfig::ShadowCube:
|
||||
// return "shadowTextureCube(texcoord0, texcoord0_w)";
|
||||
case Pica::TexturingRegs::TextureConfig::Disabled:
|
||||
return zero_vec;
|
||||
default:
|
||||
LOG_CRITICAL(Render_Vulkan, "Unhandled texture type {:x}", state.texture0_type);
|
||||
UNIMPLEMENTED();
|
||||
return zero_vec;
|
||||
}
|
||||
case 1:
|
||||
return sample_lod(tex1_id, texcoord1_id);
|
||||
case 2:
|
||||
if (state.texture2_use_coord1) {
|
||||
return sample_lod(tex2_id, texcoord1_id);
|
||||
} else {
|
||||
return sample_lod(tex2_id, texcoord2_id);
|
||||
}
|
||||
case 3:
|
||||
if (state.proctex.enable) {
|
||||
return OpFunctionCall(vec_ids.Get(4), proctex_func);
|
||||
} else {
|
||||
LOG_DEBUG(Render_Vulkan, "Using Texture3 without enabling it");
|
||||
return zero_vec;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return void_id;
|
||||
}
|
||||
}
|
||||
|
||||
Id FragmentModule::CompareShadow(Id pixel, Id z) {
|
||||
const Id pixel_d24{OpShiftRightLogical(u32_id, pixel, ConstS32(8))};
|
||||
const Id pixel_s8{OpConvertUToF(f32_id, OpBitwiseAnd(u32_id, pixel, ConstU32(255u)))};
|
||||
@ -800,7 +720,7 @@ Id FragmentModule::CompareShadow(Id pixel, Id z) {
|
||||
}
|
||||
|
||||
Id FragmentModule::SampleShadow() {
|
||||
const Id texcoord0{OpLoad(vec_ids.Get(2), texcoord0_id)};
|
||||
const Id texcoord0{OpLoad(vec_ids.Get(2), texcoord_id[0])};
|
||||
const Id texcoord0_w{OpLoad(f32_id, texcoord0_w_id)};
|
||||
const Id abs_min_w{OpFMul(f32_id, OpFMin(f32_id, OpFAbs(f32_id, texcoord0_w), ConstF32(1.f)),
|
||||
ConstF32(16777215.f))};
|
||||
@ -939,11 +859,145 @@ Id FragmentModule::AppendProcTexCombineAndMap(ProcTexCombiner combiner, Id u, Id
|
||||
return ProcTexLookupLUT(offset, combined);
|
||||
}
|
||||
|
||||
void FragmentModule::DefineProcTexSampler() {
|
||||
void FragmentModule::DefineTexSampler(u32 texture_unit) {
|
||||
const PicaFSConfigState& state = config.state;
|
||||
|
||||
const Id func_type{TypeFunction(vec_ids.Get(4))};
|
||||
proctex_func = OpFunction(vec_ids.Get(4), spv::FunctionControlMask::MaskNone, func_type);
|
||||
sample_tex_unit_func[texture_unit] =
|
||||
OpFunction(vec_ids.Get(4), spv::FunctionControlMask::MaskNone, func_type);
|
||||
AddLabel(OpLabel());
|
||||
|
||||
const Id zero_vec{ConstF32(0.f, 0.f, 0.f, 0.f)};
|
||||
|
||||
if (texture_unit == 0 && state.texture0_type == TexturingRegs::TextureConfig::Disabled) {
|
||||
OpReturnValue(zero_vec);
|
||||
OpFunctionEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
if (texture_unit == 3) {
|
||||
if (state.proctex.enable) {
|
||||
OpReturnValue(ProcTexSampler());
|
||||
} else {
|
||||
OpReturnValue(zero_vec);
|
||||
}
|
||||
OpFunctionEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
const Id border_label{OpLabel()};
|
||||
const Id not_border_label{OpLabel()};
|
||||
|
||||
u32 texcoord_num = texture_unit == 2 && state.texture2_use_coord1 ? 1 : texture_unit;
|
||||
const Id texcoord{OpLoad(vec_ids.Get(2), texcoord_id[texcoord_num])};
|
||||
|
||||
auto& texture_border_color = state.texture_border_color[texture_unit];
|
||||
if (texture_border_color.enable_s || texture_border_color.enable_t) {
|
||||
const Id texcoord_s{OpCompositeExtract(f32_id, texcoord, 0)};
|
||||
const Id texcoord_t{OpCompositeExtract(f32_id, texcoord, 1)};
|
||||
|
||||
const Id s_lt_zero{OpFOrdLessThan(bool_id, texcoord_s, ConstF32(0.0f))};
|
||||
const Id s_gt_one{OpFOrdGreaterThan(bool_id, texcoord_s, ConstF32(1.0f))};
|
||||
const Id t_lt_zero{OpFOrdLessThan(bool_id, texcoord_t, ConstF32(0.0f))};
|
||||
const Id t_gt_one{OpFOrdGreaterThan(bool_id, texcoord_t, ConstF32(1.0f))};
|
||||
|
||||
Id cond{};
|
||||
if (texture_border_color.enable_s && texture_border_color.enable_t) {
|
||||
cond = OpAny(bool_id, OpCompositeConstruct(bvec_ids.Get(4), s_lt_zero, s_gt_one,
|
||||
t_lt_zero, t_gt_one));
|
||||
} else if (texture_border_color.enable_s) {
|
||||
cond = OpAny(bool_id, OpCompositeConstruct(bvec_ids.Get(2), s_lt_zero, s_gt_one));
|
||||
} else if (texture_border_color.enable_t) {
|
||||
cond = OpAny(bool_id, OpCompositeConstruct(bvec_ids.Get(2), t_lt_zero, t_gt_one));
|
||||
}
|
||||
|
||||
OpSelectionMerge(not_border_label, spv::SelectionControlMask::MaskNone);
|
||||
OpBranchConditional(cond, border_label, not_border_label);
|
||||
|
||||
AddLabel(border_label);
|
||||
const Id border_color{
|
||||
GetShaderDataMember(vec_ids.Get(4), ConstS32(29), ConstU32(texture_unit))};
|
||||
OpReturnValue(border_color);
|
||||
|
||||
AddLabel(not_border_label);
|
||||
}
|
||||
|
||||
// PICA's LOD formula for 2D textures.
|
||||
// This LOD formula is the same as the LOD lower limit defined in OpenGL.
|
||||
// f(x, y) >= max{m_u, m_v, m_w}
|
||||
// (See OpenGL 4.6 spec, 8.14.1 - Scale Factor and Level-of-Detail)
|
||||
const auto sample_lod = [&](Id tex_id) {
|
||||
const Id sampled_image{OpLoad(TypeSampledImage(image2d_id), tex_id)};
|
||||
const Id tex_image{OpImage(image2d_id, sampled_image)};
|
||||
const Id tex_size{OpImageQuerySizeLod(ivec_ids.Get(2), tex_image, ConstS32(0))};
|
||||
const Id coord{OpFMul(vec_ids.Get(2), texcoord, OpConvertSToF(vec_ids.Get(2), tex_size))};
|
||||
const Id abs_dfdx_coord{OpFAbs(vec_ids.Get(2), OpDPdx(vec_ids.Get(2), coord))};
|
||||
const Id abs_dfdy_coord{OpFAbs(vec_ids.Get(2), OpDPdy(vec_ids.Get(2), coord))};
|
||||
const Id d{OpFMax(vec_ids.Get(2), abs_dfdx_coord, abs_dfdy_coord)};
|
||||
const Id dx_dy_max{
|
||||
OpFMax(f32_id, OpCompositeExtract(f32_id, d, 0), OpCompositeExtract(f32_id, d, 1))};
|
||||
const Id lod{OpLog2(f32_id, dx_dy_max)};
|
||||
const Id lod_bias{GetShaderDataMember(f32_id, ConstS32(28), ConstU32(texture_unit))};
|
||||
const Id biased_lod{OpFAdd(f32_id, lod, lod_bias)};
|
||||
return OpImageSampleExplicitLod(vec_ids.Get(4), sampled_image, texcoord,
|
||||
spv::ImageOperandsMask::Lod, biased_lod);
|
||||
};
|
||||
|
||||
const auto sample_3d = [&](Id tex_id, bool projection) {
|
||||
const Id image_type = tex_id.value == tex_cube_id.value ? image_cube_id : image2d_id;
|
||||
const Id sampled_image{OpLoad(TypeSampledImage(image_type), tex_id)};
|
||||
const Id texcoord0_w{OpLoad(f32_id, texcoord0_w_id)};
|
||||
const Id coord{OpCompositeConstruct(vec_ids.Get(3), OpCompositeExtract(f32_id, texcoord, 0),
|
||||
OpCompositeExtract(f32_id, texcoord, 1), texcoord0_w)};
|
||||
if (projection) {
|
||||
return OpImageSampleProjImplicitLod(vec_ids.Get(4), sampled_image, coord);
|
||||
} else {
|
||||
return OpImageSampleImplicitLod(vec_ids.Get(4), sampled_image, coord);
|
||||
}
|
||||
};
|
||||
|
||||
Id ret_val{void_id};
|
||||
switch (texture_unit) {
|
||||
case 0:
|
||||
// Only unit 0 respects the texturing type
|
||||
switch (state.texture0_type) {
|
||||
case Pica::TexturingRegs::TextureConfig::Texture2D:
|
||||
ret_val = sample_lod(tex0_id);
|
||||
break;
|
||||
case Pica::TexturingRegs::TextureConfig::Projection2D:
|
||||
ret_val = sample_3d(tex0_id, true);
|
||||
break;
|
||||
case Pica::TexturingRegs::TextureConfig::TextureCube:
|
||||
ret_val = sample_3d(tex_cube_id, false);
|
||||
break;
|
||||
case Pica::TexturingRegs::TextureConfig::Shadow2D:
|
||||
ret_val = SampleShadow();
|
||||
// case Pica::TexturingRegs::TextureConfig::ShadowCube:
|
||||
// return "shadowTextureCube(texcoord0, texcoord0_w)";
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Render_Vulkan, "Unhandled texture type {:x}", state.texture0_type);
|
||||
UNIMPLEMENTED();
|
||||
ret_val = zero_vec;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
ret_val = sample_lod(tex1_id);
|
||||
break;
|
||||
case 2:
|
||||
ret_val = sample_lod(tex2_id);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
|
||||
OpReturnValue(ret_val);
|
||||
OpFunctionEnd();
|
||||
}
|
||||
|
||||
Id FragmentModule::ProcTexSampler() {
|
||||
// Define noise tables at the beginning of the function
|
||||
if (config.state.proctex.noise_enable) {
|
||||
noise1d_table =
|
||||
@ -955,24 +1009,11 @@ void FragmentModule::DefineProcTexSampler() {
|
||||
|
||||
Id uv{};
|
||||
if (config.state.proctex.coord < 3) {
|
||||
Id texcoord_id{};
|
||||
switch (config.state.proctex.coord.Value()) {
|
||||
case 0:
|
||||
texcoord_id = texcoord0_id;
|
||||
break;
|
||||
case 1:
|
||||
texcoord_id = texcoord1_id;
|
||||
break;
|
||||
case 2:
|
||||
texcoord_id = texcoord2_id;
|
||||
break;
|
||||
}
|
||||
|
||||
const Id texcoord{OpLoad(vec_ids.Get(2), texcoord_id)};
|
||||
const Id texcoord{OpLoad(vec_ids.Get(2), texcoord_id[config.state.proctex.coord.Value()])};
|
||||
uv = OpFAbs(vec_ids.Get(2), texcoord);
|
||||
} else {
|
||||
LOG_CRITICAL(Render_Vulkan, "Unexpected proctex.coord >= 3");
|
||||
uv = OpFAbs(vec_ids.Get(2), OpLoad(vec_ids.Get(2), texcoord0_id));
|
||||
uv = OpFAbs(vec_ids.Get(2), OpLoad(vec_ids.Get(2), texcoord_id[0]));
|
||||
}
|
||||
|
||||
// This LOD formula is the same as the LOD upper limit defined in OpenGL.
|
||||
@ -1056,8 +1097,7 @@ void FragmentModule::DefineProcTexSampler() {
|
||||
final_color = OpCompositeInsert(vec_ids.Get(4), final_alpha, final_color, 3);
|
||||
}
|
||||
|
||||
OpReturnValue(final_color);
|
||||
OpFunctionEnd();
|
||||
return final_color;
|
||||
}
|
||||
|
||||
Id FragmentModule::Byteround(Id variable_id, u32 size) {
|
||||
@ -1224,13 +1264,13 @@ Id FragmentModule::AppendSource(TevStageConfig::Source source, s32 index) {
|
||||
case Source::SecondaryFragmentColor:
|
||||
return secondary_fragment_color;
|
||||
case Source::Texture0:
|
||||
return SampleTexture(0);
|
||||
return OpFunctionCall(vec_ids.Get(4), sample_tex_unit_func[0]);
|
||||
case Source::Texture1:
|
||||
return SampleTexture(1);
|
||||
return OpFunctionCall(vec_ids.Get(4), sample_tex_unit_func[1]);
|
||||
case Source::Texture2:
|
||||
return SampleTexture(2);
|
||||
return OpFunctionCall(vec_ids.Get(4), sample_tex_unit_func[2]);
|
||||
case Source::Texture3:
|
||||
return SampleTexture(3);
|
||||
return OpFunctionCall(vec_ids.Get(4), sample_tex_unit_func[3]);
|
||||
case Source::PreviousBuffer:
|
||||
return combiner_buffer;
|
||||
case Source::Constant:
|
||||
@ -1426,9 +1466,9 @@ void FragmentModule::DefineEntryPoint() {
|
||||
|
||||
const Id main_type{TypeFunction(TypeVoid())};
|
||||
const Id main_func{OpFunction(TypeVoid(), spv::FunctionControlMask::MaskNone, main_type)};
|
||||
AddEntryPoint(spv::ExecutionModel::Fragment, main_func, "main", primary_color_id, texcoord0_id,
|
||||
texcoord1_id, texcoord2_id, texcoord0_w_id, normquat_id, view_id, color_id,
|
||||
gl_frag_coord_id, gl_frag_depth_id);
|
||||
AddEntryPoint(spv::ExecutionModel::Fragment, main_func, "main", primary_color_id,
|
||||
texcoord_id[0], texcoord_id[1], texcoord_id[2], texcoord0_w_id, normquat_id,
|
||||
view_id, color_id, gl_frag_coord_id, gl_frag_depth_id);
|
||||
AddExecutionMode(main_func, spv::ExecutionMode::OriginUpperLeft);
|
||||
AddExecutionMode(main_func, spv::ExecutionMode::DepthReplacing);
|
||||
}
|
||||
@ -1441,21 +1481,25 @@ void FragmentModule::DefineUniformStructs() {
|
||||
const Id light_src_array_id{TypeArray(light_src_struct_id, ConstU32(NUM_LIGHTS))};
|
||||
const Id lighting_lut_array_id{TypeArray(ivec_ids.Get(4), ConstU32(NUM_LIGHTING_SAMPLERS / 4))};
|
||||
const Id const_color_array_id{TypeArray(vec_ids.Get(4), ConstU32(NUM_TEV_STAGES))};
|
||||
const Id border_color_array_id{TypeArray(vec_ids.Get(4), ConstU32(NUM_NON_PROC_TEX_UNITS))};
|
||||
|
||||
const Id shader_data_struct_id{TypeStruct(
|
||||
i32_id, i32_id, f32_id, f32_id, f32_id, f32_id, i32_id, i32_id, i32_id, i32_id, i32_id,
|
||||
i32_id, i32_id, i32_id, i32_id, i32_id, f32_id, i32_id, u32_id, lighting_lut_array_id,
|
||||
vec_ids.Get(3), vec_ids.Get(2), vec_ids.Get(2), vec_ids.Get(2), vec_ids.Get(3),
|
||||
light_src_array_id, const_color_array_id, vec_ids.Get(4), vec_ids.Get(3), vec_ids.Get(4))};
|
||||
const Id shader_data_struct_id{
|
||||
TypeStruct(i32_id, i32_id, f32_id, f32_id, f32_id, f32_id, i32_id, i32_id, i32_id, i32_id,
|
||||
i32_id, i32_id, i32_id, i32_id, i32_id, i32_id, f32_id, i32_id, u32_id,
|
||||
lighting_lut_array_id, vec_ids.Get(3), vec_ids.Get(2), vec_ids.Get(2),
|
||||
vec_ids.Get(2), vec_ids.Get(3), light_src_array_id, const_color_array_id,
|
||||
vec_ids.Get(4), vec_ids.Get(3), border_color_array_id, vec_ids.Get(4))};
|
||||
|
||||
constexpr std::array light_src_offsets{0u, 16u, 32u, 48u, 64u, 80u, 92u, 96u};
|
||||
constexpr std::array shader_data_offsets{
|
||||
0u, 4u, 8u, 12u, 16u, 20u, 24u, 28u, 32u, 36u, 40u, 44u, 48u, 52u, 56u,
|
||||
60u, 64u, 68u, 72u, 80u, 176u, 192u, 200u, 208u, 224u, 240u, 1136u, 1232u, 1248u, 1264u};
|
||||
constexpr std::array shader_data_offsets{0u, 4u, 8u, 12u, 16u, 20u, 24u, 28u,
|
||||
32u, 36u, 40u, 44u, 48u, 52u, 56u, 60u,
|
||||
64u, 68u, 72u, 80u, 176u, 192u, 200u, 208u,
|
||||
224u, 240u, 1136u, 1232u, 1248u, 1264u, 1312u};
|
||||
|
||||
Decorate(lighting_lut_array_id, spv::Decoration::ArrayStride, 16u);
|
||||
Decorate(light_src_array_id, spv::Decoration::ArrayStride, 112u);
|
||||
Decorate(const_color_array_id, spv::Decoration::ArrayStride, 16u);
|
||||
Decorate(border_color_array_id, spv::Decoration::ArrayStride, 16u);
|
||||
for (u32 i = 0; i < static_cast<u32>(light_src_offsets.size()); i++) {
|
||||
MemberDecorate(light_src_struct_id, i, spv::Decoration::Offset, light_src_offsets[i]);
|
||||
}
|
||||
@ -1473,9 +1517,9 @@ void FragmentModule::DefineUniformStructs() {
|
||||
void FragmentModule::DefineInterface() {
|
||||
// Define interface block
|
||||
primary_color_id = DefineInput(vec_ids.Get(4), 1);
|
||||
texcoord0_id = DefineInput(vec_ids.Get(2), 2);
|
||||
texcoord1_id = DefineInput(vec_ids.Get(2), 3);
|
||||
texcoord2_id = DefineInput(vec_ids.Get(2), 4);
|
||||
texcoord_id[0] = DefineInput(vec_ids.Get(2), 2);
|
||||
texcoord_id[1] = DefineInput(vec_ids.Get(2), 3);
|
||||
texcoord_id[2] = DefineInput(vec_ids.Get(2), 4);
|
||||
texcoord0_w_id = DefineInput(f32_id, 5);
|
||||
normquat_id = DefineInput(vec_ids.Get(4), 6);
|
||||
view_id = DefineInput(vec_ids.Get(3), 7);
|
||||
|
@ -30,6 +30,8 @@ class FragmentModule : public Sirit::Module {
|
||||
static constexpr u32 NUM_TEV_STAGES = 6;
|
||||
static constexpr u32 NUM_LIGHTS = 8;
|
||||
static constexpr u32 NUM_LIGHTING_SAMPLERS = 24;
|
||||
static constexpr u32 NUM_TEX_UNITS = 4;
|
||||
static constexpr u32 NUM_NON_PROC_TEX_UNITS = 3;
|
||||
|
||||
public:
|
||||
explicit FragmentModule(Core::TelemetrySession& telemetry, const PicaFSConfig& config);
|
||||
@ -57,15 +59,15 @@ private:
|
||||
/// Writes the code to emulate the specified TEV stage
|
||||
void WriteTevStage(s32 index);
|
||||
|
||||
/// Defines the tex3 proctex sampling function
|
||||
void DefineProcTexSampler();
|
||||
/// Defines the basic texture sampling functions for a unit
|
||||
void DefineTexSampler(u32 texture_unit);
|
||||
|
||||
/// Function for sampling the procedurally generated texture unit.
|
||||
Id ProcTexSampler();
|
||||
|
||||
/// Writes the if-statement condition used to evaluate alpha testing.
|
||||
void WriteAlphaTestCondition(Pica::FramebufferRegs::CompareFunc func);
|
||||
|
||||
/// Samples the current fragment texel from the provided texture unit
|
||||
[[nodiscard]] Id SampleTexture(u32 texture_unit);
|
||||
|
||||
/// Samples the current fragment texel from shadow plane
|
||||
[[nodiscard]] Id SampleShadow();
|
||||
|
||||
@ -237,9 +239,7 @@ private:
|
||||
Id shader_data_id{};
|
||||
|
||||
Id primary_color_id{};
|
||||
Id texcoord0_id{};
|
||||
Id texcoord1_id{};
|
||||
Id texcoord2_id{};
|
||||
Id texcoord_id[NUM_NON_PROC_TEX_UNITS]{};
|
||||
Id texcoord0_w_id{};
|
||||
Id normquat_id{};
|
||||
Id view_id{};
|
||||
@ -276,7 +276,7 @@ private:
|
||||
Id alpha_results_2{};
|
||||
Id alpha_results_3{};
|
||||
|
||||
Id proctex_func{};
|
||||
Id sample_tex_unit_func[NUM_TEX_UNITS]{};
|
||||
Id noise1d_table{};
|
||||
Id noise2d_table{};
|
||||
Id lut_offsets{};
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <nihstro/shader_bytecode.h>
|
||||
#include "common/assert.h"
|
||||
@ -26,32 +27,64 @@ using nihstro::SwizzlePattern;
|
||||
|
||||
namespace Pica::Shader {
|
||||
|
||||
struct IfStackElement {
|
||||
u32 else_address;
|
||||
u32 end_address;
|
||||
};
|
||||
|
||||
struct CallStackElement {
|
||||
u32 final_address; // Address upon which we jump to return_address
|
||||
u32 return_address; // Where to jump when leaving scope
|
||||
u8 repeat_counter; // How often to repeat until this call stack element is removed
|
||||
u8 loop_increment; // Which value to add to the loop counter after an iteration
|
||||
// TODO: Should this be a signed value? Does it even matter?
|
||||
u32 loop_address; // The address where we'll return to after each loop iteration
|
||||
u32 end_address;
|
||||
u32 return_address;
|
||||
};
|
||||
|
||||
struct LoopStackElement {
|
||||
u32 entry_address;
|
||||
u32 end_address;
|
||||
u8 loop_downcounter;
|
||||
u8 address_increment;
|
||||
u8 previous_aL;
|
||||
};
|
||||
|
||||
template <bool Debug>
|
||||
static void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug>& debug_data,
|
||||
unsigned offset) {
|
||||
// TODO: Is there a maximal size for this?
|
||||
boost::container::static_vector<CallStackElement, 16> call_stack;
|
||||
u32 program_counter = offset;
|
||||
unsigned entry_point) {
|
||||
boost::circular_buffer<IfStackElement> if_stack(8);
|
||||
boost::circular_buffer<CallStackElement> call_stack(4);
|
||||
boost::circular_buffer<LoopStackElement> loop_stack(4);
|
||||
u32 program_counter = entry_point;
|
||||
|
||||
state.conditional_code[0] = false;
|
||||
state.conditional_code[1] = false;
|
||||
|
||||
auto call = [&program_counter, &call_stack](u32 offset, u32 num_instructions, u32 return_offset,
|
||||
u8 repeat_count, u8 loop_increment) {
|
||||
// -1 to make sure when incrementing the PC we end up at the correct offset
|
||||
program_counter = offset - 1;
|
||||
ASSERT(call_stack.size() < call_stack.capacity());
|
||||
call_stack.push_back(
|
||||
{offset + num_instructions, return_offset, repeat_count, loop_increment, offset});
|
||||
const auto do_if = [&](Instruction instr, bool condition) {
|
||||
if (condition) {
|
||||
if_stack.push_back({
|
||||
.else_address = instr.flow_control.dest_offset,
|
||||
.end_address = instr.flow_control.dest_offset + instr.flow_control.num_instructions,
|
||||
});
|
||||
} else {
|
||||
program_counter = instr.flow_control.dest_offset - 1;
|
||||
}
|
||||
};
|
||||
|
||||
const auto do_call = [&](Instruction instr) {
|
||||
call_stack.push_back({
|
||||
.end_address = instr.flow_control.dest_offset + instr.flow_control.num_instructions,
|
||||
.return_address = program_counter + 1,
|
||||
});
|
||||
program_counter = instr.flow_control.dest_offset - 1;
|
||||
};
|
||||
|
||||
const auto do_loop = [&](Instruction instr, const Common::Vec4<u8>& loop_param) {
|
||||
const u8 previous_aL = static_cast<u8>(state.address_registers[2]);
|
||||
loop_stack.push_back({
|
||||
.entry_address = program_counter + 1,
|
||||
.end_address = instr.flow_control.dest_offset + 1,
|
||||
.loop_downcounter = loop_param.x,
|
||||
.address_increment = loop_param.z,
|
||||
.previous_aL = previous_aL,
|
||||
});
|
||||
state.address_registers[2] = loop_param.y;
|
||||
};
|
||||
|
||||
auto evaluate_condition = [&state](Instruction::FlowControlType flow_control) {
|
||||
@ -82,25 +115,11 @@ static void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData
|
||||
// Placeholder for invalid inputs
|
||||
static f24 dummy_vec4_float24[4];
|
||||
|
||||
unsigned iteration = 0;
|
||||
bool exit_loop = false;
|
||||
while (!exit_loop) {
|
||||
if (!call_stack.empty()) {
|
||||
auto& top = call_stack.back();
|
||||
if (program_counter == top.final_address) {
|
||||
state.address_registers[2] += top.loop_increment;
|
||||
|
||||
if (top.repeat_counter-- == 0) {
|
||||
program_counter = top.return_address;
|
||||
call_stack.pop_back();
|
||||
} else {
|
||||
program_counter = top.loop_address;
|
||||
}
|
||||
|
||||
// TODO: Is "trying again" accurate to hardware?
|
||||
continue;
|
||||
}
|
||||
}
|
||||
u32 iteration = 0;
|
||||
bool should_stop = false;
|
||||
while (!should_stop) {
|
||||
bool is_break = false;
|
||||
const u32 old_program_counter = program_counter;
|
||||
|
||||
const Instruction instr = {program_code[program_counter]};
|
||||
const SwizzlePattern swizzle = {swizzle_data[instr.common.operand_desc_id]};
|
||||
@ -538,7 +557,7 @@ static void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData
|
||||
// Handle each instruction on its own
|
||||
switch (instr.opcode.Value()) {
|
||||
case OpCode::Id::END:
|
||||
exit_loop = true;
|
||||
should_stop = true;
|
||||
break;
|
||||
|
||||
case OpCode::Id::JMPC:
|
||||
@ -559,72 +578,68 @@ static void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData
|
||||
break;
|
||||
|
||||
case OpCode::Id::CALL:
|
||||
call(instr.flow_control.dest_offset, instr.flow_control.num_instructions,
|
||||
program_counter + 1, 0, 0);
|
||||
do_call(instr);
|
||||
break;
|
||||
|
||||
case OpCode::Id::CALLU:
|
||||
Record<DebugDataRecord::COND_BOOL_IN>(
|
||||
debug_data, iteration, uniforms.b[instr.flow_control.bool_uniform_id]);
|
||||
if (uniforms.b[instr.flow_control.bool_uniform_id]) {
|
||||
call(instr.flow_control.dest_offset, instr.flow_control.num_instructions,
|
||||
program_counter + 1, 0, 0);
|
||||
do_call(instr);
|
||||
}
|
||||
break;
|
||||
|
||||
case OpCode::Id::CALLC:
|
||||
Record<DebugDataRecord::COND_CMP_IN>(debug_data, iteration, state.conditional_code);
|
||||
if (evaluate_condition(instr.flow_control)) {
|
||||
call(instr.flow_control.dest_offset, instr.flow_control.num_instructions,
|
||||
program_counter + 1, 0, 0);
|
||||
do_call(instr);
|
||||
}
|
||||
break;
|
||||
|
||||
case OpCode::Id::NOP:
|
||||
break;
|
||||
|
||||
case OpCode::Id::IFU:
|
||||
case OpCode::Id::IFU: {
|
||||
Record<DebugDataRecord::COND_BOOL_IN>(
|
||||
debug_data, iteration, uniforms.b[instr.flow_control.bool_uniform_id]);
|
||||
if (uniforms.b[instr.flow_control.bool_uniform_id]) {
|
||||
call(program_counter + 1, instr.flow_control.dest_offset - program_counter - 1,
|
||||
instr.flow_control.dest_offset + instr.flow_control.num_instructions, 0,
|
||||
0);
|
||||
} else {
|
||||
call(instr.flow_control.dest_offset, instr.flow_control.num_instructions,
|
||||
instr.flow_control.dest_offset + instr.flow_control.num_instructions, 0,
|
||||
0);
|
||||
}
|
||||
|
||||
const bool cond = uniforms.b[instr.flow_control.bool_uniform_id];
|
||||
do_if(instr, cond);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::IFC: {
|
||||
// TODO: Do we need to consider swizzlers here?
|
||||
|
||||
Record<DebugDataRecord::COND_CMP_IN>(debug_data, iteration, state.conditional_code);
|
||||
if (evaluate_condition(instr.flow_control)) {
|
||||
call(program_counter + 1, instr.flow_control.dest_offset - program_counter - 1,
|
||||
instr.flow_control.dest_offset + instr.flow_control.num_instructions, 0,
|
||||
0);
|
||||
} else {
|
||||
call(instr.flow_control.dest_offset, instr.flow_control.num_instructions,
|
||||
instr.flow_control.dest_offset + instr.flow_control.num_instructions, 0,
|
||||
0);
|
||||
}
|
||||
|
||||
const bool cond = evaluate_condition(instr.flow_control);
|
||||
do_if(instr, cond);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::LOOP: {
|
||||
Common::Vec4<u8> loop_param(uniforms.i[instr.flow_control.int_uniform_id].x,
|
||||
uniforms.i[instr.flow_control.int_uniform_id].y,
|
||||
uniforms.i[instr.flow_control.int_uniform_id].z,
|
||||
uniforms.i[instr.flow_control.int_uniform_id].w);
|
||||
const Common::Vec4<u8>& loop_param = uniforms.i[instr.flow_control.int_uniform_id];
|
||||
state.address_registers[2] = loop_param.y;
|
||||
|
||||
Record<DebugDataRecord::LOOP_INT_IN>(debug_data, iteration, loop_param);
|
||||
call(program_counter + 1, instr.flow_control.dest_offset - program_counter,
|
||||
instr.flow_control.dest_offset + 1, loop_param.x, loop_param.z);
|
||||
do_loop(instr, loop_param);
|
||||
Record<DebugDataRecord::ADDR_REG_OUT>(debug_data, iteration,
|
||||
state.address_registers);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::BREAK: {
|
||||
is_break = true;
|
||||
Record<DebugDataRecord::ADDR_REG_OUT>(debug_data, iteration,
|
||||
state.address_registers);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::BREAKC: {
|
||||
Record<DebugDataRecord::COND_CMP_IN>(debug_data, iteration, state.conditional_code);
|
||||
if (evaluate_condition(instr.flow_control)) {
|
||||
is_break = true;
|
||||
}
|
||||
Record<DebugDataRecord::ADDR_REG_OUT>(debug_data, iteration,
|
||||
state.address_registers);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -657,6 +672,47 @@ static void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData
|
||||
|
||||
++program_counter;
|
||||
++iteration;
|
||||
|
||||
// Stacks are checked in the order CALL -> IF -> LOOP. The CALL stack
|
||||
// can be popped multiple times per instruction. A JMP at the end of a
|
||||
// scope is never taken, this is why we compare against
|
||||
// old_program_counter + 1 here.
|
||||
u32 next_program_counter = old_program_counter + 1;
|
||||
for (u32 i = 0; i < 4; i++) {
|
||||
if (call_stack.empty() || call_stack.back().end_address != next_program_counter)
|
||||
break;
|
||||
// Hardware bug: when popping four CALL scopes at once, the last
|
||||
// one doesn't update the program counter
|
||||
if (i < 3) {
|
||||
program_counter = call_stack.back().return_address;
|
||||
next_program_counter = program_counter;
|
||||
}
|
||||
call_stack.pop_back();
|
||||
}
|
||||
|
||||
// The other two stacks can only pop one entry per instruction. They
|
||||
// are checked against the original program counter before any CALL
|
||||
// scopes were closed and they overwrite any previous program counter
|
||||
// updates.
|
||||
if (!if_stack.empty() && if_stack.back().else_address == old_program_counter + 1) {
|
||||
program_counter = if_stack.back().end_address;
|
||||
if_stack.pop_back();
|
||||
}
|
||||
|
||||
if (!loop_stack.empty() &&
|
||||
(loop_stack.back().end_address == old_program_counter + 1 || is_break)) {
|
||||
auto& loop = loop_stack.back();
|
||||
state.address_registers[2] += loop.address_increment;
|
||||
if (!is_break && loop.loop_downcounter--) {
|
||||
program_counter = loop.entry_address;
|
||||
} else {
|
||||
program_counter = loop.end_address;
|
||||
// Only restore previous value if there is a surrounding LOOP scope.
|
||||
if (loop_stack.size() > 1)
|
||||
state.address_registers[2] = loop.previous_aL;
|
||||
loop_stack.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,6 +67,7 @@ layout ({}std140) uniform shader_data {{
|
||||
vec4 const_color[NUM_TEV_STAGES];
|
||||
vec4 tev_combiner_buffer_color;
|
||||
vec3 tex_lod_bias;
|
||||
vec4 tex_border_color[3];
|
||||
vec4 clip_coef;
|
||||
}};
|
||||
)";
|
||||
|
@ -64,10 +64,11 @@ struct UniformData {
|
||||
alignas(16) Common::Vec4f const_color[6]; // A vec4 color for each of the six tev stages
|
||||
alignas(16) Common::Vec4f tev_combiner_buffer_color;
|
||||
alignas(16) Common::Vec3f tex_lod_bias;
|
||||
alignas(16) Common::Vec4f tex_border_color[3];
|
||||
alignas(16) Common::Vec4f clip_coef;
|
||||
};
|
||||
|
||||
static_assert(sizeof(UniformData) == 0x500,
|
||||
static_assert(sizeof(UniformData) == 0x530,
|
||||
"The size of the UniformData does not match the structure in the shader");
|
||||
static_assert(sizeof(UniformData) < 16384,
|
||||
"UniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
@ -1,8 +1,6 @@
|
||||
add_library(web_service STATIC
|
||||
announce_room_json.cpp
|
||||
announce_room_json.h
|
||||
nus_download.cpp
|
||||
nus_download.h
|
||||
precompiled_headers.h
|
||||
telemetry_json.cpp
|
||||
telemetry_json.h
|
||||
@ -16,7 +14,7 @@ add_library(web_service STATIC
|
||||
|
||||
create_target_directory_groups(web_service)
|
||||
|
||||
target_compile_definitions(web_service PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
target_compile_definitions(web_service PUBLIC -DENABLE_WEB_SERVICE)
|
||||
target_link_libraries(web_service PRIVATE citra_common network json-headers httplib cpp-jwt)
|
||||
target_link_libraries(web_service PUBLIC ${OPENSSL_LIBS})
|
||||
set_target_properties(web_service PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO})
|
||||
|
Loading…
Reference in New Issue
Block a user