Fix merge conflicts
commit
8ba9ac0f74
@ -1,3 +1,9 @@
|
||||
[submodule "externals/inih/inih"]
|
||||
path = externals/inih/inih
|
||||
url = https://github.com/svn2github/inih
|
||||
[submodule "externals/boost"]
|
||||
path = externals/boost
|
||||
url = https://github.com/citra-emu/ext-boost.git
|
||||
[submodule "externals/nihstro"]
|
||||
path = externals/nihstro
|
||||
url = https://github.com/neobrain/nihstro.git
|
||||
|
@ -1,17 +1,31 @@
|
||||
citra emulator
|
||||
Citra Emulator
|
||||
==============
|
||||
[![Travis CI Build Status](https://travis-ci.org/citra-emu/citra.svg)](https://travis-ci.org/citra-emu/citra)
|
||||
|
||||
An experimental open-source Nintendo 3DS emulator/debugger written in C++. Citra is written with portability in mind, with builds actively maintained for Windows, Linux and OS X. At this time, it only emulates a subset of 3DS hardware, and therefore is generally only useful for booting/debugging very simple homebrew demos. Citra is licensed under the GPLv2. Refer to the license.txt file included. Please read the [FAQ](https://github.com/citra-emu/citra/wiki/FAQ) before getting started with the project.
|
||||
Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and OS X. Citra only emulates a subset of 3DS hardware, and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! None of these run to a playable state, but we are working every day to advance the project forward.
|
||||
|
||||
For development discussion, please join us @ #citra on [freenode](http://webchat.freenode.net/).
|
||||
Citra is licensed under the GPLv2. Refer to the license.txt file included. Please read the [FAQ](https://github.com/citra-emu/citra/wiki/FAQ) before getting started with the project.
|
||||
|
||||
For development discussion, please join us @ #citra on [freenode](http://webchat.freenode.net/?channels=citra).
|
||||
|
||||
### Development
|
||||
|
||||
If you want to contribute please take a took at the [Contributor's Guide](CONTRIBUTING.md), [Roadmap](https://github.com/citra-emu/citra/wiki/Roadmap) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information) pages. You should as well contact any of the developers in the forum in order to know about the current state of the emulator.
|
||||
If you want to contribute please take a look at the [Contributor's Guide](CONTRIBUTING.md), [Roadmap](https://github.com/citra-emu/citra/wiki/Roadmap) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information) pages. You should as well contact any of the developers in the forum in order to know about the current state of the emulator.
|
||||
|
||||
### Building
|
||||
|
||||
* __Windows__: [Windows Build](https://github.com/citra-emu/citra/wiki/Windows-Build)
|
||||
* __Linux__: [Linux Build](https://github.com/citra-emu/citra/wiki/Linux-Build)
|
||||
* __OSX__: [OS X Build](https://github.com/citra-emu/citra/wiki/OS-X-Build)
|
||||
|
||||
|
||||
### Support
|
||||
If you like, you can [donate by PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=K899FANUJ2ZXW) - any donation received will go towards things like:
|
||||
* 3DS consoles for developers to explore the hardware
|
||||
* 3DS games for testing
|
||||
* Any equipment required for homebrew
|
||||
* Infrastructure setup
|
||||
* Eventually 3D displays to get proper 3D output working
|
||||
* ... etc ...
|
||||
|
||||
We also more than gladly accept used 3DS consoles, preferrably ones with firmware 4.5 or lower! If you would like to give yours away, don't hesitate to join our IRC channel #citra on [Freenode](http://webchat.freenode.net/?channels=citra) and talk to neobrain or bunnei. Mind you, IRC is slow-paced, so it might be a while until people reply. If you're in a hurry you can just leave contact details in the channel or via private message and we'll get back to you.
|
||||
|
@ -0,0 +1 @@
|
||||
Subproject commit 97052c28acb141dbf3c5e14114af99045344b695
|
@ -0,0 +1 @@
|
||||
Subproject commit fc71f8684d26ccf277ad68809c8bd7273141fe89
|
@ -0,0 +1,263 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QMetaType>
|
||||
#include <QPushButton>
|
||||
#include <QTreeWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
|
||||
#include "graphics_breakpoints.hxx"
|
||||
#include "graphics_breakpoints_p.hxx"
|
||||
|
||||
BreakPointModel::BreakPointModel(std::shared_ptr<Pica::DebugContext> debug_context, QObject* parent)
|
||||
: QAbstractListModel(parent), context_weak(debug_context),
|
||||
at_breakpoint(debug_context->at_breakpoint),
|
||||
active_breakpoint(debug_context->active_breakpoint)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int BreakPointModel::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
int BreakPointModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
return static_cast<int>(Pica::DebugContext::Event::NumEvents);
|
||||
}
|
||||
|
||||
QVariant BreakPointModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
const auto event = static_cast<Pica::DebugContext::Event>(index.row());
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
{
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
{
|
||||
static const std::map<Pica::DebugContext::Event, QString> map = {
|
||||
{ Pica::DebugContext::Event::CommandLoaded, tr("Pica command loaded") },
|
||||
{ Pica::DebugContext::Event::CommandProcessed, tr("Pica command processed") },
|
||||
{ Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incoming primitive batch") },
|
||||
{ Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch") },
|
||||
{ Pica::DebugContext::Event::VertexLoaded, tr("Vertex Loaded") }
|
||||
};
|
||||
|
||||
_dbg_assert_(Debug_GPU, map.size() == static_cast<size_t>(Pica::DebugContext::Event::NumEvents));
|
||||
|
||||
return (map.find(event) != map.end()) ? map.at(event) : QString();
|
||||
}
|
||||
|
||||
case 1:
|
||||
return data(index, Role_IsEnabled).toBool() ? tr("Enabled") : tr("Disabled");
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::BackgroundRole:
|
||||
{
|
||||
if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) {
|
||||
return QBrush(QColor(0xE0, 0xE0, 0x10));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Role_IsEnabled:
|
||||
{
|
||||
auto context = context_weak.lock();
|
||||
return context && context->breakpoints[event].enabled;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant BreakPointModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
switch(role) {
|
||||
case Qt::DisplayRole:
|
||||
{
|
||||
if (section == 0) {
|
||||
return tr("Event");
|
||||
} else if (section == 1) {
|
||||
return tr("Status");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
{
|
||||
const auto event = static_cast<Pica::DebugContext::Event>(index.row());
|
||||
|
||||
switch (role) {
|
||||
case Role_IsEnabled:
|
||||
{
|
||||
auto context = context_weak.lock();
|
||||
if (!context)
|
||||
return false;
|
||||
|
||||
context->breakpoints[event].enabled = value.toBool();
|
||||
QModelIndex changed_index = createIndex(index.row(), 1);
|
||||
emit dataChanged(changed_index, changed_index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void BreakPointModel::OnBreakPointHit(Pica::DebugContext::Event event)
|
||||
{
|
||||
auto context = context_weak.lock();
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
active_breakpoint = context->active_breakpoint;
|
||||
at_breakpoint = context->at_breakpoint;
|
||||
emit dataChanged(createIndex(static_cast<int>(event), 0),
|
||||
createIndex(static_cast<int>(event), 1));
|
||||
}
|
||||
|
||||
void BreakPointModel::OnResumed()
|
||||
{
|
||||
auto context = context_weak.lock();
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
at_breakpoint = context->at_breakpoint;
|
||||
emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0),
|
||||
createIndex(static_cast<int>(active_breakpoint), 1));
|
||||
active_breakpoint = context->active_breakpoint;
|
||||
}
|
||||
|
||||
|
||||
GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(std::shared_ptr<Pica::DebugContext> debug_context,
|
||||
QWidget* parent)
|
||||
: QDockWidget(tr("Pica Breakpoints"), parent),
|
||||
Pica::DebugContext::BreakPointObserver(debug_context)
|
||||
{
|
||||
setObjectName("PicaBreakPointsWidget");
|
||||
|
||||
status_text = new QLabel(tr("Emulation running"));
|
||||
resume_button = new QPushButton(tr("Resume"));
|
||||
resume_button->setEnabled(false);
|
||||
|
||||
breakpoint_model = new BreakPointModel(debug_context, this);
|
||||
breakpoint_list = new QTreeView;
|
||||
breakpoint_list->setModel(breakpoint_model);
|
||||
|
||||
toggle_breakpoint_button = new QPushButton(tr("Enable"));
|
||||
toggle_breakpoint_button->setEnabled(false);
|
||||
|
||||
qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event");
|
||||
|
||||
connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested()));
|
||||
|
||||
connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)),
|
||||
this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)),
|
||||
Qt::BlockingQueuedConnection);
|
||||
connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
|
||||
|
||||
connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)),
|
||||
breakpoint_model, SLOT(OnBreakPointHit(Pica::DebugContext::Event)),
|
||||
Qt::BlockingQueuedConnection);
|
||||
connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed()));
|
||||
|
||||
connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&,const QModelIndex&)),
|
||||
breakpoint_model, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&)));
|
||||
|
||||
connect(breakpoint_list->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
|
||||
this, SLOT(OnBreakpointSelectionChanged(QModelIndex)));
|
||||
|
||||
connect(toggle_breakpoint_button, SIGNAL(clicked()), this, SLOT(OnToggleBreakpointEnabled()));
|
||||
|
||||
QWidget* main_widget = new QWidget;
|
||||
auto main_layout = new QVBoxLayout;
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(status_text);
|
||||
sub_layout->addWidget(resume_button);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
main_layout->addWidget(breakpoint_list);
|
||||
main_layout->addWidget(toggle_breakpoint_button);
|
||||
main_widget->setLayout(main_layout);
|
||||
|
||||
setWidget(main_widget);
|
||||
}
|
||||
|
||||
void GraphicsBreakPointsWidget::OnPicaBreakPointHit(Event event, void* data)
|
||||
{
|
||||
// Process in GUI thread
|
||||
emit BreakPointHit(event, data);
|
||||
}
|
||||
|
||||
void GraphicsBreakPointsWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data)
|
||||
{
|
||||
status_text->setText(tr("Emulation halted at breakpoint"));
|
||||
resume_button->setEnabled(true);
|
||||
}
|
||||
|
||||
void GraphicsBreakPointsWidget::OnPicaResume()
|
||||
{
|
||||
// Process in GUI thread
|
||||
emit Resumed();
|
||||
}
|
||||
|
||||
void GraphicsBreakPointsWidget::OnResumed()
|
||||
{
|
||||
status_text->setText(tr("Emulation running"));
|
||||
resume_button->setEnabled(false);
|
||||
}
|
||||
|
||||
void GraphicsBreakPointsWidget::OnResumeRequested()
|
||||
{
|
||||
if (auto context = context_weak.lock())
|
||||
context->Resume();
|
||||
}
|
||||
|
||||
void GraphicsBreakPointsWidget::OnBreakpointSelectionChanged(const QModelIndex& index)
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
toggle_breakpoint_button->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
toggle_breakpoint_button->setEnabled(true);
|
||||
UpdateToggleBreakpointButton(index);
|
||||
}
|
||||
|
||||
void GraphicsBreakPointsWidget::OnToggleBreakpointEnabled()
|
||||
{
|
||||
QModelIndex index = breakpoint_list->selectionModel()->currentIndex();
|
||||
bool new_state = !(breakpoint_model->data(index, BreakPointModel::Role_IsEnabled).toBool());
|
||||
|
||||
breakpoint_model->setData(index, new_state,
|
||||
BreakPointModel::Role_IsEnabled);
|
||||
UpdateToggleBreakpointButton(index);
|
||||
}
|
||||
|
||||
void GraphicsBreakPointsWidget::UpdateToggleBreakpointButton(const QModelIndex& index)
|
||||
{
|
||||
if (true == breakpoint_model->data(index, BreakPointModel::Role_IsEnabled).toBool()) {
|
||||
toggle_breakpoint_button->setText(tr("Disable"));
|
||||
} else {
|
||||
toggle_breakpoint_button->setText(tr("Enable"));
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QDockWidget>
|
||||
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
class QTreeView;
|
||||
|
||||
class BreakPointModel;
|
||||
|
||||
class GraphicsBreakPointsWidget : public QDockWidget, Pica::DebugContext::BreakPointObserver {
|
||||
Q_OBJECT
|
||||
|
||||
using Event = Pica::DebugContext::Event;
|
||||
|
||||
public:
|
||||
GraphicsBreakPointsWidget(std::shared_ptr<Pica::DebugContext> debug_context,
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override;
|
||||
void OnPicaResume() override;
|
||||
|
||||
public slots:
|
||||
void OnBreakPointHit(Pica::DebugContext::Event event, void* data);
|
||||
void OnResumeRequested();
|
||||
void OnResumed();
|
||||
void OnBreakpointSelectionChanged(const QModelIndex&);
|
||||
void OnToggleBreakpointEnabled();
|
||||
|
||||
signals:
|
||||
void Resumed();
|
||||
void BreakPointHit(Pica::DebugContext::Event event, void* data);
|
||||
void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
private:
|
||||
void UpdateToggleBreakpointButton(const QModelIndex& index);
|
||||
|
||||
QLabel* status_text;
|
||||
QPushButton* resume_button;
|
||||
QPushButton* toggle_breakpoint_button;
|
||||
|
||||
BreakPointModel* breakpoint_model;
|
||||
QTreeView* breakpoint_list;
|
||||
};
|
@ -0,0 +1,38 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
|
||||
class BreakPointModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum {
|
||||
Role_IsEnabled = Qt::UserRole,
|
||||
};
|
||||
|
||||
BreakPointModel(std::shared_ptr<Pica::DebugContext> context, QObject* parent);
|
||||
|
||||
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
|
||||
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole);
|
||||
|
||||
public slots:
|
||||
void OnBreakPointHit(Pica::DebugContext::Event event);
|
||||
void OnResumed();
|
||||
|
||||
private:
|
||||
std::weak_ptr<Pica::DebugContext> context_weak;
|
||||
bool at_breakpoint;
|
||||
Pica::DebugContext::Event active_breakpoint;
|
||||
};
|
@ -0,0 +1,283 @@
|
||||
// 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 <QMetaType>
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
|
||||
#include "video_core/pica.h"
|
||||
|
||||
#include "graphics_framebuffer.hxx"
|
||||
|
||||
#include "util/spinbox.hxx"
|
||||
|
||||
BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context,
|
||||
const QString& title, QWidget* parent)
|
||||
: QDockWidget(title, parent), BreakPointObserver(debug_context)
|
||||
{
|
||||
qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event");
|
||||
|
||||
connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
|
||||
|
||||
// NOTE: This signal is emitted from a non-GUI thread, but connect() takes
|
||||
// care of delaying its handling to the GUI thread.
|
||||
connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)),
|
||||
this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)),
|
||||
Qt::BlockingQueuedConnection);
|
||||
}
|
||||
|
||||
void BreakPointObserverDock::OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data)
|
||||
{
|
||||
emit BreakPointHit(event, data);
|
||||
}
|
||||
|
||||
void BreakPointObserverDock::OnPicaResume()
|
||||
{
|
||||
emit Resumed();
|
||||
}
|
||||
|
||||
|
||||
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("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("RGBA5551"));
|
||||
framebuffer_format_control->addItem(tr("RGB565"));
|
||||
framebuffer_format_control->addItem(tr("RGBA4"));
|
||||
|
||||
// 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 != new_value) {
|
||||
framebuffer_width = new_value;
|
||||
|
||||
framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsFramebufferWidget::OnFramebufferHeightChanged(int new_value)
|
||||
{
|
||||
if (framebuffer_height != new_value) {
|
||||
framebuffer_height = 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...
|
||||
|
||||
auto framebuffer = Pica::registers.framebuffer;
|
||||
using Framebuffer = decltype(framebuffer);
|
||||
|
||||
framebuffer_address = framebuffer.GetColorBufferPhysicalAddress();
|
||||
framebuffer_width = framebuffer.GetWidth();
|
||||
framebuffer_height = framebuffer.GetHeight();
|
||||
framebuffer_format = static_cast<Format>(framebuffer.color_format);
|
||||
|
||||
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
|
||||
switch (framebuffer_format) {
|
||||
case Format::RGBA8:
|
||||
{
|
||||
QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32);
|
||||
u32* color_buffer = (u32*)Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address));
|
||||
for (unsigned y = 0; y < framebuffer_height; ++y) {
|
||||
for (unsigned x = 0; x < framebuffer_width; ++x) {
|
||||
u32 value = *(color_buffer + x + y * framebuffer_width);
|
||||
|
||||
decoded_image.setPixel(x, y, qRgba((value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF, 255/*value >> 24*/));
|
||||
}
|
||||
}
|
||||
pixmap = QPixmap::fromImage(decoded_image);
|
||||
break;
|
||||
}
|
||||
|
||||
case Format::RGB8:
|
||||
{
|
||||
QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32);
|
||||
u8* color_buffer = Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address));
|
||||
for (unsigned y = 0; y < framebuffer_height; ++y) {
|
||||
for (unsigned x = 0; x < framebuffer_width; ++x) {
|
||||
u8* pixel_pointer = color_buffer + x * 3 + y * 3 * framebuffer_width;
|
||||
|
||||
decoded_image.setPixel(x, y, qRgba(pixel_pointer[0], pixel_pointer[1], pixel_pointer[2], 255/*value >> 24*/));
|
||||
}
|
||||
}
|
||||
pixmap = QPixmap::fromImage(decoded_image);
|
||||
break;
|
||||
}
|
||||
|
||||
case Format::RGBA5551:
|
||||
{
|
||||
QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32);
|
||||
u32* color_buffer = (u32*)Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address));
|
||||
for (unsigned y = 0; y < framebuffer_height; ++y) {
|
||||
for (unsigned x = 0; x < framebuffer_width; ++x) {
|
||||
u16 value = *(u16*)(((u8*)color_buffer) + x * 2 + y * framebuffer_width * 2);
|
||||
u8 r = (value >> 11) & 0x1F;
|
||||
u8 g = (value >> 6) & 0x1F;
|
||||
u8 b = (value >> 1) & 0x1F;
|
||||
u8 a = value & 1;
|
||||
|
||||
decoded_image.setPixel(x, y, qRgba(r, g, b, 255/*a*/));
|
||||
}
|
||||
}
|
||||
pixmap = QPixmap::fromImage(decoded_image);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
qDebug() << "Unknown fb color format " << static_cast<int>(framebuffer_format);
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDockWidget>
|
||||
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
|
||||
class QComboBox;
|
||||
class QLabel;
|
||||
class QSpinBox;
|
||||
|
||||
class CSpinBox;
|
||||
|
||||
// Utility class which forwards calls to OnPicaBreakPointHit and OnPicaResume to public slots.
|
||||
// This is because the Pica breakpoint callbacks are called from a non-GUI thread, while
|
||||
// the widget usually wants to perform reactions in the GUI thread.
|
||||
class BreakPointObserverDock : public QDockWidget, Pica::DebugContext::BreakPointObserver {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, const QString& title,
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override;
|
||||
void OnPicaResume() override;
|
||||
|
||||
private slots:
|
||||
virtual void OnBreakPointHit(Pica::DebugContext::Event event, void* data) = 0;
|
||||
virtual void OnResumed() = 0;
|
||||
|
||||
signals:
|
||||
void Resumed();
|
||||
void BreakPointHit(Pica::DebugContext::Event event, void* data);
|
||||
};
|
||||
|
||||
class GraphicsFramebufferWidget : public BreakPointObserverDock {
|
||||
Q_OBJECT
|
||||
|
||||
using Event = Pica::DebugContext::Event;
|
||||
|
||||
enum class Source {
|
||||
PicaTarget = 0,
|
||||
Custom = 1,
|
||||
|
||||
// TODO: Add GPU framebuffer sources!
|
||||
};
|
||||
|
||||
enum class Format {
|
||||
RGBA8 = 0,
|
||||
RGB8 = 1,
|
||||
RGBA5551 = 2,
|
||||
RGB565 = 3,
|
||||
RGBA4 = 4,
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
@ -0,0 +1,303 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
||||
// Copyright 2014 Tony Wasserka
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// * Neither the name of the owner nor the names of its contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QRegExpValidator>
|
||||
|
||||
#include "common/log.h"
|
||||
|
||||
#include "spinbox.hxx"
|
||||
|
||||
CSpinBox::CSpinBox(QWidget* parent) : QAbstractSpinBox(parent), base(10), min_value(-100), max_value(100), value(0), num_digits(0)
|
||||
{
|
||||
// TODO: Might be nice to not immediately call the slot.
|
||||
// Think of an address that is being replaced by a different one, in which case a lot
|
||||
// invalid intermediate addresses would be read from during editing.
|
||||
connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(OnEditingFinished()));
|
||||
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
void CSpinBox::SetValue(qint64 val)
|
||||
{
|
||||
auto old_value = value;
|
||||
value = std::max(std::min(val, max_value), min_value);
|
||||
|
||||
if (old_value != value) {
|
||||
UpdateText();
|
||||
emit ValueChanged(value);
|
||||
}
|
||||
}
|
||||
|
||||
void CSpinBox::SetRange(qint64 min, qint64 max)
|
||||
{
|
||||
min_value = min;
|
||||
max_value = max;
|
||||
|
||||
SetValue(value);
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
void CSpinBox::stepBy(int steps)
|
||||
{
|
||||
auto new_value = value;
|
||||
// Scale number of steps by the currently selected digit
|
||||
// TODO: Move this code elsewhere and enable it.
|
||||
// TODO: Support for num_digits==0, too
|
||||
// TODO: Support base!=16, too
|
||||
// TODO: Make the cursor not jump back to the end of the line...
|
||||
/*if (base == 16 && num_digits > 0) {
|
||||
int digit = num_digits - (lineEdit()->cursorPosition() - prefix.length()) - 1;
|
||||
digit = std::max(0, std::min(digit, num_digits - 1));
|
||||
steps <<= digit * 4;
|
||||
}*/
|
||||
|
||||
// Increment "new_value" by "steps", and perform annoying overflow checks, too.
|
||||
if (steps < 0 && new_value + steps > new_value) {
|
||||
new_value = std::numeric_limits<qint64>::min();
|
||||
} else if (steps > 0 && new_value + steps < new_value) {
|
||||
new_value = std::numeric_limits<qint64>::max();
|
||||
} else {
|
||||
new_value += steps;
|
||||
}
|
||||
|
||||
SetValue(new_value);
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
QAbstractSpinBox::StepEnabled CSpinBox::stepEnabled() const
|
||||
{
|
||||
StepEnabled ret = StepNone;
|
||||
|
||||
if (value > min_value)
|
||||
ret |= StepDownEnabled;
|
||||
|
||||
if (value < max_value)
|
||||
ret |= StepUpEnabled;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CSpinBox::SetBase(int base)
|
||||
{
|
||||
this->base = base;
|
||||
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
void CSpinBox::SetNumDigits(int num_digits)
|
||||
{
|
||||
this->num_digits = num_digits;
|
||||
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
void CSpinBox::SetPrefix(const QString& prefix)
|
||||
{
|
||||
this->prefix = prefix;
|
||||
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
void CSpinBox::SetSuffix(const QString& suffix)
|
||||
{
|
||||
this->suffix = suffix;
|
||||
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
static QString StringToInputMask(const QString& input) {
|
||||
QString mask = input;
|
||||
|
||||
// ... replace any special characters by their escaped counterparts ...
|
||||
mask.replace("\\", "\\\\");
|
||||
mask.replace("A", "\\A");
|
||||
mask.replace("a", "\\a");
|
||||
mask.replace("N", "\\N");
|
||||
mask.replace("n", "\\n");
|
||||
mask.replace("X", "\\X");
|
||||
mask.replace("x", "\\x");
|
||||
mask.replace("9", "\\9");
|
||||
mask.replace("0", "\\0");
|
||||
mask.replace("D", "\\D");
|
||||
mask.replace("d", "\\d");
|
||||
mask.replace("#", "\\#");
|
||||
mask.replace("H", "\\H");
|
||||
mask.replace("h", "\\h");
|
||||
mask.replace("B", "\\B");
|
||||
mask.replace("b", "\\b");
|
||||
mask.replace(">", "\\>");
|
||||
mask.replace("<", "\\<");
|
||||
mask.replace("!", "\\!");
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
void CSpinBox::UpdateText()
|
||||
{
|
||||
// If a fixed number of digits is used, we put the line edit in insertion mode by setting an
|
||||
// input mask.
|
||||
QString mask;
|
||||
if (num_digits != 0) {
|
||||
mask += StringToInputMask(prefix);
|
||||
|
||||
// For base 10 and negative range, demand a single sign character
|
||||
if (HasSign())
|
||||
mask += "X"; // identified as "-" or "+" in the validator
|
||||
|
||||
// Uppercase digits greater than 9.
|
||||
mask += ">";
|
||||
|
||||
// The greatest signed 64-bit number has 19 decimal digits.
|
||||
// TODO: Could probably make this more generic with some logarithms.
|
||||
// For reference, unsigned 64-bit can have up to 20 decimal digits.
|
||||
int digits = (num_digits != 0) ? num_digits
|
||||
: (base == 16) ? 16
|
||||
: (base == 10) ? 19
|
||||
: 0xFF; // fallback case...
|
||||
|
||||
// Match num_digits digits
|
||||
// Digits irrelevant to the chosen number base are filtered in the validator
|
||||
mask += QString("H").repeated(std::max(num_digits, 1));
|
||||
|
||||
// Switch off case conversion
|
||||
mask += "!";
|
||||
|
||||
mask += StringToInputMask(suffix);
|
||||
}
|
||||
lineEdit()->setInputMask(mask);
|
||||
|
||||
// Set new text without changing the cursor position. This will cause the cursor to briefly
|
||||
// appear at the end of the line and then to jump back to its original position. That's
|
||||
// a bit ugly, but better than having setText() move the cursor permanently all the time.
|
||||
int cursor_position = lineEdit()->cursorPosition();
|
||||
lineEdit()->setText(TextFromValue());
|
||||
lineEdit()->setCursorPosition(cursor_position);
|
||||
}
|
||||
|
||||
QString CSpinBox::TextFromValue()
|
||||
{
|
||||
return prefix
|
||||
+ QString(HasSign() ? ((value < 0) ? "-" : "+") : "")
|
||||
+ QString("%1").arg(abs(value), num_digits, base, QLatin1Char('0')).toUpper()
|
||||
+ suffix;
|
||||
}
|
||||
|
||||
qint64 CSpinBox::ValueFromText()
|
||||
{
|
||||
unsigned strpos = prefix.length();
|
||||
|
||||
QString num_string = text().mid(strpos, text().length() - strpos - suffix.length());
|
||||
return num_string.toLongLong(nullptr, base);
|
||||
}
|
||||
|
||||
bool CSpinBox::HasSign() const
|
||||
{
|
||||
return base == 10 && min_value < 0;
|
||||
}
|
||||
|
||||
void CSpinBox::OnEditingFinished()
|
||||
{
|
||||
// Only update for valid input
|
||||
QString input = lineEdit()->text();
|
||||
int pos = 0;
|
||||
if (QValidator::Acceptable == validate(input, pos))
|
||||
SetValue(ValueFromText());
|
||||
}
|
||||
|
||||
QValidator::State CSpinBox::validate(QString& input, int& pos) const
|
||||
{
|
||||
if (!prefix.isEmpty() && input.left(prefix.length()) != prefix)
|
||||
return QValidator::Invalid;
|
||||
|
||||
int strpos = prefix.length();
|
||||
|
||||
// Empty "numbers" allowed as intermediate values
|
||||
if (strpos >= input.length() - HasSign() - suffix.length())
|
||||
return QValidator::Intermediate;
|
||||
|
||||
_dbg_assert_(Frontend, base <= 10 || base == 16);
|
||||
QString regexp;
|
||||
|
||||
// Demand sign character for negative ranges
|
||||
if (HasSign())
|
||||
regexp += "[+\\-]";
|
||||
|
||||
// Match digits corresponding to the chosen number base.
|
||||
regexp += QString("[0-%1").arg(std::min(base, 9));
|
||||
if (base == 16) {
|
||||
regexp += "a-fA-F";
|
||||
}
|
||||
regexp += "]";
|
||||
|
||||
// Specify number of digits
|
||||
if (num_digits > 0) {
|
||||
regexp += QString("{%1}").arg(num_digits);
|
||||
} else {
|
||||
regexp += "+";
|
||||
}
|
||||
|
||||
// Match string
|
||||
QRegExp num_regexp(regexp);
|
||||
int num_pos = strpos;
|
||||
QString sub_input = input.mid(strpos, input.length() - strpos - suffix.length());
|
||||
|
||||
if (!num_regexp.exactMatch(sub_input) && num_regexp.matchedLength() == 0)
|
||||
return QValidator::Invalid;
|
||||
|
||||
sub_input = sub_input.left(num_regexp.matchedLength());
|
||||
bool ok;
|
||||
qint64 val = sub_input.toLongLong(&ok, base);
|
||||
|
||||
if (!ok)
|
||||
return QValidator::Invalid;
|
||||
|
||||
// Outside boundaries => don't accept
|
||||
if (val < min_value || val > max_value)
|
||||
return QValidator::Invalid;
|
||||
|
||||
// Make sure we are actually at the end of this string...
|
||||
strpos += num_regexp.matchedLength();
|
||||
|
||||
if (!suffix.isEmpty() && input.mid(strpos) != suffix) {
|
||||
return QValidator::Invalid;
|
||||
} else {
|
||||
strpos += suffix.length();
|
||||
}
|
||||
|
||||
if (strpos != input.length())
|
||||
return QValidator::Invalid;
|
||||
|
||||
// At this point we can say for sure that the input is fine. Let's fix it up a bit though
|
||||
input.replace(num_pos, sub_input.length(), sub_input.toUpper());
|
||||
|
||||
return QValidator::Acceptable;
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
||||
// Copyright 2014 Tony Wasserka
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// * Neither the name of the owner nor the names of its contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractSpinBox>
|
||||
#include <QtGlobal>
|
||||
|
||||
class QVariant;
|
||||
|
||||
/**
|
||||
* A custom spin box widget with enhanced functionality over Qt's QSpinBox
|
||||
*/
|
||||
class CSpinBox : public QAbstractSpinBox {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CSpinBox(QWidget* parent = nullptr);
|
||||
|
||||
void stepBy(int steps) override;
|
||||
StepEnabled stepEnabled() const override;
|
||||
|
||||
void SetValue(qint64 val);
|
||||
|
||||
void SetRange(qint64 min, qint64 max);
|
||||
|
||||
void SetBase(int base);
|
||||
|
||||
void SetPrefix(const QString& prefix);
|
||||
void SetSuffix(const QString& suffix);
|
||||
|
||||
void SetNumDigits(int num_digits);
|
||||
|
||||
QValidator::State validate(QString& input, int& pos) const override;
|
||||
|
||||
signals:
|
||||
void ValueChanged(qint64 val);
|
||||
|
||||
private slots:
|
||||
void OnEditingFinished();
|
||||
|
||||
private:
|
||||
void UpdateText();
|
||||
|
||||
bool HasSign() const;
|
||||
|
||||
QString TextFromValue();
|
||||
qint64 ValueFromText();
|
||||
|
||||
qint64 min_value, max_value;
|
||||
|
||||
qint64 value;
|
||||
|
||||
QString prefix, suffix;
|
||||
|
||||
int base;
|
||||
|
||||
int num_digits;
|
||||
};
|
@ -0,0 +1,164 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "common/common.h" // for NonCopyable
|
||||
#include "common/log.h" // for _dbg_assert_
|
||||
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* A MPMC (Multiple-Producer Multiple-Consumer) concurrent ring buffer. This data structure permits
|
||||
* multiple threads to push and pop from a queue of bounded size.
|
||||
*/
|
||||
template <typename T, size_t ArraySize>
|
||||
class ConcurrentRingBuffer : private NonCopyable {
|
||||
public:
|
||||
/// Value returned by the popping functions when the queue has been closed.
|
||||
static const size_t QUEUE_CLOSED = -1;
|
||||
|
||||
ConcurrentRingBuffer() {}
|
||||
|
||||
~ConcurrentRingBuffer() {
|
||||
// If for whatever reason the queue wasn't completely drained, destroy the left over items.
|
||||
for (size_t i = reader_index, end = writer_index; i != end; i = (i + 1) % ArraySize) {
|
||||
Data()[i].~T();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a value to the queue. If the queue is full, this method will block. Does nothing if
|
||||
* the queue is closed.
|
||||
*/
|
||||
void Push(T val) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the buffer is full, wait
|
||||
writer.wait(lock, [&]{
|
||||
return (writer_index + 1) % ArraySize != reader_index;
|
||||
});
|
||||
|
||||
T* item = &Data()[writer_index];
|
||||
new (item) T(std::move(val));
|
||||
|
||||
writer_index = (writer_index + 1) % ArraySize;
|
||||
|
||||
// Wake up waiting readers
|
||||
lock.unlock();
|
||||
reader.notify_one();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops up to `dest_len` items from the queue, storing them in `dest`. This function will not
|
||||
* block, and might return 0 values if there are no elements in the queue when it is called.
|
||||
*
|
||||
* @return The number of elements stored in `dest`. If the queue has been closed, returns
|
||||
* `QUEUE_CLOSED`.
|
||||
*/
|
||||
size_t Pop(T* dest, size_t dest_len) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
if (closed && !CanRead()) {
|
||||
return QUEUE_CLOSED;
|
||||
}
|
||||
return PopInternal(dest, dest_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops up to `dest_len` items from the queue, storing them in `dest`. This function will block
|
||||
* if there are no elements in the queue when it is called.
|
||||
*
|
||||
* @return The number of elements stored in `dest`. If the queue has been closed, returns
|
||||
* `QUEUE_CLOSED`.
|
||||
*/
|
||||
size_t BlockingPop(T* dest, size_t dest_len) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
if (closed && !CanRead()) {
|
||||
return QUEUE_CLOSED;
|
||||
}
|
||||
|
||||
while (!CanRead()) {
|
||||
reader.wait(lock);
|
||||
if (closed && !CanRead()) {
|
||||
return QUEUE_CLOSED;
|
||||
}
|
||||
}
|
||||
_dbg_assert_(Common, CanRead());
|
||||
return PopInternal(dest, dest_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the queue. After calling this method, `Push` operations won't have any effect, and
|
||||
* `PopMany` and `PopManyBlock` will start returning `QUEUE_CLOSED`. This is intended to allow
|
||||
* a graceful shutdown of all consumers.
|
||||
*/
|
||||
void Close() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
closed = true;
|
||||
// We need to wake up any reader that are waiting for an item that will never come.
|
||||
lock.unlock();
|
||||
reader.notify_all();
|
||||
}
|
||||
|
||||
/// Returns true if `Close()` has been called.
|
||||
bool IsClosed() const {
|
||||
return closed;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t PopInternal(T* dest, size_t dest_len) {
|
||||
size_t output_count = 0;
|
||||
while (output_count < dest_len && CanRead()) {
|
||||
_dbg_assert_(Common, CanRead());
|
||||
|
||||
T* item = &Data()[reader_index];
|
||||
T out_val = std::move(*item);
|
||||
item->~T();
|
||||
|
||||
size_t prev_index = (reader_index + ArraySize - 1) % ArraySize;
|
||||
reader_index = (reader_index + 1) % ArraySize;
|
||||
if (writer_index == prev_index) {
|
||||
writer.notify_one();
|
||||
}
|
||||
dest[output_count++] = std::move(out_val);
|
||||
}
|
||||
return output_count;
|
||||
}
|
||||
|
||||
bool CanRead() const {
|
||||
return reader_index != writer_index;
|
||||
}
|
||||
|
||||
T* Data() {
|
||||
return static_cast<T*>(static_cast<void*>(&storage));
|
||||
}
|
||||
|
||||
/// Storage for entries
|
||||
typename std::aligned_storage<ArraySize * sizeof(T),
|
||||
std::alignment_of<T>::value>::type storage;
|
||||
|
||||
/// Data is valid in the half-open interval [reader, writer). If they are `QUEUE_CLOSED` then the
|
||||
/// queue has been closed.
|
||||
size_t writer_index = 0, reader_index = 0;
|
||||
// True if the queue has been closed.
|
||||
bool closed = false;
|
||||
|
||||
/// Mutex that protects the entire data structure.
|
||||
std::mutex mutex;
|
||||
/// Signaling wakes up reader which is waiting for storage to be non-empty.
|
||||
std::condition_variable reader;
|
||||
/// Signaling wakes up writer which is waiting for storage to be non-full.
|
||||
std::condition_variable writer;
|
||||
};
|
||||
|
||||
} // namespace
|
@ -1,319 +0,0 @@
|
||||
// Copyright 2013 Dolphin Emulator Project
|
||||
// Licensed under GPLv2
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <array>
|
||||
#endif
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/log_manager.h" // Common
|
||||
#include "common/console_listener.h" // Common
|
||||
|
||||
ConsoleListener::ConsoleListener()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
hConsole = NULL;
|
||||
bUseColor = true;
|
||||
#else
|
||||
bUseColor = isatty(fileno(stdout));
|
||||
#endif
|
||||
}
|
||||
|
||||
ConsoleListener::~ConsoleListener()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
// 100, 100, "Dolphin Log Console"
|
||||
// Open console window - width and height is the size of console window
|
||||
// Name is the window title
|
||||
void ConsoleListener::Open(bool Hidden, int Width, int Height, const char *Title)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!GetConsoleWindow())
|
||||
{
|
||||
// Open the console window and create the window handle for GetStdHandle()
|
||||
AllocConsole();
|
||||
// Hide
|
||||
if (Hidden) ShowWindow(GetConsoleWindow(), SW_HIDE);
|
||||
// Save the window handle that AllocConsole() created
|
||||
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
// Set the console window title
|
||||
SetConsoleTitle(Common::UTF8ToTStr(Title).c_str());
|
||||
// Set letter space
|
||||
LetterSpace(80, 4000);
|
||||
//MoveWindow(GetConsoleWindow(), 200,200, 800,800, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ConsoleListener::UpdateHandle()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Close the console window and close the eventual file handle
|
||||
void ConsoleListener::Close()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (hConsole == NULL)
|
||||
return;
|
||||
FreeConsole();
|
||||
hConsole = NULL;
|
||||
#else
|
||||
fflush(NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ConsoleListener::IsOpen()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return (hConsole != NULL);
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
LetterSpace: SetConsoleScreenBufferSize and SetConsoleWindowInfo are
|
||||
dependent on each other, that's the reason for the additional checks.
|
||||
*/
|
||||
void ConsoleListener::BufferWidthHeight(int BufferWidth, int BufferHeight, int ScreenWidth, int ScreenHeight, bool BufferFirst)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
BOOL SB, SW;
|
||||
if (BufferFirst)
|
||||
{
|
||||
// Change screen buffer size
|
||||
COORD Co = {BufferWidth, BufferHeight};
|
||||
SB = SetConsoleScreenBufferSize(hConsole, Co);
|
||||
// Change the screen buffer window size
|
||||
SMALL_RECT coo = {0,0,ScreenWidth, ScreenHeight}; // top, left, right, bottom
|
||||
SW = SetConsoleWindowInfo(hConsole, TRUE, &coo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Change the screen buffer window size
|
||||
SMALL_RECT coo = {0,0, ScreenWidth, ScreenHeight}; // top, left, right, bottom
|
||||
SW = SetConsoleWindowInfo(hConsole, TRUE, &coo);
|
||||
// Change screen buffer size
|
||||
COORD Co = {BufferWidth, BufferHeight};
|
||||
SB = SetConsoleScreenBufferSize(hConsole, Co);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void ConsoleListener::LetterSpace(int Width, int Height)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Get console info
|
||||
CONSOLE_SCREEN_BUFFER_INFO ConInfo;
|
||||
GetConsoleScreenBufferInfo(hConsole, &ConInfo);
|
||||
|
||||
//
|
||||
int OldBufferWidth = ConInfo.dwSize.X;
|
||||
int OldBufferHeight = ConInfo.dwSize.Y;
|
||||
int OldScreenWidth = (ConInfo.srWindow.Right - ConInfo.srWindow.Left);
|
||||
int OldScreenHeight = (ConInfo.srWindow.Bottom - ConInfo.srWindow.Top);
|
||||
//
|
||||
int NewBufferWidth = Width;
|
||||
int NewBufferHeight = Height;
|
||||
int NewScreenWidth = NewBufferWidth - 1;
|
||||
int NewScreenHeight = OldScreenHeight;
|
||||
|
||||
// Width
|
||||
BufferWidthHeight(NewBufferWidth, OldBufferHeight, NewScreenWidth, OldScreenHeight, (NewBufferWidth > OldScreenWidth-1));
|
||||
// Height
|
||||
BufferWidthHeight(NewBufferWidth, NewBufferHeight, NewScreenWidth, NewScreenHeight, (NewBufferHeight > OldScreenHeight-1));
|
||||
|
||||
// Resize the window too
|
||||
//MoveWindow(GetConsoleWindow(), 200,200, (Width*8 + 50),(NewScreenHeight*12 + 200), true);
|
||||
#endif
|
||||
}
|
||||
#ifdef _WIN32
|
||||
COORD ConsoleListener::GetCoordinates(int BytesRead, int BufferWidth)
|
||||
{
|
||||
COORD Ret = {0, 0};
|
||||
// Full rows
|
||||
int Step = (int)floor((float)BytesRead / (float)BufferWidth);
|
||||
Ret.Y += Step;
|
||||
// Partial row
|
||||
Ret.X = BytesRead - (BufferWidth * Step);
|
||||
return Ret;
|
||||
}
|
||||
#endif
|
||||
void ConsoleListener::PixelSpace(int Left, int Top, int Width, int Height, bool Resize)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Check size
|
||||
if (Width < 8 || Height < 12) return;
|
||||
|
||||
bool DBef = true;
|
||||
bool DAft = true;
|
||||
std::string SLog = "";
|
||||
|
||||
const HWND hWnd = GetConsoleWindow();
|
||||
const HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
|
||||
// Get console info
|
||||
CONSOLE_SCREEN_BUFFER_INFO ConInfo;
|
||||
GetConsoleScreenBufferInfo(hConsole, &ConInfo);
|
||||
DWORD BufferSize = ConInfo.dwSize.X * ConInfo.dwSize.Y;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Save the current text
|
||||
// ------------------------
|
||||
DWORD cCharsRead = 0;
|
||||
COORD coordScreen = { 0, 0 };
|
||||
|
||||
static const int MAX_BYTES = 1024 * 16;
|
||||
|
||||
std::vector<std::array<TCHAR, MAX_BYTES>> Str;
|
||||
std::vector<std::array<WORD, MAX_BYTES>> Attr;
|
||||
|
||||
// ReadConsoleOutputAttribute seems to have a limit at this level
|
||||
static const int ReadBufferSize = MAX_BYTES - 32;
|
||||
|
||||
DWORD cAttrRead = ReadBufferSize;
|
||||
DWORD BytesRead = 0;
|
||||
while (BytesRead < BufferSize)
|
||||
{
|
||||
Str.resize(Str.size() + 1);
|
||||
if (!ReadConsoleOutputCharacter(hConsole, Str.back().data(), ReadBufferSize, coordScreen, &cCharsRead))
|
||||
SLog += Common::StringFromFormat("WriteConsoleOutputCharacter error");
|
||||
|
||||
Attr.resize(Attr.size() + 1);
|
||||
if (!ReadConsoleOutputAttribute(hConsole, Attr.back().data(), ReadBufferSize, coordScreen, &cAttrRead))
|
||||
SLog += Common::StringFromFormat("WriteConsoleOutputAttribute error");
|
||||
|
||||
// Break on error
|
||||
if (cAttrRead == 0) break;
|
||||
BytesRead += cAttrRead;
|
||||
coordScreen = GetCoordinates(BytesRead, ConInfo.dwSize.X);
|
||||
}
|
||||
// Letter space
|
||||
int LWidth = (int)(floor((float)Width / 8.0f) - 1.0f);
|
||||
int LHeight = (int)(floor((float)Height / 12.0f) - 1.0f);
|
||||
int LBufWidth = LWidth + 1;
|
||||
int LBufHeight = (int)floor((float)BufferSize / (float)LBufWidth);
|
||||
// Change screen buffer size
|
||||
LetterSpace(LBufWidth, LBufHeight);
|
||||
|
||||
|
||||
ClearScreen(true);
|
||||
coordScreen.Y = 0;
|
||||
coordScreen.X = 0;
|
||||
DWORD cCharsWritten = 0;
|
||||
|
||||
int BytesWritten = 0;
|
||||
DWORD cAttrWritten = 0;
|
||||
for (size_t i = 0; i < Attr.size(); i++)
|
||||
{
|
||||
if (!WriteConsoleOutputCharacter(hConsole, Str[i].data(), ReadBufferSize, coordScreen, &cCharsWritten))
|
||||
SLog += Common::StringFromFormat("WriteConsoleOutputCharacter error");
|
||||
if (!WriteConsoleOutputAttribute(hConsole, Attr[i].data(), ReadBufferSize, coordScreen, &cAttrWritten))
|
||||
SLog += Common::StringFromFormat("WriteConsoleOutputAttribute error");
|
||||
|
||||
BytesWritten += cAttrWritten;
|
||||
coordScreen = GetCoordinates(BytesWritten, LBufWidth);
|
||||
}
|
||||
|
||||
const int OldCursor = ConInfo.dwCursorPosition.Y * ConInfo.dwSize.X + ConInfo.dwCursorPosition.X;
|
||||
COORD Coo = GetCoordinates(OldCursor, LBufWidth);
|
||||
SetConsoleCursorPosition(hConsole, Coo);
|
||||
|
||||
if (SLog.length() > 0) Log(LogTypes::LNOTICE, SLog.c_str());
|
||||
|
||||
// Resize the window too
|
||||
if (Resize) MoveWindow(GetConsoleWindow(), Left,Top, (Width + 100),Height, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ConsoleListener::Log(LogTypes::LOG_LEVELS Level, const char *Text)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
WORD Color;
|
||||
|
||||
switch (Level)
|
||||
{
|
||||
case OS_LEVEL: // light yellow
|
||||
Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
|
||||
break;
|
||||
case NOTICE_LEVEL: // light green
|
||||
Color = FOREGROUND_GREEN | FOREGROUND_INTENSITY;
|
||||
break;
|
||||
case ERROR_LEVEL: // light red
|
||||
Color = FOREGROUND_RED | FOREGROUND_INTENSITY;
|
||||
break;
|
||||
case WARNING_LEVEL: // light purple
|
||||
Color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
|
||||
break;
|
||||
case INFO_LEVEL: // cyan
|
||||
Color = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
|
||||
break;
|
||||
case DEBUG_LEVEL: // gray
|
||||
Color = FOREGROUND_INTENSITY;
|
||||
break;
|
||||
default: // off-white
|
||||
Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
|
||||
break;
|
||||
}
|
||||
SetConsoleTextAttribute(hConsole, Color);
|
||||
printf(Text);
|
||||
#else
|
||||
char ColorAttr[16] = "";
|
||||
char ResetAttr[16] = "";
|
||||
|
||||
if (bUseColor)
|
||||
{
|
||||
strcpy(ResetAttr, "\033[0m");
|
||||
switch (Level)
|
||||
{
|
||||
case NOTICE_LEVEL: // light green
|
||||
strcpy(ColorAttr, "\033[92m");
|
||||
break;
|
||||
case ERROR_LEVEL: // light red
|
||||
strcpy(ColorAttr, "\033[91m");
|
||||
break;
|
||||
case WARNING_LEVEL: // light yellow
|
||||
strcpy(ColorAttr, "\033[93m");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "%s%s%s", ColorAttr, Text, ResetAttr);
|
||||
#endif
|
||||
}
|
||||
// Clear console screen
|
||||
void ConsoleListener::ClearScreen(bool Cursor)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
COORD coordScreen = { 0, 0 };
|
||||
DWORD cCharsWritten;
|
||||
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
||||
DWORD dwConSize;
|
||||
|
||||
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
|
||||
GetConsoleScreenBufferInfo(hConsole, &csbi);
|
||||
dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
|
||||
// Write space to the entire console
|
||||
FillConsoleOutputCharacter(hConsole, TEXT(' '), dwConSize, coordScreen, &cCharsWritten);
|
||||
GetConsoleScreenBufferInfo(hConsole, &csbi);
|
||||
FillConsoleOutputAttribute(hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten);
|
||||
// Reset cursor
|
||||
if (Cursor) SetConsoleCursorPosition(hConsole, coordScreen);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -1,38 +0,0 @@
|
||||
// Copyright 2013 Dolphin Emulator Project
|
||||
// Licensed under GPLv2
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/log_manager.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
class ConsoleListener : public LogListener
|
||||
{
|
||||
public:
|
||||
ConsoleListener();
|
||||
~ConsoleListener();
|
||||
|
||||
void Open(bool Hidden = false, int Width = 100, int Height = 100, const char * Name = "Console");
|
||||
void UpdateHandle();
|
||||
void Close();
|
||||
bool IsOpen();
|
||||
void LetterSpace(int Width, int Height);
|
||||
void BufferWidthHeight(int BufferWidth, int BufferHeight, int ScreenWidth, int ScreenHeight, bool BufferFirst);
|
||||
void PixelSpace(int Left, int Top, int Width, int Height, bool);
|
||||
#ifdef _WIN32
|
||||
COORD GetCoordinates(int BytesRead, int BufferWidth);
|
||||
#endif
|
||||
void Log(LogTypes::LOG_LEVELS, const char *Text) override;
|
||||
void ClearScreen(bool Cursor = true);
|
||||
|
||||
private:
|
||||
#ifdef _WIN32
|
||||
HWND GetHwnd(void);
|
||||
HANDLE hConsole;
|
||||
#endif
|
||||
bool bUseColor;
|
||||
};
|
@ -1,199 +0,0 @@
|
||||
// Copyright 2013 Dolphin Emulator Project
|
||||
// Licensed under GPLv2
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/log_manager.h"
|
||||
#include "common/console_listener.h"
|
||||
#include "common/timer.h"
|
||||
|
||||
void GenericLog(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file, int line,
|
||||
const char* function, const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
if (LogManager::GetInstance()) {
|
||||
LogManager::GetInstance()->Log(level, type,
|
||||
file, line, function, fmt, args);
|
||||
}
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
LogManager *LogManager::m_logManager = NULL;
|
||||
|
||||
LogManager::LogManager()
|
||||
{
|
||||
// create log files
|
||||
m_Log[LogTypes::MASTER_LOG] = new LogContainer("*", "Master Log");
|
||||
m_Log[LogTypes::BOOT] = new LogContainer("BOOT", "Boot");
|
||||
m_Log[LogTypes::COMMON] = new LogContainer("COMMON", "Common");
|
||||
m_Log[LogTypes::CONFIG] = new LogContainer("CONFIG", "Configuration");
|
||||
m_Log[LogTypes::DISCIO] = new LogContainer("DIO", "Disc IO");
|
||||
m_Log[LogTypes::FILEMON] = new LogContainer("FileMon", "File Monitor");
|
||||
m_Log[LogTypes::PAD] = new LogContainer("PAD", "Pad");
|
||||
m_Log[LogTypes::PIXELENGINE] = new LogContainer("PE", "PixelEngine");
|
||||
m_Log[LogTypes::COMMANDPROCESSOR] = new LogContainer("CP", "CommandProc");
|
||||
m_Log[LogTypes::VIDEOINTERFACE] = new LogContainer("VI", "VideoInt");
|
||||
m_Log[LogTypes::SERIALINTERFACE] = new LogContainer("SI", "SerialInt");
|
||||
m_Log[LogTypes::PROCESSORINTERFACE] = new LogContainer("PI", "ProcessorInt");
|
||||
m_Log[LogTypes::MEMMAP] = new LogContainer("MI", "MI & memmap");
|
||||
m_Log[LogTypes::SP1] = new LogContainer("SP1", "Serial Port 1");
|
||||
m_Log[LogTypes::STREAMINGINTERFACE] = new LogContainer("Stream", "StreamingInt");
|
||||
m_Log[LogTypes::DSPINTERFACE] = new LogContainer("DSP", "DSPInterface");
|
||||
m_Log[LogTypes::DVDINTERFACE] = new LogContainer("DVD", "DVDInterface");
|
||||
m_Log[LogTypes::GSP] = new LogContainer("GSP", "GSP");
|
||||
m_Log[LogTypes::EXPANSIONINTERFACE] = new LogContainer("EXI", "ExpansionInt");
|
||||
m_Log[LogTypes::GDB_STUB] = new LogContainer("GDB_STUB", "GDB Stub");
|
||||
m_Log[LogTypes::AUDIO_INTERFACE] = new LogContainer("AI", "AudioInt");
|
||||
m_Log[LogTypes::ARM11] = new LogContainer("ARM11", "ARM11");
|
||||
m_Log[LogTypes::OSHLE] = new LogContainer("HLE", "HLE");
|
||||
m_Log[LogTypes::DSPHLE] = new LogContainer("DSPHLE", "DSP HLE");
|
||||
m_Log[LogTypes::DSPLLE] = new LogContainer("DSPLLE", "DSP LLE");
|
||||
m_Log[LogTypes::DSP_MAIL] = new LogContainer("DSPMails", "DSP Mails");
|
||||
m_Log[LogTypes::VIDEO] = new LogContainer("Video", "Video Backend");
|
||||
m_Log[LogTypes::AUDIO] = new LogContainer("Audio", "Audio Emulator");
|
||||
m_Log[LogTypes::DYNA_REC] = new LogContainer("JIT", "JIT");
|
||||
m_Log[LogTypes::CONSOLE] = new LogContainer("CONSOLE", "Dolphin Console");
|
||||
m_Log[LogTypes::OSREPORT] = new LogContainer("OSREPORT", "OSReport");
|
||||
m_Log[LogTypes::TIME] = new LogContainer("Time", "Core Timing");
|
||||
m_Log[LogTypes::LOADER] = new LogContainer("Loader", "Loader");
|
||||
m_Log[LogTypes::FILESYS] = new LogContainer("FileSys", "File System");
|
||||
m_Log[LogTypes::WII_IPC_HID] = new LogContainer("WII_IPC_HID", "WII IPC HID");
|
||||
m_Log[LogTypes::KERNEL] = new LogContainer("KERNEL", "KERNEL HLE");
|
||||
m_Log[LogTypes::WII_IPC_DVD] = new LogContainer("WII_IPC_DVD", "WII IPC DVD");
|
||||
m_Log[LogTypes::WII_IPC_ES] = new LogContainer("WII_IPC_ES", "WII IPC ES");
|
||||
m_Log[LogTypes::WII_IPC_FILEIO] = new LogContainer("WII_IPC_FILEIO", "WII IPC FILEIO");
|
||||
m_Log[LogTypes::RENDER] = new LogContainer("RENDER", "RENDER");
|
||||
m_Log[LogTypes::GPU] = new LogContainer("GPU", "GPU");
|
||||
m_Log[LogTypes::SVC] = new LogContainer("SVC", "Supervisor Call HLE");
|
||||
m_Log[LogTypes::NDMA] = new LogContainer("NDMA", "NDMA");
|
||||
m_Log[LogTypes::HLE] = new LogContainer("HLE", "High Level Emulation");
|
||||
m_Log[LogTypes::HW] = new LogContainer("HW", "Hardware");
|
||||
m_Log[LogTypes::ACTIONREPLAY] = new LogContainer("ActionReplay", "ActionReplay");
|
||||
m_Log[LogTypes::MEMCARD_MANAGER] = new LogContainer("MemCard Manager", "MemCard Manager");
|
||||
m_Log[LogTypes::NETPLAY] = new LogContainer("NETPLAY", "Netplay");
|
||||
m_Log[LogTypes::GUI] = new LogContainer("GUI", "GUI");
|
||||
|
||||
m_fileLog = new FileLogListener(FileUtil::GetUserPath(F_MAINLOG_IDX).c_str());
|
||||
m_consoleLog = new ConsoleListener();
|
||||
m_debuggerLog = new DebuggerLogListener();
|
||||
|
||||
for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i)
|
||||
{
|
||||
m_Log[i]->SetEnable(true);
|
||||
m_Log[i]->AddListener(m_fileLog);
|
||||
m_Log[i]->AddListener(m_consoleLog);
|
||||
#ifdef _MSC_VER
|
||||
if (IsDebuggerPresent())
|
||||
m_Log[i]->AddListener(m_debuggerLog);
|
||||
#endif
|
||||
}
|
||||
|
||||
m_consoleLog->Open();
|
||||
}
|
||||
|
||||
LogManager::~LogManager()
|
||||
{
|
||||
for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i)
|
||||
{
|
||||
m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_fileLog);
|
||||
m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_consoleLog);
|
||||
m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_debuggerLog);
|
||||
}
|
||||
|
||||
for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i)
|
||||
delete m_Log[i];
|
||||
|
||||
delete m_fileLog;
|
||||
delete m_consoleLog;
|
||||
delete m_debuggerLog;
|
||||
}
|
||||
|
||||
void LogManager::Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file,
|
||||
int line, const char* function, const char *fmt, va_list args)
|
||||
{
|
||||
char temp[MAX_MSGLEN];
|
||||
char msg[MAX_MSGLEN * 2];
|
||||
LogContainer *log = m_Log[type];
|
||||
|
||||
if (!log->IsEnabled() || level > log->GetLevel() || ! log->HasListeners())
|
||||
return;
|
||||
|
||||
Common::CharArrayFromFormatV(temp, MAX_MSGLEN, fmt, args);
|
||||
|
||||
static const char level_to_char[7] = "ONEWID";
|
||||
sprintf(msg, "%s %s:%u %c[%s] %s: %s\n", Common::Timer::GetTimeFormatted().c_str(), file, line,
|
||||
level_to_char[(int)level], log->GetShortName(), function, temp);
|
||||
|
||||
#ifdef ANDROID
|
||||
Host_SysMessage(msg);
|
||||
#endif
|
||||
log->Trigger(level, msg);
|
||||
}
|
||||
|
||||
void LogManager::Init()
|
||||
{
|
||||
m_logManager = new LogManager();
|
||||
}
|
||||
|
||||
void LogManager::Shutdown()
|
||||
{
|
||||
delete m_logManager;
|
||||
m_logManager = NULL;
|
||||
}
|
||||
|
||||
LogContainer::LogContainer(const char* shortName, const char* fullName, bool enable)
|
||||
: m_enable(enable)
|
||||
{
|
||||
strncpy(m_fullName, fullName, 128);
|
||||
strncpy(m_shortName, shortName, 32);
|
||||
m_level = LogTypes::MAX_LOGLEVEL;
|
||||
}
|
||||
|
||||
// LogContainer
|
||||
void LogContainer::AddListener(LogListener *listener)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_listeners_lock);
|
||||
m_listeners.insert(listener);
|
||||
}
|
||||
|
||||
void LogContainer::RemoveListener(LogListener *listener)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_listeners_lock);
|
||||
m_listeners.erase(listener);
|
||||
}
|
||||
|
||||
void LogContainer::Trigger(LogTypes::LOG_LEVELS level, const char *msg)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_listeners_lock);
|
||||
|
||||
std::set<LogListener*>::const_iterator i;
|
||||
for (i = m_listeners.begin(); i != m_listeners.end(); ++i)
|
||||
{
|
||||
(*i)->Log(level, msg);
|
||||
}
|
||||
}
|
||||
|
||||
FileLogListener::FileLogListener(const char *filename)
|
||||
{
|
||||
OpenFStream(m_logfile, filename, std::ios::app);
|
||||
SetEnable(true);
|
||||
}
|
||||
|
||||
void FileLogListener::Log(LogTypes::LOG_LEVELS, const char *msg)
|
||||
{
|
||||
if (!IsEnabled() || !IsValid())
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> lk(m_log_lock);
|
||||
m_logfile << msg << std::flush;
|
||||
}
|
||||
|
||||
void DebuggerLogListener::Log(LogTypes::LOG_LEVELS, const char *msg)
|
||||
{
|
||||
#if _MSC_VER
|
||||
::OutputDebugStringA(msg);
|
||||
#endif
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
// Copyright 2013 Dolphin Emulator Project
|
||||
// Licensed under GPLv2
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/file_util.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <set>
|
||||
#include <mutex>
|
||||
|
||||
#define MAX_MESSAGES 8000
|
||||
#define MAX_MSGLEN 1024
|
||||
|
||||
|
||||
// pure virtual interface
|
||||
class LogListener
|
||||
{
|
||||
public:
|
||||
virtual ~LogListener() {}
|
||||
|
||||
virtual void Log(LogTypes::LOG_LEVELS, const char *msg) = 0;
|
||||
};
|
||||
|
||||
class FileLogListener : public LogListener
|
||||
{
|
||||
public:
|
||||
FileLogListener(const char *filename);
|
||||
|
||||
void Log(LogTypes::LOG_LEVELS, const char *msg) override;
|
||||
|
||||
bool IsValid() { return !m_logfile.fail(); }
|
||||
bool IsEnabled() const { return m_enable; }
|
||||
void SetEnable(bool enable) { m_enable = enable; }
|
||||
|
||||
const char* GetName() const { return "file"; }
|
||||
|
||||
private:
|
||||
std::mutex m_log_lock;
|
||||
std::ofstream m_logfile;
|
||||
bool m_enable;
|
||||
};
|
||||
|
||||
class DebuggerLogListener : public LogListener
|
||||
{
|
||||
public:
|
||||
void Log(LogTypes::LOG_LEVELS, const char *msg) override;
|
||||
};
|
||||
|
||||
class LogContainer
|
||||
{
|
||||
public:
|
||||
LogContainer(const char* shortName, const char* fullName, bool enable = false);
|
||||
|
||||
const char* GetShortName() const { return m_shortName; }
|
||||
const char* GetFullName() const { return m_fullName; }
|
||||
|
||||
void AddListener(LogListener* listener);
|
||||
void RemoveListener(LogListener* listener);
|
||||
|
||||
void Trigger(LogTypes::LOG_LEVELS, const char *msg);
|
||||
|
||||
bool IsEnabled() const { return m_enable; }
|
||||
void SetEnable(bool enable) { m_enable = enable; }
|
||||
|
||||
LogTypes::LOG_LEVELS GetLevel() const { return m_level; }
|
||||
|
||||
void SetLevel(LogTypes::LOG_LEVELS level) { m_level = level; }
|
||||
|
||||
bool HasListeners() const { return !m_listeners.empty(); }
|
||||
|
||||
private:
|
||||
char m_fullName[128];
|
||||
char m_shortName[32];
|
||||
bool m_enable;
|
||||
LogTypes::LOG_LEVELS m_level;
|
||||
std::mutex m_listeners_lock;
|
||||
std::set<LogListener*> m_listeners;
|
||||
};
|
||||
|
||||
class ConsoleListener;
|
||||
|
||||
class LogManager : NonCopyable
|
||||
{
|
||||
private:
|
||||
LogContainer* m_Log[LogTypes::NUMBER_OF_LOGS];
|
||||
FileLogListener *m_fileLog;
|
||||
ConsoleListener *m_consoleLog;
|
||||
DebuggerLogListener *m_debuggerLog;
|
||||
static LogManager *m_logManager; // Singleton. Ugh.
|
||||
|
||||
LogManager();
|
||||
~LogManager();
|
||||
public:
|
||||
|
||||
static u32 GetMaxLevel() { return LogTypes::MAX_LOGLEVEL; }
|
||||
|
||||
void Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file, int line,
|
||||
const char* function, const char *fmt, va_list args);
|
||||
|
||||
void SetLogLevel(LogTypes::LOG_TYPE type, LogTypes::LOG_LEVELS level)
|
||||
{
|
||||
m_Log[type]->SetLevel(level);
|
||||
}
|
||||
|
||||
void SetEnable(LogTypes::LOG_TYPE type, bool enable)
|
||||
{
|
||||
m_Log[type]->SetEnable(enable);
|
||||
}
|
||||
|
||||
bool IsEnabled(LogTypes::LOG_TYPE type) const
|
||||
{
|
||||
return m_Log[type]->IsEnabled();
|
||||
}
|
||||
|
||||
const char* GetShortName(LogTypes::LOG_TYPE type) const
|
||||
{
|
||||
return m_Log[type]->GetShortName();
|
||||
}
|
||||
|
||||
const char* GetFullName(LogTypes::LOG_TYPE type) const
|
||||
{
|
||||
return m_Log[type]->GetFullName();
|
||||
}
|
||||
|
||||
void AddListener(LogTypes::LOG_TYPE type, LogListener *listener)
|
||||
{
|
||||
m_Log[type]->AddListener(listener);
|
||||
}
|
||||
|
||||
void RemoveListener(LogTypes::LOG_TYPE type, LogListener *listener)
|
||||
{
|
||||
m_Log[type]->RemoveListener(listener);
|
||||
}
|
||||
|
||||
FileLogListener *GetFileListener() const
|
||||
{
|
||||
return m_fileLog;
|
||||
}
|
||||
|
||||
ConsoleListener *GetConsoleListener() const
|
||||
{
|
||||
return m_consoleLog;
|
||||
}
|
||||
|
||||
DebuggerLogListener *GetDebuggerListener() const
|
||||
{
|
||||
return m_debuggerLog;
|
||||
}
|
||||
|
||||
static LogManager* GetInstance()
|
||||
{
|
||||
return m_logManager;
|
||||
}
|
||||
|
||||
static void SetInstance(LogManager *logManager)
|
||||
{
|
||||
m_logManager = logManager;
|
||||
}
|
||||
|
||||
static void Init();
|
||||
static void Shutdown();
|
||||
};
|
@ -0,0 +1,151 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/log.h" // For _dbg_assert_
|
||||
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging/text_formatter.h"
|
||||
|
||||
namespace Log {
|
||||
|
||||
static std::shared_ptr<Logger> global_logger;
|
||||
|
||||
/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
|
||||
#define ALL_LOG_CLASSES() \
|
||||
CLS(Log) \
|
||||
CLS(Common) \
|
||||
SUB(Common, Filesystem) \
|
||||
SUB(Common, Memory) \
|
||||
CLS(Core) \
|
||||
SUB(Core, ARM11) \
|
||||
CLS(Config) \
|
||||
CLS(Debug) \
|
||||
SUB(Debug, Emulated) \
|
||||
SUB(Debug, GPU) \
|
||||
SUB(Debug, Breakpoint) \
|
||||
CLS(Kernel) \
|
||||
SUB(Kernel, SVC) \
|
||||
CLS(Service) \
|
||||
SUB(Service, SRV) \
|
||||
SUB(Service, FS) \
|
||||
SUB(Service, APT) \
|
||||
SUB(Service, GSP) \
|
||||
SUB(Service, AC) \
|
||||
SUB(Service, PTM) \
|
||||
SUB(Service, CFG) \
|
||||
SUB(Service, DSP) \
|
||||
SUB(Service, HID) \
|
||||
CLS(HW) \
|
||||
SUB(HW, Memory) \
|
||||
SUB(HW, GPU) \
|
||||
CLS(Frontend) \
|
||||
CLS(Render) \
|
||||
SUB(Render, Software) \
|
||||
SUB(Render, OpenGL) \
|
||||
CLS(Loader)
|
||||
|
||||
Logger::Logger() {
|
||||
// Register logging classes so that they can be queried at runtime
|
||||
size_t parent_class;
|
||||
all_classes.reserve((size_t)Class::Count);
|
||||
|
||||
#define CLS(x) \
|
||||
all_classes.push_back(Class::x); \
|
||||
parent_class = all_classes.size() - 1;
|
||||
#define SUB(x, y) \
|
||||
all_classes.push_back(Class::x##_##y); \
|
||||
all_classes[parent_class].num_children += 1;
|
||||
|
||||
ALL_LOG_CLASSES()
|
||||
#undef CLS
|
||||
#undef SUB
|
||||
|
||||
// Ensures that ALL_LOG_CLASSES isn't missing any entries.
|
||||
_dbg_assert_(Log, all_classes.size() == (size_t)Class::Count);
|
||||
}
|
||||
|
||||
// GetClassName is a macro defined by Windows.h, grrr...
|
||||
const char* Logger::GetLogClassName(Class log_class) {
|
||||
switch (log_class) {
|
||||
#define CLS(x) case Class::x: return #x;
|
||||
#define SUB(x, y) case Class::x##_##y: return #x "." #y;
|
||||
ALL_LOG_CLASSES()
|
||||
#undef CLS
|
||||
#undef SUB
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
const char* Logger::GetLevelName(Level log_level) {
|
||||
#define LVL(x) case Level::x: return #x
|
||||
switch (log_level) {
|
||||
LVL(Trace);
|
||||
LVL(Debug);
|
||||
LVL(Info);
|
||||
LVL(Warning);
|
||||
LVL(Error);
|
||||
LVL(Critical);
|
||||
}
|
||||
return "Unknown";
|
||||
#undef LVL
|
||||
}
|
||||
|
||||
void Logger::LogMessage(Entry entry) {
|
||||
ring_buffer.Push(std::move(entry));
|
||||
}
|
||||
|
||||
size_t Logger::GetEntries(Entry* out_buffer, size_t buffer_len) {
|
||||
return ring_buffer.BlockingPop(out_buffer, buffer_len);
|
||||
}
|
||||
|
||||
std::shared_ptr<Logger> InitGlobalLogger() {
|
||||
global_logger = std::make_shared<Logger>();
|
||||
return global_logger;
|
||||
}
|
||||
|
||||
Entry CreateEntry(Class log_class, Level log_level,
|
||||
const char* filename, unsigned int line_nr, const char* function,
|
||||
const char* format, va_list args) {
|
||||
using std::chrono::steady_clock;
|
||||
using std::chrono::duration_cast;
|
||||
|
||||
static steady_clock::time_point time_origin = steady_clock::now();
|
||||
|
||||
std::array<char, 4 * 1024> formatting_buffer;
|
||||
|
||||
Entry entry;
|
||||
entry.timestamp = duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin);
|
||||
entry.log_class = log_class;
|
||||
entry.log_level = log_level;
|
||||
|
||||
snprintf(formatting_buffer.data(), formatting_buffer.size(), "%s:%s:%u", filename, function, line_nr);
|
||||
entry.location = std::string(formatting_buffer.data());
|
||||
|
||||
vsnprintf(formatting_buffer.data(), formatting_buffer.size(), format, args);
|
||||
entry.message = std::string(formatting_buffer.data());
|
||||
|
||||
return std::move(entry);
|
||||
}
|
||||
|
||||
void LogMessage(Class log_class, Level log_level,
|
||||
const char* filename, unsigned int line_nr, const char* function,
|
||||
const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
Entry entry = CreateEntry(log_class, log_level,
|
||||
filename, line_nr, function, format, args);
|
||||
va_end(args);
|
||||
|
||||
if (global_logger != nullptr && !global_logger->IsClosed()) {
|
||||
global_logger->LogMessage(std::move(entry));
|
||||
} else {
|
||||
// Fall back to directly printing to stderr
|
||||
PrintMessage(entry);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdarg>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/concurrent_ring_buffer.h"
|
||||
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace Log {
|
||||
|
||||
/**
|
||||
* A log entry. Log entries are store in a structured format to permit more varied output
|
||||
* formatting on different frontends, as well as facilitating filtering and aggregation.
|
||||
*/
|
||||
struct Entry {
|
||||
std::chrono::microseconds timestamp;
|
||||
Class log_class;
|
||||
Level log_level;
|
||||
std::string location;
|
||||
std::string message;
|
||||
|
||||
Entry() = default;
|
||||
|
||||
// TODO(yuriks) Use defaulted move constructors once MSVC supports them
|
||||
#define MOVE(member) member(std::move(o.member))
|
||||
Entry(Entry&& o)
|
||||
: MOVE(timestamp), MOVE(log_class), MOVE(log_level),
|
||||
MOVE(location), MOVE(message)
|
||||
{}
|
||||
#undef MOVE
|
||||
|
||||
Entry& operator=(const Entry&& o) {
|
||||
#define MOVE(member) member = std::move(o.member)
|
||||
MOVE(timestamp);
|
||||
MOVE(log_class);
|
||||
MOVE(log_level);
|
||||
MOVE(location);
|
||||
MOVE(message);
|
||||
#undef MOVE
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct ClassInfo {
|
||||
Class log_class;
|
||||
|
||||
/**
|
||||
* Total number of (direct or indirect) sub classes this class has. If any, they follow in
|
||||
* sequence after this class in the class list.
|
||||
*/
|
||||
unsigned int num_children = 0;
|
||||
|
||||
ClassInfo(Class log_class) : log_class(log_class) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Logging management class. This class has the dual purpose of acting as an exchange point between
|
||||
* the logging clients and the log outputter, as well as containing reflection info about available
|
||||
* log classes.
|
||||
*/
|
||||
class Logger {
|
||||
private:
|
||||
using Buffer = Common::ConcurrentRingBuffer<Entry, 16 * 1024 / sizeof(Entry)>;
|
||||
|
||||
public:
|
||||
static const size_t QUEUE_CLOSED = Buffer::QUEUE_CLOSED;
|
||||
|
||||
Logger();
|
||||
|
||||
/**
|
||||
* Returns a list of all vector classes and subclasses. The sequence returned is a pre-order of
|
||||
* classes and subclasses, which together with the `num_children` field in ClassInfo, allows
|
||||
* you to recover the hierarchy.
|
||||
*/
|
||||
const std::vector<ClassInfo>& GetClasses() const { return all_classes; }
|
||||
|
||||
/**
|
||||
* Returns the name of the passed log class as a C-string. Subclasses are separated by periods
|
||||
* instead of underscores as in the enumeration.
|
||||
*/
|
||||
static const char* GetLogClassName(Class log_class);
|
||||
|
||||
/**
|
||||
* Returns the name of the passed log level as a C-string.
|
||||
*/
|
||||
static const char* GetLevelName(Level log_level);
|
||||
|
||||
/**
|
||||
* Appends a messages to the log buffer.
|
||||
* @note This function is thread safe.
|
||||
*/
|
||||
void LogMessage(Entry entry);
|
||||
|
||||
/**
|
||||
* Retrieves a batch of messages from the log buffer, blocking until they are available.
|
||||
* @note This function is thread safe.
|
||||
*
|
||||
* @param out_buffer Destination buffer that will receive the log entries.
|
||||
* @param buffer_len The maximum size of `out_buffer`.
|
||||
* @return The number of entries stored. In case the logger is shutting down, `QUEUE_CLOSED` is
|
||||
* returned, no entries are stored and the logger should shutdown.
|
||||
*/
|
||||
size_t GetEntries(Entry* out_buffer, size_t buffer_len);
|
||||
|
||||
/**
|
||||
* Initiates a shutdown of the logger. This will indicate to log output clients that they
|
||||
* should shutdown.
|
||||
*/
|
||||
void Close() { ring_buffer.Close(); }
|
||||
|
||||
/**
|
||||
* Returns true if Close() has already been called on the Logger.
|
||||
*/
|
||||
bool IsClosed() const { return ring_buffer.IsClosed(); }
|
||||
|
||||
private:
|
||||
Buffer ring_buffer;
|
||||
std::vector<ClassInfo> all_classes;
|
||||
};
|
||||
|
||||
/// Creates a log entry by formatting the given source location, and message.
|
||||
Entry CreateEntry(Class log_class, Level log_level,
|
||||
const char* filename, unsigned int line_nr, const char* function,
|
||||
const char* format, va_list args);
|
||||
/// Initializes the default Logger.
|
||||
std::shared_ptr<Logger> InitGlobalLogger();
|
||||
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/logging/filter.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
namespace Log {
|
||||
|
||||
Filter::Filter(Level default_level) {
|
||||
ResetAll(default_level);
|
||||
}
|
||||
|
||||
void Filter::ResetAll(Level level) {
|
||||
class_levels.fill(level);
|
||||
}
|
||||
|
||||
void Filter::SetClassLevel(Class log_class, Level level) {
|
||||
class_levels[static_cast<size_t>(log_class)] = level;
|
||||
}
|
||||
|
||||
void Filter::SetSubclassesLevel(const ClassInfo& log_class, Level level) {
|
||||
const size_t log_class_i = static_cast<size_t>(log_class.log_class);
|
||||
|
||||
const size_t begin = log_class_i + 1;
|
||||
const size_t end = begin + log_class.num_children;
|
||||
for (size_t i = begin; begin < end; ++i) {
|
||||
class_levels[i] = level;
|
||||
}
|
||||
}
|
||||
|
||||
void Filter::ParseFilterString(const std::string& filter_str) {
|
||||
auto clause_begin = filter_str.cbegin();
|
||||
while (clause_begin != filter_str.cend()) {
|
||||
auto clause_end = std::find(clause_begin, filter_str.cend(), ' ');
|
||||
|
||||
// If clause isn't empty
|
||||
if (clause_end != clause_begin) {
|
||||
ParseFilterRule(clause_begin, clause_end);
|
||||
}
|
||||
|
||||
if (clause_end != filter_str.cend()) {
|
||||
// Skip over the whitespace
|
||||
++clause_end;
|
||||
}
|
||||
clause_begin = clause_end;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
static Level GetLevelByName(const It begin, const It end) {
|
||||
for (u8 i = 0; i < static_cast<u8>(Level::Count); ++i) {
|
||||
const char* level_name = Logger::GetLevelName(static_cast<Level>(i));
|
||||
if (Common::ComparePartialString(begin, end, level_name)) {
|
||||
return static_cast<Level>(i);
|
||||
}
|
||||
}
|
||||
return Level::Count;
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
static Class GetClassByName(const It begin, const It end) {
|
||||
for (ClassType i = 0; i < static_cast<ClassType>(Class::Count); ++i) {
|
||||
const char* level_name = Logger::GetLogClassName(static_cast<Class>(i));
|
||||
if (Common::ComparePartialString(begin, end, level_name)) {
|
||||
return static_cast<Class>(i);
|
||||
}
|
||||
}
|
||||
return Class::Count;
|
||||
}
|
||||
|
||||
template <typename InputIt, typename T>
|
||||
static InputIt find_last(InputIt begin, const InputIt end, const T& value) {
|
||||
auto match = end;
|
||||
while (begin != end) {
|
||||
auto new_match = std::find(begin, end, value);
|
||||
if (new_match != end) {
|
||||
match = new_match;
|
||||
++new_match;
|
||||
}
|
||||
begin = new_match;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
bool Filter::ParseFilterRule(const std::string::const_iterator begin,
|
||||
const std::string::const_iterator end) {
|
||||
auto level_separator = std::find(begin, end, ':');
|
||||
if (level_separator == end) {
|
||||
LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: %s",
|
||||
std::string(begin, end).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const Level level = GetLevelByName(level_separator + 1, end);
|
||||
if (level == Level::Count) {
|
||||
LOG_ERROR(Log, "Unknown log level in filter: %s", std::string(begin, end).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Common::ComparePartialString(begin, level_separator, "*")) {
|
||||
ResetAll(level);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto class_name_end = find_last(begin, level_separator, '.');
|
||||
if (class_name_end != level_separator &&
|
||||
!Common::ComparePartialString(class_name_end + 1, level_separator, "*")) {
|
||||
class_name_end = level_separator;
|
||||
}
|
||||
|
||||
const Class log_class = GetClassByName(begin, class_name_end);
|
||||
if (log_class == Class::Count) {
|
||||
LOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (class_name_end == level_separator) {
|
||||
SetClassLevel(log_class, level);
|
||||
}
|
||||
SetSubclassesLevel(log_class, level);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Filter::CheckMessage(Class log_class, Level level) const {
|
||||
return static_cast<u8>(level) >= static_cast<u8>(class_levels[static_cast<size_t>(log_class)]);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace Log {
|
||||
|
||||
struct ClassInfo;
|
||||
|
||||
/**
|
||||
* Implements a log message filter which allows different log classes to have different minimum
|
||||
* severity levels. The filter can be changed at runtime and can be parsed from a string to allow
|
||||
* editing via the interface or loading from a configuration file.
|
||||
*/
|
||||
class Filter {
|
||||
public:
|
||||
/// Initializes the filter with all classes having `default_level` as the minimum level.
|
||||
Filter(Level default_level);
|
||||
|
||||
/// Resets the filter so that all classes have `level` as the minimum displayed level.
|
||||
void ResetAll(Level level);
|
||||
/// Sets the minimum level of `log_class` (and not of its subclasses) to `level`.
|
||||
void SetClassLevel(Class log_class, Level level);
|
||||
/**
|
||||
* Sets the minimum level of all of `log_class` subclasses to `level`. The level of `log_class`
|
||||
* itself is not changed.
|
||||
*/
|
||||
void SetSubclassesLevel(const ClassInfo& log_class, Level level);
|
||||
|
||||
/**
|
||||
* Parses a filter string and applies it to this filter.
|
||||
*
|
||||
* A filter string consists of a space-separated list of filter rules, each of the format
|
||||
* `<class>:<level>`. `<class>` is a log class name, with subclasses separated using periods.
|
||||
* A rule for a given class also affects all of its subclasses. `*` wildcards are allowed and
|
||||
* can be used to apply a rule to all classes or to all subclasses of a class without affecting
|
||||
* the parent class. `<level>` a severity level name which will be set as the minimum logging
|
||||
* level of the matched classes. Rules are applied left to right, with each rule overriding
|
||||
* previous ones in the sequence.
|
||||
*
|
||||
* A few examples of filter rules:
|
||||
* - `*:Info` -- Resets the level of all classes to Info.
|
||||
* - `Service:Info` -- Sets the level of Service and all subclasses (Service.FS, Service.APT,
|
||||
* etc.) to Info.
|
||||
* - `Service.*:Debug` -- Sets the level of all Service subclasses to Debug, while leaving the
|
||||
* level of Service unchanged.
|
||||
* - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace.
|
||||
*/
|
||||
void ParseFilterString(const std::string& filter_str);
|
||||
bool ParseFilterRule(const std::string::const_iterator start, const std::string::const_iterator end);
|
||||
|
||||
/// Matches class/level combination against the filter, returning true if it passed.
|
||||
bool CheckMessage(Class log_class, Level level) const;
|
||||
|
||||
private:
|
||||
std::array<Level, (size_t)Class::Count> class_levels;
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Log {
|
||||
|
||||
/// Specifies the severity or level of detail of the log message.
|
||||
enum class Level : u8 {
|
||||
Trace, ///< Extremely detailed and repetitive debugging information that is likely to
|
||||
/// pollute logs.
|
||||
Debug, ///< Less detailed debugging information.
|
||||
Info, ///< Status information from important points during execution.
|
||||
Warning, ///< Minor or potential problems found during execution of a task.
|
||||
Error, ///< Major problems found during execution of a task that prevent it from being
|
||||
/// completed.
|
||||
Critical, ///< Major problems during execution that threathen the stability of the entire
|
||||
/// application.
|
||||
|
||||
Count ///< Total number of logging levels
|
||||
};
|
||||
|
||||
typedef u8 ClassType;
|
||||
|
||||
/**
|
||||
* Specifies the sub-system that generated the log message.
|
||||
*
|
||||
* @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in log.cpp.
|
||||
*/
|
||||
enum class Class : ClassType {
|
||||
Log, ///< Messages about the log system itself
|
||||
Common, ///< Library routines
|
||||
Common_Filesystem, ///< Filesystem interface library
|
||||
Common_Memory, ///< Memory mapping and management functions
|
||||
Core, ///< LLE emulation core
|
||||
Core_ARM11, ///< ARM11 CPU core
|
||||
Config, ///< Emulator configuration (including commandline)
|
||||
Debug, ///< Debugging tools
|
||||
Debug_Emulated, ///< Debug messages from the emulated programs
|
||||
Debug_GPU, ///< GPU debugging tools
|
||||
Debug_Breakpoint, ///< Logging breakpoints and watchpoints
|
||||
Kernel, ///< The HLE implementation of the CTR kernel
|
||||
Kernel_SVC, ///< Kernel system calls
|
||||
Service, ///< HLE implementation of system services. Each major service
|
||||
/// should have its own subclass.
|
||||
Service_SRV, ///< The SRV (Service Directory) implementation
|
||||
Service_FS, ///< The FS (Filesystem) service implementation
|
||||
Service_APT, ///< The APT (Applets) service
|
||||
Service_GSP, ///< The GSP (GPU control) service
|
||||
Service_AC, ///< The AC (WiFi status) service
|
||||
Service_PTM, ///< The PTM (Power status & misc.) service
|
||||
Service_CFG, ///< The CFG (Configuration) service
|
||||
Service_DSP, ///< The DSP (DSP control) service
|
||||
Service_HID, ///< The HID (User input) service
|
||||
HW, ///< Low-level hardware emulation
|
||||
HW_Memory, ///< Memory-map and address translation
|
||||
HW_GPU, ///< GPU control emulation
|
||||
Frontend, ///< Emulator UI
|
||||
Render, ///< Emulator video output and hardware acceleration
|
||||
Render_Software, ///< Software renderer backend
|
||||
Render_OpenGL, ///< OpenGL backend
|
||||
Loader, ///< ROM loader
|
||||
|
||||
Count ///< Total number of logging classes
|
||||
};
|
||||
|
||||
/**
|
||||
* Level below which messages are simply discarded without buffering regardless of the display
|
||||
* settings.
|
||||
*/
|
||||
const Level MINIMUM_LEVEL =
|
||||
#ifdef _DEBUG
|
||||
Level::Trace;
|
||||
#else
|
||||
Level::Debug;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Logs a message to the global logger. This proxy exists to avoid exposing the details of the
|
||||
* Logger class, including the ConcurrentRingBuffer template, to all files that desire to log
|
||||
* messages, reducing unecessary recompilations.
|
||||
*/
|
||||
void LogMessage(Class log_class, Level log_level,
|
||||
const char* filename, unsigned int line_nr, const char* function,
|
||||
#ifdef _MSC_VER
|
||||
_Printf_format_string_
|
||||
#endif
|
||||
const char* format, ...)
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 6, 7)))
|
||||
#endif
|
||||
;
|
||||
|
||||
} // namespace Log
|
||||
|
||||
#define LOG_GENERIC(log_class, log_level, ...) \
|
||||
do { \
|
||||
if (::Log::Level::log_level >= ::Log::MINIMUM_LEVEL) \
|
||||
::Log::LogMessage(::Log::Class::log_class, ::Log::Level::log_level, \
|
||||
__FILE__, __LINE__, __func__, __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define LOG_TRACE( log_class, ...) LOG_GENERIC(log_class, Trace, __VA_ARGS__)
|
||||
#define LOG_DEBUG( log_class, ...) LOG_GENERIC(log_class, Debug, __VA_ARGS__)
|
||||
#define LOG_INFO( log_class, ...) LOG_GENERIC(log_class, Info, __VA_ARGS__)
|
||||
#define LOG_WARNING( log_class, ...) LOG_GENERIC(log_class, Warning, __VA_ARGS__)
|
||||
#define LOG_ERROR( log_class, ...) LOG_GENERIC(log_class, Error, __VA_ARGS__)
|
||||
#define LOG_CRITICAL(log_class, ...) LOG_GENERIC(log_class, Critical, __VA_ARGS__)
|
@ -0,0 +1,136 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
|
||||
#ifdef _WIN32
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <Windows.h>
|
||||
#endif
|
||||
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/filter.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging/text_formatter.h"
|
||||
|
||||
#include "common/string_util.h"
|
||||
|
||||
namespace Log {
|
||||
|
||||
// TODO(bunnei): This should be moved to a generic path manipulation library
|
||||
const char* TrimSourcePath(const char* path, const char* root) {
|
||||
const char* p = path;
|
||||
|
||||
while (*p != '\0') {
|
||||
const char* next_slash = p;
|
||||
while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') {
|
||||
++next_slash;
|
||||
}
|
||||
|
||||
bool is_src = Common::ComparePartialString(p, next_slash, root);
|
||||
p = next_slash;
|
||||
|
||||
if (*p != '\0') {
|
||||
++p;
|
||||
}
|
||||
if (is_src) {
|
||||
path = p;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len) {
|
||||
unsigned int time_seconds = static_cast<unsigned int>(entry.timestamp.count() / 1000000);
|
||||
unsigned int time_fractional = static_cast<unsigned int>(entry.timestamp.count() % 1000000);
|
||||
|
||||
const char* class_name = Logger::GetLogClassName(entry.log_class);
|
||||
const char* level_name = Logger::GetLevelName(entry.log_level);
|
||||
|
||||
snprintf(out_text, text_len, "[%4u.%06u] %s <%s> %s: %s",
|
||||
time_seconds, time_fractional, class_name, level_name,
|
||||
TrimSourcePath(entry.location.c_str()), entry.message.c_str());
|
||||
}
|
||||
|
||||
void PrintMessage(const Entry& entry) {
|
||||
std::array<char, 4 * 1024> format_buffer;
|
||||
FormatLogMessage(entry, format_buffer.data(), format_buffer.size());
|
||||
fputs(format_buffer.data(), stderr);
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
|
||||
void PrintColoredMessage(const Entry& entry) {
|
||||
#ifdef _WIN32
|
||||
static HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE);
|
||||
|
||||
CONSOLE_SCREEN_BUFFER_INFO original_info = {0};
|
||||
GetConsoleScreenBufferInfo(console_handle, &original_info);
|
||||
|
||||
WORD color = 0;
|
||||
switch (entry.log_level) {
|
||||
case Level::Trace: // Grey
|
||||
color = FOREGROUND_INTENSITY; break;
|
||||
case Level::Debug: // Cyan
|
||||
color = FOREGROUND_GREEN | FOREGROUND_BLUE; break;
|
||||
case Level::Info: // Bright gray
|
||||
color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
|
||||
case Level::Warning: // Bright yellow
|
||||
color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; break;
|
||||
case Level::Error: // Bright red
|
||||
color = FOREGROUND_RED | FOREGROUND_INTENSITY; break;
|
||||
case Level::Critical: // Bright magenta
|
||||
color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break;
|
||||
}
|
||||
|
||||
SetConsoleTextAttribute(console_handle, color);
|
||||
#else
|
||||
# define ESC "\x1b"
|
||||
const char* color = "";
|
||||
switch (entry.log_level) {
|
||||
case Level::Trace: // Grey
|
||||
color = ESC "[1;30m"; break;
|
||||
case Level::Debug: // Cyan
|
||||
color = ESC "[0;36m"; break;
|
||||
case Level::Info: // Bright gray
|
||||
color = ESC "[0;37m"; break;
|
||||
case Level::Warning: // Bright yellow
|
||||
color = ESC "[1;33m"; break;
|
||||
case Level::Error: // Bright red
|
||||
color = ESC "[1;31m"; break;
|
||||
case Level::Critical: // Bright magenta
|
||||
color = ESC "[1;35m"; break;
|
||||
}
|
||||
|
||||
fputs(color, stderr);
|
||||
#endif
|
||||
|
||||
PrintMessage(entry);
|
||||
|
||||
#ifdef _WIN32
|
||||
SetConsoleTextAttribute(console_handle, original_info.wAttributes);
|
||||
#else
|
||||
fputs(ESC "[0m", stderr);
|
||||
# undef ESC
|
||||
#endif
|
||||
}
|
||||
|
||||
void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter) {
|
||||
std::array<Entry, 256> entry_buffer;
|
||||
|
||||
while (true) {
|
||||
size_t num_entries = logger->GetEntries(entry_buffer.data(), entry_buffer.size());
|
||||
if (num_entries == Logger::QUEUE_CLOSED) {
|
||||
break;
|
||||
}
|
||||
for (size_t i = 0; i < num_entries; ++i) {
|
||||
const Entry& entry = entry_buffer[i];
|
||||
if (filter->CheckMessage(entry.log_class, entry.log_level)) {
|
||||
PrintColoredMessage(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
namespace Log {
|
||||
|
||||
class Logger;
|
||||
struct Entry;
|
||||
class Filter;
|
||||
|
||||
/**
|
||||
* Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's
|
||||
* intended to be used to strip a system-specific build directory from the `__FILE__` macro,
|
||||
* leaving only the path relative to the sources root.
|
||||
*
|
||||
* @param path The input file path as a null-terminated string
|
||||
* @param root The name of the root source directory as a null-terminated string. Path up to and
|
||||
* including the last occurence of this name will be stripped
|
||||
* @return A pointer to the same string passed as `path`, but starting at the trimmed portion
|
||||
*/
|
||||
const char* TrimSourcePath(const char* path, const char* root = "src");
|
||||
|
||||
/// Formats a log entry into the provided text buffer.
|
||||
void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len);
|
||||
/// Formats and prints a log entry to stderr.
|
||||
void PrintMessage(const Entry& entry);
|
||||
/// Prints the same message as `PrintMessage`, but colored acoording to the severity level.
|
||||
void PrintColoredMessage(const Entry& entry);
|
||||
|
||||
/**
|
||||
* Logging loop that repeatedly reads messages from the provided logger and prints them to the
|
||||
* console. It is the baseline barebones log outputter.
|
||||
*/
|
||||
void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter);
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename T, typename... Args>
|
||||
std::unique_ptr<T> make_unique(Args&&... args) {
|
||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
} // namespace
|
@ -0,0 +1,37 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace detail {
|
||||
template <typename Func>
|
||||
struct ScopeExitHelper {
|
||||
explicit ScopeExitHelper(Func&& func) : func(std::move(func)) {}
|
||||
~ScopeExitHelper() { func(); }
|
||||
|
||||
Func func;
|
||||
};
|
||||
|
||||
template <typename Func>
|
||||
ScopeExitHelper<Func> ScopeExit(Func&& func) { return ScopeExitHelper<Func>(std::move(func)); }
|
||||
}
|
||||
|
||||
/**
|
||||
* This macro allows you to conveniently specify a block of code that will run on scope exit. Handy
|
||||
* for doing ad-hoc clean-up tasks in a function with multiple returns.
|
||||
*
|
||||
* Example usage:
|
||||
* \code
|
||||
* const int saved_val = g_foo;
|
||||
* g_foo = 55;
|
||||
* SCOPE_EXIT({ g_foo = saved_val; });
|
||||
*
|
||||
* if (Bar()) {
|
||||
* return 0;
|
||||
* } else {
|
||||
* return 20;
|
||||
* }
|
||||
* \endcode
|
||||
*/
|
||||
#define SCOPE_EXIT(body) auto scope_exit_helper_##__LINE__ = detail::ScopeExit([&]() body)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue