mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-25 17:50:14 +00:00
commit
ee0669d709
@ -114,7 +114,13 @@ int main(int argc, char **argv) {
|
||||
System::Init(emu_window.get());
|
||||
SCOPE_EXIT({ System::Shutdown(); });
|
||||
|
||||
Loader::ResultStatus load_result = Loader::LoadFile(boot_filename);
|
||||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(boot_filename);
|
||||
if (!loader) {
|
||||
LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", boot_filename.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
Loader::ResultStatus load_result = loader->Load();
|
||||
if (Loader::ResultStatus::Success != load_result) {
|
||||
LOG_CRITICAL(Frontend, "Failed to load ROM (Error %i)!", load_result);
|
||||
return -1;
|
||||
|
@ -44,12 +44,16 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
|
||||
}
|
||||
|
||||
static const std::array<int, Settings::NativeInput::NUM_INPUTS> defaults = {
|
||||
// directly mapped keys
|
||||
SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X,
|
||||
SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_1, SDL_SCANCODE_2,
|
||||
SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_B,
|
||||
SDL_SCANCODE_T, SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H,
|
||||
SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, SDL_SCANCODE_L,
|
||||
|
||||
// indirectly mapped keys
|
||||
SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT,
|
||||
SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, SDL_SCANCODE_L
|
||||
SDL_SCANCODE_D,
|
||||
};
|
||||
|
||||
void Config::ReadValues() {
|
||||
@ -58,6 +62,7 @@ void Config::ReadValues() {
|
||||
Settings::values.input_mappings[Settings::NativeInput::All[i]] =
|
||||
sdl2_config->GetInteger("Controls", Settings::NativeInput::Mapping[i], defaults[i]);
|
||||
}
|
||||
Settings::values.pad_circle_modifier_scale = (float)sdl2_config->GetReal("Controls", "pad_circle_modifier_scale", 0.5);
|
||||
|
||||
// Core
|
||||
Settings::values.frame_skip = sdl2_config->GetInteger("Core", "frame_skip", 0);
|
||||
@ -77,8 +82,9 @@ void Config::ReadValues() {
|
||||
// Data Storage
|
||||
Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
|
||||
|
||||
// System Region
|
||||
Settings::values.region_value = sdl2_config->GetInteger("System Region", "region_value", 1);
|
||||
// System
|
||||
Settings::values.is_new_3ds = sdl2_config->GetBoolean("System", "is_new_3ds", false);
|
||||
Settings::values.region_value = sdl2_config->GetInteger("System", "region_value", 1);
|
||||
|
||||
// Miscellaneous
|
||||
Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Info");
|
||||
|
@ -23,14 +23,19 @@ pad_l =
|
||||
pad_r =
|
||||
pad_zl =
|
||||
pad_zr =
|
||||
pad_sup =
|
||||
pad_sdown =
|
||||
pad_sleft =
|
||||
pad_sright =
|
||||
pad_cup =
|
||||
pad_cdown =
|
||||
pad_cleft =
|
||||
pad_cright =
|
||||
pad_circle_up =
|
||||
pad_circle_down =
|
||||
pad_circle_left =
|
||||
pad_circle_right =
|
||||
pad_circle_modifier =
|
||||
|
||||
# The applied modifier scale to circle pad.
|
||||
# Must be in range of 0.0-1.0. Defaults to 0.5
|
||||
pad_circle_modifier_scale =
|
||||
|
||||
[Core]
|
||||
# The applied frameskip amount. Must be a power of two.
|
||||
@ -66,7 +71,11 @@ output_engine =
|
||||
# 1 (default): Yes, 0: No
|
||||
use_virtual_sd =
|
||||
|
||||
[System Region]
|
||||
[System]
|
||||
# The system model that Citra will try to emulate
|
||||
# 0: Old 3DS (default), 1: New 3DS
|
||||
is_new_3ds =
|
||||
|
||||
# The system region that Citra will use during emulation
|
||||
# 0: Japan, 1: USA (default), 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
|
||||
region_value =
|
||||
|
@ -40,9 +40,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
|
||||
|
||||
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
|
||||
if (state == SDL_PRESSED) {
|
||||
KeyPressed({ key, keyboard_id });
|
||||
KeyMap::PressKey(*this, { key, keyboard_id });
|
||||
} else if (state == SDL_RELEASED) {
|
||||
KeyReleased({ key, keyboard_id });
|
||||
KeyMap::ReleaseKey(*this, { key, keyboard_id });
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,8 +168,9 @@ void EmuWindow_SDL2::DoneCurrent() {
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::ReloadSetKeymaps() {
|
||||
KeyMap::ClearKeyMapping(keyboard_id);
|
||||
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
|
||||
KeyMap::SetKeyMapping({ Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id }, Service::HID::pad_mapping[i]);
|
||||
KeyMap::SetKeyMapping({ Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id }, KeyMap::mapping_targets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,6 @@ set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
set(SRCS
|
||||
config/controller_config.cpp
|
||||
config/controller_config_util.cpp
|
||||
config.cpp
|
||||
debugger/callstack.cpp
|
||||
debugger/disassembler.cpp
|
||||
@ -11,7 +9,7 @@ set(SRCS
|
||||
debugger/graphics_breakpoint_observer.cpp
|
||||
debugger/graphics_breakpoints.cpp
|
||||
debugger/graphics_cmdlists.cpp
|
||||
debugger/graphics_framebuffer.cpp
|
||||
debugger/graphics_surface.cpp
|
||||
debugger/graphics_tracing.cpp
|
||||
debugger/graphics_vertex_shader.cpp
|
||||
debugger/profiler.cpp
|
||||
@ -33,8 +31,6 @@ set(SRCS
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
config/controller_config.h
|
||||
config/controller_config_util.h
|
||||
config.h
|
||||
debugger/callstack.h
|
||||
debugger/disassembler.h
|
||||
@ -43,7 +39,7 @@ set(HEADERS
|
||||
debugger/graphics_breakpoints.h
|
||||
debugger/graphics_breakpoints_p.h
|
||||
debugger/graphics_cmdlists.h
|
||||
debugger/graphics_framebuffer.h
|
||||
debugger/graphics_surface.h
|
||||
debugger/graphics_tracing.h
|
||||
debugger/graphics_vertex_shader.h
|
||||
debugger/profiler.h
|
||||
@ -65,7 +61,6 @@ set(HEADERS
|
||||
)
|
||||
|
||||
set(UIS
|
||||
config/controller_config.ui
|
||||
debugger/callstack.ui
|
||||
debugger/disassembler.ui
|
||||
debugger/profiler.ui
|
||||
|
@ -235,12 +235,12 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
|
||||
|
||||
void GRenderWindow::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
this->KeyPressed({event->key(), keyboard_id});
|
||||
KeyMap::PressKey(*this, { event->key(), keyboard_id });
|
||||
}
|
||||
|
||||
void GRenderWindow::keyReleaseEvent(QKeyEvent* event)
|
||||
{
|
||||
this->KeyReleased({event->key(), keyboard_id});
|
||||
KeyMap::ReleaseKey(*this, { event->key(), keyboard_id });
|
||||
}
|
||||
|
||||
void GRenderWindow::mousePressEvent(QMouseEvent *event)
|
||||
@ -270,8 +270,9 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent *event)
|
||||
|
||||
void GRenderWindow::ReloadSetKeymaps()
|
||||
{
|
||||
KeyMap::ClearKeyMapping(keyboard_id);
|
||||
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
|
||||
KeyMap::SetKeyMapping({Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id}, Service::HID::pad_mapping[i]);
|
||||
KeyMap::SetKeyMapping({ Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id }, KeyMap::mapping_targets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,12 +22,16 @@ Config::Config() {
|
||||
}
|
||||
|
||||
static const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> defaults = {
|
||||
// directly mapped keys
|
||||
Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X,
|
||||
Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2,
|
||||
Qt::Key_M, Qt::Key_N, Qt::Key_B,
|
||||
Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H,
|
||||
Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L,
|
||||
|
||||
// indirectly mapped keys
|
||||
Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right,
|
||||
Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L
|
||||
Qt::Key_D,
|
||||
};
|
||||
|
||||
void Config::ReadValues() {
|
||||
@ -36,6 +40,7 @@ void Config::ReadValues() {
|
||||
Settings::values.input_mappings[Settings::NativeInput::All[i]] =
|
||||
qt_config->value(QString::fromStdString(Settings::NativeInput::Mapping[i]), defaults[i]).toInt();
|
||||
}
|
||||
Settings::values.pad_circle_modifier_scale = qt_config->value("pad_circle_modifier_scale", 0.5).toFloat();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Core");
|
||||
@ -60,7 +65,8 @@ void Config::ReadValues() {
|
||||
Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("System Region");
|
||||
qt_config->beginGroup("System");
|
||||
Settings::values.is_new_3ds = qt_config->value("is_new_3ds", false).toBool();
|
||||
Settings::values.region_value = qt_config->value("region_value", 1).toInt();
|
||||
qt_config->endGroup();
|
||||
|
||||
@ -125,6 +131,7 @@ void Config::SaveValues() {
|
||||
qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]),
|
||||
Settings::values.input_mappings[Settings::NativeInput::All[i]]);
|
||||
}
|
||||
qt_config->setValue("pad_circle_modifier_scale", (double)Settings::values.pad_circle_modifier_scale);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Core");
|
||||
@ -150,7 +157,8 @@ void Config::SaveValues() {
|
||||
qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("System Region");
|
||||
qt_config->beginGroup("System");
|
||||
qt_config->setValue("is_new_3ds", Settings::values.is_new_3ds);
|
||||
qt_config->setValue("region_value", Settings::values.region_value);
|
||||
qt_config->endGroup();
|
||||
|
||||
|
@ -1,95 +0,0 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
|
||||
#include "controller_config.h"
|
||||
#include "controller_config_util.h"
|
||||
|
||||
/* TODO(bunnei): ImplementMe
|
||||
|
||||
using common::Config;
|
||||
|
||||
GControllerConfig::GControllerConfig(common::Config::ControllerPort* initial_config, QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
ui.setupUi(this);
|
||||
((QGridLayout*)ui.mainStickTab->layout())->addWidget(new GStickConfig(Config::ANALOG_LEFT, Config::ANALOG_RIGHT, Config::ANALOG_UP, Config::ANALOG_DOWN, this, this), 1, 1);
|
||||
((QGridLayout*)ui.cStickTab->layout())->addWidget(new GStickConfig(Config::C_LEFT, Config::C_RIGHT, Config::C_UP, Config::C_DOWN, this, this), 1, 1);
|
||||
((QGridLayout*)ui.dPadTab->layout())->addWidget(new GStickConfig(Config::DPAD_LEFT, Config::DPAD_RIGHT, Config::DPAD_UP, Config::DPAD_DOWN, this, this), 1, 1);
|
||||
|
||||
// TODO: Arrange these more compactly?
|
||||
QVBoxLayout* layout = (QVBoxLayout*)ui.buttonsTab->layout();
|
||||
layout->addWidget(new GButtonConfigGroup("A Button", Config::BUTTON_A, this, ui.buttonsTab));
|
||||
layout->addWidget(new GButtonConfigGroup("B Button", Config::BUTTON_B, this, ui.buttonsTab));
|
||||
layout->addWidget(new GButtonConfigGroup("X Button", Config::BUTTON_X, this, ui.buttonsTab));
|
||||
layout->addWidget(new GButtonConfigGroup("Y Button", Config::BUTTON_Y, this, ui.buttonsTab));
|
||||
layout->addWidget(new GButtonConfigGroup("Z Button", Config::BUTTON_Z, this, ui.buttonsTab));
|
||||
layout->addWidget(new GButtonConfigGroup("L Trigger", Config::TRIGGER_L, this, ui.buttonsTab));
|
||||
layout->addWidget(new GButtonConfigGroup("R Trigger", Config::TRIGGER_R, this, ui.buttonsTab));
|
||||
layout->addWidget(new GButtonConfigGroup("Start Button", Config::BUTTON_START, this, ui.buttonsTab));
|
||||
|
||||
memcpy(config, initial_config, sizeof(config));
|
||||
|
||||
emit ActivePortChanged(config[0]);
|
||||
}
|
||||
|
||||
void GControllerConfig::OnKeyConfigChanged(common::Config::Control id, int key, const QString& name)
|
||||
{
|
||||
if (InputSourceJoypad())
|
||||
{
|
||||
config[GetActiveController()].pads.key_code[id] = key;
|
||||
}
|
||||
else
|
||||
{
|
||||
config[GetActiveController()].keys.key_code[id] = key;
|
||||
}
|
||||
emit ActivePortChanged(config[GetActiveController()]);
|
||||
}
|
||||
|
||||
int GControllerConfig::GetActiveController()
|
||||
{
|
||||
return ui.activeControllerCB->currentIndex();
|
||||
}
|
||||
|
||||
bool GControllerConfig::InputSourceJoypad()
|
||||
{
|
||||
return ui.inputSourceCB->currentIndex() == 1;
|
||||
}
|
||||
|
||||
GControllerConfigDialog::GControllerConfigDialog(common::Config::ControllerPort* controller_ports, QWidget* parent) : QDialog(parent), config_ptr(controller_ports)
|
||||
{
|
||||
setWindowTitle(tr("Input configuration"));
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
config_widget = new GControllerConfig(controller_ports, this);
|
||||
layout->addWidget(config_widget);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
connect(buttons, SIGNAL(rejected()), this, SLOT(reject()));
|
||||
connect(buttons, SIGNAL(accepted()), this, SLOT(accept()));
|
||||
|
||||
connect(this, SIGNAL(accepted()), this, SLOT(EnableChanges()));
|
||||
|
||||
layout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setLayout(layout);
|
||||
setModal(true);
|
||||
show();
|
||||
}
|
||||
|
||||
void GControllerConfigDialog::EnableChanges()
|
||||
{
|
||||
for (unsigned int i = 0; i < 4; ++i)
|
||||
{
|
||||
memcpy(&config_ptr[i], &config_widget->GetControllerConfig(i), sizeof(common::Config::ControllerPort));
|
||||
|
||||
if (common::g_config) {
|
||||
// Apply changes if running a game
|
||||
memcpy(&common::g_config->controller_ports(i), &config_widget->GetControllerConfig(i), sizeof(common::Config::ControllerPort));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
@ -1,56 +0,0 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#ifndef _CONTROLLER_CONFIG_HXX_
|
||||
#define _CONTROLLER_CONFIG_HXX_
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
//#include "ui_controller_config.h"
|
||||
|
||||
/* TODO(bunnei): ImplementMe
|
||||
|
||||
#include "config.h"
|
||||
|
||||
class GControllerConfig : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GControllerConfig(common::Config::ControllerPort* initial_config, QWidget* parent = NULL);
|
||||
|
||||
const common::Config::ControllerPort& GetControllerConfig(int index) const { return config[index]; }
|
||||
|
||||
signals:
|
||||
void ActivePortChanged(const common::Config::ControllerPort&);
|
||||
|
||||
public slots:
|
||||
void OnKeyConfigChanged(common::Config::Control id, int key, const QString& name);
|
||||
|
||||
private:
|
||||
int GetActiveController();
|
||||
bool InputSourceJoypad();
|
||||
|
||||
Ui::ControllerConfig ui;
|
||||
common::Config::ControllerPort config[4];
|
||||
};
|
||||
|
||||
class GControllerConfigDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GControllerConfigDialog(common::Config::ControllerPort* controller_ports, QWidget* parent = NULL);
|
||||
|
||||
public slots:
|
||||
void EnableChanges();
|
||||
|
||||
private:
|
||||
GControllerConfig* config_widget;
|
||||
common::Config::ControllerPort* config_ptr;
|
||||
};
|
||||
|
||||
*/
|
||||
|
||||
#endif // _CONTROLLER_CONFIG_HXX_
|
@ -1,308 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ControllerConfig</class>
|
||||
<widget class="QWidget" name="ControllerConfig">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>503</width>
|
||||
<height>293</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Controller Configuration</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="activeControllerCB">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Controller 1</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Controller 2</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Controller 3</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Controller 4</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QCheckBox" name="checkBox">
|
||||
<property name="text">
|
||||
<string>Enabled</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="inputSourceCB">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Keyboard</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Joypad</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Active Controller:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Input Source:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="mainStickTab">
|
||||
<attribute name="title">
|
||||
<string>Main Stick</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="2" column="2">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="cStickTab">
|
||||
<attribute name="title">
|
||||
<string>C-Stick</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="1" column="0">
|
||||
<spacer name="horizontalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<spacer name="verticalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="dPadTab">
|
||||
<attribute name="title">
|
||||
<string>D-Pad</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="1" column="2">
|
||||
<spacer name="horizontalSpacer_7">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<spacer name="verticalSpacer_7">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<spacer name="verticalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<spacer name="horizontalSpacer_8">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="buttonsTab">
|
||||
<attribute name="title">
|
||||
<string>Buttons</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2"/>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<slots>
|
||||
<signal>ControlsChanged()</signal>
|
||||
<signal>MainStickCleared()</signal>
|
||||
<signal>CStickCleared()</signal>
|
||||
<signal>DPadCleared()</signal>
|
||||
<signal>ButtonsCleared()</signal>
|
||||
<slot>OnControlsChanged()</slot>
|
||||
<slot>OnMainStickCleared()</slot>
|
||||
<slot>OnCStickCleared()</slot>
|
||||
<slot>OnDPadCleared()</slot>
|
||||
<slot>OnButtonsCleared()</slot>
|
||||
</slots>
|
||||
</ui>
|
@ -1,125 +0,0 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QStyle>
|
||||
#include <QGridLayout>
|
||||
#include <QKeyEvent>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
|
||||
#include "controller_config_util.h"
|
||||
|
||||
/* TODO(bunnei): ImplementMe
|
||||
GStickConfig::GStickConfig(common::Config::Control leftid, common::Config::Control rightid, common::Config::Control upid, common::Config::Control downid, QObject* change_receiver, QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
left = new GKeyConfigButton(leftid, style()->standardIcon(QStyle::SP_ArrowLeft), QString(), change_receiver, this);
|
||||
right = new GKeyConfigButton(rightid, style()->standardIcon(QStyle::SP_ArrowRight), QString(), change_receiver, this);
|
||||
up = new GKeyConfigButton(upid, style()->standardIcon(QStyle::SP_ArrowUp), QString(), change_receiver, this);
|
||||
down = new GKeyConfigButton(downid, style()->standardIcon(QStyle::SP_ArrowDown), QString(), change_receiver, this);
|
||||
clear = new QPushButton(tr("Clear"), this);
|
||||
|
||||
QGridLayout* layout = new QGridLayout(this);
|
||||
layout->addWidget(left, 1, 0);
|
||||
layout->addWidget(right, 1, 2);
|
||||
layout->addWidget(up, 0, 1);
|
||||
layout->addWidget(down, 2, 1);
|
||||
layout->addWidget(clear, 1, 1);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
GKeyConfigButton::GKeyConfigButton(common::Config::Control id, const QIcon& icon, const QString& text, QObject* change_receiver, QWidget* parent) : QPushButton(icon, text, parent), id(id), inputGrabbed(false)
|
||||
{
|
||||
connect(this, SIGNAL(clicked()), this, SLOT(OnClicked()));
|
||||
connect(this, SIGNAL(KeyAssigned(common::Config::Control, int, const QString&)), change_receiver, SLOT(OnKeyConfigChanged(common::Config::Control, int, const QString&)));
|
||||
connect(change_receiver, SIGNAL(ActivePortChanged(const common::Config::ControllerPort&)), this, SLOT(OnActivePortChanged(const common::Config::ControllerPort&)));
|
||||
}
|
||||
|
||||
GKeyConfigButton::GKeyConfigButton(common::Config::Control id, const QString& text, QObject* change_receiver, QWidget* parent) : QPushButton(text, parent), id(id), inputGrabbed(false)
|
||||
{
|
||||
connect(this, SIGNAL(clicked()), this, SLOT(OnClicked()));
|
||||
connect(this, SIGNAL(KeyAssigned(common::Config::Control, int, const QString&)), change_receiver, SLOT(OnKeyConfigChanged(common::Config::Control, int, const QString&)));
|
||||
connect(change_receiver, SIGNAL(ActivePortChanged(const common::Config::ControllerPort&)), this, SLOT(OnActivePortChanged(const common::Config::ControllerPort&)));
|
||||
}
|
||||
|
||||
void GKeyConfigButton::OnActivePortChanged(const common::Config::ControllerPort& config)
|
||||
{
|
||||
// TODO: Doesn't use joypad struct if that's the input source...
|
||||
QString text = QKeySequence(config.keys.key_code[id]).toString(); // has a nicer format
|
||||
if (config.keys.key_code[id] == Qt::Key_Shift) text = tr("Shift");
|
||||
else if (config.keys.key_code[id] == Qt::Key_Control) text = tr("Control");
|
||||
else if (config.keys.key_code[id] == Qt::Key_Alt) text = tr("Alt");
|
||||
else if (config.keys.key_code[id] == Qt::Key_Meta) text = tr("Meta");
|
||||
setText(text);
|
||||
}
|
||||
|
||||
void GKeyConfigButton::OnClicked()
|
||||
{
|
||||
grabKeyboard();
|
||||
grabMouse();
|
||||
inputGrabbed = true;
|
||||
|
||||
old_text = text();
|
||||
setText(tr("Input..."));
|
||||
}
|
||||
|
||||
void GKeyConfigButton::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
if (inputGrabbed)
|
||||
{
|
||||
releaseKeyboard();
|
||||
releaseMouse();
|
||||
setText(QString());
|
||||
|
||||
// TODO: Doesn't capture "return" key
|
||||
// TODO: This doesn't quite work well, yet... find a better way
|
||||
QString text = QKeySequence(event->key()).toString(); // has a nicer format than event->text()
|
||||
int key = event->key();
|
||||
if (event->modifiers() == Qt::ShiftModifier) { text = tr("Shift"); key = Qt::Key_Shift; }
|
||||
else if (event->modifiers() == Qt::ControlModifier) { text = tr("Ctrl"); key = Qt::Key_Control; }
|
||||
else if (event->modifiers() == Qt::AltModifier) { text = tr("Alt"); key = Qt::Key_Alt; }
|
||||
else if (event->modifiers() == Qt::MetaModifier) { text = tr("Meta"); key = Qt::Key_Meta; }
|
||||
|
||||
setText(old_text);
|
||||
emit KeyAssigned(id, key, text);
|
||||
|
||||
inputGrabbed = false;
|
||||
|
||||
// TODO: Keys like "return" cause another keyPressEvent to be generated after this one...
|
||||
}
|
||||
|
||||
QPushButton::keyPressEvent(event); // TODO: Necessary?
|
||||
}
|
||||
|
||||
void GKeyConfigButton::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
// Abort key assignment
|
||||
if (inputGrabbed)
|
||||
{
|
||||
releaseKeyboard();
|
||||
releaseMouse();
|
||||
setText(old_text);
|
||||
inputGrabbed = false;
|
||||
}
|
||||
|
||||
QAbstractButton::mousePressEvent(event);
|
||||
}
|
||||
|
||||
GButtonConfigGroup::GButtonConfigGroup(const QString& name, common::Config::Control id, QObject* change_receiver, QWidget* parent) : QWidget(parent), id(id)
|
||||
{
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
|
||||
QPushButton* clear_button = new QPushButton(tr("Clear"));
|
||||
|
||||
layout->addWidget(new QLabel(name, this));
|
||||
layout->addWidget(config_button = new GKeyConfigButton(id, QString(), change_receiver, this));
|
||||
layout->addWidget(clear_button);
|
||||
|
||||
// TODO: connect config_button, clear_button
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
*/
|
@ -1,82 +0,0 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#ifndef _CONTROLLER_CONFIG_UTIL_HXX_
|
||||
#define _CONTROLLER_CONFIG_UTIL_HXX_
|
||||
|
||||
#include <QWidget>
|
||||
#include <QPushButton>
|
||||
|
||||
/* TODO(bunnei): ImplementMe
|
||||
|
||||
#include "config.h"
|
||||
|
||||
class GStickConfig : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// change_receiver needs to have a OnKeyConfigChanged(common::Config::Control, int, const QString&) slot!
|
||||
GStickConfig(common::Config::Control leftid, common::Config::Control rightid, common::Config::Control upid, common::Config::Control downid, QObject* change_receiver, QWidget* parent = NULL);
|
||||
|
||||
signals:
|
||||
void LeftChanged();
|
||||
void RightChanged();
|
||||
void UpChanged();
|
||||
void DownChanged();
|
||||
|
||||
private:
|
||||
QPushButton* left;
|
||||
QPushButton* right;
|
||||
QPushButton* up;
|
||||
QPushButton* down;
|
||||
|
||||
QPushButton* clear;
|
||||
};
|
||||
|
||||
class GKeyConfigButton : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// TODO: change_receiver also needs to have an ActivePortChanged(const common::Config::ControllerPort&) signal
|
||||
// change_receiver needs to have a OnKeyConfigChanged(common::Config::Control, int, const QString&) slot!
|
||||
GKeyConfigButton(common::Config::Control id, const QIcon& icon, const QString& text, QObject* change_receiver, QWidget* parent);
|
||||
GKeyConfigButton(common::Config::Control id, const QString& text, QObject* change_receiver, QWidget* parent);
|
||||
|
||||
signals:
|
||||
void KeyAssigned(common::Config::Control id, int key, const QString& text);
|
||||
|
||||
private slots:
|
||||
void OnActivePortChanged(const common::Config::ControllerPort& config);
|
||||
|
||||
void OnClicked();
|
||||
|
||||
void keyPressEvent(QKeyEvent* event); // TODO: bGrabbed?
|
||||
void mousePressEvent(QMouseEvent* event);
|
||||
|
||||
private:
|
||||
common::Config::Control id;
|
||||
bool inputGrabbed;
|
||||
|
||||
QString old_text;
|
||||
};
|
||||
|
||||
class GButtonConfigGroup : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// change_receiver needs to have a OnKeyConfigChanged(common::Config::Control, int, const QString&) slot!
|
||||
GButtonConfigGroup(const QString& name, common::Config::Control id, QObject* change_receiver, QWidget* parent = NULL);
|
||||
|
||||
private:
|
||||
GKeyConfigButton* config_button;
|
||||
|
||||
common::Config::Control id;
|
||||
};
|
||||
|
||||
*/
|
||||
|
||||
#endif // _CONTROLLER_CONFIG_HXX_
|
@ -37,10 +37,13 @@ void CallstackWidget::OnDebugModeEntered()
|
||||
int counter = 0;
|
||||
for (u32 addr = 0x10000000; addr >= sp; addr -= 4)
|
||||
{
|
||||
if (!Memory::IsValidVirtualAddress(addr))
|
||||
break;
|
||||
|
||||
const u32 ret_addr = Memory::Read32(addr);
|
||||
const u32 call_addr = ret_addr - 4; //get call address???
|
||||
|
||||
if (Memory::GetPointer(call_addr) == nullptr)
|
||||
if (!Memory::IsValidVirtualAddress(call_addr))
|
||||
break;
|
||||
|
||||
/* TODO (mattvail) clean me, move to debugger interface */
|
||||
|
@ -50,123 +50,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent)
|
||||
: QDockWidget(tr("Texture 0x%1").arg(info.physical_address, 8, 16, QLatin1Char('0'))),
|
||||
info(info) {
|
||||
|
||||
QWidget* main_widget = new QWidget;
|
||||
|
||||
QLabel* image_widget = new QLabel;
|
||||
|
||||
connect(this, SIGNAL(UpdatePixmap(const QPixmap&)), image_widget, SLOT(setPixmap(const QPixmap&)));
|
||||
|
||||
CSpinBox* phys_address_spinbox = new CSpinBox;
|
||||
phys_address_spinbox->SetBase(16);
|
||||
phys_address_spinbox->SetRange(0, 0xFFFFFFFF);
|
||||
phys_address_spinbox->SetPrefix("0x");
|
||||
phys_address_spinbox->SetValue(info.physical_address);
|
||||
connect(phys_address_spinbox, SIGNAL(ValueChanged(qint64)), this, SLOT(OnAddressChanged(qint64)));
|
||||
|
||||
QComboBox* format_choice = new QComboBox;
|
||||
format_choice->addItem(tr("RGBA8"));
|
||||
format_choice->addItem(tr("RGB8"));
|
||||
format_choice->addItem(tr("RGB5A1"));
|
||||
format_choice->addItem(tr("RGB565"));
|
||||
format_choice->addItem(tr("RGBA4"));
|
||||
format_choice->addItem(tr("IA8"));
|
||||
format_choice->addItem(tr("RG8"));
|
||||
format_choice->addItem(tr("I8"));
|
||||
format_choice->addItem(tr("A8"));
|
||||
format_choice->addItem(tr("IA4"));
|
||||
format_choice->addItem(tr("I4"));
|
||||
format_choice->addItem(tr("A4"));
|
||||
format_choice->addItem(tr("ETC1"));
|
||||
format_choice->addItem(tr("ETC1A4"));
|
||||
format_choice->setCurrentIndex(static_cast<int>(info.format));
|
||||
connect(format_choice, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFormatChanged(int)));
|
||||
|
||||
QSpinBox* width_spinbox = new QSpinBox;
|
||||
width_spinbox->setMaximum(65535);
|
||||
width_spinbox->setValue(info.width);
|
||||
connect(width_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnWidthChanged(int)));
|
||||
|
||||
QSpinBox* height_spinbox = new QSpinBox;
|
||||
height_spinbox->setMaximum(65535);
|
||||
height_spinbox->setValue(info.height);
|
||||
connect(height_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnHeightChanged(int)));
|
||||
|
||||
QSpinBox* stride_spinbox = new QSpinBox;
|
||||
stride_spinbox->setMaximum(65535 * 4);
|
||||
stride_spinbox->setValue(info.stride);
|
||||
connect(stride_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnStrideChanged(int)));
|
||||
|
||||
QVBoxLayout* main_layout = new QVBoxLayout;
|
||||
main_layout->addWidget(image_widget);
|
||||
|
||||
{
|
||||
QHBoxLayout* sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Source Address:")));
|
||||
sub_layout->addWidget(phys_address_spinbox);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
|
||||
{
|
||||
QHBoxLayout* sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Format")));
|
||||
sub_layout->addWidget(format_choice);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
|
||||
{
|
||||
QHBoxLayout* sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Width:")));
|
||||
sub_layout->addWidget(width_spinbox);
|
||||
sub_layout->addStretch();
|
||||
sub_layout->addWidget(new QLabel(tr("Height:")));
|
||||
sub_layout->addWidget(height_spinbox);
|
||||
sub_layout->addStretch();
|
||||
sub_layout->addWidget(new QLabel(tr("Stride:")));
|
||||
sub_layout->addWidget(stride_spinbox);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
|
||||
main_widget->setLayout(main_layout);
|
||||
|
||||
emit UpdatePixmap(ReloadPixmap());
|
||||
|
||||
setWidget(main_widget);
|
||||
}
|
||||
|
||||
void TextureInfoDockWidget::OnAddressChanged(qint64 value) {
|
||||
info.physical_address = value;
|
||||
emit UpdatePixmap(ReloadPixmap());
|
||||
}
|
||||
|
||||
void TextureInfoDockWidget::OnFormatChanged(int value) {
|
||||
info.format = static_cast<Pica::Regs::TextureFormat>(value);
|
||||
emit UpdatePixmap(ReloadPixmap());
|
||||
}
|
||||
|
||||
void TextureInfoDockWidget::OnWidthChanged(int value) {
|
||||
info.width = value;
|
||||
emit UpdatePixmap(ReloadPixmap());
|
||||
}
|
||||
|
||||
void TextureInfoDockWidget::OnHeightChanged(int value) {
|
||||
info.height = value;
|
||||
emit UpdatePixmap(ReloadPixmap());
|
||||
}
|
||||
|
||||
void TextureInfoDockWidget::OnStrideChanged(int value) {
|
||||
info.stride = value;
|
||||
emit UpdatePixmap(ReloadPixmap());
|
||||
}
|
||||
|
||||
QPixmap TextureInfoDockWidget::ReloadPixmap() const {
|
||||
u8* src = Memory::GetPhysicalPointer(info.physical_address);
|
||||
return QPixmap::fromImage(LoadTexture(src, info));
|
||||
}
|
||||
|
||||
GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) {
|
||||
|
||||
}
|
||||
@ -249,16 +132,16 @@ void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) {
|
||||
index = 0;
|
||||
} else if (COMMAND_IN_RANGE(command_id, texture1)) {
|
||||
index = 1;
|
||||
} else {
|
||||
} else if (COMMAND_IN_RANGE(command_id, texture2)) {
|
||||
index = 2;
|
||||
} else {
|
||||
UNREACHABLE_MSG("Unknown texture command");
|
||||
}
|
||||
auto config = Pica::g_state.regs.GetTextures()[index].config;
|
||||
auto format = Pica::g_state.regs.GetTextures()[index].format;
|
||||
auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format);
|
||||
|
||||
// TODO: Instead, emit a signal here to be caught by the main window widget.
|
||||
auto main_window = static_cast<QMainWindow*>(parent());
|
||||
main_window->tabifyDockWidget(this, new TextureInfoDockWidget(info, main_window));
|
||||
// TODO: Open a surface debugger
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,25 +61,3 @@ private:
|
||||
QWidget* command_info_widget;
|
||||
QPushButton* toggle_tracing;
|
||||
};
|
||||
|
||||
class TextureInfoDockWidget : public QDockWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent = nullptr);
|
||||
|
||||
signals:
|
||||
void UpdatePixmap(const QPixmap& pixmap);
|
||||
|
||||
private slots:
|
||||
void OnAddressChanged(qint64 value);
|
||||
void OnFormatChanged(int value);
|
||||
void OnWidthChanged(int value);
|
||||
void OnHeightChanged(int value);
|
||||
void OnStrideChanged(int value);
|
||||
|
||||
private:
|
||||
QPixmap ReloadPixmap() const;
|
||||
|
||||
Pica::DebugUtils::TextureInfo info;
|
||||
};
|
||||
|
@ -1,356 +0,0 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QBoxLayout>
|
||||
#include <QComboBox>
|
||||
#include <QDebug>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
|
||||
#include "citra_qt/debugger/graphics_framebuffer.h"
|
||||
#include "citra_qt/util/spinbox.h"
|
||||
|
||||
#include "common/color.h"
|
||||
|
||||
#include "core/memory.h"
|
||||
#include "core/hw/gpu.h"
|
||||
|
||||
#include "video_core/pica.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/utils.h"
|
||||
|
||||
GraphicsFramebufferWidget::GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context,
|
||||
QWidget* parent)
|
||||
: BreakPointObserverDock(debug_context, tr("Pica Framebuffer"), parent),
|
||||
framebuffer_source(Source::PicaTarget)
|
||||
{
|
||||
setObjectName("PicaFramebuffer");
|
||||
|
||||
framebuffer_source_list = new QComboBox;
|
||||
framebuffer_source_list->addItem(tr("Active Render Target"));
|
||||
framebuffer_source_list->addItem(tr("Active Depth Buffer"));
|
||||
framebuffer_source_list->addItem(tr("Custom"));
|
||||
framebuffer_source_list->setCurrentIndex(static_cast<int>(framebuffer_source));
|
||||
|
||||
framebuffer_address_control = new CSpinBox;
|
||||
framebuffer_address_control->SetBase(16);
|
||||
framebuffer_address_control->SetRange(0, 0xFFFFFFFF);
|
||||
framebuffer_address_control->SetPrefix("0x");
|
||||
|
||||
framebuffer_width_control = new QSpinBox;
|
||||
framebuffer_width_control->setMinimum(1);
|
||||
framebuffer_width_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum
|
||||
|
||||
framebuffer_height_control = new QSpinBox;
|
||||
framebuffer_height_control->setMinimum(1);
|
||||
framebuffer_height_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum
|
||||
|
||||
framebuffer_format_control = new QComboBox;
|
||||
framebuffer_format_control->addItem(tr("RGBA8"));
|
||||
framebuffer_format_control->addItem(tr("RGB8"));
|
||||
framebuffer_format_control->addItem(tr("RGB5A1"));
|
||||
framebuffer_format_control->addItem(tr("RGB565"));
|
||||
framebuffer_format_control->addItem(tr("RGBA4"));
|
||||
framebuffer_format_control->addItem(tr("D16"));
|
||||
framebuffer_format_control->addItem(tr("D24"));
|
||||
framebuffer_format_control->addItem(tr("D24X8"));
|
||||
framebuffer_format_control->addItem(tr("X24S8"));
|
||||
framebuffer_format_control->addItem(tr("(unknown)"));
|
||||
|
||||
// TODO: This QLabel should shrink the image to the available space rather than just expanding...
|
||||
framebuffer_picture_label = new QLabel;
|
||||
|
||||
auto enlarge_button = new QPushButton(tr("Enlarge"));
|
||||
|
||||
// Connections
|
||||
connect(this, SIGNAL(Update()), this, SLOT(OnUpdate()));
|
||||
connect(framebuffer_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferSourceChanged(int)));
|
||||
connect(framebuffer_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnFramebufferAddressChanged(qint64)));
|
||||
connect(framebuffer_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferWidthChanged(int)));
|
||||
connect(framebuffer_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferHeightChanged(int)));
|
||||
connect(framebuffer_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferFormatChanged(int)));
|
||||
|
||||
auto main_widget = new QWidget;
|
||||
auto main_layout = new QVBoxLayout;
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Source:")));
|
||||
sub_layout->addWidget(framebuffer_source_list);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Virtual Address:")));
|
||||
sub_layout->addWidget(framebuffer_address_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Width:")));
|
||||
sub_layout->addWidget(framebuffer_width_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Height:")));
|
||||
sub_layout->addWidget(framebuffer_height_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Format:")));
|
||||
sub_layout->addWidget(framebuffer_format_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
main_layout->addWidget(framebuffer_picture_label);
|
||||
main_layout->addWidget(enlarge_button);
|
||||
main_widget->setLayout(main_layout);
|
||||
setWidget(main_widget);
|
||||
|
||||
// Load current data - TODO: Make sure this works when emulation is not running
|
||||
if (debug_context && debug_context->at_breakpoint)
|
||||
emit Update();
|
||||
widget()->setEnabled(false); // TODO: Only enable if currently at breakpoint
|
||||
}
|
||||
|
||||
void GraphicsFramebufferWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data)
|
||||
{
|
||||
emit Update();
|
||||
widget()->setEnabled(true);
|
||||
}
|
||||
|
||||
void GraphicsFramebufferWidget::OnResumed()
|
||||
{
|
||||
widget()->setEnabled(false);
|
||||
}
|
||||
|
||||
void GraphicsFramebufferWidget::OnFramebufferSourceChanged(int new_value)
|
||||
{
|
||||
framebuffer_source = static_cast<Source>(new_value);
|
||||
emit Update();
|
||||
}
|
||||
|
||||
void GraphicsFramebufferWidget::OnFramebufferAddressChanged(qint64 new_value)
|
||||
{
|
||||
if (framebuffer_address != new_value) {
|
||||
framebuffer_address = static_cast<unsigned>(new_value);
|
||||
|
||||
framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsFramebufferWidget::OnFramebufferWidthChanged(int new_value)
|
||||
{
|
||||
if (framebuffer_width != static_cast<unsigned>(new_value)) {
|
||||
framebuffer_width = static_cast<unsigned>(new_value);
|
||||
|
||||
framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsFramebufferWidget::OnFramebufferHeightChanged(int new_value)
|
||||
{
|
||||
if (framebuffer_height != static_cast<unsigned>(new_value)) {
|
||||
framebuffer_height = static_cast<unsigned>(new_value);
|
||||
|
||||
framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsFramebufferWidget::OnFramebufferFormatChanged(int new_value)
|
||||
{
|
||||
if (framebuffer_format != static_cast<Format>(new_value)) {
|
||||
framebuffer_format = static_cast<Format>(new_value);
|
||||
|
||||
framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsFramebufferWidget::OnUpdate()
|
||||
{
|
||||
QPixmap pixmap;
|
||||
|
||||
switch (framebuffer_source) {
|
||||
case Source::PicaTarget:
|
||||
{
|
||||
// TODO: Store a reference to the registers in the debug context instead of accessing them directly...
|
||||
|
||||
const auto& framebuffer = Pica::g_state.regs.framebuffer;
|
||||
|
||||
framebuffer_address = framebuffer.GetColorBufferPhysicalAddress();
|
||||
framebuffer_width = framebuffer.GetWidth();
|
||||
framebuffer_height = framebuffer.GetHeight();
|
||||
|
||||
switch (framebuffer.color_format) {
|
||||
case Pica::Regs::ColorFormat::RGBA8:
|
||||
framebuffer_format = Format::RGBA8;
|
||||
break;
|
||||
|
||||
case Pica::Regs::ColorFormat::RGB8:
|
||||
framebuffer_format = Format::RGB8;
|
||||
break;
|
||||
|
||||
case Pica::Regs::ColorFormat::RGB5A1:
|
||||
framebuffer_format = Format::RGB5A1;
|
||||
break;
|
||||
|
||||
case Pica::Regs::ColorFormat::RGB565:
|
||||
framebuffer_format = Format::RGB565;
|
||||
break;
|
||||
|
||||
case Pica::Regs::ColorFormat::RGBA4:
|
||||
framebuffer_format = Format::RGBA4;
|
||||
break;
|
||||
|
||||
default:
|
||||
framebuffer_format = Format::Unknown;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Source::DepthBuffer:
|
||||
{
|
||||
const auto& framebuffer = Pica::g_state.regs.framebuffer;
|
||||
|
||||
framebuffer_address = framebuffer.GetDepthBufferPhysicalAddress();
|
||||
framebuffer_width = framebuffer.GetWidth();
|
||||
framebuffer_height = framebuffer.GetHeight();
|
||||
|
||||
switch (framebuffer.depth_format) {
|
||||
case Pica::Regs::DepthFormat::D16:
|
||||
framebuffer_format = Format::D16;
|
||||
break;
|
||||
|
||||
case Pica::Regs::DepthFormat::D24:
|
||||
framebuffer_format = Format::D24;
|
||||
break;
|
||||
|
||||
case Pica::Regs::DepthFormat::D24S8:
|
||||
framebuffer_format = Format::D24X8;
|
||||
break;
|
||||
|
||||
default:
|
||||
framebuffer_format = Format::Unknown;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Source::Custom:
|
||||
{
|
||||
// Keep user-specified values
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
qDebug() << "Unknown framebuffer source " << static_cast<int>(framebuffer_source);
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Implement a good way to visualize alpha components!
|
||||
// TODO: Unify this decoding code with the texture decoder
|
||||
u32 bytes_per_pixel = GraphicsFramebufferWidget::BytesPerPixel(framebuffer_format);
|
||||
|
||||
QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32);
|
||||
u8* buffer = Memory::GetPhysicalPointer(framebuffer_address);
|
||||
|
||||
for (unsigned int y = 0; y < framebuffer_height; ++y) {
|
||||
for (unsigned int x = 0; x < framebuffer_width; ++x) {
|
||||
const u32 coarse_y = y & ~7;
|
||||
u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * framebuffer_width * bytes_per_pixel;
|
||||
const u8* pixel = buffer + offset;
|
||||
Math::Vec4<u8> color = { 0, 0, 0, 0 };
|
||||
|
||||
switch (framebuffer_format) {
|
||||
case Format::RGBA8:
|
||||
color = Color::DecodeRGBA8(pixel);
|
||||
break;
|
||||
case Format::RGB8:
|
||||
color = Color::DecodeRGB8(pixel);
|
||||
break;
|
||||
case Format::RGB5A1:
|
||||
color = Color::DecodeRGB5A1(pixel);
|
||||
break;
|
||||
case Format::RGB565:
|
||||
color = Color::DecodeRGB565(pixel);
|
||||
break;
|
||||
case Format::RGBA4:
|
||||
color = Color::DecodeRGBA4(pixel);
|
||||
break;
|
||||
case Format::D16:
|
||||
{
|
||||
u32 data = Color::DecodeD16(pixel);
|
||||
color.r() = data & 0xFF;
|
||||
color.g() = (data >> 8) & 0xFF;
|
||||
break;
|
||||
}
|
||||
case Format::D24:
|
||||
{
|
||||
u32 data = Color::DecodeD24(pixel);
|
||||
color.r() = data & 0xFF;
|
||||
color.g() = (data >> 8) & 0xFF;
|
||||
color.b() = (data >> 16) & 0xFF;
|
||||
break;
|
||||
}
|
||||
case Format::D24X8:
|
||||
{
|
||||
Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
|
||||
color.r() = data.x & 0xFF;
|
||||
color.g() = (data.x >> 8) & 0xFF;
|
||||
color.b() = (data.x >> 16) & 0xFF;
|
||||
break;
|
||||
}
|
||||
case Format::X24S8:
|
||||
{
|
||||
Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
|
||||
color.r() = color.g() = color.b() = data.y;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qDebug() << "Unknown fb color format " << static_cast<int>(framebuffer_format);
|
||||
break;
|
||||
}
|
||||
|
||||
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255));
|
||||
}
|
||||
}
|
||||
pixmap = QPixmap::fromImage(decoded_image);
|
||||
|
||||
framebuffer_address_control->SetValue(framebuffer_address);
|
||||
framebuffer_width_control->setValue(framebuffer_width);
|
||||
framebuffer_height_control->setValue(framebuffer_height);
|
||||
framebuffer_format_control->setCurrentIndex(static_cast<int>(framebuffer_format));
|
||||
framebuffer_picture_label->setPixmap(pixmap);
|
||||
}
|
||||
|
||||
u32 GraphicsFramebufferWidget::BytesPerPixel(GraphicsFramebufferWidget::Format format) {
|
||||
switch (format) {
|
||||
case Format::RGBA8:
|
||||
case Format::D24X8:
|
||||
case Format::X24S8:
|
||||
return 4;
|
||||
case Format::RGB8:
|
||||
case Format::D24:
|
||||
return 3;
|
||||
case Format::RGB5A1:
|
||||
case Format::RGB565:
|
||||
case Format::RGBA4:
|
||||
case Format::D16:
|
||||
return 2;
|
||||
default:
|
||||
UNREACHABLE_MSG("GraphicsFramebufferWidget::BytesPerPixel: this "
|
||||
"should not be reached as this function should "
|
||||
"be given a format which is in "
|
||||
"GraphicsFramebufferWidget::Format. Instead got %i",
|
||||
static_cast<int>(format));
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "citra_qt/debugger/graphics_breakpoint_observer.h"
|
||||
|
||||
class QComboBox;
|
||||
class QLabel;
|
||||
class QSpinBox;
|
||||
|
||||
class CSpinBox;
|
||||
|
||||
class GraphicsFramebufferWidget : public BreakPointObserverDock {
|
||||
Q_OBJECT
|
||||
|
||||
using Event = Pica::DebugContext::Event;
|
||||
|
||||
enum class Source {
|
||||
PicaTarget = 0,
|
||||
DepthBuffer = 1,
|
||||
Custom = 2,
|
||||
|
||||
// TODO: Add GPU framebuffer sources!
|
||||
};
|
||||
|
||||
enum class Format {
|
||||
RGBA8 = 0,
|
||||
RGB8 = 1,
|
||||
RGB5A1 = 2,
|
||||
RGB565 = 3,
|
||||
RGBA4 = 4,
|
||||
D16 = 5,
|
||||
D24 = 6,
|
||||
D24X8 = 7,
|
||||
X24S8 = 8,
|
||||
Unknown = 9
|
||||
};
|
||||
|
||||
static u32 BytesPerPixel(Format format);
|
||||
|
||||
public:
|
||||
GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void OnFramebufferSourceChanged(int new_value);
|
||||
void OnFramebufferAddressChanged(qint64 new_value);
|
||||
void OnFramebufferWidthChanged(int new_value);
|
||||
void OnFramebufferHeightChanged(int new_value);
|
||||
void OnFramebufferFormatChanged(int new_value);
|
||||
void OnUpdate();
|
||||
|
||||
private slots:
|
||||
void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override;
|
||||
void OnResumed() override;
|
||||
|
||||
signals:
|
||||
void Update();
|
||||
|
||||
private:
|
||||
|
||||
QComboBox* framebuffer_source_list;
|
||||
CSpinBox* framebuffer_address_control;
|
||||
QSpinBox* framebuffer_width_control;
|
||||
QSpinBox* framebuffer_height_control;
|
||||
QComboBox* framebuffer_format_control;
|
||||
|
||||
QLabel* framebuffer_picture_label;
|
||||
|
||||
Source framebuffer_source;
|
||||
unsigned framebuffer_address;
|
||||
unsigned framebuffer_width;
|
||||
unsigned framebuffer_height;
|
||||
Format framebuffer_format;
|
||||
};
|
736
src/citra_qt/debugger/graphics_surface.cpp
Normal file
736
src/citra_qt/debugger/graphics_surface.cpp
Normal file
@ -0,0 +1,736 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QBoxLayout>
|
||||
#include <QComboBox>
|
||||
#include <QDebug>
|
||||
#include <QFileDialog>
|
||||
#include <QLabel>
|
||||
#include <QMouseEvent>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QSpinBox>
|
||||
|
||||
#include "citra_qt/debugger/graphics_surface.h"
|
||||
#include "citra_qt/util/spinbox.h"
|
||||
|
||||
#include "common/color.h"
|
||||
|
||||
#include "core/memory.h"
|
||||
#include "core/hw/gpu.h"
|
||||
|
||||
#include "video_core/pica.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/utils.h"
|
||||
|
||||
SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) : QLabel(parent), surface_widget(surface_widget_) {}
|
||||
SurfacePicture::~SurfacePicture() {}
|
||||
|
||||
void SurfacePicture::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
// Only do something while the left mouse button is held down
|
||||
if (!(event->buttons() & Qt::LeftButton))
|
||||
return;
|
||||
|
||||
if (pixmap() == nullptr)
|
||||
return;
|
||||
|
||||
if (surface_widget)
|
||||
surface_widget->Pick(event->x() * pixmap()->width() / width(),
|
||||
event->y() * pixmap()->height() / height());
|
||||
}
|
||||
|
||||
void SurfacePicture::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
// We also want to handle the event if the user moves the mouse while holding down the LMB
|
||||
mousePressEvent(event);
|
||||
}
|
||||
|
||||
|
||||
GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context,
|
||||
QWidget* parent)
|
||||
: BreakPointObserverDock(debug_context, tr("Pica Surface Viewer"), parent),
|
||||
surface_source(Source::ColorBuffer)
|
||||
{
|
||||
setObjectName("PicaSurface");
|
||||
|
||||
surface_source_list = new QComboBox;
|
||||
surface_source_list->addItem(tr("Color Buffer"));
|
||||
surface_source_list->addItem(tr("Depth Buffer"));
|
||||
surface_source_list->addItem(tr("Stencil Buffer"));
|
||||
surface_source_list->addItem(tr("Texture 0"));
|
||||
surface_source_list->addItem(tr("Texture 1"));
|
||||
surface_source_list->addItem(tr("Texture 2"));
|
||||
surface_source_list->addItem(tr("Custom"));
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(surface_source));
|
||||
|
||||
surface_address_control = new CSpinBox;
|
||||
surface_address_control->SetBase(16);
|
||||
surface_address_control->SetRange(0, 0xFFFFFFFF);
|
||||
surface_address_control->SetPrefix("0x");
|
||||
|
||||
unsigned max_dimension = 16384; // TODO: Find actual maximum
|
||||
|
||||
surface_width_control = new QSpinBox;
|
||||
surface_width_control->setRange(0, max_dimension);
|
||||
|
||||
surface_height_control = new QSpinBox;
|
||||
surface_height_control->setRange(0, max_dimension);
|
||||
|
||||
surface_picker_x_control = new QSpinBox;
|
||||
surface_picker_x_control->setRange(0, max_dimension - 1);
|
||||
|
||||
surface_picker_y_control = new QSpinBox;
|
||||
surface_picker_y_control->setRange(0, max_dimension - 1);
|
||||
|
||||
surface_format_control = new QComboBox;
|
||||
|
||||
// Color formats sorted by Pica texture format index
|
||||
surface_format_control->addItem(tr("RGBA8"));
|
||||
surface_format_control->addItem(tr("RGB8"));
|
||||
surface_format_control->addItem(tr("RGB5A1"));
|
||||
surface_format_control->addItem(tr("RGB565"));
|
||||
surface_format_control->addItem(tr("RGBA4"));
|
||||
surface_format_control->addItem(tr("IA8"));
|
||||
surface_format_control->addItem(tr("RG8"));
|
||||
surface_format_control->addItem(tr("I8"));
|
||||
surface_format_control->addItem(tr("A8"));
|
||||
surface_format_control->addItem(tr("IA4"));
|
||||
surface_format_control->addItem(tr("I4"));
|
||||
surface_format_control->addItem(tr("A4"));
|
||||
surface_format_control->addItem(tr("ETC1"));
|
||||
surface_format_control->addItem(tr("ETC1A4"));
|
||||
surface_format_control->addItem(tr("D16"));
|
||||
surface_format_control->addItem(tr("D24"));
|
||||
surface_format_control->addItem(tr("D24X8"));
|
||||
surface_format_control->addItem(tr("X24S8"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
|
||||
surface_info_label = new QLabel();
|
||||
surface_info_label->setWordWrap(true);
|
||||
|
||||
surface_picture_label = new SurfacePicture(0, this);
|
||||
surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
surface_picture_label->setScaledContents(false);
|
||||
|
||||
auto scroll_area = new QScrollArea();
|
||||
scroll_area->setBackgroundRole(QPalette::Dark);
|
||||
scroll_area->setWidgetResizable(false);
|
||||
scroll_area->setWidget(surface_picture_label);
|
||||
|
||||
save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save"));
|
||||
|
||||
// Connections
|
||||
connect(this, SIGNAL(Update()), this, SLOT(OnUpdate()));
|
||||
connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSurfaceSourceChanged(int)));
|
||||
connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnSurfaceAddressChanged(qint64)));
|
||||
connect(surface_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfaceWidthChanged(int)));
|
||||
connect(surface_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfaceHeightChanged(int)));
|
||||
connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSurfaceFormatChanged(int)));
|
||||
connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfacePickerXChanged(int)));
|
||||
connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfacePickerYChanged(int)));
|
||||
connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface()));
|
||||
|
||||
auto main_widget = new QWidget;
|
||||
auto main_layout = new QVBoxLayout;
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Source:")));
|
||||
sub_layout->addWidget(surface_source_list);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Physical Address:")));
|
||||
sub_layout->addWidget(surface_address_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Width:")));
|
||||
sub_layout->addWidget(surface_width_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Height:")));
|
||||
sub_layout->addWidget(surface_height_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Format:")));
|
||||
sub_layout->addWidget(surface_format_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
main_layout->addWidget(scroll_area);
|
||||
|
||||
auto info_layout = new QHBoxLayout;
|
||||
{
|
||||
auto xy_layout = new QVBoxLayout;
|
||||
{
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("X:")));
|
||||
sub_layout->addWidget(surface_picker_x_control);
|
||||
xy_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Y:")));
|
||||
sub_layout->addWidget(surface_picker_y_control);
|
||||
xy_layout->addLayout(sub_layout);
|
||||
}
|
||||
}
|
||||
info_layout->addLayout(xy_layout);
|
||||
surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
info_layout->addWidget(surface_info_label);
|
||||
}
|
||||
main_layout->addLayout(info_layout);
|
||||
|
||||
main_layout->addWidget(save_surface);
|
||||
main_widget->setLayout(main_layout);
|
||||
setWidget(main_widget);
|
||||
|
||||
// Load current data - TODO: Make sure this works when emulation is not running
|
||||
if (debug_context && debug_context->at_breakpoint) {
|
||||
emit Update();
|
||||
widget()->setEnabled(debug_context->at_breakpoint);
|
||||
} else {
|
||||
widget()->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data)
|
||||
{
|
||||
emit Update();
|
||||
widget()->setEnabled(true);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnResumed()
|
||||
{
|
||||
widget()->setEnabled(false);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value)
|
||||
{
|
||||
surface_source = static_cast<Source>(new_value);
|
||||
emit Update();
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value)
|
||||
{
|
||||
if (surface_address != new_value) {
|
||||
surface_address = static_cast<unsigned>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value)
|
||||
{
|
||||
if (surface_width != static_cast<unsigned>(new_value)) {
|
||||
surface_width = static_cast<unsigned>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value)
|
||||
{
|
||||
if (surface_height != static_cast<unsigned>(new_value)) {
|
||||
surface_height = static_cast<unsigned>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value)
|
||||
{
|
||||
if (surface_format != static_cast<Format>(new_value)) {
|
||||
surface_format = static_cast<Format>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value)
|
||||
{
|
||||
if (surface_picker_x != new_value) {
|
||||
surface_picker_x = new_value;
|
||||
Pick(surface_picker_x, surface_picker_y);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value)
|
||||
{
|
||||
if (surface_picker_y != new_value) {
|
||||
surface_picker_y = new_value;
|
||||
Pick(surface_picker_x, surface_picker_y);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::Pick(int x, int y)
|
||||
{
|
||||
surface_picker_x_control->setValue(x);
|
||||
surface_picker_y_control->setValue(y);
|
||||
|
||||
if (x < 0 || x >= surface_width || y < 0 || y >= surface_height) {
|
||||
surface_info_label->setText(tr("Pixel out of bounds"));
|
||||
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
return;
|
||||
}
|
||||
|
||||
u8* buffer = Memory::GetPhysicalPointer(surface_address);
|
||||
if (buffer == nullptr) {
|
||||
surface_info_label->setText(tr("(unable to access pixel data)"));
|
||||
surface_info_label->setAlignment(Qt::AlignCenter);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format);
|
||||
unsigned stride = nibbles_per_pixel * surface_width / 2;
|
||||
|
||||
unsigned bytes_per_pixel;
|
||||
bool nibble_mode = (nibbles_per_pixel == 1);
|
||||
if (nibble_mode) {
|
||||
// As nibbles are contained in a byte we still need to access one byte per nibble
|
||||
bytes_per_pixel = 1;
|
||||
} else {
|
||||
bytes_per_pixel = nibbles_per_pixel / 2;
|
||||
}
|
||||
|
||||
const u32 coarse_y = y & ~7;
|
||||
u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
|
||||
const u8* pixel = buffer + (nibble_mode ? (offset / 2) : offset);
|
||||
|
||||
auto GetText = [offset](Format format, const u8* pixel) {
|
||||
switch (format) {
|
||||
case Format::RGBA8:
|
||||
{
|
||||
auto value = Color::DecodeRGBA8(pixel) / 255.0f;
|
||||
return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
||||
.arg(QString::number(value.r(), 'f', 2))
|
||||
.arg(QString::number(value.g(), 'f', 2))
|
||||
.arg(QString::number(value.b(), 'f', 2))
|
||||
.arg(QString::number(value.a(), 'f', 2));
|
||||
}
|
||||
case Format::RGB8:
|
||||
{
|
||||
auto value = Color::DecodeRGB8(pixel) / 255.0f;
|
||||
return QString("Red: %1, Green: %2, Blue: %3")
|
||||
.arg(QString::number(value.r(), 'f', 2))
|
||||
.arg(QString::number(value.g(), 'f', 2))
|
||||
.arg(QString::number(value.b(), 'f', 2));
|
||||
}
|
||||
case Format::RGB5A1:
|
||||
{
|
||||
auto value = Color::DecodeRGB5A1(pixel) / 255.0f;
|
||||
return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
||||
.arg(QString::number(value.r(), 'f', 2))
|
||||
.arg(QString::number(value.g(), 'f', 2))
|
||||
.arg(QString::number(value.b(), 'f', 2))
|
||||
.arg(QString::number(value.a(), 'f', 2));
|
||||
}
|
||||
case Format::RGB565:
|
||||
{
|
||||
auto value = Color::DecodeRGB565(pixel) / 255.0f;
|
||||
return QString("Red: %1, Green: %2, Blue: %3")
|
||||
.arg(QString::number(value.r(), 'f', 2))
|
||||
.arg(QString::number(value.g(), 'f', 2))
|
||||
.arg(QString::number(value.b(), 'f', 2));
|
||||
}
|
||||
case Format::RGBA4:
|
||||
{
|
||||
auto value = Color::DecodeRGBA4(pixel) / 255.0f;
|
||||
return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
||||
.arg(QString::number(value.r(), 'f', 2))
|
||||
.arg(QString::number(value.g(), 'f', 2))
|
||||
.arg(QString::number(value.b(), 'f', 2))
|
||||
.arg(QString::number(value.a(), 'f', 2));
|
||||
}
|
||||
case Format::IA8:
|
||||
return QString("Index: %1, Alpha: %2")
|
||||
.arg(pixel[0])
|
||||
.arg(pixel[1]);
|
||||
case Format::RG8: {
|
||||
auto value = Color::DecodeRG8(pixel) / 255.0f;
|
||||
return QString("Red: %1, Green: %2")
|
||||
.arg(QString::number(value.r(), 'f', 2))
|
||||
.arg(QString::number(value.g(), 'f', 2));
|
||||
}
|
||||
case Format::I8:
|
||||
return QString("Index: %1").arg(*pixel);
|
||||
case Format::A8:
|
||||
return QString("Alpha: %1").arg(QString::number(*pixel / 255.0f, 'f', 2));
|
||||
case Format::IA4:
|
||||
return QString("Index: %1, Alpha: %2")
|
||||
.arg(*pixel & 0xF)
|
||||
.arg((*pixel & 0xF0) >> 4);
|
||||
case Format::I4:
|
||||
{
|
||||
u8 i = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
|
||||
return QString("Index: %1").arg(i);
|
||||
}
|
||||
case Format::A4:
|
||||
{
|
||||
u8 a = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
|
||||
return QString("Alpha: %1").arg(QString::number(a / 15.0f, 'f', 2));
|
||||
}
|
||||
case Format::ETC1:
|
||||
case Format::ETC1A4:
|
||||
// TODO: Display block information or channel values?
|
||||
return QString("Compressed data");
|
||||
case Format::D16:
|
||||
{
|
||||
auto value = Color::DecodeD16(pixel);
|
||||
return QString("Depth: %1").arg(QString::number(value / (float)0xFFFF, 'f', 4));
|
||||
}
|
||||
case Format::D24:
|
||||
{
|
||||
auto value = Color::DecodeD24(pixel);
|
||||
return QString("Depth: %1").arg(QString::number(value / (float)0xFFFFFF, 'f', 4));
|
||||
}
|
||||
case Format::D24X8:
|
||||
case Format::X24S8:
|
||||
{
|
||||
auto values = Color::DecodeD24S8(pixel);
|
||||
return QString("Depth: %1, Stencil: %2").arg(QString::number(values[0] / (float)0xFFFFFF, 'f', 4)).arg(values[1]);
|
||||
}
|
||||
case Format::Unknown:
|
||||
return QString("Unknown format");
|
||||
default:
|
||||
return QString("Unhandled format");
|
||||
}
|
||||
return QString("");
|
||||
};
|
||||
|
||||
QString nibbles = "";
|
||||
for (unsigned i = 0; i < nibbles_per_pixel; i++) {
|
||||
unsigned nibble_index = i;
|
||||
if (nibble_mode) {
|
||||
nibble_index += (offset % 2) ? 0 : 1;
|
||||
}
|
||||
u8 byte = pixel[nibble_index / 2];
|
||||
u8 nibble = (byte >> ((nibble_index % 2) ? 0 : 4)) & 0xF;
|
||||
nibbles.append(QString::number(nibble, 16).toUpper());
|
||||
}
|
||||
|
||||
surface_info_label->setText(QString("Raw: 0x%3\n(%4)").arg(nibbles).arg(GetText(surface_format, pixel)));
|
||||
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnUpdate()
|
||||
{
|
||||
QPixmap pixmap;
|
||||
|
||||
switch (surface_source) {
|
||||
case Source::ColorBuffer:
|
||||
{
|
||||
// TODO: Store a reference to the registers in the debug context instead of accessing them directly...
|
||||
|
||||
const auto& framebuffer = Pica::g_state.regs.framebuffer;
|
||||
|
||||
surface_address = framebuffer.GetColorBufferPhysicalAddress();
|
||||
surface_width = framebuffer.GetWidth();
|
||||
surface_height = framebuffer.GetHeight();
|
||||
|
||||
switch (framebuffer.color_format) {
|
||||
case Pica::Regs::ColorFormat::RGBA8:
|
||||
surface_format = Format::RGBA8;
|
||||
break;
|
||||
|
||||
case Pica::Regs::ColorFormat::RGB8:
|
||||
surface_format = Format::RGB8;
|
||||
break;
|
||||
|
||||
case Pica::Regs::ColorFormat::RGB5A1:
|
||||
surface_format = Format::RGB5A1;
|
||||
break;
|
||||
|
||||
case Pica::Regs::ColorFormat::RGB565:
|
||||
surface_format = Format::RGB565;
|
||||
break;
|
||||
|
||||
case Pica::Regs::ColorFormat::RGBA4:
|
||||
surface_format = Format::RGBA4;
|
||||
break;
|
||||
|
||||
default:
|
||||
surface_format = Format::Unknown;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Source::DepthBuffer:
|
||||
{
|
||||
const auto& framebuffer = Pica::g_state.regs.framebuffer;
|
||||
|
||||
surface_address = framebuffer.GetDepthBufferPhysicalAddress();
|
||||
surface_width = framebuffer.GetWidth();
|
||||
surface_height = framebuffer.GetHeight();
|
||||
|
||||
switch (framebuffer.depth_format) {
|
||||
case Pica::Regs::DepthFormat::D16:
|
||||
surface_format = Format::D16;
|
||||
break;
|
||||
|
||||
case Pica::Regs::DepthFormat::D24:
|
||||
surface_format = Format::D24;
|
||||
break;
|
||||
|
||||
case Pica::Regs::DepthFormat::D24S8:
|
||||
surface_format = Format::D24X8;
|
||||
break;
|
||||
|
||||
default:
|
||||
surface_format = Format::Unknown;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Source::StencilBuffer:
|
||||
{
|
||||
const auto& framebuffer = Pica::g_state.regs.framebuffer;
|
||||
|
||||
surface_address = framebuffer.GetDepthBufferPhysicalAddress();
|
||||
surface_width = framebuffer.GetWidth();
|
||||
surface_height = framebuffer.GetHeight();
|
||||
|
||||
switch (framebuffer.depth_format) {
|
||||
case Pica::Regs::DepthFormat::D24S8:
|
||||
surface_format = Format::X24S8;
|
||||
break;
|
||||
|
||||
default:
|
||||
surface_format = Format::Unknown;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Source::Texture0:
|
||||
case Source::Texture1:
|
||||
case Source::Texture2:
|
||||
{
|
||||
unsigned texture_index;
|
||||
if (surface_source == Source::Texture0) texture_index = 0;
|
||||
else if (surface_source == Source::Texture1) texture_index = 1;
|
||||
else if (surface_source == Source::Texture2) texture_index = 2;
|
||||
else {
|
||||
qDebug() << "Unknown texture source " << static_cast<int>(surface_source);
|
||||
break;
|
||||
}
|
||||
|
||||
const auto texture = Pica::g_state.regs.GetTextures()[texture_index];
|
||||
auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format);
|
||||
|
||||
surface_address = info.physical_address;
|
||||
surface_width = info.width;
|
||||
surface_height = info.height;
|
||||
surface_format = static_cast<Format>(info.format);
|
||||
|
||||
if (surface_format > Format::MaxTextureFormat) {
|
||||
qDebug() << "Unknown texture format " << static_cast<int>(info.format);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Source::Custom:
|
||||
{
|
||||
// Keep user-specified values
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
qDebug() << "Unknown surface source " << static_cast<int>(surface_source);
|
||||
break;
|
||||
}
|
||||
|
||||
surface_address_control->SetValue(surface_address);
|
||||
surface_width_control->setValue(surface_width);
|
||||
surface_height_control->setValue(surface_height);
|
||||
surface_format_control->setCurrentIndex(static_cast<int>(surface_format));
|
||||
|
||||
// TODO: Implement a good way to visualize alpha components!
|
||||
|
||||
QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
|
||||
u8* buffer = Memory::GetPhysicalPointer(surface_address);
|
||||
|
||||
if (buffer == nullptr) {
|
||||
surface_picture_label->hide();
|
||||
surface_info_label->setText(tr("(invalid surface address)"));
|
||||
surface_info_label->setAlignment(Qt::AlignCenter);
|
||||
surface_picker_x_control->setEnabled(false);
|
||||
surface_picker_y_control->setEnabled(false);
|
||||
save_surface->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (surface_format == Format::Unknown) {
|
||||
surface_picture_label->hide();
|
||||
surface_info_label->setText(tr("(unknown surface format)"));
|
||||
surface_info_label->setAlignment(Qt::AlignCenter);
|
||||
surface_picker_x_control->setEnabled(false);
|
||||
surface_picker_y_control->setEnabled(false);
|
||||
save_surface->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
surface_picture_label->show();
|
||||
|
||||
unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format);
|
||||
unsigned stride = nibbles_per_pixel * surface_width / 2;
|
||||
|
||||
// We handle depth formats here because DebugUtils only supports TextureFormats
|
||||
if (surface_format <= Format::MaxTextureFormat) {
|
||||
|
||||
// Generate a virtual texture
|
||||
Pica::DebugUtils::TextureInfo info;
|
||||
info.physical_address = surface_address;
|
||||
info.width = surface_width;
|
||||
info.height = surface_height;
|
||||
info.format = static_cast<Pica::Regs::TextureFormat>(surface_format);
|
||||
info.stride = stride;
|
||||
|
||||
for (unsigned int y = 0; y < surface_height; ++y) {
|
||||
for (unsigned int x = 0; x < surface_width; ++x) {
|
||||
Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(buffer, x, y, info, true);
|
||||
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
ASSERT_MSG(nibbles_per_pixel >= 2, "Depth decoder only supports formats with at least one byte per pixel");
|
||||
unsigned bytes_per_pixel = nibbles_per_pixel / 2;
|
||||
|
||||
for (unsigned int y = 0; y < surface_height; ++y) {
|
||||
for (unsigned int x = 0; x < surface_width; ++x) {
|
||||
const u32 coarse_y = y & ~7;
|
||||
u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
|
||||
const u8* pixel = buffer + offset;
|
||||
Math::Vec4<u8> color = { 0, 0, 0, 0 };
|
||||
|
||||
switch(surface_format) {
|
||||
case Format::D16:
|
||||
{
|
||||
u32 data = Color::DecodeD16(pixel);
|
||||
color.r() = data & 0xFF;
|
||||
color.g() = (data >> 8) & 0xFF;
|
||||
break;
|
||||
}
|
||||
case Format::D24:
|
||||
{
|
||||
u32 data = Color::DecodeD24(pixel);
|
||||
color.r() = data & 0xFF;
|
||||
color.g() = (data >> 8) & 0xFF;
|
||||
color.b() = (data >> 16) & 0xFF;
|
||||
break;
|
||||
}
|
||||
case Format::D24X8:
|
||||
{
|
||||
Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
|
||||
color.r() = data.x & 0xFF;
|
||||
color.g() = (data.x >> 8) & 0xFF;
|
||||
color.b() = (data.x >> 16) & 0xFF;
|
||||
break;
|
||||
}
|
||||
case Format::X24S8:
|
||||
{
|
||||
Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
|
||||
color.r() = color.g() = color.b() = data.y;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qDebug() << "Unknown surface format " << static_cast<int>(surface_format);
|
||||
break;
|
||||
}
|
||||
|
||||
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pixmap = QPixmap::fromImage(decoded_image);
|
||||
surface_picture_label->setPixmap(pixmap);
|
||||
surface_picture_label->resize(pixmap.size());
|
||||
|
||||
// Update the info with pixel data
|
||||
surface_picker_x_control->setEnabled(true);
|
||||
surface_picker_y_control->setEnabled(true);
|
||||
Pick(surface_picker_x, surface_picker_y);
|
||||
|
||||
// Enable saving the converted pixmap to file
|
||||
save_surface->setEnabled(true);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::SaveSurface() {
|
||||
QString png_filter = tr("Portable Network Graphic (*.png)");
|
||||
QString bin_filter = tr("Binary data (*.bin)");
|
||||
|
||||
QString selectedFilter;
|
||||
QString filename = QFileDialog::getSaveFileName(this, tr("Save Surface"), QString("texture-0x%1.png").arg(QString::number(surface_address, 16)),
|
||||
QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter);
|
||||
|
||||
if (filename.isEmpty()) {
|
||||
// If the user canceled the dialog, don't save anything.
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedFilter == png_filter) {
|
||||
const QPixmap* pixmap = surface_picture_label->pixmap();
|
||||
ASSERT_MSG(pixmap != nullptr, "No pixmap set");
|
||||
|
||||
QFile file(filename);
|
||||
file.open(QIODevice::WriteOnly);
|
||||
if (pixmap)
|
||||
pixmap->save(&file, "PNG");
|
||||
} else if (selectedFilter == bin_filter) {
|
||||
const u8* buffer = Memory::GetPhysicalPointer(surface_address);
|
||||
ASSERT_MSG(buffer != nullptr, "Memory not accessible");
|
||||
|
||||
QFile file(filename);
|
||||
file.open(QIODevice::WriteOnly);
|
||||
int size = surface_width * surface_height * NibblesPerPixel(surface_format) / 2;
|
||||
QByteArray data(reinterpret_cast<const char*>(buffer), size);
|
||||
file.write(data);
|
||||
} else {
|
||||
UNREACHABLE_MSG("Unhandled filter selected");
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int GraphicsSurfaceWidget::NibblesPerPixel(GraphicsSurfaceWidget::Format format) {
|
||||
if (format <= Format::MaxTextureFormat) {
|
||||
return Pica::Regs::NibblesPerPixel(static_cast<Pica::Regs::TextureFormat>(format));
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case Format::D24X8:
|
||||
case Format::X24S8:
|
||||
return 4 * 2;
|
||||
case Format::D24:
|
||||
return 3 * 2;
|
||||
case Format::D16:
|
||||
return 2 * 2;
|
||||
default:
|
||||
UNREACHABLE_MSG("GraphicsSurfaceWidget::BytesPerPixel: this "
|
||||
"should not be reached as this function should "
|
||||
"be given a format which is in "
|
||||
"GraphicsSurfaceWidget::Format. Instead got %i",
|
||||
static_cast<int>(format));
|
||||
return 0;
|
||||
}
|
||||
}
|
120
src/citra_qt/debugger/graphics_surface.h
Normal file
120
src/citra_qt/debugger/graphics_surface.h
Normal file
@ -0,0 +1,120 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "citra_qt/debugger/graphics_breakpoint_observer.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
||||
class QComboBox;
|
||||
class QSpinBox;
|
||||
class CSpinBox;
|
||||
|
||||
class GraphicsSurfaceWidget;
|
||||
|
||||
class SurfacePicture : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SurfacePicture(QWidget* parent = 0, GraphicsSurfaceWidget* surface_widget = nullptr);
|
||||
~SurfacePicture();
|
||||
|
||||
protected slots:
|
||||
virtual void mouseMoveEvent(QMouseEvent* event);
|
||||
virtual void mousePressEvent(QMouseEvent* event);
|
||||
|
||||
private:
|
||||
GraphicsSurfaceWidget* surface_widget;
|
||||
|
||||
};
|
||||
|
||||
class GraphicsSurfaceWidget : public BreakPointObserverDock {
|
||||
Q_OBJECT
|
||||
|
||||
using Event = Pica::DebugContext::Event;
|
||||
|
||||
enum class Source {
|
||||
ColorBuffer = 0,
|
||||
DepthBuffer = 1,
|
||||
StencilBuffer = 2,
|
||||
Texture0 = 3,
|
||||
Texture1 = 4,
|
||||
Texture2 = 5,
|
||||
Custom = 6,
|
||||
};
|
||||
|
||||
enum class Format {
|
||||
// These must match the TextureFormat type!
|
||||
RGBA8 = 0,
|
||||
RGB8 = 1,
|
||||
RGB5A1 = 2,
|
||||
RGB565 = 3,
|
||||
RGBA4 = 4,
|
||||
IA8 = 5,
|
||||
RG8 = 6, ///< @note Also called HILO8 in 3DBrew.
|
||||
I8 = 7,
|
||||
A8 = 8,
|
||||
IA4 = 9,
|
||||
I4 = 10,
|
||||
A4 = 11,
|
||||
ETC1 = 12, // compressed
|
||||
ETC1A4 = 13,
|
||||
MaxTextureFormat = 13,
|
||||
D16 = 14,
|
||||
D24 = 15,
|
||||
D24X8 = 16,
|
||||
X24S8 = 17,
|
||||
Unknown = 18,
|
||||
};
|
||||
|
||||
static unsigned int NibblesPerPixel(Format format);
|
||||
|
||||
public:
|
||||
GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr);
|
||||
void Pick(int x, int y);
|
||||
|
||||
public slots:
|
||||
void OnSurfaceSourceChanged(int new_value);
|
||||
void OnSurfaceAddressChanged(qint64 new_value);
|
||||
void OnSurfaceWidthChanged(int new_value);
|
||||
void OnSurfaceHeightChanged(int new_value);
|
||||
void OnSurfaceFormatChanged(int new_value);
|
||||
void OnSurfacePickerXChanged(int new_value);
|
||||
void OnSurfacePickerYChanged(int new_value);
|
||||
void OnUpdate();
|
||||
|
||||
private slots:
|
||||
void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override;
|
||||
void OnResumed() override;
|
||||
|
||||
void SaveSurface();
|
||||
|
||||
signals:
|
||||
void Update();
|
||||
|
||||
private:
|
||||
|
||||
QComboBox* surface_source_list;
|
||||
CSpinBox* surface_address_control;
|
||||
QSpinBox* surface_width_control;
|
||||
QSpinBox* surface_height_control;
|
||||
QComboBox* surface_format_control;
|
||||
|
||||
SurfacePicture* surface_picture_label;
|
||||
QSpinBox* surface_picker_x_control;
|
||||
QSpinBox* surface_picker_y_control;
|
||||
QLabel* surface_info_label;
|
||||
QPushButton* save_surface;
|
||||
|
||||
Source surface_source;
|
||||
unsigned surface_address;
|
||||
unsigned surface_width;
|
||||
unsigned surface_height;
|
||||
Format surface_format;
|
||||
int surface_picker_x = 0;
|
||||
int surface_picker_y = 0;
|
||||
};
|
@ -118,46 +118,33 @@ void GameList::LoadInterfaceLayout()
|
||||
item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder());
|
||||
}
|
||||
|
||||
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool deep_scan)
|
||||
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion)
|
||||
{
|
||||
const auto callback = [&](unsigned* num_entries_out,
|
||||
const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
const std::string& virtual_name,
|
||||
unsigned int recursion) -> bool {
|
||||
|
||||
std::string physical_name = directory + DIR_SEP + virtual_name;
|
||||
|
||||
if (stop_processing)
|
||||
return false; // Breaks the callback loop.
|
||||
|
||||
if (deep_scan && FileUtil::IsDirectory(physical_name)) {
|
||||
AddFstEntriesToGameList(physical_name, true);
|
||||
} else {
|
||||
std::string filename_filename, filename_extension;
|
||||
Common::SplitPath(physical_name, nullptr, &filename_filename, &filename_extension);
|
||||
|
||||
Loader::FileType guessed_filetype = Loader::GuessFromExtension(filename_extension);
|
||||
if (guessed_filetype == Loader::FileType::Unknown)
|
||||
if (!FileUtil::IsDirectory(physical_name)) {
|
||||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
|
||||
if (!loader)
|
||||
return true;
|
||||
Loader::FileType filetype = Loader::IdentifyFile(physical_name);
|
||||
if (filetype == Loader::FileType::Unknown) {
|
||||
LOG_WARNING(Frontend, "File %s is of indeterminate type and is possibly corrupted.", physical_name.c_str());
|
||||
return true;
|
||||
}
|
||||
if (guessed_filetype != filetype) {
|
||||
LOG_WARNING(Frontend, "Filetype and extension of file %s do not match.", physical_name.c_str());
|
||||
}
|
||||
|
||||
std::vector<u8> smdh;
|
||||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(FileUtil::IOFile(physical_name, "rb"), filetype, filename_filename, physical_name);
|
||||
|
||||
if (loader)
|
||||
loader->ReadIcon(smdh);
|
||||
loader->ReadIcon(smdh);
|
||||
|
||||
emit EntryReady({
|
||||
new GameListItemPath(QString::fromStdString(physical_name), smdh),
|
||||
new GameListItem(QString::fromStdString(Loader::GetFileTypeString(filetype))),
|
||||
new GameListItem(QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||
new GameListItemSize(FileUtil::GetSize(physical_name)),
|
||||
});
|
||||
} else if (recursion > 0) {
|
||||
AddFstEntriesToGameList(physical_name, recursion - 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -169,7 +156,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool d
|
||||
void GameListWorker::run()
|
||||
{
|
||||
stop_processing = false;
|
||||
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan);
|
||||
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
|
||||
emit Finished();
|
||||
}
|
||||
|
||||
|
@ -15,52 +15,21 @@
|
||||
#include "common/string_util.h"
|
||||
#include "common/color.h"
|
||||
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/loader/smdh.h"
|
||||
|
||||
#include "video_core/utils.h"
|
||||
|
||||
/**
|
||||
* Tests if data is a valid SMDH by its length and magic number.
|
||||
* @param smdh_data data buffer to test
|
||||
* @return bool test result
|
||||
*/
|
||||
static bool IsValidSMDH(const std::vector<u8>& smdh_data) {
|
||||
if (smdh_data.size() < sizeof(Loader::SMDH))
|
||||
return false;
|
||||
|
||||
u32 magic;
|
||||
memcpy(&magic, smdh_data.data(), 4);
|
||||
|
||||
return Loader::MakeMagic('S', 'M', 'D', 'H') == magic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets game icon from SMDH
|
||||
* @param sdmh SMDH data
|
||||
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
|
||||
* @return QPixmap game icon
|
||||
*/
|
||||
static QPixmap GetIconFromSMDH(const Loader::SMDH& smdh, bool large) {
|
||||
u32 size;
|
||||
const u8* icon_data;
|
||||
|
||||
if (large) {
|
||||
size = 48;
|
||||
icon_data = smdh.large_icon.data();
|
||||
} else {
|
||||
size = 24;
|
||||
icon_data = smdh.small_icon.data();
|
||||
}
|
||||
|
||||
QImage icon(size, size, QImage::Format::Format_RGB888);
|
||||
for (u32 x = 0; x < size; ++x) {
|
||||
for (u32 y = 0; y < size; ++y) {
|
||||
u32 coarse_y = y & ~7;
|
||||
auto v = Color::DecodeRGB565(
|
||||
icon_data + VideoCore::GetMortonOffset(x, y, 2) + coarse_y * size * 2);
|
||||
icon.setPixel(x, y, qRgb(v.r(), v.g(), v.b()));
|
||||
}
|
||||
}
|
||||
static QPixmap GetQPixmapFromSMDH(const Loader::SMDH& smdh, bool large) {
|
||||
std::vector<u16> icon_data = smdh.GetIcon(large);
|
||||
const uchar* data = reinterpret_cast<const uchar*>(icon_data.data());
|
||||
int size = large ? 48 : 24;
|
||||
QImage icon(data, size, size, QImage::Format::Format_RGB16);
|
||||
return QPixmap::fromImage(icon);
|
||||
}
|
||||
|
||||
@ -82,8 +51,8 @@ static QPixmap GetDefaultIcon(bool large) {
|
||||
* @param language title language
|
||||
* @return QString short title
|
||||
*/
|
||||
static QString GetShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) {
|
||||
return QString::fromUtf16(smdh.titles[static_cast<int>(language)].short_title.data());
|
||||
static QString GetQStringShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) {
|
||||
return QString::fromUtf16(smdh.GetShortTitle(language).data());
|
||||
}
|
||||
|
||||
class GameListItem : public QStandardItem {
|
||||
@ -112,7 +81,7 @@ public:
|
||||
{
|
||||
setData(game_path, FullPathRole);
|
||||
|
||||
if (!IsValidSMDH(smdh_data)) {
|
||||
if (!Loader::IsValidSMDH(smdh_data)) {
|
||||
// SMDH is not valid, set a default icon
|
||||
setData(GetDefaultIcon(true), Qt::DecorationRole);
|
||||
return;
|
||||
@ -122,10 +91,10 @@ public:
|
||||
memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH));
|
||||
|
||||
// Get icon from SMDH
|
||||
setData(GetIconFromSMDH(smdh, true), Qt::DecorationRole);
|
||||
setData(GetQPixmapFromSMDH(smdh, true), Qt::DecorationRole);
|
||||
|
||||
// Get title form SMDH
|
||||
setData(GetShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English), TitleRole);
|
||||
setData(GetQStringShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English), TitleRole);
|
||||
}
|
||||
|
||||
QVariant data(int role) const override {
|
||||
@ -212,5 +181,5 @@ private:
|
||||
bool deep_scan;
|
||||
std::atomic_bool stop_processing;
|
||||
|
||||
void AddFstEntriesToGameList(const std::string& dir_path, bool deep_scan);
|
||||
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
|
||||
};
|
||||
|
@ -29,7 +29,7 @@
|
||||
#include "citra_qt/debugger/graphics.h"
|
||||
#include "citra_qt/debugger/graphics_breakpoints.h"
|
||||
#include "citra_qt/debugger/graphics_cmdlists.h"
|
||||
#include "citra_qt/debugger/graphics_framebuffer.h"
|
||||
#include "citra_qt/debugger/graphics_surface.h"
|
||||
#include "citra_qt/debugger/graphics_tracing.h"
|
||||
#include "citra_qt/debugger/graphics_vertex_shader.h"
|
||||
#include "citra_qt/debugger/profiler.h"
|
||||
@ -101,10 +101,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
|
||||
addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget);
|
||||
graphicsBreakpointsWidget->hide();
|
||||
|
||||
auto graphicsFramebufferWidget = new GraphicsFramebufferWidget(Pica::g_debug_context, this);
|
||||
addDockWidget(Qt::RightDockWidgetArea, graphicsFramebufferWidget);
|
||||
graphicsFramebufferWidget->hide();
|
||||
|
||||
auto graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this);
|
||||
addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget);
|
||||
graphicsVertexShaderWidget->hide();
|
||||
@ -113,7 +109,12 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
|
||||
addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget);
|
||||
graphicsTracingWidget->hide();
|
||||
|
||||
auto graphicsSurfaceViewerAction = new QAction(tr("Create Pica surface viewer"), this);
|
||||
connect(graphicsSurfaceViewerAction, SIGNAL(triggered()), this, SLOT(OnCreateGraphicsSurfaceViewer()));
|
||||
|
||||
QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging"));
|
||||
debug_menu->addAction(graphicsSurfaceViewerAction);
|
||||
debug_menu->addSeparator();
|
||||
debug_menu->addAction(profilerWidget->toggleViewAction());
|
||||
#if MICROPROFILE_ENABLED
|
||||
debug_menu->addAction(microProfileDialog->toggleViewAction());
|
||||
@ -124,7 +125,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
|
||||
debug_menu->addAction(graphicsWidget->toggleViewAction());
|
||||
debug_menu->addAction(graphicsCommandsWidget->toggleViewAction());
|
||||
debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
|
||||
debug_menu->addAction(graphicsFramebufferWidget->toggleViewAction());
|
||||
debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction());
|
||||
debug_menu->addAction(graphicsTracingWidget->toggleViewAction());
|
||||
|
||||
@ -272,7 +272,15 @@ bool GMainWindow::InitializeSystem() {
|
||||
}
|
||||
|
||||
bool GMainWindow::LoadROM(const std::string& filename) {
|
||||
Loader::ResultStatus result = Loader::LoadFile(filename);
|
||||
std::unique_ptr<Loader::AppLoader> app_loader = Loader::GetLoader(filename);
|
||||
if (!app_loader) {
|
||||
LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", filename.c_str());
|
||||
QMessageBox::critical(this, tr("Error while loading ROM!"),
|
||||
tr("The ROM format is not supported."));
|
||||
return false;
|
||||
}
|
||||
|
||||
Loader::ResultStatus result = app_loader->Load();
|
||||
if (Loader::ResultStatus::Success != result) {
|
||||
LOG_CRITICAL(Frontend, "Failed to load ROM!");
|
||||
System::Shutdown();
|
||||
@ -509,6 +517,13 @@ void GMainWindow::OnConfigure() {
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::OnCreateGraphicsSurfaceViewer() {
|
||||
auto graphicsSurfaceViewerWidget = new GraphicsSurfaceWidget(Pica::g_debug_context, this);
|
||||
addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceViewerWidget);
|
||||
// TODO: Maybe graphicsSurfaceViewerWidget->setFloating(true);
|
||||
graphicsSurfaceViewerWidget->show();
|
||||
}
|
||||
|
||||
bool GMainWindow::ConfirmClose() {
|
||||
if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
|
||||
return true;
|
||||
|
@ -108,6 +108,7 @@ private slots:
|
||||
void OnConfigure();
|
||||
void OnDisplayTitleBars(bool);
|
||||
void ToggleWindowMode();
|
||||
void OnCreateGraphicsSurfaceViewer();
|
||||
|
||||
private:
|
||||
Ui::MainWindow ui;
|
||||
|
@ -72,18 +72,24 @@ inline u64 _rotr64(u64 x, unsigned int shift){
|
||||
}
|
||||
|
||||
#else // _MSC_VER
|
||||
#if (_MSC_VER < 1900)
|
||||
// Function Cross-Compatibility
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
// Locale Cross-Compatibility
|
||||
#define locale_t _locale_t
|
||||
#if (_MSC_VER < 1900)
|
||||
// Function Cross-Compatibility
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
// Locale Cross-Compatibility
|
||||
#define locale_t _locale_t
|
||||
|
||||
extern "C" {
|
||||
__declspec(dllimport) void __stdcall DebugBreak(void);
|
||||
}
|
||||
#define Crash() {DebugBreak();}
|
||||
|
||||
// cstdlib provides these on MSVC
|
||||
#define rotr _rotr
|
||||
#define rotl _rotl
|
||||
|
||||
extern "C" {
|
||||
__declspec(dllimport) void __stdcall DebugBreak(void);
|
||||
}
|
||||
#define Crash() {DebugBreak();}
|
||||
#endif // _MSC_VER ndef
|
||||
|
||||
// Generic function to get last error message.
|
||||
|
@ -11,12 +11,28 @@
|
||||
#include "emu_window.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
void EmuWindow::KeyPressed(KeyMap::HostDeviceKey key) {
|
||||
pad_state.hex |= KeyMap::GetPadKey(key).hex;
|
||||
void EmuWindow::ButtonPressed(Service::HID::PadState pad) {
|
||||
pad_state.hex |= pad.hex;
|
||||
}
|
||||
|
||||
void EmuWindow::KeyReleased(KeyMap::HostDeviceKey key) {
|
||||
pad_state.hex &= ~KeyMap::GetPadKey(key).hex;
|
||||
void EmuWindow::ButtonReleased(Service::HID::PadState pad) {
|
||||
pad_state.hex &= ~pad.hex;
|
||||
}
|
||||
|
||||
void EmuWindow::CirclePadUpdated(float x, float y) {
|
||||
constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position
|
||||
|
||||
// Make sure the coordinates are in the unit circle,
|
||||
// otherwise normalize it.
|
||||
float r = x * x + y * y;
|
||||
if (r > 1) {
|
||||
r = std::sqrt(r);
|
||||
x /= r;
|
||||
y /= r;
|
||||
}
|
||||
|
||||
circle_pad_x = static_cast<s16>(x * MAX_CIRCLEPAD_POS);
|
||||
circle_pad_y = static_cast<s16>(y * MAX_CIRCLEPAD_POS);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,10 +12,6 @@
|
||||
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
|
||||
namespace KeyMap {
|
||||
struct HostDeviceKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstraction class used to provide an interface between emulation code and the frontend
|
||||
* (e.g. SDL, QGLWidget, GLFW, etc...).
|
||||
@ -76,11 +72,27 @@ public:
|
||||
|
||||
virtual void ReloadSetKeymaps() = 0;
|
||||
|
||||
/// Signals a key press action to the HID module
|
||||
void KeyPressed(KeyMap::HostDeviceKey key);
|
||||
/**
|
||||
* Signals a button press action to the HID module.
|
||||
* @param pad_state indicates which button to press
|
||||
* @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
|
||||
*/
|
||||
void ButtonPressed(Service::HID::PadState pad_state);
|
||||
|
||||
/// Signals a key release action to the HID module
|
||||
void KeyReleased(KeyMap::HostDeviceKey key);
|
||||
/**
|
||||
* Signals a button release action to the HID module.
|
||||
* @param pad_state indicates which button to press
|
||||
* @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
|
||||
*/
|
||||
void ButtonReleased(Service::HID::PadState pad_state);
|
||||
|
||||
/**
|
||||
* Signals a circle pad change action to the HID module.
|
||||
* @param x new x-coordinate of the circle pad, in the range [-1.0, 1.0]
|
||||
* @param y new y-coordinate of the circle pad, in the range [-1.0, 1.0]
|
||||
* @note the coordinates will be normalized if the radius is larger than 1
|
||||
*/
|
||||
void CirclePadUpdated(float x, float y);
|
||||
|
||||
/**
|
||||
* Signal that a touch pressed event has occurred (e.g. mouse click pressed)
|
||||
@ -100,8 +112,9 @@ public:
|
||||
void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
|
||||
|
||||
/**
|
||||
* Gets the current pad state (which buttons are pressed and the circle pad direction).
|
||||
* Gets the current pad state (which buttons are pressed).
|
||||
* @note This should be called by the core emu thread to get a state set by the window thread.
|
||||
* @note This doesn't include analog input like circle pad direction
|
||||
* @todo Fix this function to be thread-safe.
|
||||
* @return PadState object indicating the current pad state
|
||||
*/
|
||||
@ -109,6 +122,16 @@ public:
|
||||
return pad_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current circle pad state.
|
||||
* @note This should be called by the core emu thread to get a state set by the window thread.
|
||||
* @todo Fix this function to be thread-safe.
|
||||
* @return std::tuple of (x, y), where `x` and `y` are the circle pad coordinates
|
||||
*/
|
||||
std::tuple<s16, s16> GetCirclePadState() const {
|
||||
return std::make_tuple(circle_pad_x, circle_pad_y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed).
|
||||
* @note This should be called by the core emu thread to get a state set by the window thread.
|
||||
@ -200,6 +223,8 @@ protected:
|
||||
pad_state.hex = 0;
|
||||
touch_x = 0;
|
||||
touch_y = 0;
|
||||
circle_pad_x = 0;
|
||||
circle_pad_y = 0;
|
||||
touch_pressed = false;
|
||||
}
|
||||
virtual ~EmuWindow() {}
|
||||
@ -260,6 +285,9 @@ private:
|
||||
u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
|
||||
u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240)
|
||||
|
||||
s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156)
|
||||
s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156)
|
||||
|
||||
/**
|
||||
* Clip the provided coordinates to be inside the touchscreen area.
|
||||
*/
|
||||
|
@ -434,7 +434,7 @@ bool CreateEmptyFile(const std::string &filename)
|
||||
}
|
||||
|
||||
|
||||
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback)
|
||||
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback, unsigned int recursion)
|
||||
{
|
||||
LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str());
|
||||
|
||||
@ -472,7 +472,7 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo
|
||||
continue;
|
||||
|
||||
unsigned ret_entries = 0;
|
||||
if (!callback(&ret_entries, directory, virtual_name)) {
|
||||
if (!callback(&ret_entries, directory, virtual_name, recursion)) {
|
||||
callback_error = true;
|
||||
break;
|
||||
}
|
||||
@ -486,30 +486,34 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo
|
||||
closedir(dirp);
|
||||
#endif
|
||||
|
||||
if (!callback_error) {
|
||||
// num_entries_out is allowed to be specified nullptr, in which case we shouldn't try to set it
|
||||
if (num_entries_out != nullptr)
|
||||
*num_entries_out = found_entries;
|
||||
return true;
|
||||
} else {
|
||||
if (callback_error)
|
||||
return false;
|
||||
}
|
||||
|
||||
// num_entries_out is allowed to be specified nullptr, in which case we shouldn't try to set it
|
||||
if (num_entries_out != nullptr)
|
||||
*num_entries_out = found_entries;
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry)
|
||||
unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry, unsigned int recursion)
|
||||
{
|
||||
const auto callback = [&parent_entry](unsigned* num_entries_out,
|
||||
const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
const std::string& virtual_name,
|
||||
unsigned int recursion) -> bool {
|
||||
FSTEntry entry;
|
||||
entry.virtualName = virtual_name;
|
||||
entry.physicalName = directory + DIR_SEP + virtual_name;
|
||||
|
||||
if (IsDirectory(entry.physicalName)) {
|
||||
entry.isDirectory = true;
|
||||
// is a directory, lets go inside
|
||||
entry.size = ScanDirectoryTree(entry.physicalName, entry);
|
||||
*num_entries_out += (int)entry.size;
|
||||
// is a directory, lets go inside if we didn't recurse to often
|
||||
if (recursion > 0) {
|
||||
entry.size = ScanDirectoryTree(entry.physicalName, entry, recursion - 1);
|
||||
*num_entries_out += (int)entry.size;
|
||||
} else {
|
||||
entry.size = 0;
|
||||
}
|
||||
} else { // is a file
|
||||
entry.isDirectory = false;
|
||||
entry.size = GetSize(entry.physicalName);
|
||||
@ -522,23 +526,27 @@ unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry)
|
||||
};
|
||||
|
||||
unsigned num_entries;
|
||||
return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
|
||||
return ForeachDirectoryEntry(&num_entries, directory, callback, recursion) ? num_entries : 0;
|
||||
}
|
||||
|
||||
|
||||
bool DeleteDirRecursively(const std::string &directory)
|
||||
bool DeleteDirRecursively(const std::string &directory, unsigned int recursion)
|
||||
{
|
||||
const static auto callback = [](unsigned* num_entries_out,
|
||||
const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
const std::string& virtual_name,
|
||||
unsigned int recursion) -> bool {
|
||||
std::string new_path = directory + DIR_SEP_CHR + virtual_name;
|
||||
if (IsDirectory(new_path))
|
||||
return DeleteDirRecursively(new_path);
|
||||
|
||||
if (IsDirectory(new_path)) {
|
||||
if (recursion == 0)
|
||||
return false;
|
||||
return DeleteDirRecursively(new_path, recursion - 1);
|
||||
}
|
||||
return Delete(new_path);
|
||||
};
|
||||
|
||||
if (!ForeachDirectoryEntry(nullptr, directory, callback))
|
||||
if (!ForeachDirectoryEntry(nullptr, directory, callback, recursion))
|
||||
return false;
|
||||
|
||||
// Delete the outermost directory
|
||||
|
@ -105,11 +105,13 @@ bool CreateEmptyFile(const std::string &filename);
|
||||
* @param num_entries_out to be assigned by the callable with the number of iterated directory entries, never null
|
||||
* @param directory the path to the enclosing directory
|
||||
* @param virtual_name the entry name, without any preceding directory info
|
||||
* @param recursion Number of children directory to read before giving up
|
||||
* @return whether handling the entry succeeded
|
||||
*/
|
||||
using DirectoryEntryCallable = std::function<bool(unsigned* num_entries_out,
|
||||
const std::string& directory,
|
||||
const std::string& virtual_name)>;
|
||||
const std::string& virtual_name,
|
||||
unsigned int recursion)>;
|
||||
|
||||
/**
|
||||
* Scans a directory, calling the callback for each file/directory contained within.
|
||||
@ -117,20 +119,22 @@ using DirectoryEntryCallable = std::function<bool(unsigned* num_entries_out,
|
||||
* @param num_entries_out assigned by the function with the number of iterated directory entries, can be null
|
||||
* @param directory the directory to scan
|
||||
* @param callback The callback which will be called for each entry
|
||||
* @param recursion Number of children directories to read before giving up
|
||||
* @return whether scanning the directory succeeded
|
||||
*/
|
||||
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback);
|
||||
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback, unsigned int recursion = 0);
|
||||
|
||||
/**
|
||||
* Scans the directory tree, storing the results.
|
||||
* @param directory the parent directory to start scanning from
|
||||
* @param parent_entry FSTEntry where the filesystem tree results will be stored.
|
||||
* @param recursion Number of children directories to read before giving up.
|
||||
* @return the total number of files/directories found
|
||||
*/
|
||||
unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry);
|
||||
unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry, unsigned int recursion = 0);
|
||||
|
||||
// deletes the given directory and anything under it. Returns true on success.
|
||||
bool DeleteDirRecursively(const std::string &directory);
|
||||
bool DeleteDirRecursively(const std::string &directory, unsigned int recursion = 256);
|
||||
|
||||
// Returns the current directory
|
||||
std::string GetCurrentDir();
|
||||
|
@ -2,24 +2,138 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "key_map.h"
|
||||
#include <map>
|
||||
|
||||
#include "common/emu_window.h"
|
||||
#include "common/key_map.h"
|
||||
|
||||
namespace KeyMap {
|
||||
|
||||
static std::map<HostDeviceKey, Service::HID::PadState> key_map;
|
||||
// TODO (wwylele): currently we treat c-stick as four direction buttons
|
||||
// and map it directly to EmuWindow::ButtonPressed.
|
||||
// It should go the analog input way like circle pad does.
|
||||
const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets = {{
|
||||
Service::HID::PAD_A, Service::HID::PAD_B, Service::HID::PAD_X, Service::HID::PAD_Y,
|
||||
Service::HID::PAD_L, Service::HID::PAD_R, Service::HID::PAD_ZL, Service::HID::PAD_ZR,
|
||||
Service::HID::PAD_START, Service::HID::PAD_SELECT, Service::HID::PAD_NONE,
|
||||
Service::HID::PAD_UP, Service::HID::PAD_DOWN, Service::HID::PAD_LEFT, Service::HID::PAD_RIGHT,
|
||||
Service::HID::PAD_C_UP, Service::HID::PAD_C_DOWN, Service::HID::PAD_C_LEFT, Service::HID::PAD_C_RIGHT,
|
||||
|
||||
IndirectTarget::CirclePadUp,
|
||||
IndirectTarget::CirclePadDown,
|
||||
IndirectTarget::CirclePadLeft,
|
||||
IndirectTarget::CirclePadRight,
|
||||
IndirectTarget::CirclePadModifier,
|
||||
}};
|
||||
|
||||
static std::map<HostDeviceKey, KeyTarget> key_map;
|
||||
static int next_device_id = 0;
|
||||
|
||||
static bool circle_pad_up = false;
|
||||
static bool circle_pad_down = false;
|
||||
static bool circle_pad_left = false;
|
||||
static bool circle_pad_right = false;
|
||||
static bool circle_pad_modifier = false;
|
||||
|
||||
static void UpdateCirclePad(EmuWindow& emu_window) {
|
||||
constexpr float SQRT_HALF = 0.707106781;
|
||||
int x = 0, y = 0;
|
||||
|
||||
if (circle_pad_right)
|
||||
++x;
|
||||
if (circle_pad_left)
|
||||
--x;
|
||||
if (circle_pad_up)
|
||||
++y;
|
||||
if (circle_pad_down)
|
||||
--y;
|
||||
|
||||
float modifier = circle_pad_modifier ? Settings::values.pad_circle_modifier_scale : 1.0;
|
||||
emu_window.CirclePadUpdated(x * modifier * (y == 0 ? 1.0 : SQRT_HALF), y * modifier * (x == 0 ? 1.0 : SQRT_HALF));
|
||||
}
|
||||
|
||||
int NewDeviceId() {
|
||||
return next_device_id++;
|
||||
}
|
||||
|
||||
void SetKeyMapping(HostDeviceKey key, Service::HID::PadState padState) {
|
||||
key_map[key].hex = padState.hex;
|
||||
void SetKeyMapping(HostDeviceKey key, KeyTarget target) {
|
||||
key_map[key] = target;
|
||||
}
|
||||
|
||||
Service::HID::PadState GetPadKey(HostDeviceKey key) {
|
||||
return key_map[key];
|
||||
void ClearKeyMapping(int device_id) {
|
||||
auto iter = key_map.begin();
|
||||
while (iter != key_map.end()) {
|
||||
if (iter->first.device_id == device_id)
|
||||
key_map.erase(iter++);
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
void PressKey(EmuWindow& emu_window, HostDeviceKey key) {
|
||||
auto target = key_map.find(key);
|
||||
if (target == key_map.end())
|
||||
return;
|
||||
|
||||
if (target->second.direct) {
|
||||
emu_window.ButtonPressed({{target->second.target.direct_target_hex}});
|
||||
} else {
|
||||
switch (target->second.target.indirect_target) {
|
||||
case IndirectTarget::CirclePadUp:
|
||||
circle_pad_up = true;
|
||||
UpdateCirclePad(emu_window);
|
||||
break;
|
||||
case IndirectTarget::CirclePadDown:
|
||||
circle_pad_down = true;
|
||||
UpdateCirclePad(emu_window);
|
||||
break;
|
||||
case IndirectTarget::CirclePadLeft:
|
||||
circle_pad_left = true;
|
||||
UpdateCirclePad(emu_window);
|
||||
break;
|
||||
case IndirectTarget::CirclePadRight:
|
||||
circle_pad_right = true;
|
||||
UpdateCirclePad(emu_window);
|
||||
break;
|
||||
case IndirectTarget::CirclePadModifier:
|
||||
circle_pad_modifier = true;
|
||||
UpdateCirclePad(emu_window);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReleaseKey(EmuWindow& emu_window,HostDeviceKey key) {
|
||||
auto target = key_map.find(key);
|
||||
if (target == key_map.end())
|
||||
return;
|
||||
|
||||
if (target->second.direct) {
|
||||
emu_window.ButtonReleased({{target->second.target.direct_target_hex}});
|
||||
} else {
|
||||
switch (target->second.target.indirect_target) {
|
||||
case IndirectTarget::CirclePadUp:
|
||||
circle_pad_up = false;
|
||||
UpdateCirclePad(emu_window);
|
||||
break;
|
||||
case IndirectTarget::CirclePadDown:
|
||||
circle_pad_down = false;
|
||||
UpdateCirclePad(emu_window);
|
||||
break;
|
||||
case IndirectTarget::CirclePadLeft:
|
||||
circle_pad_left = false;
|
||||
UpdateCirclePad(emu_window);
|
||||
break;
|
||||
case IndirectTarget::CirclePadRight:
|
||||
circle_pad_right = false;
|
||||
UpdateCirclePad(emu_window);
|
||||
break;
|
||||
case IndirectTarget::CirclePadModifier:
|
||||
circle_pad_modifier = false;
|
||||
UpdateCirclePad(emu_window);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,11 +4,50 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
|
||||
class EmuWindow;
|
||||
|
||||
namespace KeyMap {
|
||||
|
||||
/**
|
||||
* Represents key mapping targets that are not real 3DS buttons.
|
||||
* They will be handled by KeyMap and translated to 3DS input.
|
||||
*/
|
||||
enum class IndirectTarget {
|
||||
CirclePadUp,
|
||||
CirclePadDown,
|
||||
CirclePadLeft,
|
||||
CirclePadRight,
|
||||
CirclePadModifier,
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a key mapping target. It can be a PadState that represents real 3DS buttons,
|
||||
* or an IndirectTarget.
|
||||
*/
|
||||
struct KeyTarget {
|
||||
bool direct;
|
||||
union {
|
||||
u32 direct_target_hex;
|
||||
IndirectTarget indirect_target;
|
||||
} target;
|
||||
|
||||
KeyTarget() : direct(true) {
|
||||
target.direct_target_hex = 0;
|
||||
}
|
||||
|
||||
KeyTarget(Service::HID::PadState pad) : direct(true) {
|
||||
target.direct_target_hex = pad.hex;
|
||||
}
|
||||
|
||||
KeyTarget(IndirectTarget i) : direct(false) {
|
||||
target.indirect_target = i;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a key for a specific host device.
|
||||
*/
|
||||
@ -27,19 +66,31 @@ struct HostDeviceKey {
|
||||
}
|
||||
};
|
||||
|
||||
extern const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets;
|
||||
|
||||
/**
|
||||
* Generates a new device id, which uniquely identifies a host device within KeyMap.
|
||||
*/
|
||||
int NewDeviceId();
|
||||
|
||||
/**
|
||||
* Maps a device-specific key to a PadState.
|
||||
* Maps a device-specific key to a target (a PadState or an IndirectTarget).
|
||||
*/
|
||||
void SetKeyMapping(HostDeviceKey key, Service::HID::PadState padState);
|
||||
void SetKeyMapping(HostDeviceKey key, KeyTarget target);
|
||||
|
||||
/**
|
||||
* Gets the PadState that's mapped to the provided device-specific key.
|
||||
* Clears all key mappings belonging to one device.
|
||||
*/
|
||||
Service::HID::PadState GetPadKey(HostDeviceKey key);
|
||||
void ClearKeyMapping(int device_id);
|
||||
|
||||
/**
|
||||
* Maps a key press action and call the corresponding function in EmuWindow
|
||||
*/
|
||||
void PressKey(EmuWindow& emu_window, HostDeviceKey key);
|
||||
|
||||
/**
|
||||
* Maps a key release action and call the corresponding function in EmuWindow
|
||||
*/
|
||||
void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key);
|
||||
|
||||
}
|
||||
|
@ -70,7 +70,10 @@ set(SRCS
|
||||
hle/service/cfg/cfg_s.cpp
|
||||
hle/service/cfg/cfg_u.cpp
|
||||
hle/service/csnd_snd.cpp
|
||||
hle/service/dlp_srvr.cpp
|
||||
hle/service/dlp/dlp.cpp
|
||||
hle/service/dlp/dlp_clnt.cpp
|
||||
hle/service/dlp/dlp_fkcl.cpp
|
||||
hle/service/dlp/dlp_srvr.cpp
|
||||
hle/service/dsp_dsp.cpp
|
||||
hle/service/err_f.cpp
|
||||
hle/service/frd/frd.cpp
|
||||
@ -121,6 +124,7 @@ set(SRCS
|
||||
loader/elf.cpp
|
||||
loader/loader.cpp
|
||||
loader/ncch.cpp
|
||||
loader/smdh.cpp
|
||||
tracer/recorder.cpp
|
||||
memory.cpp
|
||||
settings.cpp
|
||||
@ -205,7 +209,10 @@ set(HEADERS
|
||||
hle/service/cfg/cfg_s.h
|
||||
hle/service/cfg/cfg_u.h
|
||||
hle/service/csnd_snd.h
|
||||
hle/service/dlp_srvr.h
|
||||
hle/service/dlp/dlp.h
|
||||
hle/service/dlp/dlp_clnt.h
|
||||
hle/service/dlp/dlp_fkcl.h
|
||||
hle/service/dlp/dlp_srvr.h
|
||||
hle/service/dsp_dsp.h
|
||||
hle/service/err_f.h
|
||||
hle/service/frd/frd.h
|
||||
@ -256,6 +263,7 @@ set(HEADERS
|
||||
loader/elf.h
|
||||
loader/loader.h
|
||||
loader/ncch.h
|
||||
loader/smdh.h
|
||||
tracer/recorder.h
|
||||
tracer/citrace.h
|
||||
memory.h
|
||||
|
@ -422,6 +422,10 @@ ARMDecodeStatus DecodeARMInstruction(u32 instr, s32* idx) {
|
||||
n = arm_instruction[i].attribute_value;
|
||||
base = 0;
|
||||
|
||||
// 3DS has no VFP3 support
|
||||
if (arm_instruction[i].version == ARMVFP3)
|
||||
continue;
|
||||
|
||||
while (n) {
|
||||
if (arm_instruction[i].content[base + 1] == 31 && arm_instruction[i].content[base] == 0) {
|
||||
// clrex
|
||||
|
@ -271,8 +271,9 @@ inline int vfp_single_type(const vfp_single* s)
|
||||
// Unpack a single-precision float. Note that this returns the magnitude
|
||||
// of the single-precision float mantissa with the 1. if necessary,
|
||||
// aligned to bit 30.
|
||||
inline void vfp_single_unpack(vfp_single* s, s32 val, u32* fpscr)
|
||||
inline u32 vfp_single_unpack(vfp_single* s, s32 val, u32 fpscr)
|
||||
{
|
||||
u32 exceptions = 0;
|
||||
s->sign = vfp_single_packed_sign(val) >> 16,
|
||||
s->exponent = vfp_single_packed_exponent(val);
|
||||
|
||||
@ -283,12 +284,13 @@ inline void vfp_single_unpack(vfp_single* s, s32 val, u32* fpscr)
|
||||
|
||||
// If flush-to-zero mode is enabled, turn the denormal into zero.
|
||||
// On a VFPv2 architecture, the sign of the zero is always positive.
|
||||
if ((*fpscr & FPSCR_FLUSH_TO_ZERO) != 0 && (vfp_single_type(s) & VFP_DENORMAL) != 0) {
|
||||
if ((fpscr & FPSCR_FLUSH_TO_ZERO) != 0 && (vfp_single_type(s) & VFP_DENORMAL) != 0) {
|
||||
s->sign = 0;
|
||||
s->exponent = 0;
|
||||
s->significand = 0;
|
||||
*fpscr |= FPSCR_IDC;
|
||||
exceptions |= FPSCR_IDC;
|
||||
}
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
// Re-pack a single-precision float. This assumes that the float is
|
||||
@ -302,7 +304,7 @@ inline s32 vfp_single_pack(const vfp_single* s)
|
||||
}
|
||||
|
||||
|
||||
u32 vfp_single_normaliseround(ARMul_State* state, int sd, vfp_single* vs, u32 fpscr, u32 exceptions, const char* func);
|
||||
u32 vfp_single_normaliseround(ARMul_State* state, int sd, vfp_single* vs, u32 fpscr, const char* func);
|
||||
|
||||
// Double-precision
|
||||
struct vfp_double {
|
||||
@ -357,8 +359,9 @@ inline int vfp_double_type(const vfp_double* s)
|
||||
// Unpack a double-precision float. Note that this returns the magnitude
|
||||
// of the double-precision float mantissa with the 1. if necessary,
|
||||
// aligned to bit 62.
|
||||
inline void vfp_double_unpack(vfp_double* s, s64 val, u32* fpscr)
|
||||
inline u32 vfp_double_unpack(vfp_double* s, s64 val, u32 fpscr)
|
||||
{
|
||||
u32 exceptions = 0;
|
||||
s->sign = vfp_double_packed_sign(val) >> 48;
|
||||
s->exponent = vfp_double_packed_exponent(val);
|
||||
|
||||
@ -369,12 +372,13 @@ inline void vfp_double_unpack(vfp_double* s, s64 val, u32* fpscr)
|
||||
|
||||
// If flush-to-zero mode is enabled, turn the denormal into zero.
|
||||
// On a VFPv2 architecture, the sign of the zero is always positive.
|
||||
if ((*fpscr & FPSCR_FLUSH_TO_ZERO) != 0 && (vfp_double_type(s) & VFP_DENORMAL) != 0) {
|
||||
if ((fpscr & FPSCR_FLUSH_TO_ZERO) != 0 && (vfp_double_type(s) & VFP_DENORMAL) != 0) {
|
||||
s->sign = 0;
|
||||
s->exponent = 0;
|
||||
s->significand = 0;
|
||||
*fpscr |= FPSCR_IDC;
|
||||
exceptions |= FPSCR_IDC;
|
||||
}
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
// Re-pack a double-precision float. This assumes that the float is
|
||||
@ -447,4 +451,4 @@ inline u32 fls(u32 x)
|
||||
|
||||
u32 vfp_double_multiply(vfp_double* vdd, vfp_double* vdn, vfp_double* vdm, u32 fpscr);
|
||||
u32 vfp_double_add(vfp_double* vdd, vfp_double* vdn, vfp_double *vdm, u32 fpscr);
|
||||
u32 vfp_double_normaliseround(ARMul_State* state, int dd, vfp_double* vd, u32 fpscr, u32 exceptions, const char* func);
|
||||
u32 vfp_double_normaliseround(ARMul_State* state, int dd, vfp_double* vd, u32 fpscr, const char* func);
|
||||
|
@ -85,11 +85,12 @@ static void vfp_double_normalise_denormal(struct vfp_double *vd)
|
||||
vfp_double_dump("normalise_denormal: out", vd);
|
||||
}
|
||||
|
||||
u32 vfp_double_normaliseround(ARMul_State* state, int dd, struct vfp_double *vd, u32 fpscr, u32 exceptions, const char *func)
|
||||
u32 vfp_double_normaliseround(ARMul_State* state, int dd, struct vfp_double *vd, u32 fpscr, const char *func)
|
||||
{
|
||||
u64 significand, incr;
|
||||
int exponent, shift, underflow;
|
||||
u32 rmode;
|
||||
u32 exceptions = 0;
|
||||
|
||||
vfp_double_dump("pack: in", vd);
|
||||
|
||||
@ -291,8 +292,9 @@ static u32 vfp_double_fsqrt(ARMul_State* state, int dd, int unused, int dm, u32
|
||||
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
|
||||
vfp_double vdm, vdd, *vdp;
|
||||
int ret, tm;
|
||||
u32 exceptions = 0;
|
||||
|
||||
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
|
||||
|
||||
tm = vfp_double_type(&vdm);
|
||||
if (tm & (VFP_NAN|VFP_INFINITY)) {
|
||||
@ -369,7 +371,8 @@ sqrt_invalid:
|
||||
}
|
||||
vdd.significand = vfp_shiftright64jamming(vdd.significand, 1);
|
||||
|
||||
return vfp_double_normaliseround(state, dd, &vdd, fpscr, 0, "fsqrt");
|
||||
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fsqrt");
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -475,7 +478,7 @@ static u32 vfp_double_fcvts(ARMul_State* state, int sd, int unused, int dm, u32
|
||||
u32 exceptions = 0;
|
||||
|
||||
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
|
||||
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
|
||||
|
||||
tm = vfp_double_type(&vdm);
|
||||
|
||||
@ -504,7 +507,8 @@ static u32 vfp_double_fcvts(ARMul_State* state, int sd, int unused, int dm, u32
|
||||
else
|
||||
vsd.exponent = vdm.exponent - (1023 - 127);
|
||||
|
||||
return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fcvts");
|
||||
exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fcvts");
|
||||
return exceptions;
|
||||
|
||||
pack_nan:
|
||||
vfp_put_float(state, vfp_single_pack(&vsd), sd);
|
||||
@ -514,6 +518,7 @@ pack_nan:
|
||||
static u32 vfp_double_fuito(ARMul_State* state, int dd, int unused, int dm, u32 fpscr)
|
||||
{
|
||||
struct vfp_double vdm;
|
||||
u32 exceptions = 0;
|
||||
u32 m = vfp_get_float(state, dm);
|
||||
|
||||
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
|
||||
@ -521,12 +526,14 @@ static u32 vfp_double_fuito(ARMul_State* state, int dd, int unused, int dm, u32
|
||||
vdm.exponent = 1023 + 63 - 1;
|
||||
vdm.significand = (u64)m;
|
||||
|
||||
return vfp_double_normaliseround(state, dd, &vdm, fpscr, 0, "fuito");
|
||||
exceptions |= vfp_double_normaliseround(state, dd, &vdm, fpscr, "fuito");
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
static u32 vfp_double_fsito(ARMul_State* state, int dd, int unused, int dm, u32 fpscr)
|
||||
{
|
||||
struct vfp_double vdm;
|
||||
u32 exceptions = 0;
|
||||
u32 m = vfp_get_float(state, dm);
|
||||
|
||||
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
|
||||
@ -534,7 +541,8 @@ static u32 vfp_double_fsito(ARMul_State* state, int dd, int unused, int dm, u32
|
||||
vdm.exponent = 1023 + 63 - 1;
|
||||
vdm.significand = vdm.sign ? (~m + 1) : m;
|
||||
|
||||
return vfp_double_normaliseround(state, dd, &vdm, fpscr, 0, "fsito");
|
||||
exceptions |= vfp_double_normaliseround(state, dd, &vdm, fpscr, "fsito");
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32 fpscr)
|
||||
@ -545,7 +553,7 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32
|
||||
int tm;
|
||||
|
||||
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
|
||||
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
|
||||
|
||||
/*
|
||||
* Do we have a denormalised number?
|
||||
@ -560,7 +568,7 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32
|
||||
if (vdm.exponent >= 1023 + 32) {
|
||||
d = vdm.sign ? 0 : 0xffffffff;
|
||||
exceptions = FPSCR_IOC;
|
||||
} else if (vdm.exponent >= 1023 - 1) {
|
||||
} else if (vdm.exponent >= 1023) {
|
||||
int shift = 1023 + 63 - vdm.exponent;
|
||||
u64 rem, incr = 0;
|
||||
|
||||
@ -595,12 +603,20 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32
|
||||
} else {
|
||||
d = 0;
|
||||
if (vdm.exponent | vdm.significand) {
|
||||
exceptions |= FPSCR_IXC;
|
||||
if (rmode == FPSCR_ROUND_PLUSINF && vdm.sign == 0)
|
||||
if (rmode == FPSCR_ROUND_NEAREST) {
|
||||
if (vdm.exponent >= 1022) {
|
||||
d = vdm.sign ? 0 : 1;
|
||||
exceptions |= vdm.sign ? FPSCR_IOC : FPSCR_IXC;
|
||||
} else {
|
||||
exceptions |= FPSCR_IXC;
|
||||
}
|
||||
} else if (rmode == FPSCR_ROUND_PLUSINF && vdm.sign == 0) {
|
||||
d = 1;
|
||||
else if (rmode == FPSCR_ROUND_MINUSINF && vdm.sign) {
|
||||
d = 0;
|
||||
exceptions |= FPSCR_IOC;
|
||||
exceptions |= FPSCR_IXC;
|
||||
} else if (rmode == FPSCR_ROUND_MINUSINF) {
|
||||
exceptions |= vdm.sign ? FPSCR_IOC : FPSCR_IXC;
|
||||
} else {
|
||||
exceptions |= FPSCR_IXC;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -615,7 +631,7 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32
|
||||
static u32 vfp_double_ftouiz(ARMul_State* state, int sd, int unused, int dm, u32 fpscr)
|
||||
{
|
||||
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
|
||||
return vfp_double_ftoui(state, sd, unused, dm, FPSCR_ROUND_TOZERO);
|
||||
return vfp_double_ftoui(state, sd, unused, dm, (fpscr & ~FPSCR_RMODE_MASK) | FPSCR_ROUND_TOZERO);
|
||||
}
|
||||
|
||||
static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32 fpscr)
|
||||
@ -626,7 +642,7 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32
|
||||
int tm;
|
||||
|
||||
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
|
||||
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
|
||||
vfp_double_dump("VDM", &vdm);
|
||||
|
||||
/*
|
||||
@ -639,12 +655,12 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32
|
||||
if (tm & VFP_NAN) {
|
||||
d = 0;
|
||||
exceptions |= FPSCR_IOC;
|
||||
} else if (vdm.exponent >= 1023 + 32) {
|
||||
} else if (vdm.exponent >= 1023 + 31) {
|
||||
d = 0x7fffffff;
|
||||
if (vdm.sign)
|
||||
d = ~d;
|
||||
exceptions |= FPSCR_IOC;
|
||||
} else if (vdm.exponent >= 1023 - 1) {
|
||||
} else if (vdm.exponent >= 1023) {
|
||||
int shift = 1023 + 63 - vdm.exponent; /* 58 */
|
||||
u64 rem, incr = 0;
|
||||
|
||||
@ -675,10 +691,17 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32
|
||||
d = 0;
|
||||
if (vdm.exponent | vdm.significand) {
|
||||
exceptions |= FPSCR_IXC;
|
||||
if (rmode == FPSCR_ROUND_PLUSINF && vdm.sign == 0)
|
||||
if (rmode == FPSCR_ROUND_NEAREST) {
|
||||
if (vdm.exponent >= 1022) {
|
||||
d = vdm.sign ? 0xffffffff : 1;
|
||||
} else {
|
||||
d = 0;
|
||||
}
|
||||
} else if (rmode == FPSCR_ROUND_PLUSINF && vdm.sign == 0) {
|
||||
d = 1;
|
||||
else if (rmode == FPSCR_ROUND_MINUSINF && vdm.sign)
|
||||
d = -1;
|
||||
} else if (rmode == FPSCR_ROUND_MINUSINF && vdm.sign) {
|
||||
d = 0xffffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -692,7 +715,7 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32
|
||||
static u32 vfp_double_ftosiz(ARMul_State* state, int dd, int unused, int dm, u32 fpscr)
|
||||
{
|
||||
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
|
||||
return vfp_double_ftosi(state, dd, unused, dm, FPSCR_ROUND_TOZERO);
|
||||
return vfp_double_ftosi(state, dd, unused, dm, (fpscr & ~FPSCR_RMODE_MASK) | FPSCR_ROUND_TOZERO);
|
||||
}
|
||||
|
||||
static struct op fops_ext[] = {
|
||||
@ -892,21 +915,21 @@ static u32
|
||||
vfp_double_multiply_accumulate(ARMul_State* state, int dd, int dn, int dm, u32 fpscr, u32 negate, const char *func)
|
||||
{
|
||||
struct vfp_double vdd, vdp, vdn, vdm;
|
||||
u32 exceptions;
|
||||
u32 exceptions = 0;
|
||||
|
||||
vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr);
|
||||
if (vdn.exponent == 0 && vdn.significand)
|
||||
vfp_double_normalise_denormal(&vdn);
|
||||
|
||||
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
|
||||
if (vdm.exponent == 0 && vdm.significand)
|
||||
vfp_double_normalise_denormal(&vdm);
|
||||
|
||||
exceptions = vfp_double_multiply(&vdp, &vdn, &vdm, fpscr);
|
||||
exceptions |= vfp_double_multiply(&vdp, &vdn, &vdm, fpscr);
|
||||
if (negate & NEG_MULTIPLY)
|
||||
vdp.sign = vfp_sign_negate(vdp.sign);
|
||||
|
||||
vfp_double_unpack(&vdn, vfp_get_double(state, dd), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dd), fpscr);
|
||||
if (vdn.exponent == 0 && vdn.significand != 0)
|
||||
vfp_double_normalise_denormal(&vdn);
|
||||
|
||||
@ -915,7 +938,8 @@ vfp_double_multiply_accumulate(ARMul_State* state, int dd, int dn, int dm, u32 f
|
||||
|
||||
exceptions |= vfp_double_add(&vdd, &vdn, &vdp, fpscr);
|
||||
|
||||
return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, func);
|
||||
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, func);
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -964,19 +988,21 @@ static u32 vfp_double_fnmsc(ARMul_State* state, int dd, int dn, int dm, u32 fpsc
|
||||
static u32 vfp_double_fmul(ARMul_State* state, int dd, int dn, int dm, u32 fpscr)
|
||||
{
|
||||
struct vfp_double vdd, vdn, vdm;
|
||||
u32 exceptions;
|
||||
u32 exceptions = 0;
|
||||
|
||||
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
|
||||
vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr);
|
||||
if (vdn.exponent == 0 && vdn.significand)
|
||||
vfp_double_normalise_denormal(&vdn);
|
||||
|
||||
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
|
||||
if (vdm.exponent == 0 && vdm.significand)
|
||||
vfp_double_normalise_denormal(&vdm);
|
||||
|
||||
exceptions = vfp_double_multiply(&vdd, &vdn, &vdm, fpscr);
|
||||
return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fmul");
|
||||
exceptions |= vfp_double_multiply(&vdd, &vdn, &vdm, fpscr);
|
||||
|
||||
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fmul");
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -985,21 +1011,22 @@ static u32 vfp_double_fmul(ARMul_State* state, int dd, int dn, int dm, u32 fpscr
|
||||
static u32 vfp_double_fnmul(ARMul_State* state, int dd, int dn, int dm, u32 fpscr)
|
||||
{
|
||||
struct vfp_double vdd, vdn, vdm;
|
||||
u32 exceptions;
|
||||
u32 exceptions = 0;
|
||||
|
||||
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
|
||||
vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr);
|
||||
if (vdn.exponent == 0 && vdn.significand)
|
||||
vfp_double_normalise_denormal(&vdn);
|
||||
|
||||
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
|
||||
if (vdm.exponent == 0 && vdm.significand)
|
||||
vfp_double_normalise_denormal(&vdm);
|
||||
|
||||
exceptions = vfp_double_multiply(&vdd, &vdn, &vdm, fpscr);
|
||||
exceptions |= vfp_double_multiply(&vdd, &vdn, &vdm, fpscr);
|
||||
vdd.sign = vfp_sign_negate(vdd.sign);
|
||||
|
||||
return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fnmul");
|
||||
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fnmul");
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1008,20 +1035,21 @@ static u32 vfp_double_fnmul(ARMul_State* state, int dd, int dn, int dm, u32 fpsc
|
||||
static u32 vfp_double_fadd(ARMul_State* state, int dd, int dn, int dm, u32 fpscr)
|
||||
{
|
||||
struct vfp_double vdd, vdn, vdm;
|
||||
u32 exceptions;
|
||||
u32 exceptions = 0;
|
||||
|
||||
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
|
||||
vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr);
|
||||
if (vdn.exponent == 0 && vdn.significand)
|
||||
vfp_double_normalise_denormal(&vdn);
|
||||
|
||||
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
|
||||
if (vdm.exponent == 0 && vdm.significand)
|
||||
vfp_double_normalise_denormal(&vdm);
|
||||
|
||||
exceptions = vfp_double_add(&vdd, &vdn, &vdm, fpscr);
|
||||
exceptions |= vfp_double_add(&vdd, &vdn, &vdm, fpscr);
|
||||
|
||||
return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fadd");
|
||||
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fadd");
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1030,14 +1058,14 @@ static u32 vfp_double_fadd(ARMul_State* state, int dd, int dn, int dm, u32 fpscr
|
||||
static u32 vfp_double_fsub(ARMul_State* state, int dd, int dn, int dm, u32 fpscr)
|
||||
{
|
||||
struct vfp_double vdd, vdn, vdm;
|
||||
u32 exceptions;
|
||||
u32 exceptions = 0;
|
||||
|
||||
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
|
||||
vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr);
|
||||
if (vdn.exponent == 0 && vdn.significand)
|
||||
vfp_double_normalise_denormal(&vdn);
|
||||
|
||||
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
|
||||
if (vdm.exponent == 0 && vdm.significand)
|
||||
vfp_double_normalise_denormal(&vdm);
|
||||
|
||||
@ -1046,9 +1074,10 @@ static u32 vfp_double_fsub(ARMul_State* state, int dd, int dn, int dm, u32 fpscr
|
||||
*/
|
||||
vdm.sign = vfp_sign_negate(vdm.sign);
|
||||
|
||||
exceptions = vfp_double_add(&vdd, &vdn, &vdm, fpscr);
|
||||
exceptions |= vfp_double_add(&vdd, &vdn, &vdm, fpscr);
|
||||
|
||||
return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fsub");
|
||||
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fsub");
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1061,8 +1090,8 @@ static u32 vfp_double_fdiv(ARMul_State* state, int dd, int dn, int dm, u32 fpscr
|
||||
int tm, tn;
|
||||
|
||||
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
|
||||
vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr);
|
||||
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr);
|
||||
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
|
||||
|
||||
vdd.sign = vdn.sign ^ vdm.sign;
|
||||
|
||||
@ -1131,16 +1160,18 @@ static u32 vfp_double_fdiv(ARMul_State* state, int dd, int dn, int dm, u32 fpscr
|
||||
}
|
||||
vdd.significand |= (reml != 0);
|
||||
}
|
||||
return vfp_double_normaliseround(state, dd, &vdd, fpscr, 0, "fdiv");
|
||||
|
||||
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fdiv");
|
||||
return exceptions;
|
||||
|
||||
vdn_nan:
|
||||
exceptions = vfp_propagate_nan(&vdd, &vdn, &vdm, fpscr);
|
||||
exceptions |= vfp_propagate_nan(&vdd, &vdn, &vdm, fpscr);
|
||||
pack:
|
||||
vfp_put_double(state, vfp_double_pack(&vdd), dd);
|
||||
return exceptions;
|
||||
|
||||
vdm_nan:
|
||||
exceptions = vfp_propagate_nan(&vdd, &vdm, &vdn, fpscr);
|
||||
exceptions |= vfp_propagate_nan(&vdd, &vdm, &vdn, fpscr);
|
||||
goto pack;
|
||||
|
||||
zero:
|
||||
@ -1149,7 +1180,7 @@ zero:
|
||||
goto pack;
|
||||
|
||||
divzero:
|
||||
exceptions = FPSCR_DZC;
|
||||
exceptions |= FPSCR_DZC;
|
||||
infinity:
|
||||
vdd.exponent = 2047;
|
||||
vdd.significand = 0;
|
||||
@ -1157,7 +1188,8 @@ infinity:
|
||||
|
||||
invalid:
|
||||
vfp_put_double(state, vfp_double_pack(&vfp_double_default_qnan), dd);
|
||||
return FPSCR_IOC;
|
||||
exceptions |= FPSCR_IOC;
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
static struct op fops[] = {
|
||||
|
@ -89,10 +89,11 @@ static void vfp_single_normalise_denormal(struct vfp_single *vs)
|
||||
}
|
||||
|
||||
|
||||
u32 vfp_single_normaliseround(ARMul_State* state, int sd, struct vfp_single *vs, u32 fpscr, u32 exceptions, const char *func)
|
||||
u32 vfp_single_normaliseround(ARMul_State* state, int sd, struct vfp_single *vs, u32 fpscr, const char *func)
|
||||
{
|
||||
u32 significand, incr, rmode;
|
||||
int exponent, shift, underflow;
|
||||
u32 exceptions = 0;
|
||||
|
||||
vfp_single_dump("pack: in", vs);
|
||||
|
||||
@ -334,8 +335,9 @@ static u32 vfp_single_fsqrt(ARMul_State* state, int sd, int unused, s32 m, u32 f
|
||||
{
|
||||
struct vfp_single vsm, vsd, *vsp;
|
||||
int ret, tm;
|
||||
u32 exceptions = 0;
|
||||
|
||||
vfp_single_unpack(&vsm, m, &fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
|
||||
tm = vfp_single_type(&vsm);
|
||||
if (tm & (VFP_NAN|VFP_INFINITY)) {
|
||||
vsp = &vsd;
|
||||
@ -408,7 +410,8 @@ sqrt_invalid:
|
||||
}
|
||||
vsd.significand = vfp_shiftright32jamming(vsd.significand, 1);
|
||||
|
||||
return vfp_single_normaliseround(state, sd, &vsd, fpscr, 0, "fsqrt");
|
||||
exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fsqrt");
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -503,7 +506,7 @@ static u32 vfp_single_fcvtd(ARMul_State* state, int dd, int unused, s32 m, u32 f
|
||||
int tm;
|
||||
u32 exceptions = 0;
|
||||
|
||||
vfp_single_unpack(&vsm, m, &fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
|
||||
|
||||
tm = vfp_single_type(&vsm);
|
||||
|
||||
@ -511,7 +514,7 @@ static u32 vfp_single_fcvtd(ARMul_State* state, int dd, int unused, s32 m, u32 f
|
||||
* If we have a signalling NaN, signal invalid operation.
|
||||
*/
|
||||
if (tm == VFP_SNAN)
|
||||
exceptions = FPSCR_IOC;
|
||||
exceptions |= FPSCR_IOC;
|
||||
|
||||
if (tm & VFP_DENORMAL)
|
||||
vfp_single_normalise_denormal(&vsm);
|
||||
@ -532,7 +535,8 @@ static u32 vfp_single_fcvtd(ARMul_State* state, int dd, int unused, s32 m, u32 f
|
||||
else
|
||||
vdd.exponent = vsm.exponent + (1023 - 127);
|
||||
|
||||
return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fcvtd");
|
||||
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fcvtd");
|
||||
return exceptions;
|
||||
|
||||
pack_nan:
|
||||
vfp_put_double(state, vfp_double_pack(&vdd), dd);
|
||||
@ -542,23 +546,27 @@ pack_nan:
|
||||
static u32 vfp_single_fuito(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr)
|
||||
{
|
||||
struct vfp_single vs;
|
||||
u32 exceptions = 0;
|
||||
|
||||
vs.sign = 0;
|
||||
vs.exponent = 127 + 31 - 1;
|
||||
vs.significand = (u32)m;
|
||||
|
||||
return vfp_single_normaliseround(state, sd, &vs, fpscr, 0, "fuito");
|
||||
exceptions |= vfp_single_normaliseround(state, sd, &vs, fpscr, "fuito");
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
static u32 vfp_single_fsito(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr)
|
||||
{
|
||||
struct vfp_single vs;
|
||||
u32 exceptions = 0;
|
||||
|
||||
vs.sign = (m & 0x80000000) >> 16;
|
||||
vs.exponent = 127 + 31 - 1;
|
||||
vs.significand = vs.sign ? -m : m;
|
||||
|
||||
return vfp_single_normaliseround(state, sd, &vs, fpscr, 0, "fsito");
|
||||
exceptions |= vfp_single_normaliseround(state, sd, &vs, fpscr, "fsito");
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr)
|
||||
@ -568,7 +576,7 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f
|
||||
int rmode = fpscr & FPSCR_RMODE_MASK;
|
||||
int tm;
|
||||
|
||||
vfp_single_unpack(&vsm, m, &fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
|
||||
vfp_single_dump("VSM", &vsm);
|
||||
|
||||
/*
|
||||
@ -583,7 +591,7 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f
|
||||
|
||||
if (vsm.exponent >= 127 + 32) {
|
||||
d = vsm.sign ? 0 : 0xffffffff;
|
||||
exceptions = FPSCR_IOC;
|
||||
exceptions |= FPSCR_IOC;
|
||||
} else if (vsm.exponent >= 127) {
|
||||
int shift = 127 + 31 - vsm.exponent;
|
||||
u32 rem, incr = 0;
|
||||
@ -592,7 +600,11 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f
|
||||
* 2^0 <= m < 2^32-2^8
|
||||
*/
|
||||
d = (vsm.significand << 1) >> shift;
|
||||
rem = vsm.significand << (33 - shift);
|
||||
if (shift > 0) {
|
||||
rem = (vsm.significand << 1) << (32 - shift);
|
||||
} else {
|
||||
rem = 0;
|
||||
}
|
||||
|
||||
if (rmode == FPSCR_ROUND_NEAREST) {
|
||||
incr = 0x80000000;
|
||||
@ -619,12 +631,20 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f
|
||||
} else {
|
||||
d = 0;
|
||||
if (vsm.exponent | vsm.significand) {
|
||||
exceptions |= FPSCR_IXC;
|
||||
if (rmode == FPSCR_ROUND_PLUSINF && vsm.sign == 0)
|
||||
if (rmode == FPSCR_ROUND_NEAREST) {
|
||||
if (vsm.exponent >= 126) {
|
||||
d = vsm.sign ? 0 : 1;
|
||||
exceptions |= vsm.sign ? FPSCR_IOC : FPSCR_IXC;
|
||||
} else {
|
||||
exceptions |= FPSCR_IXC;
|
||||
}
|
||||
} else if (rmode == FPSCR_ROUND_PLUSINF && vsm.sign == 0) {
|
||||
d = 1;
|
||||
else if (rmode == FPSCR_ROUND_MINUSINF && vsm.sign) {
|
||||
d = 0;
|
||||
exceptions |= FPSCR_IOC;
|
||||
exceptions |= FPSCR_IXC;
|
||||
} else if (rmode == FPSCR_ROUND_MINUSINF) {
|
||||
exceptions |= vsm.sign ? FPSCR_IOC : FPSCR_IXC;
|
||||
} else {
|
||||
exceptions |= FPSCR_IXC;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -638,7 +658,7 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f
|
||||
|
||||
static u32 vfp_single_ftouiz(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr)
|
||||
{
|
||||
return vfp_single_ftoui(state, sd, unused, m, FPSCR_ROUND_TOZERO);
|
||||
return vfp_single_ftoui(state, sd, unused, m, (fpscr & ~FPSCR_RMODE_MASK) | FPSCR_ROUND_TOZERO);
|
||||
}
|
||||
|
||||
static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr)
|
||||
@ -648,7 +668,7 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f
|
||||
int rmode = fpscr & FPSCR_RMODE_MASK;
|
||||
int tm;
|
||||
|
||||
vfp_single_unpack(&vsm, m, &fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
|
||||
vfp_single_dump("VSM", &vsm);
|
||||
|
||||
/*
|
||||
@ -661,7 +681,7 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f
|
||||
if (tm & VFP_NAN) {
|
||||
d = 0;
|
||||
exceptions |= FPSCR_IOC;
|
||||
} else if (vsm.exponent >= 127 + 32) {
|
||||
} else if (vsm.exponent >= 127 + 31) {
|
||||
/*
|
||||
* m >= 2^31-2^7: invalid
|
||||
*/
|
||||
@ -675,7 +695,7 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f
|
||||
|
||||
/* 2^0 <= m <= 2^31-2^7 */
|
||||
d = (vsm.significand << 1) >> shift;
|
||||
rem = vsm.significand << (33 - shift);
|
||||
rem = (vsm.significand << 1) << (32 - shift);
|
||||
|
||||
if (rmode == FPSCR_ROUND_NEAREST) {
|
||||
incr = 0x80000000;
|
||||
@ -701,10 +721,14 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f
|
||||
d = 0;
|
||||
if (vsm.exponent | vsm.significand) {
|
||||
exceptions |= FPSCR_IXC;
|
||||
if (rmode == FPSCR_ROUND_PLUSINF && vsm.sign == 0)
|
||||
if (rmode == FPSCR_ROUND_NEAREST) {
|
||||
if (vsm.exponent >= 126)
|
||||
d = vsm.sign ? 0xffffffff : 1;
|
||||
} else if (rmode == FPSCR_ROUND_PLUSINF && vsm.sign == 0) {
|
||||
d = 1;
|
||||
else if (rmode == FPSCR_ROUND_MINUSINF && vsm.sign)
|
||||
d = -1;
|
||||
} else if (rmode == FPSCR_ROUND_MINUSINF && vsm.sign) {
|
||||
d = 0xffffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -717,7 +741,7 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f
|
||||
|
||||
static u32 vfp_single_ftosiz(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr)
|
||||
{
|
||||
return vfp_single_ftosi(state, sd, unused, m, FPSCR_ROUND_TOZERO);
|
||||
return vfp_single_ftosi(state, sd, unused, m, (fpscr & ~FPSCR_RMODE_MASK) | FPSCR_ROUND_TOZERO);
|
||||
}
|
||||
|
||||
static struct op fops_ext[] = {
|
||||
@ -774,7 +798,7 @@ vfp_single_fadd_nonnumber(struct vfp_single *vsd, struct vfp_single *vsn,
|
||||
/*
|
||||
* different signs -> invalid
|
||||
*/
|
||||
exceptions = FPSCR_IOC;
|
||||
exceptions |= FPSCR_IOC;
|
||||
vsp = &vfp_single_default_qnan;
|
||||
} else {
|
||||
/*
|
||||
@ -921,27 +945,27 @@ static u32
|
||||
vfp_single_multiply_accumulate(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr, u32 negate, const char *func)
|
||||
{
|
||||
vfp_single vsd, vsp, vsn, vsm;
|
||||
u32 exceptions;
|
||||
u32 exceptions = 0;
|
||||
s32 v;
|
||||
|
||||
v = vfp_get_float(state, sn);
|
||||
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, v);
|
||||
vfp_single_unpack(&vsn, v, &fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsn, v, fpscr);
|
||||
if (vsn.exponent == 0 && vsn.significand)
|
||||
vfp_single_normalise_denormal(&vsn);
|
||||
|
||||
vfp_single_unpack(&vsm, m, &fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
|
||||
if (vsm.exponent == 0 && vsm.significand)
|
||||
vfp_single_normalise_denormal(&vsm);
|
||||
|
||||
exceptions = vfp_single_multiply(&vsp, &vsn, &vsm, fpscr);
|
||||
exceptions |= vfp_single_multiply(&vsp, &vsn, &vsm, fpscr);
|
||||
|
||||
if (negate & NEG_MULTIPLY)
|
||||
vsp.sign = vfp_sign_negate(vsp.sign);
|
||||
|
||||
v = vfp_get_float(state, sd);
|
||||
LOG_TRACE(Core_ARM11, "s%u = %08x", sd, v);
|
||||
vfp_single_unpack(&vsn, v, &fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsn, v, fpscr);
|
||||
if (vsn.exponent == 0 && vsn.significand != 0)
|
||||
vfp_single_normalise_denormal(&vsn);
|
||||
|
||||
@ -950,7 +974,8 @@ vfp_single_multiply_accumulate(ARMul_State* state, int sd, int sn, s32 m, u32 fp
|
||||
|
||||
exceptions |= vfp_single_add(&vsd, &vsn, &vsp, fpscr);
|
||||
|
||||
return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, func);
|
||||
exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, func);
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -962,8 +987,10 @@ vfp_single_multiply_accumulate(ARMul_State* state, int sd, int sn, s32 m, u32 fp
|
||||
*/
|
||||
static u32 vfp_single_fmac(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
|
||||
{
|
||||
u32 exceptions = 0;
|
||||
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, sd);
|
||||
return vfp_single_multiply_accumulate(state, sd, sn, m, fpscr, 0, "fmac");
|
||||
exceptions |= vfp_single_multiply_accumulate(state, sd, sn, m, fpscr, 0, "fmac");
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1000,21 +1027,23 @@ static u32 vfp_single_fnmsc(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr
|
||||
static u32 vfp_single_fmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
|
||||
{
|
||||
struct vfp_single vsd, vsn, vsm;
|
||||
u32 exceptions;
|
||||
u32 exceptions = 0;
|
||||
s32 n = vfp_get_float(state, sn);
|
||||
|
||||
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n);
|
||||
|
||||
vfp_single_unpack(&vsn, n, &fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsn, n, fpscr);
|
||||
if (vsn.exponent == 0 && vsn.significand)
|
||||
vfp_single_normalise_denormal(&vsn);
|
||||
|
||||
vfp_single_unpack(&vsm, m, &fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
|
||||
if (vsm.exponent == 0 && vsm.significand)
|
||||
vfp_single_normalise_denormal(&vsm);
|
||||
|
||||
exceptions = vfp_single_multiply(&vsd, &vsn, &vsm, fpscr);
|
||||
return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fmul");
|
||||
exceptions |= vfp_single_multiply(&vsd, &vsn, &vsm, fpscr);
|
||||
|
||||
exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fmul");
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1023,22 +1052,24 @@ static u32 vfp_single_fmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
|
||||
static u32 vfp_single_fnmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
|
||||
{
|
||||
struct vfp_single vsd, vsn, vsm;
|
||||
u32 exceptions;
|
||||
u32 exceptions = 0;
|
||||
s32 n = vfp_get_float(state, sn);
|
||||
|
||||
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n);
|
||||
|
||||
vfp_single_unpack(&vsn, n, &fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsn, n, fpscr);
|
||||
if (vsn.exponent == 0 && vsn.significand)
|
||||
vfp_single_normalise_denormal(&vsn);
|
||||
|
||||
vfp_single_unpack(&vsm, m, &fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
|
||||
if (vsm.exponent == 0 && vsm.significand)
|
||||
vfp_single_normalise_denormal(&vsm);
|
||||
|
||||
exceptions = vfp_single_multiply(&vsd, &vsn, &vsm, fpscr);
|
||||
exceptions |= vfp_single_multiply(&vsd, &vsn, &vsm, fpscr);
|
||||
vsd.sign = vfp_sign_negate(vsd.sign);
|
||||
return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fnmul");
|
||||
|
||||
exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fnmul");
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1047,7 +1078,7 @@ static u32 vfp_single_fnmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr
|
||||
static u32 vfp_single_fadd(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
|
||||
{
|
||||
struct vfp_single vsd, vsn, vsm;
|
||||
u32 exceptions;
|
||||
u32 exceptions = 0;
|
||||
s32 n = vfp_get_float(state, sn);
|
||||
|
||||
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n);
|
||||
@ -1055,17 +1086,18 @@ static u32 vfp_single_fadd(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
|
||||
/*
|
||||
* Unpack and normalise denormals.
|
||||
*/
|
||||
vfp_single_unpack(&vsn, n, &fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsn, n, fpscr);
|
||||
if (vsn.exponent == 0 && vsn.significand)
|
||||
vfp_single_normalise_denormal(&vsn);
|
||||
|
||||
vfp_single_unpack(&vsm, m, &fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
|
||||
if (vsm.exponent == 0 && vsm.significand)
|
||||
vfp_single_normalise_denormal(&vsm);
|
||||
|
||||
exceptions = vfp_single_add(&vsd, &vsn, &vsm, fpscr);
|
||||
exceptions |= vfp_single_add(&vsd, &vsn, &vsm, fpscr);
|
||||
|
||||
return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fadd");
|
||||
exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fadd");
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1095,8 +1127,8 @@ static u32 vfp_single_fdiv(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
|
||||
|
||||
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n);
|
||||
|
||||
vfp_single_unpack(&vsn, n, &fpscr);
|
||||
vfp_single_unpack(&vsm, m, &fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsn, n, fpscr);
|
||||
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
|
||||
|
||||
vsd.sign = vsn.sign ^ vsm.sign;
|
||||
|
||||
@ -1162,16 +1194,17 @@ static u32 vfp_single_fdiv(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
|
||||
if ((vsd.significand & 0x3f) == 0)
|
||||
vsd.significand |= ((u64)vsm.significand * vsd.significand != (u64)vsn.significand << 32);
|
||||
|
||||
return vfp_single_normaliseround(state, sd, &vsd, fpscr, 0, "fdiv");
|
||||
exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fdiv");
|
||||
return exceptions;
|
||||
|
||||
vsn_nan:
|
||||
exceptions = vfp_propagate_nan(&vsd, &vsn, &vsm, fpscr);
|
||||
exceptions |= vfp_propagate_nan(&vsd, &vsn, &vsm, fpscr);
|
||||
pack:
|
||||
vfp_put_float(state, vfp_single_pack(&vsd), sd);
|
||||
return exceptions;
|
||||
|
||||
vsm_nan:
|
||||
exceptions = vfp_propagate_nan(&vsd, &vsm, &vsn, fpscr);
|
||||
exceptions |= vfp_propagate_nan(&vsd, &vsm, &vsn, fpscr);
|
||||
goto pack;
|
||||
|
||||
zero:
|
||||
@ -1180,7 +1213,7 @@ zero:
|
||||
goto pack;
|
||||
|
||||
divzero:
|
||||
exceptions = FPSCR_DZC;
|
||||
exceptions |= FPSCR_DZC;
|
||||
infinity:
|
||||
vsd.exponent = 255;
|
||||
vsd.significand = 0;
|
||||
@ -1188,7 +1221,8 @@ infinity:
|
||||
|
||||
invalid:
|
||||
vfp_put_float(state, vfp_single_pack(&vfp_single_default_qnan), sd);
|
||||
return FPSCR_IOC;
|
||||
exceptions |= FPSCR_IOC;
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
static struct op fops[] = {
|
||||
|
@ -19,22 +19,22 @@ Path::Path(LowPathType type, u32 size, u32 pointer) : type(type) {
|
||||
switch (type) {
|
||||
case Binary:
|
||||
{
|
||||
u8* data = Memory::GetPointer(pointer);
|
||||
binary = std::vector<u8>(data, data + size);
|
||||
binary.resize(size);
|
||||
Memory::ReadBlock(pointer, binary.data(), binary.size());
|
||||
break;
|
||||
}
|
||||
|
||||
case Char:
|
||||
{
|
||||
const char* data = reinterpret_cast<const char*>(Memory::GetPointer(pointer));
|
||||
string = std::string(data, size - 1); // Data is always null-terminated.
|
||||
string.resize(size - 1); // Data is always null-terminated.
|
||||
Memory::ReadBlock(pointer, &string[0], string.size());
|
||||
break;
|
||||
}
|
||||
|
||||
case Wchar:
|
||||
{
|
||||
const char16_t* data = reinterpret_cast<const char16_t*>(Memory::GetPointer(pointer));
|
||||
u16str = std::u16string(data, size/2 - 1); // Data is always null-terminated.
|
||||
u16str.resize(size / 2 - 1); // Data is always null-terminated.
|
||||
Memory::ReadBlock(pointer, &u16str[0], u16str.size() * sizeof(char16_t));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -646,7 +646,7 @@ static void ReadMemory() {
|
||||
|
||||
u8* data = Memory::GetPointer(addr);
|
||||
if (!data) {
|
||||
return SendReply("E0");
|
||||
return SendReply("E00");
|
||||
}
|
||||
|
||||
MemToGdbHex(reply, data, len);
|
||||
|
@ -32,9 +32,9 @@ ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& p
|
||||
// The LibAppJustStarted message contains a buffer with the size of the framebuffer shared memory.
|
||||
// Create the SharedMemory that will hold the framebuffer data
|
||||
Service::APT::CaptureBufferInfo capture_info;
|
||||
ASSERT(sizeof(capture_info) == parameter.buffer_size);
|
||||
ASSERT(sizeof(capture_info) == parameter.buffer.size());
|
||||
|
||||
memcpy(&capture_info, parameter.data, sizeof(capture_info));
|
||||
memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info));
|
||||
|
||||
using Kernel::MemoryPermission;
|
||||
// Allocate a heap block of the required size for this applet.
|
||||
@ -47,8 +47,7 @@ ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& p
|
||||
// Send the response message with the newly created SharedMemory
|
||||
Service::APT::MessageParameter result;
|
||||
result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
|
||||
result.data = nullptr;
|
||||
result.buffer_size = 0;
|
||||
result.buffer.clear();
|
||||
result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
|
||||
result.sender_id = static_cast<u32>(id);
|
||||
result.object = framebuffer_memory;
|
||||
@ -63,15 +62,17 @@ ResultCode MiiSelector::StartImpl(const Service::APT::AppletStartupParameter& pa
|
||||
// TODO(Subv): Set the expected fields in the response buffer before resending it to the application.
|
||||
// TODO(Subv): Reverse the parameter format for the Mii Selector
|
||||
|
||||
if(parameter.buffer_size >= sizeof(u32)) {
|
||||
// TODO: defaults return no error, but garbage in other unknown fields
|
||||
memset(parameter.data, 0, sizeof(u32));
|
||||
}
|
||||
memcpy(&config, parameter.buffer.data(), parameter.buffer.size());
|
||||
|
||||
// TODO(Subv): Find more about this structure, result code 0 is enough to let most games continue.
|
||||
MiiResult result;
|
||||
memset(&result, 0, sizeof(result));
|
||||
result.result_code = 0;
|
||||
|
||||
// Let the application know that we're closing
|
||||
Service::APT::MessageParameter message;
|
||||
message.buffer_size = parameter.buffer_size;
|
||||
message.data = parameter.data;
|
||||
message.buffer.resize(sizeof(MiiResult));
|
||||
std::memcpy(message.buffer.data(), &result, message.buffer.size());
|
||||
message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed);
|
||||
message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
|
||||
message.sender_id = static_cast<u32>(id);
|
||||
|
@ -24,7 +24,7 @@ struct MiiConfig {
|
||||
u8 unk_004;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
u16 unk_008;
|
||||
INSERT_PADDING_BYTES(0x8C - 0xA);
|
||||
INSERT_PADDING_BYTES(0x82);
|
||||
u8 unk_08C;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
u16 unk_090;
|
||||
@ -75,6 +75,8 @@ public:
|
||||
|
||||
/// Whether this applet is currently running instead of the host application or not.
|
||||
bool started;
|
||||
|
||||
MiiConfig config;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -35,9 +35,9 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con
|
||||
// The LibAppJustStarted message contains a buffer with the size of the framebuffer shared memory.
|
||||
// Create the SharedMemory that will hold the framebuffer data
|
||||
Service::APT::CaptureBufferInfo capture_info;
|
||||
ASSERT(sizeof(capture_info) == parameter.buffer_size);
|
||||
ASSERT(sizeof(capture_info) == parameter.buffer.size());
|
||||
|
||||
memcpy(&capture_info, parameter.data, sizeof(capture_info));
|
||||
memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info));
|
||||
|
||||
using Kernel::MemoryPermission;
|
||||
// Allocate a heap block of the required size for this applet.
|
||||
@ -50,8 +50,7 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con
|
||||
// Send the response message with the newly created SharedMemory
|
||||
Service::APT::MessageParameter result;
|
||||
result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
|
||||
result.data = nullptr;
|
||||
result.buffer_size = 0;
|
||||
result.buffer.clear();
|
||||
result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
|
||||
result.sender_id = static_cast<u32>(id);
|
||||
result.object = framebuffer_memory;
|
||||
@ -61,9 +60,9 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con
|
||||
}
|
||||
|
||||
ResultCode SoftwareKeyboard::StartImpl(Service::APT::AppletStartupParameter const& parameter) {
|
||||
ASSERT_MSG(parameter.buffer_size == sizeof(config), "The size of the parameter (SoftwareKeyboardConfig) is wrong");
|
||||
ASSERT_MSG(parameter.buffer.size() == sizeof(config), "The size of the parameter (SoftwareKeyboardConfig) is wrong");
|
||||
|
||||
memcpy(&config, parameter.data, parameter.buffer_size);
|
||||
memcpy(&config, parameter.buffer.data(), parameter.buffer.size());
|
||||
text_memory = boost::static_pointer_cast<Kernel::SharedMemory, Kernel::Object>(parameter.object);
|
||||
|
||||
// TODO(Subv): Verify if this is the correct behavior
|
||||
@ -99,7 +98,7 @@ void SoftwareKeyboard::DrawScreenKeyboard() {
|
||||
auto info = bottom_screen->framebuffer_info[bottom_screen->index];
|
||||
|
||||
// TODO(Subv): Draw the HLE keyboard, for now just zero-fill the framebuffer
|
||||
memset(Memory::GetPointer(info.address_left), 0, info.stride * 320);
|
||||
Memory::ZeroBlock(info.address_left, info.stride * 320);
|
||||
|
||||
GSP_GPU::SetBufferSwap(1, info);
|
||||
}
|
||||
@ -107,8 +106,8 @@ void SoftwareKeyboard::DrawScreenKeyboard() {
|
||||
void SoftwareKeyboard::Finalize() {
|
||||
// Let the application know that we're closing
|
||||
Service::APT::MessageParameter message;
|
||||
message.buffer_size = sizeof(SoftwareKeyboardConfig);
|
||||
message.data = reinterpret_cast<u8*>(&config);
|
||||
message.buffer.resize(sizeof(SoftwareKeyboardConfig));
|
||||
std::memcpy(message.buffer.data(), &config, message.buffer.size());
|
||||
message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed);
|
||||
message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
|
||||
message.sender_id = static_cast<u32>(id);
|
||||
|
@ -403,7 +403,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
|
||||
priority = new_priority;
|
||||
}
|
||||
|
||||
if (!Memory::GetPointer(entry_point)) {
|
||||
if (!Memory::IsValidVirtualAddress(entry_point)) {
|
||||
LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %08x", name.c_str(), entry_point);
|
||||
// TODO: Verify error
|
||||
return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel,
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "common/assert.h"
|
||||
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory_setup.h"
|
||||
#include "core/mmio.h"
|
||||
|
||||
|
@ -26,6 +26,7 @@ enum class ErrorDescription : u32 {
|
||||
FS_NotAFile = 250,
|
||||
FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
|
||||
OutofRangeOrMisalignedAddress = 513, // TODO(purpasmart): Check if this name fits its actual usage
|
||||
GPU_FirstInitialization = 519,
|
||||
FS_InvalidPath = 702,
|
||||
InvalidSection = 1000,
|
||||
TooLarge = 1001,
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "core/hle/service/apt/apt_u.h"
|
||||
#include "core/hle/service/apt/bcfnt/bcfnt.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/hle/service/ptm/ptm.h"
|
||||
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
@ -33,6 +34,9 @@ static Kernel::SharedPtr<Kernel::Event> parameter_event; ///< APT parameter even
|
||||
|
||||
static u32 cpu_percent; ///< CPU time available to the running application
|
||||
|
||||
// APT::CheckNew3DSApp will check this unknown_ns_state_field to determine processing mode
|
||||
static u8 unknown_ns_state_field;
|
||||
|
||||
/// Parameter data to be returned in the next call to Glance/ReceiveParameter
|
||||
static MessageParameter next_parameter;
|
||||
|
||||
@ -176,12 +180,12 @@ void SendParameter(Service::Interface* self) {
|
||||
}
|
||||
|
||||
MessageParameter param;
|
||||
param.buffer_size = buffer_size;
|
||||
param.destination_id = dst_app_id;
|
||||
param.sender_id = src_app_id;
|
||||
param.object = Kernel::g_handle_table.GetGeneric(handle);
|
||||
param.signal = signal_type;
|
||||
param.data = Memory::GetPointer(buffer);
|
||||
param.buffer.resize(buffer_size);
|
||||
Memory::ReadBlock(buffer, param.buffer.data(), param.buffer.size());
|
||||
|
||||
cmd_buff[1] = dest_applet->ReceiveParameter(param).raw;
|
||||
|
||||
@ -199,16 +203,15 @@ void ReceiveParameter(Service::Interface* self) {
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||
cmd_buff[2] = next_parameter.sender_id;
|
||||
cmd_buff[3] = next_parameter.signal; // Signal type
|
||||
cmd_buff[4] = next_parameter.buffer_size; // Parameter buffer size
|
||||
cmd_buff[4] = next_parameter.buffer.size(); // Parameter buffer size
|
||||
cmd_buff[5] = 0x10;
|
||||
cmd_buff[6] = 0;
|
||||
if (next_parameter.object != nullptr)
|
||||
cmd_buff[6] = Kernel::g_handle_table.Create(next_parameter.object).MoveFrom();
|
||||
cmd_buff[7] = (next_parameter.buffer_size << 14) | 2;
|
||||
cmd_buff[7] = (next_parameter.buffer.size() << 14) | 2;
|
||||
cmd_buff[8] = buffer;
|
||||
|
||||
if (next_parameter.data)
|
||||
memcpy(Memory::GetPointer(buffer), next_parameter.data, std::min(buffer_size, next_parameter.buffer_size));
|
||||
Memory::WriteBlock(buffer, next_parameter.buffer.data(), next_parameter.buffer.size());
|
||||
|
||||
LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size);
|
||||
}
|
||||
@ -222,16 +225,15 @@ void GlanceParameter(Service::Interface* self) {
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||
cmd_buff[2] = next_parameter.sender_id;
|
||||
cmd_buff[3] = next_parameter.signal; // Signal type
|
||||
cmd_buff[4] = next_parameter.buffer_size; // Parameter buffer size
|
||||
cmd_buff[4] = next_parameter.buffer.size(); // Parameter buffer size
|
||||
cmd_buff[5] = 0x10;
|
||||
cmd_buff[6] = 0;
|
||||
if (next_parameter.object != nullptr)
|
||||
cmd_buff[6] = Kernel::g_handle_table.Create(next_parameter.object).MoveFrom();
|
||||
cmd_buff[7] = (next_parameter.buffer_size << 14) | 2;
|
||||
cmd_buff[7] = (next_parameter.buffer.size() << 14) | 2;
|
||||
cmd_buff[8] = buffer;
|
||||
|
||||
if (next_parameter.data)
|
||||
memcpy(Memory::GetPointer(buffer), next_parameter.data, std::min(buffer_size, next_parameter.buffer_size));
|
||||
Memory::WriteBlock(buffer, next_parameter.buffer.data(), std::min(static_cast<size_t>(buffer_size), next_parameter.buffer.size()));
|
||||
|
||||
LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size);
|
||||
}
|
||||
@ -258,6 +260,10 @@ void PrepareToStartApplication(Service::Interface* self) {
|
||||
u32 title_info4 = cmd_buff[4];
|
||||
u32 flags = cmd_buff[5];
|
||||
|
||||
if (flags & 0x00000100) {
|
||||
unknown_ns_state_field = 1;
|
||||
}
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||
|
||||
LOG_WARNING(Service_APT, "(STUBBED) called title_info1=0x%08X, title_info2=0x%08X, title_info3=0x%08X,"
|
||||
@ -365,14 +371,36 @@ void StartLibraryApplet(Service::Interface* self) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t buffer_size = cmd_buff[2];
|
||||
VAddr buffer_addr = cmd_buff[6];
|
||||
|
||||
AppletStartupParameter parameter;
|
||||
parameter.buffer_size = cmd_buff[2];
|
||||
parameter.object = Kernel::g_handle_table.GetGeneric(cmd_buff[4]);
|
||||
parameter.data = Memory::GetPointer(cmd_buff[6]);
|
||||
parameter.buffer.resize(buffer_size);
|
||||
Memory::ReadBlock(buffer_addr, parameter.buffer.data(), parameter.buffer.size());
|
||||
|
||||
cmd_buff[1] = applet->Start(parameter).raw;
|
||||
}
|
||||
|
||||
void SetNSStateField(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
|
||||
unknown_ns_state_field = cmd_buff[1];
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x55, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
LOG_WARNING(Service_APT, "(STUBBED) unknown_ns_state_field=%u", unknown_ns_state_field);
|
||||
}
|
||||
|
||||
void GetNSStateField(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x56, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[8] = unknown_ns_state_field;
|
||||
LOG_WARNING(Service_APT, "(STUBBED) unknown_ns_state_field=%u", unknown_ns_state_field);
|
||||
}
|
||||
|
||||
void GetAppletInfo(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
auto app_id = static_cast<AppletId>(cmd_buff[1]);
|
||||
@ -408,6 +436,29 @@ void GetStartupArgument(Service::Interface* self) {
|
||||
cmd_buff[2] = (parameter_size > 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
void CheckNew3DSApp(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
|
||||
if (unknown_ns_state_field) {
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = 0;
|
||||
} else {
|
||||
PTM::CheckNew3DS(self);
|
||||
}
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x101, 2, 0);
|
||||
LOG_WARNING(Service_APT, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void CheckNew3DS(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
|
||||
PTM::CheckNew3DS(self);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x102, 2, 0);
|
||||
LOG_WARNING(Service_APT, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void Init() {
|
||||
AddService(new APT_A_Interface);
|
||||
AddService(new APT_S_Interface);
|
||||
@ -441,6 +492,7 @@ void Init() {
|
||||
lock = Kernel::Mutex::Create(false, "APT_U:Lock");
|
||||
|
||||
cpu_percent = 0;
|
||||
unknown_ns_state_field = 0;
|
||||
|
||||
// TODO(bunnei): Check if these are created in Initialize or on APT process startup.
|
||||
notification_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Notification");
|
||||
|
@ -20,16 +20,14 @@ struct MessageParameter {
|
||||
u32 sender_id = 0;
|
||||
u32 destination_id = 0;
|
||||
u32 signal = 0;
|
||||
u32 buffer_size = 0;
|
||||
Kernel::SharedPtr<Kernel::Object> object = nullptr;
|
||||
u8* data = nullptr;
|
||||
std::vector<u8> buffer;
|
||||
};
|
||||
|
||||
/// Holds information about the parameters used in StartLibraryApplet
|
||||
struct AppletStartupParameter {
|
||||
u32 buffer_size = 0;
|
||||
Kernel::SharedPtr<Kernel::Object> object = nullptr;
|
||||
u8* data = nullptr;
|
||||
std::vector<u8> buffer;
|
||||
};
|
||||
|
||||
/// Used by the application to pass information about the current framebuffer to applets.
|
||||
@ -376,6 +374,50 @@ void StartLibraryApplet(Service::Interface* self);
|
||||
*/
|
||||
void GetStartupArgument(Service::Interface* self);
|
||||
|
||||
/**
|
||||
* APT::SetNSStateField service function
|
||||
* Inputs:
|
||||
* 1 : u8 NS state field
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* Note:
|
||||
* This writes the input u8 to a NS state field.
|
||||
*/
|
||||
void SetNSStateField(Service::Interface* self);
|
||||
|
||||
/**
|
||||
* APT::GetNSStateField service function
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 8 : u8 NS state field
|
||||
* Note:
|
||||
* This returns a u8 NS state field(which can be set by cmd 0x00550040), at cmdreply+8.
|
||||
*/
|
||||
void GetNSStateField(Service::Interface* self);
|
||||
|
||||
/**
|
||||
* APT::CheckNew3DSApp service function
|
||||
* Outputs:
|
||||
* 1: Result code, 0 on success, otherwise error code
|
||||
* 2: u8 output: 0 = Old3DS, 1 = New3DS.
|
||||
* Note:
|
||||
* This uses PTMSYSM:CheckNew3DS.
|
||||
* When a certain NS state field is non-zero, the output value is zero,
|
||||
* Otherwise the output is from PTMSYSM:CheckNew3DS.
|
||||
* Normally this NS state field is zero, however this state field is set to 1
|
||||
* when APT:PrepareToStartApplication is used with flags bit8 is set.
|
||||
*/
|
||||
void CheckNew3DSApp(Service::Interface* self);
|
||||
|
||||
/**
|
||||
* Wrapper for PTMSYSM:CheckNew3DS
|
||||
* APT::CheckNew3DS service function
|
||||
* Outputs:
|
||||
* 1: Result code, 0 on success, otherwise error code
|
||||
* 2: u8 output: 0 = Old3DS, 1 = New3DS.
|
||||
*/
|
||||
void CheckNew3DS(Service::Interface* self);
|
||||
|
||||
/// Initialize the APT service
|
||||
void Init();
|
||||
|
||||
|
@ -21,6 +21,7 @@ const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x000D0080, ReceiveParameter, "ReceiveParameter"},
|
||||
{0x000E0080, GlanceParameter, "GlanceParameter"},
|
||||
{0x000F0100, CancelParameter, "CancelParameter"},
|
||||
{0x00150140, PrepareToStartApplication, "PrepareToStartApplication"},
|
||||
{0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"},
|
||||
{0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"},
|
||||
{0x001E0084, StartLibraryApplet, "StartLibraryApplet"},
|
||||
@ -32,7 +33,10 @@ const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x004F0080, SetAppCpuTimeLimit, "SetAppCpuTimeLimit"},
|
||||
{0x00500040, GetAppCpuTimeLimit, "GetAppCpuTimeLimit"},
|
||||
{0x00510080, GetStartupArgument, "GetStartupArgument"},
|
||||
{0x00550040, nullptr, "WriteInputToNsState?"},
|
||||
{0x00550040, SetNSStateField, "SetNSStateField?"},
|
||||
{0x00560000, GetNSStateField, "GetNSStateField?"},
|
||||
{0x01010000, CheckNew3DSApp, "CheckNew3DSApp"},
|
||||
{0x01020000, CheckNew3DS, "CheckNew3DS"}
|
||||
};
|
||||
|
||||
APT_A_Interface::APT_A_Interface() {
|
||||
|
@ -29,7 +29,7 @@ const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"},
|
||||
{0x00130000, nullptr, "GetPreparationState"},
|
||||
{0x00140040, nullptr, "SetPreparationState"},
|
||||
{0x00150140, nullptr, "PrepareToStartApplication"},
|
||||
{0x00150140, PrepareToStartApplication, "PrepareToStartApplication"},
|
||||
{0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"},
|
||||
{0x00170040, nullptr, "FinishPreloadingLibraryApplet"},
|
||||
{0x00180040, PrepareToStartLibraryApplet,"PrepareToStartLibraryApplet"},
|
||||
@ -92,9 +92,11 @@ const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x00510080, GetStartupArgument, "GetStartupArgument"},
|
||||
{0x00520104, nullptr, "Wrap1"},
|
||||
{0x00530104, nullptr, "Unwrap1"},
|
||||
{0x00550040, SetNSStateField, "SetNSStateField?" },
|
||||
{0x00560000, GetNSStateField, "GetNSStateField?" },
|
||||
{0x00580002, nullptr, "GetProgramID"},
|
||||
{0x01010000, nullptr, "CheckNew3DSApp"},
|
||||
{0x01020000, nullptr, "CheckNew3DS"}
|
||||
{0x01010000, CheckNew3DSApp, "CheckNew3DSApp"},
|
||||
{0x01020000, CheckNew3DS, "CheckNew3DS"}
|
||||
};
|
||||
|
||||
APT_S_Interface::APT_S_Interface() {
|
||||
|
@ -29,7 +29,7 @@ const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"},
|
||||
{0x00130000, nullptr, "GetPreparationState"},
|
||||
{0x00140040, nullptr, "SetPreparationState"},
|
||||
{0x00150140, nullptr, "PrepareToStartApplication"},
|
||||
{0x00150140, PrepareToStartApplication, "PrepareToStartApplication"},
|
||||
{0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"},
|
||||
{0x00170040, nullptr, "FinishPreloadingLibraryApplet"},
|
||||
{0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"},
|
||||
@ -92,9 +92,11 @@ const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x00510080, GetStartupArgument, "GetStartupArgument"},
|
||||
{0x00520104, nullptr, "Wrap1"},
|
||||
{0x00530104, nullptr, "Unwrap1"},
|
||||
{0x00550040, SetNSStateField, "SetNSStateField?"},
|
||||
{0x00560000, GetNSStateField, "GetNSStateField?"},
|
||||
{0x00580002, nullptr, "GetProgramID"},
|
||||
{0x01010000, nullptr, "CheckNew3DSApp"},
|
||||
{0x01020000, nullptr, "CheckNew3DS"}
|
||||
{0x01010000, CheckNew3DSApp, "CheckNew3DSApp"},
|
||||
{0x01020000, CheckNew3DS, "CheckNew3DS"}
|
||||
};
|
||||
|
||||
APT_U_Interface::APT_U_Interface() {
|
||||
|
@ -47,6 +47,12 @@ struct UsernameBlock {
|
||||
};
|
||||
static_assert(sizeof(UsernameBlock) == 0x1C, "UsernameBlock must be exactly 0x1C bytes");
|
||||
|
||||
struct BirthdayBlock {
|
||||
u8 month; ///< The month of the birthday
|
||||
u8 day; ///< The day of the birthday
|
||||
};
|
||||
static_assert(sizeof(BirthdayBlock) == 2, "BirthdayBlock must be exactly 2 bytes");
|
||||
|
||||
struct ConsoleModelInfo {
|
||||
u8 model; ///< The console model (3DS, 2DS, etc)
|
||||
u8 unknown[3]; ///< Unknown data
|
||||
@ -65,9 +71,8 @@ static const u64 CFG_SAVE_ID = 0x00010017;
|
||||
static const u64 CONSOLE_UNIQUE_ID = 0xDEADC0DE;
|
||||
static const ConsoleModelInfo CONSOLE_MODEL = { NINTENDO_3DS_XL, { 0, 0, 0 } };
|
||||
static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN;
|
||||
static const char CONSOLE_USERNAME[0x14] = "CITRA";
|
||||
/// This will be initialized in Init, and will be used when creating the block
|
||||
static UsernameBlock CONSOLE_USERNAME_BLOCK;
|
||||
static const UsernameBlock CONSOLE_USERNAME_BLOCK = { u"CITRA", 0, 0 };
|
||||
static const BirthdayBlock PROFILE_BIRTHDAY = { 3, 25 }; // March 25th, 2014
|
||||
/// TODO(Subv): Find out what this actually is
|
||||
static const u8 SOUND_OUTPUT_MODE = 2;
|
||||
static const u8 UNITED_STATES_COUNTRY_ID = 49;
|
||||
@ -191,28 +196,32 @@ void GetConfigInfoBlk2(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
u32 size = cmd_buff[1];
|
||||
u32 block_id = cmd_buff[2];
|
||||
u8* data_pointer = Memory::GetPointer(cmd_buff[4]);
|
||||
VAddr data_pointer = cmd_buff[4];
|
||||
|
||||
if (data_pointer == nullptr) {
|
||||
if (!Memory::IsValidVirtualAddress(data_pointer)) {
|
||||
cmd_buff[1] = -1; // TODO(Subv): Find the right error code
|
||||
return;
|
||||
}
|
||||
|
||||
cmd_buff[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x2, data_pointer).raw;
|
||||
std::vector<u8> data(size);
|
||||
cmd_buff[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x2, data.data()).raw;
|
||||
Memory::WriteBlock(data_pointer, data.data(), data.size());
|
||||
}
|
||||
|
||||
void GetConfigInfoBlk8(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
u32 size = cmd_buff[1];
|
||||
u32 block_id = cmd_buff[2];
|
||||
u8* data_pointer = Memory::GetPointer(cmd_buff[4]);
|
||||
VAddr data_pointer = cmd_buff[4];
|
||||
|
||||
if (data_pointer == nullptr) {
|
||||
if (!Memory::IsValidVirtualAddress(data_pointer)) {
|
||||
cmd_buff[1] = -1; // TODO(Subv): Find the right error code
|
||||
return;
|
||||
}
|
||||
|
||||
cmd_buff[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x8, data_pointer).raw;
|
||||
std::vector<u8> data(size);
|
||||
cmd_buff[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x8, data.data()).raw;
|
||||
Memory::WriteBlock(data_pointer, data.data(), data.size());
|
||||
}
|
||||
|
||||
void UpdateConfigNANDSavegame(Service::Interface* self) {
|
||||
@ -329,32 +338,22 @@ ResultCode FormatConfig() {
|
||||
|
||||
res = CreateConfigInfoBlk(0x00050005, sizeof(STEREO_CAMERA_SETTINGS), 0xE, STEREO_CAMERA_SETTINGS.data());
|
||||
if (!res.IsSuccess()) return res;
|
||||
|
||||
res = CreateConfigInfoBlk(0x00070001, sizeof(SOUND_OUTPUT_MODE), 0xE, &SOUND_OUTPUT_MODE);
|
||||
if (!res.IsSuccess()) return res;
|
||||
|
||||
res = CreateConfigInfoBlk(0x00090001, sizeof(CONSOLE_UNIQUE_ID), 0xE, &CONSOLE_UNIQUE_ID);
|
||||
if (!res.IsSuccess()) return res;
|
||||
|
||||
res = CreateConfigInfoBlk(0x000A0000, sizeof(CONSOLE_USERNAME_BLOCK), 0xE, &CONSOLE_USERNAME_BLOCK);
|
||||
if (!res.IsSuccess()) return res;
|
||||
|
||||
// 0x000A0000 - Profile username
|
||||
struct {
|
||||
u16_le username[10];
|
||||
u8 unused[4];
|
||||
u32_le wordfilter_version; // Unused by Citra
|
||||
} profile_username = {};
|
||||
|
||||
std::u16string username_string = Common::UTF8ToUTF16("Citra");
|
||||
std::copy(username_string.cbegin(), username_string.cend(), profile_username.username);
|
||||
res = CreateConfigInfoBlk(0x000A0000, sizeof(profile_username), 0xE, &profile_username);
|
||||
if (!res.IsSuccess()) return res;
|
||||
|
||||
// 0x000A0001 - Profile birthday
|
||||
const u8 profile_birthday[2] = {3, 25}; // March 25th, 2014
|
||||
res = CreateConfigInfoBlk(0x000A0001, sizeof(profile_birthday), 0xE, profile_birthday);
|
||||
res = CreateConfigInfoBlk(0x000A0001, sizeof(PROFILE_BIRTHDAY), 0xE, &PROFILE_BIRTHDAY);
|
||||
if (!res.IsSuccess()) return res;
|
||||
|
||||
res = CreateConfigInfoBlk(0x000A0002, sizeof(CONSOLE_LANGUAGE), 0xE, &CONSOLE_LANGUAGE);
|
||||
if (!res.IsSuccess()) return res;
|
||||
|
||||
res = CreateConfigInfoBlk(0x000B0000, sizeof(COUNTRY_INFO), 0xE, &COUNTRY_INFO);
|
||||
if (!res.IsSuccess()) return res;
|
||||
|
||||
@ -435,17 +434,6 @@ void Init() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the Username block
|
||||
// TODO(Subv): Initialize this directly in the variable when MSVC supports char16_t string literals
|
||||
memset(&CONSOLE_USERNAME_BLOCK, 0, sizeof(CONSOLE_USERNAME_BLOCK));
|
||||
CONSOLE_USERNAME_BLOCK.ng_word = 0;
|
||||
CONSOLE_USERNAME_BLOCK.zero = 0;
|
||||
|
||||
// Copy string to buffer and pad with zeros at the end
|
||||
auto size = Common::UTF8ToUTF16(CONSOLE_USERNAME).copy(CONSOLE_USERNAME_BLOCK.username, 0x14);
|
||||
std::fill(std::begin(CONSOLE_USERNAME_BLOCK.username) + size,
|
||||
std::end(CONSOLE_USERNAME_BLOCK.username), 0);
|
||||
|
||||
FormatConfig();
|
||||
}
|
||||
|
||||
|
24
src/core/hle/service/dlp/dlp.cpp
Normal file
24
src/core/hle/service/dlp/dlp.cpp
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/dlp/dlp.h"
|
||||
#include "core/hle/service/dlp/dlp_clnt.h"
|
||||
#include "core/hle/service/dlp/dlp_fkcl.h"
|
||||
#include "core/hle/service/dlp/dlp_srvr.h"
|
||||
|
||||
namespace Service {
|
||||
namespace DLP {
|
||||
|
||||
void Init() {
|
||||
AddService(new DLP_CLNT_Interface);
|
||||
AddService(new DLP_FKCL_Interface);
|
||||
AddService(new DLP_SRVR_Interface);
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
}
|
||||
|
||||
} // namespace DLP
|
||||
} // namespace Service
|
15
src/core/hle/service/dlp/dlp.h
Normal file
15
src/core/hle/service/dlp/dlp.h
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
namespace Service {
|
||||
namespace DLP {
|
||||
|
||||
/// Initializes the DLP services.
|
||||
void Init();
|
||||
|
||||
/// Shuts down the DLP services.
|
||||
void Shutdown();
|
||||
|
||||
} // namespace DLP
|
||||
} // namespace Service
|
20
src/core/hle/service/dlp/dlp_clnt.cpp
Normal file
20
src/core/hle/service/dlp/dlp_clnt.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/service/dlp/dlp_clnt.h"
|
||||
|
||||
namespace Service {
|
||||
namespace DLP {
|
||||
|
||||
const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x000100C3, nullptr, "Initialize"},
|
||||
{0x00110000, nullptr, "GetWirelessRebootPassphrase"},
|
||||
};
|
||||
|
||||
DLP_CLNT_Interface::DLP_CLNT_Interface() {
|
||||
Register(FunctionTable);
|
||||
}
|
||||
|
||||
} // namespace DLP
|
||||
} // namespace Service
|
22
src/core/hle/service/dlp/dlp_clnt.h
Normal file
22
src/core/hle/service/dlp/dlp_clnt.h
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service {
|
||||
namespace DLP {
|
||||
|
||||
class DLP_CLNT_Interface final : public Interface {
|
||||
public:
|
||||
DLP_CLNT_Interface();
|
||||
|
||||
std::string GetPortName() const override {
|
||||
return "dlp:CLNT";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace DLP
|
||||
} // namespace Service
|
20
src/core/hle/service/dlp/dlp_fkcl.cpp
Normal file
20
src/core/hle/service/dlp/dlp_fkcl.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/service/dlp/dlp_fkcl.h"
|
||||
|
||||
namespace Service {
|
||||
namespace DLP {
|
||||
|
||||
const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x00010083, nullptr, "Initialize"},
|
||||
{0x000F0000, nullptr, "GetWirelessRebootPassphrase"},
|
||||
};
|
||||
|
||||
DLP_FKCL_Interface::DLP_FKCL_Interface() {
|
||||
Register(FunctionTable);
|
||||
}
|
||||
|
||||
} // namespace DLP
|
||||
} // namespace Service
|
22
src/core/hle/service/dlp/dlp_fkcl.h
Normal file
22
src/core/hle/service/dlp/dlp_fkcl.h
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service {
|
||||
namespace DLP {
|
||||
|
||||
class DLP_FKCL_Interface final : public Interface {
|
||||
public:
|
||||
DLP_FKCL_Interface();
|
||||
|
||||
std::string GetPortName() const override {
|
||||
return "dlp:FKCL";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace DLP
|
||||
} // namespace Service
|
@ -2,16 +2,15 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/hle.h"
|
||||
#include "core/hle/service/dlp_srvr.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/dlp/dlp_srvr.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Namespace DLP_SRVR
|
||||
namespace Service {
|
||||
namespace DLP {
|
||||
|
||||
namespace DLP_SRVR {
|
||||
|
||||
static void unk_0x000E0040(Service::Interface* self) {
|
||||
static void unk_0x000E0040(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
@ -23,14 +22,13 @@ static void unk_0x000E0040(Service::Interface* self) {
|
||||
const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x00010183, nullptr, "Initialize"},
|
||||
{0x00020000, nullptr, "Finalize"},
|
||||
{0x000800C0, nullptr, "SendWirelessRebootPassphrase"},
|
||||
{0x000E0040, unk_0x000E0040, "unk_0x000E0040"},
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Interface class
|
||||
|
||||
Interface::Interface() {
|
||||
DLP_SRVR_Interface::DLP_SRVR_Interface() {
|
||||
Register(FunctionTable);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace DLP
|
||||
} // namespace Service
|
@ -6,18 +6,17 @@
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Namespace DLP_SRVR
|
||||
namespace Service {
|
||||
namespace DLP {
|
||||
|
||||
namespace DLP_SRVR {
|
||||
|
||||
class Interface : public Service::Interface {
|
||||
class DLP_SRVR_Interface final : public Interface {
|
||||
public:
|
||||
Interface();
|
||||
DLP_SRVR_Interface();
|
||||
|
||||
std::string GetPortName() const override {
|
||||
return "dlp:SRVR";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace DLP
|
||||
} // namespace Service
|
@ -140,12 +140,15 @@ static void LoadComponent(Service::Interface* self) {
|
||||
|
||||
// TODO(bunnei): Implement real DSP firmware loading
|
||||
|
||||
ASSERT(Memory::GetPointer(buffer) != nullptr);
|
||||
ASSERT(size > 0x37C);
|
||||
ASSERT(Memory::IsValidVirtualAddress(buffer));
|
||||
|
||||
LOG_INFO(Service_DSP, "Firmware hash: %#" PRIx64, Common::ComputeHash64(Memory::GetPointer(buffer), size));
|
||||
std::vector<u8> component_data(size);
|
||||
Memory::ReadBlock(buffer, component_data.data(), component_data.size());
|
||||
|
||||
LOG_INFO(Service_DSP, "Firmware hash: %#" PRIx64, Common::ComputeHash64(component_data.data(), component_data.size()));
|
||||
// Some versions of the firmware have the location of DSP structures listed here.
|
||||
LOG_INFO(Service_DSP, "Structures hash: %#" PRIx64, Common::ComputeHash64(Memory::GetPointer(buffer) + 0x340, 60));
|
||||
ASSERT(size > 0x37C);
|
||||
LOG_INFO(Service_DSP, "Structures hash: %#" PRIx64, Common::ComputeHash64(component_data.data() + 0x340, 60));
|
||||
|
||||
LOG_WARNING(Service_DSP, "(STUBBED) called size=0x%X, prog_mask=0x%08X, data_mask=0x%08X, buffer=0x%08X",
|
||||
size, prog_mask, data_mask, buffer);
|
||||
@ -285,7 +288,7 @@ static void WriteProcessPipe(Service::Interface* self) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_MSG(Memory::GetPointer(buffer) != nullptr, "Invalid Buffer: pipe=%u, size=0x%X, buffer=0x%08X", pipe_index, size, buffer);
|
||||
ASSERT_MSG(Memory::IsValidVirtualAddress(buffer), "Invalid Buffer: pipe=%u, size=0x%X, buffer=0x%08X", pipe, size, buffer);
|
||||
|
||||
std::vector<u8> message(size);
|
||||
for (u32 i = 0; i < size; i++) {
|
||||
@ -324,7 +327,7 @@ static void ReadPipeIfPossible(Service::Interface* self) {
|
||||
|
||||
DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index);
|
||||
|
||||
ASSERT_MSG(Memory::GetPointer(addr) != nullptr, "Invalid addr: pipe=%u, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe_index, unknown, size, addr);
|
||||
ASSERT_MSG(Memory::IsValidVirtualAddress(addr), "Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe, unknown, size, addr);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x10, 1, 2);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||
@ -364,7 +367,7 @@ static void ReadPipe(Service::Interface* self) {
|
||||
|
||||
DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index);
|
||||
|
||||
ASSERT_MSG(Memory::GetPointer(addr) != nullptr, "Invalid addr: pipe=%u, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe_index, unknown, size, addr);
|
||||
ASSERT_MSG(Memory::IsValidVirtualAddress(addr), "Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe, unknown, size, addr);
|
||||
|
||||
if (DSP::HLE::GetPipeReadableSize(pipe) >= size) {
|
||||
std::vector<u8> response = DSP::HLE::PipeRead(pipe, size);
|
||||
|
@ -23,7 +23,7 @@ void GetMyPresence(Service::Interface* self) {
|
||||
|
||||
ASSERT(shifted_out_size == ((sizeof(MyPresence) << 14) | 2));
|
||||
|
||||
Memory::WriteBlock(my_presence_addr, reinterpret_cast<const u8*>(&my_presence), sizeof(MyPresence));
|
||||
Memory::WriteBlock(my_presence_addr, &my_presence, sizeof(MyPresence));
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||
|
||||
@ -39,8 +39,7 @@ void GetFriendKeyList(Service::Interface* self) {
|
||||
|
||||
FriendKey zero_key = {};
|
||||
for (u32 i = 0; i < frd_count; ++i) {
|
||||
Memory::WriteBlock(frd_key_addr + i * sizeof(FriendKey),
|
||||
reinterpret_cast<const u8*>(&zero_key), sizeof(FriendKey));
|
||||
Memory::WriteBlock(frd_key_addr + i * sizeof(FriendKey), &zero_key, sizeof(FriendKey));
|
||||
}
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||
@ -58,8 +57,7 @@ void GetFriendProfile(Service::Interface* self) {
|
||||
|
||||
Profile zero_profile = {};
|
||||
for (u32 i = 0; i < count; ++i) {
|
||||
Memory::WriteBlock(profiles_addr + i * sizeof(Profile),
|
||||
reinterpret_cast<const u8*>(&zero_profile), sizeof(Profile));
|
||||
Memory::WriteBlock(profiles_addr + i * sizeof(Profile), &zero_profile, sizeof(Profile));
|
||||
}
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||
@ -88,7 +86,7 @@ void GetMyFriendKey(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||
Memory::WriteBlock(cmd_buff[2], reinterpret_cast<const u8*>(&my_friend_key), sizeof(FriendKey));
|
||||
Memory::WriteBlock(cmd_buff[2], &my_friend_key, sizeof(FriendKey));
|
||||
LOG_WARNING(Service_FRD, "(STUBBED) called");
|
||||
}
|
||||
|
||||
|
@ -108,13 +108,14 @@ ResultVal<bool> File::SyncRequest() {
|
||||
offset, length, backend->GetSize());
|
||||
}
|
||||
|
||||
ResultVal<size_t> read = backend->Read(offset, length, Memory::GetPointer(address));
|
||||
std::vector<u8> data(length);
|
||||
ResultVal<size_t> read = backend->Read(offset, data.size(), data.data());
|
||||
if (read.Failed()) {
|
||||
cmd_buff[1] = read.Code().raw;
|
||||
return read.Code();
|
||||
}
|
||||
Memory::WriteBlock(address, data.data(), *read);
|
||||
cmd_buff[2] = static_cast<u32>(*read);
|
||||
Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(address), length);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -128,7 +129,9 @@ ResultVal<bool> File::SyncRequest() {
|
||||
LOG_TRACE(Service_FS, "Write %s %s: offset=0x%llx length=%d address=0x%x, flush=0x%x",
|
||||
GetTypeName().c_str(), GetName().c_str(), offset, length, address, flush);
|
||||
|
||||
ResultVal<size_t> written = backend->Write(offset, length, flush != 0, Memory::GetPointer(address));
|
||||
std::vector<u8> data(length);
|
||||
Memory::ReadBlock(address, data.data(), data.size());
|
||||
ResultVal<size_t> written = backend->Write(offset, data.size(), flush != 0, data.data());
|
||||
if (written.Failed()) {
|
||||
cmd_buff[1] = written.Code().raw;
|
||||
return written.Code();
|
||||
@ -216,12 +219,14 @@ ResultVal<bool> Directory::SyncRequest() {
|
||||
{
|
||||
u32 count = cmd_buff[1];
|
||||
u32 address = cmd_buff[3];
|
||||
auto entries = reinterpret_cast<FileSys::Entry*>(Memory::GetPointer(address));
|
||||
std::vector<FileSys::Entry> entries(count);
|
||||
LOG_TRACE(Service_FS, "Read %s %s: count=%d",
|
||||
GetTypeName().c_str(), GetName().c_str(), count);
|
||||
|
||||
// Number of entries actually read
|
||||
cmd_buff[2] = backend->Read(count, entries);
|
||||
u32 read = backend->Read(entries.size(), entries.data());
|
||||
cmd_buff[2] = read;
|
||||
Memory::WriteBlock(address, entries.data(), read * sizeof(FileSys::Entry));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -456,11 +461,12 @@ ResultCode CreateExtSaveData(MediaType media_type, u32 high, u32 low, VAddr icon
|
||||
if (result.IsError())
|
||||
return result;
|
||||
|
||||
u8* smdh_icon = Memory::GetPointer(icon_buffer);
|
||||
if (!smdh_icon)
|
||||
if (!Memory::IsValidVirtualAddress(icon_buffer))
|
||||
return ResultCode(-1); // TODO(Subv): Find the right error code
|
||||
|
||||
ext_savedata->WriteIcon(path, smdh_icon, icon_size);
|
||||
std::vector<u8> smdh_icon(icon_size);
|
||||
Memory::ReadBlock(icon_buffer, smdh_icon.data(), smdh_icon.size());
|
||||
ext_savedata->WriteIcon(path, smdh_icon.data(), smdh_icon.size());
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ Kernel::SharedPtr<Kernel::SharedMemory> g_shared_memory;
|
||||
u32 g_thread_id = 0;
|
||||
|
||||
static bool gpu_right_acquired = false;
|
||||
|
||||
static bool first_initialization = true;
|
||||
/// Gets a pointer to a thread command buffer in GSP shared memory
|
||||
static inline u8* GetCommandBuffer(u32 thread_id) {
|
||||
return g_shared_memory->GetPointer(0x800 + (thread_id * sizeof(CommandBuffer)));
|
||||
@ -65,15 +65,27 @@ static inline InterruptRelayQueue* GetInterruptRelayQueue(u32 thread_id) {
|
||||
return reinterpret_cast<InterruptRelayQueue*>(ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a single GSP GPU hardware registers with a single u32 value
|
||||
* (For internal use.)
|
||||
*
|
||||
* @param base_address The address of the register in question
|
||||
* @param data Data to be written
|
||||
*/
|
||||
static void WriteSingleHWReg(u32 base_address, u32 data) {
|
||||
DEBUG_ASSERT_MSG((base_address & 3) == 0 && base_address < 0x420000, "Write address out of range or misaligned");
|
||||
HW::Write<u32>(base_address + REGS_BEGIN, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes sequential GSP GPU hardware registers using an array of source data
|
||||
*
|
||||
* @param base_address The address of the first register in the sequence
|
||||
* @param size_in_bytes The number of registers to update (size of data)
|
||||
* @param data A pointer to the source data
|
||||
* @param data_vaddr A pointer to the source data
|
||||
* @return RESULT_SUCCESS if the parameters are valid, error code otherwise
|
||||
*/
|
||||
static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, const u32* data) {
|
||||
static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, VAddr data_vaddr) {
|
||||
// This magic number is verified to be done by the gsp module
|
||||
const u32 max_size_in_bytes = 0x80;
|
||||
|
||||
@ -87,10 +99,10 @@ static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, const u32* da
|
||||
return ERR_GSP_REGS_MISALIGNED;
|
||||
} else {
|
||||
while (size_in_bytes > 0) {
|
||||
HW::Write<u32>(base_address + REGS_BEGIN, *data);
|
||||
WriteSingleHWReg(base_address, Memory::Read32(data_vaddr));
|
||||
|
||||
size_in_bytes -= 4;
|
||||
++data;
|
||||
data_vaddr += 4;
|
||||
base_address += 4;
|
||||
}
|
||||
return RESULT_SUCCESS;
|
||||
@ -112,7 +124,7 @@ static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, const u32* da
|
||||
* @param masks A pointer to the masks
|
||||
* @return RESULT_SUCCESS if the parameters are valid, error code otherwise
|
||||
*/
|
||||
static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, const u32* data, const u32* masks) {
|
||||
static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, VAddr data_vaddr, VAddr masks_vaddr) {
|
||||
// This magic number is verified to be done by the gsp module
|
||||
const u32 max_size_in_bytes = 0x80;
|
||||
|
||||
@ -131,14 +143,17 @@ static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, const
|
||||
u32 reg_value;
|
||||
HW::Read<u32>(reg_value, reg_address);
|
||||
|
||||
// Update the current value of the register only for set mask bits
|
||||
reg_value = (reg_value & ~*masks) | (*data | *masks);
|
||||
u32 data = Memory::Read32(data_vaddr);
|
||||
u32 mask = Memory::Read32(masks_vaddr);
|
||||
|
||||
HW::Write<u32>(reg_address, reg_value);
|
||||
// Update the current value of the register only for set mask bits
|
||||
reg_value = (reg_value & ~mask) | (data | mask);
|
||||
|
||||
WriteSingleHWReg(base_address, reg_value);
|
||||
|
||||
size_in_bytes -= 4;
|
||||
++data;
|
||||
++masks;
|
||||
data_vaddr += 4;
|
||||
masks_vaddr += 4;
|
||||
base_address += 4;
|
||||
}
|
||||
return RESULT_SUCCESS;
|
||||
@ -164,8 +179,7 @@ static void WriteHWRegs(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
u32 reg_addr = cmd_buff[1];
|
||||
u32 size = cmd_buff[2];
|
||||
|
||||
u32* src = (u32*)Memory::GetPointer(cmd_buff[4]);
|
||||
VAddr src = cmd_buff[4];
|
||||
|
||||
cmd_buff[1] = WriteHWRegs(reg_addr, size, src).raw;
|
||||
}
|
||||
@ -186,8 +200,8 @@ static void WriteHWRegsWithMask(Service::Interface* self) {
|
||||
u32 reg_addr = cmd_buff[1];
|
||||
u32 size = cmd_buff[2];
|
||||
|
||||
u32* src_data = (u32*)Memory::GetPointer(cmd_buff[4]);
|
||||
u32* mask_data = (u32*)Memory::GetPointer(cmd_buff[6]);
|
||||
VAddr src_data = cmd_buff[4];
|
||||
VAddr mask_data = cmd_buff[6];
|
||||
|
||||
cmd_buff[1] = WriteHWRegsWithMask(reg_addr, size, src_data, mask_data).raw;
|
||||
}
|
||||
@ -210,13 +224,16 @@ static void ReadHWRegs(Service::Interface* self) {
|
||||
return;
|
||||
}
|
||||
|
||||
u32* dst = (u32*)Memory::GetPointer(cmd_buff[0x41]);
|
||||
VAddr dst_vaddr = cmd_buff[0x41];
|
||||
|
||||
while (size > 0) {
|
||||
HW::Read<u32>(*dst, reg_addr + REGS_BEGIN);
|
||||
u32 value;
|
||||
HW::Read<u32>(value, reg_addr + REGS_BEGIN);
|
||||
|
||||
Memory::Write32(dst_vaddr, value);
|
||||
|
||||
size -= 4;
|
||||
++dst;
|
||||
dst_vaddr += 4;
|
||||
reg_addr += 4;
|
||||
}
|
||||
}
|
||||
@ -226,22 +243,22 @@ ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) {
|
||||
PAddr phys_address_left = Memory::VirtualToPhysicalAddress(info.address_left);
|
||||
PAddr phys_address_right = Memory::VirtualToPhysicalAddress(info.address_right);
|
||||
if (info.active_fb == 0) {
|
||||
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left1)),
|
||||
4, &phys_address_left);
|
||||
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right1)),
|
||||
4, &phys_address_right);
|
||||
WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left1)),
|
||||
phys_address_left);
|
||||
WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right1)),
|
||||
phys_address_right);
|
||||
} else {
|
||||
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left2)),
|
||||
4, &phys_address_left);
|
||||
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right2)),
|
||||
4, &phys_address_right);
|
||||
WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left2)),
|
||||
phys_address_left);
|
||||
WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right2)),
|
||||
phys_address_right);
|
||||
}
|
||||
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].stride)),
|
||||
4, &info.stride);
|
||||
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].color_format)),
|
||||
4, &info.format);
|
||||
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].active_fb)),
|
||||
4, &info.shown_fb);
|
||||
WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].stride)),
|
||||
info.stride);
|
||||
WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].color_format)),
|
||||
info.format);
|
||||
WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].active_fb)),
|
||||
info.shown_fb);
|
||||
|
||||
if (Pica::g_debug_context)
|
||||
Pica::g_debug_context->OnEvent(Pica::DebugContext::Event::BufferSwapped, nullptr);
|
||||
@ -330,24 +347,25 @@ static void RegisterInterruptRelayQueue(Service::Interface* self) {
|
||||
u32 flags = cmd_buff[1];
|
||||
|
||||
g_interrupt_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[3]);
|
||||
// TODO(mailwl): return right error code instead assert
|
||||
ASSERT_MSG((g_interrupt_event != nullptr), "handle is not valid!");
|
||||
|
||||
g_interrupt_event->name = "GSP_GPU::interrupt_event";
|
||||
|
||||
using Kernel::MemoryPermission;
|
||||
g_shared_memory = Kernel::SharedMemory::Create(nullptr, 0x1000,
|
||||
MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
|
||||
0, Kernel::MemoryRegion::BASE, "GSP:SharedMemory");
|
||||
|
||||
Handle shmem_handle = Kernel::g_handle_table.Create(g_shared_memory).MoveFrom();
|
||||
|
||||
// This specific code is required for a successful initialization, rather than 0
|
||||
cmd_buff[1] = ResultCode((ErrorDescription)519, ErrorModule::GX,
|
||||
ErrorSummary::Success, ErrorLevel::Success).raw;
|
||||
if (first_initialization) {
|
||||
// This specific code is required for a successful initialization, rather than 0
|
||||
first_initialization = false;
|
||||
cmd_buff[1] = ResultCode(ErrorDescription::GPU_FirstInitialization, ErrorModule::GX,
|
||||
ErrorSummary::Success, ErrorLevel::Success).raw;
|
||||
} else {
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
}
|
||||
cmd_buff[2] = g_thread_id++; // Thread ID
|
||||
cmd_buff[4] = shmem_handle; // GSP shared memory
|
||||
cmd_buff[4] = Kernel::g_handle_table.Create(g_shared_memory).MoveFrom(); // GSP shared memory
|
||||
|
||||
g_interrupt_event->Signal(); // TODO(bunnei): Is this correct?
|
||||
|
||||
LOG_WARNING(Service_GSP, "called, flags=0x%08X", flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -358,12 +376,12 @@ static void RegisterInterruptRelayQueue(Service::Interface* self) {
|
||||
static void UnregisterInterruptRelayQueue(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
|
||||
g_shared_memory = nullptr;
|
||||
g_thread_id = 0;
|
||||
g_interrupt_event = nullptr;
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
|
||||
LOG_WARNING(Service_GSP, "called");
|
||||
LOG_WARNING(Service_GSP, "(STUBBED) called");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -432,9 +450,9 @@ static void ExecuteCommand(const Command& command, u32 thread_id) {
|
||||
Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(command.dma_request.dest_address),
|
||||
command.dma_request.size);
|
||||
|
||||
memcpy(Memory::GetPointer(command.dma_request.dest_address),
|
||||
Memory::GetPointer(command.dma_request.source_address),
|
||||
command.dma_request.size);
|
||||
// TODO(Subv): These memory accesses should not go through the application's memory mapping.
|
||||
// They should go through the GSP module's memory mapping.
|
||||
Memory::CopyBlock(command.dma_request.dest_address, command.dma_request.source_address, command.dma_request.size);
|
||||
SignalInterrupt(InterruptId::DMA);
|
||||
break;
|
||||
}
|
||||
@ -701,10 +719,15 @@ Interface::Interface() {
|
||||
Register(FunctionTable);
|
||||
|
||||
g_interrupt_event = nullptr;
|
||||
g_shared_memory = nullptr;
|
||||
|
||||
using Kernel::MemoryPermission;
|
||||
g_shared_memory = Kernel::SharedMemory::Create(nullptr, 0x1000,
|
||||
MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
|
||||
0, Kernel::MemoryRegion::BASE, "GSP:SharedMemory");
|
||||
|
||||
g_thread_id = 0;
|
||||
gpu_right_acquired = false;
|
||||
first_initialization = true;
|
||||
}
|
||||
|
||||
Interface::~Interface() {
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/emu_window.h"
|
||||
|
||||
@ -19,8 +21,6 @@
|
||||
namespace Service {
|
||||
namespace HID {
|
||||
|
||||
static const int MAX_CIRCLEPAD_POS = 0x9C; ///< Max value for a circle pad position
|
||||
|
||||
// Handle to shared memory region designated to HID_User service
|
||||
static Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
|
||||
|
||||
@ -39,38 +39,48 @@ static u32 next_gyroscope_index;
|
||||
static int enable_accelerometer_count = 0; // positive means enabled
|
||||
static int enable_gyroscope_count = 0; // positive means enabled
|
||||
|
||||
const std::array<Service::HID::PadState, Settings::NativeInput::NUM_INPUTS> pad_mapping = {{
|
||||
Service::HID::PAD_A, Service::HID::PAD_B, Service::HID::PAD_X, Service::HID::PAD_Y,
|
||||
Service::HID::PAD_L, Service::HID::PAD_R, Service::HID::PAD_ZL, Service::HID::PAD_ZR,
|
||||
Service::HID::PAD_START, Service::HID::PAD_SELECT, Service::HID::PAD_NONE,
|
||||
Service::HID::PAD_UP, Service::HID::PAD_DOWN, Service::HID::PAD_LEFT, Service::HID::PAD_RIGHT,
|
||||
Service::HID::PAD_CIRCLE_UP, Service::HID::PAD_CIRCLE_DOWN, Service::HID::PAD_CIRCLE_LEFT, Service::HID::PAD_CIRCLE_RIGHT,
|
||||
Service::HID::PAD_C_UP, Service::HID::PAD_C_DOWN, Service::HID::PAD_C_LEFT, Service::HID::PAD_C_RIGHT
|
||||
}};
|
||||
static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
|
||||
constexpr float TAN30 = 0.577350269, TAN60 = 1 / TAN30; // 30 degree and 60 degree are angular thresholds for directions
|
||||
constexpr int CIRCLE_PAD_THRESHOLD_SQUARE = 40 * 40; // a circle pad radius greater than 40 will trigger circle pad direction
|
||||
PadState state;
|
||||
state.hex = 0;
|
||||
|
||||
if (circle_pad_x * circle_pad_x + circle_pad_y * circle_pad_y > CIRCLE_PAD_THRESHOLD_SQUARE) {
|
||||
float t = std::abs(static_cast<float>(circle_pad_y) / circle_pad_x);
|
||||
|
||||
// TODO(peachum):
|
||||
// Add a method for setting analog input from joystick device for the circle Pad.
|
||||
//
|
||||
// This method should:
|
||||
// * Be called after both PadButton<Press, Release>().
|
||||
// * Be called before PadUpdateComplete()
|
||||
// * Set current PadEntry.circle_pad_<axis> using analog data
|
||||
// * Set PadData.raw_circle_pad_data
|
||||
// * Set PadData.current_state.circle_right = 1 if current PadEntry.circle_pad_x >= 41
|
||||
// * Set PadData.current_state.circle_up = 1 if current PadEntry.circle_pad_y >= 41
|
||||
// * Set PadData.current_state.circle_left = 1 if current PadEntry.circle_pad_x <= -41
|
||||
// * Set PadData.current_state.circle_right = 1 if current PadEntry.circle_pad_y <= -41
|
||||
if (circle_pad_x != 0 && t < TAN60) {
|
||||
if (circle_pad_x > 0)
|
||||
state.circle_right.Assign(1);
|
||||
else
|
||||
state.circle_left.Assign(1);
|
||||
}
|
||||
|
||||
if (circle_pad_x == 0 || t > TAN30) {
|
||||
if (circle_pad_y > 0)
|
||||
state.circle_up.Assign(1);
|
||||
else
|
||||
state.circle_down.Assign(1);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void Update() {
|
||||
SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
|
||||
const PadState state = VideoCore::g_emu_window->GetPadState();
|
||||
|
||||
if (mem == nullptr) {
|
||||
LOG_DEBUG(Service_HID, "Cannot update HID prior to mapping shared memory!");
|
||||
return;
|
||||
}
|
||||
|
||||
PadState state = VideoCore::g_emu_window->GetPadState();
|
||||
|
||||
// Get current circle pad position and update circle pad direction
|
||||
s16 circle_pad_x, circle_pad_y;
|
||||
std::tie(circle_pad_x, circle_pad_y) = VideoCore::g_emu_window->GetCirclePadState();
|
||||
state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex;
|
||||
|
||||
mem->pad.current_state.hex = state.hex;
|
||||
mem->pad.index = next_pad_index;
|
||||
next_pad_index = (next_pad_index + 1) % mem->pad.entries.size();
|
||||
@ -88,13 +98,9 @@ void Update() {
|
||||
// Update entry properties
|
||||
pad_entry.current_state.hex = state.hex;
|
||||
pad_entry.delta_additions.hex = changed.hex & state.hex;
|
||||
pad_entry.delta_removals.hex = changed.hex & old_state.hex;;
|
||||
|
||||
// Set circle Pad
|
||||
pad_entry.circle_pad_x = state.circle_left ? -MAX_CIRCLEPAD_POS :
|
||||
state.circle_right ? MAX_CIRCLEPAD_POS : 0x0;
|
||||
pad_entry.circle_pad_y = state.circle_down ? -MAX_CIRCLEPAD_POS :
|
||||
state.circle_up ? MAX_CIRCLEPAD_POS : 0x0;
|
||||
pad_entry.delta_removals.hex = changed.hex & old_state.hex;
|
||||
pad_entry.circle_pad_x = circle_pad_x;
|
||||
pad_entry.circle_pad_y = circle_pad_y;
|
||||
|
||||
// If we just updated index 0, provide a new timestamp
|
||||
if (mem->pad.index == 0) {
|
||||
|
@ -215,9 +215,6 @@ const PadState PAD_CIRCLE_LEFT = {{1u << 29}};
|
||||
const PadState PAD_CIRCLE_UP = {{1u << 30}};
|
||||
const PadState PAD_CIRCLE_DOWN = {{1u << 31}};
|
||||
|
||||
|
||||
extern const std::array<Service::HID::PadState, Settings::NativeInput::NUM_INPUTS> pad_mapping;
|
||||
|
||||
/**
|
||||
* HID::GetIPCHandles service function
|
||||
* Inputs:
|
||||
|
@ -3,7 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include "core/settings.h"
|
||||
#include "core/file_sys/file_backend.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/hle/service/ptm/ptm.h"
|
||||
@ -89,6 +89,20 @@ void IsLegacyPowerOff(Service::Interface* self) {
|
||||
LOG_WARNING(Service_PTM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void CheckNew3DS(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
const bool is_new_3ds = Settings::values.is_new_3ds;
|
||||
|
||||
if (is_new_3ds) {
|
||||
LOG_CRITICAL(Service_PTM, "The option 'is_new_3ds' is enabled as part of the 'System' settings. Citra does not fully support New 3DS emulation yet!");
|
||||
}
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = is_new_3ds ? 1 : 0;
|
||||
|
||||
LOG_WARNING(Service_PTM, "(STUBBED) called isNew3DS = 0x%08x", static_cast<u32>(is_new_3ds));
|
||||
}
|
||||
|
||||
void Init() {
|
||||
AddService(new PTM_Play_Interface);
|
||||
AddService(new PTM_Sysm_Interface);
|
||||
|
@ -88,6 +88,14 @@ void GetTotalStepCount(Interface* self);
|
||||
*/
|
||||
void IsLegacyPowerOff(Interface* self);
|
||||
|
||||
/**
|
||||
* PTM::CheckNew3DS service function
|
||||
* Outputs:
|
||||
* 1: Result code, 0 on success, otherwise error code
|
||||
* 2: u8 output: 0 = Old3DS, 1 = New3DS.
|
||||
*/
|
||||
void CheckNew3DS(Interface* self);
|
||||
|
||||
/// Initialize the PTM service
|
||||
void Init();
|
||||
|
||||
|
@ -18,7 +18,7 @@ const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x040700C0, nullptr, "ShutdownAsync"},
|
||||
{0x04080000, nullptr, "Awake"},
|
||||
{0x04090080, nullptr, "RebootAsync"},
|
||||
{0x040A0000, nullptr, "CheckNew3DS"},
|
||||
{0x040A0000, CheckNew3DS, "CheckNew3DS"},
|
||||
{0x08010640, nullptr, "SetInfoLEDPattern"},
|
||||
{0x08020040, nullptr, "SetInfoLEDPatternHeader"},
|
||||
{0x08030000, nullptr, "GetInfoLEDStatus"},
|
||||
@ -35,7 +35,7 @@ const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x080E0140, nullptr, "NotifyPlayEvent"},
|
||||
{0x080F0000, IsLegacyPowerOff, "IsLegacyPowerOff"},
|
||||
{0x08100000, nullptr, "ClearLegacyPowerOff"},
|
||||
{0x08110000, nullptr, "GetShellStatus"},
|
||||
{0x08110000, GetShellState, "GetShellState"},
|
||||
{0x08120000, nullptr, "IsShutdownByBatteryEmpty"},
|
||||
{0x08130000, nullptr, "FormatSavedata"},
|
||||
{0x08140000, nullptr, "GetLegacyJumpProhibitedFlag"},
|
||||
|
@ -10,7 +10,6 @@
|
||||
#include "core/hle/service/act_a.h"
|
||||
#include "core/hle/service/act_u.h"
|
||||
#include "core/hle/service/csnd_snd.h"
|
||||
#include "core/hle/service/dlp_srvr.h"
|
||||
#include "core/hle/service/dsp_dsp.h"
|
||||
#include "core/hle/service/err_f.h"
|
||||
#include "core/hle/service/gsp_gpu.h"
|
||||
@ -31,6 +30,7 @@
|
||||
#include "core/hle/service/boss/boss.h"
|
||||
#include "core/hle/service/cam/cam.h"
|
||||
#include "core/hle/service/cecd/cecd.h"
|
||||
#include "core/hle/service/dlp/dlp.h"
|
||||
#include "core/hle/service/frd/frd.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/hle/service/cfg/cfg.h"
|
||||
@ -111,6 +111,7 @@ void Init() {
|
||||
Service::CAM::Init();
|
||||
Service::CECD::Init();
|
||||
Service::CFG::Init();
|
||||
Service::DLP::Init();
|
||||
Service::FRD::Init();
|
||||
Service::HID::Init();
|
||||
Service::IR::Init();
|
||||
@ -123,7 +124,6 @@ void Init() {
|
||||
AddService(new ACT_A::Interface);
|
||||
AddService(new ACT_U::Interface);
|
||||
AddService(new CSND_SND::Interface);
|
||||
AddService(new DLP_SRVR::Interface);
|
||||
AddService(new DSP_DSP::Interface);
|
||||
AddService(new GSP_GPU::Interface);
|
||||
AddService(new GSP_LCD::Interface);
|
||||
@ -150,6 +150,7 @@ void Shutdown() {
|
||||
Service::IR::Shutdown();
|
||||
Service::HID::Shutdown();
|
||||
Service::FRD::Shutdown();
|
||||
Service::DLP::Shutdown();
|
||||
Service::CFG::Shutdown();
|
||||
Service::CECD::Shutdown();
|
||||
Service::CAM::Shutdown();
|
||||
|
@ -373,14 +373,18 @@ static void Bind(Service::Interface* self) {
|
||||
u32* cmd_buffer = Kernel::GetCommandBuffer();
|
||||
u32 socket_handle = cmd_buffer[1];
|
||||
u32 len = cmd_buffer[2];
|
||||
CTRSockAddr* ctr_sock_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[6]));
|
||||
|
||||
if (ctr_sock_addr == nullptr) {
|
||||
// Virtual address of the sock_addr structure
|
||||
VAddr sock_addr_addr = cmd_buffer[6];
|
||||
if (!Memory::IsValidVirtualAddress(sock_addr_addr)) {
|
||||
cmd_buffer[1] = -1; // TODO(Subv): Correct code
|
||||
return;
|
||||
}
|
||||
|
||||
sockaddr sock_addr = CTRSockAddr::ToPlatform(*ctr_sock_addr);
|
||||
CTRSockAddr ctr_sock_addr;
|
||||
Memory::ReadBlock(sock_addr_addr, reinterpret_cast<u8*>(&ctr_sock_addr), sizeof(CTRSockAddr));
|
||||
|
||||
sockaddr sock_addr = CTRSockAddr::ToPlatform(ctr_sock_addr);
|
||||
|
||||
int res = ::bind(socket_handle, &sock_addr, std::max<u32>(sizeof(sock_addr), len));
|
||||
|
||||
@ -496,7 +500,7 @@ static void Accept(Service::Interface* self) {
|
||||
result = TranslateError(GET_ERRNO);
|
||||
} else {
|
||||
CTRSockAddr ctr_addr = CTRSockAddr::FromPlatform(addr);
|
||||
Memory::WriteBlock(cmd_buffer[0x104 >> 2], (const u8*)&ctr_addr, max_addr_len);
|
||||
Memory::WriteBlock(cmd_buffer[0x104 >> 2], &ctr_addr, sizeof(ctr_addr));
|
||||
}
|
||||
|
||||
cmd_buffer[0] = IPC::MakeHeader(4, 2, 2);
|
||||
@ -547,20 +551,31 @@ static void SendTo(Service::Interface* self) {
|
||||
u32 flags = cmd_buffer[3];
|
||||
u32 addr_len = cmd_buffer[4];
|
||||
|
||||
u8* input_buff = Memory::GetPointer(cmd_buffer[8]);
|
||||
CTRSockAddr* ctr_dest_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[10]));
|
||||
|
||||
if (ctr_dest_addr == nullptr) {
|
||||
VAddr input_buff_address = cmd_buffer[8];
|
||||
if (!Memory::IsValidVirtualAddress(input_buff_address)) {
|
||||
cmd_buffer[1] = -1; // TODO(Subv): Find the right error code
|
||||
return;
|
||||
}
|
||||
|
||||
// Memory address of the dest_addr structure
|
||||
VAddr dest_addr_addr = cmd_buffer[10];
|
||||
if (!Memory::IsValidVirtualAddress(dest_addr_addr)) {
|
||||
cmd_buffer[1] = -1; // TODO(Subv): Find the right error code
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<u8> input_buff(len);
|
||||
Memory::ReadBlock(input_buff_address, input_buff.data(), input_buff.size());
|
||||
|
||||
CTRSockAddr ctr_dest_addr;
|
||||
Memory::ReadBlock(dest_addr_addr, &ctr_dest_addr, sizeof(ctr_dest_addr));
|
||||
|
||||
int ret = -1;
|
||||
if (addr_len > 0) {
|
||||
sockaddr dest_addr = CTRSockAddr::ToPlatform(*ctr_dest_addr);
|
||||
ret = ::sendto(socket_handle, (const char*)input_buff, len, flags, &dest_addr, sizeof(dest_addr));
|
||||
sockaddr dest_addr = CTRSockAddr::ToPlatform(ctr_dest_addr);
|
||||
ret = ::sendto(socket_handle, reinterpret_cast<const char*>(input_buff.data()), len, flags, &dest_addr, sizeof(dest_addr));
|
||||
} else {
|
||||
ret = ::sendto(socket_handle, (const char*)input_buff, len, flags, nullptr, 0);
|
||||
ret = ::sendto(socket_handle, reinterpret_cast<const char*>(input_buff.data()), len, flags, nullptr, 0);
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
@ -591,14 +606,24 @@ static void RecvFrom(Service::Interface* self) {
|
||||
|
||||
std::memcpy(&buffer_parameters, &cmd_buffer[64], sizeof(buffer_parameters));
|
||||
|
||||
u8* output_buff = Memory::GetPointer(buffer_parameters.output_buffer_addr);
|
||||
if (!Memory::IsValidVirtualAddress(buffer_parameters.output_buffer_addr)) {
|
||||
cmd_buffer[1] = -1; // TODO(Subv): Find the right error code
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Memory::IsValidVirtualAddress(buffer_parameters.output_src_address_buffer)) {
|
||||
cmd_buffer[1] = -1; // TODO(Subv): Find the right error code
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<u8> output_buff(len);
|
||||
sockaddr src_addr;
|
||||
socklen_t src_addr_len = sizeof(src_addr);
|
||||
int ret = ::recvfrom(socket_handle, (char*)output_buff, len, flags, &src_addr, &src_addr_len);
|
||||
int ret = ::recvfrom(socket_handle, reinterpret_cast<char*>(output_buff.data()), len, flags, &src_addr, &src_addr_len);
|
||||
|
||||
if (ret >= 0 && buffer_parameters.output_src_address_buffer != 0 && src_addr_len > 0) {
|
||||
CTRSockAddr* ctr_src_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(buffer_parameters.output_src_address_buffer));
|
||||
*ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
|
||||
CTRSockAddr ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
|
||||
Memory::WriteBlock(buffer_parameters.output_src_address_buffer, &ctr_src_addr, sizeof(ctr_src_addr));
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
@ -606,6 +631,9 @@ static void RecvFrom(Service::Interface* self) {
|
||||
if (ret == SOCKET_ERROR_VALUE) {
|
||||
result = TranslateError(GET_ERRNO);
|
||||
total_received = 0;
|
||||
} else {
|
||||
// Write only the data we received to avoid overwriting parts of the buffer with zeros
|
||||
Memory::WriteBlock(buffer_parameters.output_buffer_addr, output_buff.data(), total_received);
|
||||
}
|
||||
|
||||
cmd_buffer[1] = result;
|
||||
@ -617,18 +645,28 @@ static void Poll(Service::Interface* self) {
|
||||
u32* cmd_buffer = Kernel::GetCommandBuffer();
|
||||
u32 nfds = cmd_buffer[1];
|
||||
int timeout = cmd_buffer[2];
|
||||
CTRPollFD* input_fds = reinterpret_cast<CTRPollFD*>(Memory::GetPointer(cmd_buffer[6]));
|
||||
CTRPollFD* output_fds = reinterpret_cast<CTRPollFD*>(Memory::GetPointer(cmd_buffer[0x104 >> 2]));
|
||||
|
||||
VAddr input_fds_addr = cmd_buffer[6];
|
||||
VAddr output_fds_addr = cmd_buffer[0x104 >> 2];
|
||||
if (!Memory::IsValidVirtualAddress(input_fds_addr) || !Memory::IsValidVirtualAddress(output_fds_addr)) {
|
||||
cmd_buffer[1] = -1; // TODO(Subv): Find correct error code.
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<CTRPollFD> ctr_fds(nfds);
|
||||
Memory::ReadBlock(input_fds_addr, ctr_fds.data(), nfds * sizeof(CTRPollFD));
|
||||
|
||||
// The 3ds_pollfd and the pollfd structures may be different (Windows/Linux have different sizes)
|
||||
// so we have to copy the data
|
||||
std::vector<pollfd> platform_pollfd(nfds);
|
||||
std::transform(input_fds, input_fds + nfds, platform_pollfd.begin(), CTRPollFD::ToPlatform);
|
||||
std::transform(ctr_fds.begin(), ctr_fds.end(), platform_pollfd.begin(), CTRPollFD::ToPlatform);
|
||||
|
||||
const int ret = ::poll(platform_pollfd.data(), nfds, timeout);
|
||||
|
||||
// Now update the output pollfd structure
|
||||
std::transform(platform_pollfd.begin(), platform_pollfd.end(), output_fds, CTRPollFD::FromPlatform);
|
||||
std::transform(platform_pollfd.begin(), platform_pollfd.end(), ctr_fds.begin(), CTRPollFD::FromPlatform);
|
||||
|
||||
Memory::WriteBlock(output_fds_addr, ctr_fds.data(), nfds * sizeof(CTRPollFD));
|
||||
|
||||
int result = 0;
|
||||
if (ret == SOCKET_ERROR_VALUE)
|
||||
@ -643,14 +681,16 @@ static void GetSockName(Service::Interface* self) {
|
||||
u32 socket_handle = cmd_buffer[1];
|
||||
socklen_t ctr_len = cmd_buffer[2];
|
||||
|
||||
CTRSockAddr* ctr_dest_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[0x104 >> 2]));
|
||||
// Memory address of the ctr_dest_addr structure
|
||||
VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2];
|
||||
|
||||
sockaddr dest_addr;
|
||||
socklen_t dest_addr_len = sizeof(dest_addr);
|
||||
int ret = ::getsockname(socket_handle, &dest_addr, &dest_addr_len);
|
||||
|
||||
if (ctr_dest_addr != nullptr) {
|
||||
*ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
|
||||
if (ctr_dest_addr_addr != 0 && Memory::IsValidVirtualAddress(ctr_dest_addr_addr)) {
|
||||
CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
|
||||
Memory::WriteBlock(ctr_dest_addr_addr, &ctr_dest_addr, sizeof(ctr_dest_addr));
|
||||
} else {
|
||||
cmd_buffer[1] = -1; // TODO(Subv): Verify error
|
||||
return;
|
||||
@ -682,14 +722,16 @@ static void GetPeerName(Service::Interface* self) {
|
||||
u32 socket_handle = cmd_buffer[1];
|
||||
socklen_t len = cmd_buffer[2];
|
||||
|
||||
CTRSockAddr* ctr_dest_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[0x104 >> 2]));
|
||||
// Memory address of the ctr_dest_addr structure
|
||||
VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2];
|
||||
|
||||
sockaddr dest_addr;
|
||||
socklen_t dest_addr_len = sizeof(dest_addr);
|
||||
int ret = ::getpeername(socket_handle, &dest_addr, &dest_addr_len);
|
||||
|
||||
if (ctr_dest_addr != nullptr) {
|
||||
*ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
|
||||
if (ctr_dest_addr_addr != 0 && Memory::IsValidVirtualAddress(ctr_dest_addr_addr)) {
|
||||
CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
|
||||
Memory::WriteBlock(ctr_dest_addr_addr, &ctr_dest_addr, sizeof(ctr_dest_addr));
|
||||
} else {
|
||||
cmd_buffer[1] = -1;
|
||||
return;
|
||||
@ -711,13 +753,17 @@ static void Connect(Service::Interface* self) {
|
||||
u32 socket_handle = cmd_buffer[1];
|
||||
socklen_t len = cmd_buffer[2];
|
||||
|
||||
CTRSockAddr* ctr_input_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[6]));
|
||||
if (ctr_input_addr == nullptr) {
|
||||
// Memory address of the ctr_input_addr structure
|
||||
VAddr ctr_input_addr_addr = cmd_buffer[6];
|
||||
if (!Memory::IsValidVirtualAddress(ctr_input_addr_addr)) {
|
||||
cmd_buffer[1] = -1; // TODO(Subv): Verify error
|
||||
return;
|
||||
}
|
||||
|
||||
sockaddr input_addr = CTRSockAddr::ToPlatform(*ctr_input_addr);
|
||||
CTRSockAddr ctr_input_addr;
|
||||
Memory::ReadBlock(ctr_input_addr_addr, &ctr_input_addr, sizeof(ctr_input_addr));
|
||||
|
||||
sockaddr input_addr = CTRSockAddr::ToPlatform(ctr_input_addr);
|
||||
int ret = ::connect(socket_handle, &input_addr, sizeof(input_addr));
|
||||
int result = 0;
|
||||
if (ret != 0)
|
||||
|
@ -31,7 +31,6 @@ static void GenerateRandomData(Service::Interface* self) {
|
||||
|
||||
u32 size = cmd_buff[1];
|
||||
VAddr address = cmd_buff[3];
|
||||
u8* output_buff = Memory::GetPointer(address);
|
||||
|
||||
// Fill the output buffer with random data.
|
||||
u32 data = 0;
|
||||
@ -44,13 +43,13 @@ static void GenerateRandomData(Service::Interface* self) {
|
||||
|
||||
if (size > 4) {
|
||||
// Use up the entire 4 bytes of the random data for as long as possible
|
||||
*(u32*)(output_buff + i) = data;
|
||||
Memory::Write32(address + i, data);
|
||||
i += 4;
|
||||
} else if (size == 2) {
|
||||
*(u16*)(output_buff + i) = (u16)(data & 0xffff);
|
||||
Memory::Write16(address + i, static_cast<u16>(data & 0xffff));
|
||||
i += 2;
|
||||
} else {
|
||||
*(u8*)(output_buff + i) = (u8)(data & 0xff);
|
||||
Memory::Write8(address + i, static_cast<u8>(data & 0xff));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/symbols.h"
|
||||
|
||||
@ -326,9 +327,9 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou
|
||||
}
|
||||
}
|
||||
|
||||
HLE::Reschedule(__func__);
|
||||
SCOPE_EXIT({HLE::Reschedule("WaitSynchronizationN");}); // Reschedule after putting the threads to sleep.
|
||||
|
||||
// If thread should wait, then set its state to waiting and then reschedule...
|
||||
// If thread should wait, then set its state to waiting
|
||||
if (wait_thread) {
|
||||
|
||||
// Actually wait the current thread on each object if we decided to wait...
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "core/file_sys/archive_romfs.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/loader/3dsx.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
@ -263,6 +264,8 @@ ResultStatus AppLoader_THREEDSX::Load() {
|
||||
|
||||
Kernel::g_current_process->Run(48, Kernel::DEFAULT_STACK_SIZE);
|
||||
|
||||
Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this), Service::FS::ArchiveIdCode::RomFS);
|
||||
|
||||
is_loaded = true;
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
@ -27,6 +27,14 @@ public:
|
||||
*/
|
||||
static FileType IdentifyType(FileUtil::IOFile& file);
|
||||
|
||||
/**
|
||||
* Returns the type of this file
|
||||
* @return FileType corresponding to the loaded file
|
||||
*/
|
||||
FileType GetFileType() override {
|
||||
return IdentifyType(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the bootable file
|
||||
* @return ResultStatus result of function
|
||||
|
@ -27,6 +27,14 @@ public:
|
||||
*/
|
||||
static FileType IdentifyType(FileUtil::IOFile& file);
|
||||
|
||||
/**
|
||||
* Returns the type of this file
|
||||
* @return FileType corresponding to the loaded file
|
||||
*/
|
||||
FileType GetFileType() override {
|
||||
return IdentifyType(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the bootable file
|
||||
* @return ResultStatus result of function
|
||||
|
@ -8,9 +8,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
#include "core/file_sys/archive_romfs.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/loader/3dsx.h"
|
||||
#include "core/loader/elf.h"
|
||||
#include "core/loader/ncch.h"
|
||||
@ -67,6 +65,9 @@ FileType GuessFromExtension(const std::string& extension_) {
|
||||
if (extension == ".3dsx")
|
||||
return FileType::THREEDSX;
|
||||
|
||||
if (extension == ".cia")
|
||||
return FileType::CIA;
|
||||
|
||||
return FileType::Unknown;
|
||||
}
|
||||
|
||||
@ -90,7 +91,15 @@ const char* GetFileTypeString(FileType type) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type,
|
||||
/**
|
||||
* Get a loader for a file with a specific type
|
||||
* @param file The file to load
|
||||
* @param type The type of the file
|
||||
* @param filename the file name (without path)
|
||||
* @param filepath the file full path (with name)
|
||||
* @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type
|
||||
*/
|
||||
static std::unique_ptr<AppLoader> GetFileLoader(FileUtil::IOFile&& file, FileType type,
|
||||
const std::string& filename, const std::string& filepath) {
|
||||
switch (type) {
|
||||
|
||||
@ -108,15 +117,15 @@ std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type,
|
||||
return std::make_unique<AppLoader_NCCH>(std::move(file), filepath);
|
||||
|
||||
default:
|
||||
return std::unique_ptr<AppLoader>();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ResultStatus LoadFile(const std::string& filename) {
|
||||
std::unique_ptr<AppLoader> GetLoader(const std::string& filename) {
|
||||
FileUtil::IOFile file(filename, "rb");
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(Loader, "Failed to load file %s", filename.c_str());
|
||||
return ResultStatus::Error;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string filename_filename, filename_extension;
|
||||
@ -133,44 +142,7 @@ ResultStatus LoadFile(const std::string& filename) {
|
||||
|
||||
LOG_INFO(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type));
|
||||
|
||||
std::unique_ptr<AppLoader> app_loader = GetLoader(std::move(file), type, filename_filename, filename);
|
||||
|
||||
switch (type) {
|
||||
|
||||
// 3DSX file format...
|
||||
// or NCCH/NCSD container formats...
|
||||
case FileType::THREEDSX:
|
||||
case FileType::CXI:
|
||||
case FileType::CCI:
|
||||
{
|
||||
// Load application and RomFS
|
||||
ResultStatus result = app_loader->Load();
|
||||
if (ResultStatus::Success == result) {
|
||||
Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*app_loader), Service::FS::ArchiveIdCode::RomFS);
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Standard ELF file format...
|
||||
case FileType::ELF:
|
||||
return app_loader->Load();
|
||||
|
||||
// CIA file format...
|
||||
case FileType::CIA:
|
||||
return ResultStatus::ErrorNotImplemented;
|
||||
|
||||
// Error occurred durring IdentifyFile...
|
||||
case FileType::Error:
|
||||
|
||||
// IdentifyFile could know identify file type...
|
||||
case FileType::Unknown:
|
||||
{
|
||||
LOG_CRITICAL(Loader, "File %s is of unknown type.", filename.c_str());
|
||||
return ResultStatus::ErrorInvalidFormat;
|
||||
}
|
||||
}
|
||||
return ResultStatus::Error;
|
||||
return GetFileLoader(std::move(file), type, filename_filename, filename);
|
||||
}
|
||||
|
||||
} // namespace Loader
|
||||
|
@ -10,10 +10,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
namespace Kernel {
|
||||
struct AddressMapping;
|
||||
@ -80,57 +78,18 @@ constexpr u32 MakeMagic(char a, char b, char c, char d) {
|
||||
return a | b << 8 | c << 16 | d << 24;
|
||||
}
|
||||
|
||||
/// SMDH data structure that contains titles, icons etc. See https://www.3dbrew.org/wiki/SMDH
|
||||
struct SMDH {
|
||||
u32_le magic;
|
||||
u16_le version;
|
||||
INSERT_PADDING_BYTES(2);
|
||||
|
||||
struct Title {
|
||||
std::array<u16, 0x40> short_title;
|
||||
std::array<u16, 0x80> long_title;
|
||||
std::array<u16, 0x40> publisher;
|
||||
};
|
||||
std::array<Title, 16> titles;
|
||||
|
||||
std::array<u8, 16> ratings;
|
||||
u32_le region_lockout;
|
||||
u32_le match_maker_id;
|
||||
u64_le match_maker_bit_id;
|
||||
u32_le flags;
|
||||
u16_le eula_version;
|
||||
INSERT_PADDING_BYTES(2);
|
||||
float_le banner_animation_frame;
|
||||
u32_le cec_id;
|
||||
INSERT_PADDING_BYTES(8);
|
||||
|
||||
std::array<u8, 0x480> small_icon;
|
||||
std::array<u8, 0x1200> large_icon;
|
||||
|
||||
/// indicates the language used for each title entry
|
||||
enum class TitleLanguage {
|
||||
Japanese = 0,
|
||||
English = 1,
|
||||
French = 2,
|
||||
German = 3,
|
||||
Italian = 4,
|
||||
Spanish = 5,
|
||||
SimplifiedChinese = 6,
|
||||
Korean= 7,
|
||||
Dutch = 8,
|
||||
Portuguese = 9,
|
||||
Russian = 10,
|
||||
TraditionalChinese = 11
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(SMDH) == 0x36C0, "SMDH structure size is wrong");
|
||||
|
||||
/// Interface for loading an application
|
||||
class AppLoader : NonCopyable {
|
||||
public:
|
||||
AppLoader(FileUtil::IOFile&& file) : file(std::move(file)) { }
|
||||
virtual ~AppLoader() { }
|
||||
|
||||
/**
|
||||
* Returns the type of this file
|
||||
* @return FileType corresponding to the loaded file
|
||||
*/
|
||||
virtual FileType GetFileType() = 0;
|
||||
|
||||
/**
|
||||
* Load the application
|
||||
* @return ResultStatus result of function
|
||||
@ -197,20 +156,10 @@ protected:
|
||||
extern const std::initializer_list<Kernel::AddressMapping> default_address_mappings;
|
||||
|
||||
/**
|
||||
* Get a loader for a file with a specific type
|
||||
* @param file The file to load
|
||||
* @param type The type of the file
|
||||
* @param filename the file name (without path)
|
||||
* @param filepath the file full path (with name)
|
||||
* @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type
|
||||
*/
|
||||
std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type, const std::string& filename, const std::string& filepath);
|
||||
|
||||
/**
|
||||
* Identifies and loads a bootable file
|
||||
* Identifies a bootable file and return a suitable loader
|
||||
* @param filename String filename of bootable file
|
||||
* @return ResultStatus result of function
|
||||
* @return best loader for this file
|
||||
*/
|
||||
ResultStatus LoadFile(const std::string& filename);
|
||||
std::unique_ptr<AppLoader> GetLoader(const std::string& filename);
|
||||
|
||||
} // namespace
|
||||
|
@ -10,8 +10,10 @@
|
||||
#include "common/string_util.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
#include "core/file_sys/archive_romfs.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/loader/ncch.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
@ -303,7 +305,12 @@ ResultStatus AppLoader_NCCH::Load() {
|
||||
|
||||
is_loaded = true; // Set state to loaded
|
||||
|
||||
return LoadExec(); // Load the executable into memory for booting
|
||||
result = LoadExec(); // Load the executable into memory for booting
|
||||
if (ResultStatus::Success != result)
|
||||
return result;
|
||||
|
||||
Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this), Service::FS::ArchiveIdCode::RomFS);
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NCCH::ReadCode(std::vector<u8>& buffer) {
|
||||
|
@ -173,6 +173,14 @@ public:
|
||||
*/
|
||||
static FileType IdentifyType(FileUtil::IOFile& file);
|
||||
|
||||
/**
|
||||
* Returns the type of this file
|
||||
* @return FileType corresponding to the loaded file
|
||||
*/
|
||||
FileType GetFileType() override {
|
||||
return IdentifyType(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the application
|
||||
* @return ResultStatus result of function
|
||||
|
54
src/core/loader/smdh.cpp
Normal file
54
src/core/loader/smdh.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/loader/smdh.h"
|
||||
|
||||
#include "video_core/utils.h"
|
||||
|
||||
namespace Loader {
|
||||
|
||||
bool IsValidSMDH(const std::vector<u8>& smdh_data) {
|
||||
if (smdh_data.size() < sizeof(Loader::SMDH))
|
||||
return false;
|
||||
|
||||
u32 magic;
|
||||
memcpy(&magic, smdh_data.data(), sizeof(u32));
|
||||
|
||||
return Loader::MakeMagic('S', 'M', 'D', 'H') == magic;
|
||||
}
|
||||
|
||||
std::vector<u16> SMDH::GetIcon(bool large) const {
|
||||
u32 size;
|
||||
const u8* icon_data;
|
||||
|
||||
if (large) {
|
||||
size = 48;
|
||||
icon_data = large_icon.data();
|
||||
} else {
|
||||
size = 24;
|
||||
icon_data = small_icon.data();
|
||||
}
|
||||
|
||||
std::vector<u16> icon(size * size);
|
||||
for (u32 x = 0; x < size; ++x) {
|
||||
for (u32 y = 0; y < size; ++y) {
|
||||
u32 coarse_y = y & ~7;
|
||||
const u8* pixel = icon_data + VideoCore::GetMortonOffset(x, y, 2) + coarse_y * size * 2;
|
||||
icon[x + size * y] = (pixel[1] << 8) + pixel[0];
|
||||
}
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
std::array<u16, 0x40> SMDH::GetShortTitle(Loader::SMDH::TitleLanguage language) const {
|
||||
return titles[static_cast<int>(language)].short_title;
|
||||
}
|
||||
|
||||
} // namespace
|
82
src/core/loader/smdh.h
Normal file
82
src/core/loader/smdh.h
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
namespace Loader {
|
||||
|
||||
/**
|
||||
* Tests if data is a valid SMDH by its length and magic number.
|
||||
* @param smdh_data data buffer to test
|
||||
* @return bool test result
|
||||
*/
|
||||
bool IsValidSMDH(const std::vector<u8>& smdh_data);
|
||||
|
||||
/// SMDH data structure that contains titles, icons etc. See https://www.3dbrew.org/wiki/SMDH
|
||||
struct SMDH {
|
||||
u32_le magic;
|
||||
u16_le version;
|
||||
INSERT_PADDING_BYTES(2);
|
||||
|
||||
struct Title {
|
||||
std::array<u16, 0x40> short_title;
|
||||
std::array<u16, 0x80> long_title;
|
||||
std::array<u16, 0x40> publisher;
|
||||
};
|
||||
std::array<Title, 16> titles;
|
||||
|
||||
std::array<u8, 16> ratings;
|
||||
u32_le region_lockout;
|
||||
u32_le match_maker_id;
|
||||
u64_le match_maker_bit_id;
|
||||
u32_le flags;
|
||||
u16_le eula_version;
|
||||
INSERT_PADDING_BYTES(2);
|
||||
float_le banner_animation_frame;
|
||||
u32_le cec_id;
|
||||
INSERT_PADDING_BYTES(8);
|
||||
|
||||
std::array<u8, 0x480> small_icon;
|
||||
std::array<u8, 0x1200> large_icon;
|
||||
|
||||
/// indicates the language used for each title entry
|
||||
enum class TitleLanguage {
|
||||
Japanese = 0,
|
||||
English = 1,
|
||||
French = 2,
|
||||
German = 3,
|
||||
Italian = 4,
|
||||
Spanish = 5,
|
||||
SimplifiedChinese = 6,
|
||||
Korean= 7,
|
||||
Dutch = 8,
|
||||
Portuguese = 9,
|
||||
Russian = 10,
|
||||
TraditionalChinese = 11
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets game icon from SMDH
|
||||
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
|
||||
* @return vector of RGB565 data
|
||||
*/
|
||||
std::vector<u16> GetIcon(bool large) const;
|
||||
|
||||
/**
|
||||
* Gets the short game title from SMDH
|
||||
* @param language title language
|
||||
* @return UTF-16 array of the short title
|
||||
*/
|
||||
std::array<u16, 0x40> GetShortTitle(Loader::SMDH::TitleLanguage language) const;
|
||||
};
|
||||
static_assert(sizeof(SMDH) == 0x36C0, "SMDH structure size is wrong");
|
||||
|
||||
} // namespace
|
@ -246,6 +246,26 @@ void Write(const VAddr vaddr, const T data) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValidVirtualAddress(const VAddr vaddr) {
|
||||
const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
|
||||
if (page_pointer)
|
||||
return true;
|
||||
|
||||
if (current_page_table->attributes[vaddr >> PAGE_BITS] != PageType::Special)
|
||||
return false;
|
||||
|
||||
MMIORegionPointer mmio_region = GetMMIOHandler(vaddr);
|
||||
if (mmio_region) {
|
||||
return mmio_region->IsValidAddress(vaddr);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsValidPhysicalAddress(const PAddr paddr) {
|
||||
return IsValidVirtualAddress(PhysicalToVirtualAddress(paddr));
|
||||
}
|
||||
|
||||
u8* GetPointer(const VAddr vaddr) {
|
||||
u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
|
||||
if (page_pointer) {
|
||||
@ -261,6 +281,7 @@ u8* GetPointer(const VAddr vaddr) {
|
||||
}
|
||||
|
||||
u8* GetPhysicalPointer(PAddr address) {
|
||||
// TODO(Subv): This call should not go through the application's memory mapping.
|
||||
return GetPointer(PhysicalToVirtualAddress(address));
|
||||
}
|
||||
|
||||
@ -343,6 +364,59 @@ u64 Read64(const VAddr addr) {
|
||||
return Read<u64_le>(addr);
|
||||
}
|
||||
|
||||
void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) {
|
||||
size_t remaining_size = size;
|
||||
size_t page_index = src_addr >> PAGE_BITS;
|
||||
size_t page_offset = src_addr & PAGE_MASK;
|
||||
|
||||
while (remaining_size > 0) {
|
||||
const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size);
|
||||
const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset;
|
||||
|
||||
switch (current_page_table->attributes[page_index]) {
|
||||
case PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory, "unmapped ReadBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, src_addr, size);
|
||||
std::memset(dest_buffer, 0, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::Memory: {
|
||||
DEBUG_ASSERT(current_page_table->pointers[page_index]);
|
||||
|
||||
const u8* src_ptr = current_page_table->pointers[page_index] + page_offset;
|
||||
std::memcpy(dest_buffer, src_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::Special: {
|
||||
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
|
||||
|
||||
GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, dest_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
|
||||
|
||||
std::memcpy(dest_buffer, GetPointerFromVMA(current_vaddr), copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedSpecial: {
|
||||
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
|
||||
|
||||
RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
|
||||
|
||||
GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, dest_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
page_index++;
|
||||
page_offset = 0;
|
||||
dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
|
||||
remaining_size -= copy_amount;
|
||||
}
|
||||
}
|
||||
|
||||
void Write8(const VAddr addr, const u8 data) {
|
||||
Write<u8>(addr, data);
|
||||
}
|
||||
@ -359,9 +433,165 @@ void Write64(const VAddr addr, const u64 data) {
|
||||
Write<u64_le>(addr, data);
|
||||
}
|
||||
|
||||
void WriteBlock(const VAddr addr, const u8* data, const size_t size) {
|
||||
for (u32 offset = 0; offset < size; offset++) {
|
||||
Write8(addr + offset, data[offset]);
|
||||
void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size) {
|
||||
size_t remaining_size = size;
|
||||
size_t page_index = dest_addr >> PAGE_BITS;
|
||||
size_t page_offset = dest_addr & PAGE_MASK;
|
||||
|
||||
while (remaining_size > 0) {
|
||||
const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size);
|
||||
const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset;
|
||||
|
||||
switch (current_page_table->attributes[page_index]) {
|
||||
case PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory, "unmapped WriteBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, dest_addr, size);
|
||||
break;
|
||||
}
|
||||
case PageType::Memory: {
|
||||
DEBUG_ASSERT(current_page_table->pointers[page_index]);
|
||||
|
||||
u8* dest_ptr = current_page_table->pointers[page_index] + page_offset;
|
||||
std::memcpy(dest_ptr, src_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::Special: {
|
||||
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
|
||||
|
||||
GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, src_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
|
||||
|
||||
std::memcpy(GetPointerFromVMA(current_vaddr), src_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedSpecial: {
|
||||
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
|
||||
|
||||
RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
|
||||
|
||||
GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, src_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
page_index++;
|
||||
page_offset = 0;
|
||||
src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
|
||||
remaining_size -= copy_amount;
|
||||
}
|
||||
}
|
||||
|
||||
void ZeroBlock(const VAddr dest_addr, const size_t size) {
|
||||
size_t remaining_size = size;
|
||||
size_t page_index = dest_addr >> PAGE_BITS;
|
||||
size_t page_offset = dest_addr & PAGE_MASK;
|
||||
|
||||
static const std::array<u8, PAGE_SIZE> zeros = {};
|
||||
|
||||
while (remaining_size > 0) {
|
||||
const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size);
|
||||
const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset;
|
||||
|
||||
switch (current_page_table->attributes[page_index]) {
|
||||
case PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory, "unmapped ZeroBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, dest_addr, size);
|
||||
break;
|
||||
}
|
||||
case PageType::Memory: {
|
||||
DEBUG_ASSERT(current_page_table->pointers[page_index]);
|
||||
|
||||
u8* dest_ptr = current_page_table->pointers[page_index] + page_offset;
|
||||
std::memset(dest_ptr, 0, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::Special: {
|
||||
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
|
||||
|
||||
GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
|
||||
|
||||
std::memset(GetPointerFromVMA(current_vaddr), 0, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedSpecial: {
|
||||
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
|
||||
|
||||
RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
|
||||
|
||||
GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
page_index++;
|
||||
page_offset = 0;
|
||||
remaining_size -= copy_amount;
|
||||
}
|
||||
}
|
||||
|
||||
void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) {
|
||||
size_t remaining_size = size;
|
||||
size_t page_index = src_addr >> PAGE_BITS;
|
||||
size_t page_offset = src_addr & PAGE_MASK;
|
||||
|
||||
while (remaining_size > 0) {
|
||||
const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size);
|
||||
const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset;
|
||||
|
||||
switch (current_page_table->attributes[page_index]) {
|
||||
case PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory, "unmapped CopyBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, src_addr, size);
|
||||
ZeroBlock(dest_addr, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::Memory: {
|
||||
DEBUG_ASSERT(current_page_table->pointers[page_index]);
|
||||
const u8* src_ptr = current_page_table->pointers[page_index] + page_offset;
|
||||
WriteBlock(dest_addr, src_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::Special: {
|
||||
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
|
||||
|
||||
std::vector<u8> buffer(copy_amount);
|
||||
GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size());
|
||||
WriteBlock(dest_addr, buffer.data(), buffer.size());
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
|
||||
|
||||
WriteBlock(dest_addr, GetPointerFromVMA(current_vaddr), copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedSpecial: {
|
||||
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
|
||||
|
||||
RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
|
||||
|
||||
std::vector<u8> buffer(copy_amount);
|
||||
GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size());
|
||||
WriteBlock(dest_addr, buffer.data(), buffer.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
page_index++;
|
||||
page_offset = 0;
|
||||
dest_addr += copy_amount;
|
||||
src_addr += copy_amount;
|
||||
remaining_size -= copy_amount;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,6 +110,9 @@ enum : VAddr {
|
||||
NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE,
|
||||
};
|
||||
|
||||
bool IsValidVirtualAddress(const VAddr addr);
|
||||
bool IsValidPhysicalAddress(const PAddr addr);
|
||||
|
||||
u8 Read8(VAddr addr);
|
||||
u16 Read16(VAddr addr);
|
||||
u32 Read32(VAddr addr);
|
||||
@ -120,7 +123,10 @@ void Write16(VAddr addr, u16 data);
|
||||
void Write32(VAddr addr, u32 data);
|
||||
void Write64(VAddr addr, u64 data);
|
||||
|
||||
void WriteBlock(VAddr addr, const u8* data, size_t size);
|
||||
void ReadBlock(const VAddr src_addr, void* dest_buffer, size_t size);
|
||||
void WriteBlock(const VAddr dest_addr, const void* src_buffer, size_t size);
|
||||
void ZeroBlock(const VAddr dest_addr, const size_t size);
|
||||
void CopyBlock(VAddr dest_addr, VAddr src_addr, size_t size);
|
||||
|
||||
u8* GetPointer(VAddr virtual_address);
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
#include "core/memory.h"
|
||||
#include "core/mmio.h"
|
||||
|
||||
namespace Memory {
|
||||
|
||||
|
@ -18,15 +18,21 @@ class MMIORegion {
|
||||
public:
|
||||
virtual ~MMIORegion() = default;
|
||||
|
||||
virtual bool IsValidAddress(VAddr addr) = 0;
|
||||
|
||||
virtual u8 Read8(VAddr addr) = 0;
|
||||
virtual u16 Read16(VAddr addr) = 0;
|
||||
virtual u32 Read32(VAddr addr) = 0;
|
||||
virtual u64 Read64(VAddr addr) = 0;
|
||||
|
||||
virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, size_t size) = 0;
|
||||
|
||||
virtual void Write8(VAddr addr, u8 data) = 0;
|
||||
virtual void Write16(VAddr addr, u16 data) = 0;
|
||||
virtual void Write32(VAddr addr, u32 data) = 0;
|
||||
virtual void Write64(VAddr addr, u64 data) = 0;
|
||||
|
||||
virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size) = 0;
|
||||
};
|
||||
|
||||
using MMIORegionPointer = std::shared_ptr<MMIORegion>;
|
||||
|
@ -13,36 +13,51 @@ namespace Settings {
|
||||
|
||||
namespace NativeInput {
|
||||
enum Values {
|
||||
// directly mapped keys
|
||||
A, B, X, Y,
|
||||
L, R, ZL, ZR,
|
||||
START, SELECT, HOME,
|
||||
DUP, DDOWN, DLEFT, DRIGHT,
|
||||
SUP, SDOWN, SLEFT, SRIGHT,
|
||||
CUP, CDOWN, CLEFT, CRIGHT,
|
||||
|
||||
// indirectly mapped keys
|
||||
CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT,
|
||||
CIRCLE_MODIFIER,
|
||||
|
||||
NUM_INPUTS
|
||||
};
|
||||
|
||||
static const std::array<const char*, NUM_INPUTS> Mapping = {{
|
||||
// directly mapped keys
|
||||
"pad_a", "pad_b", "pad_x", "pad_y",
|
||||
"pad_l", "pad_r", "pad_zl", "pad_zr",
|
||||
"pad_start", "pad_select", "pad_home",
|
||||
"pad_dup", "pad_ddown", "pad_dleft", "pad_dright",
|
||||
"pad_sup", "pad_sdown", "pad_sleft", "pad_sright",
|
||||
"pad_cup", "pad_cdown", "pad_cleft", "pad_cright"
|
||||
"pad_cup", "pad_cdown", "pad_cleft", "pad_cright",
|
||||
|
||||
// indirectly mapped keys
|
||||
"pad_circle_up", "pad_circle_down", "pad_circle_left", "pad_circle_right",
|
||||
"pad_circle_modifier",
|
||||
}};
|
||||
static const std::array<Values, NUM_INPUTS> All = {{
|
||||
A, B, X, Y,
|
||||
L, R, ZL, ZR,
|
||||
START, SELECT, HOME,
|
||||
DUP, DDOWN, DLEFT, DRIGHT,
|
||||
SUP, SDOWN, SLEFT, SRIGHT,
|
||||
CUP, CDOWN, CLEFT, CRIGHT
|
||||
CUP, CDOWN, CLEFT, CRIGHT,
|
||||
CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT,
|
||||
CIRCLE_MODIFIER,
|
||||
}};
|
||||
}
|
||||
|
||||
|
||||
struct Values {
|
||||
// CheckNew3DS
|
||||
bool is_new_3ds;
|
||||
|
||||
// Controls
|
||||
std::array<int, NativeInput::NUM_INPUTS> input_mappings;
|
||||
float pad_circle_modifier_scale;
|
||||
|
||||
// Core
|
||||
int frame_skip;
|
||||
|
@ -149,7 +149,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
||||
// Send to vertex shader
|
||||
if (g_debug_context)
|
||||
g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, static_cast<void*>(&immediate_input));
|
||||
Shader::OutputVertex output = g_state.vs.Run(shader_unit, immediate_input, regs.vs.num_input_attributes+1);
|
||||
g_state.vs.Run(shader_unit, immediate_input, regs.vs.num_input_attributes+1);
|
||||
Shader::OutputVertex output_vertex = shader_unit.output_registers.ToVertex(regs.vs);
|
||||
|
||||
// Send to renderer
|
||||
using Pica::Shader::OutputVertex;
|
||||
@ -157,7 +158,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
||||
VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
|
||||
};
|
||||
|
||||
g_state.primitive_assembler.SubmitVertex(output, AddTriangle);
|
||||
g_state.primitive_assembler.SubmitVertex(output_vertex, AddTriangle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -199,9 +200,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
||||
|
||||
// Processes information about internal vertex attributes to figure out how a vertex is loaded.
|
||||
// Later, these can be compiled and cached.
|
||||
VertexLoader loader;
|
||||
const u32 base_address = regs.vertex_attributes.GetPhysicalBaseAddress();
|
||||
loader.Setup(regs);
|
||||
VertexLoader loader(regs);
|
||||
|
||||
// Load vertices
|
||||
bool is_indexed = (id == PICA_REG_INDEX(trigger_draw_indexed));
|
||||
@ -231,7 +231,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
||||
// The size has been tuned for optimal balance between hit-rate and the cost of lookup
|
||||
const size_t VERTEX_CACHE_SIZE = 32;
|
||||
std::array<u16, VERTEX_CACHE_SIZE> vertex_cache_ids;
|
||||
std::array<Shader::OutputVertex, VERTEX_CACHE_SIZE> vertex_cache;
|
||||
std::array<Shader::OutputRegisters, VERTEX_CACHE_SIZE> vertex_cache;
|
||||
|
||||
unsigned int vertex_cache_pos = 0;
|
||||
vertex_cache_ids.fill(-1);
|
||||
@ -249,7 +249,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
||||
ASSERT(vertex != -1);
|
||||
|
||||
bool vertex_cache_hit = false;
|
||||
Shader::OutputVertex output;
|
||||
Shader::OutputRegisters output_registers;
|
||||
|
||||
if (is_indexed) {
|
||||
if (g_debug_context && Pica::g_debug_context->recorder) {
|
||||
@ -259,7 +259,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
||||
|
||||
for (unsigned int i = 0; i < VERTEX_CACHE_SIZE; ++i) {
|
||||
if (vertex == vertex_cache_ids[i]) {
|
||||
output = vertex_cache[i];
|
||||
output_registers = vertex_cache[i];
|
||||
vertex_cache_hit = true;
|
||||
break;
|
||||
}
|
||||
@ -274,15 +274,19 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
||||
// Send to vertex shader
|
||||
if (g_debug_context)
|
||||
g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, (void*)&input);
|
||||
output = g_state.vs.Run(shader_unit, input, loader.GetNumTotalAttributes());
|
||||
g_state.vs.Run(shader_unit, input, loader.GetNumTotalAttributes());
|
||||
output_registers = shader_unit.output_registers;
|
||||
|
||||
if (is_indexed) {
|
||||
vertex_cache[vertex_cache_pos] = output;
|
||||
vertex_cache[vertex_cache_pos] = output_registers;
|
||||
vertex_cache_ids[vertex_cache_pos] = vertex;
|
||||
vertex_cache_pos = (vertex_cache_pos + 1) % VERTEX_CACHE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// Retreive vertex from register data
|
||||
Shader::OutputVertex output_vertex = output_registers.ToVertex(regs.vs);
|
||||
|
||||
// Send to renderer
|
||||
using Pica::Shader::OutputVertex;
|
||||
auto AddTriangle = [](
|
||||
@ -290,7 +294,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
||||
VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
|
||||
};
|
||||
|
||||
primitive_assembler.SubmitVertex(output, AddTriangle);
|
||||
primitive_assembler.SubmitVertex(output_vertex, AddTriangle);
|
||||
}
|
||||
|
||||
for (auto& range : memory_accesses.ranges) {
|
||||
|
@ -787,23 +787,21 @@ struct Regs {
|
||||
LightColor diffuse; // material.diffuse * light.diffuse
|
||||
LightColor ambient; // material.ambient * light.ambient
|
||||
|
||||
struct {
|
||||
// Encoded as 16-bit floating point
|
||||
union {
|
||||
BitField< 0, 16, u32> x;
|
||||
BitField<16, 16, u32> y;
|
||||
};
|
||||
union {
|
||||
BitField< 0, 16, u32> z;
|
||||
};
|
||||
|
||||
INSERT_PADDING_WORDS(0x3);
|
||||
|
||||
union {
|
||||
BitField<0, 1, u32> directional;
|
||||
BitField<1, 1, u32> two_sided_diffuse; // When disabled, clamp dot-product to 0
|
||||
};
|
||||
// Encoded as 16-bit floating point
|
||||
union {
|
||||
BitField< 0, 16, u32> x;
|
||||
BitField<16, 16, u32> y;
|
||||
};
|
||||
union {
|
||||
BitField< 0, 16, u32> z;
|
||||
};
|
||||
|
||||
INSERT_PADDING_WORDS(0x3);
|
||||
|
||||
union {
|
||||
BitField<0, 1, u32> directional;
|
||||
BitField<1, 1, u32> two_sided_diffuse; // When disabled, clamp dot-product to 0
|
||||
} config;
|
||||
|
||||
BitField<0, 20, u32> dist_atten_bias;
|
||||
BitField<0, 20, u32> dist_atten_scale;
|
||||
@ -824,7 +822,7 @@ struct Regs {
|
||||
BitField<27, 1, u32> clamp_highlights;
|
||||
BitField<28, 2, LightingBumpMode> bump_mode;
|
||||
BitField<30, 1, u32> disable_bump_renorm;
|
||||
};
|
||||
} config0;
|
||||
|
||||
union {
|
||||
BitField<16, 1, u32> disable_lut_d0;
|
||||
@ -845,13 +843,13 @@ struct Regs {
|
||||
BitField<29, 1, u32> disable_dist_atten_light_5;
|
||||
BitField<30, 1, u32> disable_dist_atten_light_6;
|
||||
BitField<31, 1, u32> disable_dist_atten_light_7;
|
||||
};
|
||||
} config1;
|
||||
|
||||
bool IsDistAttenDisabled(unsigned index) const {
|
||||
const unsigned disable[] = { disable_dist_atten_light_0, disable_dist_atten_light_1,
|
||||
disable_dist_atten_light_2, disable_dist_atten_light_3,
|
||||
disable_dist_atten_light_4, disable_dist_atten_light_5,
|
||||
disable_dist_atten_light_6, disable_dist_atten_light_7 };
|
||||
const unsigned disable[] = { config1.disable_dist_atten_light_0, config1.disable_dist_atten_light_1,
|
||||
config1.disable_dist_atten_light_2, config1.disable_dist_atten_light_3,
|
||||
config1.disable_dist_atten_light_4, config1.disable_dist_atten_light_5,
|
||||
config1.disable_dist_atten_light_6, config1.disable_dist_atten_light_7 };
|
||||
return disable[index] != 0;
|
||||
}
|
||||
|
||||
|
@ -380,6 +380,17 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
|
||||
SyncCombinerColor();
|
||||
break;
|
||||
|
||||
// Fragment lighting switches
|
||||
case PICA_REG_INDEX(lighting.disable):
|
||||
case PICA_REG_INDEX(lighting.num_lights):
|
||||
case PICA_REG_INDEX(lighting.config0):
|
||||
case PICA_REG_INDEX(lighting.config1):
|
||||
case PICA_REG_INDEX(lighting.abs_lut_input):
|
||||
case PICA_REG_INDEX(lighting.lut_input):
|
||||
case PICA_REG_INDEX(lighting.lut_scale):
|
||||
case PICA_REG_INDEX(lighting.light_enable):
|
||||
break;
|
||||
|
||||
// Fragment lighting specular 0 color
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[0].specular_0, 0x140 + 0 * 0x10):
|
||||
SyncLightSpecular0(0);
|
||||
@ -518,6 +529,70 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
|
||||
SyncLightPosition(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting light source config
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[0].config, 0x149 + 0 * 0x10):
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[1].config, 0x149 + 1 * 0x10):
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[2].config, 0x149 + 2 * 0x10):
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[3].config, 0x149 + 3 * 0x10):
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[4].config, 0x149 + 4 * 0x10):
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[5].config, 0x149 + 5 * 0x10):
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[6].config, 0x149 + 6 * 0x10):
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[7].config, 0x149 + 7 * 0x10):
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
// Fragment lighting distance attenuation bias
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[0].dist_atten_bias, 0x014A + 0 * 0x10):
|
||||
SyncLightDistanceAttenuationBias(0);
|
||||
break;
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[1].dist_atten_bias, 0x014A + 1 * 0x10):
|
||||
SyncLightDistanceAttenuationBias(1);
|
||||
break;
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[2].dist_atten_bias, 0x014A + 2 * 0x10):
|
||||
SyncLightDistanceAttenuationBias(2);
|
||||
break;
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[3].dist_atten_bias, 0x014A + 3 * 0x10):
|
||||
SyncLightDistanceAttenuationBias(3);
|
||||
break;
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[4].dist_atten_bias, 0x014A + 4 * 0x10):
|
||||
SyncLightDistanceAttenuationBias(4);
|
||||
break;
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[5].dist_atten_bias, 0x014A + 5 * 0x10):
|
||||
SyncLightDistanceAttenuationBias(5);
|
||||
break;
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[6].dist_atten_bias, 0x014A + 6 * 0x10):
|
||||
SyncLightDistanceAttenuationBias(6);
|
||||
break;
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[7].dist_atten_bias, 0x014A + 7 * 0x10):
|
||||
SyncLightDistanceAttenuationBias(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting distance attenuation scale
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[0].dist_atten_scale, 0x014B + 0 * 0x10):
|
||||
SyncLightDistanceAttenuationScale(0);
|
||||
break;
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[1].dist_atten_scale, 0x014B + 1 * 0x10):
|
||||
SyncLightDistanceAttenuationScale(1);
|
||||
break;
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[2].dist_atten_scale, 0x014B + 2 * 0x10):
|
||||
SyncLightDistanceAttenuationScale(2);
|
||||
break;
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[3].dist_atten_scale, 0x014B + 3 * 0x10):
|
||||
SyncLightDistanceAttenuationScale(3);
|
||||
break;
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[4].dist_atten_scale, 0x014B + 4 * 0x10):
|
||||
SyncLightDistanceAttenuationScale(4);
|
||||
break;
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[5].dist_atten_scale, 0x014B + 5 * 0x10):
|
||||
SyncLightDistanceAttenuationScale(5);
|
||||
break;
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[6].dist_atten_scale, 0x014B + 6 * 0x10):
|
||||
SyncLightDistanceAttenuationScale(6);
|
||||
break;
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.light[7].dist_atten_scale, 0x014B + 7 * 0x10):
|
||||
SyncLightDistanceAttenuationScale(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting global ambient color (emission + ambient * ambient)
|
||||
case PICA_REG_INDEX_WORKAROUND(lighting.global_ambient, 0x1c0):
|
||||
SyncGlobalAmbient();
|
||||
@ -896,6 +971,8 @@ void RasterizerOpenGL::SetShader() {
|
||||
SyncLightDiffuse(light_index);
|
||||
SyncLightAmbient(light_index);
|
||||
SyncLightPosition(light_index);
|
||||
SyncLightDistanceAttenuationBias(light_index);
|
||||
SyncLightDistanceAttenuationScale(light_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1105,3 +1182,21 @@ void RasterizerOpenGL::SyncLightPosition(int light_index) {
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncLightDistanceAttenuationBias(int light_index) {
|
||||
GLfloat dist_atten_bias = Pica::float20::FromRaw(Pica::g_state.regs.lighting.light[light_index].dist_atten_bias).ToFloat32();
|
||||
|
||||
if (dist_atten_bias != uniform_block_data.data.light_src[light_index].dist_atten_bias) {
|
||||
uniform_block_data.data.light_src[light_index].dist_atten_bias = dist_atten_bias;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncLightDistanceAttenuationScale(int light_index) {
|
||||
GLfloat dist_atten_scale = Pica::float20::FromRaw(Pica::g_state.regs.lighting.light[light_index].dist_atten_scale).ToFloat32();
|
||||
|
||||
if (dist_atten_scale != uniform_block_data.data.light_src[light_index].dist_atten_scale) {
|
||||
uniform_block_data.data.light_src[light_index].dist_atten_scale = dist_atten_scale;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
@ -89,49 +89,47 @@ union PicaShaderConfig {
|
||||
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.directional != 0;
|
||||
state.lighting.light[light_index].two_sided_diffuse = light.two_sided_diffuse != 0;
|
||||
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].dist_atten_enable = !regs.lighting.IsDistAttenDisabled(num);
|
||||
state.lighting.light[light_index].dist_atten_bias = Pica::float20::FromRaw(light.dist_atten_bias).ToFloat32();
|
||||
state.lighting.light[light_index].dist_atten_scale = Pica::float20::FromRaw(light.dist_atten_scale).ToFloat32();
|
||||
}
|
||||
|
||||
state.lighting.lut_d0.enable = regs.lighting.disable_lut_d0 == 0;
|
||||
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.disable_lut_d1 == 0;
|
||||
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);
|
||||
|
||||
state.lighting.lut_fr.enable = regs.lighting.disable_lut_fr == 0;
|
||||
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.disable_lut_rr == 0;
|
||||
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.disable_lut_rg == 0;
|
||||
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.disable_lut_rb == 0;
|
||||
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.config;
|
||||
state.lighting.fresnel_selector = regs.lighting.fresnel_selector;
|
||||
state.lighting.bump_mode = regs.lighting.bump_mode;
|
||||
state.lighting.bump_selector = regs.lighting.bump_selector;
|
||||
state.lighting.bump_renorm = regs.lighting.disable_bump_renorm == 0;
|
||||
state.lighting.clamp_highlights = regs.lighting.clamp_highlights != 0;
|
||||
state.lighting.config = regs.lighting.config0.config;
|
||||
state.lighting.fresnel_selector = regs.lighting.config0.fresnel_selector;
|
||||
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;
|
||||
|
||||
return res;
|
||||
}
|
||||
@ -184,8 +182,6 @@ union PicaShaderConfig {
|
||||
bool directional;
|
||||
bool two_sided_diffuse;
|
||||
bool dist_atten_enable;
|
||||
GLfloat dist_atten_scale;
|
||||
GLfloat dist_atten_bias;
|
||||
} light[8];
|
||||
|
||||
bool enable;
|
||||
@ -316,6 +312,8 @@ private:
|
||||
alignas(16) GLvec3 diffuse;
|
||||
alignas(16) GLvec3 ambient;
|
||||
alignas(16) GLvec3 position;
|
||||
GLfloat dist_atten_bias;
|
||||
GLfloat dist_atten_scale;
|
||||
};
|
||||
|
||||
/// Uniform structure for the Uniform Buffer Object, all members must be 16-byte aligned
|
||||
@ -330,7 +328,7 @@ private:
|
||||
LightSrc light_src[8];
|
||||
};
|
||||
|
||||
static_assert(sizeof(UniformData) == 0x310, "The size of the UniformData structure has changed, update the structure in the shader");
|
||||
static_assert(sizeof(UniformData) == 0x390, "The size of the UniformData structure has changed, update the structure in the shader");
|
||||
static_assert(sizeof(UniformData) < 16384, "UniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
/// Sets the OpenGL shader in accordance with the current PICA register state
|
||||
@ -402,6 +400,12 @@ private:
|
||||
/// Syncs the specified light's position to match the PICA register
|
||||
void SyncLightPosition(int light_index);
|
||||
|
||||
/// Syncs the specified light's distance attenuation bias to match the PICA register
|
||||
void SyncLightDistanceAttenuationBias(int light_index);
|
||||
|
||||
/// Syncs the specified light's distance attenuation scale to match the PICA register
|
||||
void SyncLightDistanceAttenuationScale(int light_index);
|
||||
|
||||
OpenGLState state;
|
||||
|
||||
RasterizerCacheOpenGL res_cache;
|
||||
|
@ -439,9 +439,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
|
||||
// If enabled, compute distance attenuation value
|
||||
std::string dist_atten = "1.0";
|
||||
if (light_config.dist_atten_enable) {
|
||||
std::string scale = std::to_string(light_config.dist_atten_scale);
|
||||
std::string bias = std::to_string(light_config.dist_atten_bias);
|
||||
std::string index = "(" + scale + " * length(-view - " + light_src + ".position) + " + bias + ")";
|
||||
std::string index = "(" + light_src + ".dist_atten_scale * length(-view - " + light_src + ".position) + " + light_src + ".dist_atten_bias)";
|
||||
index = "((clamp(" + index + ", 0.0, FLOAT_255)))";
|
||||
const unsigned lut_num = ((unsigned)Regs::LightingSampler::DistanceAttenuation + light_config.num);
|
||||
dist_atten = GetLutValue((Regs::LightingSampler)lut_num, index);
|
||||
@ -549,6 +547,8 @@ struct LightSrc {
|
||||
vec3 diffuse;
|
||||
vec3 ambient;
|
||||
vec3 position;
|
||||
float dist_atten_bias;
|
||||
float dist_atten_scale;
|
||||
};
|
||||
|
||||
layout (std140) uniform shader_data {
|
||||
|
@ -450,7 +450,7 @@ static const char* GetType(GLenum type) {
|
||||
#undef RET
|
||||
}
|
||||
|
||||
static void DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
|
||||
static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
|
||||
const GLchar* message, const void* user_param) {
|
||||
Log::Level level;
|
||||
switch (severity) {
|
||||
|
@ -30,6 +30,58 @@ namespace Pica {
|
||||
|
||||
namespace Shader {
|
||||
|
||||
OutputVertex OutputRegisters::ToVertex(const Regs::ShaderConfig& config) {
|
||||
// Setup output data
|
||||
OutputVertex ret;
|
||||
// TODO(neobrain): Under some circumstances, up to 16 attributes may be output. We need to
|
||||
// figure out what those circumstances are and enable the remaining outputs then.
|
||||
unsigned index = 0;
|
||||
for (unsigned i = 0; i < 7; ++i) {
|
||||
|
||||
if (index >= g_state.regs.vs_output_total)
|
||||
break;
|
||||
|
||||
if ((config.output_mask & (1 << i)) == 0)
|
||||
continue;
|
||||
|
||||
const auto& output_register_map = g_state.regs.vs_output_attributes[index];
|
||||
|
||||
u32 semantics[4] = {
|
||||
output_register_map.map_x, output_register_map.map_y,
|
||||
output_register_map.map_z, output_register_map.map_w
|
||||
};
|
||||
|
||||
for (unsigned comp = 0; comp < 4; ++comp) {
|
||||
float24* out = ((float24*)&ret) + semantics[comp];
|
||||
if (semantics[comp] != Regs::VSOutputAttributes::INVALID) {
|
||||
*out = value[i][comp];
|
||||
} else {
|
||||
// Zero output so that attributes which aren't output won't have denormals in them,
|
||||
// which would slow us down later.
|
||||
memset(out, 0, sizeof(*out));
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
// The hardware takes the absolute and saturates vertex colors like this, *before* doing interpolation
|
||||
for (unsigned i = 0; i < 4; ++i) {
|
||||
ret.color[i] = float24::FromFloat32(
|
||||
std::fmin(std::fabs(ret.color[i].ToFloat32()), 1.0f));
|
||||
}
|
||||
|
||||
LOG_TRACE(HW_GPU, "Output vertex: pos(%.2f, %.2f, %.2f, %.2f), quat(%.2f, %.2f, %.2f, %.2f), "
|
||||
"col(%.2f, %.2f, %.2f, %.2f), tc0(%.2f, %.2f), view(%.2f, %.2f, %.2f)",
|
||||
ret.pos.x.ToFloat32(), ret.pos.y.ToFloat32(), ret.pos.z.ToFloat32(), ret.pos.w.ToFloat32(),
|
||||
ret.quat.x.ToFloat32(), ret.quat.y.ToFloat32(), ret.quat.z.ToFloat32(), ret.quat.w.ToFloat32(),
|
||||
ret.color.x.ToFloat32(), ret.color.y.ToFloat32(), ret.color.z.ToFloat32(), ret.color.w.ToFloat32(),
|
||||
ret.tc0.u().ToFloat32(), ret.tc0.v().ToFloat32(),
|
||||
ret.view.x.ToFloat32(), ret.view.y.ToFloat32(), ret.view.z.ToFloat32());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
static std::unordered_map<u64, std::unique_ptr<JitShader>> shader_map;
|
||||
static const JitShader* jit_shader;
|
||||
@ -62,7 +114,7 @@ void ShaderSetup::Setup() {
|
||||
|
||||
MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240));
|
||||
|
||||
OutputVertex ShaderSetup::Run(UnitState<false>& state, const InputVertex& input, int num_attributes) {
|
||||
void ShaderSetup::Run(UnitState<false>& state, const InputVertex& input, int num_attributes) {
|
||||
auto& config = g_state.regs.vs;
|
||||
auto& setup = g_state.vs;
|
||||
|
||||
@ -89,55 +141,6 @@ OutputVertex ShaderSetup::Run(UnitState<false>& state, const InputVertex& input,
|
||||
RunInterpreter(setup, state, config.main_offset);
|
||||
#endif // ARCHITECTURE_x86_64
|
||||
|
||||
// Setup output data
|
||||
OutputVertex ret;
|
||||
// TODO(neobrain): Under some circumstances, up to 16 attributes may be output. We need to
|
||||
// figure out what those circumstances are and enable the remaining outputs then.
|
||||
unsigned index = 0;
|
||||
for (unsigned i = 0; i < 7; ++i) {
|
||||
|
||||
if (index >= g_state.regs.vs_output_total)
|
||||
break;
|
||||
|
||||
if ((g_state.regs.vs.output_mask & (1 << i)) == 0)
|
||||
continue;
|
||||
|
||||
const auto& output_register_map = g_state.regs.vs_output_attributes[index]; // TODO: Don't hardcode VS here
|
||||
|
||||
u32 semantics[4] = {
|
||||
output_register_map.map_x, output_register_map.map_y,
|
||||
output_register_map.map_z, output_register_map.map_w
|
||||
};
|
||||
|
||||
for (unsigned comp = 0; comp < 4; ++comp) {
|
||||
float24* out = ((float24*)&ret) + semantics[comp];
|
||||
if (semantics[comp] != Regs::VSOutputAttributes::INVALID) {
|
||||
*out = state.registers.output[i][comp];
|
||||
} else {
|
||||
// Zero output so that attributes which aren't output won't have denormals in them,
|
||||
// which would slow us down later.
|
||||
memset(out, 0, sizeof(*out));
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
// The hardware takes the absolute and saturates vertex colors like this, *before* doing interpolation
|
||||
for (unsigned i = 0; i < 4; ++i) {
|
||||
ret.color[i] = float24::FromFloat32(
|
||||
std::fmin(std::fabs(ret.color[i].ToFloat32()), 1.0f));
|
||||
}
|
||||
|
||||
LOG_TRACE(HW_GPU, "Output vertex: pos(%.2f, %.2f, %.2f, %.2f), quat(%.2f, %.2f, %.2f, %.2f), "
|
||||
"col(%.2f, %.2f, %.2f, %.2f), tc0(%.2f, %.2f), view(%.2f, %.2f, %.2f)",
|
||||
ret.pos.x.ToFloat32(), ret.pos.y.ToFloat32(), ret.pos.z.ToFloat32(), ret.pos.w.ToFloat32(),
|
||||
ret.quat.x.ToFloat32(), ret.quat.y.ToFloat32(), ret.quat.z.ToFloat32(), ret.quat.w.ToFloat32(),
|
||||
ret.color.x.ToFloat32(), ret.color.y.ToFloat32(), ret.color.z.ToFloat32(), ret.color.w.ToFloat32(),
|
||||
ret.tc0.u().ToFloat32(), ret.tc0.v().ToFloat32(),
|
||||
ret.view.x.ToFloat32(), ret.view.y.ToFloat32(), ret.view.z.ToFloat32());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
DebugData<true> ShaderSetup::ProduceDebugInfo(const InputVertex& input, int num_attributes, const Regs::ShaderConfig& config, const ShaderSetup& setup) {
|
||||
|
@ -84,6 +84,15 @@ struct OutputVertex {
|
||||
static_assert(std::is_pod<OutputVertex>::value, "Structure is not POD");
|
||||
static_assert(sizeof(OutputVertex) == 32 * sizeof(float), "OutputVertex has invalid size");
|
||||
|
||||
struct OutputRegisters {
|
||||
OutputRegisters() = default;
|
||||
|
||||
alignas(16) Math::Vec4<float24> value[16];
|
||||
|
||||
OutputVertex ToVertex(const Regs::ShaderConfig& config);
|
||||
};
|
||||
static_assert(std::is_pod<OutputRegisters>::value, "Structure is not POD");
|
||||
|
||||
// Helper structure used to keep track of data useful for inspection of shader emulation
|
||||
template<bool full_debugging>
|
||||
struct DebugData;
|
||||
@ -267,11 +276,12 @@ struct UnitState {
|
||||
// The registers are accessed by the shader JIT using SSE instructions, and are therefore
|
||||
// required to be 16-byte aligned.
|
||||
alignas(16) Math::Vec4<float24> input[16];
|
||||
alignas(16) Math::Vec4<float24> output[16];
|
||||
alignas(16) Math::Vec4<float24> temporary[16];
|
||||
} registers;
|
||||
static_assert(std::is_pod<Registers>::value, "Structure is not POD");
|
||||
|
||||
OutputRegisters output_registers;
|
||||
|
||||
bool conditional_code[2];
|
||||
|
||||
// Two Address registers and one loop counter
|
||||
@ -297,7 +307,7 @@ struct UnitState {
|
||||
static size_t OutputOffset(const DestRegister& reg) {
|
||||
switch (reg.GetRegisterType()) {
|
||||
case RegisterType::Output:
|
||||
return offsetof(UnitState, registers.output) + reg.GetIndex()*sizeof(Math::Vec4<float24>);
|
||||
return offsetof(UnitState, output_registers.value) + reg.GetIndex()*sizeof(Math::Vec4<float24>);
|
||||
|
||||
case RegisterType::Temporary:
|
||||
return offsetof(UnitState, registers.temporary) + reg.GetIndex()*sizeof(Math::Vec4<float24>);
|
||||
@ -354,9 +364,8 @@ struct ShaderSetup {
|
||||
* @param state Shader unit state, must be setup per shader and per shader unit
|
||||
* @param input Input vertex into the shader
|
||||
* @param num_attributes The number of vertex shader attributes
|
||||
* @return The output vertex, after having been processed by the vertex shader
|
||||
*/
|
||||
OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attributes);
|
||||
void Run(UnitState<false>& state, const InputVertex& input, int num_attributes);
|
||||
|
||||
/**
|
||||
* Produce debug information based on the given shader and input vertex
|
||||
|
@ -144,7 +144,7 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
|
||||
src2[3] = src2[3] * float24::FromFloat32(-1);
|
||||
}
|
||||
|
||||
float24* dest = (instr.common.dest.Value() < 0x10) ? &state.registers.output[instr.common.dest.Value().GetIndex()][0]
|
||||
float24* dest = (instr.common.dest.Value() < 0x10) ? &state.output_registers.value[instr.common.dest.Value().GetIndex()][0]
|
||||
: (instr.common.dest.Value() < 0x20) ? &state.registers.temporary[instr.common.dest.Value().GetIndex()][0]
|
||||
: dummy_vec4_float24;
|
||||
|
||||
@ -483,7 +483,7 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
|
||||
src3[3] = src3[3] * float24::FromFloat32(-1);
|
||||
}
|
||||
|
||||
float24* dest = (instr.mad.dest.Value() < 0x10) ? &state.registers.output[instr.mad.dest.Value().GetIndex()][0]
|
||||
float24* dest = (instr.mad.dest.Value() < 0x10) ? &state.output_registers.value[instr.mad.dest.Value().GetIndex()][0]
|
||||
: (instr.mad.dest.Value() < 0x20) ? &state.registers.temporary[instr.mad.dest.Value().GetIndex()][0]
|
||||
: dummy_vec4_float24;
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
#include <boost/range/algorithm/fill.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
@ -21,6 +21,8 @@
|
||||
namespace Pica {
|
||||
|
||||
void VertexLoader::Setup(const Pica::Regs& regs) {
|
||||
ASSERT_MSG(!is_setup, "VertexLoader is not intended to be setup more than once.");
|
||||
|
||||
const auto& attribute_config = regs.vertex_attributes;
|
||||
num_total_attributes = attribute_config.GetNumTotalAttributes();
|
||||
|
||||
@ -60,9 +62,13 @@ void VertexLoader::Setup(const Pica::Regs& regs) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is_setup = true;
|
||||
}
|
||||
|
||||
void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, DebugUtils::MemoryAccessTracker& memory_accesses) {
|
||||
ASSERT_MSG(is_setup, "A VertexLoader needs to be setup before loading vertices.");
|
||||
|
||||
for (int i = 0; i < num_total_attributes; ++i) {
|
||||
if (vertex_attribute_elements[i] != 0) {
|
||||
// Load per-vertex data from the loader arrays
|
||||
|
@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/pica.h"
|
||||
|
||||
namespace Pica {
|
||||
@ -11,23 +12,29 @@ class MemoryAccessTracker;
|
||||
}
|
||||
|
||||
namespace Shader {
|
||||
class InputVertex;
|
||||
struct InputVertex;
|
||||
}
|
||||
|
||||
class VertexLoader {
|
||||
public:
|
||||
VertexLoader() = default;
|
||||
explicit VertexLoader(const Pica::Regs& regs) {
|
||||
Setup(regs);
|
||||
}
|
||||
|
||||
void Setup(const Pica::Regs& regs);
|
||||
void LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, DebugUtils::MemoryAccessTracker& memory_accesses);
|
||||
|
||||
int GetNumTotalAttributes() const { return num_total_attributes; }
|
||||
|
||||
private:
|
||||
u32 vertex_attribute_sources[16];
|
||||
u32 vertex_attribute_strides[16] = {};
|
||||
Regs::VertexAttributeFormat vertex_attribute_formats[16] = {};
|
||||
u32 vertex_attribute_elements[16] = {};
|
||||
bool vertex_attribute_is_default[16];
|
||||
int num_total_attributes;
|
||||
std::array<u32, 16> vertex_attribute_sources;
|
||||
std::array<u32, 16> vertex_attribute_strides{};
|
||||
std::array<Regs::VertexAttributeFormat, 16> vertex_attribute_formats;
|
||||
std::array<u32, 16> vertex_attribute_elements{};
|
||||
std::array<bool, 16> vertex_attribute_is_default;
|
||||
int num_total_attributes = 0;
|
||||
bool is_setup = false;
|
||||
};
|
||||
|
||||
} // namespace Pica
|
||||
|
Loading…
Reference in New Issue
Block a user