288 changed files with 17607 additions and 14163 deletions
-
16.gitignore
-
6.gitmodules
-
1.travis-build.sh
-
13.travis-deps.sh
-
5.travis.yml
-
42CMakeLists.txt
-
1CONTRIBUTING.md
-
2Doxyfile
-
22README.md
-
1externals/boost
-
1externals/nihstro
-
14src/citra/CMakeLists.txt
-
25src/citra/citra.cpp
-
30src/citra/config.cpp
-
7src/citra/config.h
-
7src/citra/default_ini.h
-
26src/citra/emu_window/emu_window_glfw.cpp
-
2src/citra/emu_window/emu_window_glfw.h
-
11src/citra_qt/CMakeLists.txt
-
23src/citra_qt/bootmanager.cpp
-
58src/citra_qt/config.cpp
-
13src/citra_qt/config.h
-
4src/citra_qt/debugger/callstack.cpp
-
4src/citra_qt/debugger/disassembler.cpp
-
2src/citra_qt/debugger/graphics.cpp
-
2src/citra_qt/debugger/graphics.hxx
-
263src/citra_qt/debugger/graphics_breakpoints.cpp
-
53src/citra_qt/debugger/graphics_breakpoints.hxx
-
38src/citra_qt/debugger/graphics_breakpoints_p.hxx
-
275src/citra_qt/debugger/graphics_cmdlists.cpp
-
39src/citra_qt/debugger/graphics_cmdlists.hxx
-
283src/citra_qt/debugger/graphics_framebuffer.cpp
-
92src/citra_qt/debugger/graphics_framebuffer.hxx
-
4src/citra_qt/hotkeys.cpp
-
64src/citra_qt/main.cpp
-
303src/citra_qt/util/spinbox.cpp
-
88src/citra_qt/util/spinbox.hxx
-
14src/common/CMakeLists.txt
-
14src/common/bit_field.h
-
6src/common/break_points.cpp
-
4src/common/break_points.h
-
59src/common/chunk_file.h
-
4src/common/common.h
-
11src/common/common_funcs.h
-
23src/common/common_paths.h
-
2src/common/common_types.h
-
164src/common/concurrent_ring_buffer.h
-
319src/common/console_listener.cpp
-
38src/common/console_listener.h
-
4src/common/cpu_detect.h
-
2src/common/emu_window.cpp
-
2src/common/emu_window.h
-
24src/common/extended_trace.cpp
-
4src/common/fifo_queue.h
-
4src/common/file_search.cpp
-
4src/common/file_search.h
-
130src/common/file_util.cpp
-
11src/common/file_util.h
-
4src/common/hash.cpp
-
4src/common/hash.h
-
2src/common/key_map.cpp
-
2src/common/key_map.h
-
6src/common/linear_disk_cache.h
-
133src/common/log.h
-
199src/common/log_manager.cpp
-
166src/common/log_manager.h
-
151src/common/logging/backend.cpp
-
134src/common/logging/backend.h
-
132src/common/logging/filter.cpp
-
63src/common/logging/filter.h
-
115src/common/logging/log.h
-
136src/common/logging/text_formatter.cpp
-
41src/common/logging/text_formatter.h
-
16src/common/make_unique.h
-
4src/common/math_util.cpp
-
4src/common/math_util.h
-
34src/common/mem_arena.cpp
-
16src/common/memory_util.cpp
-
4src/common/memory_util.h
-
8src/common/misc.cpp
-
6src/common/msg_handler.cpp
-
4src/common/msg_handler.h
-
2src/common/platform.h
-
2src/common/scm_rev.h
-
37src/common/scope_exit.h
-
32src/common/string_util.cpp
-
19src/common/string_util.h
-
2src/common/symbols.cpp
-
2src/common/symbols.h
-
4src/common/thread.cpp
-
5src/common/thread.h
-
14src/common/thread_queue_list.h
-
4src/common/thunk.h
-
10src/common/timer.cpp
-
4src/common/timer.h
-
24src/common/utf8.cpp
-
57src/core/CMakeLists.txt
-
8src/core/arm/arm_interface.h
-
2src/core/arm/disassembler/load_symbol_map.cpp
-
2src/core/arm/disassembler/load_symbol_map.h
@ -1,3 +1,9 @@ |
|||||
[submodule "externals/inih/inih"] |
[submodule "externals/inih/inih"] |
||||
path = externals/inih/inih |
path = externals/inih/inih |
||||
url = https://github.com/svn2github/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 |
||||
============== |
============== |
||||
[](https://travis-ci.org/citra-emu/citra) |
[](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 |
### 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 |
### Building |
||||
|
|
||||
* __Windows__: [Windows Build](https://github.com/citra-emu/citra/wiki/Windows-Build) |
* __Windows__: [Windows Build](https://github.com/citra-emu/citra/wiki/Windows-Build) |
||||
* __Linux__: [Linux Build](https://github.com/citra-emu/citra/wiki/Linux-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) |
* __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,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 changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue