mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-06 02:33:44 -04:00
Merge branch 'master' into master
This commit is contained in:
commit
e6463438bb
36 changed files with 773 additions and 48 deletions
|
|
@ -129,7 +129,8 @@ CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support
|
|||
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_SOFTWARE_RENDERER "Enables the software renderer" ON "NOT ANDROID" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENABLE_OPENGL} "NOT APPLE" OFF)
|
||||
option(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
|
||||
# NetBSD doesn't support Vulkan yet, remove this check when it does.
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_VULKAN "Enables the Vulkan renderer" ON "NOT (BSD MATCHES \"NetBSD\")" OFF)
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
|
||||
|
|
|
|||
11
CMakeModules/DisablePaxMprotect.cmake
Normal file
11
CMakeModules/DisablePaxMprotect.cmake
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
function(disable_pax_mprotect target)
|
||||
if (BSD STREQUAL "NetBSD")
|
||||
add_custom_command(TARGET ${target} POST_BUILD
|
||||
COMMAND paxctl +m "$<TARGET_FILE:${target}>"
|
||||
COMMENT "Disabling PaX MPROTECT restrictions for '${target}'"
|
||||
VERBATIM
|
||||
)
|
||||
else()
|
||||
message(FATAL_ERROR "disable_pax_mprotect only applies on NetBSD.")
|
||||
endif()
|
||||
endfunction()
|
||||
10
externals/CMakeLists.txt
vendored
10
externals/CMakeLists.txt
vendored
|
|
@ -60,6 +60,7 @@ if (ENABLE_TESTS)
|
|||
add_subdirectory(catch2)
|
||||
endif()
|
||||
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
||||
include(Catch)
|
||||
endif()
|
||||
|
||||
# Crypto++
|
||||
|
|
@ -504,6 +505,15 @@ if (ENABLE_VULKAN)
|
|||
else()
|
||||
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
|
||||
target_disable_warnings(vulkan-headers)
|
||||
if (BSD STREQUAL "NetBSD")
|
||||
# There may be a better way to do this with
|
||||
# find_package(X11), but I couldn't get
|
||||
# CMake to do it, so we're depending on
|
||||
# the x11-links package and assuming the
|
||||
# prefix location. -OS
|
||||
target_include_directories(vulkan-headers INTERFACE
|
||||
/usr/pkg/share/x11-links/include)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# adrenotools
|
||||
|
|
|
|||
|
|
@ -504,8 +504,9 @@ object NativeLibrary {
|
|||
const val ErrorSystemFiles = 8
|
||||
const val ErrorSavestate = 9
|
||||
const val ErrorArticDisconnected = 10
|
||||
const val ShutdownRequested = 11
|
||||
const val ErrorUnknown = 12
|
||||
const val ErrorN3DSApplication = 11
|
||||
const val ShutdownRequested = 12
|
||||
const val ErrorUnknown = 13
|
||||
|
||||
fun newInstance(resultCode: Int): EmulationErrorDialogFragment {
|
||||
val args = Bundle()
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ enum class IntSetting(
|
|||
CAMERA_INNER_FLIP(SettingKeys.camera_inner_flip(), Settings.SECTION_CAMERA, 0),
|
||||
CAMERA_OUTER_LEFT_FLIP(SettingKeys.camera_outer_left_flip(), Settings.SECTION_CAMERA, 0),
|
||||
CAMERA_OUTER_RIGHT_FLIP(SettingKeys.camera_outer_right_flip(), Settings.SECTION_CAMERA, 0),
|
||||
GRAPHICS_API(SettingKeys.graphics_api(), Settings.SECTION_RENDERER, 1),
|
||||
GRAPHICS_API(SettingKeys.graphics_api(), Settings.SECTION_RENDERER, 2),
|
||||
RESOLUTION_FACTOR(SettingKeys.resolution_factor(), Settings.SECTION_RENDERER, 1),
|
||||
STEREOSCOPIC_3D_MODE(SettingKeys.render_3d(), Settings.SECTION_RENDERER, 2),
|
||||
STEREOSCOPIC_3D_DEPTH(SettingKeys.factor_3d(), Settings.SECTION_RENDERER, 0),
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ static const char* android_config_default_file_content = (BOOST_HANA_STRING(R"(
|
|||
|
||||
[Renderer]
|
||||
# Whether to render using OpenGL
|
||||
# 1: OpenGL ES (default), 2: Vulkan
|
||||
# 1: OpenGL ES, 2: Vulkan (default)
|
||||
)") DECLARE_KEY(graphics_api) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether to compile shaders on multiple worker threads (Vulkan only)
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
android:id="@+id/coordinator_about"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:background="?attr/colorSurface">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_about"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar_system_files"
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@ add_library(azahar_libretro SHARED
|
|||
|
||||
create_target_directory_groups(azahar_libretro)
|
||||
|
||||
if (BSD STREQUAL "NetBSD")
|
||||
include(DisablePaxMprotect)
|
||||
disable_pax_mprotect(azahar_libretro)
|
||||
endif()
|
||||
|
||||
target_link_libraries(citra_common PRIVATE libretro)
|
||||
target_link_libraries(citra_core PRIVATE libretro)
|
||||
target_link_libraries(video_core PRIVATE libretro)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,14 @@ bool GetMicrophoneInterface(struct retro_microphone_interface* mic_interface) {
|
|||
}
|
||||
|
||||
Settings::GraphicsAPI GetPreferredRenderer() {
|
||||
// try and maintain the current driver
|
||||
// On Android, we really want to default to Vulkan if we can, so we'll ignore the frontend's
|
||||
// recommendation if possible...
|
||||
#if defined(ANDROID) && defined(ENABLE_VULKAN)
|
||||
return Settings::GraphicsAPI::Vulkan;
|
||||
#endif
|
||||
// ...Otherwise negotiate with the RetroArch frontend as usual
|
||||
|
||||
// Attempt to use the renderer recommended by the frontend if possible
|
||||
retro_hw_context_type context_type = RETRO_HW_CONTEXT_OPENGL;
|
||||
environ_cb(RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER, &context_type);
|
||||
switch (context_type) {
|
||||
|
|
@ -80,7 +87,7 @@ Settings::GraphicsAPI GetPreferredRenderer() {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
// we can't maintain the current driver, need to switch
|
||||
// We can't get a recommendation from the frontend, so fall back to whatever's available
|
||||
#if defined(ENABLE_VULKAN)
|
||||
return Settings::GraphicsAPI::Vulkan;
|
||||
#elif defined(ENABLE_OPENGL)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@ add_executable(citra_meta
|
|||
|
||||
set_target_properties(citra_meta PROPERTIES OUTPUT_NAME "azahar")
|
||||
|
||||
if (BSD STREQUAL "NetBSD")
|
||||
include(DisablePaxMprotect)
|
||||
disable_pax_mprotect(citra_meta)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
set(DIST_DIR "../../dist/apple")
|
||||
set(APPLE_RESOURCES
|
||||
|
|
|
|||
|
|
@ -174,6 +174,8 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL
|
|||
multiplayer/state.cpp
|
||||
multiplayer/state.h
|
||||
multiplayer/validation.h
|
||||
notification_led.cpp
|
||||
notification_led.h
|
||||
precompiled_headers.h
|
||||
qt_image_interface.cpp
|
||||
qt_image_interface.h
|
||||
|
|
|
|||
|
|
@ -352,6 +352,8 @@ GMainWindow::GMainWindow(Core::System& system_)
|
|||
Camera::RegisterFactory("image", std::make_unique<Camera::StillImageCameraFactory>());
|
||||
Camera::RegisterFactory("qt", std::make_unique<Camera::QtMultimediaCameraFactory>(qt_cameras));
|
||||
|
||||
system.RegisterInfoLEDColorChanged([this]() { emit InfoLEDColorChanged(); });
|
||||
|
||||
LoadTranslation();
|
||||
|
||||
Pica::g_debug_context = Pica::DebugContext::Construct();
|
||||
|
|
@ -606,6 +608,20 @@ void GMainWindow::InitializeWidgets() {
|
|||
statusBar()->addPermanentWidget(multiplayer_state->GetStatusText());
|
||||
statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon());
|
||||
|
||||
QFrame* sep = new QFrame(this);
|
||||
sep->setFrameShape(QFrame::VLine);
|
||||
sep->setFrameShadow(QFrame::Sunken);
|
||||
sep->setFixedHeight(16);
|
||||
statusBar()->addPermanentWidget(sep);
|
||||
|
||||
notification_led = new LedWidget();
|
||||
notification_led->setToolTip(tr("Emulated notification LED"));
|
||||
statusBar()->addPermanentWidget(notification_led);
|
||||
connect(this, &GMainWindow::InfoLEDColorChanged, this, [this] {
|
||||
auto led_color = system.GetInfoLEDColor();
|
||||
notification_led->setColor(QColor(led_color.r(), led_color.g(), led_color.b()));
|
||||
});
|
||||
|
||||
statusBar()->setVisible(true);
|
||||
|
||||
// Removes an ugly inner border from the status bar widgets under Linux
|
||||
|
|
@ -1600,6 +1616,7 @@ void GMainWindow::ShutdownGame() {
|
|||
emu_speed_label->setVisible(false);
|
||||
game_fps_label->setVisible(false);
|
||||
emu_frametime_label->setVisible(false);
|
||||
notification_led->setColor(QColor(0, 0, 0));
|
||||
|
||||
UpdateSaveStates();
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include <QTranslator>
|
||||
#include "citra_qt/compatibility_list.h"
|
||||
#include "citra_qt/hotkeys.h"
|
||||
#include "citra_qt/notification_led.h"
|
||||
#include "citra_qt/user_data_migration.h"
|
||||
#include "core/core.h"
|
||||
#include "core/savestate.h"
|
||||
|
|
@ -147,6 +148,7 @@ signals:
|
|||
void CIAInstallReport(Service::AM::InstallStatus status, QString filepath);
|
||||
void CompressFinished(bool is_compress, bool success);
|
||||
void CIAInstallFinished();
|
||||
void InfoLEDColorChanged();
|
||||
// Signal that tells widgets to update icons to use the current theme
|
||||
void UpdateThemedIcons();
|
||||
|
||||
|
|
@ -364,6 +366,8 @@ private:
|
|||
|
||||
MultiplayerState* multiplayer_state = nullptr;
|
||||
|
||||
LedWidget* notification_led = nullptr;
|
||||
|
||||
// Created before `config` to ensure that emu data directory
|
||||
// isn't created before the check is performed
|
||||
UserDataMigrator user_data_migrator;
|
||||
|
|
|
|||
87
src/citra_qt/notification_led.cpp
Normal file
87
src/citra_qt/notification_led.cpp
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <QPainter>
|
||||
#include <QRadialGradient>
|
||||
#include "citra_qt/notification_led.h"
|
||||
|
||||
LedWidget::LedWidget(QWidget* parent) : QWidget(parent), color(0, 0, 0) {
|
||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
}
|
||||
|
||||
QSize LedWidget::sizeHint() const {
|
||||
return QSize(16, 16);
|
||||
}
|
||||
|
||||
QSize LedWidget::minimumSizeHint() const {
|
||||
return QSize(16, 16);
|
||||
}
|
||||
|
||||
void LedWidget::setColor(const QColor& _color) {
|
||||
if (color == _color)
|
||||
return;
|
||||
|
||||
color = _color;
|
||||
update();
|
||||
}
|
||||
|
||||
QColor LedWidget::lerpColor(const QColor& a, const QColor& b, float t) {
|
||||
t = std::clamp(t, 0.0f, 1.0f);
|
||||
|
||||
return QColor(int(a.red() + (b.red() - a.red()) * t),
|
||||
int(a.green() + (b.green() - a.green()) * t),
|
||||
int(a.blue() + (b.blue() - a.blue()) * t));
|
||||
}
|
||||
|
||||
QColor LedWidget::blendLedColor(int r, int g, int b) const {
|
||||
// Default "off" color
|
||||
const QColor off_color(64, 64, 64);
|
||||
|
||||
// If completely off, just show gray and skip further calculations
|
||||
if (r == 0 && g == 0 && b == 0)
|
||||
return off_color;
|
||||
|
||||
// Normalize lit color so hue stays pure
|
||||
int max_c = std::max({r, g, b});
|
||||
QColor lit_color((r * 255) / max_c, (g * 255) / max_c, (b * 255) / max_c);
|
||||
|
||||
// Convert PWM duty to perceived brightness.
|
||||
// This gives better results as LED RGB values
|
||||
// are not linear.
|
||||
constexpr float gamma = 2.4f;
|
||||
float pwm = max_c / 255.0;
|
||||
float t = std::powf(pwm, 1.f / gamma);
|
||||
|
||||
return lerpColor(off_color, lit_color, t * 0.8f);
|
||||
}
|
||||
|
||||
void LedWidget::paintEvent(QPaintEvent*) {
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
QRectF rect = this->rect().adjusted(0, 2, 0, -2);
|
||||
|
||||
qreal size = std::min(rect.width(), rect.height());
|
||||
QRectF circle((rect.center().x() - size / 2.f) - 2, rect.center().y() - size / 2.f, size, size);
|
||||
|
||||
QPointF center = circle.center();
|
||||
qreal radius = circle.width() / 2.f;
|
||||
|
||||
QColor base = blendLedColor(color.red(), color.green(), color.blue());
|
||||
|
||||
QRadialGradient g(center, radius);
|
||||
|
||||
QColor inner = base.lighter(135);
|
||||
QColor outer = base.darker(125);
|
||||
|
||||
g.setColorAt(0.f, inner);
|
||||
g.setColorAt(0.7f, base);
|
||||
g.setColorAt(1.f, outer);
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(g);
|
||||
p.drawEllipse(circle);
|
||||
}
|
||||
30
src/citra_qt/notification_led.h
Normal file
30
src/citra_qt/notification_led.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QColor>
|
||||
#include <QWidget>
|
||||
|
||||
class LedWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LedWidget(QWidget* parent = nullptr);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
void setColor(const QColor& color);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
private:
|
||||
QColor blendLedColor(int r, int g, int b) const;
|
||||
static QColor lerpColor(const QColor& a, const QColor& b, float t);
|
||||
|
||||
private:
|
||||
QColor color;
|
||||
};
|
||||
|
|
@ -4,6 +4,11 @@ add_executable(citra_room_standalone
|
|||
|
||||
set_target_properties(citra_room_standalone PROPERTIES OUTPUT_NAME "azahar-room")
|
||||
|
||||
if (BSD STREQUAL "NetBSD")
|
||||
include(DisablePaxMprotect)
|
||||
disable_pax_mprotect(citra_room_standalone)
|
||||
endif()
|
||||
|
||||
target_link_libraries(citra_room_standalone PRIVATE citra_room)
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
|
|
|
|||
|
|
@ -498,8 +498,11 @@ struct Values {
|
|||
Setting<bool> apply_region_free_patch{true, Keys::apply_region_free_patch};
|
||||
|
||||
// Renderer
|
||||
// clang-format off
|
||||
SwitchableSetting<GraphicsAPI, true> graphics_api{
|
||||
#if defined(ENABLE_OPENGL)
|
||||
#if defined(ANDROID) && defined(ENABLE_VULKAN) // Prefer Vulkan on Android, OpenGL on everything else
|
||||
GraphicsAPI::Vulkan,
|
||||
#elif defined(ENABLE_OPENGL)
|
||||
GraphicsAPI::OpenGL,
|
||||
#elif defined(ENABLE_VULKAN)
|
||||
GraphicsAPI::Vulkan,
|
||||
|
|
@ -510,6 +513,7 @@ struct Values {
|
|||
#error "At least one renderer must be enabled."
|
||||
#endif
|
||||
GraphicsAPI::Software, GraphicsAPI::Vulkan, Keys::graphics_api};
|
||||
// clang-format on
|
||||
SwitchableSetting<u32> physical_device{0, Keys::physical_device};
|
||||
Setting<bool> use_gles{false, Keys::use_gles};
|
||||
Setting<bool> renderer_debug{false, Keys::renderer_debug};
|
||||
|
|
|
|||
|
|
@ -350,6 +350,8 @@ add_library(citra_core STATIC
|
|||
hle/service/ldr_ro/ldr_ro.h
|
||||
hle/service/mcu/mcu_hwc.cpp
|
||||
hle/service/mcu/mcu_hwc.h
|
||||
hle/service/mcu/mcu_rtc.cpp
|
||||
hle/service/mcu/mcu_rtc.h
|
||||
hle/service/mcu/mcu.cpp
|
||||
hle/service/mcu/mcu.h
|
||||
hle/service/mic/mic_u.cpp
|
||||
|
|
|
|||
|
|
@ -590,6 +590,8 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
|
|||
plg_ldr->SetAllowGameChangeState(Settings::values.allow_plugin_loader.GetValue());
|
||||
}
|
||||
|
||||
SetInfoLEDColor({});
|
||||
|
||||
LOG_DEBUG(Core, "Initialized OK");
|
||||
|
||||
is_powered_on = true;
|
||||
|
|
@ -720,6 +722,8 @@ void System::Shutdown(bool is_deserializing) {
|
|||
|
||||
memory.reset();
|
||||
|
||||
SetInfoLEDColor({});
|
||||
|
||||
LOG_DEBUG(Core, "Shutdown OK");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <boost/optional.hpp>
|
||||
#include <boost/serialization/version.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/cheats/cheats.h"
|
||||
#include "core/hle/service/apt/applet_manager.h"
|
||||
|
|
@ -381,6 +382,27 @@ public:
|
|||
|
||||
bool IsInitialSetup();
|
||||
|
||||
// This returns the 3DS notification LED RGB value.
|
||||
// Keep in mind this is used as a PWM duty cycle on real HW,
|
||||
// so the percieved LED brightness is not linear.
|
||||
const Common::Vec3<u8>& GetInfoLEDColor() const {
|
||||
return info_led_color;
|
||||
}
|
||||
|
||||
void SetInfoLEDColor(const Common::Vec3<u8>& color) {
|
||||
if (color == info_led_color)
|
||||
return;
|
||||
|
||||
info_led_color = color;
|
||||
if (info_led_color_changed) {
|
||||
info_led_color_changed();
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterInfoLEDColorChanged(const std::function<void()>& func) {
|
||||
info_led_color_changed = func;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Initialize the emulated system.
|
||||
|
|
@ -487,6 +509,9 @@ private:
|
|||
|
||||
std::vector<u64> lle_modules;
|
||||
|
||||
Common::Vec3<u8> info_led_color;
|
||||
std::function<void()> info_led_color_changed;
|
||||
|
||||
friend class boost::serialization::access;
|
||||
template <typename Archive>
|
||||
void serialize(Archive& ar, const unsigned int file_version);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
// Copyright 2024 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/mcu/mcu.h"
|
||||
#include "core/hle/service/mcu/mcu_hwc.h"
|
||||
#include "core/hle/service/mcu/mcu_rtc.h"
|
||||
|
||||
namespace Service::MCU {
|
||||
|
||||
void InstallInterfaces(Core::System& system) {
|
||||
auto& service_manager = system.ServiceManager();
|
||||
std::make_shared<HWC>()->InstallAsService(service_manager);
|
||||
std::make_shared<HWC>(system)->InstallAsService(service_manager);
|
||||
std::make_shared<RTC>(system)->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
} // namespace Service::MCU
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
// Copyright 2024 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/archives.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/service/mcu/mcu_hwc.h"
|
||||
#include "core/hle/service/mcu/mcu_rtc.h"
|
||||
|
||||
SERVICE_CONSTRUCT_IMPL(Service::MCU::HWC)
|
||||
SERIALIZE_EXPORT_IMPL(Service::MCU::HWC)
|
||||
|
||||
namespace Service::MCU {
|
||||
|
||||
HWC::HWC() : ServiceFramework("mcu::HWC", 1) {
|
||||
HWC::HWC(Core::System& _system) : ServiceFramework("mcu::HWC", 1), system(_system) {
|
||||
static const FunctionInfo functions[] = {
|
||||
// clang-format off
|
||||
{0x0001, nullptr, "ReadRegister"},
|
||||
|
|
@ -21,7 +24,7 @@ HWC::HWC() : ServiceFramework("mcu::HWC", 1) {
|
|||
{0x0007, nullptr, "SetWifiLEDState"},
|
||||
{0x0008, nullptr, "SetCameraLEDPattern"},
|
||||
{0x0009, nullptr, "Set3DLEDState"},
|
||||
{0x000A, nullptr, "SetInfoLEDPattern"},
|
||||
{0x000A, &HWC::SetInfoLEDPattern, "SetInfoLEDPattern"},
|
||||
{0x000B, nullptr, "GetSoundVolume"},
|
||||
{0x000C, nullptr, "SetTopScreenFlicker"},
|
||||
{0x000D, nullptr, "SetBottomScreenFlicker"},
|
||||
|
|
@ -33,4 +36,19 @@ HWC::HWC() : ServiceFramework("mcu::HWC", 1) {
|
|||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
void HWC::SetInfoLEDPattern(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
auto pat = rp.PopRaw<MCU::InfoLedPattern>();
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
|
||||
auto mcu_rtc = MCU::RTC::GetService(system);
|
||||
if (mcu_rtc) {
|
||||
mcu_rtc->UpdateInfoLEDPattern(pat);
|
||||
rb.Push(ResultSuccess);
|
||||
} else {
|
||||
rb.Push(ResultUnknown);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Service::MCU
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2024 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -10,12 +10,17 @@ namespace Service::MCU {
|
|||
|
||||
class HWC final : public ServiceFramework<HWC> {
|
||||
public:
|
||||
explicit HWC();
|
||||
explicit HWC(Core::System& _system);
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
|
||||
void SetInfoLEDPattern(Kernel::HLERequestContext& ctx);
|
||||
|
||||
SERVICE_SERIALIZATION_SIMPLE
|
||||
};
|
||||
|
||||
} // namespace Service::MCU
|
||||
|
||||
SERVICE_CONSTRUCT(Service::MCU::HWC)
|
||||
BOOST_CLASS_EXPORT_KEY(Service::MCU::HWC)
|
||||
|
|
|
|||
293
src/core/hle/service/mcu/mcu_rtc.cpp
Normal file
293
src/core/hle/service/mcu/mcu_rtc.cpp
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include "common/archives.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/service/mcu/mcu.h"
|
||||
#include "core/hle/service/mcu/mcu_rtc.h"
|
||||
|
||||
SERVICE_CONSTRUCT_IMPL(Service::MCU::RTC)
|
||||
SERIALIZE_EXPORT_IMPL(Service::MCU::RTC)
|
||||
|
||||
namespace Service::MCU {
|
||||
|
||||
class InfoLedHandler {
|
||||
public:
|
||||
InfoLedHandler() = default;
|
||||
~InfoLedHandler() = default;
|
||||
|
||||
static constexpr s64 CALLBACK_PERIOD_NS = 1'000'000'000ll / 60; // 60Hz (~16ms)
|
||||
static constexpr s64 MCU_TICK_PERIOD_NS = 1'000'000'000ll / 512; // 512Hz (~2ms)
|
||||
|
||||
void SetPattern(const InfoLedPattern& p) {
|
||||
current_pattern = p;
|
||||
pattern_changed = true;
|
||||
}
|
||||
|
||||
void SetHeader(const InfoLedPattern::Header& header) {
|
||||
current_pattern.header = header;
|
||||
pattern_changed = true;
|
||||
}
|
||||
|
||||
// The MCU led code is updated with a frequency of 512Hz on real hardware. However
|
||||
// it is not a very relevant feature for emulation, so to prevent slicing the core
|
||||
// timing too much let's update it every frame instead (60Hz) and adjust for it.
|
||||
void Tick(s64 cycles_late) {
|
||||
|
||||
const s64 late_ns = cyclesToNs(cycles_late);
|
||||
|
||||
// Accumulate elapsed time.
|
||||
arm_time_ns += CALLBACK_PERIOD_NS + late_ns;
|
||||
if (arm_time_ns < 0)
|
||||
arm_time_ns = 0;
|
||||
|
||||
// Sync the MCU state up to the current ARM time
|
||||
while (arm_time_ns >= MCU_TICK_PERIOD_NS) {
|
||||
arm_time_ns -= MCU_TICK_PERIOD_NS;
|
||||
TickMCULed();
|
||||
}
|
||||
}
|
||||
|
||||
Common::Vec3<u8> Color() const {
|
||||
return result_color;
|
||||
}
|
||||
|
||||
// To save CPU time, do not tick if all smooth state has finished
|
||||
// and the pattern is all zero.
|
||||
bool NeedsTicking() {
|
||||
auto patAllZero = [this]() -> bool {
|
||||
u32* data = reinterpret_cast<u32*>(¤t_pattern);
|
||||
for (size_t i = 0; i < sizeof(InfoLedPattern) / sizeof(u32); i++) {
|
||||
if (data[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
return !patAllZero() || !state_r.Finished() || !state_g.Finished() || !state_b.Finished();
|
||||
}
|
||||
|
||||
bool Status() const {
|
||||
return status_finished;
|
||||
}
|
||||
|
||||
private:
|
||||
struct LedSmoothState {
|
||||
s16 target = 0;
|
||||
s16 increment = 0;
|
||||
s16 current = 0;
|
||||
|
||||
bool Finished() {
|
||||
return current == target;
|
||||
}
|
||||
|
||||
friend class boost::serialization::access;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar & target;
|
||||
ar & increment;
|
||||
ar & current;
|
||||
}
|
||||
};
|
||||
|
||||
// Decompilation of MCU function at address 0x2f44
|
||||
void setSmoothState(LedSmoothState& state, u8 color) {
|
||||
// Looks like the color is multiplied for better precision
|
||||
state.target = static_cast<s16>(color) * 128;
|
||||
|
||||
// Real HW makes sure ticks_to_progress is not 0 when the led pattern
|
||||
// is set through I2C. We check for it here instead as it's equivalent.
|
||||
const u8 ticks = std::max<u8>(current_pattern.header.ticks_to_progress, 1);
|
||||
state.increment = (state.target - state.current) / ticks;
|
||||
}
|
||||
|
||||
// Decompilation of MCU function at address 0x2dc0
|
||||
static u8 updateSmoothState(LedSmoothState& status) {
|
||||
if (!status.Finished()) {
|
||||
if (std::abs(status.target - status.current) > std::abs(status.increment)) {
|
||||
status.current += status.increment;
|
||||
} else {
|
||||
status.current = status.target;
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<u8>(status.current / 128);
|
||||
}
|
||||
|
||||
// Decompilation of MCU function at address 0x2f6b
|
||||
// This function is called every 1/512 seconds
|
||||
void TickMCULed() {
|
||||
|
||||
// Here, a few things happen.
|
||||
// If a global variable is set to 2 (0xff904), the led state is cleared.
|
||||
// If a global variable bit 0 is set (0xffe98), this function does not run at all.
|
||||
// If a global variable bit 7 is set (0xffe97), this function takes another path which
|
||||
// runs function 0x2f1d instead of setSmoothState() to set the LED smooth status.
|
||||
// This function seems to setup smooth to fade to off state.
|
||||
// TODO(PabloMK7): Figure out what those mean. Maybe power on/off related
|
||||
|
||||
if (pattern_changed) {
|
||||
pattern_changed = false;
|
||||
status_finished = false;
|
||||
ticks_to_next_index = 0;
|
||||
index = 0;
|
||||
} else {
|
||||
if (ticks_to_next_index == 0) {
|
||||
ticks_to_next_index = current_pattern.header.ticks_per_index;
|
||||
|
||||
if (index < InfoLedPattern::PATTERN_INDEX_COUNT - 1) {
|
||||
status_finished = false;
|
||||
index = (index + 1) % InfoLedPattern::PATTERN_INDEX_COUNT;
|
||||
last_index_repeat_times = 0;
|
||||
} else {
|
||||
status_finished = true;
|
||||
if (current_pattern.header.last_index_repeat_times != 0xFF) {
|
||||
last_index_repeat_times++;
|
||||
if (last_index_repeat_times >
|
||||
current_pattern.header.last_index_repeat_times) {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set smooth for the next index
|
||||
setSmoothState(state_r, current_pattern.r[index]);
|
||||
setSmoothState(state_g, current_pattern.g[index]);
|
||||
setSmoothState(state_b, current_pattern.b[index]);
|
||||
}
|
||||
ticks_to_next_index--;
|
||||
}
|
||||
|
||||
// Update smooth state
|
||||
result_color.r() = updateSmoothState(state_r);
|
||||
result_color.g() = updateSmoothState(state_g);
|
||||
result_color.b() = updateSmoothState(state_b);
|
||||
}
|
||||
|
||||
private:
|
||||
InfoLedPattern current_pattern{};
|
||||
|
||||
bool pattern_changed = false;
|
||||
bool status_finished = false;
|
||||
|
||||
u8 ticks_to_next_index = 0;
|
||||
u8 index = 0;
|
||||
u8 last_index_repeat_times = 0;
|
||||
|
||||
LedSmoothState state_r{};
|
||||
LedSmoothState state_g{};
|
||||
LedSmoothState state_b{};
|
||||
|
||||
Common::Vec3<u8> result_color{};
|
||||
|
||||
s64 arm_time_ns = 0;
|
||||
|
||||
friend class boost::serialization::access;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar & current_pattern;
|
||||
ar & pattern_changed;
|
||||
ar & status_finished;
|
||||
ar & ticks_to_next_index;
|
||||
ar & index;
|
||||
ar & last_index_repeat_times;
|
||||
ar & state_r;
|
||||
ar & state_g;
|
||||
ar & state_b;
|
||||
ar & result_color;
|
||||
ar & arm_time_ns;
|
||||
}
|
||||
};
|
||||
|
||||
RTC::RTC(Core::System& _system) : ServiceFramework("mcu::RTC", 1), system(_system) {
|
||||
static const FunctionInfo functions[] = {
|
||||
// clang-format off
|
||||
{0x003B, &RTC::SetInfoLEDPattern, "SetInfoLEDPattern"},
|
||||
{0x003C, &RTC::SetInfoLEDPatternHeader, "SetInfoLEDPattern"},
|
||||
{0x003D, &RTC::GetInfoLEDStatus, "SetInfoLEDPattern"},
|
||||
// clang-format on
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
info_led = std::make_unique<InfoLedHandler>();
|
||||
info_led_tick_event =
|
||||
system.Kernel().timing.RegisterEvent("MCUTickInfoLED", [this](u64, s64 cycles_late) {
|
||||
info_led->Tick(cycles_late);
|
||||
system.SetInfoLEDColor(info_led->Color());
|
||||
if (info_led->NeedsTicking()) {
|
||||
system.Kernel().timing.ScheduleEvent(nsToCycles(InfoLedHandler::CALLBACK_PERIOD_NS),
|
||||
info_led_tick_event, 0, 1);
|
||||
} else {
|
||||
info_led_ticking = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RTC::~RTC() {}
|
||||
|
||||
void RTC::UpdateInfoLEDPattern(const InfoLedPattern& pat) {
|
||||
info_led->SetPattern(pat);
|
||||
if (!info_led_ticking) {
|
||||
system.Kernel().timing.ScheduleEvent(0, info_led_tick_event, 0, 1);
|
||||
info_led_ticking = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RTC::UpdateInfoLEDHeader(const InfoLedPattern::Header& header) {
|
||||
info_led->SetHeader(header);
|
||||
if (!info_led_ticking) {
|
||||
system.Kernel().timing.ScheduleEvent(0, info_led_tick_event, 0, 1);
|
||||
info_led_ticking = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool RTC::GetInfoLEDStatusFinished() {
|
||||
return info_led->Status();
|
||||
}
|
||||
|
||||
std::shared_ptr<RTC> RTC::GetService(Core::System& system) {
|
||||
return system.ServiceManager().GetService<RTC>("mcu::RTC");
|
||||
}
|
||||
|
||||
void RTC::SetInfoLEDPattern(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
auto pat = rp.PopRaw<InfoLedPattern>();
|
||||
|
||||
UpdateInfoLEDPattern(pat);
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void RTC::SetInfoLEDPatternHeader(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
auto head = rp.PopRaw<InfoLedPattern::Header>();
|
||||
|
||||
UpdateInfoLEDHeader(head);
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void RTC::GetInfoLEDStatus(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(static_cast<u8>(GetInfoLEDStatusFinished()));
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
void RTC::serialize(Archive& ar, const unsigned int) {
|
||||
DEBUG_SERIALIZATION_POINT;
|
||||
ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
|
||||
ar & info_led;
|
||||
ar & info_led_ticking;
|
||||
}
|
||||
|
||||
} // namespace Service::MCU
|
||||
83
src/core/hle/service/mcu/mcu_rtc.h
Normal file
83
src/core/hle/service/mcu/mcu_rtc.h
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::MCU {
|
||||
class InfoLedHandler;
|
||||
|
||||
struct InfoLedPattern {
|
||||
static constexpr size_t PATTERN_INDEX_COUNT = 32;
|
||||
|
||||
struct Header {
|
||||
u8 ticks_per_index{}; // Amount of ticks to stay in the current index (1 tick == 1/512 s)
|
||||
u8 ticks_to_progress{}; // Amount of ticks to go from the previous value to the current
|
||||
// index value. Normally, this only makes sense to be set to 0 to
|
||||
// disable interpolation, or equal to "ticks_to_progress" for linear
|
||||
// interpolation. Any other value breaks the interpolation math.
|
||||
u8 last_index_repeat_times{}; // Amount of times to repeat the last index, as if the color
|
||||
// array had "last_index_repeat_times" more elements equal to
|
||||
// the last array value. (0xFF means repeat forever)
|
||||
u8 padding{};
|
||||
} header;
|
||||
|
||||
// RGB color elements, corresponding to the LED PWM duty cycle.
|
||||
// (0x0 -> fully off, 0xFF -> fully on)
|
||||
std::array<u8, PATTERN_INDEX_COUNT> r{};
|
||||
std::array<u8, PATTERN_INDEX_COUNT> g{};
|
||||
std::array<u8, PATTERN_INDEX_COUNT> b{};
|
||||
|
||||
friend class boost::serialization::access;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar & header.ticks_per_index;
|
||||
ar & header.ticks_to_progress;
|
||||
ar & header.last_index_repeat_times;
|
||||
ar & header.padding;
|
||||
|
||||
ar & r;
|
||||
ar & g;
|
||||
ar & b;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(InfoLedPattern) == 0x64);
|
||||
|
||||
class RTC final : public ServiceFramework<RTC> {
|
||||
public:
|
||||
explicit RTC(Core::System& _system);
|
||||
~RTC();
|
||||
|
||||
void UpdateInfoLEDPattern(const InfoLedPattern& pat);
|
||||
|
||||
void UpdateInfoLEDHeader(const InfoLedPattern::Header& header);
|
||||
|
||||
bool GetInfoLEDStatusFinished();
|
||||
|
||||
static std::shared_ptr<RTC> GetService(Core::System& system);
|
||||
|
||||
private:
|
||||
void SetInfoLEDPattern(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void SetInfoLEDPatternHeader(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void GetInfoLEDStatus(Kernel::HLERequestContext& ctx);
|
||||
|
||||
Core::System& system;
|
||||
|
||||
std::unique_ptr<InfoLedHandler> info_led;
|
||||
Core::TimingEventType* info_led_tick_event{};
|
||||
bool info_led_ticking{};
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int);
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
} // namespace Service::MCU
|
||||
|
||||
SERVICE_CONSTRUCT(Service::MCU::RTC)
|
||||
BOOST_CLASS_EXPORT_KEY(Service::MCU::RTC)
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/file_backend.h"
|
||||
#include "core/hle/kernel/shared_page.h"
|
||||
#include "core/hle/service/mcu/mcu_rtc.h"
|
||||
#include "core/hle/service/ptm/ptm.h"
|
||||
#include "core/hle/service/ptm/ptm_gets.h"
|
||||
#include "core/hle/service/ptm/ptm_play.h"
|
||||
|
|
@ -133,6 +134,51 @@ void Module::Interface::CheckNew3DS(Kernel::HLERequestContext& ctx) {
|
|||
Service::PTM::CheckNew3DS(rb);
|
||||
}
|
||||
|
||||
void Module::Interface::SetInfoLEDPattern(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
auto pat = rp.PopRaw<MCU::InfoLedPattern>();
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
|
||||
auto mcu_rtc = MCU::RTC::GetService(ptm->system);
|
||||
if (mcu_rtc) {
|
||||
mcu_rtc->UpdateInfoLEDPattern(pat);
|
||||
rb.Push(ResultSuccess);
|
||||
} else {
|
||||
rb.Push(ResultUnknown);
|
||||
}
|
||||
}
|
||||
|
||||
void Module::Interface::SetInfoLEDPatternHeader(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
auto head = rp.PopRaw<MCU::InfoLedPattern::Header>();
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
|
||||
auto mcu_rtc = MCU::RTC::GetService(ptm->system);
|
||||
if (mcu_rtc) {
|
||||
mcu_rtc->UpdateInfoLEDHeader(head);
|
||||
rb.Push(ResultSuccess);
|
||||
} else {
|
||||
rb.Push(ResultUnknown);
|
||||
}
|
||||
}
|
||||
|
||||
void Module::Interface::GetInfoLEDStatus(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
|
||||
auto mcu_rtc = MCU::RTC::GetService(ptm->system);
|
||||
if (mcu_rtc) {
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(static_cast<u8>(mcu_rtc->GetInfoLEDStatusFinished()));
|
||||
} else {
|
||||
rb.Push(ResultUnknown);
|
||||
rb.Push(u8{});
|
||||
}
|
||||
}
|
||||
|
||||
void Module::Interface::GetSystemTime(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2015 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -139,6 +139,12 @@ public:
|
|||
*/
|
||||
void CheckNew3DS(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void SetInfoLEDPattern(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void SetInfoLEDPatternHeader(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void GetInfoLEDStatus(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* PTM::GetSystemTime service function
|
||||
* Outputs:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2015 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -41,9 +41,9 @@ PTM_S_Common::PTM_S_Common(std::shared_ptr<Module> ptm, const char* name)
|
|||
{0x0408, nullptr, "Awake"},
|
||||
{0x0409, nullptr, "RebootAsync"},
|
||||
{0x040A, &PTM_S_Common::CheckNew3DS, "CheckNew3DS"},
|
||||
{0x0801, nullptr, "SetInfoLEDPattern"},
|
||||
{0x0802, nullptr, "SetInfoLEDPatternHeader"},
|
||||
{0x0803, nullptr, "GetInfoLEDStatus"},
|
||||
{0x0801, &PTM_S_Common::SetInfoLEDPattern, "SetInfoLEDPattern"},
|
||||
{0x0802, &PTM_S_Common::SetInfoLEDPatternHeader, "SetInfoLEDPatternHeader"},
|
||||
{0x0803, &PTM_S_Common::GetInfoLEDStatus, "GetInfoLEDStatus"},
|
||||
{0x0804, nullptr, "SetBatteryEmptyLEDPattern"},
|
||||
{0x0805, nullptr, "ClearStepHistory"},
|
||||
{0x0806, nullptr, "SetStepHistory"},
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ add_executable(tests
|
|||
|
||||
create_target_directory_groups(tests)
|
||||
|
||||
if (BSD STREQUAL "NetBSD")
|
||||
include(DisablePaxMprotect)
|
||||
disable_pax_mprotect(tests)
|
||||
endif()
|
||||
|
||||
target_link_libraries(tests PRIVATE citra_common citra_core video_core audio_core)
|
||||
target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch2 nihstro-headers Threads::Threads)
|
||||
|
||||
|
|
@ -31,6 +36,10 @@ if (ENABLE_LIBRETRO)
|
|||
endif()
|
||||
|
||||
add_test(NAME tests COMMAND tests)
|
||||
if(NOT ANDROID)
|
||||
catch_discover_tests(tests)
|
||||
endif()
|
||||
|
||||
|
||||
if (CITRA_USE_PRECOMPILED_HEADERS)
|
||||
target_precompile_headers(tests PRIVATE precompiled_headers.h)
|
||||
|
|
|
|||
|
|
@ -481,6 +481,39 @@ SHADER_TEST_CASE("RSQ", "[video_core][shader]") {
|
|||
REQUIRE(shader.Run({0.0625f}).x == Catch::Approx(4.0f).margin(0.004f));
|
||||
}
|
||||
|
||||
SHADER_TEST_CASE("SETEMIT", "[video_core][shader]") {
|
||||
Pica::GeometryEmitter geometry_emitter;
|
||||
|
||||
for (u8 winding = 0; winding <= 1; ++winding) {
|
||||
for (u8 prim_emit = 0; prim_emit <= 1; ++prim_emit) {
|
||||
for (u8 vertex_id = 0; vertex_id <= 3; ++vertex_id) {
|
||||
auto shader_setup = CompileShaderSetup({
|
||||
{OpCode::Id::NOP}, // setemit
|
||||
{OpCode::Id::END},
|
||||
});
|
||||
|
||||
// nihstro does not support the SETEMIT instructions, so the instruction-binary must
|
||||
// be manually
|
||||
// inserted here:
|
||||
nihstro::Instruction SETEMIT = {};
|
||||
SETEMIT.opcode = nihstro::OpCode(nihstro::OpCode::Id::SETEMIT);
|
||||
SETEMIT.setemit.winding.Assign(winding);
|
||||
SETEMIT.setemit.prim_emit.Assign(prim_emit);
|
||||
SETEMIT.setemit.vertex_id.Assign(vertex_id);
|
||||
shader_setup->UpdateProgramCode(0, SETEMIT.hex);
|
||||
|
||||
auto shader = TestType(std::move(shader_setup));
|
||||
Pica::ShaderUnit shader_unit(&geometry_emitter);
|
||||
shader.Run(shader_unit, 1.0f);
|
||||
|
||||
REQUIRE(geometry_emitter.emit_state.winding == winding);
|
||||
REQUIRE(geometry_emitter.emit_state.prim_emit == prim_emit);
|
||||
REQUIRE(geometry_emitter.emit_state.vertex_id == vertex_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SHADER_TEST_CASE("Uniform Read", "[video_core][shader]") {
|
||||
const auto sh_input = SourceRegister::MakeInput(0);
|
||||
const auto sh_c0 = SourceRegister::MakeFloat(0);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <boost/serialization/binary_object.hpp>
|
||||
|
||||
#include "common/vector_math.h"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -29,15 +29,15 @@ void ShaderUnit::WriteOutput(const ShaderRegs& config, AttributeBuffer& buffer)
|
|||
}
|
||||
|
||||
void GeometryEmitter::Emit(std::span<Common::Vec4<f24>, 16> output_regs) {
|
||||
ASSERT(vertex_id < 3);
|
||||
ASSERT(emit_state.vertex_id < 3);
|
||||
|
||||
u32 output_index{};
|
||||
for (u32 reg : Common::BitSet<u32>(output_mask)) {
|
||||
buffer[vertex_id][output_index++] = output_regs[reg];
|
||||
buffer[emit_state.vertex_id][output_index++] = output_regs[reg];
|
||||
}
|
||||
|
||||
if (prim_emit) {
|
||||
if (winding) {
|
||||
if (emit_state.prim_emit) {
|
||||
if (emit_state.winding) {
|
||||
handlers->winding_setter();
|
||||
}
|
||||
for (std::size_t i = 0; i < buffer.size(); ++i) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -75,11 +75,18 @@ struct GeometryEmitter {
|
|||
void Emit(std::span<Common::Vec4<f24>, 16> output_regs);
|
||||
|
||||
public:
|
||||
std::array<AttributeBuffer, 3> buffer;
|
||||
u8 vertex_id;
|
||||
bool prim_emit;
|
||||
bool winding;
|
||||
union EmitState {
|
||||
struct {
|
||||
bool winding : 1;
|
||||
bool prim_emit : 1;
|
||||
u8 vertex_id : 2;
|
||||
};
|
||||
u8 raw;
|
||||
} emit_state;
|
||||
static_assert(sizeof(emit_state) == 1);
|
||||
|
||||
u32 output_mask;
|
||||
std::array<AttributeBuffer, 3> buffer;
|
||||
Handlers* handlers;
|
||||
|
||||
private:
|
||||
|
|
@ -87,9 +94,7 @@ private:
|
|||
template <class Archive>
|
||||
void serialize(Archive& ar, const u32 file_version) {
|
||||
ar & buffer;
|
||||
ar & vertex_id;
|
||||
ar & prim_emit;
|
||||
ar & winding;
|
||||
ar & emit_state.raw;
|
||||
ar & output_mask;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -671,9 +671,9 @@ static void RunInterpreter(const ShaderSetup& setup, ShaderUnit& state,
|
|||
case OpCode::Id::SETEMIT: {
|
||||
auto* emitter = state.emitter_ptr;
|
||||
ASSERT_MSG(emitter, "Execute SETEMIT on VS");
|
||||
emitter->vertex_id = instr.setemit.vertex_id;
|
||||
emitter->prim_emit = instr.setemit.prim_emit != 0;
|
||||
emitter->winding = instr.setemit.winding != 0;
|
||||
emitter->emit_state.vertex_id = instr.setemit.vertex_id;
|
||||
emitter->emit_state.prim_emit = instr.setemit.prim_emit != 0;
|
||||
emitter->emit_state.winding = instr.setemit.winding != 0;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -865,12 +865,13 @@ void JitShader::Compile_SETE(Instruction instr) {
|
|||
|
||||
l(have_emitter);
|
||||
|
||||
MOV(XSCRATCH1.toW(), instr.setemit.vertex_id);
|
||||
STRB(XSCRATCH1.toW(), XSCRATCH0, u32(offsetof(GeometryEmitter, vertex_id)));
|
||||
MOV(XSCRATCH1.toW(), instr.setemit.prim_emit);
|
||||
STRB(XSCRATCH1.toW(), XSCRATCH0, u32(offsetof(GeometryEmitter, prim_emit)));
|
||||
MOV(XSCRATCH1.toW(), instr.setemit.winding);
|
||||
STRB(XSCRATCH1.toW(), XSCRATCH0, u32(offsetof(GeometryEmitter, winding)));
|
||||
const GeometryEmitter::EmitState new_state{
|
||||
.winding = instr.setemit.winding != 0,
|
||||
.prim_emit = instr.setemit.prim_emit != 0,
|
||||
.vertex_id = static_cast<uint8_t>(instr.setemit.vertex_id),
|
||||
};
|
||||
MOV(XSCRATCH1.toW(), new_state.raw);
|
||||
STRB(XSCRATCH1.toW(), XSCRATCH0, u32(offsetof(GeometryEmitter, emit_state)));
|
||||
|
||||
l(end);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -905,9 +905,12 @@ void JitShader::Compile_SETE(Instruction instr) {
|
|||
jmp(end);
|
||||
|
||||
L(have_emitter);
|
||||
mov(byte[rax + offsetof(GeometryEmitter, vertex_id)], instr.setemit.vertex_id);
|
||||
mov(byte[rax + offsetof(GeometryEmitter, prim_emit)], instr.setemit.prim_emit);
|
||||
mov(byte[rax + offsetof(GeometryEmitter, winding)], instr.setemit.winding);
|
||||
const GeometryEmitter::EmitState new_state{
|
||||
.winding = instr.setemit.winding != 0,
|
||||
.prim_emit = instr.setemit.prim_emit != 0,
|
||||
.vertex_id = static_cast<uint8_t>(instr.setemit.vertex_id),
|
||||
};
|
||||
mov(byte[rax + offsetof(GeometryEmitter, emit_state)], new_state.raw);
|
||||
L(end);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue