Merge master

This commit is contained in:
PabloMK7 2023-08-23 19:58:18 +02:00
commit 9854cf63dc
67 changed files with 3130 additions and 1343 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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/")

View File

@ -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}")

View File

@ -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"

View File

@ -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;
}

View File

@ -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

View File

@ -12,27 +12,29 @@ include(DownloadExternals)
include(ExternalProject)
# Boost
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 "")
add_library(boost INTERFACE)
target_include_directories(boost SYSTEM INTERFACE ${Boost_INCLUDE_DIR})
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 "")
add_library(boost INTERFACE)
target_include_directories(boost SYSTEM INTERFACE ${Boost_INCLUDE_DIR})
# Boost::serialization
file(GLOB boost_serialization_SRC "${CMAKE_SOURCE_DIR}/externals/boost/libs/serialization/src/*.cpp")
add_library(boost_serialization STATIC ${boost_serialization_SRC})
target_link_libraries(boost_serialization PUBLIC boost)
# Boost::iostreams
add_library(
boost_iostreams
STATIC
${CMAKE_SOURCE_DIR}/externals/boost/libs/iostreams/src/file_descriptor.cpp
${CMAKE_SOURCE_DIR}/externals/boost/libs/iostreams/src/mapped_file.cpp
)
target_link_libraries(boost_iostreams PUBLIC boost)
# Boost::serialization
file(GLOB boost_serialization_SRC "${CMAKE_SOURCE_DIR}/externals/boost/libs/serialization/src/*.cpp")
add_library(boost_serialization STATIC ${boost_serialization_SRC})
target_link_libraries(boost_serialization PUBLIC boost)
# Boost::iostreams
add_library(
boost_iostreams
STATIC
${CMAKE_SOURCE_DIR}/externals/boost/libs/iostreams/src/file_descriptor.cpp
${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,37 +174,39 @@ endif()
add_library(json-headers INTERFACE)
target_include_directories(json-headers INTERFACE ./json)
# OpenSSL
if (USE_SYSTEM_OPENSSL)
find_package(OpenSSL 1.1)
if (OPENSSL_FOUND)
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
endif()
endif()
if (NOT OPENSSL_FOUND)
# LibreSSL
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
set(OPENSSLDIR "/etc/ssl/")
add_subdirectory(libressl EXCLUDE_FROM_ALL)
target_include_directories(ssl INTERFACE ./libressl/include)
target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
get_directory_property(OPENSSL_LIBRARIES
DIRECTORY libressl
DEFINITION OPENSSL_LIBS)
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)
if (USE_SYSTEM_OPENSSL)
find_package(OpenSSL 1.1)
if (OPENSSL_FOUND)
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
endif()
endif()
if (NOT OPENSSL_FOUND)
# LibreSSL
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
set(OPENSSLDIR "/etc/ssl/")
add_subdirectory(libressl EXCLUDE_FROM_ALL)
target_include_directories(ssl INTERFACE ./libressl/include)
target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
get_directory_property(OPENSSL_LIBRARIES
DIRECTORY libressl
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})
# cpp-jwt
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

@ -1 +1 @@
Subproject commit 700ae2eff3134792f09cea2b051666688b1d5b97
Subproject commit 3c27c785ad0f8a742af02e620dc225673f3a12d8

View File

@ -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)

View File

@ -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)

View File

@ -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();

File diff suppressed because it is too large Load Diff

View File

@ -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
}

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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);

View File

@ -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;

View File

@ -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")
.arg(savestate.slot)
.arg(QDateTime::fromSecsSinceEpoch(savestate.time)
.toString(QStringLiteral("yyyy-MM-dd hh:mm:ss")));
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")))
.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);

View File

@ -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());

View File

@ -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)
endif()
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)

View File

@ -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();

View File

@ -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;

View File

@ -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>();

View File

@ -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:

View File

@ -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"},

View File

@ -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"},

View File

@ -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) {

View File

@ -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);

View File

@ -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();

View File

@ -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 {

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -15,18 +15,21 @@
#include "core/core.h"
#include "core/movie.h"
#include "core/savestate.h"
#include "core/savestate_data.h"
#include "network/network.h"
namespace Core {
#pragma pack(push, 1)
struct CSTHeader {
std::array<u8, 4> filetype; /// Unique Identifier to check the file type (always "CST"0x1B)
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, 4> filetype; /// Unique Identifier to check the file type (always "CST"0x1B)
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 {
LOG_WARNING(Core, "Save state file {} created from a different revision {}", path,
revision);
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()) {

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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()

View File

@ -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)

View File

@ -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 =

View File

@ -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}
)

View 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(),

View File

@ -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();

View File

@ -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()) {
const SurfaceId surface_id = it->first;
sentenced.erase(it);
return surface_id;
if (it == sentenced.end()) {
return slot_surfaces.insert(runtime, params);
}
return slot_surfaces.insert(runtime, params);
const SurfaceId surface_id = it->first;
sentenced.erase(it);
return surface_id;
}();
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>

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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};

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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,
};
/**

View File

@ -109,76 +109,100 @@ PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs, bool use_normal
<< 4;
// Fragment lighting
state.lighting.enable = !regs.lighting.disable;
state.lighting.src_num = regs.lighting.max_light_index + 1;
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) {
unsigned num = regs.lighting.light_enable.GetNum(light_index);
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].dist_atten_enable =
!regs.lighting.IsDistAttenDisabled(num);
state.lighting.light[light_index].spot_atten_enable =
!regs.lighting.IsSpotAttenDisabled(num);
state.lighting.light[light_index].shadow_enable = !regs.lighting.IsShadowDisabled(num);
for (unsigned light_index = 0; light_index < state.lighting.src_num; ++light_index) {
unsigned num = regs.lighting.light_enable.GetNum(light_index);
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].dist_atten_enable =
!regs.lighting.IsDistAttenDisabled(num);
state.lighting.light[light_index].spot_atten_enable =
!regs.lighting.IsSpotAttenDisabled(num);
state.lighting.light[light_index].shadow_enable = !regs.lighting.IsShadowDisabled(num);
}
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_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);
}
// this is a dummy field due to lack of the corresponding register
state.lighting.lut_sp.enable = true;
state.lighting.lut_sp.abs_input = regs.lighting.abs_lut_input.disable_sp == 0;
state.lighting.lut_sp.type = regs.lighting.lut_input.sp.Value();
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_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_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_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.config = regs.lighting.config0.config;
state.lighting.enable_primary_alpha = regs.lighting.config0.enable_primary_alpha;
state.lighting.enable_secondary_alpha = regs.lighting.config0.enable_secondary_alpha;
state.lighting.bump_mode = regs.lighting.config0.bump_mode;
state.lighting.bump_selector = regs.lighting.config0.bump_selector;
state.lighting.bump_renorm = regs.lighting.config0.disable_bump_renorm == 0;
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.lighting.lut_d0.enable = regs.lighting.config1.disable_lut_d0 == 0;
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_d1.enable = regs.lighting.config1.disable_lut_d1 == 0;
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);
// this is a dummy field due to lack of the corresponding register
state.lighting.lut_sp.enable = true;
state.lighting.lut_sp.abs_input = regs.lighting.abs_lut_input.disable_sp == 0;
state.lighting.lut_sp.type = regs.lighting.lut_input.sp.Value();
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;
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_rr.enable = regs.lighting.config1.disable_lut_rr == 0;
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_rg.enable = regs.lighting.config1.disable_lut_rg == 0;
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_rb.enable = regs.lighting.config1.disable_lut_rb == 0;
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.config = regs.lighting.config0.config;
state.lighting.enable_primary_alpha = regs.lighting.config0.enable_primary_alpha;
state.lighting.enable_secondary_alpha = regs.lighting.config0.enable_secondary_alpha;
state.lighting.bump_mode = regs.lighting.config0.bump_mode;
state.lighting.bump_selector = regs.lighting.config0.bump_selector;
state.lighting.bump_renorm = regs.lighting.config0.disable_bump_renorm == 0;
state.lighting.clamp_highlights = regs.lighting.config0.clamp_highlights != 0;
state.lighting.enable_shadow = regs.lighting.config0.enable_shadow != 0;
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) {
state.proctex.coord = regs.texturing.main_config.texture3_coordinates;
@ -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);

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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,80 +112,101 @@ 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);
state.lighting.src_num.Assign(regs.lighting.max_light_index + 1);
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) {
const u32 num = regs.lighting.light_enable.GetNum(light_index);
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].geometric_factor_0.Assign(
light.config.geometric_factor_0 != 0);
state.lighting.light[light_index].geometric_factor_1.Assign(
light.config.geometric_factor_1 != 0);
state.lighting.light[light_index].dist_atten_enable.Assign(
!regs.lighting.IsDistAttenDisabled(num));
state.lighting.light[light_index].spot_atten_enable.Assign(
!regs.lighting.IsSpotAttenDisabled(num));
state.lighting.light[light_index].shadow_enable.Assign(
!regs.lighting.IsShadowDisabled(num));
for (u32 light_index = 0; light_index < state.lighting.src_num; ++light_index) {
const u32 num = regs.lighting.light_enable.GetNum(light_index);
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].geometric_factor_0.Assign(
light.config.geometric_factor_0 != 0);
state.lighting.light[light_index].geometric_factor_1.Assign(
light.config.geometric_factor_1 != 0);
state.lighting.light[light_index].dist_atten_enable.Assign(
!regs.lighting.IsDistAttenDisabled(num));
state.lighting.light[light_index].spot_atten_enable.Assign(
!regs.lighting.IsSpotAttenDisabled(num));
state.lighting.light[light_index].shadow_enable.Assign(
!regs.lighting.IsShadowDisabled(num));
}
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_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);
}
// this is a dummy field due to lack of the corresponding register
state.lighting.lut_sp.enable.Assign(1);
state.lighting.lut_sp.abs_input.Assign(regs.lighting.abs_lut_input.disable_sp == 0);
state.lighting.lut_sp.type.Assign(regs.lighting.lut_input.sp.Value());
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_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_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_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.config.Assign(regs.lighting.config0.config);
state.lighting.enable_primary_alpha.Assign(regs.lighting.config0.enable_primary_alpha);
state.lighting.enable_secondary_alpha.Assign(regs.lighting.config0.enable_secondary_alpha);
state.lighting.bump_mode.Assign(regs.lighting.config0.bump_mode);
state.lighting.bump_selector.Assign(regs.lighting.config0.bump_selector);
state.lighting.bump_renorm.Assign(regs.lighting.config0.disable_bump_renorm == 0);
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.lighting.lut_d0.enable.Assign(regs.lighting.config1.disable_lut_d0 == 0);
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_d1.enable.Assign(regs.lighting.config1.disable_lut_d1 == 0);
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);
// this is a dummy field due to lack of the corresponding register
state.lighting.lut_sp.enable.Assign(1);
state.lighting.lut_sp.abs_input.Assign(regs.lighting.abs_lut_input.disable_sp == 0);
state.lighting.lut_sp.type.Assign(regs.lighting.lut_input.sp.Value());
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);
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_rr.enable.Assign(regs.lighting.config1.disable_lut_rr == 0);
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_rg.enable.Assign(regs.lighting.config1.disable_lut_rg == 0);
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_rb.enable.Assign(regs.lighting.config1.disable_lut_rb == 0);
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.config.Assign(regs.lighting.config0.config);
state.lighting.enable_primary_alpha.Assign(regs.lighting.config0.enable_primary_alpha);
state.lighting.enable_secondary_alpha.Assign(regs.lighting.config0.enable_secondary_alpha);
state.lighting.bump_mode.Assign(regs.lighting.config0.bump_mode);
state.lighting.bump_selector.Assign(regs.lighting.config0.bump_selector);
state.lighting.bump_renorm.Assign(regs.lighting.config0.disable_bump_renorm == 0);
state.lighting.clamp_highlights.Assign(regs.lighting.config0.clamp_highlights != 0);
state.lighting.enable_shadow.Assign(regs.lighting.config0.enable_shadow != 0);
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) {
state.proctex.coord.Assign(regs.texturing.main_config.texture3_coordinates);
@ -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;
@ -1409,10 +1392,92 @@ vec4 shadowTextureCube(vec2 uv, float w) {
CompareShadow(pixels.w, z));
return vec4(mix2(s, f));
}
)";
)";
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

View File

@ -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 {

View File

@ -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);

View File

@ -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{};

View File

@ -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();
}
}
}
}

View File

@ -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;
}};
)";

View File

@ -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");

View File

@ -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})