mirror of
https://git.suyu.dev/suyu/suyu
synced 2024-11-01 04:47:53 +00:00
Sources: Run clang-format on everything.
This commit is contained in:
parent
fe948af095
commit
dc8479928c
386 changed files with 19560 additions and 18080 deletions
|
@ -42,10 +42,18 @@ void Init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddAddressSpace(Kernel::VMManager& address_space) {
|
void AddAddressSpace(Kernel::VMManager& address_space) {
|
||||||
auto r0_vma = address_space.MapBackingMemory(DSP::HLE::region0_base, reinterpret_cast<u8*>(&DSP::HLE::g_regions[0]), sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO).MoveFrom();
|
auto r0_vma = address_space
|
||||||
|
.MapBackingMemory(DSP::HLE::region0_base,
|
||||||
|
reinterpret_cast<u8*>(&DSP::HLE::g_regions[0]),
|
||||||
|
sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO)
|
||||||
|
.MoveFrom();
|
||||||
address_space.Reprotect(r0_vma, Kernel::VMAPermission::ReadWrite);
|
address_space.Reprotect(r0_vma, Kernel::VMAPermission::ReadWrite);
|
||||||
|
|
||||||
auto r1_vma = address_space.MapBackingMemory(DSP::HLE::region1_base, reinterpret_cast<u8*>(&DSP::HLE::g_regions[1]), sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO).MoveFrom();
|
auto r1_vma = address_space
|
||||||
|
.MapBackingMemory(DSP::HLE::region1_base,
|
||||||
|
reinterpret_cast<u8*>(&DSP::HLE::g_regions[1]),
|
||||||
|
sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO)
|
||||||
|
.MoveFrom();
|
||||||
address_space.Reprotect(r1_vma, Kernel::VMAPermission::ReadWrite);
|
address_space.Reprotect(r1_vma, Kernel::VMAPermission::ReadWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,9 +66,9 @@ void SelectSink(std::string sink_id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto iter = std::find_if(g_sink_details.begin(), g_sink_details.end(), [sink_id](const auto& sink_detail) {
|
auto iter =
|
||||||
return sink_detail.id == sink_id;
|
std::find_if(g_sink_details.begin(), g_sink_details.end(),
|
||||||
});
|
[sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
|
||||||
|
|
||||||
if (iter == g_sink_details.end()) {
|
if (iter == g_sink_details.end()) {
|
||||||
LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id");
|
LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id");
|
||||||
|
|
|
@ -15,22 +15,25 @@
|
||||||
|
|
||||||
namespace Codec {
|
namespace Codec {
|
||||||
|
|
||||||
StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count, const std::array<s16, 16>& adpcm_coeff, ADPCMState& state) {
|
StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count,
|
||||||
|
const std::array<s16, 16>& adpcm_coeff, ADPCMState& state) {
|
||||||
// GC-ADPCM with scale factor and variable coefficients.
|
// GC-ADPCM with scale factor and variable coefficients.
|
||||||
// Frames are 8 bytes long containing 14 samples each.
|
// Frames are 8 bytes long containing 14 samples each.
|
||||||
// Samples are 4 bits (one nibble) long.
|
// Samples are 4 bits (one nibble) long.
|
||||||
|
|
||||||
constexpr size_t FRAME_LEN = 8;
|
constexpr size_t FRAME_LEN = 8;
|
||||||
constexpr size_t SAMPLES_PER_FRAME = 14;
|
constexpr size_t SAMPLES_PER_FRAME = 14;
|
||||||
constexpr std::array<int, 16> SIGNED_NIBBLES {{ 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1 }};
|
constexpr std::array<int, 16> SIGNED_NIBBLES{
|
||||||
|
{0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}};
|
||||||
|
|
||||||
const size_t ret_size = sample_count % 2 == 0 ? sample_count : sample_count + 1; // Ensure multiple of two.
|
const size_t ret_size =
|
||||||
|
sample_count % 2 == 0 ? sample_count : sample_count + 1; // Ensure multiple of two.
|
||||||
StereoBuffer16 ret(ret_size);
|
StereoBuffer16 ret(ret_size);
|
||||||
|
|
||||||
int yn1 = state.yn1,
|
int yn1 = state.yn1, yn2 = state.yn2;
|
||||||
yn2 = state.yn2;
|
|
||||||
|
|
||||||
const size_t NUM_FRAMES = (sample_count + (SAMPLES_PER_FRAME - 1)) / SAMPLES_PER_FRAME; // Round up.
|
const size_t NUM_FRAMES =
|
||||||
|
(sample_count + (SAMPLES_PER_FRAME - 1)) / SAMPLES_PER_FRAME; // Round up.
|
||||||
for (size_t framei = 0; framei < NUM_FRAMES; framei++) {
|
for (size_t framei = 0; framei < NUM_FRAMES; framei++) {
|
||||||
const int frame_header = data[framei * FRAME_LEN];
|
const int frame_header = data[framei * FRAME_LEN];
|
||||||
const int scale = 1 << (frame_header & 0xF);
|
const int scale = 1 << (frame_header & 0xF);
|
||||||
|
@ -43,7 +46,8 @@ StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count, cons
|
||||||
// Decodes an audio sample. One nibble produces one sample.
|
// Decodes an audio sample. One nibble produces one sample.
|
||||||
const auto decode_sample = [&](const int nibble) -> s16 {
|
const auto decode_sample = [&](const int nibble) -> s16 {
|
||||||
const int xn = nibble * scale;
|
const int xn = nibble * scale;
|
||||||
// We first transform everything into 11 bit fixed point, perform the second order digital filter, then transform back.
|
// We first transform everything into 11 bit fixed point, perform the second order
|
||||||
|
// digital filter, then transform back.
|
||||||
// 0x400 == 0.5 in 11 bit fixed point.
|
// 0x400 == 0.5 in 11 bit fixed point.
|
||||||
// Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
|
// Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
|
||||||
int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
|
int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
|
||||||
|
@ -82,7 +86,8 @@ static s16 SignExtendS8(u8 x) {
|
||||||
return static_cast<s16>(static_cast<s8>(x));
|
return static_cast<s16>(static_cast<s8>(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data, const size_t sample_count) {
|
StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data,
|
||||||
|
const size_t sample_count) {
|
||||||
ASSERT(num_channels == 1 || num_channels == 2);
|
ASSERT(num_channels == 1 || num_channels == 2);
|
||||||
|
|
||||||
StereoBuffer16 ret(sample_count);
|
StereoBuffer16 ret(sample_count);
|
||||||
|
@ -101,7 +106,8 @@ StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data, con
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data, const size_t sample_count) {
|
StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data,
|
||||||
|
const size_t sample_count) {
|
||||||
ASSERT(num_channels == 1 || num_channels == 2);
|
ASSERT(num_channels == 1 || num_channels == 2);
|
||||||
|
|
||||||
StereoBuffer16 ret(sample_count);
|
StereoBuffer16 ret(sample_count);
|
||||||
|
@ -118,5 +124,4 @@ StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data, co
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,7 +29,8 @@ struct ADPCMState {
|
||||||
* @param state ADPCM state, this is updated with new state
|
* @param state ADPCM state, this is updated with new state
|
||||||
* @return Decoded stereo signed PCM16 data, sample_count in length
|
* @return Decoded stereo signed PCM16 data, sample_count in length
|
||||||
*/
|
*/
|
||||||
StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count, const std::array<s16, 16>& adpcm_coeff, ADPCMState& state);
|
StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count,
|
||||||
|
const std::array<s16, 16>& adpcm_coeff, ADPCMState& state);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param num_channels Number of channels
|
* @param num_channels Number of channels
|
||||||
|
@ -37,7 +38,8 @@ StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count, cons
|
||||||
* @param sample_count Length of buffer in terms of number of samples
|
* @param sample_count Length of buffer in terms of number of samples
|
||||||
* @return Decoded stereo signed PCM16 data, sample_count in length
|
* @return Decoded stereo signed PCM16 data, sample_count in length
|
||||||
*/
|
*/
|
||||||
StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data, const size_t sample_count);
|
StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data,
|
||||||
|
const size_t sample_count);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param num_channels Number of channels
|
* @param num_channels Number of channels
|
||||||
|
@ -45,6 +47,6 @@ StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data, con
|
||||||
* @param sample_count Length of buffer in terms of number of samples
|
* @param sample_count Length of buffer in terms of number of samples
|
||||||
* @return Decoded stereo signed PCM16 data, sample_count in length
|
* @return Decoded stereo signed PCM16 data, sample_count in length
|
||||||
*/
|
*/
|
||||||
StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data, const size_t sample_count);
|
StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data,
|
||||||
|
const size_t sample_count);
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,11 +25,10 @@ using QuadFrame32 = std::array<std::array<s32, 4>, samples_per_frame>;
|
||||||
* This performs the filter operation defined by FilterT::ProcessSample on the frame in-place.
|
* This performs the filter operation defined by FilterT::ProcessSample on the frame in-place.
|
||||||
* FilterT::ProcessSample is called sequentially on the samples.
|
* FilterT::ProcessSample is called sequentially on the samples.
|
||||||
*/
|
*/
|
||||||
template<typename FrameT, typename FilterT>
|
template <typename FrameT, typename FilterT>
|
||||||
void FilterFrame(FrameT& frame, FilterT& filter) {
|
void FilterFrame(FrameT& frame, FilterT& filter) {
|
||||||
std::transform(frame.begin(), frame.end(), frame.begin(), [&filter](const auto& sample) {
|
std::transform(frame.begin(), frame.end(), frame.begin(),
|
||||||
return filter.ProcessSample(sample);
|
[&filter](const auto& sample) { return filter.ProcessSample(sample); });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace HLE
|
} // namespace HLE
|
||||||
|
|
|
@ -47,11 +47,9 @@ static SharedMemory& WriteRegion() {
|
||||||
// Audio processing and mixing
|
// Audio processing and mixing
|
||||||
|
|
||||||
static std::array<Source, num_sources> sources = {
|
static std::array<Source, num_sources> sources = {
|
||||||
Source(0), Source(1), Source(2), Source(3), Source(4), Source(5),
|
Source(0), Source(1), Source(2), Source(3), Source(4), Source(5), Source(6), Source(7),
|
||||||
Source(6), Source(7), Source(8), Source(9), Source(10), Source(11),
|
Source(8), Source(9), Source(10), Source(11), Source(12), Source(13), Source(14), Source(15),
|
||||||
Source(12), Source(13), Source(14), Source(15), Source(16), Source(17),
|
Source(16), Source(17), Source(18), Source(19), Source(20), Source(21), Source(22), Source(23)};
|
||||||
Source(18), Source(19), Source(20), Source(21), Source(22), Source(23)
|
|
||||||
};
|
|
||||||
static Mixers mixers;
|
static Mixers mixers;
|
||||||
|
|
||||||
static StereoFrame16 GenerateCurrentFrame() {
|
static StereoFrame16 GenerateCurrentFrame() {
|
||||||
|
@ -62,14 +60,16 @@ static StereoFrame16 GenerateCurrentFrame() {
|
||||||
|
|
||||||
// Generate intermediate mixes
|
// Generate intermediate mixes
|
||||||
for (size_t i = 0; i < num_sources; i++) {
|
for (size_t i = 0; i < num_sources; i++) {
|
||||||
write.source_statuses.status[i] = sources[i].Tick(read.source_configurations.config[i], read.adpcm_coefficients.coeff[i]);
|
write.source_statuses.status[i] =
|
||||||
|
sources[i].Tick(read.source_configurations.config[i], read.adpcm_coefficients.coeff[i]);
|
||||||
for (size_t mix = 0; mix < 3; mix++) {
|
for (size_t mix = 0; mix < 3; mix++) {
|
||||||
sources[i].MixInto(intermediate_mixes[mix], mix);
|
sources[i].MixInto(intermediate_mixes[mix], mix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate final mix
|
// Generate final mix
|
||||||
write.dsp_status = mixers.Tick(read.dsp_configuration, read.intermediate_mix_samples, write.intermediate_mix_samples, intermediate_mixes);
|
write.dsp_status = mixers.Tick(read.dsp_configuration, read.intermediate_mix_samples,
|
||||||
|
write.intermediate_mix_samples, intermediate_mixes);
|
||||||
|
|
||||||
StereoFrame16 output_frame = mixers.GetOutput();
|
StereoFrame16 output_frame = mixers.GetOutput();
|
||||||
|
|
||||||
|
@ -152,7 +152,8 @@ void Shutdown() {
|
||||||
bool Tick() {
|
bool Tick() {
|
||||||
StereoFrame16 current_frame = {};
|
StereoFrame16 current_frame = {};
|
||||||
|
|
||||||
// TODO: Check dsp::DSP semaphore (which indicates emulated application has finished writing to shared memory region)
|
// TODO: Check dsp::DSP semaphore (which indicates emulated application has finished writing to
|
||||||
|
// shared memory region)
|
||||||
current_frame = GenerateCurrentFrame();
|
current_frame = GenerateCurrentFrame();
|
||||||
|
|
||||||
OutputCurrentFrame(current_frame);
|
OutputCurrentFrame(current_frame);
|
||||||
|
|
|
@ -30,7 +30,8 @@ namespace HLE {
|
||||||
// Second Region: 0x1FF70000 (Size: 0x8000)
|
// Second Region: 0x1FF70000 (Size: 0x8000)
|
||||||
//
|
//
|
||||||
// The DSP reads from each region alternately based on the frame counter for each region much like a
|
// The DSP reads from each region alternately based on the frame counter for each region much like a
|
||||||
// double-buffer. The frame counter is located as the very last u16 of each region and is incremented
|
// double-buffer. The frame counter is located as the very last u16 of each region and is
|
||||||
|
// incremented
|
||||||
// each audio tick.
|
// each audio tick.
|
||||||
|
|
||||||
constexpr VAddr region0_base = 0x1FF50000;
|
constexpr VAddr region0_base = 0x1FF50000;
|
||||||
|
@ -56,6 +57,7 @@ struct u32_dsp {
|
||||||
void operator=(u32 new_value) {
|
void operator=(u32 new_value) {
|
||||||
storage = Convert(new_value);
|
storage = Convert(new_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr u32 Convert(u32 value) {
|
static constexpr u32 Convert(u32 value) {
|
||||||
return (value << 16) | (value >> 16);
|
return (value << 16) | (value >> 16);
|
||||||
|
@ -89,11 +91,13 @@ static_assert(std::is_trivially_copyable<u32_dsp>::value, "u32_dsp isn't trivial
|
||||||
// #: This refers to the order in which they appear in the DspPipe::Audio DSP pipe.
|
// #: This refers to the order in which they appear in the DspPipe::Audio DSP pipe.
|
||||||
// See also: DSP::HLE::PipeRead.
|
// See also: DSP::HLE::PipeRead.
|
||||||
//
|
//
|
||||||
// Note that the above addresses do vary slightly between audio firmwares observed; the addresses are
|
// Note that the above addresses do vary slightly between audio firmwares observed; the addresses
|
||||||
|
// are
|
||||||
// not fixed in stone. The addresses above are only an examplar; they're what this implementation
|
// not fixed in stone. The addresses above are only an examplar; they're what this implementation
|
||||||
// does and provides to applications.
|
// does and provides to applications.
|
||||||
//
|
//
|
||||||
// Application requests the DSP service to convert DSP addresses into ARM11 virtual addresses using the
|
// Application requests the DSP service to convert DSP addresses into ARM11 virtual addresses using
|
||||||
|
// the
|
||||||
// ConvertProcessAddressFromDspDram service call. Applications seem to derive the addresses for the
|
// ConvertProcessAddressFromDspDram service call. Applications seem to derive the addresses for the
|
||||||
// second region via:
|
// second region via:
|
||||||
// second_region_dsp_addr = first_region_dsp_addr | 0x10000
|
// second_region_dsp_addr = first_region_dsp_addr | 0x10000
|
||||||
|
@ -110,13 +114,16 @@ static_assert(std::is_trivially_copyable<u32_dsp>::value, "u32_dsp isn't trivial
|
||||||
// GCC versions < 5.0 do not implement std::is_trivially_copyable.
|
// GCC versions < 5.0 do not implement std::is_trivially_copyable.
|
||||||
// Excluding MSVC because it has weird behaviour for std::is_trivially_copyable.
|
// Excluding MSVC because it has weird behaviour for std::is_trivially_copyable.
|
||||||
#if (__GNUC__ >= 5) || defined(__clang__)
|
#if (__GNUC__ >= 5) || defined(__clang__)
|
||||||
#define ASSERT_DSP_STRUCT(name, size) \
|
#define ASSERT_DSP_STRUCT(name, size) \
|
||||||
static_assert(std::is_standard_layout<name>::value, "DSP structure " #name " doesn't use standard layout"); \
|
static_assert(std::is_standard_layout<name>::value, \
|
||||||
static_assert(std::is_trivially_copyable<name>::value, "DSP structure " #name " isn't trivially copyable"); \
|
"DSP structure " #name " doesn't use standard layout"); \
|
||||||
|
static_assert(std::is_trivially_copyable<name>::value, \
|
||||||
|
"DSP structure " #name " isn't trivially copyable"); \
|
||||||
static_assert(sizeof(name) == (size), "Unexpected struct size for DSP structure " #name)
|
static_assert(sizeof(name) == (size), "Unexpected struct size for DSP structure " #name)
|
||||||
#else
|
#else
|
||||||
#define ASSERT_DSP_STRUCT(name, size) \
|
#define ASSERT_DSP_STRUCT(name, size) \
|
||||||
static_assert(std::is_standard_layout<name>::value, "DSP structure " #name " doesn't use standard layout"); \
|
static_assert(std::is_standard_layout<name>::value, \
|
||||||
|
"DSP structure " #name " doesn't use standard layout"); \
|
||||||
static_assert(sizeof(name) == (size), "Unexpected struct size for DSP structure " #name)
|
static_assert(sizeof(name) == (size), "Unexpected struct size for DSP structure " #name)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -130,7 +137,8 @@ struct SourceConfiguration {
|
||||||
BitField<0, 1, u32_le> format_dirty;
|
BitField<0, 1, u32_le> format_dirty;
|
||||||
BitField<1, 1, u32_le> mono_or_stereo_dirty;
|
BitField<1, 1, u32_le> mono_or_stereo_dirty;
|
||||||
BitField<2, 1, u32_le> adpcm_coefficients_dirty;
|
BitField<2, 1, u32_le> adpcm_coefficients_dirty;
|
||||||
BitField<3, 1, u32_le> partial_embedded_buffer_dirty; ///< Tends to be set when a looped buffer is queued.
|
BitField<3, 1, u32_le>
|
||||||
|
partial_embedded_buffer_dirty; ///< Tends to be set when a looped buffer is queued.
|
||||||
BitField<4, 1, u32_le> partial_reset_flag;
|
BitField<4, 1, u32_le> partial_reset_flag;
|
||||||
|
|
||||||
BitField<16, 1, u32_le> enable_dirty;
|
BitField<16, 1, u32_le> enable_dirty;
|
||||||
|
@ -138,7 +146,8 @@ struct SourceConfiguration {
|
||||||
BitField<18, 1, u32_le> rate_multiplier_dirty;
|
BitField<18, 1, u32_le> rate_multiplier_dirty;
|
||||||
BitField<19, 1, u32_le> buffer_queue_dirty;
|
BitField<19, 1, u32_le> buffer_queue_dirty;
|
||||||
BitField<20, 1, u32_le> loop_related_dirty;
|
BitField<20, 1, u32_le> loop_related_dirty;
|
||||||
BitField<21, 1, u32_le> play_position_dirty; ///< Tends to also be set when embedded buffer is updated.
|
BitField<21, 1, u32_le>
|
||||||
|
play_position_dirty; ///< Tends to also be set when embedded buffer is updated.
|
||||||
BitField<22, 1, u32_le> filters_enabled_dirty;
|
BitField<22, 1, u32_le> filters_enabled_dirty;
|
||||||
BitField<23, 1, u32_le> simple_filter_dirty;
|
BitField<23, 1, u32_le> simple_filter_dirty;
|
||||||
BitField<24, 1, u32_le> biquad_filter_dirty;
|
BitField<24, 1, u32_le> biquad_filter_dirty;
|
||||||
|
@ -164,11 +173,7 @@ struct SourceConfiguration {
|
||||||
/// Multiplier for sample rate. Resampling occurs with the selected interpolation method.
|
/// Multiplier for sample rate. Resampling occurs with the selected interpolation method.
|
||||||
float_le rate_multiplier;
|
float_le rate_multiplier;
|
||||||
|
|
||||||
enum class InterpolationMode : u8 {
|
enum class InterpolationMode : u8 { Polyphase = 0, Linear = 1, None = 2 };
|
||||||
Polyphase = 0,
|
|
||||||
Linear = 1,
|
|
||||||
None = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
InterpolationMode interpolation_mode;
|
InterpolationMode interpolation_mode;
|
||||||
INSERT_PADDING_BYTES(1); ///< Interpolation related
|
INSERT_PADDING_BYTES(1); ///< Interpolation related
|
||||||
|
@ -191,7 +196,8 @@ struct SourceConfiguration {
|
||||||
* This is a normalised biquad filter (second-order).
|
* This is a normalised biquad filter (second-order).
|
||||||
* The transfer function of this filter is:
|
* The transfer function of this filter is:
|
||||||
* H(z) = (b0 + b1 z^-1 + b2 z^-2) / (1 - a1 z^-1 - a2 z^-2)
|
* H(z) = (b0 + b1 z^-1 + b2 z^-2) / (1 - a1 z^-1 - a2 z^-2)
|
||||||
* Nintendo chose to negate the feedbackward coefficients. This differs from standard notation
|
* Nintendo chose to negate the feedbackward coefficients. This differs from standard
|
||||||
|
* notation
|
||||||
* as in: https://ccrma.stanford.edu/~jos/filters/Direct_Form_I.html
|
* as in: https://ccrma.stanford.edu/~jos/filters/Direct_Form_I.html
|
||||||
* Values are signed fixed point with 14 fractional bits.
|
* Values are signed fixed point with 14 fractional bits.
|
||||||
*/
|
*/
|
||||||
|
@ -239,7 +245,8 @@ struct SourceConfiguration {
|
||||||
/// Is a looping buffer.
|
/// Is a looping buffer.
|
||||||
u8 is_looping;
|
u8 is_looping;
|
||||||
|
|
||||||
/// This value is shown in SourceStatus::previous_buffer_id when this buffer has finished.
|
/// This value is shown in SourceStatus::previous_buffer_id when this buffer has
|
||||||
|
/// finished.
|
||||||
/// This allows the emulated application to tell what buffer is currently playing
|
/// This allows the emulated application to tell what buffer is currently playing
|
||||||
u16_le buffer_id;
|
u16_le buffer_id;
|
||||||
|
|
||||||
|
@ -268,16 +275,9 @@ struct SourceConfiguration {
|
||||||
/// Note a sample takes up different number of bytes in different buffer formats.
|
/// Note a sample takes up different number of bytes in different buffer formats.
|
||||||
u32_dsp length;
|
u32_dsp length;
|
||||||
|
|
||||||
enum class MonoOrStereo : u16_le {
|
enum class MonoOrStereo : u16_le { Mono = 1, Stereo = 2 };
|
||||||
Mono = 1,
|
|
||||||
Stereo = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Format : u16_le {
|
enum class Format : u16_le { PCM8 = 0, PCM16 = 1, ADPCM = 2 };
|
||||||
PCM8 = 0,
|
|
||||||
PCM16 = 1,
|
|
||||||
ADPCM = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
union {
|
union {
|
||||||
u16_le flags1_raw;
|
u16_le flags1_raw;
|
||||||
|
@ -302,7 +302,8 @@ struct SourceConfiguration {
|
||||||
BitField<1, 1, u16_le> is_looping; ///< Is this a looping buffer?
|
BitField<1, 1, u16_le> is_looping; ///< Is this a looping buffer?
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Buffer id of embedded buffer (used as a buffer id in SourceStatus to reference this buffer).
|
/// Buffer id of embedded buffer (used as a buffer id in SourceStatus to reference this
|
||||||
|
/// buffer).
|
||||||
u16_le buffer_id;
|
u16_le buffer_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -347,16 +348,13 @@ struct DspConfiguration {
|
||||||
BitField<28, 1, u32_le> headphones_connected_dirty;
|
BitField<28, 1, u32_le> headphones_connected_dirty;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The DSP has three intermediate audio mixers. This controls the volume level (0.0-1.0) for each at the final mixer
|
/// The DSP has three intermediate audio mixers. This controls the volume level (0.0-1.0) for
|
||||||
|
/// each at the final mixer
|
||||||
float_le volume[3];
|
float_le volume[3];
|
||||||
|
|
||||||
INSERT_PADDING_DSPWORDS(3);
|
INSERT_PADDING_DSPWORDS(3);
|
||||||
|
|
||||||
enum class OutputFormat : u16_le {
|
enum class OutputFormat : u16_le { Mono = 0, Stereo = 1, Surround = 2 };
|
||||||
Mono = 0,
|
|
||||||
Stereo = 1,
|
|
||||||
Surround = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
OutputFormat output_format;
|
OutputFormat output_format;
|
||||||
|
|
||||||
|
@ -388,7 +386,8 @@ struct DspConfiguration {
|
||||||
u16_le enable;
|
u16_le enable;
|
||||||
INSERT_PADDING_DSPWORDS(1);
|
INSERT_PADDING_DSPWORDS(1);
|
||||||
u16_le outputs;
|
u16_le outputs;
|
||||||
u32_dsp work_buffer_address; ///< The application allocates a block of memory for the DSP to use as a work buffer.
|
u32_dsp work_buffer_address; ///< The application allocates a block of memory for the DSP to
|
||||||
|
/// use as a work buffer.
|
||||||
u16_le frame_count; ///< Frames to delay by
|
u16_le frame_count; ///< Frames to delay by
|
||||||
|
|
||||||
// Coefficients
|
// Coefficients
|
||||||
|
@ -506,21 +505,36 @@ ASSERT_DSP_STRUCT(SharedMemory, 0x8000);
|
||||||
extern std::array<SharedMemory, 2> g_regions;
|
extern std::array<SharedMemory, 2> g_regions;
|
||||||
|
|
||||||
// Structures must have an offset that is a multiple of two.
|
// Structures must have an offset that is a multiple of two.
|
||||||
static_assert(offsetof(SharedMemory, frame_counter) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
static_assert(offsetof(SharedMemory, frame_counter) % 2 == 0,
|
||||||
static_assert(offsetof(SharedMemory, source_configurations) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
static_assert(offsetof(SharedMemory, source_statuses) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
static_assert(offsetof(SharedMemory, source_configurations) % 2 == 0,
|
||||||
static_assert(offsetof(SharedMemory, adpcm_coefficients) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
static_assert(offsetof(SharedMemory, dsp_configuration) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
static_assert(offsetof(SharedMemory, source_statuses) % 2 == 0,
|
||||||
static_assert(offsetof(SharedMemory, dsp_status) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
static_assert(offsetof(SharedMemory, final_samples) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
static_assert(offsetof(SharedMemory, adpcm_coefficients) % 2 == 0,
|
||||||
static_assert(offsetof(SharedMemory, intermediate_mix_samples) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
static_assert(offsetof(SharedMemory, compressor) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
static_assert(offsetof(SharedMemory, dsp_configuration) % 2 == 0,
|
||||||
static_assert(offsetof(SharedMemory, dsp_debug) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
static_assert(offsetof(SharedMemory, unknown10) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
static_assert(offsetof(SharedMemory, dsp_status) % 2 == 0,
|
||||||
static_assert(offsetof(SharedMemory, unknown11) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
static_assert(offsetof(SharedMemory, unknown12) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
static_assert(offsetof(SharedMemory, final_samples) % 2 == 0,
|
||||||
static_assert(offsetof(SharedMemory, unknown13) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
static_assert(offsetof(SharedMemory, unknown14) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
static_assert(offsetof(SharedMemory, intermediate_mix_samples) % 2 == 0,
|
||||||
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
|
static_assert(offsetof(SharedMemory, compressor) % 2 == 0,
|
||||||
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
|
static_assert(offsetof(SharedMemory, dsp_debug) % 2 == 0,
|
||||||
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
|
static_assert(offsetof(SharedMemory, unknown10) % 2 == 0,
|
||||||
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
|
static_assert(offsetof(SharedMemory, unknown11) % 2 == 0,
|
||||||
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
|
static_assert(offsetof(SharedMemory, unknown12) % 2 == 0,
|
||||||
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
|
static_assert(offsetof(SharedMemory, unknown13) % 2 == 0,
|
||||||
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
|
static_assert(offsetof(SharedMemory, unknown14) % 2 == 0,
|
||||||
|
"Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
|
||||||
|
|
||||||
#undef INSERT_PADDING_DSPWORDS
|
#undef INSERT_PADDING_DSPWORDS
|
||||||
#undef ASSERT_DSP_STRUCT
|
#undef ASSERT_DSP_STRUCT
|
||||||
|
|
|
@ -59,7 +59,8 @@ void SourceFilters::SimpleFilter::Reset() {
|
||||||
b0 = 1 << 15;
|
b0 = 1 << 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SourceFilters::SimpleFilter::Configure(SourceConfiguration::Configuration::SimpleFilter config) {
|
void SourceFilters::SimpleFilter::Configure(
|
||||||
|
SourceConfiguration::Configuration::SimpleFilter config) {
|
||||||
a1 = config.a1;
|
a1 = config.a1;
|
||||||
b0 = config.b0;
|
b0 = config.b0;
|
||||||
}
|
}
|
||||||
|
@ -88,7 +89,8 @@ void SourceFilters::BiquadFilter::Reset() {
|
||||||
b0 = 1 << 14;
|
b0 = 1 << 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SourceFilters::BiquadFilter::Configure(SourceConfiguration::Configuration::BiquadFilter config) {
|
void SourceFilters::BiquadFilter::Configure(
|
||||||
|
SourceConfiguration::Configuration::BiquadFilter config) {
|
||||||
a1 = config.a1;
|
a1 = config.a1;
|
||||||
a2 = config.a2;
|
a2 = config.a2;
|
||||||
b0 = config.b0;
|
b0 = config.b0;
|
||||||
|
|
|
@ -17,7 +17,9 @@ namespace HLE {
|
||||||
/// Preprocessing filters. There is an independent set of filters for each Source.
|
/// Preprocessing filters. There is an independent set of filters for each Source.
|
||||||
class SourceFilters final {
|
class SourceFilters final {
|
||||||
public:
|
public:
|
||||||
SourceFilters() { Reset(); }
|
SourceFilters() {
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
/// Reset internal state.
|
/// Reset internal state.
|
||||||
void Reset();
|
void Reset();
|
||||||
|
@ -54,7 +56,9 @@ private:
|
||||||
bool biquad_filter_enabled;
|
bool biquad_filter_enabled;
|
||||||
|
|
||||||
struct SimpleFilter {
|
struct SimpleFilter {
|
||||||
SimpleFilter() { Reset(); }
|
SimpleFilter() {
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
/// Resets internal state.
|
/// Resets internal state.
|
||||||
void Reset();
|
void Reset();
|
||||||
|
@ -80,7 +84,9 @@ private:
|
||||||
} simple_filter;
|
} simple_filter;
|
||||||
|
|
||||||
struct BiquadFilter {
|
struct BiquadFilter {
|
||||||
BiquadFilter() { Reset(); }
|
BiquadFilter() {
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
/// Resets internal state.
|
/// Resets internal state.
|
||||||
void Reset();
|
void Reset();
|
||||||
|
|
|
@ -20,11 +20,9 @@ void Mixers::Reset() {
|
||||||
state = {};
|
state = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
DspStatus Mixers::Tick(DspConfiguration& config,
|
DspStatus Mixers::Tick(DspConfiguration& config, const IntermediateMixSamples& read_samples,
|
||||||
const IntermediateMixSamples& read_samples,
|
|
||||||
IntermediateMixSamples& write_samples,
|
IntermediateMixSamples& write_samples,
|
||||||
const std::array<QuadFrame32, 3>& input)
|
const std::array<QuadFrame32, 3>& input) {
|
||||||
{
|
|
||||||
ParseConfig(config);
|
ParseConfig(config);
|
||||||
|
|
||||||
AuxReturn(read_samples);
|
AuxReturn(read_samples);
|
||||||
|
@ -73,13 +71,15 @@ void Mixers::ParseConfig(DspConfiguration& config) {
|
||||||
if (config.output_format_dirty) {
|
if (config.output_format_dirty) {
|
||||||
config.output_format_dirty.Assign(0);
|
config.output_format_dirty.Assign(0);
|
||||||
state.output_format = config.output_format;
|
state.output_format = config.output_format;
|
||||||
LOG_TRACE(Audio_DSP, "mixers output_format = %zu", static_cast<size_t>(config.output_format));
|
LOG_TRACE(Audio_DSP, "mixers output_format = %zu",
|
||||||
|
static_cast<size_t>(config.output_format));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.headphones_connected_dirty) {
|
if (config.headphones_connected_dirty) {
|
||||||
config.headphones_connected_dirty.Assign(0);
|
config.headphones_connected_dirty.Assign(0);
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
// (Note: Whether headphones are connected does affect coefficients used for surround sound.)
|
// (Note: Whether headphones are connected does affect coefficients used for surround
|
||||||
|
// sound.)
|
||||||
LOG_TRACE(Audio_DSP, "mixers headphones_connected=%hu", config.headphones_connected);
|
LOG_TRACE(Audio_DSP, "mixers headphones_connected=%hu", config.headphones_connected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,11 +94,10 @@ static s16 ClampToS16(s32 value) {
|
||||||
return static_cast<s16>(MathUtil::Clamp(value, -32768, 32767));
|
return static_cast<s16>(MathUtil::Clamp(value, -32768, 32767));
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::array<s16, 2> AddAndClampToS16(const std::array<s16, 2>& a, const std::array<s16, 2>& b) {
|
static std::array<s16, 2> AddAndClampToS16(const std::array<s16, 2>& a,
|
||||||
return {
|
const std::array<s16, 2>& b) {
|
||||||
ClampToS16(static_cast<s32>(a[0]) + static_cast<s32>(b[0])),
|
return {ClampToS16(static_cast<s32>(a[0]) + static_cast<s32>(b[0])),
|
||||||
ClampToS16(static_cast<s32>(a[1]) + static_cast<s32>(b[1]))
|
ClampToS16(static_cast<s32>(a[1]) + static_cast<s32>(b[1]))};
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mixers::DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& samples) {
|
void Mixers::DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& samples) {
|
||||||
|
@ -106,12 +105,16 @@ void Mixers::DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& sample
|
||||||
|
|
||||||
switch (state.output_format) {
|
switch (state.output_format) {
|
||||||
case OutputFormat::Mono:
|
case OutputFormat::Mono:
|
||||||
std::transform(current_frame.begin(), current_frame.end(), samples.begin(), current_frame.begin(),
|
std::transform(
|
||||||
[gain](const std::array<s16, 2>& accumulator, const std::array<s32, 4>& sample) -> std::array<s16, 2> {
|
current_frame.begin(), current_frame.end(), samples.begin(), current_frame.begin(),
|
||||||
|
[gain](const std::array<s16, 2>& accumulator,
|
||||||
|
const std::array<s32, 4>& sample) -> std::array<s16, 2> {
|
||||||
// Downmix to mono
|
// Downmix to mono
|
||||||
s16 mono = ClampToS16(static_cast<s32>((gain * sample[0] + gain * sample[1] + gain * sample[2] + gain * sample[3]) / 2));
|
s16 mono = ClampToS16(static_cast<s32>(
|
||||||
|
(gain * sample[0] + gain * sample[1] + gain * sample[2] + gain * sample[3]) /
|
||||||
|
2));
|
||||||
// Mix into current frame
|
// Mix into current frame
|
||||||
return AddAndClampToS16(accumulator, { mono, mono });
|
return AddAndClampToS16(accumulator, {mono, mono});
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -120,13 +123,15 @@ void Mixers::DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& sample
|
||||||
// fallthrough
|
// fallthrough
|
||||||
|
|
||||||
case OutputFormat::Stereo:
|
case OutputFormat::Stereo:
|
||||||
std::transform(current_frame.begin(), current_frame.end(), samples.begin(), current_frame.begin(),
|
std::transform(
|
||||||
[gain](const std::array<s16, 2>& accumulator, const std::array<s32, 4>& sample) -> std::array<s16, 2> {
|
current_frame.begin(), current_frame.end(), samples.begin(), current_frame.begin(),
|
||||||
|
[gain](const std::array<s16, 2>& accumulator,
|
||||||
|
const std::array<s32, 4>& sample) -> std::array<s16, 2> {
|
||||||
// Downmix to stereo
|
// Downmix to stereo
|
||||||
s16 left = ClampToS16(static_cast<s32>(gain * sample[0] + gain * sample[2]));
|
s16 left = ClampToS16(static_cast<s32>(gain * sample[0] + gain * sample[2]));
|
||||||
s16 right = ClampToS16(static_cast<s32>(gain * sample[1] + gain * sample[3]));
|
s16 right = ClampToS16(static_cast<s32>(gain * sample[1] + gain * sample[3]));
|
||||||
// Mix into current frame
|
// Mix into current frame
|
||||||
return AddAndClampToS16(accumulator, { left, right });
|
return AddAndClampToS16(accumulator, {left, right});
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -135,12 +140,14 @@ void Mixers::DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& sample
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mixers::AuxReturn(const IntermediateMixSamples& read_samples) {
|
void Mixers::AuxReturn(const IntermediateMixSamples& read_samples) {
|
||||||
// NOTE: read_samples.mix{1,2}.pcm32 annoyingly have their dimensions in reverse order to QuadFrame32.
|
// NOTE: read_samples.mix{1,2}.pcm32 annoyingly have their dimensions in reverse order to
|
||||||
|
// QuadFrame32.
|
||||||
|
|
||||||
if (state.mixer1_enabled) {
|
if (state.mixer1_enabled) {
|
||||||
for (size_t sample = 0; sample < samples_per_frame; sample++) {
|
for (size_t sample = 0; sample < samples_per_frame; sample++) {
|
||||||
for (size_t channel = 0; channel < 4; channel++) {
|
for (size_t channel = 0; channel < 4; channel++) {
|
||||||
state.intermediate_mix_buffer[1][sample][channel] = read_samples.mix1.pcm32[channel][sample];
|
state.intermediate_mix_buffer[1][sample][channel] =
|
||||||
|
read_samples.mix1.pcm32[channel][sample];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,14 +155,17 @@ void Mixers::AuxReturn(const IntermediateMixSamples& read_samples) {
|
||||||
if (state.mixer2_enabled) {
|
if (state.mixer2_enabled) {
|
||||||
for (size_t sample = 0; sample < samples_per_frame; sample++) {
|
for (size_t sample = 0; sample < samples_per_frame; sample++) {
|
||||||
for (size_t channel = 0; channel < 4; channel++) {
|
for (size_t channel = 0; channel < 4; channel++) {
|
||||||
state.intermediate_mix_buffer[2][sample][channel] = read_samples.mix2.pcm32[channel][sample];
|
state.intermediate_mix_buffer[2][sample][channel] =
|
||||||
|
read_samples.mix2.pcm32[channel][sample];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mixers::AuxSend(IntermediateMixSamples& write_samples, const std::array<QuadFrame32, 3>& input) {
|
void Mixers::AuxSend(IntermediateMixSamples& write_samples,
|
||||||
// NOTE: read_samples.mix{1,2}.pcm32 annoyingly have their dimensions in reverse order to QuadFrame32.
|
const std::array<QuadFrame32, 3>& input) {
|
||||||
|
// NOTE: read_samples.mix{1,2}.pcm32 annoyingly have their dimensions in reverse order to
|
||||||
|
// QuadFrame32.
|
||||||
|
|
||||||
state.intermediate_mix_buffer[0] = input[0];
|
state.intermediate_mix_buffer[0] = input[0];
|
||||||
|
|
||||||
|
@ -184,7 +194,8 @@ void Mixers::MixCurrentFrame() {
|
||||||
current_frame.fill({});
|
current_frame.fill({});
|
||||||
|
|
||||||
for (size_t mix = 0; mix < 3; mix++) {
|
for (size_t mix = 0; mix < 3; mix++) {
|
||||||
DownmixAndMixIntoCurrentFrame(state.intermediate_mixer_volume[mix], state.intermediate_mix_buffer[mix]);
|
DownmixAndMixIntoCurrentFrame(state.intermediate_mixer_volume[mix],
|
||||||
|
state.intermediate_mix_buffer[mix]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(merry): Compressor. (We currently assume a disabled compressor.)
|
// TODO(merry): Compressor. (We currently assume a disabled compressor.)
|
||||||
|
|
|
@ -20,10 +20,8 @@ public:
|
||||||
|
|
||||||
void Reset();
|
void Reset();
|
||||||
|
|
||||||
DspStatus Tick(DspConfiguration& config,
|
DspStatus Tick(DspConfiguration& config, const IntermediateMixSamples& read_samples,
|
||||||
const IntermediateMixSamples& read_samples,
|
IntermediateMixSamples& write_samples, const std::array<QuadFrame32, 3>& input);
|
||||||
IntermediateMixSamples& write_samples,
|
|
||||||
const std::array<QuadFrame32, 3>& input);
|
|
||||||
|
|
||||||
StereoFrame16 GetOutput() const {
|
StereoFrame16 GetOutput() const {
|
||||||
return current_frame;
|
return current_frame;
|
||||||
|
@ -53,7 +51,8 @@ private:
|
||||||
void AuxSend(IntermediateMixSamples& write_samples, const std::array<QuadFrame32, 3>& input);
|
void AuxSend(IntermediateMixSamples& write_samples, const std::array<QuadFrame32, 3>& input);
|
||||||
/// INTERNAL: Mix current_frame.
|
/// INTERNAL: Mix current_frame.
|
||||||
void MixCurrentFrame();
|
void MixCurrentFrame();
|
||||||
/// INTERNAL: Downmix from quadraphonic to stereo based on status.output_format and accumulate into current_frame.
|
/// INTERNAL: Downmix from quadraphonic to stereo based on status.output_format and accumulate
|
||||||
|
/// into current_frame.
|
||||||
void DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& samples);
|
void DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& samples);
|
||||||
/// INTERNAL: Generate DspStatus based on internal state.
|
/// INTERNAL: Generate DspStatus based on internal state.
|
||||||
DspStatus GetCurrentStatus() const;
|
DspStatus GetCurrentStatus() const;
|
||||||
|
|
|
@ -44,7 +44,9 @@ std::vector<u8> PipeRead(DspPipe pipe_number, u32 length) {
|
||||||
std::vector<u8>& data = pipe_data[pipe_index];
|
std::vector<u8>& data = pipe_data[pipe_index];
|
||||||
|
|
||||||
if (length > data.size()) {
|
if (length > data.size()) {
|
||||||
LOG_WARNING(Audio_DSP, "pipe_number = %zu is out of data, application requested read of %u but %zu remain",
|
LOG_WARNING(
|
||||||
|
Audio_DSP,
|
||||||
|
"pipe_number = %zu is out of data, application requested read of %u but %zu remain",
|
||||||
pipe_index, length, data.size());
|
pipe_index, length, data.size());
|
||||||
length = static_cast<u32>(data.size());
|
length = static_cast<u32>(data.size());
|
||||||
}
|
}
|
||||||
|
@ -95,8 +97,7 @@ static void AudioPipeWriteStructAddresses() {
|
||||||
0x8000 + offsetof(SharedMemory, unknown11) / 2,
|
0x8000 + offsetof(SharedMemory, unknown11) / 2,
|
||||||
0x8000 + offsetof(SharedMemory, unknown12) / 2,
|
0x8000 + offsetof(SharedMemory, unknown12) / 2,
|
||||||
0x8000 + offsetof(SharedMemory, unknown13) / 2,
|
0x8000 + offsetof(SharedMemory, unknown13) / 2,
|
||||||
0x8000 + offsetof(SharedMemory, unknown14) / 2
|
0x8000 + offsetof(SharedMemory, unknown14) / 2};
|
||||||
};
|
|
||||||
|
|
||||||
// Begin with a u16 denoting the number of structs.
|
// Begin with a u16 denoting the number of structs.
|
||||||
WriteU16(DspPipe::Audio, static_cast<u16>(struct_addresses.size()));
|
WriteU16(DspPipe::Audio, static_cast<u16>(struct_addresses.size()));
|
||||||
|
@ -112,16 +113,12 @@ void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) {
|
||||||
switch (pipe_number) {
|
switch (pipe_number) {
|
||||||
case DspPipe::Audio: {
|
case DspPipe::Audio: {
|
||||||
if (buffer.size() != 4) {
|
if (buffer.size() != 4) {
|
||||||
LOG_ERROR(Audio_DSP, "DspPipe::Audio: Unexpected buffer length %zu was written", buffer.size());
|
LOG_ERROR(Audio_DSP, "DspPipe::Audio: Unexpected buffer length %zu was written",
|
||||||
|
buffer.size());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class StateChange {
|
enum class StateChange { Initalize = 0, Shutdown = 1, Wakeup = 2, Sleep = 3 };
|
||||||
Initalize = 0,
|
|
||||||
Shutdown = 1,
|
|
||||||
Wakeup = 2,
|
|
||||||
Sleep = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
// The difference between Initialize and Wakeup is that Input state is maintained
|
// The difference between Initialize and Wakeup is that Input state is maintained
|
||||||
// when sleeping but isn't when turning it off and on again. (TODO: Implement this.)
|
// when sleeping but isn't when turning it off and on again. (TODO: Implement this.)
|
||||||
|
@ -152,7 +149,9 @@ void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) {
|
||||||
dsp_state = DspState::Sleeping;
|
dsp_state = DspState::Sleeping;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOG_ERROR(Audio_DSP, "Application has requested unknown state transition of DSP hardware %hhu", buffer[0]);
|
LOG_ERROR(Audio_DSP,
|
||||||
|
"Application has requested unknown state transition of DSP hardware %hhu",
|
||||||
|
buffer[0]);
|
||||||
dsp_state = DspState::Off;
|
dsp_state = DspState::Off;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -160,7 +159,8 @@ void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
LOG_CRITICAL(Audio_DSP, "pipe_number = %zu unimplemented", static_cast<size_t>(pipe_number));
|
LOG_CRITICAL(Audio_DSP, "pipe_number = %zu unimplemented",
|
||||||
|
static_cast<size_t>(pipe_number));
|
||||||
UNIMPLEMENTED();
|
UNIMPLEMENTED();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,20 +15,17 @@ namespace HLE {
|
||||||
/// Reset the pipes by setting pipe positions back to the beginning.
|
/// Reset the pipes by setting pipe positions back to the beginning.
|
||||||
void ResetPipes();
|
void ResetPipes();
|
||||||
|
|
||||||
enum class DspPipe {
|
enum class DspPipe { Debug = 0, Dma = 1, Audio = 2, Binary = 3 };
|
||||||
Debug = 0,
|
|
||||||
Dma = 1,
|
|
||||||
Audio = 2,
|
|
||||||
Binary = 3
|
|
||||||
};
|
|
||||||
constexpr size_t NUM_DSP_PIPE = 8;
|
constexpr size_t NUM_DSP_PIPE = 8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads `length` bytes from the DSP pipe identified with `pipe_number`.
|
* Reads `length` bytes from the DSP pipe identified with `pipe_number`.
|
||||||
* @note Can read up to the maximum value of a u16 in bytes (65,535).
|
* @note Can read up to the maximum value of a u16 in bytes (65,535).
|
||||||
* @note IF an error is encoutered with either an invalid `pipe_number` or `length` value, an empty vector will be returned.
|
* @note IF an error is encoutered with either an invalid `pipe_number` or `length` value, an empty
|
||||||
|
* vector will be returned.
|
||||||
* @note IF `length` is set to 0, an empty vector will be returned.
|
* @note IF `length` is set to 0, an empty vector will be returned.
|
||||||
* @note IF `length` is greater than the amount of data available, this function will only read the available amount.
|
* @note IF `length` is greater than the amount of data available, this function will only read the
|
||||||
|
* available amount.
|
||||||
* @param pipe_number a `DspPipe`
|
* @param pipe_number a `DspPipe`
|
||||||
* @param length the number of bytes to read. The max is 65,535 (max of u16).
|
* @param length the number of bytes to read. The max is 65,535 (max of u16).
|
||||||
* @returns a vector of bytes from the specified pipe. On error, will be empty.
|
* @returns a vector of bytes from the specified pipe. On error, will be empty.
|
||||||
|
@ -49,11 +46,7 @@ size_t GetPipeReadableSize(DspPipe pipe_number);
|
||||||
*/
|
*/
|
||||||
void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer);
|
void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer);
|
||||||
|
|
||||||
enum class DspState {
|
enum class DspState { Off, On, Sleeping };
|
||||||
Off,
|
|
||||||
On,
|
|
||||||
Sleeping
|
|
||||||
};
|
|
||||||
/// Get the state of the DSP
|
/// Get the state of the DSP
|
||||||
DspState GetDspState();
|
DspState GetDspState();
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
namespace DSP {
|
namespace DSP {
|
||||||
namespace HLE {
|
namespace HLE {
|
||||||
|
|
||||||
SourceStatus::Status Source::Tick(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]) {
|
SourceStatus::Status Source::Tick(SourceConfiguration::Configuration& config,
|
||||||
|
const s16_le (&adpcm_coeffs)[16]) {
|
||||||
ParseConfig(config, adpcm_coeffs);
|
ParseConfig(config, adpcm_coeffs);
|
||||||
|
|
||||||
if (state.enabled) {
|
if (state.enabled) {
|
||||||
|
@ -47,7 +48,8 @@ void Source::Reset() {
|
||||||
state = {};
|
state = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]) {
|
void Source::ParseConfig(SourceConfiguration::Configuration& config,
|
||||||
|
const s16_le (&adpcm_coeffs)[16]) {
|
||||||
if (!config.dirty_raw) {
|
if (!config.dirty_raw) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -82,7 +84,8 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_l
|
||||||
LOG_TRACE(Audio_DSP, "source_id=%zu rate=%f", source_id, state.rate_multiplier);
|
LOG_TRACE(Audio_DSP, "source_id=%zu rate=%f", source_id, state.rate_multiplier);
|
||||||
|
|
||||||
if (state.rate_multiplier <= 0) {
|
if (state.rate_multiplier <= 0) {
|
||||||
LOG_ERROR(Audio_DSP, "Was given an invalid rate multiplier: source_id=%zu rate=%f", source_id, state.rate_multiplier);
|
LOG_ERROR(Audio_DSP, "Was given an invalid rate multiplier: source_id=%zu rate=%f",
|
||||||
|
source_id, state.rate_multiplier);
|
||||||
state.rate_multiplier = 1.0f;
|
state.rate_multiplier = 1.0f;
|
||||||
// Note: Actual firmware starts producing garbage if this occurs.
|
// Note: Actual firmware starts producing garbage if this occurs.
|
||||||
}
|
}
|
||||||
|
@ -90,7 +93,8 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_l
|
||||||
|
|
||||||
if (config.adpcm_coefficients_dirty) {
|
if (config.adpcm_coefficients_dirty) {
|
||||||
config.adpcm_coefficients_dirty.Assign(0);
|
config.adpcm_coefficients_dirty.Assign(0);
|
||||||
std::transform(adpcm_coeffs, adpcm_coeffs + state.adpcm_coeffs.size(), state.adpcm_coeffs.begin(),
|
std::transform(adpcm_coeffs, adpcm_coeffs + state.adpcm_coeffs.size(),
|
||||||
|
state.adpcm_coeffs.begin(),
|
||||||
[](const auto& coeff) { return static_cast<s16>(coeff); });
|
[](const auto& coeff) { return static_cast<s16>(coeff); });
|
||||||
LOG_TRACE(Audio_DSP, "source_id=%zu adpcm update", source_id);
|
LOG_TRACE(Audio_DSP, "source_id=%zu adpcm update", source_id);
|
||||||
}
|
}
|
||||||
|
@ -118,9 +122,10 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_l
|
||||||
|
|
||||||
if (config.filters_enabled_dirty) {
|
if (config.filters_enabled_dirty) {
|
||||||
config.filters_enabled_dirty.Assign(0);
|
config.filters_enabled_dirty.Assign(0);
|
||||||
state.filters.Enable(config.simple_filter_enabled.ToBool(), config.biquad_filter_enabled.ToBool());
|
state.filters.Enable(config.simple_filter_enabled.ToBool(),
|
||||||
LOG_TRACE(Audio_DSP, "source_id=%zu enable_simple=%hu enable_biquad=%hu",
|
config.biquad_filter_enabled.ToBool());
|
||||||
source_id, config.simple_filter_enabled.Value(), config.biquad_filter_enabled.Value());
|
LOG_TRACE(Audio_DSP, "source_id=%zu enable_simple=%hu enable_biquad=%hu", source_id,
|
||||||
|
config.simple_filter_enabled.Value(), config.biquad_filter_enabled.Value());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.simple_filter_dirty) {
|
if (config.simple_filter_dirty) {
|
||||||
|
@ -138,36 +143,38 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_l
|
||||||
if (config.interpolation_dirty) {
|
if (config.interpolation_dirty) {
|
||||||
config.interpolation_dirty.Assign(0);
|
config.interpolation_dirty.Assign(0);
|
||||||
state.interpolation_mode = config.interpolation_mode;
|
state.interpolation_mode = config.interpolation_mode;
|
||||||
LOG_TRACE(Audio_DSP, "source_id=%zu interpolation_mode=%zu", source_id, static_cast<size_t>(state.interpolation_mode));
|
LOG_TRACE(Audio_DSP, "source_id=%zu interpolation_mode=%zu", source_id,
|
||||||
|
static_cast<size_t>(state.interpolation_mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.format_dirty || config.embedded_buffer_dirty) {
|
if (config.format_dirty || config.embedded_buffer_dirty) {
|
||||||
config.format_dirty.Assign(0);
|
config.format_dirty.Assign(0);
|
||||||
state.format = config.format;
|
state.format = config.format;
|
||||||
LOG_TRACE(Audio_DSP, "source_id=%zu format=%zu", source_id, static_cast<size_t>(state.format));
|
LOG_TRACE(Audio_DSP, "source_id=%zu format=%zu", source_id,
|
||||||
|
static_cast<size_t>(state.format));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.mono_or_stereo_dirty || config.embedded_buffer_dirty) {
|
if (config.mono_or_stereo_dirty || config.embedded_buffer_dirty) {
|
||||||
config.mono_or_stereo_dirty.Assign(0);
|
config.mono_or_stereo_dirty.Assign(0);
|
||||||
state.mono_or_stereo = config.mono_or_stereo;
|
state.mono_or_stereo = config.mono_or_stereo;
|
||||||
LOG_TRACE(Audio_DSP, "source_id=%zu mono_or_stereo=%zu", source_id, static_cast<size_t>(state.mono_or_stereo));
|
LOG_TRACE(Audio_DSP, "source_id=%zu mono_or_stereo=%zu", source_id,
|
||||||
|
static_cast<size_t>(state.mono_or_stereo));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.embedded_buffer_dirty) {
|
if (config.embedded_buffer_dirty) {
|
||||||
config.embedded_buffer_dirty.Assign(0);
|
config.embedded_buffer_dirty.Assign(0);
|
||||||
state.input_queue.emplace(Buffer{
|
state.input_queue.emplace(Buffer{config.physical_address,
|
||||||
config.physical_address,
|
|
||||||
config.length,
|
config.length,
|
||||||
static_cast<u8>(config.adpcm_ps),
|
static_cast<u8>(config.adpcm_ps),
|
||||||
{ config.adpcm_yn[0], config.adpcm_yn[1] },
|
{config.adpcm_yn[0], config.adpcm_yn[1]},
|
||||||
config.adpcm_dirty.ToBool(),
|
config.adpcm_dirty.ToBool(),
|
||||||
config.is_looping.ToBool(),
|
config.is_looping.ToBool(),
|
||||||
config.buffer_id,
|
config.buffer_id,
|
||||||
state.mono_or_stereo,
|
state.mono_or_stereo,
|
||||||
state.format,
|
state.format,
|
||||||
false
|
false});
|
||||||
});
|
LOG_TRACE(Audio_DSP, "enqueuing embedded addr=0x%08x len=%u id=%hu",
|
||||||
LOG_TRACE(Audio_DSP, "enqueuing embedded addr=0x%08x len=%u id=%hu", config.physical_address, config.length, config.buffer_id);
|
config.physical_address, config.length, config.buffer_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.buffer_queue_dirty) {
|
if (config.buffer_queue_dirty) {
|
||||||
|
@ -175,19 +182,18 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_l
|
||||||
for (size_t i = 0; i < 4; i++) {
|
for (size_t i = 0; i < 4; i++) {
|
||||||
if (config.buffers_dirty & (1 << i)) {
|
if (config.buffers_dirty & (1 << i)) {
|
||||||
const auto& b = config.buffers[i];
|
const auto& b = config.buffers[i];
|
||||||
state.input_queue.emplace(Buffer{
|
state.input_queue.emplace(Buffer{b.physical_address,
|
||||||
b.physical_address,
|
|
||||||
b.length,
|
b.length,
|
||||||
static_cast<u8>(b.adpcm_ps),
|
static_cast<u8>(b.adpcm_ps),
|
||||||
{ b.adpcm_yn[0], b.adpcm_yn[1] },
|
{b.adpcm_yn[0], b.adpcm_yn[1]},
|
||||||
b.adpcm_dirty != 0,
|
b.adpcm_dirty != 0,
|
||||||
b.is_looping != 0,
|
b.is_looping != 0,
|
||||||
b.buffer_id,
|
b.buffer_id,
|
||||||
state.mono_or_stereo,
|
state.mono_or_stereo,
|
||||||
state.format,
|
state.format,
|
||||||
true
|
true});
|
||||||
});
|
LOG_TRACE(Audio_DSP, "enqueuing queued %zu addr=0x%08x len=%u id=%hu", i,
|
||||||
LOG_TRACE(Audio_DSP, "enqueuing queued %zu addr=0x%08x len=%u id=%hu", i, b.physical_address, b.length, b.buffer_id);
|
b.physical_address, b.length, b.buffer_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.buffers_dirty = 0;
|
config.buffers_dirty = 0;
|
||||||
|
@ -218,10 +224,13 @@ void Source::GenerateFrame() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t size_to_copy = std::min(state.current_buffer.size(), current_frame.size() - frame_position);
|
const size_t size_to_copy =
|
||||||
|
std::min(state.current_buffer.size(), current_frame.size() - frame_position);
|
||||||
|
|
||||||
std::copy(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy, current_frame.begin() + frame_position);
|
std::copy(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy,
|
||||||
state.current_buffer.erase(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy);
|
current_frame.begin() + frame_position);
|
||||||
|
state.current_buffer.erase(state.current_buffer.begin(),
|
||||||
|
state.current_buffer.begin() + size_to_copy);
|
||||||
|
|
||||||
frame_position += size_to_copy;
|
frame_position += size_to_copy;
|
||||||
state.next_sample_number += static_cast<u32>(size_to_copy);
|
state.next_sample_number += static_cast<u32>(size_to_copy);
|
||||||
|
@ -230,9 +239,9 @@ void Source::GenerateFrame() {
|
||||||
state.filters.ProcessFrame(current_frame);
|
state.filters.ProcessFrame(current_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Source::DequeueBuffer() {
|
bool Source::DequeueBuffer() {
|
||||||
ASSERT_MSG(state.current_buffer.empty(), "Shouldn't dequeue; we still have data in current_buffer");
|
ASSERT_MSG(state.current_buffer.empty(),
|
||||||
|
"Shouldn't dequeue; we still have data in current_buffer");
|
||||||
|
|
||||||
if (state.input_queue.empty())
|
if (state.input_queue.empty())
|
||||||
return false;
|
return false;
|
||||||
|
@ -261,14 +270,16 @@ bool Source::DequeueBuffer() {
|
||||||
break;
|
break;
|
||||||
case Format::ADPCM:
|
case Format::ADPCM:
|
||||||
DEBUG_ASSERT(num_channels == 1);
|
DEBUG_ASSERT(num_channels == 1);
|
||||||
state.current_buffer = Codec::DecodeADPCM(memory, buf.length, state.adpcm_coeffs, state.adpcm_state);
|
state.current_buffer =
|
||||||
|
Codec::DecodeADPCM(memory, buf.length, state.adpcm_coeffs, state.adpcm_state);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
UNIMPLEMENTED();
|
UNIMPLEMENTED();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_WARNING(Audio_DSP, "source_id=%zu buffer_id=%hu length=%u: Invalid physical address 0x%08X",
|
LOG_WARNING(Audio_DSP,
|
||||||
|
"source_id=%zu buffer_id=%hu length=%u: Invalid physical address 0x%08X",
|
||||||
source_id, buf.buffer_id, buf.length, buf.physical_address);
|
source_id, buf.buffer_id, buf.length, buf.physical_address);
|
||||||
state.current_buffer.clear();
|
state.current_buffer.clear();
|
||||||
return true;
|
return true;
|
||||||
|
@ -276,14 +287,17 @@ bool Source::DequeueBuffer() {
|
||||||
|
|
||||||
switch (state.interpolation_mode) {
|
switch (state.interpolation_mode) {
|
||||||
case InterpolationMode::None:
|
case InterpolationMode::None:
|
||||||
state.current_buffer = AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier);
|
state.current_buffer =
|
||||||
|
AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier);
|
||||||
break;
|
break;
|
||||||
case InterpolationMode::Linear:
|
case InterpolationMode::Linear:
|
||||||
state.current_buffer = AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
|
state.current_buffer =
|
||||||
|
AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
|
||||||
break;
|
break;
|
||||||
case InterpolationMode::Polyphase:
|
case InterpolationMode::Polyphase:
|
||||||
// TODO(merry): Implement polyphase interpolation
|
// TODO(merry): Implement polyphase interpolation
|
||||||
state.current_buffer = AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
|
state.current_buffer =
|
||||||
|
AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
UNIMPLEMENTED();
|
UNIMPLEMENTED();
|
||||||
|
@ -296,7 +310,8 @@ bool Source::DequeueBuffer() {
|
||||||
state.buffer_update = buf.from_queue;
|
state.buffer_update = buf.from_queue;
|
||||||
|
|
||||||
LOG_TRACE(Audio_DSP, "source_id=%zu buffer_id=%hu from_queue=%s current_buffer.size()=%zu",
|
LOG_TRACE(Audio_DSP, "source_id=%zu buffer_id=%hu from_queue=%s current_buffer.size()=%zu",
|
||||||
source_id, buf.buffer_id, buf.from_queue ? "true" : "false", state.current_buffer.size());
|
source_id, buf.buffer_id, buf.from_queue ? "true" : "false",
|
||||||
|
state.current_buffer.size());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,13 +40,17 @@ public:
|
||||||
/**
|
/**
|
||||||
* This is called once every audio frame. This performs per-source processing every frame.
|
* This is called once every audio frame. This performs per-source processing every frame.
|
||||||
* @param config The new configuration we've got for this Source from the application.
|
* @param config The new configuration we've got for this Source from the application.
|
||||||
* @param adpcm_coeffs ADPCM coefficients to use if config tells us to use them (may contain invalid values otherwise).
|
* @param adpcm_coeffs ADPCM coefficients to use if config tells us to use them (may contain
|
||||||
* @return The current status of this Source. This is given back to the emulated application via SharedMemory.
|
* invalid values otherwise).
|
||||||
|
* @return The current status of this Source. This is given back to the emulated application via
|
||||||
|
* SharedMemory.
|
||||||
*/
|
*/
|
||||||
SourceStatus::Status Tick(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]);
|
SourceStatus::Status Tick(SourceConfiguration::Configuration& config,
|
||||||
|
const s16_le (&adpcm_coeffs)[16]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mix this source's output into dest, using the gains for the `intermediate_mix_id`-th intermediate mixer.
|
* Mix this source's output into dest, using the gains for the `intermediate_mix_id`-th
|
||||||
|
* intermediate mixer.
|
||||||
* @param dest The QuadFrame32 to mix into.
|
* @param dest The QuadFrame32 to mix into.
|
||||||
* @param intermediate_mix_id The id of the intermediate mix whose gains we are using.
|
* @param intermediate_mix_id The id of the intermediate mix whose gains we are using.
|
||||||
*/
|
*/
|
||||||
|
@ -77,7 +81,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BufferOrder {
|
struct BufferOrder {
|
||||||
bool operator() (const Buffer& a, const Buffer& b) const {
|
bool operator()(const Buffer& a, const Buffer& b) const {
|
||||||
// Lower buffer_id comes first.
|
// Lower buffer_id comes first.
|
||||||
return a.buffer_id > b.buffer_id;
|
return a.buffer_id > b.buffer_id;
|
||||||
}
|
}
|
||||||
|
@ -134,7 +138,8 @@ private:
|
||||||
void ParseConfig(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]);
|
void ParseConfig(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]);
|
||||||
/// INTERNAL: Generate the current audio output for this frame based on our internal state.
|
/// INTERNAL: Generate the current audio output for this frame based on our internal state.
|
||||||
void GenerateFrame();
|
void GenerateFrame();
|
||||||
/// INTERNAL: Dequeues a buffer and does preprocessing on it (decoding, resampling). Puts it into current_buffer.
|
/// INTERNAL: Dequeues a buffer and does preprocessing on it (decoding, resampling). Puts it
|
||||||
|
/// into current_buffer.
|
||||||
bool DequeueBuffer();
|
bool DequeueBuffer();
|
||||||
/// INTERNAL: Generates a SourceStatus::Status based on our internal state.
|
/// INTERNAL: Generates a SourceStatus::Status based on our internal state.
|
||||||
SourceStatus::Status GetCurrentStatus();
|
SourceStatus::Status GetCurrentStatus();
|
||||||
|
|
|
@ -17,7 +17,8 @@ constexpr u64 scale_mask = scale_factor - 1;
|
||||||
/// Here we step over the input in steps of rate_multiplier, until we consume all of the input.
|
/// Here we step over the input in steps of rate_multiplier, until we consume all of the input.
|
||||||
/// Three adjacent samples are passed to fn each step.
|
/// Three adjacent samples are passed to fn each step.
|
||||||
template <typename Function>
|
template <typename Function>
|
||||||
static StereoBuffer16 StepOverSamples(State& state, const StereoBuffer16& input, float rate_multiplier, Function fn) {
|
static StereoBuffer16 StepOverSamples(State& state, const StereoBuffer16& input,
|
||||||
|
float rate_multiplier, Function fn) {
|
||||||
ASSERT(rate_multiplier > 0);
|
ASSERT(rate_multiplier > 0);
|
||||||
|
|
||||||
if (input.size() < 2)
|
if (input.size() < 2)
|
||||||
|
@ -63,22 +64,21 @@ static StereoBuffer16 StepOverSamples(State& state, const StereoBuffer16& input,
|
||||||
}
|
}
|
||||||
|
|
||||||
StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier) {
|
StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier) {
|
||||||
return StepOverSamples(state, input, rate_multiplier, [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) {
|
return StepOverSamples(
|
||||||
return x0;
|
state, input, rate_multiplier,
|
||||||
});
|
[](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; });
|
||||||
}
|
}
|
||||||
|
|
||||||
StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier) {
|
StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier) {
|
||||||
// Note on accuracy: Some values that this produces are +/- 1 from the actual firmware.
|
// Note on accuracy: Some values that this produces are +/- 1 from the actual firmware.
|
||||||
return StepOverSamples(state, input, rate_multiplier, [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) {
|
return StepOverSamples(state, input, rate_multiplier, [](u64 fraction, const auto& x0,
|
||||||
|
const auto& x1, const auto& x2) {
|
||||||
// This is a saturated subtraction. (Verified by black-box fuzzing.)
|
// This is a saturated subtraction. (Verified by black-box fuzzing.)
|
||||||
s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767);
|
s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767);
|
||||||
s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767);
|
s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767);
|
||||||
|
|
||||||
return std::array<s16, 2> {
|
return std::array<s16, 2>{static_cast<s16>(x0[0] + fraction * delta0 / scale_factor),
|
||||||
static_cast<s16>(x0[0] + fraction * delta0 / scale_factor),
|
static_cast<s16>(x0[1] + fraction * delta1 / scale_factor)};
|
||||||
static_cast<s16>(x0[1] + fraction * delta1 / scale_factor)
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,8 @@ struct State {
|
||||||
* No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay.
|
* No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay.
|
||||||
* @param input Input buffer.
|
* @param input Input buffer.
|
||||||
* @param rate_multiplier Stretch factor. Must be a positive non-zero value.
|
* @param rate_multiplier Stretch factor. Must be a positive non-zero value.
|
||||||
* rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 performs upsampling.
|
* rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0
|
||||||
|
* performs upsampling.
|
||||||
* @return The resampled audio buffer.
|
* @return The resampled audio buffer.
|
||||||
*/
|
*/
|
||||||
StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier);
|
StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier);
|
||||||
|
@ -33,7 +34,8 @@ StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multip
|
||||||
* Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay.
|
* Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay.
|
||||||
* @param input Input buffer.
|
* @param input Input buffer.
|
||||||
* @param rate_multiplier Stretch factor. Must be a positive non-zero value.
|
* @param rate_multiplier Stretch factor. Must be a positive non-zero value.
|
||||||
* rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 performs upsampling.
|
* rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0
|
||||||
|
* performs upsampling.
|
||||||
* @return The resampled audio buffer.
|
* @return The resampled audio buffer.
|
||||||
*/
|
*/
|
||||||
StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier);
|
StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier);
|
||||||
|
|
|
@ -19,7 +19,8 @@ public:
|
||||||
return native_sample_rate;
|
return native_sample_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnqueueSamples(const s16*, size_t) override {}
|
void EnqueueSamples(const s16*, size_t) override {
|
||||||
|
}
|
||||||
|
|
||||||
size_t SamplesInQueue() const override {
|
size_t SamplesInQueue() const override {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/audio_core.h"
|
||||||
#include "audio_core/sdl2_sink.h"
|
#include "audio_core/sdl2_sink.h"
|
||||||
|
|
||||||
|
#include <numeric>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include <numeric>
|
|
||||||
|
|
||||||
namespace AudioCore {
|
namespace AudioCore {
|
||||||
|
|
||||||
|
@ -45,7 +45,8 @@ SDL2Sink::SDL2Sink() : impl(std::make_unique<Impl>()) {
|
||||||
SDL_AudioSpec obtained_audiospec;
|
SDL_AudioSpec obtained_audiospec;
|
||||||
SDL_zero(obtained_audiospec);
|
SDL_zero(obtained_audiospec);
|
||||||
|
|
||||||
impl->audio_device_id = SDL_OpenAudioDevice(nullptr, false, &desired_audiospec, &obtained_audiospec, 0);
|
impl->audio_device_id =
|
||||||
|
SDL_OpenAudioDevice(nullptr, false, &desired_audiospec, &obtained_audiospec, 0);
|
||||||
if (impl->audio_device_id <= 0) {
|
if (impl->audio_device_id <= 0) {
|
||||||
LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed");
|
LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed");
|
||||||
return;
|
return;
|
||||||
|
@ -86,9 +87,10 @@ size_t SDL2Sink::SamplesInQueue() const {
|
||||||
|
|
||||||
SDL_LockAudioDevice(impl->audio_device_id);
|
SDL_LockAudioDevice(impl->audio_device_id);
|
||||||
|
|
||||||
size_t total_size = std::accumulate(impl->queue.begin(), impl->queue.end(), static_cast<size_t>(0),
|
size_t total_size = std::accumulate(impl->queue.begin(), impl->queue.end(),
|
||||||
[](size_t sum, const auto& buffer) {
|
static_cast<size_t>(0), [](size_t sum, const auto& buffer) {
|
||||||
// Division by two because each stereo sample is made of two s16.
|
// Division by two because each stereo sample is made of
|
||||||
|
// two s16.
|
||||||
return sum + buffer.size() / 2;
|
return sum + buffer.size() / 2;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -100,7 +102,8 @@ size_t SDL2Sink::SamplesInQueue() const {
|
||||||
void SDL2Sink::Impl::Callback(void* impl_, u8* buffer, int buffer_size_in_bytes) {
|
void SDL2Sink::Impl::Callback(void* impl_, u8* buffer, int buffer_size_in_bytes) {
|
||||||
Impl* impl = reinterpret_cast<Impl*>(impl_);
|
Impl* impl = reinterpret_cast<Impl*>(impl_);
|
||||||
|
|
||||||
size_t remaining_size = static_cast<size_t>(buffer_size_in_bytes) / sizeof(s16); // Keep track of size in 16-bit increments.
|
size_t remaining_size = static_cast<size_t>(buffer_size_in_bytes) /
|
||||||
|
sizeof(s16); // Keep track of size in 16-bit increments.
|
||||||
|
|
||||||
while (remaining_size > 0 && !impl->queue.empty()) {
|
while (remaining_size > 0 && !impl->queue.empty()) {
|
||||||
if (impl->queue.front().size() <= remaining_size) {
|
if (impl->queue.front().size() <= remaining_size) {
|
||||||
|
@ -111,7 +114,8 @@ void SDL2Sink::Impl::Callback(void* impl_, u8* buffer, int buffer_size_in_bytes)
|
||||||
} else {
|
} else {
|
||||||
memcpy(buffer, impl->queue.front().data(), remaining_size * sizeof(s16));
|
memcpy(buffer, impl->queue.front().data(), remaining_size * sizeof(s16));
|
||||||
buffer += remaining_size * sizeof(s16);
|
buffer += remaining_size * sizeof(s16);
|
||||||
impl->queue.front().erase(impl->queue.front().begin(), impl->queue.front().begin() + remaining_size);
|
impl->queue.front().erase(impl->queue.front().begin(),
|
||||||
|
impl->queue.front().begin() + remaining_size);
|
||||||
remaining_size = 0;
|
remaining_size = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,14 +11,16 @@
|
||||||
namespace AudioCore {
|
namespace AudioCore {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is an interface for an audio sink. An audio sink accepts samples in stereo signed PCM16 format to be output.
|
* This class is an interface for an audio sink. An audio sink accepts samples in stereo signed
|
||||||
|
* PCM16 format to be output.
|
||||||
* Sinks *do not* handle resampling and expect the correct sample rate. They are dumb outputs.
|
* Sinks *do not* handle resampling and expect the correct sample rate. They are dumb outputs.
|
||||||
*/
|
*/
|
||||||
class Sink {
|
class Sink {
|
||||||
public:
|
public:
|
||||||
virtual ~Sink() = default;
|
virtual ~Sink() = default;
|
||||||
|
|
||||||
/// The native rate of this sink. The sink expects to be fed samples that respect this. (Units: samples/sec)
|
/// The native rate of this sink. The sink expects to be fed samples that respect this. (Units:
|
||||||
|
/// samples/sec)
|
||||||
virtual unsigned int GetNativeSampleRate() const = 0;
|
virtual unsigned int GetNativeSampleRate() const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,9 +17,9 @@ namespace AudioCore {
|
||||||
// g_sink_details is ordered in terms of desirability, with the best choice at the top.
|
// g_sink_details is ordered in terms of desirability, with the best choice at the top.
|
||||||
const std::vector<SinkDetails> g_sink_details = {
|
const std::vector<SinkDetails> g_sink_details = {
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
{ "sdl2", []() { return std::make_unique<SDL2Sink>(); } },
|
{"sdl2", []() { return std::make_unique<SDL2Sink>(); }},
|
||||||
#endif
|
#endif
|
||||||
{ "null", []() { return std::make_unique<NullSink>(); } },
|
{"null", []() { return std::make_unique<NullSink>(); }},
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace AudioCore
|
} // namespace AudioCore
|
||||||
|
|
|
@ -14,7 +14,8 @@ class Sink;
|
||||||
|
|
||||||
struct SinkDetails {
|
struct SinkDetails {
|
||||||
SinkDetails(const char* id_, std::function<std::unique_ptr<Sink>()> factory_)
|
SinkDetails(const char* id_, std::function<std::unique_ptr<Sink>()> factory_)
|
||||||
: id(id_), factory(factory_) {}
|
: id(id_), factory(factory_) {
|
||||||
|
}
|
||||||
|
|
||||||
/// Name for this sink.
|
/// Name for this sink.
|
||||||
const char* id;
|
const char* id;
|
||||||
|
|
|
@ -48,7 +48,8 @@ std::vector<s16> TimeStretcher::Process(size_t samples_in_queue) {
|
||||||
|
|
||||||
double ratio = CalculateCurrentRatio();
|
double ratio = CalculateCurrentRatio();
|
||||||
ratio = CorrectForUnderAndOverflow(ratio, samples_in_queue);
|
ratio = CorrectForUnderAndOverflow(ratio, samples_in_queue);
|
||||||
impl->smoothed_ratio = (1.0 - SMOOTHING_FACTOR) * impl->smoothed_ratio + SMOOTHING_FACTOR * ratio;
|
impl->smoothed_ratio =
|
||||||
|
(1.0 - SMOOTHING_FACTOR) * impl->smoothed_ratio + SMOOTHING_FACTOR * ratio;
|
||||||
impl->smoothed_ratio = ClampRatio(impl->smoothed_ratio);
|
impl->smoothed_ratio = ClampRatio(impl->smoothed_ratio);
|
||||||
|
|
||||||
// SoundTouch's tempo definition the inverse of our ratio definition.
|
// SoundTouch's tempo definition the inverse of our ratio definition.
|
||||||
|
@ -100,7 +101,8 @@ double TimeStretcher::CalculateCurrentRatio() {
|
||||||
const steady_clock::time_point now = steady_clock::now();
|
const steady_clock::time_point now = steady_clock::now();
|
||||||
const std::chrono::duration<double> duration = now - impl->frame_timer;
|
const std::chrono::duration<double> duration = now - impl->frame_timer;
|
||||||
|
|
||||||
const double expected_time = static_cast<double>(impl->samples_queued) / static_cast<double>(native_sample_rate);
|
const double expected_time =
|
||||||
|
static_cast<double>(impl->samples_queued) / static_cast<double>(native_sample_rate);
|
||||||
const double actual_time = duration.count();
|
const double actual_time = duration.count();
|
||||||
|
|
||||||
double ratio;
|
double ratio;
|
||||||
|
|
|
@ -37,7 +37,8 @@ public:
|
||||||
/**
|
/**
|
||||||
* Does audio stretching and produces the time-stretched samples.
|
* Does audio stretching and produces the time-stretched samples.
|
||||||
* Timer calculations use sample_delay to determine how much of a margin we have.
|
* Timer calculations use sample_delay to determine how much of a margin we have.
|
||||||
* @param sample_delay How many samples are buffered downstream of this module and haven't been played yet.
|
* @param sample_delay How many samples are buffered downstream of this module and haven't been
|
||||||
|
* played yet.
|
||||||
* @return Samples to play in interleaved stereo PCM16 format.
|
* @return Samples to play in interleaved stereo PCM16 format.
|
||||||
*/
|
*/
|
||||||
std::vector<s16> Process(size_t sample_delay);
|
std::vector<s16> Process(size_t sample_delay);
|
||||||
|
@ -48,7 +49,8 @@ private:
|
||||||
|
|
||||||
/// INTERNAL: ratio = wallclock time / emulated time
|
/// INTERNAL: ratio = wallclock time / emulated time
|
||||||
double CalculateCurrentRatio();
|
double CalculateCurrentRatio();
|
||||||
/// INTERNAL: If we have too many or too few samples downstream, nudge ratio in the appropriate direction.
|
/// INTERNAL: If we have too many or too few samples downstream, nudge ratio in the appropriate
|
||||||
|
/// direction.
|
||||||
double CorrectForUnderAndOverflow(double ratio, size_t sample_delay) const;
|
double CorrectForUnderAndOverflow(double ratio, size_t sample_delay) const;
|
||||||
/// INTERNAL: Gets the time-stretched samples from SoundTouch.
|
/// INTERNAL: Gets the time-stretched samples from SoundTouch.
|
||||||
std::vector<s16> GetSamples();
|
std::vector<s16> GetSamples();
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
// This needs to be included before getopt.h because the latter #defines symbols used by it
|
// This needs to be included before getopt.h because the latter #defines symbols used by it
|
||||||
#include "common/microprofile.h"
|
#include "common/microprofile.h"
|
||||||
|
@ -13,53 +13,51 @@
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#else
|
#else
|
||||||
#include <unistd.h>
|
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
|
||||||
#include "common/logging/backend.h"
|
#include "common/logging/backend.h"
|
||||||
#include "common/logging/filter.h"
|
#include "common/logging/filter.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
|
|
||||||
#include "core/settings.h"
|
|
||||||
#include "core/system.h"
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/gdbstub/gdbstub.h"
|
#include "core/gdbstub/gdbstub.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
|
#include "core/settings.h"
|
||||||
|
#include "core/system.h"
|
||||||
|
|
||||||
#include "citra/config.h"
|
#include "citra/config.h"
|
||||||
#include "citra/emu_window/emu_window_sdl2.h"
|
#include "citra/emu_window/emu_window_sdl2.h"
|
||||||
|
|
||||||
#include "video_core/video_core.h"
|
#include "video_core/video_core.h"
|
||||||
|
|
||||||
|
static void PrintHelp(const char* argv0) {
|
||||||
static void PrintHelp(const char *argv0)
|
std::cout << "Usage: " << argv0
|
||||||
{
|
<< " [options] <filename>\n"
|
||||||
std::cout << "Usage: " << argv0 << " [options] <filename>\n"
|
|
||||||
"-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n"
|
"-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n"
|
||||||
"-h, --help Display this help and exit\n"
|
"-h, --help Display this help and exit\n"
|
||||||
"-v, --version Output version information and exit\n";
|
"-v, --version Output version information and exit\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PrintVersion()
|
static void PrintVersion() {
|
||||||
{
|
|
||||||
std::cout << "Citra " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl;
|
std::cout << "Citra " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Application entry point
|
/// Application entry point
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char** argv) {
|
||||||
Config config;
|
Config config;
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
bool use_gdbstub = Settings::values.use_gdbstub;
|
bool use_gdbstub = Settings::values.use_gdbstub;
|
||||||
u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port);
|
u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port);
|
||||||
char *endarg;
|
char* endarg;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
int argc_w;
|
int argc_w;
|
||||||
auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
|
auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
|
||||||
|
@ -71,12 +69,10 @@ int main(int argc, char **argv) {
|
||||||
#endif
|
#endif
|
||||||
std::string boot_filename;
|
std::string boot_filename;
|
||||||
|
|
||||||
static struct option long_options[] = {
|
static struct option long_options[] = {{"gdbport", required_argument, 0, 'g'},
|
||||||
{ "gdbport", required_argument, 0, 'g' },
|
{"help", no_argument, 0, 'h'},
|
||||||
{ "help", no_argument, 0, 'h' },
|
{"version", no_argument, 0, 'v'},
|
||||||
{ "version", no_argument, 0, 'v' },
|
{0, 0, 0, 0}};
|
||||||
{ 0, 0, 0, 0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
while (optind < argc) {
|
while (optind < argc) {
|
||||||
char arg = getopt_long(argc, argv, "g:hv", long_options, &option_index);
|
char arg = getopt_long(argc, argv, "g:hv", long_options, &option_index);
|
||||||
|
@ -86,7 +82,8 @@ int main(int argc, char **argv) {
|
||||||
errno = 0;
|
errno = 0;
|
||||||
gdb_port = strtoul(optarg, &endarg, 0);
|
gdb_port = strtoul(optarg, &endarg, 0);
|
||||||
use_gdbstub = true;
|
use_gdbstub = true;
|
||||||
if (endarg == optarg) errno = EINVAL;
|
if (endarg == optarg)
|
||||||
|
errno = EINVAL;
|
||||||
if (errno != 0) {
|
if (errno != 0) {
|
||||||
perror("--gdbport");
|
perror("--gdbport");
|
||||||
exit(1);
|
exit(1);
|
||||||
|
|
|
@ -45,15 +45,13 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
|
||||||
|
|
||||||
static const std::array<int, Settings::NativeInput::NUM_INPUTS> defaults = {
|
static const std::array<int, Settings::NativeInput::NUM_INPUTS> defaults = {
|
||||||
// directly mapped keys
|
// directly mapped keys
|
||||||
SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X,
|
SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_Q, SDL_SCANCODE_W,
|
||||||
SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_1, SDL_SCANCODE_2,
|
SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_B, SDL_SCANCODE_T,
|
||||||
SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_B,
|
SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J,
|
||||||
SDL_SCANCODE_T, SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H,
|
SDL_SCANCODE_L,
|
||||||
SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, SDL_SCANCODE_L,
|
|
||||||
|
|
||||||
// indirectly mapped keys
|
// indirectly mapped keys
|
||||||
SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT,
|
SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_D,
|
||||||
SDL_SCANCODE_D,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void Config::ReadValues() {
|
void Config::ReadValues() {
|
||||||
|
@ -62,7 +60,8 @@ void Config::ReadValues() {
|
||||||
Settings::values.input_mappings[Settings::NativeInput::All[i]] =
|
Settings::values.input_mappings[Settings::NativeInput::All[i]] =
|
||||||
sdl2_config->GetInteger("Controls", Settings::NativeInput::Mapping[i], defaults[i]);
|
sdl2_config->GetInteger("Controls", Settings::NativeInput::Mapping[i], defaults[i]);
|
||||||
}
|
}
|
||||||
Settings::values.pad_circle_modifier_scale = (float)sdl2_config->GetReal("Controls", "pad_circle_modifier_scale", 0.5);
|
Settings::values.pad_circle_modifier_scale =
|
||||||
|
(float)sdl2_config->GetReal("Controls", "pad_circle_modifier_scale", 0.5);
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
|
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
|
||||||
|
@ -71,7 +70,8 @@ void Config::ReadValues() {
|
||||||
// Renderer
|
// Renderer
|
||||||
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true);
|
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true);
|
||||||
Settings::values.use_shader_jit = sdl2_config->GetBoolean("Renderer", "use_shader_jit", true);
|
Settings::values.use_shader_jit = sdl2_config->GetBoolean("Renderer", "use_shader_jit", true);
|
||||||
Settings::values.use_scaled_resolution = sdl2_config->GetBoolean("Renderer", "use_scaled_resolution", false);
|
Settings::values.use_scaled_resolution =
|
||||||
|
sdl2_config->GetBoolean("Renderer", "use_scaled_resolution", false);
|
||||||
Settings::values.use_vsync = sdl2_config->GetBoolean("Renderer", "use_vsync", false);
|
Settings::values.use_vsync = sdl2_config->GetBoolean("Renderer", "use_vsync", false);
|
||||||
|
|
||||||
Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 1.0);
|
Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 1.0);
|
||||||
|
@ -80,10 +80,12 @@ void Config::ReadValues() {
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
|
Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
|
||||||
Settings::values.enable_audio_stretching = sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
|
Settings::values.enable_audio_stretching =
|
||||||
|
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
|
||||||
|
|
||||||
// Data Storage
|
// Data Storage
|
||||||
Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
|
Settings::values.use_virtual_sd =
|
||||||
|
sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
|
||||||
|
|
||||||
// System
|
// System
|
||||||
Settings::values.is_new_3ds = sdl2_config->GetBoolean("System", "is_new_3ds", false);
|
Settings::values.is_new_3ds = sdl2_config->GetBoolean("System", "is_new_3ds", false);
|
||||||
|
@ -94,7 +96,8 @@ void Config::ReadValues() {
|
||||||
|
|
||||||
// Debugging
|
// Debugging
|
||||||
Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
|
Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
|
||||||
Settings::values.gdbstub_port = static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
|
Settings::values.gdbstub_port =
|
||||||
|
static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::Reload() {
|
void Config::Reload() {
|
||||||
|
|
|
@ -13,8 +13,9 @@ class Config {
|
||||||
std::unique_ptr<INIReader> sdl2_config;
|
std::unique_ptr<INIReader> sdl2_config;
|
||||||
std::string sdl2_config_loc;
|
std::string sdl2_config_loc;
|
||||||
|
|
||||||
bool LoadINI(const std::string& default_contents="", bool retry=true);
|
bool LoadINI(const std::string& default_contents = "", bool retry = true);
|
||||||
void ReadValues();
|
void ReadValues();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Config();
|
Config();
|
||||||
|
|
||||||
|
|
|
@ -104,5 +104,4 @@ log_filter = *:Info
|
||||||
use_gdbstub=false
|
use_gdbstub=false
|
||||||
gdbstub_port=24689
|
gdbstub_port=24689
|
||||||
)";
|
)";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
|
|
||||||
#include "core/settings.h"
|
|
||||||
#include "core/hle/service/hid/hid.h"
|
#include "core/hle/service/hid/hid.h"
|
||||||
|
#include "core/settings.h"
|
||||||
|
|
||||||
#include "citra/emu_window/emu_window_sdl2.h"
|
#include "citra/emu_window/emu_window_sdl2.h"
|
||||||
|
|
||||||
|
@ -40,9 +40,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
|
||||||
|
|
||||||
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
|
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
|
||||||
if (state == SDL_PRESSED) {
|
if (state == SDL_PRESSED) {
|
||||||
KeyMap::PressKey(*this, { key, keyboard_id });
|
KeyMap::PressKey(*this, {key, keyboard_id});
|
||||||
} else if (state == SDL_RELEASED) {
|
} else if (state == SDL_RELEASED) {
|
||||||
KeyMap::ReleaseKey(*this, { key, keyboard_id });
|
KeyMap::ReleaseKey(*this, {key, keyboard_id});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,8 @@ void EmuWindow_SDL2::OnResize() {
|
||||||
|
|
||||||
SDL_GetWindowSize(render_window, &width, &height);
|
SDL_GetWindowSize(render_window, &width, &height);
|
||||||
|
|
||||||
NotifyFramebufferLayoutChanged(EmuWindow::FramebufferLayout::DefaultScreenLayout(width, height));
|
NotifyFramebufferLayoutChanged(
|
||||||
|
EmuWindow::FramebufferLayout::DefaultScreenLayout(width, height));
|
||||||
}
|
}
|
||||||
|
|
||||||
EmuWindow_SDL2::EmuWindow_SDL2() {
|
EmuWindow_SDL2::EmuWindow_SDL2() {
|
||||||
|
@ -80,12 +81,13 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
|
||||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
||||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
|
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
|
||||||
|
|
||||||
std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc);
|
std::string window_title =
|
||||||
render_window = SDL_CreateWindow(window_title.c_str(),
|
Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc);
|
||||||
|
render_window = SDL_CreateWindow(
|
||||||
|
window_title.c_str(),
|
||||||
SDL_WINDOWPOS_UNDEFINED, // x position
|
SDL_WINDOWPOS_UNDEFINED, // x position
|
||||||
SDL_WINDOWPOS_UNDEFINED, // y position
|
SDL_WINDOWPOS_UNDEFINED, // y position
|
||||||
VideoCore::kScreenTopWidth,
|
VideoCore::kScreenTopWidth, VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight,
|
||||||
VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight,
|
|
||||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||||
|
|
||||||
if (render_window == nullptr) {
|
if (render_window == nullptr) {
|
||||||
|
@ -171,10 +173,13 @@ void EmuWindow_SDL2::DoneCurrent() {
|
||||||
void EmuWindow_SDL2::ReloadSetKeymaps() {
|
void EmuWindow_SDL2::ReloadSetKeymaps() {
|
||||||
KeyMap::ClearKeyMapping(keyboard_id);
|
KeyMap::ClearKeyMapping(keyboard_id);
|
||||||
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
|
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
|
||||||
KeyMap::SetKeyMapping({ Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id }, KeyMap::mapping_targets[i]);
|
KeyMap::SetKeyMapping(
|
||||||
|
{Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id},
|
||||||
|
KeyMap::mapping_targets[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(const std::pair<unsigned, unsigned>& minimal_size) {
|
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(
|
||||||
|
const std::pair<unsigned, unsigned>& minimal_size) {
|
||||||
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
|
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,8 @@ private:
|
||||||
void OnResize();
|
void OnResize();
|
||||||
|
|
||||||
/// Called when a configuration change affects the minimal size of the window
|
/// Called when a configuration change affects the minimal size of the window
|
||||||
void OnMinimalClientAreaChangeRequest(const std::pair<unsigned, unsigned>& minimal_size) override;
|
void
|
||||||
|
OnMinimalClientAreaChangeRequest(const std::pair<unsigned, unsigned>& minimal_size) override;
|
||||||
|
|
||||||
/// Is the window still open?
|
/// Is the window still open?
|
||||||
bool is_open = true;
|
bool is_open = true;
|
||||||
|
@ -55,7 +56,7 @@ private:
|
||||||
/// Internal SDL2 render window
|
/// Internal SDL2 render window
|
||||||
SDL_Window* render_window;
|
SDL_Window* render_window;
|
||||||
|
|
||||||
using SDL_GLContext = void *;
|
using SDL_GLContext = void*;
|
||||||
/// The OpenGL context associated with the window
|
/// The OpenGL context associated with the window
|
||||||
SDL_GLContext gl_context;
|
SDL_GLContext gl_context;
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,8 @@
|
||||||
#define APP_TITLE APP_NAME " " APP_VERSION
|
#define APP_TITLE APP_NAME " " APP_VERSION
|
||||||
#define COPYRIGHT "Copyright (C) 2013-2014 Citra Team"
|
#define COPYRIGHT "Copyright (C) 2013-2014 Citra Team"
|
||||||
|
|
||||||
EmuThread::EmuThread(GRenderWindow* render_window) :
|
EmuThread::EmuThread(GRenderWindow* render_window)
|
||||||
exec_step(false), running(false), stop_run(false), render_window(render_window) {
|
: exec_step(false), running(false), stop_run(false), render_window(render_window) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::run() {
|
void EmuThread::run() {
|
||||||
|
@ -64,7 +64,7 @@ void EmuThread::run() {
|
||||||
was_active = false;
|
was_active = false;
|
||||||
} else {
|
} else {
|
||||||
std::unique_lock<std::mutex> lock(running_mutex);
|
std::unique_lock<std::mutex> lock(running_mutex);
|
||||||
running_cv.wait(lock, [this]{ return IsRunning() || exec_step || stop_run; });
|
running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,10 +78,10 @@ void EmuThread::run() {
|
||||||
render_window->moveContext();
|
render_window->moveContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL context.
|
// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
|
||||||
|
// context.
|
||||||
// The corresponding functionality is handled in EmuThread instead
|
// The corresponding functionality is handled in EmuThread instead
|
||||||
class GGLWidgetInternal : public QGLWidget
|
class GGLWidgetInternal : public QGLWidget {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent)
|
GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent)
|
||||||
: QGLWidget(fmt, parent), parent(parent) {
|
: QGLWidget(fmt, parent), parent(parent) {
|
||||||
|
@ -98,37 +98,43 @@ public:
|
||||||
parent->OnFramebufferSizeChanged();
|
parent->OnFramebufferSizeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisablePainting() { do_painting = false; }
|
void DisablePainting() {
|
||||||
void EnablePainting() { do_painting = true; }
|
do_painting = false;
|
||||||
|
}
|
||||||
|
void EnablePainting() {
|
||||||
|
do_painting = true;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GRenderWindow* parent;
|
GRenderWindow* parent;
|
||||||
bool do_painting;
|
bool do_painting;
|
||||||
};
|
};
|
||||||
|
|
||||||
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) :
|
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
|
||||||
QWidget(parent), keyboard_id(0), emu_thread(emu_thread), child(nullptr) {
|
: QWidget(parent), keyboard_id(0), emu_thread(emu_thread), child(nullptr) {
|
||||||
|
|
||||||
std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc);
|
std::string window_title =
|
||||||
|
Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc);
|
||||||
setWindowTitle(QString::fromStdString(window_title));
|
setWindowTitle(QString::fromStdString(window_title));
|
||||||
|
|
||||||
keyboard_id = KeyMap::NewDeviceId();
|
keyboard_id = KeyMap::NewDeviceId();
|
||||||
ReloadSetKeymaps();
|
ReloadSetKeymaps();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::moveContext()
|
void GRenderWindow::moveContext() {
|
||||||
{
|
|
||||||
DoneCurrent();
|
DoneCurrent();
|
||||||
// We need to move GL context to the swapping thread in Qt5
|
// We need to move GL context to the swapping thread in Qt5
|
||||||
#if QT_VERSION > QT_VERSION_CHECK(5, 0, 0)
|
#if QT_VERSION > QT_VERSION_CHECK(5, 0, 0)
|
||||||
// If the thread started running, move the GL Context to the new thread. Otherwise, move it back.
|
// If the thread started running, move the GL Context to the new thread. Otherwise, move it
|
||||||
auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr) ? emu_thread : qApp->thread();
|
// back.
|
||||||
|
auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)
|
||||||
|
? emu_thread
|
||||||
|
: qApp->thread();
|
||||||
child->context()->moveToThread(thread);
|
child->context()->moveToThread(thread);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::SwapBuffers()
|
void GRenderWindow::SwapBuffers() {
|
||||||
{
|
|
||||||
#if !defined(QT_NO_DEBUG)
|
#if !defined(QT_NO_DEBUG)
|
||||||
// Qt debug runtime prints a bogus warning on the console if you haven't called makeCurrent
|
// Qt debug runtime prints a bogus warning on the console if you haven't called makeCurrent
|
||||||
// since the last time you called swapBuffers. This presumably means something if you're using
|
// since the last time you called swapBuffers. This presumably means something if you're using
|
||||||
|
@ -139,13 +145,11 @@ void GRenderWindow::SwapBuffers()
|
||||||
child->swapBuffers();
|
child->swapBuffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::MakeCurrent()
|
void GRenderWindow::MakeCurrent() {
|
||||||
{
|
|
||||||
child->makeCurrent();
|
child->makeCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::DoneCurrent()
|
void GRenderWindow::DoneCurrent() {
|
||||||
{
|
|
||||||
child->doneCurrent();
|
child->doneCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,36 +161,33 @@ void GRenderWindow::PollEvents() {
|
||||||
// Older versions get the window size (density independent pixels),
|
// Older versions get the window size (density independent pixels),
|
||||||
// and hence, do not support DPI scaling ("retina" displays).
|
// and hence, do not support DPI scaling ("retina" displays).
|
||||||
// The result will be a viewport that is smaller than the extent of the window.
|
// The result will be a viewport that is smaller than the extent of the window.
|
||||||
void GRenderWindow::OnFramebufferSizeChanged()
|
void GRenderWindow::OnFramebufferSizeChanged() {
|
||||||
{
|
// Screen changes potentially incur a change in screen DPI, hence we should update the
|
||||||
// Screen changes potentially incur a change in screen DPI, hence we should update the framebuffer size
|
// framebuffer size
|
||||||
qreal pixelRatio = windowPixelRatio();
|
qreal pixelRatio = windowPixelRatio();
|
||||||
unsigned width = child->QPaintDevice::width() * pixelRatio;
|
unsigned width = child->QPaintDevice::width() * pixelRatio;
|
||||||
unsigned height = child->QPaintDevice::height() * pixelRatio;
|
unsigned height = child->QPaintDevice::height() * pixelRatio;
|
||||||
|
|
||||||
NotifyFramebufferLayoutChanged(EmuWindow::FramebufferLayout::DefaultScreenLayout(width, height));
|
NotifyFramebufferLayoutChanged(
|
||||||
|
EmuWindow::FramebufferLayout::DefaultScreenLayout(width, height));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::BackupGeometry()
|
void GRenderWindow::BackupGeometry() {
|
||||||
{
|
|
||||||
geometry = ((QGLWidget*)this)->saveGeometry();
|
geometry = ((QGLWidget*)this)->saveGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::RestoreGeometry()
|
void GRenderWindow::RestoreGeometry() {
|
||||||
{
|
|
||||||
// We don't want to back up the geometry here (obviously)
|
// We don't want to back up the geometry here (obviously)
|
||||||
QWidget::restoreGeometry(geometry);
|
QWidget::restoreGeometry(geometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::restoreGeometry(const QByteArray& geometry)
|
void GRenderWindow::restoreGeometry(const QByteArray& geometry) {
|
||||||
{
|
|
||||||
// Make sure users of this class don't need to deal with backing up the geometry themselves
|
// Make sure users of this class don't need to deal with backing up the geometry themselves
|
||||||
QWidget::restoreGeometry(geometry);
|
QWidget::restoreGeometry(geometry);
|
||||||
BackupGeometry();
|
BackupGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray GRenderWindow::saveGeometry()
|
QByteArray GRenderWindow::saveGeometry() {
|
||||||
{
|
|
||||||
// If we are a top-level widget, store the current geometry
|
// If we are a top-level widget, store the current geometry
|
||||||
// otherwise, store the last backup
|
// otherwise, store the last backup
|
||||||
if (parent() == nullptr)
|
if (parent() == nullptr)
|
||||||
|
@ -195,8 +196,7 @@ QByteArray GRenderWindow::saveGeometry()
|
||||||
return geometry;
|
return geometry;
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal GRenderWindow::windowPixelRatio()
|
qreal GRenderWindow::windowPixelRatio() {
|
||||||
{
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
|
||||||
// windowHandle() might not be accessible until the window is displayed to screen.
|
// windowHandle() might not be accessible until the window is displayed to screen.
|
||||||
return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
|
return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
|
||||||
|
@ -210,20 +210,16 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
|
||||||
QWidget::closeEvent(event);
|
QWidget::closeEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::keyPressEvent(QKeyEvent* event)
|
void GRenderWindow::keyPressEvent(QKeyEvent* event) {
|
||||||
{
|
KeyMap::PressKey(*this, {event->key(), keyboard_id});
|
||||||
KeyMap::PressKey(*this, { event->key(), keyboard_id });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::keyReleaseEvent(QKeyEvent* event)
|
void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
|
||||||
{
|
KeyMap::ReleaseKey(*this, {event->key(), keyboard_id});
|
||||||
KeyMap::ReleaseKey(*this, { event->key(), keyboard_id });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::mousePressEvent(QMouseEvent *event)
|
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
|
||||||
{
|
if (event->button() == Qt::LeftButton) {
|
||||||
if (event->button() == Qt::LeftButton)
|
|
||||||
{
|
|
||||||
auto pos = event->pos();
|
auto pos = event->pos();
|
||||||
qreal pixelRatio = windowPixelRatio();
|
qreal pixelRatio = windowPixelRatio();
|
||||||
this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
|
this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
|
||||||
|
@ -231,30 +227,28 @@ void GRenderWindow::mousePressEvent(QMouseEvent *event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::mouseMoveEvent(QMouseEvent *event)
|
void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
|
||||||
{
|
|
||||||
auto pos = event->pos();
|
auto pos = event->pos();
|
||||||
qreal pixelRatio = windowPixelRatio();
|
qreal pixelRatio = windowPixelRatio();
|
||||||
this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
|
this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
|
||||||
std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
|
std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::mouseReleaseEvent(QMouseEvent *event)
|
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
|
||||||
{
|
|
||||||
if (event->button() == Qt::LeftButton)
|
if (event->button() == Qt::LeftButton)
|
||||||
this->TouchReleased();
|
this->TouchReleased();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::ReloadSetKeymaps()
|
void GRenderWindow::ReloadSetKeymaps() {
|
||||||
{
|
|
||||||
KeyMap::ClearKeyMapping(keyboard_id);
|
KeyMap::ClearKeyMapping(keyboard_id);
|
||||||
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
|
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
|
||||||
KeyMap::SetKeyMapping({ Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id }, KeyMap::mapping_targets[i]);
|
KeyMap::SetKeyMapping(
|
||||||
|
{Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id},
|
||||||
|
KeyMap::mapping_targets[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height)
|
void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
|
||||||
{
|
|
||||||
NotifyClientAreaSizeChanged(std::make_pair(width, height));
|
NotifyClientAreaSizeChanged(std::make_pair(width, height));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,7 +261,8 @@ void GRenderWindow::InitRenderTarget() {
|
||||||
delete layout();
|
delete layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, WA_DontShowOnScreen, WA_DeleteOnClose
|
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
|
||||||
|
// WA_DontShowOnScreen, WA_DeleteOnClose
|
||||||
QGLFormat fmt;
|
QGLFormat fmt;
|
||||||
fmt.setVersion(3, 3);
|
fmt.setVersion(3, 3);
|
||||||
fmt.setProfile(QGLFormat::CoreProfile);
|
fmt.setProfile(QGLFormat::CoreProfile);
|
||||||
|
@ -279,7 +274,8 @@ void GRenderWindow::InitRenderTarget() {
|
||||||
child = new GGLWidgetInternal(fmt, this);
|
child = new GGLWidgetInternal(fmt, this);
|
||||||
QBoxLayout* layout = new QHBoxLayout(this);
|
QBoxLayout* layout = new QHBoxLayout(this);
|
||||||
|
|
||||||
resize(VideoCore::kScreenTopWidth, VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight);
|
resize(VideoCore::kScreenTopWidth,
|
||||||
|
VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight);
|
||||||
layout->addWidget(child);
|
layout->addWidget(child);
|
||||||
layout->setMargin(0);
|
layout->setMargin(0);
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
@ -292,7 +288,8 @@ void GRenderWindow::InitRenderTarget() {
|
||||||
BackupGeometry();
|
BackupGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::OnMinimalClientAreaChangeRequest(const std::pair<unsigned,unsigned>& minimal_size) {
|
void GRenderWindow::OnMinimalClientAreaChangeRequest(
|
||||||
|
const std::pair<unsigned, unsigned>& minimal_size) {
|
||||||
setMinimumSize(minimal_size.first, minimal_size.second);
|
setMinimumSize(minimal_size.first, minimal_size.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,11 +303,12 @@ void GRenderWindow::OnEmulationStopping() {
|
||||||
child->EnablePainting();
|
child->EnablePainting();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::showEvent(QShowEvent * event) {
|
void GRenderWindow::showEvent(QShowEvent* event) {
|
||||||
QWidget::showEvent(event);
|
QWidget::showEvent(event);
|
||||||
|
|
||||||
// windowHandle() is not initialized until the Window is shown, so we connect it here.
|
// windowHandle() is not initialized until the Window is shown, so we connect it here.
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
|
||||||
connect(this->windowHandle(), SIGNAL(screenChanged(QScreen*)), this, SLOT(OnFramebufferSizeChanged()), Qt::UniqueConnection);
|
connect(this->windowHandle(), SIGNAL(screenChanged(QScreen*)), this,
|
||||||
#endif
|
SLOT(OnFramebufferSizeChanged()), Qt::UniqueConnection);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,7 @@ class GGLWidgetInternal;
|
||||||
class GMainWindow;
|
class GMainWindow;
|
||||||
class GRenderWindow;
|
class GRenderWindow;
|
||||||
|
|
||||||
class EmuThread : public QThread
|
class EmuThread : public QThread {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -58,7 +57,9 @@ public:
|
||||||
* @return True if the emulation thread is running, otherwise false
|
* @return True if the emulation thread is running, otherwise false
|
||||||
* @note This function is thread-safe
|
* @note This function is thread-safe
|
||||||
*/
|
*/
|
||||||
bool IsRunning() { return running; }
|
bool IsRunning() {
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests for the emulation thread to stop running
|
* Requests for the emulation thread to stop running
|
||||||
|
@ -81,20 +82,23 @@ signals:
|
||||||
/**
|
/**
|
||||||
* Emitted when the CPU has halted execution
|
* Emitted when the CPU has halted execution
|
||||||
*
|
*
|
||||||
* @warning When connecting to this signal from other threads, make sure to specify either Qt::QueuedConnection (invoke slot within the destination object's message thread) or even Qt::BlockingQueuedConnection (additionally block source thread until slot returns)
|
* @warning When connecting to this signal from other threads, make sure to specify either
|
||||||
|
* Qt::QueuedConnection (invoke slot within the destination object's message thread) or even
|
||||||
|
* Qt::BlockingQueuedConnection (additionally block source thread until slot returns)
|
||||||
*/
|
*/
|
||||||
void DebugModeEntered();
|
void DebugModeEntered();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted right before the CPU continues execution
|
* Emitted right before the CPU continues execution
|
||||||
*
|
*
|
||||||
* @warning When connecting to this signal from other threads, make sure to specify either Qt::QueuedConnection (invoke slot within the destination object's message thread) or even Qt::BlockingQueuedConnection (additionally block source thread until slot returns)
|
* @warning When connecting to this signal from other threads, make sure to specify either
|
||||||
|
* Qt::QueuedConnection (invoke slot within the destination object's message thread) or even
|
||||||
|
* Qt::BlockingQueuedConnection (additionally block source thread until slot returns)
|
||||||
*/
|
*/
|
||||||
void DebugModeLeft();
|
void DebugModeLeft();
|
||||||
};
|
};
|
||||||
|
|
||||||
class GRenderWindow : public QWidget, public EmuWindow
|
class GRenderWindow : public QWidget, public EmuWindow {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -118,9 +122,9 @@ public:
|
||||||
void keyPressEvent(QKeyEvent* event) override;
|
void keyPressEvent(QKeyEvent* event) override;
|
||||||
void keyReleaseEvent(QKeyEvent* event) override;
|
void keyReleaseEvent(QKeyEvent* event) override;
|
||||||
|
|
||||||
void mousePressEvent(QMouseEvent *event) override;
|
void mousePressEvent(QMouseEvent* event) override;
|
||||||
void mouseMoveEvent(QMouseEvent *event) override;
|
void mouseMoveEvent(QMouseEvent* event) override;
|
||||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||||
|
|
||||||
void ReloadSetKeymaps() override;
|
void ReloadSetKeymaps() override;
|
||||||
|
|
||||||
|
@ -140,7 +144,8 @@ signals:
|
||||||
void Closed();
|
void Closed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void OnMinimalClientAreaChangeRequest(const std::pair<unsigned,unsigned>& minimal_size) override;
|
void
|
||||||
|
OnMinimalClientAreaChangeRequest(const std::pair<unsigned, unsigned>& minimal_size) override;
|
||||||
|
|
||||||
GGLWidgetInternal* child;
|
GGLWidgetInternal* child;
|
||||||
|
|
||||||
|
|
|
@ -20,24 +20,23 @@ Config::Config() {
|
||||||
|
|
||||||
const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> Config::defaults = {
|
const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> Config::defaults = {
|
||||||
// directly mapped keys
|
// directly mapped keys
|
||||||
Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X,
|
Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2,
|
||||||
Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2,
|
Qt::Key_M, Qt::Key_N, Qt::Key_B, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H, Qt::Key_I,
|
||||||
Qt::Key_M, Qt::Key_N, Qt::Key_B,
|
Qt::Key_K, Qt::Key_J, Qt::Key_L,
|
||||||
Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H,
|
|
||||||
Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L,
|
|
||||||
|
|
||||||
// indirectly mapped keys
|
// indirectly mapped keys
|
||||||
Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right,
|
Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_D,
|
||||||
Qt::Key_D,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void Config::ReadValues() {
|
void Config::ReadValues() {
|
||||||
qt_config->beginGroup("Controls");
|
qt_config->beginGroup("Controls");
|
||||||
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
|
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
|
||||||
Settings::values.input_mappings[Settings::NativeInput::All[i]] =
|
Settings::values.input_mappings[Settings::NativeInput::All[i]] =
|
||||||
qt_config->value(QString::fromStdString(Settings::NativeInput::Mapping[i]), defaults[i]).toInt();
|
qt_config->value(QString::fromStdString(Settings::NativeInput::Mapping[i]), defaults[i])
|
||||||
|
.toInt();
|
||||||
}
|
}
|
||||||
Settings::values.pad_circle_modifier_scale = qt_config->value("pad_circle_modifier_scale", 0.5).toFloat();
|
Settings::values.pad_circle_modifier_scale =
|
||||||
|
qt_config->value("pad_circle_modifier_scale", 0.5).toFloat();
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("Core");
|
qt_config->beginGroup("Core");
|
||||||
|
@ -48,7 +47,8 @@ void Config::ReadValues() {
|
||||||
qt_config->beginGroup("Renderer");
|
qt_config->beginGroup("Renderer");
|
||||||
Settings::values.use_hw_renderer = qt_config->value("use_hw_renderer", true).toBool();
|
Settings::values.use_hw_renderer = qt_config->value("use_hw_renderer", true).toBool();
|
||||||
Settings::values.use_shader_jit = qt_config->value("use_shader_jit", true).toBool();
|
Settings::values.use_shader_jit = qt_config->value("use_shader_jit", true).toBool();
|
||||||
Settings::values.use_scaled_resolution = qt_config->value("use_scaled_resolution", false).toBool();
|
Settings::values.use_scaled_resolution =
|
||||||
|
qt_config->value("use_scaled_resolution", false).toBool();
|
||||||
Settings::values.use_vsync = qt_config->value("use_vsync", false).toBool();
|
Settings::values.use_vsync = qt_config->value("use_vsync", false).toBool();
|
||||||
|
|
||||||
Settings::values.bg_red = qt_config->value("bg_red", 1.0).toFloat();
|
Settings::values.bg_red = qt_config->value("bg_red", 1.0).toFloat();
|
||||||
|
@ -58,7 +58,8 @@ void Config::ReadValues() {
|
||||||
|
|
||||||
qt_config->beginGroup("Audio");
|
qt_config->beginGroup("Audio");
|
||||||
Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString();
|
Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString();
|
||||||
Settings::values.enable_audio_stretching = qt_config->value("enable_audio_stretching", true).toBool();
|
Settings::values.enable_audio_stretching =
|
||||||
|
qt_config->value("enable_audio_stretching", true).toBool();
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("Data Storage");
|
qt_config->beginGroup("Data Storage");
|
||||||
|
@ -84,10 +85,14 @@ void Config::ReadValues() {
|
||||||
qt_config->beginGroup("UILayout");
|
qt_config->beginGroup("UILayout");
|
||||||
UISettings::values.geometry = qt_config->value("geometry").toByteArray();
|
UISettings::values.geometry = qt_config->value("geometry").toByteArray();
|
||||||
UISettings::values.state = qt_config->value("state").toByteArray();
|
UISettings::values.state = qt_config->value("state").toByteArray();
|
||||||
UISettings::values.renderwindow_geometry = qt_config->value("geometryRenderWindow").toByteArray();
|
UISettings::values.renderwindow_geometry =
|
||||||
UISettings::values.gamelist_header_state = qt_config->value("gameListHeaderState").toByteArray();
|
qt_config->value("geometryRenderWindow").toByteArray();
|
||||||
UISettings::values.microprofile_geometry = qt_config->value("microProfileDialogGeometry").toByteArray();
|
UISettings::values.gamelist_header_state =
|
||||||
UISettings::values.microprofile_visible = qt_config->value("microProfileDialogVisible", false).toBool();
|
qt_config->value("gameListHeaderState").toByteArray();
|
||||||
|
UISettings::values.microprofile_geometry =
|
||||||
|
qt_config->value("microProfileDialogGeometry").toByteArray();
|
||||||
|
UISettings::values.microprofile_visible =
|
||||||
|
qt_config->value("microProfileDialogVisible", false).toBool();
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("Paths");
|
qt_config->beginGroup("Paths");
|
||||||
|
@ -106,8 +111,8 @@ void Config::ReadValues() {
|
||||||
QStringList hotkeys = qt_config->childGroups();
|
QStringList hotkeys = qt_config->childGroups();
|
||||||
for (auto hotkey : hotkeys) {
|
for (auto hotkey : hotkeys) {
|
||||||
qt_config->beginGroup(hotkey);
|
qt_config->beginGroup(hotkey);
|
||||||
UISettings::values.shortcuts.emplace_back(
|
UISettings::values.shortcuts.emplace_back(UISettings::Shortcut(
|
||||||
UISettings::Shortcut(group + "/" + hotkey,
|
group + "/" + hotkey,
|
||||||
UISettings::ContextualShortcut(qt_config->value("KeySeq").toString(),
|
UISettings::ContextualShortcut(qt_config->value("KeySeq").toString(),
|
||||||
qt_config->value("Context").toInt())));
|
qt_config->value("Context").toInt())));
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
@ -119,7 +124,7 @@ void Config::ReadValues() {
|
||||||
|
|
||||||
UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool();
|
UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool();
|
||||||
UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool();
|
UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool();
|
||||||
UISettings::values.confirm_before_closing = qt_config->value("confirmClose",true).toBool();
|
UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool();
|
||||||
UISettings::values.first_start = qt_config->value("firstStart", true).toBool();
|
UISettings::values.first_start = qt_config->value("firstStart", true).toBool();
|
||||||
|
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
@ -131,7 +136,8 @@ void Config::SaveValues() {
|
||||||
qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]),
|
qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]),
|
||||||
Settings::values.input_mappings[Settings::NativeInput::All[i]]);
|
Settings::values.input_mappings[Settings::NativeInput::All[i]]);
|
||||||
}
|
}
|
||||||
qt_config->setValue("pad_circle_modifier_scale", (double)Settings::values.pad_circle_modifier_scale);
|
qt_config->setValue("pad_circle_modifier_scale",
|
||||||
|
(double)Settings::values.pad_circle_modifier_scale);
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("Core");
|
qt_config->beginGroup("Core");
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ class Config {
|
||||||
|
|
||||||
void ReadValues();
|
void ReadValues();
|
||||||
void SaveValues();
|
void SaveValues();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Config();
|
Config();
|
||||||
~Config();
|
~Config();
|
||||||
|
|
|
@ -9,10 +9,8 @@
|
||||||
|
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
ConfigureAudio::ConfigureAudio(QWidget* parent) :
|
ConfigureAudio::ConfigureAudio(QWidget* parent)
|
||||||
QWidget(parent),
|
: QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()) {
|
||||||
ui(std::make_unique<Ui::ConfigureAudio>())
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
ui->output_sink_combo_box->clear();
|
ui->output_sink_combo_box->clear();
|
||||||
|
@ -41,7 +39,9 @@ void ConfigureAudio::setConfiguration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureAudio::applyConfiguration() {
|
void ConfigureAudio::applyConfiguration() {
|
||||||
Settings::values.sink_id = ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()).toStdString();
|
Settings::values.sink_id =
|
||||||
|
ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
|
||||||
|
.toStdString();
|
||||||
Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked();
|
Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked();
|
||||||
Settings::Apply();
|
Settings::Apply();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ConfigureAudio;
|
class ConfigureAudio;
|
||||||
|
|
|
@ -7,10 +7,7 @@
|
||||||
|
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
ConfigureDebug::ConfigureDebug(QWidget *parent) :
|
ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureDebug) {
|
||||||
QWidget(parent),
|
|
||||||
ui(new Ui::ConfigureDebug)
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
this->setConfiguration();
|
this->setConfiguration();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,18 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ConfigureDebug;
|
class ConfigureDebug;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigureDebug : public QWidget
|
class ConfigureDebug : public QWidget {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ConfigureDebug(QWidget *parent = nullptr);
|
explicit ConfigureDebug(QWidget* parent = nullptr);
|
||||||
~ConfigureDebug();
|
~ConfigureDebug();
|
||||||
|
|
||||||
void applyConfiguration();
|
void applyConfiguration();
|
||||||
|
|
|
@ -6,13 +6,9 @@
|
||||||
#include "citra_qt/configure_dialog.h"
|
#include "citra_qt/configure_dialog.h"
|
||||||
#include "ui_configure.h"
|
#include "ui_configure.h"
|
||||||
|
|
||||||
|
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
ConfigureDialog::ConfigureDialog(QWidget *parent) :
|
ConfigureDialog::ConfigureDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ConfigureDialog) {
|
||||||
QDialog(parent),
|
|
||||||
ui(new Ui::ConfigureDialog)
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
this->setConfiguration();
|
this->setConfiguration();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,18 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ConfigureDialog;
|
class ConfigureDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigureDialog : public QDialog
|
class ConfigureDialog : public QDialog {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ConfigureDialog(QWidget *parent);
|
explicit ConfigureDialog(QWidget* parent);
|
||||||
~ConfigureDialog();
|
~ConfigureDialog();
|
||||||
|
|
||||||
void applyConfiguration();
|
void applyConfiguration();
|
||||||
|
|
|
@ -9,10 +9,8 @@
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "core/system.h"
|
#include "core/system.h"
|
||||||
|
|
||||||
ConfigureGeneral::ConfigureGeneral(QWidget *parent) :
|
ConfigureGeneral::ConfigureGeneral(QWidget* parent)
|
||||||
QWidget(parent),
|
: QWidget(parent), ui(new Ui::ConfigureGeneral) {
|
||||||
ui(new Ui::ConfigureGeneral)
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
this->setConfiguration();
|
this->setConfiguration();
|
||||||
|
|
||||||
|
|
|
@ -4,19 +4,18 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ConfigureGeneral;
|
class ConfigureGeneral;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigureGeneral : public QWidget
|
class ConfigureGeneral : public QWidget {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ConfigureGeneral(QWidget *parent = nullptr);
|
explicit ConfigureGeneral(QWidget* parent = nullptr);
|
||||||
~ConfigureGeneral();
|
~ConfigureGeneral();
|
||||||
|
|
||||||
void applyConfiguration();
|
void applyConfiguration();
|
||||||
|
|
|
@ -8,10 +8,8 @@
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "core/system.h"
|
#include "core/system.h"
|
||||||
|
|
||||||
ConfigureGraphics::ConfigureGraphics(QWidget *parent) :
|
ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
||||||
QWidget(parent),
|
: QWidget(parent), ui(new Ui::ConfigureGraphics) {
|
||||||
ui(new Ui::ConfigureGraphics)
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
this->setConfiguration();
|
this->setConfiguration();
|
||||||
|
|
||||||
|
|
|
@ -4,19 +4,18 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ConfigureGraphics;
|
class ConfigureGraphics;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigureGraphics : public QWidget
|
class ConfigureGraphics : public QWidget {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ConfigureGraphics(QWidget *parent = nullptr);
|
explicit ConfigureGraphics(QWidget* parent = nullptr);
|
||||||
~ConfigureGraphics();
|
~ConfigureGraphics();
|
||||||
|
|
||||||
void applyConfiguration();
|
void applyConfiguration();
|
||||||
|
|
|
@ -2,41 +2,42 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include "citra_qt/configure_input.h"
|
#include "citra_qt/configure_input.h"
|
||||||
|
|
||||||
ConfigureInput::ConfigureInput(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
|
ConfigureInput::ConfigureInput(QWidget* parent)
|
||||||
|
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
// Initialize mapping of input enum to UI button.
|
// Initialize mapping of input enum to UI button.
|
||||||
input_mapping = {
|
input_mapping = {
|
||||||
{ std::make_pair(Settings::NativeInput::Values::A, ui->buttonA) },
|
{std::make_pair(Settings::NativeInput::Values::A, ui->buttonA)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::B, ui->buttonB) },
|
{std::make_pair(Settings::NativeInput::Values::B, ui->buttonB)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::X, ui->buttonX) },
|
{std::make_pair(Settings::NativeInput::Values::X, ui->buttonX)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::Y, ui->buttonY) },
|
{std::make_pair(Settings::NativeInput::Values::Y, ui->buttonY)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::L, ui->buttonL) },
|
{std::make_pair(Settings::NativeInput::Values::L, ui->buttonL)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::R, ui->buttonR) },
|
{std::make_pair(Settings::NativeInput::Values::R, ui->buttonR)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::ZL, ui->buttonZL) },
|
{std::make_pair(Settings::NativeInput::Values::ZL, ui->buttonZL)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::ZR, ui->buttonZR) },
|
{std::make_pair(Settings::NativeInput::Values::ZR, ui->buttonZR)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::START, ui->buttonStart) },
|
{std::make_pair(Settings::NativeInput::Values::START, ui->buttonStart)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::SELECT, ui->buttonSelect) },
|
{std::make_pair(Settings::NativeInput::Values::SELECT, ui->buttonSelect)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::HOME, ui->buttonHome) },
|
{std::make_pair(Settings::NativeInput::Values::HOME, ui->buttonHome)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::DUP, ui->buttonDpadUp) },
|
{std::make_pair(Settings::NativeInput::Values::DUP, ui->buttonDpadUp)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::DDOWN, ui->buttonDpadDown) },
|
{std::make_pair(Settings::NativeInput::Values::DDOWN, ui->buttonDpadDown)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::DLEFT, ui->buttonDpadLeft) },
|
{std::make_pair(Settings::NativeInput::Values::DLEFT, ui->buttonDpadLeft)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::DRIGHT, ui->buttonDpadRight) },
|
{std::make_pair(Settings::NativeInput::Values::DRIGHT, ui->buttonDpadRight)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::CUP, ui->buttonCStickUp) },
|
{std::make_pair(Settings::NativeInput::Values::CUP, ui->buttonCStickUp)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::CDOWN, ui->buttonCStickDown) },
|
{std::make_pair(Settings::NativeInput::Values::CDOWN, ui->buttonCStickDown)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::CLEFT, ui->buttonCStickLeft) },
|
{std::make_pair(Settings::NativeInput::Values::CLEFT, ui->buttonCStickLeft)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::CRIGHT, ui->buttonCStickRight) },
|
{std::make_pair(Settings::NativeInput::Values::CRIGHT, ui->buttonCStickRight)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::CIRCLE_UP, ui->buttonCircleUp) },
|
{std::make_pair(Settings::NativeInput::Values::CIRCLE_UP, ui->buttonCircleUp)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::CIRCLE_DOWN, ui->buttonCircleDown) },
|
{std::make_pair(Settings::NativeInput::Values::CIRCLE_DOWN, ui->buttonCircleDown)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::CIRCLE_LEFT, ui->buttonCircleLeft) },
|
{std::make_pair(Settings::NativeInput::Values::CIRCLE_LEFT, ui->buttonCircleLeft)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::CIRCLE_RIGHT, ui->buttonCircleRight) },
|
{std::make_pair(Settings::NativeInput::Values::CIRCLE_RIGHT, ui->buttonCircleRight)},
|
||||||
{ std::make_pair(Settings::NativeInput::Values::CIRCLE_MODIFIER, ui->buttonCircleMod) },
|
{std::make_pair(Settings::NativeInput::Values::CIRCLE_MODIFIER, ui->buttonCircleMod)},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Attach handle click method to each button click.
|
// Attach handle click method to each button click.
|
||||||
|
@ -47,7 +48,10 @@ ConfigureInput::ConfigureInput(QWidget* parent) : QWidget(parent), ui(std::make_
|
||||||
setFocusPolicy(Qt::ClickFocus);
|
setFocusPolicy(Qt::ClickFocus);
|
||||||
timer = new QTimer(this);
|
timer = new QTimer(this);
|
||||||
timer->setSingleShot(true);
|
timer->setSingleShot(true);
|
||||||
connect(timer, &QTimer::timeout, this, [&]() { key_pressed = Qt::Key_Escape; setKey(); });
|
connect(timer, &QTimer::timeout, this, [&]() {
|
||||||
|
key_pressed = Qt::Key_Escape;
|
||||||
|
setKey();
|
||||||
|
});
|
||||||
this->setConfiguration();
|
this->setConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +63,7 @@ void ConfigureInput::handleClick() {
|
||||||
grabKeyboard();
|
grabKeyboard();
|
||||||
grabMouse();
|
grabMouse();
|
||||||
changing_button = sender;
|
changing_button = sender;
|
||||||
timer->start(5000); //Cancel after 5 seconds
|
timer->start(5000); // Cancel after 5 seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureInput::applyConfiguration() {
|
void ConfigureInput::applyConfiguration() {
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
#include "citra_qt/config.h"
|
#include "citra_qt/config.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
@ -16,7 +16,7 @@ class QString;
|
||||||
class QTimer;
|
class QTimer;
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ConfigureInput;
|
class ConfigureInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigureInput : public QWidget {
|
class ConfigureInput : public QWidget {
|
||||||
|
@ -39,7 +39,8 @@ private:
|
||||||
/// Load configuration settings into button text
|
/// Load configuration settings into button text
|
||||||
void setConfiguration();
|
void setConfiguration();
|
||||||
|
|
||||||
/// Check all inputs for duplicate keys. Clears out any other button with the same value as this button's new value.
|
/// Check all inputs for duplicate keys. Clears out any other button with the same value as this
|
||||||
|
/// button's new value.
|
||||||
void removeDuplicates(const QString& newValue);
|
void removeDuplicates(const QString& newValue);
|
||||||
|
|
||||||
/// Handle key press event for input tab when a button is 'waiting'.
|
/// Handle key press event for input tab when a button is 'waiting'.
|
||||||
|
|
|
@ -6,19 +6,16 @@
|
||||||
#include "citra_qt/ui_settings.h"
|
#include "citra_qt/ui_settings.h"
|
||||||
#include "ui_configure_system.h"
|
#include "ui_configure_system.h"
|
||||||
|
|
||||||
#include "core/hle/service/fs/archive.h"
|
|
||||||
#include "core/hle/service/cfg/cfg.h"
|
#include "core/hle/service/cfg/cfg.h"
|
||||||
|
#include "core/hle/service/fs/archive.h"
|
||||||
#include "core/system.h"
|
#include "core/system.h"
|
||||||
|
|
||||||
static const std::array<int, 12> days_in_month = {{
|
static const std::array<int, 12> days_in_month = {{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
|
||||||
31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
|
|
||||||
}};
|
|
||||||
|
|
||||||
ConfigureSystem::ConfigureSystem(QWidget *parent) :
|
ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
|
||||||
QWidget(parent),
|
|
||||||
ui(new Ui::ConfigureSystem) {
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
connect(ui->combo_birthmonth, SIGNAL(currentIndexChanged(int)), SLOT(updateBirthdayComboBox(int)));
|
connect(ui->combo_birthmonth, SIGNAL(currentIndexChanged(int)),
|
||||||
|
SLOT(updateBirthdayComboBox(int)));
|
||||||
|
|
||||||
this->setConfiguration();
|
this->setConfiguration();
|
||||||
}
|
}
|
||||||
|
@ -54,13 +51,17 @@ void ConfigureSystem::setConfiguration() {
|
||||||
void ConfigureSystem::ReadSystemSettings() {
|
void ConfigureSystem::ReadSystemSettings() {
|
||||||
// set username
|
// set username
|
||||||
username = Service::CFG::GetUsername();
|
username = Service::CFG::GetUsername();
|
||||||
// ui->edit_username->setText(QString::fromStdU16String(username)); // TODO(wwylele): Use this when we move to Qt 5.5
|
// ui->edit_username->setText(QString::fromStdU16String(username)); // TODO(wwylele): Use this
|
||||||
ui->edit_username->setText(QString::fromUtf16(reinterpret_cast<const ushort*>(username.data())));
|
// when we move to Qt 5.5
|
||||||
|
ui->edit_username->setText(
|
||||||
|
QString::fromUtf16(reinterpret_cast<const ushort*>(username.data())));
|
||||||
|
|
||||||
// set birthday
|
// set birthday
|
||||||
std::tie(birthmonth, birthday) = Service::CFG::GetBirthday();
|
std::tie(birthmonth, birthday) = Service::CFG::GetBirthday();
|
||||||
ui->combo_birthmonth->setCurrentIndex(birthmonth - 1);
|
ui->combo_birthmonth->setCurrentIndex(birthmonth - 1);
|
||||||
updateBirthdayComboBox(birthmonth - 1); // explicitly update it because the signal from setCurrentIndex is not reliable
|
updateBirthdayComboBox(
|
||||||
|
birthmonth -
|
||||||
|
1); // explicitly update it because the signal from setCurrentIndex is not reliable
|
||||||
ui->combo_birthday->setCurrentIndex(birthday - 1);
|
ui->combo_birthday->setCurrentIndex(birthday - 1);
|
||||||
|
|
||||||
// set system language
|
// set system language
|
||||||
|
@ -79,8 +80,10 @@ void ConfigureSystem::applyConfiguration() {
|
||||||
bool modified = false;
|
bool modified = false;
|
||||||
|
|
||||||
// apply username
|
// apply username
|
||||||
// std::u16string new_username = ui->edit_username->text().toStdU16String(); // TODO(wwylele): Use this when we move to Qt 5.5
|
// std::u16string new_username = ui->edit_username->text().toStdU16String(); // TODO(wwylele):
|
||||||
std::u16string new_username(reinterpret_cast<const char16_t*>(ui->edit_username->text().utf16()));
|
// Use this when we move to Qt 5.5
|
||||||
|
std::u16string new_username(
|
||||||
|
reinterpret_cast<const char16_t*>(ui->edit_username->text().utf16()));
|
||||||
if (new_username != username) {
|
if (new_username != username) {
|
||||||
Service::CFG::SetUsername(new_username);
|
Service::CFG::SetUsername(new_username);
|
||||||
modified = true;
|
modified = true;
|
||||||
|
|
|
@ -4,19 +4,18 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ConfigureSystem;
|
class ConfigureSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigureSystem : public QWidget
|
class ConfigureSystem : public QWidget {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ConfigureSystem(QWidget *parent = nullptr);
|
explicit ConfigureSystem(QWidget* parent = nullptr);
|
||||||
~ConfigureSystem();
|
~ConfigureSystem();
|
||||||
|
|
||||||
void applyConfiguration();
|
void applyConfiguration();
|
||||||
|
|
|
@ -9,13 +9,12 @@
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/symbols.h"
|
#include "common/symbols.h"
|
||||||
|
|
||||||
#include "core/core.h"
|
|
||||||
#include "core/memory.h"
|
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#include "core/arm/disassembler/arm_disasm.h"
|
#include "core/arm/disassembler/arm_disasm.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
|
||||||
CallstackWidget::CallstackWidget(QWidget* parent): QDockWidget(parent)
|
CallstackWidget::CallstackWidget(QWidget* parent) : QDockWidget(parent) {
|
||||||
{
|
|
||||||
ui.setupUi(this);
|
ui.setupUi(this);
|
||||||
|
|
||||||
callstack_model = new QStandardItemModel(this);
|
callstack_model = new QStandardItemModel(this);
|
||||||
|
@ -27,29 +26,26 @@ CallstackWidget::CallstackWidget(QWidget* parent): QDockWidget(parent)
|
||||||
ui.treeView->setModel(callstack_model);
|
ui.treeView->setModel(callstack_model);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CallstackWidget::OnDebugModeEntered()
|
void CallstackWidget::OnDebugModeEntered() {
|
||||||
{
|
|
||||||
// Stack pointer
|
// Stack pointer
|
||||||
const u32 sp = Core::g_app_core->GetReg(13);
|
const u32 sp = Core::g_app_core->GetReg(13);
|
||||||
|
|
||||||
Clear();
|
Clear();
|
||||||
|
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
for (u32 addr = 0x10000000; addr >= sp; addr -= 4)
|
for (u32 addr = 0x10000000; addr >= sp; addr -= 4) {
|
||||||
{
|
|
||||||
if (!Memory::IsValidVirtualAddress(addr))
|
if (!Memory::IsValidVirtualAddress(addr))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
const u32 ret_addr = Memory::Read32(addr);
|
const u32 ret_addr = Memory::Read32(addr);
|
||||||
const u32 call_addr = ret_addr - 4; //get call address???
|
const u32 call_addr = ret_addr - 4; // get call address???
|
||||||
|
|
||||||
if (!Memory::IsValidVirtualAddress(call_addr))
|
if (!Memory::IsValidVirtualAddress(call_addr))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* TODO (mattvail) clean me, move to debugger interface */
|
/* TODO (mattvail) clean me, move to debugger interface */
|
||||||
u32 insn = Memory::Read32(call_addr);
|
u32 insn = Memory::Read32(call_addr);
|
||||||
if (ARM_Disasm::Decode(insn) == OP_BL)
|
if (ARM_Disasm::Decode(insn) == OP_BL) {
|
||||||
{
|
|
||||||
std::string name;
|
std::string name;
|
||||||
// ripped from disasm
|
// ripped from disasm
|
||||||
u8 cond = (insn >> 28) & 0xf;
|
u8 cond = (insn >> 28) & 0xf;
|
||||||
|
@ -63,12 +59,18 @@ void CallstackWidget::OnDebugModeEntered()
|
||||||
i_offset += 8;
|
i_offset += 8;
|
||||||
const u32 func_addr = call_addr + i_offset;
|
const u32 func_addr = call_addr + i_offset;
|
||||||
|
|
||||||
callstack_model->setItem(counter, 0, new QStandardItem(QString("0x%1").arg(addr, 8, 16, QLatin1Char('0'))));
|
callstack_model->setItem(
|
||||||
callstack_model->setItem(counter, 1, new QStandardItem(QString("0x%1").arg(ret_addr, 8, 16, QLatin1Char('0'))));
|
counter, 0, new QStandardItem(QString("0x%1").arg(addr, 8, 16, QLatin1Char('0'))));
|
||||||
callstack_model->setItem(counter, 2, new QStandardItem(QString("0x%1").arg(call_addr, 8, 16, QLatin1Char('0'))));
|
callstack_model->setItem(counter, 1, new QStandardItem(QString("0x%1").arg(
|
||||||
|
ret_addr, 8, 16, QLatin1Char('0'))));
|
||||||
|
callstack_model->setItem(counter, 2, new QStandardItem(QString("0x%1").arg(
|
||||||
|
call_addr, 8, 16, QLatin1Char('0'))));
|
||||||
|
|
||||||
name = Symbols::HasSymbol(func_addr) ? Symbols::GetSymbol(func_addr).name : "unknown";
|
name = Symbols::HasSymbol(func_addr) ? Symbols::GetSymbol(func_addr).name : "unknown";
|
||||||
callstack_model->setItem(counter, 3, new QStandardItem(QString("%1_%2").arg(QString::fromStdString(name))
|
callstack_model->setItem(
|
||||||
|
counter, 3, new QStandardItem(
|
||||||
|
QString("%1_%2")
|
||||||
|
.arg(QString::fromStdString(name))
|
||||||
.arg(QString("0x%1").arg(func_addr, 8, 16, QLatin1Char('0')))));
|
.arg(QString("0x%1").arg(func_addr, 8, 16, QLatin1Char('0')))));
|
||||||
|
|
||||||
counter++;
|
counter++;
|
||||||
|
@ -76,13 +78,10 @@ void CallstackWidget::OnDebugModeEntered()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CallstackWidget::OnDebugModeLeft()
|
void CallstackWidget::OnDebugModeLeft() {
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CallstackWidget::Clear()
|
void CallstackWidget::Clear() {
|
||||||
{
|
|
||||||
for (int row = 0; row < callstack_model->rowCount(); row++) {
|
for (int row = 0; row < callstack_model->rowCount(); row++) {
|
||||||
for (int column = 0; column < callstack_model->columnCount(); column++) {
|
for (int column = 0; column < callstack_model->columnCount(); column++) {
|
||||||
callstack_model->setItem(row, column, new QStandardItem());
|
callstack_model->setItem(row, column, new QStandardItem());
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
|
|
||||||
class QStandardItemModel;
|
class QStandardItemModel;
|
||||||
|
|
||||||
class CallstackWidget : public QDockWidget
|
class CallstackWidget : public QDockWidget {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -5,20 +5,21 @@
|
||||||
#include <QShortcut>
|
#include <QShortcut>
|
||||||
|
|
||||||
#include "citra_qt/bootmanager.h"
|
#include "citra_qt/bootmanager.h"
|
||||||
#include "citra_qt/hotkeys.h"
|
|
||||||
#include "citra_qt/debugger/disassembler.h"
|
#include "citra_qt/debugger/disassembler.h"
|
||||||
|
#include "citra_qt/hotkeys.h"
|
||||||
#include "citra_qt/util/util.h"
|
#include "citra_qt/util/util.h"
|
||||||
|
|
||||||
#include "common/break_points.h"
|
#include "common/break_points.h"
|
||||||
#include "common/symbols.h"
|
#include "common/symbols.h"
|
||||||
|
|
||||||
#include "core/core.h"
|
|
||||||
#include "core/memory.h"
|
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#include "core/arm/disassembler/arm_disasm.h"
|
#include "core/arm/disassembler/arm_disasm.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
|
||||||
DisassemblerModel::DisassemblerModel(QObject* parent) :
|
DisassemblerModel::DisassemblerModel(QObject* parent)
|
||||||
QAbstractListModel(parent), base_address(0), code_size(0), program_counter(0), selection(QModelIndex()) {
|
: QAbstractListModel(parent), base_address(0), code_size(0), program_counter(0),
|
||||||
|
selection(QModelIndex()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int DisassemblerModel::columnCount(const QModelIndex& parent) const {
|
int DisassemblerModel::columnCount(const QModelIndex& parent) const {
|
||||||
|
@ -31,8 +32,7 @@ int DisassemblerModel::rowCount(const QModelIndex& parent) const {
|
||||||
|
|
||||||
QVariant DisassemblerModel::data(const QModelIndex& index, int role) const {
|
QVariant DisassemblerModel::data(const QModelIndex& index, int role) const {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case Qt::DisplayRole:
|
case Qt::DisplayRole: {
|
||||||
{
|
|
||||||
u32 address = base_address + index.row() * 4;
|
u32 address = base_address + index.row() * 4;
|
||||||
u32 instr = Memory::Read32(address);
|
u32 instr = Memory::Read32(address);
|
||||||
std::string disassembly = ARM_Disasm::Disassemble(address, instr);
|
std::string disassembly = ARM_Disasm::Disassemble(address, instr);
|
||||||
|
@ -42,9 +42,10 @@ QVariant DisassemblerModel::data(const QModelIndex& index, int role) const {
|
||||||
} else if (index.column() == 1) {
|
} else if (index.column() == 1) {
|
||||||
return QString::fromStdString(disassembly);
|
return QString::fromStdString(disassembly);
|
||||||
} else if (index.column() == 2) {
|
} else if (index.column() == 2) {
|
||||||
if(Symbols::HasSymbol(address)) {
|
if (Symbols::HasSymbol(address)) {
|
||||||
TSymbol symbol = Symbols::GetSymbol(address);
|
TSymbol symbol = Symbols::GetSymbol(address);
|
||||||
return QString("%1 - Size:%2").arg(QString::fromStdString(symbol.name))
|
return QString("%1 - Size:%2")
|
||||||
|
.arg(QString::fromStdString(symbol.name))
|
||||||
.arg(symbol.size / 4); // divide by 4 to get instruction count
|
.arg(symbol.size / 4); // divide by 4 to get instruction count
|
||||||
} else if (ARM_Disasm::Decode(instr) == OP_BL) {
|
} else if (ARM_Disasm::Decode(instr) == OP_BL) {
|
||||||
u32 offset = instr & 0xFFFFFF;
|
u32 offset = instr & 0xFFFFFF;
|
||||||
|
@ -65,8 +66,7 @@ QVariant DisassemblerModel::data(const QModelIndex& index, int role) const {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Qt::BackgroundRole:
|
case Qt::BackgroundRole: {
|
||||||
{
|
|
||||||
unsigned int address = base_address + 4 * index.row();
|
unsigned int address = base_address + 4 * index.row();
|
||||||
|
|
||||||
if (breakpoints.IsAddressBreakPoint(address))
|
if (breakpoints.IsAddressBreakPoint(address))
|
||||||
|
@ -77,8 +77,7 @@ QVariant DisassemblerModel::data(const QModelIndex& index, int role) const {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Qt::FontRole:
|
case Qt::FontRole: {
|
||||||
{
|
|
||||||
if (index.column() == 0 || index.column() == 1) { // 2 is the symbols column
|
if (index.column() == 0 || index.column() == 1) { // 2 is the symbols column
|
||||||
return GetMonospaceFont();
|
return GetMonospaceFont();
|
||||||
}
|
}
|
||||||
|
@ -103,7 +102,7 @@ const BreakPoints& DisassemblerModel::GetBreakPoints() const {
|
||||||
void DisassemblerModel::ParseFromAddress(unsigned int address) {
|
void DisassemblerModel::ParseFromAddress(unsigned int address) {
|
||||||
|
|
||||||
// NOTE: A too large value causes lagging when scrolling the disassembly
|
// NOTE: A too large value causes lagging when scrolling the disassembly
|
||||||
const unsigned int chunk_size = 1000*500;
|
const unsigned int chunk_size = 1000 * 500;
|
||||||
|
|
||||||
// If we haven't loaded anything yet, initialize base address to the parameter address
|
// If we haven't loaded anything yet, initialize base address to the parameter address
|
||||||
if (code_size == 0)
|
if (code_size == 0)
|
||||||
|
@ -165,23 +164,26 @@ void DisassemblerModel::SetNextInstruction(unsigned int address) {
|
||||||
emit dataChanged(prev_index, prev_index);
|
emit dataChanged(prev_index, prev_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
DisassemblerWidget::DisassemblerWidget(QWidget* parent, EmuThread* emu_thread) :
|
DisassemblerWidget::DisassemblerWidget(QWidget* parent, EmuThread* emu_thread)
|
||||||
QDockWidget(parent), base_addr(0), emu_thread(emu_thread) {
|
: QDockWidget(parent), base_addr(0), emu_thread(emu_thread) {
|
||||||
|
|
||||||
disasm_ui.setupUi(this);
|
disasm_ui.setupUi(this);
|
||||||
|
|
||||||
RegisterHotkey("Disassembler", "Start/Stop", QKeySequence(Qt::Key_F5), Qt::ApplicationShortcut);
|
RegisterHotkey("Disassembler", "Start/Stop", QKeySequence(Qt::Key_F5), Qt::ApplicationShortcut);
|
||||||
RegisterHotkey("Disassembler", "Step", QKeySequence(Qt::Key_F10), Qt::ApplicationShortcut);
|
RegisterHotkey("Disassembler", "Step", QKeySequence(Qt::Key_F10), Qt::ApplicationShortcut);
|
||||||
RegisterHotkey("Disassembler", "Step into", QKeySequence(Qt::Key_F11), Qt::ApplicationShortcut);
|
RegisterHotkey("Disassembler", "Step into", QKeySequence(Qt::Key_F11), Qt::ApplicationShortcut);
|
||||||
RegisterHotkey("Disassembler", "Set Breakpoint", QKeySequence(Qt::Key_F9), Qt::ApplicationShortcut);
|
RegisterHotkey("Disassembler", "Set Breakpoint", QKeySequence(Qt::Key_F9),
|
||||||
|
Qt::ApplicationShortcut);
|
||||||
|
|
||||||
connect(disasm_ui.button_step, SIGNAL(clicked()), this, SLOT(OnStep()));
|
connect(disasm_ui.button_step, SIGNAL(clicked()), this, SLOT(OnStep()));
|
||||||
connect(disasm_ui.button_pause, SIGNAL(clicked()), this, SLOT(OnPause()));
|
connect(disasm_ui.button_pause, SIGNAL(clicked()), this, SLOT(OnPause()));
|
||||||
connect(disasm_ui.button_continue, SIGNAL(clicked()), this, SLOT(OnContinue()));
|
connect(disasm_ui.button_continue, SIGNAL(clicked()), this, SLOT(OnContinue()));
|
||||||
|
|
||||||
connect(GetHotkey("Disassembler", "Start/Stop", this), SIGNAL(activated()), this, SLOT(OnToggleStartStop()));
|
connect(GetHotkey("Disassembler", "Start/Stop", this), SIGNAL(activated()), this,
|
||||||
|
SLOT(OnToggleStartStop()));
|
||||||
connect(GetHotkey("Disassembler", "Step", this), SIGNAL(activated()), this, SLOT(OnStep()));
|
connect(GetHotkey("Disassembler", "Step", this), SIGNAL(activated()), this, SLOT(OnStep()));
|
||||||
connect(GetHotkey("Disassembler", "Step into", this), SIGNAL(activated()), this, SLOT(OnStepInto()));
|
connect(GetHotkey("Disassembler", "Step into", this), SIGNAL(activated()), this,
|
||||||
|
SLOT(OnStepInto()));
|
||||||
|
|
||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
}
|
}
|
||||||
|
@ -195,7 +197,8 @@ void DisassemblerWidget::Init() {
|
||||||
|
|
||||||
QModelIndex model_index = model->IndexFromAbsoluteAddress(Core::g_app_core->GetPC());
|
QModelIndex model_index = model->IndexFromAbsoluteAddress(Core::g_app_core->GetPC());
|
||||||
disasm_ui.treeView->scrollTo(model_index);
|
disasm_ui.treeView->scrollTo(model_index);
|
||||||
disasm_ui.treeView->selectionModel()->setCurrentIndex(model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
|
disasm_ui.treeView->selectionModel()->setCurrentIndex(
|
||||||
|
model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisassemblerWidget::OnContinue() {
|
void DisassemblerWidget::OnContinue() {
|
||||||
|
@ -234,7 +237,8 @@ void DisassemblerWidget::OnDebugModeEntered() {
|
||||||
|
|
||||||
QModelIndex model_index = model->IndexFromAbsoluteAddress(next_instr);
|
QModelIndex model_index = model->IndexFromAbsoluteAddress(next_instr);
|
||||||
disasm_ui.treeView->scrollTo(model_index);
|
disasm_ui.treeView->scrollTo(model_index);
|
||||||
disasm_ui.treeView->selectionModel()->setCurrentIndex(model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
|
disasm_ui.treeView->selectionModel()->setCurrentIndex(
|
||||||
|
model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisassemblerWidget::OnDebugModeLeft() {
|
void DisassemblerWidget::OnDebugModeLeft() {
|
||||||
|
@ -254,10 +258,12 @@ void DisassemblerWidget::OnEmulationStarting(EmuThread* emu_thread) {
|
||||||
model = new DisassemblerModel(this);
|
model = new DisassemblerModel(this);
|
||||||
disasm_ui.treeView->setModel(model);
|
disasm_ui.treeView->setModel(model);
|
||||||
|
|
||||||
connect(disasm_ui.treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
|
connect(disasm_ui.treeView->selectionModel(),
|
||||||
model, SLOT(OnSelectionChanged(const QModelIndex&)));
|
SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), model,
|
||||||
|
SLOT(OnSelectionChanged(const QModelIndex&)));
|
||||||
connect(disasm_ui.button_breakpoint, SIGNAL(clicked()), model, SLOT(OnSetOrUnsetBreakpoint()));
|
connect(disasm_ui.button_breakpoint, SIGNAL(clicked()), model, SLOT(OnSetOrUnsetBreakpoint()));
|
||||||
connect(GetHotkey("Disassembler", "Set Breakpoint", this), SIGNAL(activated()), model, SLOT(OnSetOrUnsetBreakpoint()));
|
connect(GetHotkey("Disassembler", "Set Breakpoint", this), SIGNAL(activated()), model,
|
||||||
|
SLOT(OnSetOrUnsetBreakpoint()));
|
||||||
|
|
||||||
Init();
|
Init();
|
||||||
setEnabled(true);
|
setEnabled(true);
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
class QAction;
|
class QAction;
|
||||||
class EmuThread;
|
class EmuThread;
|
||||||
|
|
||||||
class DisassemblerModel : public QAbstractListModel
|
class DisassemblerModel : public QAbstractListModel {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -46,8 +45,7 @@ private:
|
||||||
mutable BreakPoints breakpoints;
|
mutable BreakPoints breakpoints;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DisassemblerWidget : public QDockWidget
|
class DisassemblerWidget : public QDockWidget {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -9,35 +9,33 @@
|
||||||
|
|
||||||
extern GraphicsDebugger g_debugger;
|
extern GraphicsDebugger g_debugger;
|
||||||
|
|
||||||
GPUCommandStreamItemModel::GPUCommandStreamItemModel(QObject* parent) : QAbstractListModel(parent), command_count(0)
|
GPUCommandStreamItemModel::GPUCommandStreamItemModel(QObject* parent)
|
||||||
{
|
: QAbstractListModel(parent), command_count(0) {
|
||||||
connect(this, SIGNAL(GXCommandFinished(int)), this, SLOT(OnGXCommandFinishedInternal(int)));
|
connect(this, SIGNAL(GXCommandFinished(int)), this, SLOT(OnGXCommandFinishedInternal(int)));
|
||||||
}
|
}
|
||||||
|
|
||||||
int GPUCommandStreamItemModel::rowCount(const QModelIndex& parent) const
|
int GPUCommandStreamItemModel::rowCount(const QModelIndex& parent) const {
|
||||||
{
|
|
||||||
return command_count;
|
return command_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant GPUCommandStreamItemModel::data(const QModelIndex& index, int role) const
|
QVariant GPUCommandStreamItemModel::data(const QModelIndex& index, int role) const {
|
||||||
{
|
|
||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
int command_index = index.row();
|
int command_index = index.row();
|
||||||
const GSP_GPU::Command& command = GetDebugger()->ReadGXCommandHistory(command_index);
|
const GSP_GPU::Command& command = GetDebugger()->ReadGXCommandHistory(command_index);
|
||||||
if (role == Qt::DisplayRole)
|
if (role == Qt::DisplayRole) {
|
||||||
{
|
|
||||||
std::map<GSP_GPU::CommandId, const char*> command_names = {
|
std::map<GSP_GPU::CommandId, const char*> command_names = {
|
||||||
{ GSP_GPU::CommandId::REQUEST_DMA, "REQUEST_DMA" },
|
{GSP_GPU::CommandId::REQUEST_DMA, "REQUEST_DMA"},
|
||||||
{ GSP_GPU::CommandId::SUBMIT_GPU_CMDLIST, "SUBMIT_GPU_CMDLIST" },
|
{GSP_GPU::CommandId::SUBMIT_GPU_CMDLIST, "SUBMIT_GPU_CMDLIST"},
|
||||||
{ GSP_GPU::CommandId::SET_MEMORY_FILL, "SET_MEMORY_FILL" },
|
{GSP_GPU::CommandId::SET_MEMORY_FILL, "SET_MEMORY_FILL"},
|
||||||
{ GSP_GPU::CommandId::SET_DISPLAY_TRANSFER, "SET_DISPLAY_TRANSFER" },
|
{GSP_GPU::CommandId::SET_DISPLAY_TRANSFER, "SET_DISPLAY_TRANSFER"},
|
||||||
{ GSP_GPU::CommandId::SET_TEXTURE_COPY, "SET_TEXTURE_COPY" },
|
{GSP_GPU::CommandId::SET_TEXTURE_COPY, "SET_TEXTURE_COPY"},
|
||||||
{ GSP_GPU::CommandId::CACHE_FLUSH, "CACHE_FLUSH" },
|
{GSP_GPU::CommandId::CACHE_FLUSH, "CACHE_FLUSH"},
|
||||||
};
|
};
|
||||||
const u32* command_data = reinterpret_cast<const u32*>(&command);
|
const u32* command_data = reinterpret_cast<const u32*>(&command);
|
||||||
QString str = QString("%1 %2 %3 %4 %5 %6 %7 %8 %9").arg(command_names[command.id])
|
QString str = QString("%1 %2 %3 %4 %5 %6 %7 %8 %9")
|
||||||
|
.arg(command_names[command.id])
|
||||||
.arg(command_data[0], 8, 16, QLatin1Char('0'))
|
.arg(command_data[0], 8, 16, QLatin1Char('0'))
|
||||||
.arg(command_data[1], 8, 16, QLatin1Char('0'))
|
.arg(command_data[1], 8, 16, QLatin1Char('0'))
|
||||||
.arg(command_data[2], 8, 16, QLatin1Char('0'))
|
.arg(command_data[2], 8, 16, QLatin1Char('0'))
|
||||||
|
@ -47,31 +45,26 @@ QVariant GPUCommandStreamItemModel::data(const QModelIndex& index, int role) con
|
||||||
.arg(command_data[6], 8, 16, QLatin1Char('0'))
|
.arg(command_data[6], 8, 16, QLatin1Char('0'))
|
||||||
.arg(command_data[7], 8, 16, QLatin1Char('0'));
|
.arg(command_data[7], 8, 16, QLatin1Char('0'));
|
||||||
return QVariant(str);
|
return QVariant(str);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPUCommandStreamItemModel::GXCommandProcessed(int total_command_count)
|
void GPUCommandStreamItemModel::GXCommandProcessed(int total_command_count) {
|
||||||
{
|
|
||||||
emit GXCommandFinished(total_command_count);
|
emit GXCommandFinished(total_command_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPUCommandStreamItemModel::OnGXCommandFinishedInternal(int total_command_count)
|
void GPUCommandStreamItemModel::OnGXCommandFinishedInternal(int total_command_count) {
|
||||||
{
|
|
||||||
if (total_command_count == 0)
|
if (total_command_count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int prev_command_count = command_count;
|
int prev_command_count = command_count;
|
||||||
command_count = total_command_count;
|
command_count = total_command_count;
|
||||||
emit dataChanged(index(prev_command_count,0), index(total_command_count-1,0));
|
emit dataChanged(index(prev_command_count, 0), index(total_command_count - 1, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GPUCommandStreamWidget::GPUCommandStreamWidget(QWidget* parent)
|
||||||
GPUCommandStreamWidget::GPUCommandStreamWidget(QWidget* parent) : QDockWidget(tr("Graphics Debugger"), parent)
|
: QDockWidget(tr("Graphics Debugger"), parent) {
|
||||||
{
|
|
||||||
setObjectName("GraphicsDebugger");
|
setObjectName("GraphicsDebugger");
|
||||||
|
|
||||||
GPUCommandStreamItemModel* command_model = new GPUCommandStreamItemModel(this);
|
GPUCommandStreamItemModel* command_model = new GPUCommandStreamItemModel(this);
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
|
|
||||||
#include "video_core/gpu_debugger.h"
|
#include "video_core/gpu_debugger.h"
|
||||||
|
|
||||||
class GPUCommandStreamItemModel : public QAbstractListModel, public GraphicsDebugger::DebuggerObserver
|
class GPUCommandStreamItemModel : public QAbstractListModel,
|
||||||
{
|
public GraphicsDebugger::DebuggerObserver {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -32,8 +32,7 @@ private:
|
||||||
int command_count;
|
int command_count;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GPUCommandStreamWidget : public QDockWidget
|
class GPUCommandStreamWidget : public QDockWidget {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -8,25 +8,21 @@
|
||||||
|
|
||||||
BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context,
|
BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context,
|
||||||
const QString& title, QWidget* parent)
|
const QString& title, QWidget* parent)
|
||||||
: QDockWidget(title, parent), BreakPointObserver(debug_context)
|
: QDockWidget(title, parent), BreakPointObserver(debug_context) {
|
||||||
{
|
|
||||||
qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event");
|
qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event");
|
||||||
|
|
||||||
connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
|
connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
|
||||||
|
|
||||||
// NOTE: This signal is emitted from a non-GUI thread, but connect() takes
|
// NOTE: This signal is emitted from a non-GUI thread, but connect() takes
|
||||||
// care of delaying its handling to the GUI thread.
|
// care of delaying its handling to the GUI thread.
|
||||||
connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)),
|
connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event, void*)), this,
|
||||||
this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)),
|
SLOT(OnBreakPointHit(Pica::DebugContext::Event, void*)), Qt::BlockingQueuedConnection);
|
||||||
Qt::BlockingQueuedConnection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreakPointObserverDock::OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data)
|
void BreakPointObserverDock::OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) {
|
||||||
{
|
|
||||||
emit BreakPointHit(event, data);
|
emit BreakPointHit(event, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreakPointObserverDock::OnPicaResume()
|
void BreakPointObserverDock::OnPicaResume() {
|
||||||
{
|
|
||||||
emit Resumed();
|
emit Resumed();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
* This is because the Pica breakpoint callbacks are called from a non-GUI thread, while
|
* 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.
|
* the widget usually wants to perform reactions in the GUI thread.
|
||||||
*/
|
*/
|
||||||
class BreakPointObserverDock : public QDockWidget, protected Pica::DebugContext::BreakPointObserver {
|
class BreakPointObserverDock : public QDockWidget,
|
||||||
|
protected Pica::DebugContext::BreakPointObserver {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -16,39 +16,33 @@
|
||||||
BreakPointModel::BreakPointModel(std::shared_ptr<Pica::DebugContext> debug_context, QObject* parent)
|
BreakPointModel::BreakPointModel(std::shared_ptr<Pica::DebugContext> debug_context, QObject* parent)
|
||||||
: QAbstractListModel(parent), context_weak(debug_context),
|
: QAbstractListModel(parent), context_weak(debug_context),
|
||||||
at_breakpoint(debug_context->at_breakpoint),
|
at_breakpoint(debug_context->at_breakpoint),
|
||||||
active_breakpoint(debug_context->active_breakpoint)
|
active_breakpoint(debug_context->active_breakpoint) {
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int BreakPointModel::columnCount(const QModelIndex& parent) const
|
int BreakPointModel::columnCount(const QModelIndex& parent) const {
|
||||||
{
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int BreakPointModel::rowCount(const QModelIndex& parent) const
|
int BreakPointModel::rowCount(const QModelIndex& parent) const {
|
||||||
{
|
|
||||||
return static_cast<int>(Pica::DebugContext::Event::NumEvents);
|
return static_cast<int>(Pica::DebugContext::Event::NumEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant BreakPointModel::data(const QModelIndex& index, int role) const
|
QVariant BreakPointModel::data(const QModelIndex& index, int role) const {
|
||||||
{
|
|
||||||
const auto event = static_cast<Pica::DebugContext::Event>(index.row());
|
const auto event = static_cast<Pica::DebugContext::Event>(index.row());
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case Qt::DisplayRole:
|
case Qt::DisplayRole: {
|
||||||
{
|
|
||||||
if (index.column() == 0) {
|
if (index.column() == 0) {
|
||||||
static const std::map<Pica::DebugContext::Event, QString> map = {
|
static const std::map<Pica::DebugContext::Event, QString> map = {
|
||||||
{ Pica::DebugContext::Event::PicaCommandLoaded, tr("Pica command loaded") },
|
{Pica::DebugContext::Event::PicaCommandLoaded, tr("Pica command loaded")},
|
||||||
{ Pica::DebugContext::Event::PicaCommandProcessed, tr("Pica command processed") },
|
{Pica::DebugContext::Event::PicaCommandProcessed, tr("Pica command processed")},
|
||||||
{ Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incoming primitive batch") },
|
{Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incoming primitive batch")},
|
||||||
{ Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch") },
|
{Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch")},
|
||||||
{ Pica::DebugContext::Event::VertexShaderInvocation, tr("Vertex shader invocation") },
|
{Pica::DebugContext::Event::VertexShaderInvocation, tr("Vertex shader invocation")},
|
||||||
{ Pica::DebugContext::Event::IncomingDisplayTransfer, tr("Incoming display transfer") },
|
{Pica::DebugContext::Event::IncomingDisplayTransfer,
|
||||||
{ Pica::DebugContext::Event::GSPCommandProcessed, tr("GSP command processed") },
|
tr("Incoming display transfer")},
|
||||||
{ Pica::DebugContext::Event::BufferSwapped, tr("Buffers swapped") }
|
{Pica::DebugContext::Event::GSPCommandProcessed, tr("GSP command processed")},
|
||||||
};
|
{Pica::DebugContext::Event::BufferSwapped, tr("Buffers swapped")}};
|
||||||
|
|
||||||
DEBUG_ASSERT(map.size() == static_cast<size_t>(Pica::DebugContext::Event::NumEvents));
|
DEBUG_ASSERT(map.size() == static_cast<size_t>(Pica::DebugContext::Event::NumEvents));
|
||||||
return (map.find(event) != map.end()) ? map.at(event) : QString();
|
return (map.find(event) != map.end()) ? map.at(event) : QString();
|
||||||
|
@ -57,23 +51,20 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Qt::CheckStateRole:
|
case Qt::CheckStateRole: {
|
||||||
{
|
|
||||||
if (index.column() == 0)
|
if (index.column() == 0)
|
||||||
return data(index, Role_IsEnabled).toBool() ? Qt::Checked : Qt::Unchecked;
|
return data(index, Role_IsEnabled).toBool() ? Qt::Checked : Qt::Unchecked;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Qt::BackgroundRole:
|
case Qt::BackgroundRole: {
|
||||||
{
|
|
||||||
if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) {
|
if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) {
|
||||||
return QBrush(QColor(0xE0, 0xE0, 0x10));
|
return QBrush(QColor(0xE0, 0xE0, 0x10));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Role_IsEnabled:
|
case Role_IsEnabled: {
|
||||||
{
|
|
||||||
auto context = context_weak.lock();
|
auto context = context_weak.lock();
|
||||||
return context && context->breakpoints[(int)event].enabled;
|
return context && context->breakpoints[(int)event].enabled;
|
||||||
}
|
}
|
||||||
|
@ -84,8 +75,7 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
Qt::ItemFlags BreakPointModel::flags(const QModelIndex &index) const
|
Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const {
|
||||||
{
|
|
||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -95,14 +85,11 @@ Qt::ItemFlags BreakPointModel::flags(const QModelIndex &index) const
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) {
|
||||||
bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
|
||||||
{
|
|
||||||
const auto event = static_cast<Pica::DebugContext::Event>(index.row());
|
const auto event = static_cast<Pica::DebugContext::Event>(index.row());
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case Qt::CheckStateRole:
|
case Qt::CheckStateRole: {
|
||||||
{
|
|
||||||
if (index.column() != 0)
|
if (index.column() != 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -120,9 +107,7 @@ bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, i
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BreakPointModel::OnBreakPointHit(Pica::DebugContext::Event event) {
|
||||||
void BreakPointModel::OnBreakPointHit(Pica::DebugContext::Event event)
|
|
||||||
{
|
|
||||||
auto context = context_weak.lock();
|
auto context = context_weak.lock();
|
||||||
if (!context)
|
if (!context)
|
||||||
return;
|
return;
|
||||||
|
@ -133,8 +118,7 @@ void BreakPointModel::OnBreakPointHit(Pica::DebugContext::Event event)
|
||||||
createIndex(static_cast<int>(event), 0));
|
createIndex(static_cast<int>(event), 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreakPointModel::OnResumed()
|
void BreakPointModel::OnResumed() {
|
||||||
{
|
|
||||||
auto context = context_weak.lock();
|
auto context = context_weak.lock();
|
||||||
if (!context)
|
if (!context)
|
||||||
return;
|
return;
|
||||||
|
@ -145,12 +129,10 @@ void BreakPointModel::OnResumed()
|
||||||
active_breakpoint = context->active_breakpoint;
|
active_breakpoint = context->active_breakpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(
|
||||||
GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(std::shared_ptr<Pica::DebugContext> debug_context,
|
std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent)
|
||||||
QWidget* parent)
|
|
||||||
: QDockWidget(tr("Pica Breakpoints"), parent),
|
: QDockWidget(tr("Pica Breakpoints"), parent),
|
||||||
Pica::DebugContext::BreakPointObserver(debug_context)
|
Pica::DebugContext::BreakPointObserver(debug_context) {
|
||||||
{
|
|
||||||
setObjectName("PicaBreakPointsWidget");
|
setObjectName("PicaBreakPointsWidget");
|
||||||
|
|
||||||
status_text = new QLabel(tr("Emulation running"));
|
status_text = new QLabel(tr("Emulation running"));
|
||||||
|
@ -165,23 +147,21 @@ GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(std::shared_ptr<Pica::Debug
|
||||||
|
|
||||||
qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event");
|
qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event");
|
||||||
|
|
||||||
connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)),
|
connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)), this,
|
||||||
this, SLOT(OnItemDoubleClicked(const QModelIndex&)));
|
SLOT(OnItemDoubleClicked(const QModelIndex&)));
|
||||||
|
|
||||||
connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested()));
|
connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested()));
|
||||||
|
|
||||||
connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)),
|
connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event, void*)), this,
|
||||||
this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)),
|
SLOT(OnBreakPointHit(Pica::DebugContext::Event, void*)), Qt::BlockingQueuedConnection);
|
||||||
Qt::BlockingQueuedConnection);
|
|
||||||
connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
|
connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
|
||||||
|
|
||||||
connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)),
|
connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event, void*)), breakpoint_model,
|
||||||
breakpoint_model, SLOT(OnBreakPointHit(Pica::DebugContext::Event)),
|
SLOT(OnBreakPointHit(Pica::DebugContext::Event)), Qt::BlockingQueuedConnection);
|
||||||
Qt::BlockingQueuedConnection);
|
|
||||||
connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed()));
|
connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed()));
|
||||||
|
|
||||||
connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&,const QModelIndex&)),
|
connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&, const QModelIndex&)),
|
||||||
breakpoint_model, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&)));
|
breakpoint_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)));
|
||||||
|
|
||||||
QWidget* main_widget = new QWidget;
|
QWidget* main_widget = new QWidget;
|
||||||
auto main_layout = new QVBoxLayout;
|
auto main_layout = new QVBoxLayout;
|
||||||
|
@ -197,38 +177,32 @@ GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(std::shared_ptr<Pica::Debug
|
||||||
setWidget(main_widget);
|
setWidget(main_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsBreakPointsWidget::OnPicaBreakPointHit(Event event, void* data)
|
void GraphicsBreakPointsWidget::OnPicaBreakPointHit(Event event, void* data) {
|
||||||
{
|
|
||||||
// Process in GUI thread
|
// Process in GUI thread
|
||||||
emit BreakPointHit(event, data);
|
emit BreakPointHit(event, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsBreakPointsWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data)
|
void GraphicsBreakPointsWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) {
|
||||||
{
|
|
||||||
status_text->setText(tr("Emulation halted at breakpoint"));
|
status_text->setText(tr("Emulation halted at breakpoint"));
|
||||||
resume_button->setEnabled(true);
|
resume_button->setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsBreakPointsWidget::OnPicaResume()
|
void GraphicsBreakPointsWidget::OnPicaResume() {
|
||||||
{
|
|
||||||
// Process in GUI thread
|
// Process in GUI thread
|
||||||
emit Resumed();
|
emit Resumed();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsBreakPointsWidget::OnResumed()
|
void GraphicsBreakPointsWidget::OnResumed() {
|
||||||
{
|
|
||||||
status_text->setText(tr("Emulation running"));
|
status_text->setText(tr("Emulation running"));
|
||||||
resume_button->setEnabled(false);
|
resume_button->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsBreakPointsWidget::OnResumeRequested()
|
void GraphicsBreakPointsWidget::OnResumeRequested() {
|
||||||
{
|
|
||||||
if (auto context = context_weak.lock())
|
if (auto context = context_weak.lock())
|
||||||
context->Resume();
|
context->Resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsBreakPointsWidget::OnItemDoubleClicked(const QModelIndex& index)
|
void GraphicsBreakPointsWidget::OnItemDoubleClicked(const QModelIndex& index) {
|
||||||
{
|
|
||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ public:
|
||||||
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
int rowCount(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 data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||||
|
|
||||||
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
|
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,9 @@
|
||||||
|
|
||||||
#include "common/vector_math.h"
|
#include "common/vector_math.h"
|
||||||
|
|
||||||
|
#include "video_core/debug_utils/debug_utils.h"
|
||||||
#include "video_core/pica.h"
|
#include "video_core/pica.h"
|
||||||
#include "video_core/pica_state.h"
|
#include "video_core/pica_state.h"
|
||||||
#include "video_core/debug_utils/debug_utils.h"
|
|
||||||
|
|
||||||
QImage LoadTexture(u8* src, const Pica::DebugUtils::TextureInfo& info) {
|
QImage LoadTexture(u8* src, const Pica::DebugUtils::TextureInfo& info) {
|
||||||
QImage decoded_image(info.width, info.height, QImage::Format_ARGB32);
|
QImage decoded_image(info.width, info.height, QImage::Format_ARGB32);
|
||||||
|
@ -38,7 +38,8 @@ QImage LoadTexture(u8* src, const Pica::DebugUtils::TextureInfo& info) {
|
||||||
|
|
||||||
class TextureInfoWidget : public QWidget {
|
class TextureInfoWidget : public QWidget {
|
||||||
public:
|
public:
|
||||||
TextureInfoWidget(u8* src, const Pica::DebugUtils::TextureInfo& info, QWidget* parent = nullptr) : QWidget(parent) {
|
TextureInfoWidget(u8* src, const Pica::DebugUtils::TextureInfo& info, QWidget* parent = nullptr)
|
||||||
|
: QWidget(parent) {
|
||||||
QLabel* image_widget = new QLabel;
|
QLabel* image_widget = new QLabel;
|
||||||
QPixmap image_pixmap = QPixmap::fromImage(LoadTexture(src, info));
|
QPixmap image_pixmap = QPixmap::fromImage(LoadTexture(src, info));
|
||||||
image_pixmap = image_pixmap.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
image_pixmap = image_pixmap.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
|
@ -51,7 +52,6 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) {
|
GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int GPUCommandListModel::rowCount(const QModelIndex& parent) const {
|
int GPUCommandListModel::rowCount(const QModelIndex& parent) const {
|
||||||
|
@ -70,7 +70,7 @@ QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const {
|
||||||
|
|
||||||
if (role == Qt::DisplayRole) {
|
if (role == Qt::DisplayRole) {
|
||||||
QString content;
|
QString content;
|
||||||
switch ( index.column() ) {
|
switch (index.column()) {
|
||||||
case 0:
|
case 0:
|
||||||
return QString::fromLatin1(Pica::Regs::GetCommandName(write.cmd_id).c_str());
|
return QString::fromLatin1(Pica::Regs::GetCommandName(write.cmd_id).c_str());
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -88,9 +88,8 @@ QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant GPUCommandListModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
QVariant GPUCommandListModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||||
switch(role) {
|
switch (role) {
|
||||||
case Qt::DisplayRole:
|
case Qt::DisplayRole: {
|
||||||
{
|
|
||||||
switch (section) {
|
switch (section) {
|
||||||
case 0:
|
case 0:
|
||||||
return tr("Command Name");
|
return tr("Command Name");
|
||||||
|
@ -122,9 +121,9 @@ void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace&
|
||||||
cmd_id < PICA_REG_INDEX(reg_name) + sizeof(decltype(Pica::g_state.regs.reg_name)) / 4)
|
cmd_id < PICA_REG_INDEX(reg_name) + sizeof(decltype(Pica::g_state.regs.reg_name)) / 4)
|
||||||
|
|
||||||
void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) {
|
void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) {
|
||||||
const unsigned int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toUInt();
|
const unsigned int command_id =
|
||||||
if (COMMAND_IN_RANGE(command_id, texture0) ||
|
list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toUInt();
|
||||||
COMMAND_IN_RANGE(command_id, texture1) ||
|
if (COMMAND_IN_RANGE(command_id, texture0) || COMMAND_IN_RANGE(command_id, texture1) ||
|
||||||
COMMAND_IN_RANGE(command_id, texture2)) {
|
COMMAND_IN_RANGE(command_id, texture2)) {
|
||||||
|
|
||||||
unsigned index;
|
unsigned index;
|
||||||
|
@ -148,9 +147,9 @@ void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) {
|
||||||
void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) {
|
void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) {
|
||||||
QWidget* new_info_widget = nullptr;
|
QWidget* new_info_widget = nullptr;
|
||||||
|
|
||||||
const unsigned int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toUInt();
|
const unsigned int command_id =
|
||||||
if (COMMAND_IN_RANGE(command_id, texture0) ||
|
list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toUInt();
|
||||||
COMMAND_IN_RANGE(command_id, texture1) ||
|
if (COMMAND_IN_RANGE(command_id, texture0) || COMMAND_IN_RANGE(command_id, texture1) ||
|
||||||
COMMAND_IN_RANGE(command_id, texture2)) {
|
COMMAND_IN_RANGE(command_id, texture2)) {
|
||||||
|
|
||||||
unsigned index;
|
unsigned index;
|
||||||
|
@ -179,7 +178,8 @@ void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) {
|
||||||
}
|
}
|
||||||
#undef COMMAND_IN_RANGE
|
#undef COMMAND_IN_RANGE
|
||||||
|
|
||||||
GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pica Command List"), parent) {
|
GPUCommandListWidget::GPUCommandListWidget(QWidget* parent)
|
||||||
|
: QDockWidget(tr("Pica Command List"), parent) {
|
||||||
setObjectName("Pica Command List");
|
setObjectName("Pica Command List");
|
||||||
GPUCommandListModel* model = new GPUCommandListModel(this);
|
GPUCommandListModel* model = new GPUCommandListModel(this);
|
||||||
|
|
||||||
|
@ -191,23 +191,24 @@ GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pi
|
||||||
list_widget->setRootIsDecorated(false);
|
list_widget->setRootIsDecorated(false);
|
||||||
list_widget->setUniformRowHeights(true);
|
list_widget->setUniformRowHeights(true);
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
|
||||||
list_widget->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
list_widget->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||||
#else
|
#else
|
||||||
list_widget->header()->setResizeMode(QHeaderView::ResizeToContents);
|
list_widget->header()->setResizeMode(QHeaderView::ResizeToContents);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
connect(list_widget->selectionModel(), SIGNAL(currentChanged(const QModelIndex&,const QModelIndex&)),
|
connect(list_widget->selectionModel(),
|
||||||
this, SLOT(SetCommandInfo(const QModelIndex&)));
|
SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this,
|
||||||
connect(list_widget, SIGNAL(doubleClicked(const QModelIndex&)),
|
SLOT(SetCommandInfo(const QModelIndex&)));
|
||||||
this, SLOT(OnCommandDoubleClicked(const QModelIndex&)));
|
connect(list_widget, SIGNAL(doubleClicked(const QModelIndex&)), this,
|
||||||
|
SLOT(OnCommandDoubleClicked(const QModelIndex&)));
|
||||||
|
|
||||||
toggle_tracing = new QPushButton(tr("Start Tracing"));
|
toggle_tracing = new QPushButton(tr("Start Tracing"));
|
||||||
QPushButton* copy_all = new QPushButton(tr("Copy All"));
|
QPushButton* copy_all = new QPushButton(tr("Copy All"));
|
||||||
|
|
||||||
connect(toggle_tracing, SIGNAL(clicked()), this, SLOT(OnToggleTracing()));
|
connect(toggle_tracing, SIGNAL(clicked()), this, SLOT(OnToggleTracing()));
|
||||||
connect(this, SIGNAL(TracingFinished(const Pica::DebugUtils::PicaTrace&)),
|
connect(this, SIGNAL(TracingFinished(const Pica::DebugUtils::PicaTrace&)), model,
|
||||||
model, SLOT(OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace&)));
|
SLOT(OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace&)));
|
||||||
|
|
||||||
connect(copy_all, SIGNAL(clicked()), this, SLOT(CopyAllToClipboard()));
|
connect(copy_all, SIGNAL(clicked()), this, SLOT(CopyAllToClipboard()));
|
||||||
|
|
||||||
|
|
|
@ -7,14 +7,13 @@
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QDockWidget>
|
#include <QDockWidget>
|
||||||
|
|
||||||
#include "video_core/gpu_debugger.h"
|
|
||||||
#include "video_core/debug_utils/debug_utils.h"
|
#include "video_core/debug_utils/debug_utils.h"
|
||||||
|
#include "video_core/gpu_debugger.h"
|
||||||
|
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
class QTreeView;
|
class QTreeView;
|
||||||
|
|
||||||
class GPUCommandListModel : public QAbstractListModel
|
class GPUCommandListModel : public QAbstractListModel {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -27,7 +26,8 @@ public:
|
||||||
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
int rowCount(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 data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
QVariant headerData(int section, Qt::Orientation orientation,
|
||||||
|
int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace);
|
void OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace);
|
||||||
|
@ -36,8 +36,7 @@ private:
|
||||||
Pica::DebugUtils::PicaTrace pica_trace;
|
Pica::DebugUtils::PicaTrace pica_trace;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GPUCommandListWidget : public QDockWidget
|
class GPUCommandListWidget : public QDockWidget {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -17,18 +17,20 @@
|
||||||
|
|
||||||
#include "common/color.h"
|
#include "common/color.h"
|
||||||
|
|
||||||
#include "core/memory.h"
|
|
||||||
#include "core/hw/gpu.h"
|
#include "core/hw/gpu.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
|
||||||
#include "video_core/pica.h"
|
#include "video_core/pica.h"
|
||||||
#include "video_core/pica_state.h"
|
#include "video_core/pica_state.h"
|
||||||
#include "video_core/utils.h"
|
#include "video_core/utils.h"
|
||||||
|
|
||||||
SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) : QLabel(parent), surface_widget(surface_widget_) {}
|
SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_)
|
||||||
SurfacePicture::~SurfacePicture() {}
|
: QLabel(parent), surface_widget(surface_widget_) {
|
||||||
|
}
|
||||||
|
SurfacePicture::~SurfacePicture() {
|
||||||
|
}
|
||||||
|
|
||||||
void SurfacePicture::mousePressEvent(QMouseEvent* event)
|
void SurfacePicture::mousePressEvent(QMouseEvent* event) {
|
||||||
{
|
|
||||||
// Only do something while the left mouse button is held down
|
// Only do something while the left mouse button is held down
|
||||||
if (!(event->buttons() & Qt::LeftButton))
|
if (!(event->buttons() & Qt::LeftButton))
|
||||||
return;
|
return;
|
||||||
|
@ -41,18 +43,15 @@ void SurfacePicture::mousePressEvent(QMouseEvent* event)
|
||||||
event->y() * pixmap()->height() / height());
|
event->y() * pixmap()->height() / height());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SurfacePicture::mouseMoveEvent(QMouseEvent* event)
|
void SurfacePicture::mouseMoveEvent(QMouseEvent* event) {
|
||||||
{
|
|
||||||
// We also want to handle the event if the user moves the mouse while holding down the LMB
|
// We also want to handle the event if the user moves the mouse while holding down the LMB
|
||||||
mousePressEvent(event);
|
mousePressEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context,
|
GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context,
|
||||||
QWidget* parent)
|
QWidget* parent)
|
||||||
: BreakPointObserverDock(debug_context, tr("Pica Surface Viewer"), parent),
|
: BreakPointObserverDock(debug_context, tr("Pica Surface Viewer"), parent),
|
||||||
surface_source(Source::ColorBuffer)
|
surface_source(Source::ColorBuffer) {
|
||||||
{
|
|
||||||
setObjectName("PicaSurface");
|
setObjectName("PicaSurface");
|
||||||
|
|
||||||
surface_source_list = new QComboBox;
|
surface_source_list = new QComboBox;
|
||||||
|
@ -124,13 +123,20 @@ GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext>
|
||||||
|
|
||||||
// Connections
|
// Connections
|
||||||
connect(this, SIGNAL(Update()), this, SLOT(OnUpdate()));
|
connect(this, SIGNAL(Update()), this, SLOT(OnUpdate()));
|
||||||
connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSurfaceSourceChanged(int)));
|
connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this,
|
||||||
connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnSurfaceAddressChanged(qint64)));
|
SLOT(OnSurfaceSourceChanged(int)));
|
||||||
connect(surface_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfaceWidthChanged(int)));
|
connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this,
|
||||||
connect(surface_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfaceHeightChanged(int)));
|
SLOT(OnSurfaceAddressChanged(qint64)));
|
||||||
connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSurfaceFormatChanged(int)));
|
connect(surface_width_control, SIGNAL(valueChanged(int)), this,
|
||||||
connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfacePickerXChanged(int)));
|
SLOT(OnSurfaceWidthChanged(int)));
|
||||||
connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfacePickerYChanged(int)));
|
connect(surface_height_control, SIGNAL(valueChanged(int)), this,
|
||||||
|
SLOT(OnSurfaceHeightChanged(int)));
|
||||||
|
connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this,
|
||||||
|
SLOT(OnSurfaceFormatChanged(int)));
|
||||||
|
connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this,
|
||||||
|
SLOT(OnSurfacePickerXChanged(int)));
|
||||||
|
connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this,
|
||||||
|
SLOT(OnSurfacePickerYChanged(int)));
|
||||||
connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface()));
|
connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface()));
|
||||||
|
|
||||||
auto main_widget = new QWidget;
|
auto main_widget = new QWidget;
|
||||||
|
@ -203,25 +209,21 @@ GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSurfaceWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data)
|
void GraphicsSurfaceWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) {
|
||||||
{
|
|
||||||
emit Update();
|
emit Update();
|
||||||
widget()->setEnabled(true);
|
widget()->setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSurfaceWidget::OnResumed()
|
void GraphicsSurfaceWidget::OnResumed() {
|
||||||
{
|
|
||||||
widget()->setEnabled(false);
|
widget()->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value)
|
void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) {
|
||||||
{
|
|
||||||
surface_source = static_cast<Source>(new_value);
|
surface_source = static_cast<Source>(new_value);
|
||||||
emit Update();
|
emit Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value)
|
void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) {
|
||||||
{
|
|
||||||
if (surface_address != new_value) {
|
if (surface_address != new_value) {
|
||||||
surface_address = static_cast<unsigned>(new_value);
|
surface_address = static_cast<unsigned>(new_value);
|
||||||
|
|
||||||
|
@ -230,8 +232,7 @@ void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value)
|
void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) {
|
||||||
{
|
|
||||||
if (surface_width != static_cast<unsigned>(new_value)) {
|
if (surface_width != static_cast<unsigned>(new_value)) {
|
||||||
surface_width = static_cast<unsigned>(new_value);
|
surface_width = static_cast<unsigned>(new_value);
|
||||||
|
|
||||||
|
@ -240,8 +241,7 @@ void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value)
|
void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) {
|
||||||
{
|
|
||||||
if (surface_height != static_cast<unsigned>(new_value)) {
|
if (surface_height != static_cast<unsigned>(new_value)) {
|
||||||
surface_height = static_cast<unsigned>(new_value);
|
surface_height = static_cast<unsigned>(new_value);
|
||||||
|
|
||||||
|
@ -250,8 +250,7 @@ void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value)
|
void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) {
|
||||||
{
|
|
||||||
if (surface_format != static_cast<Format>(new_value)) {
|
if (surface_format != static_cast<Format>(new_value)) {
|
||||||
surface_format = static_cast<Format>(new_value);
|
surface_format = static_cast<Format>(new_value);
|
||||||
|
|
||||||
|
@ -260,24 +259,21 @@ void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value)
|
void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) {
|
||||||
{
|
|
||||||
if (surface_picker_x != new_value) {
|
if (surface_picker_x != new_value) {
|
||||||
surface_picker_x = new_value;
|
surface_picker_x = new_value;
|
||||||
Pick(surface_picker_x, surface_picker_y);
|
Pick(surface_picker_x, surface_picker_y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value)
|
void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) {
|
||||||
{
|
|
||||||
if (surface_picker_y != new_value) {
|
if (surface_picker_y != new_value) {
|
||||||
surface_picker_y = new_value;
|
surface_picker_y = new_value;
|
||||||
Pick(surface_picker_x, surface_picker_y);
|
Pick(surface_picker_x, surface_picker_y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSurfaceWidget::Pick(int x, int y)
|
void GraphicsSurfaceWidget::Pick(int x, int y) {
|
||||||
{
|
|
||||||
surface_picker_x_control->setValue(x);
|
surface_picker_x_control->setValue(x);
|
||||||
surface_picker_y_control->setValue(y);
|
surface_picker_y_control->setValue(y);
|
||||||
|
|
||||||
|
@ -312,8 +308,7 @@ void GraphicsSurfaceWidget::Pick(int x, int y)
|
||||||
|
|
||||||
auto GetText = [offset](Format format, const u8* pixel) {
|
auto GetText = [offset](Format format, const u8* pixel) {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case Format::RGBA8:
|
case Format::RGBA8: {
|
||||||
{
|
|
||||||
auto value = Color::DecodeRGBA8(pixel) / 255.0f;
|
auto value = Color::DecodeRGBA8(pixel) / 255.0f;
|
||||||
return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
||||||
.arg(QString::number(value.r(), 'f', 2))
|
.arg(QString::number(value.r(), 'f', 2))
|
||||||
|
@ -321,16 +316,14 @@ void GraphicsSurfaceWidget::Pick(int x, int y)
|
||||||
.arg(QString::number(value.b(), 'f', 2))
|
.arg(QString::number(value.b(), 'f', 2))
|
||||||
.arg(QString::number(value.a(), 'f', 2));
|
.arg(QString::number(value.a(), 'f', 2));
|
||||||
}
|
}
|
||||||
case Format::RGB8:
|
case Format::RGB8: {
|
||||||
{
|
|
||||||
auto value = Color::DecodeRGB8(pixel) / 255.0f;
|
auto value = Color::DecodeRGB8(pixel) / 255.0f;
|
||||||
return QString("Red: %1, Green: %2, Blue: %3")
|
return QString("Red: %1, Green: %2, Blue: %3")
|
||||||
.arg(QString::number(value.r(), 'f', 2))
|
.arg(QString::number(value.r(), 'f', 2))
|
||||||
.arg(QString::number(value.g(), 'f', 2))
|
.arg(QString::number(value.g(), 'f', 2))
|
||||||
.arg(QString::number(value.b(), 'f', 2));
|
.arg(QString::number(value.b(), 'f', 2));
|
||||||
}
|
}
|
||||||
case Format::RGB5A1:
|
case Format::RGB5A1: {
|
||||||
{
|
|
||||||
auto value = Color::DecodeRGB5A1(pixel) / 255.0f;
|
auto value = Color::DecodeRGB5A1(pixel) / 255.0f;
|
||||||
return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
||||||
.arg(QString::number(value.r(), 'f', 2))
|
.arg(QString::number(value.r(), 'f', 2))
|
||||||
|
@ -338,16 +331,14 @@ void GraphicsSurfaceWidget::Pick(int x, int y)
|
||||||
.arg(QString::number(value.b(), 'f', 2))
|
.arg(QString::number(value.b(), 'f', 2))
|
||||||
.arg(QString::number(value.a(), 'f', 2));
|
.arg(QString::number(value.a(), 'f', 2));
|
||||||
}
|
}
|
||||||
case Format::RGB565:
|
case Format::RGB565: {
|
||||||
{
|
|
||||||
auto value = Color::DecodeRGB565(pixel) / 255.0f;
|
auto value = Color::DecodeRGB565(pixel) / 255.0f;
|
||||||
return QString("Red: %1, Green: %2, Blue: %3")
|
return QString("Red: %1, Green: %2, Blue: %3")
|
||||||
.arg(QString::number(value.r(), 'f', 2))
|
.arg(QString::number(value.r(), 'f', 2))
|
||||||
.arg(QString::number(value.g(), 'f', 2))
|
.arg(QString::number(value.g(), 'f', 2))
|
||||||
.arg(QString::number(value.b(), 'f', 2));
|
.arg(QString::number(value.b(), 'f', 2));
|
||||||
}
|
}
|
||||||
case Format::RGBA4:
|
case Format::RGBA4: {
|
||||||
{
|
|
||||||
auto value = Color::DecodeRGBA4(pixel) / 255.0f;
|
auto value = Color::DecodeRGBA4(pixel) / 255.0f;
|
||||||
return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
||||||
.arg(QString::number(value.r(), 'f', 2))
|
.arg(QString::number(value.r(), 'f', 2))
|
||||||
|
@ -356,9 +347,7 @@ void GraphicsSurfaceWidget::Pick(int x, int y)
|
||||||
.arg(QString::number(value.a(), 'f', 2));
|
.arg(QString::number(value.a(), 'f', 2));
|
||||||
}
|
}
|
||||||
case Format::IA8:
|
case Format::IA8:
|
||||||
return QString("Index: %1, Alpha: %2")
|
return QString("Index: %1, Alpha: %2").arg(pixel[0]).arg(pixel[1]);
|
||||||
.arg(pixel[0])
|
|
||||||
.arg(pixel[1]);
|
|
||||||
case Format::RG8: {
|
case Format::RG8: {
|
||||||
auto value = Color::DecodeRG8(pixel) / 255.0f;
|
auto value = Color::DecodeRG8(pixel) / 255.0f;
|
||||||
return QString("Red: %1, Green: %2")
|
return QString("Red: %1, Green: %2")
|
||||||
|
@ -370,16 +359,12 @@ void GraphicsSurfaceWidget::Pick(int x, int y)
|
||||||
case Format::A8:
|
case Format::A8:
|
||||||
return QString("Alpha: %1").arg(QString::number(*pixel / 255.0f, 'f', 2));
|
return QString("Alpha: %1").arg(QString::number(*pixel / 255.0f, 'f', 2));
|
||||||
case Format::IA4:
|
case Format::IA4:
|
||||||
return QString("Index: %1, Alpha: %2")
|
return QString("Index: %1, Alpha: %2").arg(*pixel & 0xF).arg((*pixel & 0xF0) >> 4);
|
||||||
.arg(*pixel & 0xF)
|
case Format::I4: {
|
||||||
.arg((*pixel & 0xF0) >> 4);
|
|
||||||
case Format::I4:
|
|
||||||
{
|
|
||||||
u8 i = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
|
u8 i = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
|
||||||
return QString("Index: %1").arg(i);
|
return QString("Index: %1").arg(i);
|
||||||
}
|
}
|
||||||
case Format::A4:
|
case Format::A4: {
|
||||||
{
|
|
||||||
u8 a = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
|
u8 a = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
|
||||||
return QString("Alpha: %1").arg(QString::number(a / 15.0f, 'f', 2));
|
return QString("Alpha: %1").arg(QString::number(a / 15.0f, 'f', 2));
|
||||||
}
|
}
|
||||||
|
@ -387,21 +372,20 @@ void GraphicsSurfaceWidget::Pick(int x, int y)
|
||||||
case Format::ETC1A4:
|
case Format::ETC1A4:
|
||||||
// TODO: Display block information or channel values?
|
// TODO: Display block information or channel values?
|
||||||
return QString("Compressed data");
|
return QString("Compressed data");
|
||||||
case Format::D16:
|
case Format::D16: {
|
||||||
{
|
|
||||||
auto value = Color::DecodeD16(pixel);
|
auto value = Color::DecodeD16(pixel);
|
||||||
return QString("Depth: %1").arg(QString::number(value / (float)0xFFFF, 'f', 4));
|
return QString("Depth: %1").arg(QString::number(value / (float)0xFFFF, 'f', 4));
|
||||||
}
|
}
|
||||||
case Format::D24:
|
case Format::D24: {
|
||||||
{
|
|
||||||
auto value = Color::DecodeD24(pixel);
|
auto value = Color::DecodeD24(pixel);
|
||||||
return QString("Depth: %1").arg(QString::number(value / (float)0xFFFFFF, 'f', 4));
|
return QString("Depth: %1").arg(QString::number(value / (float)0xFFFFFF, 'f', 4));
|
||||||
}
|
}
|
||||||
case Format::D24X8:
|
case Format::D24X8:
|
||||||
case Format::X24S8:
|
case Format::X24S8: {
|
||||||
{
|
|
||||||
auto values = Color::DecodeD24S8(pixel);
|
auto values = Color::DecodeD24S8(pixel);
|
||||||
return QString("Depth: %1, Stencil: %2").arg(QString::number(values[0] / (float)0xFFFFFF, 'f', 4)).arg(values[1]);
|
return QString("Depth: %1, Stencil: %2")
|
||||||
|
.arg(QString::number(values[0] / (float)0xFFFFFF, 'f', 4))
|
||||||
|
.arg(values[1]);
|
||||||
}
|
}
|
||||||
case Format::Unknown:
|
case Format::Unknown:
|
||||||
return QString("Unknown format");
|
return QString("Unknown format");
|
||||||
|
@ -422,18 +406,18 @@ void GraphicsSurfaceWidget::Pick(int x, int y)
|
||||||
nibbles.append(QString::number(nibble, 16).toUpper());
|
nibbles.append(QString::number(nibble, 16).toUpper());
|
||||||
}
|
}
|
||||||
|
|
||||||
surface_info_label->setText(QString("Raw: 0x%3\n(%4)").arg(nibbles).arg(GetText(surface_format, pixel)));
|
surface_info_label->setText(
|
||||||
|
QString("Raw: 0x%3\n(%4)").arg(nibbles).arg(GetText(surface_format, pixel)));
|
||||||
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSurfaceWidget::OnUpdate()
|
void GraphicsSurfaceWidget::OnUpdate() {
|
||||||
{
|
|
||||||
QPixmap pixmap;
|
QPixmap pixmap;
|
||||||
|
|
||||||
switch (surface_source) {
|
switch (surface_source) {
|
||||||
case Source::ColorBuffer:
|
case Source::ColorBuffer: {
|
||||||
{
|
// TODO: Store a reference to the registers in the debug context instead of accessing them
|
||||||
// TODO: Store a reference to the registers in the debug context instead of accessing them directly...
|
// directly...
|
||||||
|
|
||||||
const auto& framebuffer = Pica::g_state.regs.framebuffer;
|
const auto& framebuffer = Pica::g_state.regs.framebuffer;
|
||||||
|
|
||||||
|
@ -470,8 +454,7 @@ void GraphicsSurfaceWidget::OnUpdate()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Source::DepthBuffer:
|
case Source::DepthBuffer: {
|
||||||
{
|
|
||||||
const auto& framebuffer = Pica::g_state.regs.framebuffer;
|
const auto& framebuffer = Pica::g_state.regs.framebuffer;
|
||||||
|
|
||||||
surface_address = framebuffer.GetDepthBufferPhysicalAddress();
|
surface_address = framebuffer.GetDepthBufferPhysicalAddress();
|
||||||
|
@ -499,8 +482,7 @@ void GraphicsSurfaceWidget::OnUpdate()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Source::StencilBuffer:
|
case Source::StencilBuffer: {
|
||||||
{
|
|
||||||
const auto& framebuffer = Pica::g_state.regs.framebuffer;
|
const auto& framebuffer = Pica::g_state.regs.framebuffer;
|
||||||
|
|
||||||
surface_address = framebuffer.GetDepthBufferPhysicalAddress();
|
surface_address = framebuffer.GetDepthBufferPhysicalAddress();
|
||||||
|
@ -522,12 +504,14 @@ void GraphicsSurfaceWidget::OnUpdate()
|
||||||
|
|
||||||
case Source::Texture0:
|
case Source::Texture0:
|
||||||
case Source::Texture1:
|
case Source::Texture1:
|
||||||
case Source::Texture2:
|
case Source::Texture2: {
|
||||||
{
|
|
||||||
unsigned texture_index;
|
unsigned texture_index;
|
||||||
if (surface_source == Source::Texture0) texture_index = 0;
|
if (surface_source == Source::Texture0)
|
||||||
else if (surface_source == Source::Texture1) texture_index = 1;
|
texture_index = 0;
|
||||||
else if (surface_source == Source::Texture2) texture_index = 2;
|
else if (surface_source == Source::Texture1)
|
||||||
|
texture_index = 1;
|
||||||
|
else if (surface_source == Source::Texture2)
|
||||||
|
texture_index = 2;
|
||||||
else {
|
else {
|
||||||
qDebug() << "Unknown texture source " << static_cast<int>(surface_source);
|
qDebug() << "Unknown texture source " << static_cast<int>(surface_source);
|
||||||
break;
|
break;
|
||||||
|
@ -547,8 +531,7 @@ void GraphicsSurfaceWidget::OnUpdate()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Source::Custom:
|
case Source::Custom: {
|
||||||
{
|
|
||||||
// Keep user-specified values
|
// Keep user-specified values
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -613,7 +596,8 @@ void GraphicsSurfaceWidget::OnUpdate()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
ASSERT_MSG(nibbles_per_pixel >= 2, "Depth decoder only supports formats with at least one byte per pixel");
|
ASSERT_MSG(nibbles_per_pixel >= 2,
|
||||||
|
"Depth decoder only supports formats with at least one byte per pixel");
|
||||||
unsigned bytes_per_pixel = nibbles_per_pixel / 2;
|
unsigned bytes_per_pixel = nibbles_per_pixel / 2;
|
||||||
|
|
||||||
for (unsigned int y = 0; y < surface_height; ++y) {
|
for (unsigned int y = 0; y < surface_height; ++y) {
|
||||||
|
@ -621,34 +605,30 @@ void GraphicsSurfaceWidget::OnUpdate()
|
||||||
const u32 coarse_y = y & ~7;
|
const u32 coarse_y = y & ~7;
|
||||||
u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
|
u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
|
||||||
const u8* pixel = buffer + offset;
|
const u8* pixel = buffer + offset;
|
||||||
Math::Vec4<u8> color = { 0, 0, 0, 0 };
|
Math::Vec4<u8> color = {0, 0, 0, 0};
|
||||||
|
|
||||||
switch(surface_format) {
|
switch (surface_format) {
|
||||||
case Format::D16:
|
case Format::D16: {
|
||||||
{
|
|
||||||
u32 data = Color::DecodeD16(pixel);
|
u32 data = Color::DecodeD16(pixel);
|
||||||
color.r() = data & 0xFF;
|
color.r() = data & 0xFF;
|
||||||
color.g() = (data >> 8) & 0xFF;
|
color.g() = (data >> 8) & 0xFF;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Format::D24:
|
case Format::D24: {
|
||||||
{
|
|
||||||
u32 data = Color::DecodeD24(pixel);
|
u32 data = Color::DecodeD24(pixel);
|
||||||
color.r() = data & 0xFF;
|
color.r() = data & 0xFF;
|
||||||
color.g() = (data >> 8) & 0xFF;
|
color.g() = (data >> 8) & 0xFF;
|
||||||
color.b() = (data >> 16) & 0xFF;
|
color.b() = (data >> 16) & 0xFF;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Format::D24X8:
|
case Format::D24X8: {
|
||||||
{
|
|
||||||
Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
|
Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
|
||||||
color.r() = data.x & 0xFF;
|
color.r() = data.x & 0xFF;
|
||||||
color.g() = (data.x >> 8) & 0xFF;
|
color.g() = (data.x >> 8) & 0xFF;
|
||||||
color.b() = (data.x >> 16) & 0xFF;
|
color.b() = (data.x >> 16) & 0xFF;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Format::X24S8:
|
case Format::X24S8: {
|
||||||
{
|
|
||||||
Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
|
Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
|
||||||
color.r() = color.g() = color.b() = data.y;
|
color.r() = color.g() = color.b() = data.y;
|
||||||
break;
|
break;
|
||||||
|
@ -661,7 +641,6 @@ void GraphicsSurfaceWidget::OnUpdate()
|
||||||
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255));
|
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pixmap = QPixmap::fromImage(decoded_image);
|
pixmap = QPixmap::fromImage(decoded_image);
|
||||||
|
@ -682,7 +661,9 @@ void GraphicsSurfaceWidget::SaveSurface() {
|
||||||
QString bin_filter = tr("Binary data (*.bin)");
|
QString bin_filter = tr("Binary data (*.bin)");
|
||||||
|
|
||||||
QString selectedFilter;
|
QString selectedFilter;
|
||||||
QString filename = QFileDialog::getSaveFileName(this, tr("Save Surface"), QString("texture-0x%1.png").arg(QString::number(surface_address, 16)),
|
QString filename = QFileDialog::getSaveFileName(
|
||||||
|
this, tr("Save Surface"),
|
||||||
|
QString("texture-0x%1.png").arg(QString::number(surface_address, 16)),
|
||||||
QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter);
|
QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter);
|
||||||
|
|
||||||
if (filename.isEmpty()) {
|
if (filename.isEmpty()) {
|
||||||
|
|
|
@ -15,8 +15,7 @@ class CSpinBox;
|
||||||
|
|
||||||
class GraphicsSurfaceWidget;
|
class GraphicsSurfaceWidget;
|
||||||
|
|
||||||
class SurfacePicture : public QLabel
|
class SurfacePicture : public QLabel {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -29,7 +28,6 @@ protected slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GraphicsSurfaceWidget* surface_widget;
|
GraphicsSurfaceWidget* surface_widget;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class GraphicsSurfaceWidget : public BreakPointObserverDock {
|
class GraphicsSurfaceWidget : public BreakPointObserverDock {
|
||||||
|
@ -74,7 +72,8 @@ class GraphicsSurfaceWidget : public BreakPointObserverDock {
|
||||||
static unsigned int NibblesPerPixel(Format format);
|
static unsigned int NibblesPerPixel(Format format);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr);
|
GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context,
|
||||||
|
QWidget* parent = nullptr);
|
||||||
void Pick(int x, int y);
|
void Pick(int x, int y);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -97,7 +96,6 @@ signals:
|
||||||
void Update();
|
void Update();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
QComboBox* surface_source_list;
|
QComboBox* surface_source_list;
|
||||||
CSpinBox* surface_address_control;
|
CSpinBox* surface_address_control;
|
||||||
QSpinBox* surface_width_control;
|
QSpinBox* surface_width_control;
|
||||||
|
|
|
@ -35,12 +35,16 @@ GraphicsTracingWidget::GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext>
|
||||||
setObjectName("CiTracing");
|
setObjectName("CiTracing");
|
||||||
|
|
||||||
QPushButton* start_recording = new QPushButton(tr("Start Recording"));
|
QPushButton* start_recording = new QPushButton(tr("Start Recording"));
|
||||||
QPushButton* stop_recording = new QPushButton(QIcon::fromTheme("document-save"), tr("Stop and Save"));
|
QPushButton* stop_recording =
|
||||||
|
new QPushButton(QIcon::fromTheme("document-save"), tr("Stop and Save"));
|
||||||
QPushButton* abort_recording = new QPushButton(tr("Abort Recording"));
|
QPushButton* abort_recording = new QPushButton(tr("Abort Recording"));
|
||||||
|
|
||||||
connect(this, SIGNAL(SetStartTracingButtonEnabled(bool)), start_recording, SLOT(setVisible(bool)));
|
connect(this, SIGNAL(SetStartTracingButtonEnabled(bool)), start_recording,
|
||||||
connect(this, SIGNAL(SetStopTracingButtonEnabled(bool)), stop_recording, SLOT(setVisible(bool)));
|
SLOT(setVisible(bool)));
|
||||||
connect(this, SIGNAL(SetAbortTracingButtonEnabled(bool)), abort_recording, SLOT(setVisible(bool)));
|
connect(this, SIGNAL(SetStopTracingButtonEnabled(bool)), stop_recording,
|
||||||
|
SLOT(setVisible(bool)));
|
||||||
|
connect(this, SIGNAL(SetAbortTracingButtonEnabled(bool)), abort_recording,
|
||||||
|
SLOT(setVisible(bool)));
|
||||||
connect(start_recording, SIGNAL(clicked()), this, SLOT(StartRecording()));
|
connect(start_recording, SIGNAL(clicked()), this, SLOT(StartRecording()));
|
||||||
connect(stop_recording, SIGNAL(clicked()), this, SLOT(StopRecording()));
|
connect(stop_recording, SIGNAL(clicked()), this, SLOT(StopRecording()));
|
||||||
connect(abort_recording, SIGNAL(clicked()), this, SLOT(AbortRecording()));
|
connect(abort_recording, SIGNAL(clicked()), this, SLOT(AbortRecording()));
|
||||||
|
@ -74,26 +78,31 @@ void GraphicsTracingWidget::StartRecording() {
|
||||||
std::array<u32, 4 * 16> default_attributes;
|
std::array<u32, 4 * 16> default_attributes;
|
||||||
for (unsigned i = 0; i < 16; ++i) {
|
for (unsigned i = 0; i < 16; ++i) {
|
||||||
for (unsigned comp = 0; comp < 3; ++comp) {
|
for (unsigned comp = 0; comp < 3; ++comp) {
|
||||||
default_attributes[4 * i + comp] = nihstro::to_float24(Pica::g_state.vs_default_attributes[i][comp].ToFloat32());
|
default_attributes[4 * i + comp] =
|
||||||
|
nihstro::to_float24(Pica::g_state.vs_default_attributes[i][comp].ToFloat32());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<u32, 4 * 96> vs_float_uniforms;
|
std::array<u32, 4 * 96> vs_float_uniforms;
|
||||||
for (unsigned i = 0; i < 96; ++i)
|
for (unsigned i = 0; i < 96; ++i)
|
||||||
for (unsigned comp = 0; comp < 3; ++comp)
|
for (unsigned comp = 0; comp < 3; ++comp)
|
||||||
vs_float_uniforms[4 * i + comp] = nihstro::to_float24(Pica::g_state.vs.uniforms.f[i][comp].ToFloat32());
|
vs_float_uniforms[4 * i + comp] =
|
||||||
|
nihstro::to_float24(Pica::g_state.vs.uniforms.f[i][comp].ToFloat32());
|
||||||
|
|
||||||
CiTrace::Recorder::InitialState state;
|
CiTrace::Recorder::InitialState state;
|
||||||
std::copy_n((u32*)&GPU::g_regs, sizeof(GPU::g_regs) / sizeof(u32), std::back_inserter(state.gpu_registers));
|
std::copy_n((u32*)&GPU::g_regs, sizeof(GPU::g_regs) / sizeof(u32),
|
||||||
std::copy_n((u32*)&LCD::g_regs, sizeof(LCD::g_regs) / sizeof(u32), std::back_inserter(state.lcd_registers));
|
std::back_inserter(state.gpu_registers));
|
||||||
std::copy_n((u32*)&Pica::g_state.regs, sizeof(Pica::g_state.regs) / sizeof(u32), std::back_inserter(state.pica_registers));
|
std::copy_n((u32*)&LCD::g_regs, sizeof(LCD::g_regs) / sizeof(u32),
|
||||||
|
std::back_inserter(state.lcd_registers));
|
||||||
|
std::copy_n((u32*)&Pica::g_state.regs, sizeof(Pica::g_state.regs) / sizeof(u32),
|
||||||
|
std::back_inserter(state.pica_registers));
|
||||||
boost::copy(default_attributes, std::back_inserter(state.default_attributes));
|
boost::copy(default_attributes, std::back_inserter(state.default_attributes));
|
||||||
boost::copy(shader_binary, std::back_inserter(state.vs_program_binary));
|
boost::copy(shader_binary, std::back_inserter(state.vs_program_binary));
|
||||||
boost::copy(swizzle_data, std::back_inserter(state.vs_swizzle_data));
|
boost::copy(swizzle_data, std::back_inserter(state.vs_swizzle_data));
|
||||||
boost::copy(vs_float_uniforms, std::back_inserter(state.vs_float_uniforms));
|
boost::copy(vs_float_uniforms, std::back_inserter(state.vs_float_uniforms));
|
||||||
//boost::copy(TODO: Not implemented, std::back_inserter(state.gs_program_binary));
|
// boost::copy(TODO: Not implemented, std::back_inserter(state.gs_program_binary));
|
||||||
//boost::copy(TODO: Not implemented, std::back_inserter(state.gs_swizzle_data));
|
// boost::copy(TODO: Not implemented, std::back_inserter(state.gs_swizzle_data));
|
||||||
//boost::copy(TODO: Not implemented, std::back_inserter(state.gs_float_uniforms));
|
// boost::copy(TODO: Not implemented, std::back_inserter(state.gs_float_uniforms));
|
||||||
|
|
||||||
auto recorder = new CiTrace::Recorder(state);
|
auto recorder = new CiTrace::Recorder(state);
|
||||||
context->recorder = std::shared_ptr<CiTrace::Recorder>(recorder);
|
context->recorder = std::shared_ptr<CiTrace::Recorder>(recorder);
|
||||||
|
@ -156,10 +165,11 @@ void GraphicsTracingWidget::OnEmulationStopping() {
|
||||||
if (!context)
|
if (!context)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
if (context->recorder) {
|
if (context->recorder) {
|
||||||
auto reply = QMessageBox::question(this, tr("CiTracing still active"),
|
auto reply =
|
||||||
tr("A CiTrace is still being recorded. Do you want to save it? If not, all recorded data will be discarded."),
|
QMessageBox::question(this, tr("CiTracing still active"),
|
||||||
|
tr("A CiTrace is still being recorded. Do you want to save it? "
|
||||||
|
"If not, all recorded data will be discarded."),
|
||||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||||
|
|
||||||
if (reply == QMessageBox::Yes) {
|
if (reply == QMessageBox::Yes) {
|
||||||
|
|
|
@ -12,7 +12,8 @@ class GraphicsTracingWidget : public BreakPointObserverDock {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr);
|
GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context,
|
||||||
|
QWidget* parent = nullptr);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void StartRecording();
|
void StartRecording();
|
||||||
|
|
|
@ -28,8 +28,8 @@ using nihstro::Instruction;
|
||||||
using nihstro::SourceRegister;
|
using nihstro::SourceRegister;
|
||||||
using nihstro::SwizzlePattern;
|
using nihstro::SwizzlePattern;
|
||||||
|
|
||||||
GraphicsVertexShaderModel::GraphicsVertexShaderModel(GraphicsVertexShaderWidget* parent): QAbstractTableModel(parent), par(parent) {
|
GraphicsVertexShaderModel::GraphicsVertexShaderModel(GraphicsVertexShaderWidget* parent)
|
||||||
|
: QAbstractTableModel(parent), par(parent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int GraphicsVertexShaderModel::columnCount(const QModelIndex& parent) const {
|
int GraphicsVertexShaderModel::columnCount(const QModelIndex& parent) const {
|
||||||
|
@ -40,10 +40,10 @@ int GraphicsVertexShaderModel::rowCount(const QModelIndex& parent) const {
|
||||||
return static_cast<int>(par->info.code.size());
|
return static_cast<int>(par->info.code.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant GraphicsVertexShaderModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
QVariant GraphicsVertexShaderModel::headerData(int section, Qt::Orientation orientation,
|
||||||
switch(role) {
|
int role) const {
|
||||||
case Qt::DisplayRole:
|
switch (role) {
|
||||||
{
|
case Qt::DisplayRole: {
|
||||||
if (section == 0) {
|
if (section == 0) {
|
||||||
return tr("Offset");
|
return tr("Offset");
|
||||||
} else if (section == 1) {
|
} else if (section == 1) {
|
||||||
|
@ -69,8 +69,8 @@ static std::string SelectorToString(u32 selector) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// e.g. "-c92[a0.x].xyzw"
|
// e.g. "-c92[a0.x].xyzw"
|
||||||
static void print_input(std::ostringstream& output, const SourceRegister& input,
|
static void print_input(std::ostringstream& output, const SourceRegister& input, bool negate,
|
||||||
bool negate, const std::string& swizzle_mask, bool align = true,
|
const std::string& swizzle_mask, bool align = true,
|
||||||
const std::string& address_register_name = std::string()) {
|
const std::string& address_register_name = std::string()) {
|
||||||
if (align)
|
if (align)
|
||||||
output << std::setw(4) << std::right;
|
output << std::setw(4) << std::right;
|
||||||
|
@ -83,20 +83,18 @@ static void print_input(std::ostringstream& output, const SourceRegister& input,
|
||||||
|
|
||||||
QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) const {
|
QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) const {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case Qt::DisplayRole:
|
case Qt::DisplayRole: {
|
||||||
{
|
|
||||||
switch (index.column()) {
|
switch (index.column()) {
|
||||||
case 0:
|
case 0:
|
||||||
if (par->info.HasLabel(index.row()))
|
if (par->info.HasLabel(index.row()))
|
||||||
return QString::fromStdString(par->info.GetLabel(index.row()));
|
return QString::fromStdString(par->info.GetLabel(index.row()));
|
||||||
|
|
||||||
return QString("%1").arg(4*index.row(), 4, 16, QLatin1Char('0'));
|
return QString("%1").arg(4 * index.row(), 4, 16, QLatin1Char('0'));
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
return QString("%1").arg(par->info.code[index.row()].hex, 8, 16, QLatin1Char('0'));
|
return QString("%1").arg(par->info.code[index.row()].hex, 8, 16, QLatin1Char('0'));
|
||||||
|
|
||||||
case 2:
|
case 2: {
|
||||||
{
|
|
||||||
std::ostringstream output;
|
std::ostringstream output;
|
||||||
output.flags(std::ios::uppercase);
|
output.flags(std::ios::uppercase);
|
||||||
|
|
||||||
|
@ -117,8 +115,9 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
|
||||||
const Instruction instr = par->info.code[index.row()];
|
const Instruction instr = par->info.code[index.row()];
|
||||||
const OpCode opcode = instr.opcode;
|
const OpCode opcode = instr.opcode;
|
||||||
const OpCode::Info opcode_info = opcode.GetInfo();
|
const OpCode::Info opcode_info = opcode.GetInfo();
|
||||||
const u32 operand_desc_id = opcode_info.type == OpCode::Type::MultiplyAdd ?
|
const u32 operand_desc_id = opcode_info.type == OpCode::Type::MultiplyAdd
|
||||||
instr.mad.operand_desc_id.Value() : instr.common.operand_desc_id.Value();
|
? instr.mad.operand_desc_id.Value()
|
||||||
|
: instr.common.operand_desc_id.Value();
|
||||||
const SwizzlePattern swizzle = par->info.swizzle_info[operand_desc_id].pattern;
|
const SwizzlePattern swizzle = par->info.swizzle_info[operand_desc_id].pattern;
|
||||||
|
|
||||||
// longest known instruction name: "setemit "
|
// longest known instruction name: "setemit "
|
||||||
|
@ -136,15 +135,14 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OpCode::Type::Arithmetic:
|
case OpCode::Type::Arithmetic:
|
||||||
case OpCode::Type::MultiplyAdd:
|
case OpCode::Type::MultiplyAdd: {
|
||||||
{
|
|
||||||
// Use custom code for special instructions
|
// Use custom code for special instructions
|
||||||
switch (opcode.EffectiveOpCode()) {
|
switch (opcode.EffectiveOpCode()) {
|
||||||
case OpCode::Id::CMP:
|
case OpCode::Id::CMP: {
|
||||||
{
|
|
||||||
AlignToColumn(kOpcodeColumnWidth);
|
AlignToColumn(kOpcodeColumnWidth);
|
||||||
|
|
||||||
// NOTE: CMP always writes both cc components, so we do not consider the dest mask here.
|
// NOTE: CMP always writes both cc components, so we do not consider the dest
|
||||||
|
// mask here.
|
||||||
output << " cc.xy";
|
output << " cc.xy";
|
||||||
AlignToColumn(kOutputColumnWidth);
|
AlignToColumn(kOutputColumnWidth);
|
||||||
|
|
||||||
|
@ -152,22 +150,29 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
|
||||||
SourceRegister src2 = instr.common.GetSrc2(false);
|
SourceRegister src2 = instr.common.GetSrc2(false);
|
||||||
|
|
||||||
output << ' ';
|
output << ' ';
|
||||||
print_input(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false).substr(0,1), false, instr.common.AddressRegisterName());
|
print_input(output, src1, swizzle.negate_src1,
|
||||||
output << ' ' << instr.common.compare_op.ToString(instr.common.compare_op.x) << ' ';
|
swizzle.SelectorToString(false).substr(0, 1), false,
|
||||||
print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(true).substr(0,1), false);
|
instr.common.AddressRegisterName());
|
||||||
|
output << ' ' << instr.common.compare_op.ToString(instr.common.compare_op.x)
|
||||||
|
<< ' ';
|
||||||
|
print_input(output, src2, swizzle.negate_src2,
|
||||||
|
swizzle.SelectorToString(true).substr(0, 1), false);
|
||||||
|
|
||||||
output << ", ";
|
output << ", ";
|
||||||
|
|
||||||
print_input(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false).substr(1,1), false, instr.common.AddressRegisterName());
|
print_input(output, src1, swizzle.negate_src1,
|
||||||
output << ' ' << instr.common.compare_op.ToString(instr.common.compare_op.y) << ' ';
|
swizzle.SelectorToString(false).substr(1, 1), false,
|
||||||
print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(true).substr(1,1), false);
|
instr.common.AddressRegisterName());
|
||||||
|
output << ' ' << instr.common.compare_op.ToString(instr.common.compare_op.y)
|
||||||
|
<< ' ';
|
||||||
|
print_input(output, src2, swizzle.negate_src2,
|
||||||
|
swizzle.SelectorToString(true).substr(1, 1), false);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case OpCode::Id::MAD:
|
case OpCode::Id::MAD:
|
||||||
case OpCode::Id::MADI:
|
case OpCode::Id::MADI: {
|
||||||
{
|
|
||||||
AlignToColumn(kOpcodeColumnWidth);
|
AlignToColumn(kOpcodeColumnWidth);
|
||||||
|
|
||||||
bool src_is_inverted = 0 != (opcode_info.subtype & OpCode::Info::SrcInversed);
|
bool src_is_inverted = 0 != (opcode_info.subtype & OpCode::Info::SrcInversed);
|
||||||
|
@ -175,34 +180,42 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
|
||||||
SourceRegister src2 = instr.mad.GetSrc2(src_is_inverted);
|
SourceRegister src2 = instr.mad.GetSrc2(src_is_inverted);
|
||||||
SourceRegister src3 = instr.mad.GetSrc3(src_is_inverted);
|
SourceRegister src3 = instr.mad.GetSrc3(src_is_inverted);
|
||||||
|
|
||||||
output << std::setw(3) << std::right << instr.mad.dest.Value().GetName() << '.' << swizzle.DestMaskToString();
|
output << std::setw(3) << std::right << instr.mad.dest.Value().GetName() << '.'
|
||||||
|
<< swizzle.DestMaskToString();
|
||||||
AlignToColumn(kOutputColumnWidth);
|
AlignToColumn(kOutputColumnWidth);
|
||||||
print_input(output, src1, swizzle.negate_src1, SelectorToString(swizzle.src1_selector));
|
print_input(output, src1, swizzle.negate_src1,
|
||||||
|
SelectorToString(swizzle.src1_selector));
|
||||||
AlignToColumn(kInputOperandColumnWidth);
|
AlignToColumn(kInputOperandColumnWidth);
|
||||||
if (src_is_inverted) {
|
if (src_is_inverted) {
|
||||||
print_input(output, src2, swizzle.negate_src2, SelectorToString(swizzle.src2_selector));
|
print_input(output, src2, swizzle.negate_src2,
|
||||||
|
SelectorToString(swizzle.src2_selector));
|
||||||
} else {
|
} else {
|
||||||
print_input(output, src2, swizzle.negate_src2, SelectorToString(swizzle.src2_selector), true, instr.mad.AddressRegisterName());
|
print_input(output, src2, swizzle.negate_src2,
|
||||||
|
SelectorToString(swizzle.src2_selector), true,
|
||||||
|
instr.mad.AddressRegisterName());
|
||||||
}
|
}
|
||||||
AlignToColumn(kInputOperandColumnWidth);
|
AlignToColumn(kInputOperandColumnWidth);
|
||||||
if (src_is_inverted) {
|
if (src_is_inverted) {
|
||||||
print_input(output, src3, swizzle.negate_src3, SelectorToString(swizzle.src3_selector), true, instr.mad.AddressRegisterName());
|
print_input(output, src3, swizzle.negate_src3,
|
||||||
|
SelectorToString(swizzle.src3_selector), true,
|
||||||
|
instr.mad.AddressRegisterName());
|
||||||
} else {
|
} else {
|
||||||
print_input(output, src3, swizzle.negate_src3, SelectorToString(swizzle.src3_selector));
|
print_input(output, src3, swizzle.negate_src3,
|
||||||
|
SelectorToString(swizzle.src3_selector));
|
||||||
}
|
}
|
||||||
AlignToColumn(kInputOperandColumnWidth);
|
AlignToColumn(kInputOperandColumnWidth);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default: {
|
||||||
{
|
|
||||||
AlignToColumn(kOpcodeColumnWidth);
|
AlignToColumn(kOpcodeColumnWidth);
|
||||||
|
|
||||||
bool src_is_inverted = 0 != (opcode_info.subtype & OpCode::Info::SrcInversed);
|
bool src_is_inverted = 0 != (opcode_info.subtype & OpCode::Info::SrcInversed);
|
||||||
|
|
||||||
if (opcode_info.subtype & OpCode::Info::Dest) {
|
if (opcode_info.subtype & OpCode::Info::Dest) {
|
||||||
// e.g. "r12.xy__"
|
// e.g. "r12.xy__"
|
||||||
output << std::setw(3) << std::right << instr.common.dest.Value().GetName() << '.' << swizzle.DestMaskToString();
|
output << std::setw(3) << std::right << instr.common.dest.Value().GetName()
|
||||||
|
<< '.' << swizzle.DestMaskToString();
|
||||||
} else if (opcode_info.subtype == OpCode::Info::MOVA) {
|
} else if (opcode_info.subtype == OpCode::Info::MOVA) {
|
||||||
output << " a0." << swizzle.DestMaskToString();
|
output << " a0." << swizzle.DestMaskToString();
|
||||||
}
|
}
|
||||||
|
@ -210,14 +223,18 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
|
||||||
|
|
||||||
if (opcode_info.subtype & OpCode::Info::Src1) {
|
if (opcode_info.subtype & OpCode::Info::Src1) {
|
||||||
SourceRegister src1 = instr.common.GetSrc1(src_is_inverted);
|
SourceRegister src1 = instr.common.GetSrc1(src_is_inverted);
|
||||||
print_input(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false), true, instr.common.AddressRegisterName());
|
print_input(output, src1, swizzle.negate_src1,
|
||||||
|
swizzle.SelectorToString(false), true,
|
||||||
|
instr.common.AddressRegisterName());
|
||||||
AlignToColumn(kInputOperandColumnWidth);
|
AlignToColumn(kInputOperandColumnWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: In some cases, the Address Register is used as an index for SRC2 instead of SRC1
|
// TODO: In some cases, the Address Register is used as an index for SRC2
|
||||||
|
// instead of SRC1
|
||||||
if (opcode_info.subtype & OpCode::Info::Src2) {
|
if (opcode_info.subtype & OpCode::Info::Src2) {
|
||||||
SourceRegister src2 = instr.common.GetSrc2(src_is_inverted);
|
SourceRegister src2 = instr.common.GetSrc2(src_is_inverted);
|
||||||
print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(true));
|
print_input(output, src2, swizzle.negate_src2,
|
||||||
|
swizzle.SelectorToString(true));
|
||||||
AlignToColumn(kInputOperandColumnWidth);
|
AlignToColumn(kInputOperandColumnWidth);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -228,8 +245,7 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
|
||||||
}
|
}
|
||||||
|
|
||||||
case OpCode::Type::Conditional:
|
case OpCode::Type::Conditional:
|
||||||
case OpCode::Type::UniformFlowControl:
|
case OpCode::Type::UniformFlowControl: {
|
||||||
{
|
|
||||||
output << ' ';
|
output << ' ';
|
||||||
|
|
||||||
switch (opcode.EffectiveOpCode()) {
|
switch (opcode.EffectiveOpCode()) {
|
||||||
|
@ -242,7 +258,8 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
|
||||||
output << '(';
|
output << '(';
|
||||||
|
|
||||||
if (instr.flow_control.op != instr.flow_control.JustY) {
|
if (instr.flow_control.op != instr.flow_control.JustY) {
|
||||||
if (instr.flow_control.refx) output << '!';
|
if (instr.flow_control.refx)
|
||||||
|
output << '!';
|
||||||
output << "cc.x";
|
output << "cc.x";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +270,8 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instr.flow_control.op != instr.flow_control.JustX) {
|
if (instr.flow_control.op != instr.flow_control.JustX) {
|
||||||
if (instr.flow_control.refy) output << '!';
|
if (instr.flow_control.refy)
|
||||||
|
output << '!';
|
||||||
output << "cc.y";
|
output << "cc.y";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,17 +284,23 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
|
||||||
u32 target_addr_else = instr.flow_control.dest_offset;
|
u32 target_addr_else = instr.flow_control.dest_offset;
|
||||||
|
|
||||||
if (opcode_info.subtype & OpCode::Info::HasAlternative) {
|
if (opcode_info.subtype & OpCode::Info::HasAlternative) {
|
||||||
output << "else jump to 0x" << std::setw(4) << std::right << std::setfill('0') << std::hex << (4 * instr.flow_control.dest_offset);
|
output << "else jump to 0x" << std::setw(4) << std::right
|
||||||
|
<< std::setfill('0') << std::hex
|
||||||
|
<< (4 * instr.flow_control.dest_offset);
|
||||||
} else if (opcode_info.subtype & OpCode::Info::HasExplicitDest) {
|
} else if (opcode_info.subtype & OpCode::Info::HasExplicitDest) {
|
||||||
output << "jump to 0x" << std::setw(4) << std::right << std::setfill('0') << std::hex << (4 * instr.flow_control.dest_offset);
|
output << "jump to 0x" << std::setw(4) << std::right << std::setfill('0')
|
||||||
|
<< std::hex << (4 * instr.flow_control.dest_offset);
|
||||||
} else {
|
} else {
|
||||||
// TODO: Handle other cases
|
// TODO: Handle other cases
|
||||||
output << "(unknown destination)";
|
output << "(unknown destination)";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opcode_info.subtype & OpCode::Info::HasFinishPoint) {
|
if (opcode_info.subtype & OpCode::Info::HasFinishPoint) {
|
||||||
output << " (return on 0x" << std::setw(4) << std::right << std::setfill('0') << std::hex
|
output << " (return on 0x" << std::setw(4) << std::right
|
||||||
<< (4 * instr.flow_control.dest_offset + 4 * instr.flow_control.num_instructions) << ')';
|
<< std::setfill('0') << std::hex
|
||||||
|
<< (4 * instr.flow_control.dest_offset +
|
||||||
|
4 * instr.flow_control.num_instructions)
|
||||||
|
<< ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -300,8 +324,7 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
|
||||||
case Qt::FontRole:
|
case Qt::FontRole:
|
||||||
return GetMonospaceFont();
|
return GetMonospaceFont();
|
||||||
|
|
||||||
case Qt::BackgroundRole:
|
case Qt::BackgroundRole: {
|
||||||
{
|
|
||||||
// Highlight current instruction
|
// Highlight current instruction
|
||||||
int current_record_index = par->cycle_index->value();
|
int current_record_index = par->cycle_index->value();
|
||||||
if (current_record_index < static_cast<int>(par->debug_data.records.size())) {
|
if (current_record_index < static_cast<int>(par->debug_data.records.size())) {
|
||||||
|
@ -319,10 +342,8 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
|
||||||
return QBrush(QColor(192, 192, 192));
|
return QBrush(QColor(192, 192, 192));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: Draw arrows for each "reachable" instruction to visualize control flow
|
// TODO: Draw arrows for each "reachable" instruction to visualize control flow
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -331,8 +352,8 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsVertexShaderWidget::DumpShader() {
|
void GraphicsVertexShaderWidget::DumpShader() {
|
||||||
QString filename = QFileDialog::getSaveFileName(this, tr("Save Shader Dump"), "shader_dump.shbin",
|
QString filename = QFileDialog::getSaveFileName(
|
||||||
tr("Shader Binary (*.shbin)"));
|
this, tr("Save Shader Dump"), "shader_dump.shbin", tr("Shader Binary (*.shbin)"));
|
||||||
|
|
||||||
if (filename.isEmpty()) {
|
if (filename.isEmpty()) {
|
||||||
// If the user canceled the dialog, don't dump anything.
|
// If the user canceled the dialog, don't dump anything.
|
||||||
|
@ -342,11 +363,12 @@ void GraphicsVertexShaderWidget::DumpShader() {
|
||||||
auto& setup = Pica::g_state.vs;
|
auto& setup = Pica::g_state.vs;
|
||||||
auto& config = Pica::g_state.regs.vs;
|
auto& config = Pica::g_state.regs.vs;
|
||||||
|
|
||||||
Pica::DebugUtils::DumpShader(filename.toStdString(), config, setup, Pica::g_state.regs.vs_output_attributes);
|
Pica::DebugUtils::DumpShader(filename.toStdString(), config, setup,
|
||||||
|
Pica::g_state.regs.vs_output_attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::DebugContext > debug_context,
|
GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(
|
||||||
QWidget* parent)
|
std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent)
|
||||||
: BreakPointObserverDock(debug_context, "Pica Vertex Shader", parent) {
|
: BreakPointObserverDock(debug_context, "Pica Vertex Shader", parent) {
|
||||||
setObjectName("PicaVertexShader");
|
setObjectName("PicaVertexShader");
|
||||||
|
|
||||||
|
@ -365,7 +387,8 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De
|
||||||
input_data[i]->setValidator(new QDoubleValidator(input_data[i]));
|
input_data[i]->setValidator(new QDoubleValidator(input_data[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
breakpoint_warning = new QLabel(tr("(data only available at vertex shader invocation breakpoints)"));
|
breakpoint_warning =
|
||||||
|
new QLabel(tr("(data only available at vertex shader invocation breakpoints)"));
|
||||||
|
|
||||||
// TODO: Add some button for jumping to the shader entry point
|
// TODO: Add some button for jumping to the shader entry point
|
||||||
|
|
||||||
|
@ -442,7 +465,8 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De
|
||||||
|
|
||||||
// Set a minimum height so that the size of this label doesn't cause the rest of the bottom
|
// Set a minimum height so that the size of this label doesn't cause the rest of the bottom
|
||||||
// part of the UI to keep jumping up and down when cycling through instructions.
|
// part of the UI to keep jumping up and down when cycling through instructions.
|
||||||
instruction_description->setMinimumHeight(instruction_description->fontMetrics().lineSpacing() * 6);
|
instruction_description->setMinimumHeight(instruction_description->fontMetrics().lineSpacing() *
|
||||||
|
6);
|
||||||
instruction_description->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
instruction_description->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||||
main_layout->addWidget(instruction_description);
|
main_layout->addWidget(instruction_description);
|
||||||
|
|
||||||
|
@ -471,7 +495,8 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d
|
||||||
memcpy(&input_vertex, vertex_data, sizeof(input_vertex));
|
memcpy(&input_vertex, vertex_data, sizeof(input_vertex));
|
||||||
for (unsigned attr = 0; attr < 16; ++attr) {
|
for (unsigned attr = 0; attr < 16; ++attr) {
|
||||||
for (unsigned comp = 0; comp < 4; ++comp) {
|
for (unsigned comp = 0; comp < 4; ++comp) {
|
||||||
input_data[4 * attr + comp]->setText(QString("%1").arg(input_vertex.attr[attr][comp].ToFloat32()));
|
input_data[4 * attr + comp]->setText(
|
||||||
|
QString("%1").arg(input_vertex.attr[attr][comp].ToFloat32()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
breakpoint_warning->hide();
|
breakpoint_warning->hide();
|
||||||
|
@ -498,10 +523,11 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d
|
||||||
info.swizzle_info.push_back({pattern});
|
info.swizzle_info.push_back({pattern});
|
||||||
|
|
||||||
u32 entry_point = Pica::g_state.regs.vs.main_offset;
|
u32 entry_point = Pica::g_state.regs.vs.main_offset;
|
||||||
info.labels.insert({ entry_point, "main" });
|
info.labels.insert({entry_point, "main"});
|
||||||
|
|
||||||
// Generate debug information
|
// Generate debug information
|
||||||
debug_data = Pica::g_state.vs.ProduceDebugInfo(input_vertex, num_attributes, shader_config, shader_setup);
|
debug_data = Pica::g_state.vs.ProduceDebugInfo(input_vertex, num_attributes, shader_config,
|
||||||
|
shader_setup);
|
||||||
|
|
||||||
// Reload widget state
|
// Reload widget state
|
||||||
for (int attr = 0; attr < num_attributes; ++attr) {
|
for (int attr = 0; attr < num_attributes; ++attr) {
|
||||||
|
@ -537,29 +563,60 @@ void GraphicsVertexShaderWidget::OnCycleIndexChanged(int index) {
|
||||||
|
|
||||||
auto& record = debug_data.records[index];
|
auto& record = debug_data.records[index];
|
||||||
if (record.mask & Pica::Shader::DebugDataRecord::SRC1)
|
if (record.mask & Pica::Shader::DebugDataRecord::SRC1)
|
||||||
text += tr("SRC1: %1, %2, %3, %4\n").arg(record.src1.x.ToFloat32()).arg(record.src1.y.ToFloat32()).arg(record.src1.z.ToFloat32()).arg(record.src1.w.ToFloat32());
|
text += tr("SRC1: %1, %2, %3, %4\n")
|
||||||
|
.arg(record.src1.x.ToFloat32())
|
||||||
|
.arg(record.src1.y.ToFloat32())
|
||||||
|
.arg(record.src1.z.ToFloat32())
|
||||||
|
.arg(record.src1.w.ToFloat32());
|
||||||
if (record.mask & Pica::Shader::DebugDataRecord::SRC2)
|
if (record.mask & Pica::Shader::DebugDataRecord::SRC2)
|
||||||
text += tr("SRC2: %1, %2, %3, %4\n").arg(record.src2.x.ToFloat32()).arg(record.src2.y.ToFloat32()).arg(record.src2.z.ToFloat32()).arg(record.src2.w.ToFloat32());
|
text += tr("SRC2: %1, %2, %3, %4\n")
|
||||||
|
.arg(record.src2.x.ToFloat32())
|
||||||
|
.arg(record.src2.y.ToFloat32())
|
||||||
|
.arg(record.src2.z.ToFloat32())
|
||||||
|
.arg(record.src2.w.ToFloat32());
|
||||||
if (record.mask & Pica::Shader::DebugDataRecord::SRC3)
|
if (record.mask & Pica::Shader::DebugDataRecord::SRC3)
|
||||||
text += tr("SRC3: %1, %2, %3, %4\n").arg(record.src3.x.ToFloat32()).arg(record.src3.y.ToFloat32()).arg(record.src3.z.ToFloat32()).arg(record.src3.w.ToFloat32());
|
text += tr("SRC3: %1, %2, %3, %4\n")
|
||||||
|
.arg(record.src3.x.ToFloat32())
|
||||||
|
.arg(record.src3.y.ToFloat32())
|
||||||
|
.arg(record.src3.z.ToFloat32())
|
||||||
|
.arg(record.src3.w.ToFloat32());
|
||||||
if (record.mask & Pica::Shader::DebugDataRecord::DEST_IN)
|
if (record.mask & Pica::Shader::DebugDataRecord::DEST_IN)
|
||||||
text += tr("DEST_IN: %1, %2, %3, %4\n").arg(record.dest_in.x.ToFloat32()).arg(record.dest_in.y.ToFloat32()).arg(record.dest_in.z.ToFloat32()).arg(record.dest_in.w.ToFloat32());
|
text += tr("DEST_IN: %1, %2, %3, %4\n")
|
||||||
|
.arg(record.dest_in.x.ToFloat32())
|
||||||
|
.arg(record.dest_in.y.ToFloat32())
|
||||||
|
.arg(record.dest_in.z.ToFloat32())
|
||||||
|
.arg(record.dest_in.w.ToFloat32());
|
||||||
if (record.mask & Pica::Shader::DebugDataRecord::DEST_OUT)
|
if (record.mask & Pica::Shader::DebugDataRecord::DEST_OUT)
|
||||||
text += tr("DEST_OUT: %1, %2, %3, %4\n").arg(record.dest_out.x.ToFloat32()).arg(record.dest_out.y.ToFloat32()).arg(record.dest_out.z.ToFloat32()).arg(record.dest_out.w.ToFloat32());
|
text += tr("DEST_OUT: %1, %2, %3, %4\n")
|
||||||
|
.arg(record.dest_out.x.ToFloat32())
|
||||||
|
.arg(record.dest_out.y.ToFloat32())
|
||||||
|
.arg(record.dest_out.z.ToFloat32())
|
||||||
|
.arg(record.dest_out.w.ToFloat32());
|
||||||
|
|
||||||
if (record.mask & Pica::Shader::DebugDataRecord::ADDR_REG_OUT)
|
if (record.mask & Pica::Shader::DebugDataRecord::ADDR_REG_OUT)
|
||||||
text += tr("Addres Registers: %1, %2\n").arg(record.address_registers[0]).arg(record.address_registers[1]);
|
text += tr("Addres Registers: %1, %2\n")
|
||||||
|
.arg(record.address_registers[0])
|
||||||
|
.arg(record.address_registers[1]);
|
||||||
if (record.mask & Pica::Shader::DebugDataRecord::CMP_RESULT)
|
if (record.mask & Pica::Shader::DebugDataRecord::CMP_RESULT)
|
||||||
text += tr("Compare Result: %1, %2\n").arg(record.conditional_code[0] ? "true" : "false").arg(record.conditional_code[1] ? "true" : "false");
|
text += tr("Compare Result: %1, %2\n")
|
||||||
|
.arg(record.conditional_code[0] ? "true" : "false")
|
||||||
|
.arg(record.conditional_code[1] ? "true" : "false");
|
||||||
|
|
||||||
if (record.mask & Pica::Shader::DebugDataRecord::COND_BOOL_IN)
|
if (record.mask & Pica::Shader::DebugDataRecord::COND_BOOL_IN)
|
||||||
text += tr("Static Condition: %1\n").arg(record.cond_bool ? "true" : "false");
|
text += tr("Static Condition: %1\n").arg(record.cond_bool ? "true" : "false");
|
||||||
if (record.mask & Pica::Shader::DebugDataRecord::COND_CMP_IN)
|
if (record.mask & Pica::Shader::DebugDataRecord::COND_CMP_IN)
|
||||||
text += tr("Dynamic Conditions: %1, %2\n").arg(record.cond_cmp[0] ? "true" : "false").arg(record.cond_cmp[1] ? "true" : "false");
|
text += tr("Dynamic Conditions: %1, %2\n")
|
||||||
|
.arg(record.cond_cmp[0] ? "true" : "false")
|
||||||
|
.arg(record.cond_cmp[1] ? "true" : "false");
|
||||||
if (record.mask & Pica::Shader::DebugDataRecord::LOOP_INT_IN)
|
if (record.mask & Pica::Shader::DebugDataRecord::LOOP_INT_IN)
|
||||||
text += tr("Loop Parameters: %1 (repeats), %2 (initializer), %3 (increment), %4\n").arg(record.loop_int.x).arg(record.loop_int.y).arg(record.loop_int.z).arg(record.loop_int.w);
|
text += tr("Loop Parameters: %1 (repeats), %2 (initializer), %3 (increment), %4\n")
|
||||||
|
.arg(record.loop_int.x)
|
||||||
|
.arg(record.loop_int.y)
|
||||||
|
.arg(record.loop_int.z)
|
||||||
|
.arg(record.loop_int.w);
|
||||||
|
|
||||||
text += tr("Instruction offset: 0x%1").arg(4 * record.instruction_offset, 4, 16, QLatin1Char('0'));
|
text +=
|
||||||
|
tr("Instruction offset: 0x%1").arg(4 * record.instruction_offset, 4, 16, QLatin1Char('0'));
|
||||||
if (record.mask & Pica::Shader::DebugDataRecord::NEXT_INSTR) {
|
if (record.mask & Pica::Shader::DebugDataRecord::NEXT_INSTR) {
|
||||||
text += tr(" -> 0x%2").arg(4 * record.next_instruction, 4, 16, QLatin1Char('0'));
|
text += tr(" -> 0x%2").arg(4 * record.next_instruction, 4, 16, QLatin1Char('0'));
|
||||||
} else {
|
} else {
|
||||||
|
@ -570,6 +627,7 @@ void GraphicsVertexShaderWidget::OnCycleIndexChanged(int index) {
|
||||||
|
|
||||||
// Emit model update notification and scroll to current instruction
|
// Emit model update notification and scroll to current instruction
|
||||||
QModelIndex instr_index = model->index(record.instruction_offset, 0);
|
QModelIndex instr_index = model->index(record.instruction_offset, 0);
|
||||||
emit model->dataChanged(instr_index, model->index(record.instruction_offset, model->columnCount()));
|
emit model->dataChanged(instr_index,
|
||||||
|
model->index(record.instruction_offset, model->columnCount()));
|
||||||
binary_list->scrollTo(instr_index, QAbstractItemView::EnsureVisible);
|
binary_list->scrollTo(instr_index, QAbstractItemView::EnsureVisible);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,8 @@ public:
|
||||||
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
int rowCount(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 data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
QVariant headerData(int section, Qt::Orientation orientation,
|
||||||
|
int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GraphicsVertexShaderWidget* par;
|
GraphicsVertexShaderWidget* par;
|
||||||
|
@ -56,7 +57,9 @@ private slots:
|
||||||
/**
|
/**
|
||||||
* Reload widget based on the current PICA200 state
|
* Reload widget based on the current PICA200 state
|
||||||
* @param replace_vertex_data If true, invalidate all current vertex data
|
* @param replace_vertex_data If true, invalidate all current vertex data
|
||||||
* @param vertex_data New vertex data to use, as passed to OnBreakPointHit. May be nullptr to specify that no valid vertex data can be retrieved currently. Only used if replace_vertex_data is true.
|
* @param vertex_data New vertex data to use, as passed to OnBreakPointHit. May be nullptr to
|
||||||
|
* specify that no valid vertex data can be retrieved currently. Only used if
|
||||||
|
* replace_vertex_data is true.
|
||||||
*/
|
*/
|
||||||
void Reload(bool replace_vertex_data = false, void* vertex_data = nullptr);
|
void Reload(bool replace_vertex_data = false, void* vertex_data = nullptr);
|
||||||
|
|
||||||
|
@ -66,9 +69,12 @@ private:
|
||||||
GraphicsVertexShaderModel* model;
|
GraphicsVertexShaderModel* model;
|
||||||
|
|
||||||
/// TODO: Move these into a single struct
|
/// TODO: Move these into a single struct
|
||||||
std::array<QLineEdit*, 4*16> input_data; // A text box for each of the 4 components of up to 16 vertex attributes
|
std::array<QLineEdit*, 4 * 16>
|
||||||
std::array<QWidget*, 16> input_data_container; // QWidget containing the QLayout containing each vertex attribute
|
input_data; // A text box for each of the 4 components of up to 16 vertex attributes
|
||||||
std::array<QLabel*, 16> input_data_mapping; // A QLabel denoting the shader input attribute which the vertex attribute maps to
|
std::array<QWidget*, 16>
|
||||||
|
input_data_container; // QWidget containing the QLayout containing each vertex attribute
|
||||||
|
std::array<QLabel*, 16> input_data_mapping; // A QLabel denoting the shader input attribute
|
||||||
|
// which the vertex attribute maps to
|
||||||
|
|
||||||
// Text to be shown when input vertex data is not retrievable
|
// Text to be shown when input vertex data is not retrievable
|
||||||
QLabel* breakpoint_warning;
|
QLabel* breakpoint_warning;
|
||||||
|
|
|
@ -22,57 +22,58 @@
|
||||||
|
|
||||||
using namespace Common::Profiling;
|
using namespace Common::Profiling;
|
||||||
|
|
||||||
static QVariant GetDataForColumn(int col, const AggregatedDuration& duration)
|
static QVariant GetDataForColumn(int col, const AggregatedDuration& duration) {
|
||||||
{
|
|
||||||
static auto duration_to_float = [](Duration dur) -> float {
|
static auto duration_to_float = [](Duration dur) -> float {
|
||||||
using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>;
|
using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>;
|
||||||
return std::chrono::duration_cast<FloatMs>(dur).count();
|
return std::chrono::duration_cast<FloatMs>(dur).count();
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (col) {
|
switch (col) {
|
||||||
case 1: return duration_to_float(duration.avg);
|
case 1:
|
||||||
case 2: return duration_to_float(duration.min);
|
return duration_to_float(duration.avg);
|
||||||
case 3: return duration_to_float(duration.max);
|
case 2:
|
||||||
default: return QVariant();
|
return duration_to_float(duration.min);
|
||||||
|
case 3:
|
||||||
|
return duration_to_float(duration.max);
|
||||||
|
default:
|
||||||
|
return QVariant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfilerModel::ProfilerModel(QObject* parent) : QAbstractItemModel(parent)
|
ProfilerModel::ProfilerModel(QObject* parent) : QAbstractItemModel(parent) {
|
||||||
{
|
|
||||||
updateProfilingInfo();
|
updateProfilingInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant ProfilerModel::headerData(int section, Qt::Orientation orientation, int role) const
|
QVariant ProfilerModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||||
{
|
|
||||||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
|
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
|
||||||
switch (section) {
|
switch (section) {
|
||||||
case 0: return tr("Category");
|
case 0:
|
||||||
case 1: return tr("Avg");
|
return tr("Category");
|
||||||
case 2: return tr("Min");
|
case 1:
|
||||||
case 3: return tr("Max");
|
return tr("Avg");
|
||||||
|
case 2:
|
||||||
|
return tr("Min");
|
||||||
|
case 3:
|
||||||
|
return tr("Max");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex ProfilerModel::index(int row, int column, const QModelIndex& parent) const
|
QModelIndex ProfilerModel::index(int row, int column, const QModelIndex& parent) const {
|
||||||
{
|
|
||||||
return createIndex(row, column);
|
return createIndex(row, column);
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex ProfilerModel::parent(const QModelIndex& child) const
|
QModelIndex ProfilerModel::parent(const QModelIndex& child) const {
|
||||||
{
|
|
||||||
return QModelIndex();
|
return QModelIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
int ProfilerModel::columnCount(const QModelIndex& parent) const
|
int ProfilerModel::columnCount(const QModelIndex& parent) const {
|
||||||
{
|
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ProfilerModel::rowCount(const QModelIndex& parent) const
|
int ProfilerModel::rowCount(const QModelIndex& parent) const {
|
||||||
{
|
|
||||||
if (parent.isValid()) {
|
if (parent.isValid()) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
|
@ -80,8 +81,7 @@ int ProfilerModel::rowCount(const QModelIndex& parent) const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant ProfilerModel::data(const QModelIndex& index, int role) const
|
QVariant ProfilerModel::data(const QModelIndex& index, int role) const {
|
||||||
{
|
|
||||||
if (role == Qt::DisplayRole) {
|
if (role == Qt::DisplayRole) {
|
||||||
if (index.row() == 0) {
|
if (index.row() == 0) {
|
||||||
if (index.column() == 0) {
|
if (index.column() == 0) {
|
||||||
|
@ -101,14 +101,12 @@ QVariant ProfilerModel::data(const QModelIndex& index, int role) const
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfilerModel::updateProfilingInfo()
|
void ProfilerModel::updateProfilingInfo() {
|
||||||
{
|
|
||||||
results = GetTimingResultsAggregator()->GetAggregatedResults();
|
results = GetTimingResultsAggregator()->GetAggregatedResults();
|
||||||
emit dataChanged(createIndex(0, 1), createIndex(rowCount() - 1, 3));
|
emit dataChanged(createIndex(0, 1), createIndex(rowCount() - 1, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfilerWidget::ProfilerWidget(QWidget* parent) : QDockWidget(parent)
|
ProfilerWidget::ProfilerWidget(QWidget* parent) : QDockWidget(parent) {
|
||||||
{
|
|
||||||
ui.setupUi(this);
|
ui.setupUi(this);
|
||||||
|
|
||||||
model = new ProfilerModel(this);
|
model = new ProfilerModel(this);
|
||||||
|
@ -118,8 +116,7 @@ ProfilerWidget::ProfilerWidget(QWidget* parent) : QDockWidget(parent)
|
||||||
connect(&update_timer, SIGNAL(timeout()), model, SLOT(updateProfilingInfo()));
|
connect(&update_timer, SIGNAL(timeout()), model, SLOT(updateProfilingInfo()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable)
|
void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable) {
|
||||||
{
|
|
||||||
if (enable) {
|
if (enable) {
|
||||||
update_timer.start(100);
|
update_timer.start(100);
|
||||||
model->updateProfilingInfo();
|
model->updateProfilingInfo();
|
||||||
|
@ -157,9 +154,7 @@ private:
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
MicroProfileDialog::MicroProfileDialog(QWidget* parent)
|
MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) {
|
||||||
: QWidget(parent, Qt::Dialog)
|
|
||||||
{
|
|
||||||
setObjectName("MicroProfile");
|
setObjectName("MicroProfile");
|
||||||
setWindowTitle(tr("MicroProfile"));
|
setWindowTitle(tr("MicroProfile"));
|
||||||
resize(1000, 600);
|
resize(1000, 600);
|
||||||
|
@ -175,7 +170,8 @@ MicroProfileDialog::MicroProfileDialog(QWidget* parent)
|
||||||
layout->addWidget(widget);
|
layout->addWidget(widget);
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
|
||||||
// Configure focus so that widget is focusable and the dialog automatically forwards focus to it.
|
// Configure focus so that widget is focusable and the dialog automatically forwards focus to
|
||||||
|
// it.
|
||||||
setFocusProxy(widget);
|
setFocusProxy(widget);
|
||||||
widget->setFocusPolicy(Qt::StrongFocus);
|
widget->setFocusPolicy(Qt::StrongFocus);
|
||||||
widget->setFocus();
|
widget->setFocus();
|
||||||
|
@ -207,7 +203,6 @@ void MicroProfileDialog::hideEvent(QHideEvent* ev) {
|
||||||
QWidget::hideEvent(ev);
|
QWidget::hideEvent(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if MICROPROFILE_ENABLED
|
#if MICROPROFILE_ENABLED
|
||||||
|
|
||||||
/// There's no way to pass a user pointer to MicroProfile, so this variable is used to make the
|
/// There's no way to pass a user pointer to MicroProfile, so this variable is used to make the
|
||||||
|
@ -308,7 +303,8 @@ void MicroProfileDrawText(int x, int y, u32 hex_color, const char* text, u32 tex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicroProfileDrawBox(int left, int top, int right, int bottom, u32 hex_color, MicroProfileBoxType type) {
|
void MicroProfileDrawBox(int left, int top, int right, int bottom, u32 hex_color,
|
||||||
|
MicroProfileBoxType type) {
|
||||||
QColor color = QColor::fromRgba(hex_color);
|
QColor color = QColor::fromRgba(hex_color);
|
||||||
QBrush brush = color;
|
QBrush brush = color;
|
||||||
if (type == MicroProfileBoxTypeBar) {
|
if (type == MicroProfileBoxTypeBar) {
|
||||||
|
@ -326,7 +322,7 @@ void MicroProfileDrawLine2D(u32 vertices_length, float* vertices, u32 hex_color)
|
||||||
static std::vector<QPointF> point_buf;
|
static std::vector<QPointF> point_buf;
|
||||||
|
|
||||||
for (u32 i = 0; i < vertices_length; ++i) {
|
for (u32 i = 0; i < vertices_length; ++i) {
|
||||||
point_buf.emplace_back(vertices[i*2 + 0], vertices[i*2 + 1]);
|
point_buf.emplace_back(vertices[i * 2 + 0], vertices[i * 2 + 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// hex_color does not include an alpha, so it must be assumed to be 255
|
// hex_color does not include an alpha, so it must be assumed to be 255
|
||||||
|
|
|
@ -13,15 +13,16 @@
|
||||||
#include "common/microprofile.h"
|
#include "common/microprofile.h"
|
||||||
#include "common/profiler_reporting.h"
|
#include "common/profiler_reporting.h"
|
||||||
|
|
||||||
class ProfilerModel : public QAbstractItemModel
|
class ProfilerModel : public QAbstractItemModel {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ProfilerModel(QObject* parent);
|
ProfilerModel(QObject* parent);
|
||||||
|
|
||||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
QVariant headerData(int section, Qt::Orientation orientation,
|
||||||
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
|
int role = Qt::DisplayRole) const override;
|
||||||
|
QModelIndex index(int row, int column,
|
||||||
|
const QModelIndex& parent = QModelIndex()) const override;
|
||||||
QModelIndex parent(const QModelIndex& child) const override;
|
QModelIndex parent(const QModelIndex& child) const override;
|
||||||
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
|
@ -34,8 +35,7 @@ private:
|
||||||
Common::Profiling::AggregatedFrameResult results;
|
Common::Profiling::AggregatedFrameResult results;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ProfilerWidget : public QDockWidget
|
class ProfilerWidget : public QDockWidget {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -51,7 +51,6 @@ private:
|
||||||
QTimer update_timer;
|
QTimer update_timer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class MicroProfileDialog : public QWidget {
|
class MicroProfileDialog : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,10 @@
|
||||||
|
|
||||||
#include "citra_qt/debugger/ramview.h"
|
#include "citra_qt/debugger/ramview.h"
|
||||||
|
|
||||||
GRamView::GRamView(QWidget* parent) : QHexEdit(parent)
|
GRamView::GRamView(QWidget* parent) : QHexEdit(parent) {
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRamView::OnCPUStepped()
|
void GRamView::OnCPUStepped() {
|
||||||
{
|
|
||||||
// TODO: QHexEdit doesn't show vertical scroll bars for > 10MB data streams...
|
// TODO: QHexEdit doesn't show vertical scroll bars for > 10MB data streams...
|
||||||
//setData(QByteArray((const char*)Mem_RAM,sizeof(Mem_RAM)/8));
|
// setData(QByteArray((const char*)Mem_RAM,sizeof(Mem_RAM)/8));
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
|
|
||||||
#include "qhexedit.h"
|
#include "qhexedit.h"
|
||||||
|
|
||||||
class GRamView : public QHexEdit
|
class GRamView : public QHexEdit {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
#include "citra_qt/debugger/registers.h"
|
#include "citra_qt/debugger/registers.h"
|
||||||
#include "citra_qt/util/util.h"
|
#include "citra_qt/util/util.h"
|
||||||
|
|
||||||
#include "core/core.h"
|
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
|
||||||
RegistersWidget::RegistersWidget(QWidget* parent) : QDockWidget(parent) {
|
RegistersWidget::RegistersWidget(QWidget* parent) : QDockWidget(parent) {
|
||||||
cpu_regs_ui.setupUi(this);
|
cpu_regs_ui.setupUi(this);
|
||||||
|
@ -16,7 +16,8 @@ RegistersWidget::RegistersWidget(QWidget* parent) : QDockWidget(parent) {
|
||||||
tree = cpu_regs_ui.treeWidget;
|
tree = cpu_regs_ui.treeWidget;
|
||||||
tree->addTopLevelItem(core_registers = new QTreeWidgetItem(QStringList(tr("Registers"))));
|
tree->addTopLevelItem(core_registers = new QTreeWidgetItem(QStringList(tr("Registers"))));
|
||||||
tree->addTopLevelItem(vfp_registers = new QTreeWidgetItem(QStringList(tr("VFP Registers"))));
|
tree->addTopLevelItem(vfp_registers = new QTreeWidgetItem(QStringList(tr("VFP Registers"))));
|
||||||
tree->addTopLevelItem(vfp_system_registers = new QTreeWidgetItem(QStringList(tr("VFP System Registers"))));
|
tree->addTopLevelItem(vfp_system_registers =
|
||||||
|
new QTreeWidgetItem(QStringList(tr("VFP System Registers"))));
|
||||||
tree->addTopLevelItem(cpsr = new QTreeWidgetItem(QStringList("CPSR")));
|
tree->addTopLevelItem(cpsr = new QTreeWidgetItem(QStringList("CPSR")));
|
||||||
|
|
||||||
for (int i = 0; i < 16; ++i) {
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
@ -63,10 +64,12 @@ void RegistersWidget::OnDebugModeEntered() {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (int i = 0; i < core_registers->childCount(); ++i)
|
for (int i = 0; i < core_registers->childCount(); ++i)
|
||||||
core_registers->child(i)->setText(1, QString("0x%1").arg(Core::g_app_core->GetReg(i), 8, 16, QLatin1Char('0')));
|
core_registers->child(i)->setText(
|
||||||
|
1, QString("0x%1").arg(Core::g_app_core->GetReg(i), 8, 16, QLatin1Char('0')));
|
||||||
|
|
||||||
for (int i = 0; i < vfp_registers->childCount(); ++i)
|
for (int i = 0; i < vfp_registers->childCount(); ++i)
|
||||||
vfp_registers->child(i)->setText(1, QString("0x%1").arg(Core::g_app_core->GetVFPReg(i), 8, 16, QLatin1Char('0')));
|
vfp_registers->child(i)->setText(
|
||||||
|
1, QString("0x%1").arg(Core::g_app_core->GetVFPReg(i), 8, 16, QLatin1Char('0')));
|
||||||
|
|
||||||
UpdateCPSRValues();
|
UpdateCPSRValues();
|
||||||
UpdateVFPSystemRegisterValues();
|
UpdateVFPSystemRegisterValues();
|
||||||
|
@ -130,14 +133,17 @@ void RegistersWidget::UpdateCPSRValues() {
|
||||||
const u32 cpsr_val = Core::g_app_core->GetCPSR();
|
const u32 cpsr_val = Core::g_app_core->GetCPSR();
|
||||||
|
|
||||||
cpsr->setText(1, QString("0x%1").arg(cpsr_val, 8, 16, QLatin1Char('0')));
|
cpsr->setText(1, QString("0x%1").arg(cpsr_val, 8, 16, QLatin1Char('0')));
|
||||||
cpsr->child(0)->setText(1, QString("b%1").arg(cpsr_val & 0x1F, 5, 2, QLatin1Char('0'))); // M - Mode
|
cpsr->child(0)->setText(
|
||||||
|
1, QString("b%1").arg(cpsr_val & 0x1F, 5, 2, QLatin1Char('0'))); // M - Mode
|
||||||
cpsr->child(1)->setText(1, QString::number((cpsr_val >> 5) & 1)); // T - State
|
cpsr->child(1)->setText(1, QString::number((cpsr_val >> 5) & 1)); // T - State
|
||||||
cpsr->child(2)->setText(1, QString::number((cpsr_val >> 6) & 1)); // F - FIQ disable
|
cpsr->child(2)->setText(1, QString::number((cpsr_val >> 6) & 1)); // F - FIQ disable
|
||||||
cpsr->child(3)->setText(1, QString::number((cpsr_val >> 7) & 1)); // I - IRQ disable
|
cpsr->child(3)->setText(1, QString::number((cpsr_val >> 7) & 1)); // I - IRQ disable
|
||||||
cpsr->child(4)->setText(1, QString::number((cpsr_val >> 8) & 1)); // A - Imprecise abort
|
cpsr->child(4)->setText(1, QString::number((cpsr_val >> 8) & 1)); // A - Imprecise abort
|
||||||
cpsr->child(5)->setText(1, QString::number((cpsr_val >> 9) & 1)); // E - Data endianess
|
cpsr->child(5)->setText(1, QString::number((cpsr_val >> 9) & 1)); // E - Data endianess
|
||||||
cpsr->child(6)->setText(1, QString::number((cpsr_val >> 10) & 0x3F)); // IT - If-Then state (DNM)
|
cpsr->child(6)->setText(1,
|
||||||
cpsr->child(7)->setText(1, QString::number((cpsr_val >> 16) & 0xF)); // GE - Greater-than-or-Equal
|
QString::number((cpsr_val >> 10) & 0x3F)); // IT - If-Then state (DNM)
|
||||||
|
cpsr->child(7)->setText(1,
|
||||||
|
QString::number((cpsr_val >> 16) & 0xF)); // GE - Greater-than-or-Equal
|
||||||
cpsr->child(8)->setText(1, QString::number((cpsr_val >> 20) & 0xF)); // DNM - Do not modify
|
cpsr->child(8)->setText(1, QString::number((cpsr_val >> 20) & 0xF)); // DNM - Do not modify
|
||||||
cpsr->child(9)->setText(1, QString::number((cpsr_val >> 24) & 1)); // J - Jazelle
|
cpsr->child(9)->setText(1, QString::number((cpsr_val >> 24) & 1)); // J - Jazelle
|
||||||
cpsr->child(10)->setText(1, QString::number((cpsr_val >> 27) & 1)); // Q - Saturation
|
cpsr->child(10)->setText(1, QString::number((cpsr_val >> 27) & 1)); // Q - Saturation
|
||||||
|
@ -228,6 +234,8 @@ void RegistersWidget::UpdateVFPSystemRegisterValues() {
|
||||||
fpexc->child(6)->setText(1, QString::number((fpexc_val >> 30) & 1));
|
fpexc->child(6)->setText(1, QString::number((fpexc_val >> 30) & 1));
|
||||||
fpexc->child(7)->setText(1, QString::number((fpexc_val >> 31) & 1));
|
fpexc->child(7)->setText(1, QString::number((fpexc_val >> 31) & 1));
|
||||||
|
|
||||||
vfp_system_registers->child(2)->setText(1, QString("0x%1").arg(fpinst_val, 8, 16, QLatin1Char('0')));
|
vfp_system_registers->child(2)->setText(
|
||||||
vfp_system_registers->child(3)->setText(1, QString("0x%1").arg(fpinst2_val, 8, 16, QLatin1Char('0')));
|
1, QString("0x%1").arg(fpinst_val, 8, 16, QLatin1Char('0')));
|
||||||
|
vfp_system_registers->child(3)->setText(
|
||||||
|
1, QString("0x%1").arg(fpinst2_val, 8, 16, QLatin1Char('0')));
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,7 @@ class QTreeWidget;
|
||||||
class QTreeWidgetItem;
|
class QTreeWidgetItem;
|
||||||
class EmuThread;
|
class EmuThread;
|
||||||
|
|
||||||
class RegistersWidget : public QDockWidget
|
class RegistersWidget : public QDockWidget {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -16,8 +16,7 @@
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
|
|
||||||
GameList::GameList(QWidget* parent)
|
GameList::GameList(QWidget* parent) {
|
||||||
{
|
|
||||||
QVBoxLayout* layout = new QVBoxLayout;
|
QVBoxLayout* layout = new QVBoxLayout;
|
||||||
|
|
||||||
tree_view = new QTreeView;
|
tree_view = new QTreeView;
|
||||||
|
@ -38,9 +37,11 @@ GameList::GameList(QWidget* parent)
|
||||||
item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type");
|
item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type");
|
||||||
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size");
|
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size");
|
||||||
|
|
||||||
connect(tree_view, SIGNAL(activated(const QModelIndex&)), this, SLOT(ValidateEntry(const QModelIndex&)));
|
connect(tree_view, SIGNAL(activated(const QModelIndex&)), this,
|
||||||
|
SLOT(ValidateEntry(const QModelIndex&)));
|
||||||
|
|
||||||
// We must register all custom types with the Qt Automoc system so that we are able to use it with
|
// We must register all custom types with the Qt Automoc system so that we are able to use it
|
||||||
|
// with
|
||||||
// signals/slots. In this case, QList falls under the umbrells of custom types.
|
// signals/slots. In this case, QList falls under the umbrells of custom types.
|
||||||
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
|
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
|
||||||
|
|
||||||
|
@ -48,18 +49,15 @@ GameList::GameList(QWidget* parent)
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
GameList::~GameList()
|
GameList::~GameList() {
|
||||||
{
|
|
||||||
emit ShouldCancelWorker();
|
emit ShouldCancelWorker();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameList::AddEntry(QList<QStandardItem*> entry_items)
|
void GameList::AddEntry(QList<QStandardItem*> entry_items) {
|
||||||
{
|
|
||||||
item_model->invisibleRootItem()->appendRow(entry_items);
|
item_model->invisibleRootItem()->appendRow(entry_items);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameList::ValidateEntry(const QModelIndex& item)
|
void GameList::ValidateEntry(const QModelIndex& item) {
|
||||||
{
|
|
||||||
// We don't care about the individual QStandardItem that was selected, but its row.
|
// We don't care about the individual QStandardItem that was selected, but its row.
|
||||||
int row = item_model->itemFromIndex(item)->row();
|
int row = item_model->itemFromIndex(item)->row();
|
||||||
QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
|
QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
|
||||||
|
@ -73,14 +71,13 @@ void GameList::ValidateEntry(const QModelIndex& item)
|
||||||
emit GameChosen(file_path);
|
emit GameChosen(file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameList::DonePopulating()
|
void GameList::DonePopulating() {
|
||||||
{
|
|
||||||
tree_view->setEnabled(true);
|
tree_view->setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameList::PopulateAsync(const QString& dir_path, bool deep_scan)
|
void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
|
||||||
{
|
if (!FileUtil::Exists(dir_path.toStdString()) ||
|
||||||
if (!FileUtil::Exists(dir_path.toStdString()) || !FileUtil::IsDirectory(dir_path.toStdString())) {
|
!FileUtil::IsDirectory(dir_path.toStdString())) {
|
||||||
LOG_ERROR(Frontend, "Could not find game list folder at %s", dir_path.toLocal8Bit().data());
|
LOG_ERROR(Frontend, "Could not find game list folder at %s", dir_path.toLocal8Bit().data());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -92,22 +89,22 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan)
|
||||||
emit ShouldCancelWorker();
|
emit ShouldCancelWorker();
|
||||||
GameListWorker* worker = new GameListWorker(dir_path, deep_scan);
|
GameListWorker* worker = new GameListWorker(dir_path, deep_scan);
|
||||||
|
|
||||||
connect(worker, SIGNAL(EntryReady(QList<QStandardItem*>)), this, SLOT(AddEntry(QList<QStandardItem*>)), Qt::QueuedConnection);
|
connect(worker, SIGNAL(EntryReady(QList<QStandardItem*>)), this,
|
||||||
|
SLOT(AddEntry(QList<QStandardItem*>)), Qt::QueuedConnection);
|
||||||
connect(worker, SIGNAL(Finished()), this, SLOT(DonePopulating()), Qt::QueuedConnection);
|
connect(worker, SIGNAL(Finished()), this, SLOT(DonePopulating()), Qt::QueuedConnection);
|
||||||
// Use DirectConnection here because worker->Cancel() is thread-safe and we want it to cancel without delay.
|
// Use DirectConnection here because worker->Cancel() is thread-safe and we want it to cancel
|
||||||
|
// without delay.
|
||||||
connect(this, SIGNAL(ShouldCancelWorker()), worker, SLOT(Cancel()), Qt::DirectConnection);
|
connect(this, SIGNAL(ShouldCancelWorker()), worker, SLOT(Cancel()), Qt::DirectConnection);
|
||||||
|
|
||||||
QThreadPool::globalInstance()->start(worker);
|
QThreadPool::globalInstance()->start(worker);
|
||||||
current_worker = std::move(worker);
|
current_worker = std::move(worker);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameList::SaveInterfaceLayout()
|
void GameList::SaveInterfaceLayout() {
|
||||||
{
|
|
||||||
UISettings::values.gamelist_header_state = tree_view->header()->saveState();
|
UISettings::values.gamelist_header_state = tree_view->header()->saveState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameList::LoadInterfaceLayout()
|
void GameList::LoadInterfaceLayout() {
|
||||||
{
|
|
||||||
auto header = tree_view->header();
|
auto header = tree_view->header();
|
||||||
if (!header->restoreState(UISettings::values.gamelist_header_state)) {
|
if (!header->restoreState(UISettings::values.gamelist_header_state)) {
|
||||||
// We are using the name column to display icons and titles
|
// We are using the name column to display icons and titles
|
||||||
|
@ -118,10 +115,8 @@ void GameList::LoadInterfaceLayout()
|
||||||
item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder());
|
item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion)
|
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
|
||||||
{
|
const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory,
|
||||||
const auto callback = [this, recursion](unsigned* num_entries_out,
|
|
||||||
const std::string& directory,
|
|
||||||
const std::string& virtual_name) -> bool {
|
const std::string& virtual_name) -> bool {
|
||||||
std::string physical_name = directory + DIR_SEP + virtual_name;
|
std::string physical_name = directory + DIR_SEP + virtual_name;
|
||||||
|
|
||||||
|
@ -138,7 +133,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||||
|
|
||||||
emit EntryReady({
|
emit EntryReady({
|
||||||
new GameListItemPath(QString::fromStdString(physical_name), smdh),
|
new GameListItemPath(QString::fromStdString(physical_name), smdh),
|
||||||
new GameListItem(QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
new GameListItem(
|
||||||
|
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||||
new GameListItemSize(FileUtil::GetSize(physical_name)),
|
new GameListItemSize(FileUtil::GetSize(physical_name)),
|
||||||
});
|
});
|
||||||
} else if (recursion > 0) {
|
} else if (recursion > 0) {
|
||||||
|
@ -151,15 +147,13 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||||
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
|
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameListWorker::run()
|
void GameListWorker::run() {
|
||||||
{
|
|
||||||
stop_processing = false;
|
stop_processing = false;
|
||||||
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
|
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
|
||||||
emit Finished();
|
emit Finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameListWorker::Cancel()
|
void GameListWorker::Cancel() {
|
||||||
{
|
|
||||||
disconnect(this, 0, 0, 0);
|
disconnect(this, 0, 0, 0);
|
||||||
stop_processing = true;
|
stop_processing = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
class GameListWorker;
|
class GameListWorker;
|
||||||
|
|
||||||
|
|
||||||
class GameList : public QWidget {
|
class GameList : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "citra_qt/util/util.h"
|
#include "citra_qt/util/util.h"
|
||||||
#include "common/string_util.h"
|
|
||||||
#include "common/color.h"
|
#include "common/color.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
|
||||||
#include "core/loader/smdh.h"
|
#include "core/loader/smdh.h"
|
||||||
|
|
||||||
|
@ -51,19 +51,22 @@ static QPixmap GetDefaultIcon(bool large) {
|
||||||
* @param language title language
|
* @param language title language
|
||||||
* @return QString short title
|
* @return QString short title
|
||||||
*/
|
*/
|
||||||
static QString GetQStringShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) {
|
static QString GetQStringShortTitleFromSMDH(const Loader::SMDH& smdh,
|
||||||
|
Loader::SMDH::TitleLanguage language) {
|
||||||
return QString::fromUtf16(smdh.GetShortTitle(language).data());
|
return QString::fromUtf16(smdh.GetShortTitle(language).data());
|
||||||
}
|
}
|
||||||
|
|
||||||
class GameListItem : public QStandardItem {
|
class GameListItem : public QStandardItem {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GameListItem(): QStandardItem() {}
|
GameListItem() : QStandardItem() {
|
||||||
GameListItem(const QString& string): QStandardItem(string) {}
|
}
|
||||||
virtual ~GameListItem() override {}
|
GameListItem(const QString& string) : QStandardItem(string) {
|
||||||
|
}
|
||||||
|
virtual ~GameListItem() override {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A specialization of GameListItem for path values.
|
* A specialization of GameListItem for path values.
|
||||||
* This class ensures that for every full path value it holds, a correct string representation
|
* This class ensures that for every full path value it holds, a correct string representation
|
||||||
|
@ -76,9 +79,9 @@ public:
|
||||||
static const int FullPathRole = Qt::UserRole + 1;
|
static const int FullPathRole = Qt::UserRole + 1;
|
||||||
static const int TitleRole = Qt::UserRole + 2;
|
static const int TitleRole = Qt::UserRole + 2;
|
||||||
|
|
||||||
GameListItemPath(): GameListItem() {}
|
GameListItemPath() : GameListItem() {
|
||||||
GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data): GameListItem()
|
}
|
||||||
{
|
GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data) : GameListItem() {
|
||||||
setData(game_path, FullPathRole);
|
setData(game_path, FullPathRole);
|
||||||
|
|
||||||
if (!Loader::IsValidSMDH(smdh_data)) {
|
if (!Loader::IsValidSMDH(smdh_data)) {
|
||||||
|
@ -94,13 +97,15 @@ public:
|
||||||
setData(GetQPixmapFromSMDH(smdh, true), Qt::DecorationRole);
|
setData(GetQPixmapFromSMDH(smdh, true), Qt::DecorationRole);
|
||||||
|
|
||||||
// Get title form SMDH
|
// Get title form SMDH
|
||||||
setData(GetQStringShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English), TitleRole);
|
setData(GetQStringShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English),
|
||||||
|
TitleRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant data(int role) const override {
|
QVariant data(int role) const override {
|
||||||
if (role == Qt::DisplayRole) {
|
if (role == Qt::DisplayRole) {
|
||||||
std::string filename;
|
std::string filename;
|
||||||
Common::SplitPath(data(FullPathRole).toString().toStdString(), nullptr, &filename, nullptr);
|
Common::SplitPath(data(FullPathRole).toString().toStdString(), nullptr, &filename,
|
||||||
|
nullptr);
|
||||||
QString title = data(TitleRole).toString();
|
QString title = data(TitleRole).toString();
|
||||||
return QString::fromStdString(filename) + (title.isEmpty() ? "" : "\n " + title);
|
return QString::fromStdString(filename) + (title.isEmpty() ? "" : "\n " + title);
|
||||||
} else {
|
} else {
|
||||||
|
@ -109,7 +114,6 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A specialization of GameListItem for size values.
|
* A specialization of GameListItem for size values.
|
||||||
* This class ensures that for every numerical size value it holds (in bytes), a correct
|
* This class ensures that for every numerical size value it holds (in bytes), a correct
|
||||||
|
@ -120,14 +124,13 @@ class GameListItemSize : public GameListItem {
|
||||||
public:
|
public:
|
||||||
static const int SizeRole = Qt::UserRole + 1;
|
static const int SizeRole = Qt::UserRole + 1;
|
||||||
|
|
||||||
GameListItemSize(): GameListItem() {}
|
GameListItemSize() : GameListItem() {
|
||||||
GameListItemSize(const qulonglong size_bytes): GameListItem()
|
}
|
||||||
{
|
GameListItemSize(const qulonglong size_bytes) : GameListItem() {
|
||||||
setData(size_bytes, SizeRole);
|
setData(size_bytes, SizeRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setData(const QVariant& value, int role) override
|
void setData(const QVariant& value, int role) override {
|
||||||
{
|
|
||||||
// By specializing setData for SizeRole, we can ensure that the numerical and string
|
// By specializing setData for SizeRole, we can ensure that the numerical and string
|
||||||
// representations of the data are always accurate and in the correct format.
|
// representations of the data are always accurate and in the correct format.
|
||||||
if (role == SizeRole) {
|
if (role == SizeRole) {
|
||||||
|
@ -141,15 +144,14 @@ public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This operator is, in practice, only used by the TreeView sorting systems.
|
* This operator is, in practice, only used by the TreeView sorting systems.
|
||||||
* Override it so that it will correctly sort by numerical value instead of by string representation.
|
* Override it so that it will correctly sort by numerical value instead of by string
|
||||||
|
* representation.
|
||||||
*/
|
*/
|
||||||
bool operator<(const QStandardItem& other) const override
|
bool operator<(const QStandardItem& other) const override {
|
||||||
{
|
|
||||||
return data(SizeRole).toULongLong() < other.data(SizeRole).toULongLong();
|
return data(SizeRole).toULongLong() < other.data(SizeRole).toULongLong();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronous worker object for populating the game list.
|
* Asynchronous worker object for populating the game list.
|
||||||
* Communicates with other threads through Qt's signal/slot system.
|
* Communicates with other threads through Qt's signal/slot system.
|
||||||
|
@ -158,8 +160,9 @@ class GameListWorker : public QObject, public QRunnable {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GameListWorker(QString dir_path, bool deep_scan):
|
GameListWorker(QString dir_path, bool deep_scan)
|
||||||
QObject(), QRunnable(), dir_path(dir_path), deep_scan(deep_scan) {}
|
: QObject(), QRunnable(), dir_path(dir_path), deep_scan(deep_scan) {
|
||||||
|
}
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/// Starts the processing of directory tree information.
|
/// Starts the processing of directory tree information.
|
||||||
|
|
|
@ -4,16 +4,16 @@
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QKeySequence>
|
#include <QKeySequence>
|
||||||
#include <QShortcut>
|
#include <QShortcut>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
#include "citra_qt/hotkeys.h"
|
#include "citra_qt/hotkeys.h"
|
||||||
#include "citra_qt/ui_settings.h"
|
#include "citra_qt/ui_settings.h"
|
||||||
|
|
||||||
struct Hotkey
|
struct Hotkey {
|
||||||
{
|
Hotkey() : shortcut(nullptr), context(Qt::WindowShortcut) {
|
||||||
Hotkey() : shortcut(nullptr), context(Qt::WindowShortcut) {}
|
}
|
||||||
|
|
||||||
QKeySequence keyseq;
|
QKeySequence keyseq;
|
||||||
QShortcut* shortcut;
|
QShortcut* shortcut;
|
||||||
|
@ -25,13 +25,10 @@ typedef std::map<QString, HotkeyMap> HotkeyGroupMap;
|
||||||
|
|
||||||
HotkeyGroupMap hotkey_groups;
|
HotkeyGroupMap hotkey_groups;
|
||||||
|
|
||||||
void SaveHotkeys()
|
void SaveHotkeys() {
|
||||||
{
|
|
||||||
UISettings::values.shortcuts.clear();
|
UISettings::values.shortcuts.clear();
|
||||||
for (auto group : hotkey_groups)
|
for (auto group : hotkey_groups) {
|
||||||
{
|
for (auto hotkey : group.second) {
|
||||||
for (auto hotkey : group.second)
|
|
||||||
{
|
|
||||||
UISettings::values.shortcuts.emplace_back(
|
UISettings::values.shortcuts.emplace_back(
|
||||||
UISettings::Shortcut(group.first + "/" + hotkey.first,
|
UISettings::Shortcut(group.first + "/" + hotkey.first,
|
||||||
UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
|
UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
|
||||||
|
@ -40,18 +37,16 @@ void SaveHotkeys()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadHotkeys()
|
void LoadHotkeys() {
|
||||||
{
|
// Make sure NOT to use a reference here because it would become invalid once we call
|
||||||
// Make sure NOT to use a reference here because it would become invalid once we call beginGroup()
|
// beginGroup()
|
||||||
for (auto shortcut : UISettings::values.shortcuts)
|
for (auto shortcut : UISettings::values.shortcuts) {
|
||||||
{
|
|
||||||
QStringList cat = shortcut.first.split("/");
|
QStringList cat = shortcut.first.split("/");
|
||||||
Q_ASSERT(cat.size() >= 2);
|
Q_ASSERT(cat.size() >= 2);
|
||||||
|
|
||||||
// RegisterHotkey assigns default keybindings, so use old values as default parameters
|
// RegisterHotkey assigns default keybindings, so use old values as default parameters
|
||||||
Hotkey& hk = hotkey_groups[cat[0]][cat[1]];
|
Hotkey& hk = hotkey_groups[cat[0]][cat[1]];
|
||||||
if (!shortcut.second.first.isEmpty())
|
if (!shortcut.second.first.isEmpty()) {
|
||||||
{
|
|
||||||
hk.keyseq = QKeySequence::fromString(shortcut.second.first);
|
hk.keyseq = QKeySequence::fromString(shortcut.second.first);
|
||||||
hk.context = (Qt::ShortcutContext)shortcut.second.second;
|
hk.context = (Qt::ShortcutContext)shortcut.second.second;
|
||||||
}
|
}
|
||||||
|
@ -60,17 +55,15 @@ void LoadHotkeys()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegisterHotkey(const QString& group, const QString& action, const QKeySequence& default_keyseq, Qt::ShortcutContext default_context)
|
void RegisterHotkey(const QString& group, const QString& action, const QKeySequence& default_keyseq,
|
||||||
{
|
Qt::ShortcutContext default_context) {
|
||||||
if (hotkey_groups[group].find(action) == hotkey_groups[group].end())
|
if (hotkey_groups[group].find(action) == hotkey_groups[group].end()) {
|
||||||
{
|
|
||||||
hotkey_groups[group][action].keyseq = default_keyseq;
|
hotkey_groups[group][action].keyseq = default_keyseq;
|
||||||
hotkey_groups[group][action].context = default_context;
|
hotkey_groups[group][action].context = default_context;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget)
|
QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget) {
|
||||||
{
|
|
||||||
Hotkey& hk = hotkey_groups[group][action];
|
Hotkey& hk = hotkey_groups[group][action];
|
||||||
|
|
||||||
if (!hk.shortcut)
|
if (!hk.shortcut)
|
||||||
|
@ -79,16 +72,12 @@ QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widge
|
||||||
return hk.shortcut;
|
return hk.shortcut;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GHotkeysDialog::GHotkeysDialog(QWidget* parent) : QWidget(parent) {
|
||||||
GHotkeysDialog::GHotkeysDialog(QWidget* parent): QWidget(parent)
|
|
||||||
{
|
|
||||||
ui.setupUi(this);
|
ui.setupUi(this);
|
||||||
|
|
||||||
for (auto group : hotkey_groups)
|
for (auto group : hotkey_groups) {
|
||||||
{
|
|
||||||
QTreeWidgetItem* toplevel_item = new QTreeWidgetItem(QStringList(group.first));
|
QTreeWidgetItem* toplevel_item = new QTreeWidgetItem(QStringList(group.first));
|
||||||
for (auto hotkey : group.second)
|
for (auto hotkey : group.second) {
|
||||||
{
|
|
||||||
QStringList columns;
|
QStringList columns;
|
||||||
columns << hotkey.first << hotkey.second.keyseq.toString();
|
columns << hotkey.first << hotkey.second.keyseq.toString();
|
||||||
QTreeWidgetItem* item = new QTreeWidgetItem(columns);
|
QTreeWidgetItem* item = new QTreeWidgetItem(columns);
|
||||||
|
|
|
@ -16,36 +16,42 @@ class QShortcut;
|
||||||
*
|
*
|
||||||
* @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger")
|
* @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger")
|
||||||
* @param action Name of the action (e.g. "Start Emulation", "Load Image")
|
* @param action Name of the action (e.g. "Start Emulation", "Load Image")
|
||||||
* @param default_keyseq Default key sequence to assign if the hotkey wasn't present in the settings file before
|
* @param default_keyseq Default key sequence to assign if the hotkey wasn't present in the settings
|
||||||
* @param default_context Default context to assign if the hotkey wasn't present in the settings file before
|
* file before
|
||||||
|
* @param default_context Default context to assign if the hotkey wasn't present in the settings
|
||||||
|
* file before
|
||||||
* @warning Both the group and action strings will be displayed in the hotkey settings dialog
|
* @warning Both the group and action strings will be displayed in the hotkey settings dialog
|
||||||
*/
|
*/
|
||||||
void RegisterHotkey(const QString& group, const QString& action, const QKeySequence& default_keyseq = QKeySequence(), Qt::ShortcutContext default_context = Qt::WindowShortcut);
|
void RegisterHotkey(const QString& group, const QString& action,
|
||||||
|
const QKeySequence& default_keyseq = QKeySequence(),
|
||||||
|
Qt::ShortcutContext default_context = Qt::WindowShortcut);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a QShortcut object whose activated() signal can be connected to other QObjects' slots.
|
* Returns a QShortcut object whose activated() signal can be connected to other QObjects' slots.
|
||||||
*
|
*
|
||||||
* @param widget Parent widget of the returned QShortcut.
|
* @param widget Parent widget of the returned QShortcut.
|
||||||
* @warning If multiple QWidgets' call this function for the same action, the returned QShortcut will be the same. Thus, you shouldn't rely on the caller really being the QShortcut's parent.
|
* @warning If multiple QWidgets' call this function for the same action, the returned QShortcut
|
||||||
|
* will be the same. Thus, you shouldn't rely on the caller really being the QShortcut's parent.
|
||||||
*/
|
*/
|
||||||
QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
|
QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves all registered hotkeys to the settings file.
|
* Saves all registered hotkeys to the settings file.
|
||||||
*
|
*
|
||||||
* @note Each hotkey group will be stored a settings group; For each hotkey inside that group, a settings group will be created to store the key sequence and the hotkey context.
|
* @note Each hotkey group will be stored a settings group; For each hotkey inside that group, a
|
||||||
|
* settings group will be created to store the key sequence and the hotkey context.
|
||||||
*/
|
*/
|
||||||
void SaveHotkeys();
|
void SaveHotkeys();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads hotkeys from the settings file.
|
* Loads hotkeys from the settings file.
|
||||||
*
|
*
|
||||||
* @note Yet unregistered hotkeys which are present in the settings will automatically be registered.
|
* @note Yet unregistered hotkeys which are present in the settings will automatically be
|
||||||
|
* registered.
|
||||||
*/
|
*/
|
||||||
void LoadHotkeys();
|
void LoadHotkeys();
|
||||||
|
|
||||||
class GHotkeysDialog : public QWidget
|
class GHotkeysDialog : public QWidget {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
|
|
||||||
#define QT_NO_OPENGL
|
#define QT_NO_OPENGL
|
||||||
#include <QDesktopWidget>
|
#include <QDesktopWidget>
|
||||||
#include <QtGui>
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
#include <QtGui>
|
||||||
#include "qhexedit.h"
|
#include "qhexedit.h"
|
||||||
|
|
||||||
#include "citra_qt/bootmanager.h"
|
#include "citra_qt/bootmanager.h"
|
||||||
|
@ -36,27 +36,26 @@
|
||||||
#include "citra_qt/debugger/ramview.h"
|
#include "citra_qt/debugger/ramview.h"
|
||||||
#include "citra_qt/debugger/registers.h"
|
#include "citra_qt/debugger/registers.h"
|
||||||
|
|
||||||
|
#include "common/logging/backend.h"
|
||||||
|
#include "common/logging/filter.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/logging/text_formatter.h"
|
||||||
#include "common/microprofile.h"
|
#include "common/microprofile.h"
|
||||||
#include "common/platform.h"
|
#include "common/platform.h"
|
||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "common/logging/backend.h"
|
|
||||||
#include "common/logging/filter.h"
|
|
||||||
#include "common/logging/log.h"
|
|
||||||
#include "common/logging/text_formatter.h"
|
|
||||||
|
|
||||||
#include "core/core.h"
|
|
||||||
#include "core/settings.h"
|
|
||||||
#include "core/system.h"
|
|
||||||
#include "core/arm/disassembler/load_symbol_map.h"
|
#include "core/arm/disassembler/load_symbol_map.h"
|
||||||
|
#include "core/core.h"
|
||||||
#include "core/gdbstub/gdbstub.h"
|
#include "core/gdbstub/gdbstub.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
|
#include "core/settings.h"
|
||||||
|
#include "core/system.h"
|
||||||
|
|
||||||
#include "video_core/video_core.h"
|
#include "video_core/video_core.h"
|
||||||
|
|
||||||
GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
|
GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
|
||||||
{
|
|
||||||
Pica::g_debug_context = Pica::DebugContext::Construct();
|
Pica::g_debug_context = Pica::DebugContext::Construct();
|
||||||
|
|
||||||
ui.setupUi(this);
|
ui.setupUi(this);
|
||||||
|
@ -91,7 +90,7 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
|
||||||
|
|
||||||
graphicsWidget = new GPUCommandStreamWidget(this);
|
graphicsWidget = new GPUCommandStreamWidget(this);
|
||||||
addDockWidget(Qt::RightDockWidgetArea, graphicsWidget);
|
addDockWidget(Qt::RightDockWidgetArea, graphicsWidget);
|
||||||
graphicsWidget ->hide();
|
graphicsWidget->hide();
|
||||||
|
|
||||||
graphicsCommandsWidget = new GPUCommandListWidget(this);
|
graphicsCommandsWidget = new GPUCommandListWidget(this);
|
||||||
addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget);
|
addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget);
|
||||||
|
@ -110,7 +109,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
|
||||||
graphicsTracingWidget->hide();
|
graphicsTracingWidget->hide();
|
||||||
|
|
||||||
auto graphicsSurfaceViewerAction = new QAction(tr("Create Pica surface viewer"), this);
|
auto graphicsSurfaceViewerAction = new QAction(tr("Create Pica surface viewer"), this);
|
||||||
connect(graphicsSurfaceViewerAction, SIGNAL(triggered()), this, SLOT(OnCreateGraphicsSurfaceViewer()));
|
connect(graphicsSurfaceViewerAction, SIGNAL(triggered()), this,
|
||||||
|
SLOT(OnCreateGraphicsSurfaceViewer()));
|
||||||
|
|
||||||
QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging"));
|
QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging"));
|
||||||
debug_menu->addAction(graphicsSurfaceViewerAction);
|
debug_menu->addAction(graphicsSurfaceViewerAction);
|
||||||
|
@ -167,35 +167,44 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
|
||||||
UpdateRecentFiles();
|
UpdateRecentFiles();
|
||||||
|
|
||||||
// Setup connections
|
// Setup connections
|
||||||
connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)), Qt::DirectConnection);
|
connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)),
|
||||||
|
Qt::DirectConnection);
|
||||||
connect(ui.action_Configure, SIGNAL(triggered()), this, SLOT(OnConfigure()));
|
connect(ui.action_Configure, SIGNAL(triggered()), this, SLOT(OnConfigure()));
|
||||||
connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()),Qt::DirectConnection);
|
connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()),
|
||||||
|
Qt::DirectConnection);
|
||||||
connect(ui.action_Load_Symbol_Map, SIGNAL(triggered()), this, SLOT(OnMenuLoadSymbolMap()));
|
connect(ui.action_Load_Symbol_Map, SIGNAL(triggered()), this, SLOT(OnMenuLoadSymbolMap()));
|
||||||
connect(ui.action_Select_Game_List_Root, SIGNAL(triggered()), this, SLOT(OnMenuSelectGameListRoot()));
|
connect(ui.action_Select_Game_List_Root, SIGNAL(triggered()), this,
|
||||||
|
SLOT(OnMenuSelectGameListRoot()));
|
||||||
connect(ui.action_Start, SIGNAL(triggered()), this, SLOT(OnStartGame()));
|
connect(ui.action_Start, SIGNAL(triggered()), this, SLOT(OnStartGame()));
|
||||||
connect(ui.action_Pause, SIGNAL(triggered()), this, SLOT(OnPauseGame()));
|
connect(ui.action_Pause, SIGNAL(triggered()), this, SLOT(OnPauseGame()));
|
||||||
connect(ui.action_Stop, SIGNAL(triggered()), this, SLOT(OnStopGame()));
|
connect(ui.action_Stop, SIGNAL(triggered()), this, SLOT(OnStopGame()));
|
||||||
connect(ui.action_Single_Window_Mode, SIGNAL(triggered(bool)), this, SLOT(ToggleWindowMode()));
|
connect(ui.action_Single_Window_Mode, SIGNAL(triggered(bool)), this, SLOT(ToggleWindowMode()));
|
||||||
|
|
||||||
connect(this, SIGNAL(EmulationStarting(EmuThread*)), disasmWidget, SLOT(OnEmulationStarting(EmuThread*)));
|
connect(this, SIGNAL(EmulationStarting(EmuThread*)), disasmWidget,
|
||||||
|
SLOT(OnEmulationStarting(EmuThread*)));
|
||||||
connect(this, SIGNAL(EmulationStopping()), disasmWidget, SLOT(OnEmulationStopping()));
|
connect(this, SIGNAL(EmulationStopping()), disasmWidget, SLOT(OnEmulationStopping()));
|
||||||
connect(this, SIGNAL(EmulationStarting(EmuThread*)), registersWidget, SLOT(OnEmulationStarting(EmuThread*)));
|
connect(this, SIGNAL(EmulationStarting(EmuThread*)), registersWidget,
|
||||||
|
SLOT(OnEmulationStarting(EmuThread*)));
|
||||||
connect(this, SIGNAL(EmulationStopping()), registersWidget, SLOT(OnEmulationStopping()));
|
connect(this, SIGNAL(EmulationStopping()), registersWidget, SLOT(OnEmulationStopping()));
|
||||||
connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window, SLOT(OnEmulationStarting(EmuThread*)));
|
connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window,
|
||||||
|
SLOT(OnEmulationStarting(EmuThread*)));
|
||||||
connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping()));
|
connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping()));
|
||||||
connect(this, SIGNAL(EmulationStarting(EmuThread*)), graphicsTracingWidget, SLOT(OnEmulationStarting(EmuThread*)));
|
connect(this, SIGNAL(EmulationStarting(EmuThread*)), graphicsTracingWidget,
|
||||||
|
SLOT(OnEmulationStarting(EmuThread*)));
|
||||||
connect(this, SIGNAL(EmulationStopping()), graphicsTracingWidget, SLOT(OnEmulationStopping()));
|
connect(this, SIGNAL(EmulationStopping()), graphicsTracingWidget, SLOT(OnEmulationStopping()));
|
||||||
|
|
||||||
|
|
||||||
// Setup hotkeys
|
// Setup hotkeys
|
||||||
RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
|
RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
|
||||||
RegisterHotkey("Main Window", "Start Emulation");
|
RegisterHotkey("Main Window", "Start Emulation");
|
||||||
LoadHotkeys();
|
LoadHotkeys();
|
||||||
|
|
||||||
connect(GetHotkey("Main Window", "Load File", this), SIGNAL(activated()), this, SLOT(OnMenuLoadFile()));
|
connect(GetHotkey("Main Window", "Load File", this), SIGNAL(activated()), this,
|
||||||
connect(GetHotkey("Main Window", "Start Emulation", this), SIGNAL(activated()), this, SLOT(OnStartGame()));
|
SLOT(OnMenuLoadFile()));
|
||||||
|
connect(GetHotkey("Main Window", "Start Emulation", this), SIGNAL(activated()), this,
|
||||||
|
SLOT(OnStartGame()));
|
||||||
|
|
||||||
std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc);
|
std::string window_title =
|
||||||
|
Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc);
|
||||||
setWindowTitle(window_title.c_str());
|
setWindowTitle(window_title.c_str());
|
||||||
|
|
||||||
show();
|
show();
|
||||||
|
@ -208,8 +217,7 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GMainWindow::~GMainWindow()
|
GMainWindow::~GMainWindow() {
|
||||||
{
|
|
||||||
// will get automatically deleted otherwise
|
// will get automatically deleted otherwise
|
||||||
if (render_window->parent() == nullptr)
|
if (render_window->parent() == nullptr)
|
||||||
delete render_window;
|
delete render_window;
|
||||||
|
@ -217,19 +225,18 @@ GMainWindow::~GMainWindow()
|
||||||
Pica::g_debug_context.reset();
|
Pica::g_debug_context.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnDisplayTitleBars(bool show)
|
void GMainWindow::OnDisplayTitleBars(bool show) {
|
||||||
{
|
|
||||||
QList<QDockWidget*> widgets = findChildren<QDockWidget*>();
|
QList<QDockWidget*> widgets = findChildren<QDockWidget*>();
|
||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
for (QDockWidget* widget: widgets) {
|
for (QDockWidget* widget : widgets) {
|
||||||
QWidget* old = widget->titleBarWidget();
|
QWidget* old = widget->titleBarWidget();
|
||||||
widget->setTitleBarWidget(nullptr);
|
widget->setTitleBarWidget(nullptr);
|
||||||
if (old != nullptr)
|
if (old != nullptr)
|
||||||
delete old;
|
delete old;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (QDockWidget* widget: widgets) {
|
for (QDockWidget* widget : widgets) {
|
||||||
QWidget* old = widget->titleBarWidget();
|
QWidget* old = widget->titleBarWidget();
|
||||||
widget->setTitleBarWidget(new QWidget());
|
widget->setTitleBarWidget(new QWidget());
|
||||||
if (old != nullptr)
|
if (old != nullptr)
|
||||||
|
@ -249,7 +256,8 @@ bool GMainWindow::InitializeSystem() {
|
||||||
if (!gladLoadGL()) {
|
if (!gladLoadGL()) {
|
||||||
QMessageBox::critical(this, tr("Error while starting Citra!"),
|
QMessageBox::critical(this, tr("Error while starting Citra!"),
|
||||||
tr("Failed to initialize the video core!\n\n"
|
tr("Failed to initialize the video core!\n\n"
|
||||||
"Please ensure that your GPU supports OpenGL 3.3 and that you have the latest graphics driver."));
|
"Please ensure that your GPU supports OpenGL 3.3 and that you "
|
||||||
|
"have the latest graphics driver."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +268,8 @@ bool GMainWindow::InitializeSystem() {
|
||||||
case System::Result::ErrorInitVideoCore:
|
case System::Result::ErrorInitVideoCore:
|
||||||
QMessageBox::critical(this, tr("Error while starting Citra!"),
|
QMessageBox::critical(this, tr("Error while starting Citra!"),
|
||||||
tr("Failed to initialize the video core!\n\n"
|
tr("Failed to initialize the video core!\n\n"
|
||||||
"Please ensure that your GPU supports OpenGL 3.3 and that you have the latest graphics driver."));
|
"Please ensure that your GPU supports OpenGL 3.3 and that you "
|
||||||
|
"have the latest graphics driver."));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -293,8 +302,12 @@ bool GMainWindow::LoadROM(const std::string& filename) {
|
||||||
QMessageBox popup_error;
|
QMessageBox popup_error;
|
||||||
popup_error.setTextFormat(Qt::RichText);
|
popup_error.setTextFormat(Qt::RichText);
|
||||||
popup_error.setWindowTitle(tr("Error while loading ROM!"));
|
popup_error.setWindowTitle(tr("Error while loading ROM!"));
|
||||||
popup_error.setText(tr("The game that you are trying to load must be decrypted before being used with Citra.<br/><br/>"
|
popup_error.setText(
|
||||||
"For more information on dumping and decrypting games, please see: <a href='https://citra-emu.org/wiki/Dumping-Game-Cartridges'>https://citra-emu.org/wiki/Dumping-Game-Cartridges</a>"));
|
tr("The game that you are trying to load must be decrypted before being used with "
|
||||||
|
"Citra.<br/><br/>"
|
||||||
|
"For more information on dumping and decrypting games, please see: <a "
|
||||||
|
"href='https://citra-emu.org/wiki/Dumping-Game-Cartridges'>https://"
|
||||||
|
"citra-emu.org/wiki/Dumping-Game-Cartridges</a>"));
|
||||||
popup_error.setIcon(QMessageBox::Critical);
|
popup_error.setIcon(QMessageBox::Critical);
|
||||||
popup_error.exec();
|
popup_error.exec();
|
||||||
break;
|
break;
|
||||||
|
@ -306,8 +319,7 @@ bool GMainWindow::LoadROM(const std::string& filename) {
|
||||||
case Loader::ResultStatus::Error:
|
case Loader::ResultStatus::Error:
|
||||||
|
|
||||||
default:
|
default:
|
||||||
QMessageBox::critical(this, tr("Error while loading ROM!"),
|
QMessageBox::critical(this, tr("Error while loading ROM!"), tr("Unknown error!"));
|
||||||
tr("Unknown error!"));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -332,13 +344,20 @@ void GMainWindow::BootGame(const std::string& filename) {
|
||||||
emu_thread->start();
|
emu_thread->start();
|
||||||
|
|
||||||
connect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame()));
|
connect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame()));
|
||||||
// BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views before the CPU continues
|
// BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views
|
||||||
connect(emu_thread.get(), SIGNAL(DebugModeEntered()), disasmWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection);
|
// before the CPU continues
|
||||||
connect(emu_thread.get(), SIGNAL(DebugModeEntered()), registersWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection);
|
connect(emu_thread.get(), SIGNAL(DebugModeEntered()), disasmWidget, SLOT(OnDebugModeEntered()),
|
||||||
connect(emu_thread.get(), SIGNAL(DebugModeEntered()), callstackWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection);
|
Qt::BlockingQueuedConnection);
|
||||||
connect(emu_thread.get(), SIGNAL(DebugModeLeft()), disasmWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection);
|
connect(emu_thread.get(), SIGNAL(DebugModeEntered()), registersWidget,
|
||||||
connect(emu_thread.get(), SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection);
|
SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection);
|
||||||
connect(emu_thread.get(), SIGNAL(DebugModeLeft()), callstackWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection);
|
connect(emu_thread.get(), SIGNAL(DebugModeEntered()), callstackWidget,
|
||||||
|
SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection);
|
||||||
|
connect(emu_thread.get(), SIGNAL(DebugModeLeft()), disasmWidget, SLOT(OnDebugModeLeft()),
|
||||||
|
Qt::BlockingQueuedConnection);
|
||||||
|
connect(emu_thread.get(), SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft()),
|
||||||
|
Qt::BlockingQueuedConnection);
|
||||||
|
connect(emu_thread.get(), SIGNAL(DebugModeLeft()), callstackWidget, SLOT(OnDebugModeLeft()),
|
||||||
|
Qt::BlockingQueuedConnection);
|
||||||
|
|
||||||
// Update the GUI
|
// Update the GUI
|
||||||
registersWidget->OnDebugModeEntered();
|
registersWidget->OnDebugModeEntered();
|
||||||
|
@ -393,10 +412,12 @@ void GMainWindow::StoreRecentFile(const std::string& filename) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::UpdateRecentFiles() {
|
void GMainWindow::UpdateRecentFiles() {
|
||||||
unsigned int num_recent_files = std::min(UISettings::values.recent_files.size(), static_cast<int>(max_recent_files_item));
|
unsigned int num_recent_files =
|
||||||
|
std::min(UISettings::values.recent_files.size(), static_cast<int>(max_recent_files_item));
|
||||||
|
|
||||||
for (unsigned int i = 0; i < num_recent_files; i++) {
|
for (unsigned int i = 0; i < num_recent_files; i++) {
|
||||||
QString text = QString("&%1. %2").arg(i + 1).arg(QFileInfo(UISettings::values.recent_files[i]).fileName());
|
QString text = QString("&%1. %2").arg(i + 1).arg(
|
||||||
|
QFileInfo(UISettings::values.recent_files[i]).fileName());
|
||||||
actions_recent_files[i]->setText(text);
|
actions_recent_files[i]->setText(text);
|
||||||
actions_recent_files[i]->setData(UISettings::values.recent_files[i]);
|
actions_recent_files[i]->setData(UISettings::values.recent_files[i]);
|
||||||
actions_recent_files[i]->setToolTip(UISettings::values.recent_files[i]);
|
actions_recent_files[i]->setToolTip(UISettings::values.recent_files[i]);
|
||||||
|
@ -420,7 +441,9 @@ void GMainWindow::OnGameListLoadFile(QString game_path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnMenuLoadFile() {
|
void GMainWindow::OnMenuLoadFile() {
|
||||||
QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), UISettings::values.roms_path, tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.cci *.cxi)"));
|
QString filename =
|
||||||
|
QFileDialog::getOpenFileName(this, tr("Load File"), UISettings::values.roms_path,
|
||||||
|
tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.cci *.cxi)"));
|
||||||
if (!filename.isEmpty()) {
|
if (!filename.isEmpty()) {
|
||||||
UISettings::values.roms_path = QFileInfo(filename).path();
|
UISettings::values.roms_path = QFileInfo(filename).path();
|
||||||
|
|
||||||
|
@ -429,7 +452,8 @@ void GMainWindow::OnMenuLoadFile() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnMenuLoadSymbolMap() {
|
void GMainWindow::OnMenuLoadSymbolMap() {
|
||||||
QString filename = QFileDialog::getOpenFileName(this, tr("Load Symbol Map"), UISettings::values.symbols_path, tr("Symbol map (*)"));
|
QString filename = QFileDialog::getOpenFileName(
|
||||||
|
this, tr("Load Symbol Map"), UISettings::values.symbols_path, tr("Symbol map (*)"));
|
||||||
if (!filename.isEmpty()) {
|
if (!filename.isEmpty()) {
|
||||||
UISettings::values.symbols_path = QFileInfo(filename).path();
|
UISettings::values.symbols_path = QFileInfo(filename).path();
|
||||||
|
|
||||||
|
@ -455,7 +479,8 @@ void GMainWindow::OnMenuRecentFile() {
|
||||||
BootGame(filename.toStdString());
|
BootGame(filename.toStdString());
|
||||||
} else {
|
} else {
|
||||||
// Display an error message and remove the file from the list.
|
// Display an error message and remove the file from the list.
|
||||||
QMessageBox::information(this, tr("File not found"), tr("File \"%1\" not found").arg(filename));
|
QMessageBox::information(this, tr("File not found"),
|
||||||
|
tr("File \"%1\" not found").arg(filename));
|
||||||
|
|
||||||
UISettings::values.recent_files.removeOne(filename);
|
UISettings::values.recent_files.removeOne(filename);
|
||||||
UpdateRecentFiles();
|
UpdateRecentFiles();
|
||||||
|
@ -512,8 +537,7 @@ void GMainWindow::ToggleWindowMode() {
|
||||||
void GMainWindow::OnConfigure() {
|
void GMainWindow::OnConfigure() {
|
||||||
ConfigureDialog configureDialog(this);
|
ConfigureDialog configureDialog(this);
|
||||||
auto result = configureDialog.exec();
|
auto result = configureDialog.exec();
|
||||||
if (result == QDialog::Accepted)
|
if (result == QDialog::Accepted) {
|
||||||
{
|
|
||||||
configureDialog.applyConfiguration();
|
configureDialog.applyConfiguration();
|
||||||
render_window->ReloadSetKeymaps();
|
render_window->ReloadSetKeymaps();
|
||||||
config->Save();
|
config->Save();
|
||||||
|
@ -531,8 +555,8 @@ bool GMainWindow::ConfirmClose() {
|
||||||
if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
|
if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
auto answer = QMessageBox::question(this, tr("Citra"),
|
auto answer =
|
||||||
tr("Are you sure you want to close Citra?"),
|
QMessageBox::question(this, tr("Citra"), tr("Are you sure you want to close Citra?"),
|
||||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||||
return answer != QMessageBox::No;
|
return answer != QMessageBox::No;
|
||||||
}
|
}
|
||||||
|
@ -575,9 +599,7 @@ int main(int argc, char* argv[]) {
|
||||||
Log::SetFilter(&log_filter);
|
Log::SetFilter(&log_filter);
|
||||||
|
|
||||||
MicroProfileOnThreadCreate("Frontend");
|
MicroProfileOnThreadCreate("Frontend");
|
||||||
SCOPE_EXIT({
|
SCOPE_EXIT({ MicroProfileShutdown(); });
|
||||||
MicroProfileShutdown();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Init settings params
|
// Init settings params
|
||||||
QCoreApplication::setOrganizationName("Citra team");
|
QCoreApplication::setOrganizationName("Citra team");
|
||||||
|
@ -586,7 +608,8 @@ int main(int argc, char* argv[]) {
|
||||||
QApplication::setAttribute(Qt::AA_X11InitThreads);
|
QApplication::setAttribute(Qt::AA_X11InitThreads);
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
// Qt changes the locale and causes issues in float conversion using std::to_string() when generating shaders
|
// Qt changes the locale and causes issues in float conversion using std::to_string() when
|
||||||
|
// generating shaders
|
||||||
setlocale(LC_ALL, "C");
|
setlocale(LC_ALL, "C");
|
||||||
|
|
||||||
GMainWindow main_window;
|
GMainWindow main_window;
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
#ifndef _CITRA_QT_MAIN_HXX_
|
#ifndef _CITRA_QT_MAIN_HXX_
|
||||||
#define _CITRA_QT_MAIN_HXX_
|
#define _CITRA_QT_MAIN_HXX_
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "ui_main.h"
|
#include "ui_main.h"
|
||||||
|
|
||||||
|
@ -23,11 +23,11 @@ class CallstackWidget;
|
||||||
class GPUCommandStreamWidget;
|
class GPUCommandStreamWidget;
|
||||||
class GPUCommandListWidget;
|
class GPUCommandListWidget;
|
||||||
|
|
||||||
class GMainWindow : public QMainWindow
|
class GMainWindow : public QMainWindow {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
static const int max_recent_files_item = 10; ///< Max number of recently loaded items to keep track
|
static const int max_recent_files_item =
|
||||||
|
10; ///< Max number of recently loaded items to keep track
|
||||||
|
|
||||||
// TODO: Make use of this!
|
// TODO: Make use of this!
|
||||||
enum {
|
enum {
|
||||||
|
|
|
@ -7,5 +7,4 @@
|
||||||
namespace UISettings {
|
namespace UISettings {
|
||||||
|
|
||||||
Values values = {};
|
Values values = {};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QStringList>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace UISettings {
|
namespace UISettings {
|
||||||
|
|
||||||
using ContextualShortcut = std::pair<QString, int> ;
|
using ContextualShortcut = std::pair<QString, int>;
|
||||||
using Shortcut = std::pair<QString, ContextualShortcut>;
|
using Shortcut = std::pair<QString, ContextualShortcut>;
|
||||||
|
|
||||||
struct Values {
|
struct Values {
|
||||||
|
@ -43,5 +43,4 @@ struct Values {
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Values values;
|
extern Values values;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
|
||||||
// Copyright 2014 Tony Wasserka
|
// Copyright 2014 Tony Wasserka
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
|
@ -29,15 +28,15 @@
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QRegExpValidator>
|
#include <QRegExpValidator>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
#include "citra_qt/util/spinbox.h"
|
#include "citra_qt/util/spinbox.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
|
||||||
CSpinBox::CSpinBox(QWidget* parent) : QAbstractSpinBox(parent), min_value(-100), max_value(100), value(0), base(10), num_digits(0)
|
CSpinBox::CSpinBox(QWidget* parent)
|
||||||
{
|
: QAbstractSpinBox(parent), min_value(-100), max_value(100), value(0), base(10), num_digits(0) {
|
||||||
// TODO: Might be nice to not immediately call the slot.
|
// 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
|
// 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.
|
// invalid intermediate addresses would be read from during editing.
|
||||||
|
@ -46,8 +45,7 @@ CSpinBox::CSpinBox(QWidget* parent) : QAbstractSpinBox(parent), min_value(-100),
|
||||||
UpdateText();
|
UpdateText();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSpinBox::SetValue(qint64 val)
|
void CSpinBox::SetValue(qint64 val) {
|
||||||
{
|
|
||||||
auto old_value = value;
|
auto old_value = value;
|
||||||
value = std::max(std::min(val, max_value), min_value);
|
value = std::max(std::min(val, max_value), min_value);
|
||||||
|
|
||||||
|
@ -57,8 +55,7 @@ void CSpinBox::SetValue(qint64 val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSpinBox::SetRange(qint64 min, qint64 max)
|
void CSpinBox::SetRange(qint64 min, qint64 max) {
|
||||||
{
|
|
||||||
min_value = min;
|
min_value = min;
|
||||||
max_value = max;
|
max_value = max;
|
||||||
|
|
||||||
|
@ -66,8 +63,7 @@ void CSpinBox::SetRange(qint64 min, qint64 max)
|
||||||
UpdateText();
|
UpdateText();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSpinBox::stepBy(int steps)
|
void CSpinBox::stepBy(int steps) {
|
||||||
{
|
|
||||||
auto new_value = value;
|
auto new_value = value;
|
||||||
// Scale number of steps by the currently selected digit
|
// Scale number of steps by the currently selected digit
|
||||||
// TODO: Move this code elsewhere and enable it.
|
// TODO: Move this code elsewhere and enable it.
|
||||||
|
@ -93,8 +89,7 @@ void CSpinBox::stepBy(int steps)
|
||||||
UpdateText();
|
UpdateText();
|
||||||
}
|
}
|
||||||
|
|
||||||
QAbstractSpinBox::StepEnabled CSpinBox::stepEnabled() const
|
QAbstractSpinBox::StepEnabled CSpinBox::stepEnabled() const {
|
||||||
{
|
|
||||||
StepEnabled ret = StepNone;
|
StepEnabled ret = StepNone;
|
||||||
|
|
||||||
if (value > min_value)
|
if (value > min_value)
|
||||||
|
@ -106,29 +101,25 @@ QAbstractSpinBox::StepEnabled CSpinBox::stepEnabled() const
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSpinBox::SetBase(int base)
|
void CSpinBox::SetBase(int base) {
|
||||||
{
|
|
||||||
this->base = base;
|
this->base = base;
|
||||||
|
|
||||||
UpdateText();
|
UpdateText();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSpinBox::SetNumDigits(int num_digits)
|
void CSpinBox::SetNumDigits(int num_digits) {
|
||||||
{
|
|
||||||
this->num_digits = num_digits;
|
this->num_digits = num_digits;
|
||||||
|
|
||||||
UpdateText();
|
UpdateText();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSpinBox::SetPrefix(const QString& prefix)
|
void CSpinBox::SetPrefix(const QString& prefix) {
|
||||||
{
|
|
||||||
this->prefix = prefix;
|
this->prefix = prefix;
|
||||||
|
|
||||||
UpdateText();
|
UpdateText();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSpinBox::SetSuffix(const QString& suffix)
|
void CSpinBox::SetSuffix(const QString& suffix) {
|
||||||
{
|
|
||||||
this->suffix = suffix;
|
this->suffix = suffix;
|
||||||
|
|
||||||
UpdateText();
|
UpdateText();
|
||||||
|
@ -161,8 +152,7 @@ static QString StringToInputMask(const QString& input) {
|
||||||
return mask;
|
return mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSpinBox::UpdateText()
|
void CSpinBox::UpdateText() {
|
||||||
{
|
|
||||||
// If a fixed number of digits is used, we put the line edit in insertion mode by setting an
|
// If a fixed number of digits is used, we put the line edit in insertion mode by setting an
|
||||||
// input mask.
|
// input mask.
|
||||||
QString mask;
|
QString mask;
|
||||||
|
@ -179,10 +169,9 @@ void CSpinBox::UpdateText()
|
||||||
// The greatest signed 64-bit number has 19 decimal digits.
|
// The greatest signed 64-bit number has 19 decimal digits.
|
||||||
// TODO: Could probably make this more generic with some logarithms.
|
// TODO: Could probably make this more generic with some logarithms.
|
||||||
// For reference, unsigned 64-bit can have up to 20 decimal digits.
|
// For reference, unsigned 64-bit can have up to 20 decimal digits.
|
||||||
int digits = (num_digits != 0) ? num_digits
|
int digits = (num_digits != 0)
|
||||||
: (base == 16) ? 16
|
? num_digits
|
||||||
: (base == 10) ? 19
|
: (base == 16) ? 16 : (base == 10) ? 19 : 0xFF; // fallback case...
|
||||||
: 0xFF; // fallback case...
|
|
||||||
|
|
||||||
// Match num_digits digits
|
// Match num_digits digits
|
||||||
// Digits irrelevant to the chosen number base are filtered in the validator
|
// Digits irrelevant to the chosen number base are filtered in the validator
|
||||||
|
@ -203,29 +192,24 @@ void CSpinBox::UpdateText()
|
||||||
lineEdit()->setCursorPosition(cursor_position);
|
lineEdit()->setCursorPosition(cursor_position);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CSpinBox::TextFromValue()
|
QString CSpinBox::TextFromValue() {
|
||||||
{
|
return prefix + QString(HasSign() ? ((value < 0) ? "-" : "+") : "") +
|
||||||
return prefix
|
QString("%1").arg(std::abs(value), num_digits, base, QLatin1Char('0')).toUpper() +
|
||||||
+ QString(HasSign() ? ((value < 0) ? "-" : "+") : "")
|
suffix;
|
||||||
+ QString("%1").arg(std::abs(value), num_digits, base, QLatin1Char('0')).toUpper()
|
|
||||||
+ suffix;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 CSpinBox::ValueFromText()
|
qint64 CSpinBox::ValueFromText() {
|
||||||
{
|
|
||||||
unsigned strpos = prefix.length();
|
unsigned strpos = prefix.length();
|
||||||
|
|
||||||
QString num_string = text().mid(strpos, text().length() - strpos - suffix.length());
|
QString num_string = text().mid(strpos, text().length() - strpos - suffix.length());
|
||||||
return num_string.toLongLong(nullptr, base);
|
return num_string.toLongLong(nullptr, base);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CSpinBox::HasSign() const
|
bool CSpinBox::HasSign() const {
|
||||||
{
|
|
||||||
return base == 10 && min_value < 0;
|
return base == 10 && min_value < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSpinBox::OnEditingFinished()
|
void CSpinBox::OnEditingFinished() {
|
||||||
{
|
|
||||||
// Only update for valid input
|
// Only update for valid input
|
||||||
QString input = lineEdit()->text();
|
QString input = lineEdit()->text();
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
|
@ -233,8 +217,7 @@ void CSpinBox::OnEditingFinished()
|
||||||
SetValue(ValueFromText());
|
SetValue(ValueFromText());
|
||||||
}
|
}
|
||||||
|
|
||||||
QValidator::State CSpinBox::validate(QString& input, int& pos) const
|
QValidator::State CSpinBox::validate(QString& input, int& pos) const {
|
||||||
{
|
|
||||||
if (!prefix.isEmpty() && input.left(prefix.length()) != prefix)
|
if (!prefix.isEmpty() && input.left(prefix.length()) != prefix)
|
||||||
return QValidator::Invalid;
|
return QValidator::Invalid;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
|
||||||
// Copyright 2014 Tony Wasserka
|
// Copyright 2014 Tony Wasserka
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
|
@ -29,7 +28,6 @@
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QAbstractSpinBox>
|
#include <QAbstractSpinBox>
|
||||||
|
|
|
@ -16,10 +16,12 @@ QFont GetMonospaceFont() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ReadableByteSize(qulonglong size) {
|
QString ReadableByteSize(qulonglong size) {
|
||||||
static const std::array<const char*, 6> units = { "B", "KiB", "MiB", "GiB", "TiB", "PiB" };
|
static const std::array<const char*, 6> units = {"B", "KiB", "MiB", "GiB", "TiB", "PiB"};
|
||||||
if (size == 0)
|
if (size == 0)
|
||||||
return "0";
|
return "0";
|
||||||
int digit_groups = std::min<int>(static_cast<int>(std::log10(size) / std::log10(1024)), static_cast<int>(units.size()));
|
int digit_groups = std::min<int>(static_cast<int>(std::log10(size) / std::log10(1024)),
|
||||||
return QString("%L1 %2").arg(size / std::pow(1024, digit_groups), 0, 'f', 1)
|
static_cast<int>(units.size()));
|
||||||
|
return QString("%L1 %2")
|
||||||
|
.arg(size / std::pow(1024, digit_groups), 0, 'f', 1)
|
||||||
.arg(units[digit_groups]);
|
.arg(units[digit_groups]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,25 +18,29 @@
|
||||||
// enough for our purposes.
|
// enough for our purposes.
|
||||||
template <typename Fn>
|
template <typename Fn>
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
__declspec(noinline, noreturn)
|
__declspec(noinline, noreturn)
|
||||||
#elif defined(__GNUC__)
|
#elif defined(__GNUC__)
|
||||||
__attribute__((noinline, noreturn, cold))
|
__attribute__((noinline, noreturn, cold))
|
||||||
#endif
|
#endif
|
||||||
static void assert_noinline_call(const Fn& fn) {
|
static void assert_noinline_call(const Fn& fn) {
|
||||||
fn();
|
fn();
|
||||||
Crash();
|
Crash();
|
||||||
exit(1); // Keeps GCC's mouth shut about this actually returning
|
exit(1); // Keeps GCC's mouth shut about this actually returning
|
||||||
}
|
}
|
||||||
|
|
||||||
#define ASSERT(_a_) \
|
#define ASSERT(_a_) \
|
||||||
do if (!(_a_)) { assert_noinline_call([] { \
|
do \
|
||||||
LOG_CRITICAL(Debug, "Assertion Failed!"); \
|
if (!(_a_)) { \
|
||||||
}); } while (0)
|
assert_noinline_call([] { LOG_CRITICAL(Debug, "Assertion Failed!"); }); \
|
||||||
|
} \
|
||||||
|
while (0)
|
||||||
|
|
||||||
#define ASSERT_MSG(_a_, ...) \
|
#define ASSERT_MSG(_a_, ...) \
|
||||||
do if (!(_a_)) { assert_noinline_call([&] { \
|
do \
|
||||||
LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \
|
if (!(_a_)) { \
|
||||||
}); } while (0)
|
assert_noinline_call([&] { LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); }); \
|
||||||
|
} \
|
||||||
|
while (0)
|
||||||
|
|
||||||
#define UNREACHABLE() ASSERT_MSG(false, "Unreachable code!")
|
#define UNREACHABLE() ASSERT_MSG(false, "Unreachable code!")
|
||||||
#define UNREACHABLE_MSG(...) ASSERT_MSG(false, __VA_ARGS__)
|
#define UNREACHABLE_MSG(...) ASSERT_MSG(false, __VA_ARGS__)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
|
||||||
// Copyright 2014 Tony Wasserka
|
// Copyright 2014 Tony Wasserka
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
|
@ -29,7 +28,6 @@
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
@ -111,9 +109,8 @@
|
||||||
* symptoms.
|
* symptoms.
|
||||||
*/
|
*/
|
||||||
#pragma pack(1)
|
#pragma pack(1)
|
||||||
template<std::size_t position, std::size_t bits, typename T>
|
template <std::size_t position, std::size_t bits, typename T>
|
||||||
struct BitField
|
struct BitField {
|
||||||
{
|
|
||||||
private:
|
private:
|
||||||
// We hide the copy assigment operator here, because the default copy
|
// We hide the copy assigment operator here, because the default copy
|
||||||
// assignment would copy the full storage value, rather than just the bits
|
// assignment would copy the full storage value, rather than just the bits
|
||||||
|
@ -141,13 +138,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
FORCE_INLINE T Value() const {
|
FORCE_INLINE T Value() const {
|
||||||
if (std::numeric_limits<T>::is_signed)
|
if (std::numeric_limits<T>::is_signed) {
|
||||||
{
|
std::size_t shift = 8 * sizeof(T) - bits;
|
||||||
std::size_t shift = 8 * sizeof(T)-bits;
|
|
||||||
return (T)((storage << (shift - position)) >> shift);
|
return (T)((storage << (shift - position)) >> shift);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return (T)((storage & GetMask()) >> position);
|
return (T)((storage & GetMask()) >> position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,15 +156,14 @@ private:
|
||||||
// T is an enumeration. Note that T is wrapped within an enable_if in the
|
// T is an enumeration. Note that T is wrapped within an enable_if in the
|
||||||
// former case to workaround compile errors which arise when using
|
// former case to workaround compile errors which arise when using
|
||||||
// std::underlying_type<T>::type directly.
|
// std::underlying_type<T>::type directly.
|
||||||
typedef typename std::conditional < std::is_enum<T>::value,
|
typedef typename std::conditional<std::is_enum<T>::value, std::underlying_type<T>,
|
||||||
std::underlying_type<T>,
|
std::enable_if<true, T>>::type::type StorageType;
|
||||||
std::enable_if < true, T >> ::type::type StorageType;
|
|
||||||
|
|
||||||
// Unsigned version of StorageType
|
// Unsigned version of StorageType
|
||||||
typedef typename std::make_unsigned<StorageType>::type StorageTypeU;
|
typedef typename std::make_unsigned<StorageType>::type StorageTypeU;
|
||||||
|
|
||||||
FORCE_INLINE StorageType GetMask() const {
|
FORCE_INLINE StorageType GetMask() const {
|
||||||
return (((StorageTypeU)~0) >> (8 * sizeof(T)-bits)) << position;
|
return (((StorageTypeU)~0) >> (8 * sizeof(T) - bits)) << position;
|
||||||
}
|
}
|
||||||
|
|
||||||
StorageType storage;
|
StorageType storage;
|
||||||
|
@ -186,5 +179,6 @@ private:
|
||||||
#pragma pack()
|
#pragma pack()
|
||||||
|
|
||||||
#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
|
#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
|
||||||
static_assert(std::is_trivially_copyable<BitField<0, 1, unsigned>>::value, "BitField must be trivially copyable");
|
static_assert(std::is_trivially_copyable<BitField<0, 1, unsigned>>::value,
|
||||||
|
"BitField must be trivially copyable");
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -18,49 +18,60 @@ namespace Common {
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static inline int CountSetBits(T v)
|
static inline int CountSetBits(T v) {
|
||||||
{
|
|
||||||
// from https://graphics.stanford.edu/~seander/bithacks.html
|
// from https://graphics.stanford.edu/~seander/bithacks.html
|
||||||
// GCC has this built in, but MSVC's intrinsic will only emit the actual
|
// GCC has this built in, but MSVC's intrinsic will only emit the actual
|
||||||
// POPCNT instruction, which we're not depending on
|
// POPCNT instruction, which we're not depending on
|
||||||
v = v - ((v >> 1) & (T)~(T)0/3);
|
v = v - ((v >> 1) & (T) ~(T)0 / 3);
|
||||||
v = (v & (T)~(T)0/15*3) + ((v >> 2) & (T)~(T)0/15*3);
|
v = (v & (T) ~(T)0 / 15 * 3) + ((v >> 2) & (T) ~(T)0 / 15 * 3);
|
||||||
v = (v + (v >> 4)) & (T)~(T)0/255*15;
|
v = (v + (v >> 4)) & (T) ~(T)0 / 255 * 15;
|
||||||
return (T)(v * ((T)~(T)0/255)) >> (sizeof(T) - 1) * 8;
|
return (T)(v * ((T) ~(T)0 / 255)) >> (sizeof(T) - 1) * 8;
|
||||||
}
|
}
|
||||||
static inline int LeastSignificantSetBit(u8 val)
|
static inline int LeastSignificantSetBit(u8 val) {
|
||||||
{
|
|
||||||
unsigned long index;
|
unsigned long index;
|
||||||
_BitScanForward(&index, val);
|
_BitScanForward(&index, val);
|
||||||
return (int)index;
|
return (int)index;
|
||||||
}
|
}
|
||||||
static inline int LeastSignificantSetBit(u16 val)
|
static inline int LeastSignificantSetBit(u16 val) {
|
||||||
{
|
|
||||||
unsigned long index;
|
unsigned long index;
|
||||||
_BitScanForward(&index, val);
|
_BitScanForward(&index, val);
|
||||||
return (int)index;
|
return (int)index;
|
||||||
}
|
}
|
||||||
static inline int LeastSignificantSetBit(u32 val)
|
static inline int LeastSignificantSetBit(u32 val) {
|
||||||
{
|
|
||||||
unsigned long index;
|
unsigned long index;
|
||||||
_BitScanForward(&index, val);
|
_BitScanForward(&index, val);
|
||||||
return (int)index;
|
return (int)index;
|
||||||
}
|
}
|
||||||
static inline int LeastSignificantSetBit(u64 val)
|
static inline int LeastSignificantSetBit(u64 val) {
|
||||||
{
|
|
||||||
unsigned long index;
|
unsigned long index;
|
||||||
_BitScanForward64(&index, val);
|
_BitScanForward64(&index, val);
|
||||||
return (int)index;
|
return (int)index;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
static inline int CountSetBits(u8 val) { return __builtin_popcount(val); }
|
static inline int CountSetBits(u8 val) {
|
||||||
static inline int CountSetBits(u16 val) { return __builtin_popcount(val); }
|
return __builtin_popcount(val);
|
||||||
static inline int CountSetBits(u32 val) { return __builtin_popcount(val); }
|
}
|
||||||
static inline int CountSetBits(u64 val) { return __builtin_popcountll(val); }
|
static inline int CountSetBits(u16 val) {
|
||||||
static inline int LeastSignificantSetBit(u8 val) { return __builtin_ctz(val); }
|
return __builtin_popcount(val);
|
||||||
static inline int LeastSignificantSetBit(u16 val) { return __builtin_ctz(val); }
|
}
|
||||||
static inline int LeastSignificantSetBit(u32 val) { return __builtin_ctz(val); }
|
static inline int CountSetBits(u32 val) {
|
||||||
static inline int LeastSignificantSetBit(u64 val) { return __builtin_ctzll(val); }
|
return __builtin_popcount(val);
|
||||||
|
}
|
||||||
|
static inline int CountSetBits(u64 val) {
|
||||||
|
return __builtin_popcountll(val);
|
||||||
|
}
|
||||||
|
static inline int LeastSignificantSetBit(u8 val) {
|
||||||
|
return __builtin_ctz(val);
|
||||||
|
}
|
||||||
|
static inline int LeastSignificantSetBit(u16 val) {
|
||||||
|
return __builtin_ctz(val);
|
||||||
|
}
|
||||||
|
static inline int LeastSignificantSetBit(u32 val) {
|
||||||
|
return __builtin_ctz(val);
|
||||||
|
}
|
||||||
|
static inline int LeastSignificantSetBit(u64 val) {
|
||||||
|
return __builtin_ctzll(val);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Similar to std::bitset, this is a class which encapsulates a bitset, i.e.
|
// Similar to std::bitset, this is a class which encapsulates a bitset, i.e.
|
||||||
|
@ -84,100 +95,144 @@ static inline int LeastSignificantSetBit(u64 val) { return __builtin_ctzll(val);
|
||||||
// TODO: use constexpr when MSVC gets out of the Dark Ages
|
// TODO: use constexpr when MSVC gets out of the Dark Ages
|
||||||
|
|
||||||
template <typename IntTy>
|
template <typename IntTy>
|
||||||
class BitSet
|
class BitSet {
|
||||||
{
|
|
||||||
static_assert(!std::is_signed<IntTy>::value, "BitSet should not be used with signed types");
|
static_assert(!std::is_signed<IntTy>::value, "BitSet should not be used with signed types");
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// A reference to a particular bit, returned from operator[].
|
// A reference to a particular bit, returned from operator[].
|
||||||
class Ref
|
class Ref {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
Ref(Ref&& other) : m_bs(other.m_bs), m_mask(other.m_mask) {}
|
Ref(Ref&& other) : m_bs(other.m_bs), m_mask(other.m_mask) {
|
||||||
Ref(BitSet* bs, IntTy mask) : m_bs(bs), m_mask(mask) {}
|
}
|
||||||
operator bool() const { return (m_bs->m_val & m_mask) != 0; }
|
Ref(BitSet* bs, IntTy mask) : m_bs(bs), m_mask(mask) {
|
||||||
bool operator=(bool set)
|
}
|
||||||
{
|
operator bool() const {
|
||||||
|
return (m_bs->m_val & m_mask) != 0;
|
||||||
|
}
|
||||||
|
bool operator=(bool set) {
|
||||||
m_bs->m_val = (m_bs->m_val & ~m_mask) | (set ? m_mask : 0);
|
m_bs->m_val = (m_bs->m_val & ~m_mask) | (set ? m_mask : 0);
|
||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BitSet* m_bs;
|
BitSet* m_bs;
|
||||||
IntTy m_mask;
|
IntTy m_mask;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A STL-like iterator is required to be able to use range-based for loops.
|
// A STL-like iterator is required to be able to use range-based for loops.
|
||||||
class Iterator
|
class Iterator {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
Iterator(const Iterator& other) : m_val(other.m_val), m_bit(other.m_bit) {}
|
Iterator(const Iterator& other) : m_val(other.m_val), m_bit(other.m_bit) {
|
||||||
Iterator(IntTy val, int bit) : m_val(val), m_bit(bit) {}
|
|
||||||
Iterator& operator=(Iterator other) { new (this) Iterator(other); return *this; }
|
|
||||||
int operator*() { return m_bit; }
|
|
||||||
Iterator& operator++()
|
|
||||||
{
|
|
||||||
if (m_val == 0)
|
|
||||||
{
|
|
||||||
m_bit = -1;
|
|
||||||
}
|
}
|
||||||
else
|
Iterator(IntTy val, int bit) : m_val(val), m_bit(bit) {
|
||||||
{
|
}
|
||||||
|
Iterator& operator=(Iterator other) {
|
||||||
|
new (this) Iterator(other);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
int operator*() {
|
||||||
|
return m_bit;
|
||||||
|
}
|
||||||
|
Iterator& operator++() {
|
||||||
|
if (m_val == 0) {
|
||||||
|
m_bit = -1;
|
||||||
|
} else {
|
||||||
int bit = LeastSignificantSetBit(m_val);
|
int bit = LeastSignificantSetBit(m_val);
|
||||||
m_val &= ~(1 << bit);
|
m_val &= ~(1 << bit);
|
||||||
m_bit = bit;
|
m_bit = bit;
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
Iterator operator++(int _)
|
Iterator operator++(int _) {
|
||||||
{
|
|
||||||
Iterator other(*this);
|
Iterator other(*this);
|
||||||
++*this;
|
++*this;
|
||||||
return other;
|
return other;
|
||||||
}
|
}
|
||||||
bool operator==(Iterator other) const { return m_bit == other.m_bit; }
|
bool operator==(Iterator other) const {
|
||||||
bool operator!=(Iterator other) const { return m_bit != other.m_bit; }
|
return m_bit == other.m_bit;
|
||||||
|
}
|
||||||
|
bool operator!=(Iterator other) const {
|
||||||
|
return m_bit != other.m_bit;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
IntTy m_val;
|
IntTy m_val;
|
||||||
int m_bit;
|
int m_bit;
|
||||||
};
|
};
|
||||||
|
|
||||||
BitSet() : m_val(0) {}
|
BitSet() : m_val(0) {
|
||||||
explicit BitSet(IntTy val) : m_val(val) {}
|
}
|
||||||
BitSet(std::initializer_list<int> init)
|
explicit BitSet(IntTy val) : m_val(val) {
|
||||||
{
|
}
|
||||||
|
BitSet(std::initializer_list<int> init) {
|
||||||
m_val = 0;
|
m_val = 0;
|
||||||
for (int bit : init)
|
for (int bit : init)
|
||||||
m_val |= (IntTy)1 << bit;
|
m_val |= (IntTy)1 << bit;
|
||||||
}
|
}
|
||||||
|
|
||||||
static BitSet AllTrue(size_t count)
|
static BitSet AllTrue(size_t count) {
|
||||||
{
|
return BitSet(count == sizeof(IntTy) * 8 ? ~(IntTy)0 : (((IntTy)1 << count) - 1));
|
||||||
return BitSet(count == sizeof(IntTy)*8 ? ~(IntTy)0 : (((IntTy)1 << count) - 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ref operator[](size_t bit) { return Ref(this, (IntTy)1 << bit); }
|
Ref operator[](size_t bit) {
|
||||||
const Ref operator[](size_t bit) const { return (*const_cast<BitSet*>(this))[bit]; }
|
return Ref(this, (IntTy)1 << bit);
|
||||||
bool operator==(BitSet other) const { return m_val == other.m_val; }
|
}
|
||||||
bool operator!=(BitSet other) const { return m_val != other.m_val; }
|
const Ref operator[](size_t bit) const {
|
||||||
bool operator<(BitSet other) const { return m_val < other.m_val; }
|
return (*const_cast<BitSet*>(this))[bit];
|
||||||
bool operator>(BitSet other) const { return m_val > other.m_val; }
|
}
|
||||||
BitSet operator|(BitSet other) const { return BitSet(m_val | other.m_val); }
|
bool operator==(BitSet other) const {
|
||||||
BitSet operator&(BitSet other) const { return BitSet(m_val & other.m_val); }
|
return m_val == other.m_val;
|
||||||
BitSet operator^(BitSet other) const { return BitSet(m_val ^ other.m_val); }
|
}
|
||||||
BitSet operator~() const { return BitSet(~m_val); }
|
bool operator!=(BitSet other) const {
|
||||||
BitSet& operator|=(BitSet other) { return *this = *this | other; }
|
return m_val != other.m_val;
|
||||||
BitSet& operator&=(BitSet other) { return *this = *this & other; }
|
}
|
||||||
BitSet& operator^=(BitSet other) { return *this = *this ^ other; }
|
bool operator<(BitSet other) const {
|
||||||
|
return m_val < other.m_val;
|
||||||
|
}
|
||||||
|
bool operator>(BitSet other) const {
|
||||||
|
return m_val > other.m_val;
|
||||||
|
}
|
||||||
|
BitSet operator|(BitSet other) const {
|
||||||
|
return BitSet(m_val | other.m_val);
|
||||||
|
}
|
||||||
|
BitSet operator&(BitSet other) const {
|
||||||
|
return BitSet(m_val & other.m_val);
|
||||||
|
}
|
||||||
|
BitSet operator^(BitSet other) const {
|
||||||
|
return BitSet(m_val ^ other.m_val);
|
||||||
|
}
|
||||||
|
BitSet operator~() const {
|
||||||
|
return BitSet(~m_val);
|
||||||
|
}
|
||||||
|
BitSet& operator|=(BitSet other) {
|
||||||
|
return *this = *this | other;
|
||||||
|
}
|
||||||
|
BitSet& operator&=(BitSet other) {
|
||||||
|
return *this = *this & other;
|
||||||
|
}
|
||||||
|
BitSet& operator^=(BitSet other) {
|
||||||
|
return *this = *this ^ other;
|
||||||
|
}
|
||||||
operator u32() = delete;
|
operator u32() = delete;
|
||||||
operator bool() { return m_val != 0; }
|
operator bool() {
|
||||||
|
return m_val != 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Warning: Even though on modern CPUs this is a single fast instruction,
|
// Warning: Even though on modern CPUs this is a single fast instruction,
|
||||||
// Dolphin's official builds do not currently assume POPCNT support on x86,
|
// Dolphin's official builds do not currently assume POPCNT support on x86,
|
||||||
// so slower explicit bit twiddling is generated. Still should generally
|
// so slower explicit bit twiddling is generated. Still should generally
|
||||||
// be faster than a loop.
|
// be faster than a loop.
|
||||||
unsigned int Count() const { return CountSetBits(m_val); }
|
unsigned int Count() const {
|
||||||
|
return CountSetBits(m_val);
|
||||||
|
}
|
||||||
|
|
||||||
Iterator begin() const { Iterator it(m_val, 0); return ++it; }
|
Iterator begin() const {
|
||||||
Iterator end() const { return Iterator(m_val, -1); }
|
Iterator it(m_val, 0);
|
||||||
|
return ++it;
|
||||||
|
}
|
||||||
|
Iterator end() const {
|
||||||
|
return Iterator(m_val, -1);
|
||||||
|
}
|
||||||
|
|
||||||
IntTy m_val;
|
IntTy m_val;
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,30 +5,27 @@
|
||||||
#include "common/break_points.h"
|
#include "common/break_points.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
bool BreakPoints::IsAddressBreakPoint(u32 iAddress) const
|
bool BreakPoints::IsAddressBreakPoint(u32 iAddress) const {
|
||||||
{
|
|
||||||
auto cond = [&iAddress](const TBreakPoint& bp) { return bp.iAddress == iAddress; };
|
auto cond = [&iAddress](const TBreakPoint& bp) { return bp.iAddress == iAddress; };
|
||||||
auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond);
|
auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond);
|
||||||
return it != m_BreakPoints.end();
|
return it != m_BreakPoints.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BreakPoints::IsTempBreakPoint(u32 iAddress) const
|
bool BreakPoints::IsTempBreakPoint(u32 iAddress) const {
|
||||||
{
|
auto cond = [&iAddress](const TBreakPoint& bp) {
|
||||||
auto cond = [&iAddress](const TBreakPoint& bp) { return bp.iAddress == iAddress && bp.bTemporary; };
|
return bp.iAddress == iAddress && bp.bTemporary;
|
||||||
|
};
|
||||||
auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond);
|
auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond);
|
||||||
return it != m_BreakPoints.end();
|
return it != m_BreakPoints.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const
|
BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const {
|
||||||
{
|
|
||||||
TBreakPointsStr bps;
|
TBreakPointsStr bps;
|
||||||
for (auto breakpoint : m_BreakPoints)
|
for (auto breakpoint : m_BreakPoints) {
|
||||||
{
|
if (!breakpoint.bTemporary) {
|
||||||
if (!breakpoint.bTemporary)
|
|
||||||
{
|
|
||||||
std::stringstream bp;
|
std::stringstream bp;
|
||||||
bp << std::hex << breakpoint.iAddress << " " << (breakpoint.bOn ? "n" : "");
|
bp << std::hex << breakpoint.iAddress << " " << (breakpoint.bOn ? "n" : "");
|
||||||
bps.push_back(bp.str());
|
bps.push_back(bp.str());
|
||||||
|
@ -38,10 +35,8 @@ BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const
|
||||||
return bps;
|
return bps;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreakPoints::AddFromStrings(const TBreakPointsStr& bps)
|
void BreakPoints::AddFromStrings(const TBreakPointsStr& bps) {
|
||||||
{
|
for (auto bps_item : bps) {
|
||||||
for (auto bps_item : bps)
|
|
||||||
{
|
|
||||||
TBreakPoint bp;
|
TBreakPoint bp;
|
||||||
std::stringstream bpstr;
|
std::stringstream bpstr;
|
||||||
bpstr << std::hex << bps_item;
|
bpstr << std::hex << bps_item;
|
||||||
|
@ -52,18 +47,15 @@ void BreakPoints::AddFromStrings(const TBreakPointsStr& bps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreakPoints::Add(const TBreakPoint& bp)
|
void BreakPoints::Add(const TBreakPoint& bp) {
|
||||||
{
|
if (!IsAddressBreakPoint(bp.iAddress)) {
|
||||||
if (!IsAddressBreakPoint(bp.iAddress))
|
|
||||||
{
|
|
||||||
m_BreakPoints.push_back(bp);
|
m_BreakPoints.push_back(bp);
|
||||||
//if (jit)
|
// if (jit)
|
||||||
// jit->GetBlockCache()->InvalidateICache(bp.iAddress, 4);
|
// jit->GetBlockCache()->InvalidateICache(bp.iAddress, 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreakPoints::Add(u32 em_address, bool temp)
|
void BreakPoints::Add(u32 em_address, bool temp) {
|
||||||
{
|
|
||||||
if (!IsAddressBreakPoint(em_address)) // only add new addresses
|
if (!IsAddressBreakPoint(em_address)) // only add new addresses
|
||||||
{
|
{
|
||||||
TBreakPoint pt; // breakpoint settings
|
TBreakPoint pt; // breakpoint settings
|
||||||
|
@ -73,22 +65,20 @@ void BreakPoints::Add(u32 em_address, bool temp)
|
||||||
|
|
||||||
m_BreakPoints.push_back(pt);
|
m_BreakPoints.push_back(pt);
|
||||||
|
|
||||||
//if (jit)
|
// if (jit)
|
||||||
// jit->GetBlockCache()->InvalidateICache(em_address, 4);
|
// jit->GetBlockCache()->InvalidateICache(em_address, 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreakPoints::Remove(u32 em_address)
|
void BreakPoints::Remove(u32 em_address) {
|
||||||
{
|
|
||||||
auto cond = [&em_address](const TBreakPoint& bp) { return bp.iAddress == em_address; };
|
auto cond = [&em_address](const TBreakPoint& bp) { return bp.iAddress == em_address; };
|
||||||
auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond);
|
auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond);
|
||||||
if (it != m_BreakPoints.end())
|
if (it != m_BreakPoints.end())
|
||||||
m_BreakPoints.erase(it);
|
m_BreakPoints.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreakPoints::Clear()
|
void BreakPoints::Clear() {
|
||||||
{
|
// if (jit)
|
||||||
//if (jit)
|
|
||||||
//{
|
//{
|
||||||
// std::for_each(m_BreakPoints.begin(), m_BreakPoints.end(),
|
// std::for_each(m_BreakPoints.begin(), m_BreakPoints.end(),
|
||||||
// [](const TBreakPoint& bp)
|
// [](const TBreakPoint& bp)
|
||||||
|
|
|
@ -4,28 +4,28 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
class DebugInterface;
|
class DebugInterface;
|
||||||
|
|
||||||
struct TBreakPoint
|
struct TBreakPoint {
|
||||||
{
|
|
||||||
u32 iAddress;
|
u32 iAddress;
|
||||||
bool bOn;
|
bool bOn;
|
||||||
bool bTemporary;
|
bool bTemporary;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Code breakpoints.
|
// Code breakpoints.
|
||||||
class BreakPoints
|
class BreakPoints {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
typedef std::vector<TBreakPoint> TBreakPoints;
|
typedef std::vector<TBreakPoint> TBreakPoints;
|
||||||
typedef std::vector<std::string> TBreakPointsStr;
|
typedef std::vector<std::string> TBreakPointsStr;
|
||||||
|
|
||||||
const TBreakPoints& GetBreakPoints() { return m_BreakPoints; }
|
const TBreakPoints& GetBreakPoints() {
|
||||||
|
return m_BreakPoints;
|
||||||
|
}
|
||||||
|
|
||||||
TBreakPointsStr GetStrings() const;
|
TBreakPointsStr GetStrings() const;
|
||||||
void AddFromStrings(const TBreakPointsStr& bps);
|
void AddFromStrings(const TBreakPointsStr& bps);
|
||||||
|
@ -35,7 +35,7 @@ public:
|
||||||
bool IsTempBreakPoint(u32 iAddress) const;
|
bool IsTempBreakPoint(u32 iAddress) const;
|
||||||
|
|
||||||
// Add BreakPoint
|
// Add BreakPoint
|
||||||
void Add(u32 em_address, bool temp=false);
|
void Add(u32 em_address, bool temp = false);
|
||||||
void Add(const TBreakPoint& bp);
|
void Add(const TBreakPoint& bp);
|
||||||
|
|
||||||
// Remove Breakpoint
|
// Remove Breakpoint
|
||||||
|
|
|
@ -41,72 +41,77 @@
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
struct LinkedListItem : public T
|
struct LinkedListItem : public T {
|
||||||
{
|
LinkedListItem<T>* next;
|
||||||
LinkedListItem<T> *next;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class PointerWrap;
|
class PointerWrap;
|
||||||
|
|
||||||
class PointerWrapSection
|
class PointerWrapSection {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
PointerWrapSection(PointerWrap &p, int ver, const char *title) : p_(p), ver_(ver), title_(title) {
|
PointerWrapSection(PointerWrap& p, int ver, const char* title)
|
||||||
|
: p_(p), ver_(ver), title_(title) {
|
||||||
}
|
}
|
||||||
~PointerWrapSection();
|
~PointerWrapSection();
|
||||||
|
|
||||||
bool operator == (const int &v) const { return ver_ == v; }
|
bool operator==(const int& v) const {
|
||||||
bool operator != (const int &v) const { return ver_ != v; }
|
return ver_ == v;
|
||||||
bool operator <= (const int &v) const { return ver_ <= v; }
|
}
|
||||||
bool operator >= (const int &v) const { return ver_ >= v; }
|
bool operator!=(const int& v) const {
|
||||||
bool operator < (const int &v) const { return ver_ < v; }
|
return ver_ != v;
|
||||||
bool operator > (const int &v) const { return ver_ > v; }
|
}
|
||||||
|
bool operator<=(const int& v) const {
|
||||||
|
return ver_ <= v;
|
||||||
|
}
|
||||||
|
bool operator>=(const int& v) const {
|
||||||
|
return ver_ >= v;
|
||||||
|
}
|
||||||
|
bool operator<(const int& v) const {
|
||||||
|
return ver_ < v;
|
||||||
|
}
|
||||||
|
bool operator>(const int& v) const {
|
||||||
|
return ver_ > v;
|
||||||
|
}
|
||||||
|
|
||||||
operator bool() const {
|
operator bool() const {
|
||||||
return ver_ > 0;
|
return ver_ > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PointerWrap &p_;
|
PointerWrap& p_;
|
||||||
int ver_;
|
int ver_;
|
||||||
const char *title_;
|
const char* title_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wrapper class
|
// Wrapper class
|
||||||
class PointerWrap
|
class PointerWrap {
|
||||||
{
|
// This makes it a compile error if you forget to define DoState() on non-POD.
|
||||||
// This makes it a compile error if you forget to define DoState() on non-POD.
|
// Which also can be a problem, for example struct tm is non-POD on linux, for whatever reason...
|
||||||
// Which also can be a problem, for example struct tm is non-POD on linux, for whatever reason...
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
template<typename T, bool isPOD = std::is_pod<T>::value, bool isPointer = std::is_pointer<T>::value>
|
template <typename T, bool isPOD = std::is_pod<T>::value,
|
||||||
|
bool isPointer = std::is_pointer<T>::value>
|
||||||
#else
|
#else
|
||||||
template<typename T, bool isPOD = __is_pod(T), bool isPointer = std::is_pointer<T>::value>
|
template <typename T, bool isPOD = __is_pod(T), bool isPointer = std::is_pointer<T>::value>
|
||||||
#endif
|
#endif
|
||||||
struct DoHelper
|
struct DoHelper {
|
||||||
{
|
static void DoArray(PointerWrap* p, T* x, int count) {
|
||||||
static void DoArray(PointerWrap *p, T *x, int count)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < count; ++i)
|
for (int i = 0; i < count; ++i)
|
||||||
p->Do(x[i]);
|
p->Do(x[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Do(PointerWrap *p, T &x)
|
static void Do(PointerWrap* p, T& x) {
|
||||||
{
|
|
||||||
p->DoClass(x);
|
p->DoClass(x);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
template <typename T>
|
||||||
struct DoHelper<T, true, false>
|
struct DoHelper<T, true, false> {
|
||||||
{
|
static void DoArray(PointerWrap* p, T* x, int count) {
|
||||||
static void DoArray(PointerWrap *p, T *x, int count)
|
p->DoVoid((void*)x, sizeof(T) * count);
|
||||||
{
|
|
||||||
p->DoVoid((void *)x, sizeof(T) * count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Do(PointerWrap *p, T &x)
|
static void Do(PointerWrap* p, T& x) {
|
||||||
{
|
p->DoVoid((void*)&x, sizeof(x));
|
||||||
p->DoVoid((void *)&x, sizeof(x));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -124,129 +129,142 @@ public:
|
||||||
ERROR_FAILURE = 2,
|
ERROR_FAILURE = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
u8 **ptr;
|
u8** ptr;
|
||||||
Mode mode;
|
Mode mode;
|
||||||
Error error;
|
Error error;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PointerWrap(u8 **ptr_, Mode mode_) : ptr(ptr_), mode(mode_), error(ERROR_NONE) {}
|
PointerWrap(u8** ptr_, Mode mode_) : ptr(ptr_), mode(mode_), error(ERROR_NONE) {
|
||||||
PointerWrap(unsigned char **ptr_, int mode_) : ptr((u8**)ptr_), mode((Mode)mode_), error(ERROR_NONE) {}
|
}
|
||||||
|
PointerWrap(unsigned char** ptr_, int mode_)
|
||||||
|
: ptr((u8**)ptr_), mode((Mode)mode_), error(ERROR_NONE) {
|
||||||
|
}
|
||||||
|
|
||||||
PointerWrapSection Section(const char *title, int ver) {
|
PointerWrapSection Section(const char* title, int ver) {
|
||||||
return Section(title, ver, ver);
|
return Section(title, ver, ver);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The returned object can be compared against the version that was loaded.
|
// The returned object can be compared against the version that was loaded.
|
||||||
// This can be used to support versions as old as minVer.
|
// This can be used to support versions as old as minVer.
|
||||||
// Version = 0 means the section was not found.
|
// Version = 0 means the section was not found.
|
||||||
PointerWrapSection Section(const char *title, int minVer, int ver) {
|
PointerWrapSection Section(const char* title, int minVer, int ver) {
|
||||||
char marker[16] = {0};
|
char marker[16] = {0};
|
||||||
int foundVersion = ver;
|
int foundVersion = ver;
|
||||||
|
|
||||||
strncpy(marker, title, sizeof(marker));
|
strncpy(marker, title, sizeof(marker));
|
||||||
if (!ExpectVoid(marker, sizeof(marker)))
|
if (!ExpectVoid(marker, sizeof(marker))) {
|
||||||
{
|
|
||||||
// Might be before we added name markers for safety.
|
// Might be before we added name markers for safety.
|
||||||
if (foundVersion == 1 && ExpectVoid(&foundVersion, sizeof(foundVersion)))
|
if (foundVersion == 1 && ExpectVoid(&foundVersion, sizeof(foundVersion)))
|
||||||
DoMarker(title);
|
DoMarker(title);
|
||||||
// Wasn't found, but maybe we can still load the state.
|
// Wasn't found, but maybe we can still load the state.
|
||||||
else
|
else
|
||||||
foundVersion = 0;
|
foundVersion = 0;
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
Do(foundVersion);
|
Do(foundVersion);
|
||||||
|
|
||||||
if (error == ERROR_FAILURE || foundVersion < minVer || foundVersion > ver) {
|
if (error == ERROR_FAILURE || foundVersion < minVer || foundVersion > ver) {
|
||||||
LOG_ERROR(Common, "Savestate failure: wrong version %d found for %s", foundVersion, title);
|
LOG_ERROR(Common, "Savestate failure: wrong version %d found for %s", foundVersion,
|
||||||
|
title);
|
||||||
SetError(ERROR_FAILURE);
|
SetError(ERROR_FAILURE);
|
||||||
return PointerWrapSection(*this, -1, title);
|
return PointerWrapSection(*this, -1, title);
|
||||||
}
|
}
|
||||||
return PointerWrapSection(*this, foundVersion, title);
|
return PointerWrapSection(*this, foundVersion, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetMode(Mode mode_) {mode = mode_;}
|
void SetMode(Mode mode_) {
|
||||||
Mode GetMode() const {return mode;}
|
mode = mode_;
|
||||||
u8 **GetPPtr() {return ptr;}
|
}
|
||||||
void SetError(Error error_)
|
Mode GetMode() const {
|
||||||
{
|
return mode;
|
||||||
|
}
|
||||||
|
u8** GetPPtr() {
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
void SetError(Error error_) {
|
||||||
if (error < error_)
|
if (error < error_)
|
||||||
error = error_;
|
error = error_;
|
||||||
if (error > ERROR_WARNING)
|
if (error > ERROR_WARNING)
|
||||||
mode = PointerWrap::MODE_MEASURE;
|
mode = PointerWrap::MODE_MEASURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExpectVoid(void *data, int size)
|
bool ExpectVoid(void* data, int size) {
|
||||||
{
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case MODE_READ: if (memcmp(data, *ptr, size) != 0) return false; break;
|
case MODE_READ:
|
||||||
case MODE_WRITE: memcpy(*ptr, data, size); break;
|
if (memcmp(data, *ptr, size) != 0)
|
||||||
case MODE_MEASURE: break; // MODE_MEASURE - don't need to do anything
|
return false;
|
||||||
|
break;
|
||||||
|
case MODE_WRITE:
|
||||||
|
memcpy(*ptr, data, size);
|
||||||
|
break;
|
||||||
|
case MODE_MEASURE:
|
||||||
|
break; // MODE_MEASURE - don't need to do anything
|
||||||
case MODE_VERIFY:
|
case MODE_VERIFY:
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
DEBUG_ASSERT_MSG(((u8*)data)[i] == (*ptr)[i],
|
DEBUG_ASSERT_MSG(
|
||||||
|
((u8*)data)[i] == (*ptr)[i],
|
||||||
"Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n",
|
"Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n",
|
||||||
((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i],
|
((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i],
|
||||||
(*ptr)[i], (*ptr)[i], &(*ptr)[i]);
|
&(*ptr)[i]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default: break; // throw an error?
|
default:
|
||||||
|
break; // throw an error?
|
||||||
}
|
}
|
||||||
(*ptr) += size;
|
(*ptr) += size;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DoVoid(void *data, int size)
|
void DoVoid(void* data, int size) {
|
||||||
{
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case MODE_READ: memcpy(data, *ptr, size); break;
|
case MODE_READ:
|
||||||
case MODE_WRITE: memcpy(*ptr, data, size); break;
|
memcpy(data, *ptr, size);
|
||||||
case MODE_MEASURE: break; // MODE_MEASURE - don't need to do anything
|
break;
|
||||||
|
case MODE_WRITE:
|
||||||
|
memcpy(*ptr, data, size);
|
||||||
|
break;
|
||||||
|
case MODE_MEASURE:
|
||||||
|
break; // MODE_MEASURE - don't need to do anything
|
||||||
case MODE_VERIFY:
|
case MODE_VERIFY:
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
DEBUG_ASSERT_MSG(((u8*)data)[i] == (*ptr)[i],
|
DEBUG_ASSERT_MSG(
|
||||||
|
((u8*)data)[i] == (*ptr)[i],
|
||||||
"Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n",
|
"Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n",
|
||||||
((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i],
|
((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i],
|
||||||
(*ptr)[i], (*ptr)[i], &(*ptr)[i]);
|
&(*ptr)[i]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default: break; // throw an error?
|
default:
|
||||||
|
break; // throw an error?
|
||||||
}
|
}
|
||||||
(*ptr) += size;
|
(*ptr) += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class K, class T>
|
template <class K, class T>
|
||||||
void Do(std::map<K, T *> &x)
|
void Do(std::map<K, T*>& x) {
|
||||||
{
|
if (mode == MODE_READ) {
|
||||||
if (mode == MODE_READ)
|
for (auto it = x.begin(), end = x.end(); it != end; ++it) {
|
||||||
{
|
|
||||||
for (auto it = x.begin(), end = x.end(); it != end; ++it)
|
|
||||||
{
|
|
||||||
if (it->second != nullptr)
|
if (it->second != nullptr)
|
||||||
delete it->second;
|
delete it->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
T *dv = nullptr;
|
T* dv = nullptr;
|
||||||
DoMap(x, dv);
|
DoMap(x, dv);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class K, class T>
|
template <class K, class T>
|
||||||
void Do(std::map<K, T> &x)
|
void Do(std::map<K, T>& x) {
|
||||||
{
|
|
||||||
T dv = T();
|
T dv = T();
|
||||||
DoMap(x, dv);
|
DoMap(x, dv);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class K, class T>
|
template <class K, class T>
|
||||||
void DoMap(std::map<K, T> &x, T &default_val)
|
void DoMap(std::map<K, T>& x, T& default_val) {
|
||||||
{
|
|
||||||
unsigned int number = (unsigned int)x.size();
|
unsigned int number = (unsigned int)x.size();
|
||||||
Do(number);
|
Do(number);
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case MODE_READ:
|
case MODE_READ: {
|
||||||
{
|
|
||||||
x.clear();
|
x.clear();
|
||||||
while (number > 0)
|
while (number > 0) {
|
||||||
{
|
|
||||||
K first = K();
|
K first = K();
|
||||||
Do(first);
|
Do(first);
|
||||||
T second = default_val;
|
T second = default_val;
|
||||||
|
@ -254,59 +272,48 @@ public:
|
||||||
x[first] = second;
|
x[first] = second;
|
||||||
--number;
|
--number;
|
||||||
}
|
}
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
case MODE_WRITE:
|
case MODE_WRITE:
|
||||||
case MODE_MEASURE:
|
case MODE_MEASURE:
|
||||||
case MODE_VERIFY:
|
case MODE_VERIFY: {
|
||||||
{
|
|
||||||
typename std::map<K, T>::iterator itr = x.begin();
|
typename std::map<K, T>::iterator itr = x.begin();
|
||||||
while (number > 0)
|
while (number > 0) {
|
||||||
{
|
|
||||||
K first = itr->first;
|
K first = itr->first;
|
||||||
Do(first);
|
Do(first);
|
||||||
Do(itr->second);
|
Do(itr->second);
|
||||||
--number;
|
--number;
|
||||||
++itr;
|
++itr;
|
||||||
}
|
}
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class K, class T>
|
template <class K, class T>
|
||||||
void Do(std::multimap<K, T *> &x)
|
void Do(std::multimap<K, T*>& x) {
|
||||||
{
|
if (mode == MODE_READ) {
|
||||||
if (mode == MODE_READ)
|
for (auto it = x.begin(), end = x.end(); it != end; ++it) {
|
||||||
{
|
|
||||||
for (auto it = x.begin(), end = x.end(); it != end; ++it)
|
|
||||||
{
|
|
||||||
if (it->second != nullptr)
|
if (it->second != nullptr)
|
||||||
delete it->second;
|
delete it->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
T *dv = nullptr;
|
T* dv = nullptr;
|
||||||
DoMultimap(x, dv);
|
DoMultimap(x, dv);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class K, class T>
|
template <class K, class T>
|
||||||
void Do(std::multimap<K, T> &x)
|
void Do(std::multimap<K, T>& x) {
|
||||||
{
|
|
||||||
T dv = T();
|
T dv = T();
|
||||||
DoMultimap(x, dv);
|
DoMultimap(x, dv);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class K, class T>
|
template <class K, class T>
|
||||||
void DoMultimap(std::multimap<K, T> &x, T &default_val)
|
void DoMultimap(std::multimap<K, T>& x, T& default_val) {
|
||||||
{
|
|
||||||
unsigned int number = (unsigned int)x.size();
|
unsigned int number = (unsigned int)x.size();
|
||||||
Do(number);
|
Do(number);
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case MODE_READ:
|
case MODE_READ: {
|
||||||
{
|
|
||||||
x.clear();
|
x.clear();
|
||||||
while (number > 0)
|
while (number > 0) {
|
||||||
{
|
|
||||||
K first = K();
|
K first = K();
|
||||||
Do(first);
|
Do(first);
|
||||||
T second = default_val;
|
T second = default_val;
|
||||||
|
@ -314,57 +321,47 @@ public:
|
||||||
x.insert(std::make_pair(first, second));
|
x.insert(std::make_pair(first, second));
|
||||||
--number;
|
--number;
|
||||||
}
|
}
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
case MODE_WRITE:
|
case MODE_WRITE:
|
||||||
case MODE_MEASURE:
|
case MODE_MEASURE:
|
||||||
case MODE_VERIFY:
|
case MODE_VERIFY: {
|
||||||
{
|
|
||||||
typename std::multimap<K, T>::iterator itr = x.begin();
|
typename std::multimap<K, T>::iterator itr = x.begin();
|
||||||
while (number > 0)
|
while (number > 0) {
|
||||||
{
|
|
||||||
Do(itr->first);
|
Do(itr->first);
|
||||||
Do(itr->second);
|
Do(itr->second);
|
||||||
--number;
|
--number;
|
||||||
++itr;
|
++itr;
|
||||||
}
|
}
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store vectors.
|
// Store vectors.
|
||||||
template<class T>
|
template <class T>
|
||||||
void Do(std::vector<T *> &x)
|
void Do(std::vector<T*>& x) {
|
||||||
{
|
T* dv = nullptr;
|
||||||
T *dv = nullptr;
|
|
||||||
DoVector(x, dv);
|
DoVector(x, dv);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void Do(std::vector<T> &x)
|
void Do(std::vector<T>& x) {
|
||||||
{
|
|
||||||
T dv = T();
|
T dv = T();
|
||||||
DoVector(x, dv);
|
DoVector(x, dv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
template<class T>
|
void DoPOD(std::vector<T>& x) {
|
||||||
void DoPOD(std::vector<T> &x)
|
|
||||||
{
|
|
||||||
T dv = T();
|
T dv = T();
|
||||||
DoVectorPOD(x, dv);
|
DoVectorPOD(x, dv);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void Do(std::vector<T> &x, T &default_val)
|
void Do(std::vector<T>& x, T& default_val) {
|
||||||
{
|
|
||||||
DoVector(x, default_val);
|
DoVector(x, default_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void DoVector(std::vector<T> &x, T &default_val)
|
void DoVector(std::vector<T>& x, T& default_val) {
|
||||||
{
|
|
||||||
u32 vec_size = (u32)x.size();
|
u32 vec_size = (u32)x.size();
|
||||||
Do(vec_size);
|
Do(vec_size);
|
||||||
x.resize(vec_size, default_val);
|
x.resize(vec_size, default_val);
|
||||||
|
@ -372,9 +369,8 @@ public:
|
||||||
DoArray(&x[0], vec_size);
|
DoArray(&x[0], vec_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void DoVectorPOD(std::vector<T> &x, T &default_val)
|
void DoVectorPOD(std::vector<T>& x, T& default_val) {
|
||||||
{
|
|
||||||
u32 vec_size = (u32)x.size();
|
u32 vec_size = (u32)x.size();
|
||||||
Do(vec_size);
|
Do(vec_size);
|
||||||
x.resize(vec_size, default_val);
|
x.resize(vec_size, default_val);
|
||||||
|
@ -383,55 +379,48 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store deques.
|
// Store deques.
|
||||||
template<class T>
|
template <class T>
|
||||||
void Do(std::deque<T *> &x)
|
void Do(std::deque<T*>& x) {
|
||||||
{
|
T* dv = nullptr;
|
||||||
T *dv = nullptr;
|
|
||||||
DoDeque(x, dv);
|
DoDeque(x, dv);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void Do(std::deque<T> &x)
|
void Do(std::deque<T>& x) {
|
||||||
{
|
|
||||||
T dv = T();
|
T dv = T();
|
||||||
DoDeque(x, dv);
|
DoDeque(x, dv);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void DoDeque(std::deque<T> &x, T &default_val)
|
void DoDeque(std::deque<T>& x, T& default_val) {
|
||||||
{
|
|
||||||
u32 deq_size = (u32)x.size();
|
u32 deq_size = (u32)x.size();
|
||||||
Do(deq_size);
|
Do(deq_size);
|
||||||
x.resize(deq_size, default_val);
|
x.resize(deq_size, default_val);
|
||||||
u32 i;
|
u32 i;
|
||||||
for(i = 0; i < deq_size; i++)
|
for (i = 0; i < deq_size; i++)
|
||||||
Do(x[i]);
|
Do(x[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store STL lists.
|
// Store STL lists.
|
||||||
template<class T>
|
template <class T>
|
||||||
void Do(std::list<T *> &x)
|
void Do(std::list<T*>& x) {
|
||||||
{
|
T* dv = nullptr;
|
||||||
T *dv = nullptr;
|
|
||||||
Do(x, dv);
|
Do(x, dv);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void Do(std::list<T> &x)
|
void Do(std::list<T>& x) {
|
||||||
{
|
|
||||||
T dv = T();
|
T dv = T();
|
||||||
DoList(x, dv);
|
DoList(x, dv);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void Do(std::list<T> &x, T &default_val)
|
void Do(std::list<T>& x, T& default_val) {
|
||||||
{
|
|
||||||
DoList(x, default_val);
|
DoList(x, default_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void DoList(std::list<T> &x, T &default_val)
|
void DoList(std::list<T>& x, T& default_val) {
|
||||||
{
|
|
||||||
u32 list_size = (u32)x.size();
|
u32 list_size = (u32)x.size();
|
||||||
Do(list_size);
|
Do(list_size);
|
||||||
x.resize(list_size, default_val);
|
x.resize(list_size, default_val);
|
||||||
|
@ -441,15 +430,11 @@ public:
|
||||||
Do(*itr);
|
Do(*itr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Store STL sets.
|
// Store STL sets.
|
||||||
template <class T>
|
template <class T>
|
||||||
void Do(std::set<T *> &x)
|
void Do(std::set<T*>& x) {
|
||||||
{
|
if (mode == MODE_READ) {
|
||||||
if (mode == MODE_READ)
|
for (auto it = x.begin(), end = x.end(); it != end; ++it) {
|
||||||
{
|
|
||||||
for (auto it = x.begin(), end = x.end(); it != end; ++it)
|
|
||||||
{
|
|
||||||
if (*it != nullptr)
|
if (*it != nullptr)
|
||||||
delete *it;
|
delete *it;
|
||||||
}
|
}
|
||||||
|
@ -458,39 +443,31 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void Do(std::set<T> &x)
|
void Do(std::set<T>& x) {
|
||||||
{
|
|
||||||
DoSet(x);
|
DoSet(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void DoSet(std::set<T> &x)
|
void DoSet(std::set<T>& x) {
|
||||||
{
|
|
||||||
unsigned int number = (unsigned int)x.size();
|
unsigned int number = (unsigned int)x.size();
|
||||||
Do(number);
|
Do(number);
|
||||||
|
|
||||||
switch (mode)
|
switch (mode) {
|
||||||
{
|
case MODE_READ: {
|
||||||
case MODE_READ:
|
|
||||||
{
|
|
||||||
x.clear();
|
x.clear();
|
||||||
while (number-- > 0)
|
while (number-- > 0) {
|
||||||
{
|
|
||||||
T it = T();
|
T it = T();
|
||||||
Do(it);
|
Do(it);
|
||||||
x.insert(it);
|
x.insert(it);
|
||||||
}
|
}
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
case MODE_WRITE:
|
case MODE_WRITE:
|
||||||
case MODE_MEASURE:
|
case MODE_MEASURE:
|
||||||
case MODE_VERIFY:
|
case MODE_VERIFY: {
|
||||||
{
|
|
||||||
typename std::set<T>::iterator itr = x.begin();
|
typename std::set<T>::iterator itr = x.begin();
|
||||||
while (number-- > 0)
|
while (number-- > 0)
|
||||||
Do(*itr++);
|
Do(*itr++);
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
LOG_ERROR(Common, "Savestate error: invalid mode %d.", mode);
|
LOG_ERROR(Common, "Savestate error: invalid mode %d.", mode);
|
||||||
|
@ -498,15 +475,19 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store strings.
|
// Store strings.
|
||||||
void Do(std::string &x)
|
void Do(std::string& x) {
|
||||||
{
|
|
||||||
int stringLen = (int)x.length() + 1;
|
int stringLen = (int)x.length() + 1;
|
||||||
Do(stringLen);
|
Do(stringLen);
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case MODE_READ: x = (char*)*ptr; break;
|
case MODE_READ:
|
||||||
case MODE_WRITE: memcpy(*ptr, x.c_str(), stringLen); break;
|
x = (char*)*ptr;
|
||||||
case MODE_MEASURE: break;
|
break;
|
||||||
|
case MODE_WRITE:
|
||||||
|
memcpy(*ptr, x.c_str(), stringLen);
|
||||||
|
break;
|
||||||
|
case MODE_MEASURE:
|
||||||
|
break;
|
||||||
case MODE_VERIFY:
|
case MODE_VERIFY:
|
||||||
DEBUG_ASSERT_MSG((x == (char*)*ptr),
|
DEBUG_ASSERT_MSG((x == (char*)*ptr),
|
||||||
"Savestate verification failure: \"%s\" != \"%s\" (at %p).\n",
|
"Savestate verification failure: \"%s\" != \"%s\" (at %p).\n",
|
||||||
|
@ -516,15 +497,19 @@ public:
|
||||||
(*ptr) += stringLen;
|
(*ptr) += stringLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Do(std::wstring &x)
|
void Do(std::wstring& x) {
|
||||||
{
|
int stringLen = sizeof(wchar_t) * ((int)x.length() + 1);
|
||||||
int stringLen = sizeof(wchar_t)*((int)x.length() + 1);
|
|
||||||
Do(stringLen);
|
Do(stringLen);
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case MODE_READ: x = (wchar_t*)*ptr; break;
|
case MODE_READ:
|
||||||
case MODE_WRITE: memcpy(*ptr, x.c_str(), stringLen); break;
|
x = (wchar_t*)*ptr;
|
||||||
case MODE_MEASURE: break;
|
break;
|
||||||
|
case MODE_WRITE:
|
||||||
|
memcpy(*ptr, x.c_str(), stringLen);
|
||||||
|
break;
|
||||||
|
case MODE_MEASURE:
|
||||||
|
break;
|
||||||
case MODE_VERIFY:
|
case MODE_VERIFY:
|
||||||
DEBUG_ASSERT_MSG((x == (wchar_t*)*ptr),
|
DEBUG_ASSERT_MSG((x == (wchar_t*)*ptr),
|
||||||
"Savestate verification failure: \"%ls\" != \"%ls\" (at %p).\n",
|
"Savestate verification failure: \"%ls\" != \"%ls\" (at %p).\n",
|
||||||
|
@ -534,15 +519,14 @@ public:
|
||||||
(*ptr) += stringLen;
|
(*ptr) += stringLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void DoClass(T &x) {
|
void DoClass(T& x) {
|
||||||
x.DoState(*this);
|
x.DoState(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void DoClass(T *&x) {
|
void DoClass(T*& x) {
|
||||||
if (mode == MODE_READ)
|
if (mode == MODE_READ) {
|
||||||
{
|
|
||||||
if (x != nullptr)
|
if (x != nullptr)
|
||||||
delete x;
|
delete x;
|
||||||
x = new T();
|
x = new T();
|
||||||
|
@ -550,81 +534,70 @@ public:
|
||||||
x->DoState(*this);
|
x->DoState(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void DoArray(T *x, int count) {
|
void DoArray(T* x, int count) {
|
||||||
DoHelper<T>::DoArray(this, x, count);
|
DoHelper<T>::DoArray(this, x, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void Do(T &x) {
|
void Do(T& x) {
|
||||||
DoHelper<T>::Do(this, x);
|
DoHelper<T>::Do(this, x);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void DoPOD(T &x) {
|
void DoPOD(T& x) {
|
||||||
DoHelper<T>::Do(this, x);
|
DoHelper<T>::Do(this, x);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
void DoPointer(T* &x, T*const base) {
|
void DoPointer(T*& x, T* const base) {
|
||||||
// pointers can be more than 2^31 apart, but you're using this function wrong if you need that much range
|
// pointers can be more than 2^31 apart, but you're using this function wrong if you need
|
||||||
|
// that much range
|
||||||
s32 offset = x - base;
|
s32 offset = x - base;
|
||||||
Do(offset);
|
Do(offset);
|
||||||
if (mode == MODE_READ)
|
if (mode == MODE_READ)
|
||||||
x = base + offset;
|
x = base + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T, LinkedListItem<T>* (*TNew)(), void (*TFree)(LinkedListItem<T>*), void (*TDo)(PointerWrap&, T*)>
|
template <class T, LinkedListItem<T>* (*TNew)(), void (*TFree)(LinkedListItem<T>*),
|
||||||
void DoLinkedList(LinkedListItem<T>*& list_start, LinkedListItem<T>** list_end = nullptr)
|
void (*TDo)(PointerWrap&, T*)>
|
||||||
{
|
void DoLinkedList(LinkedListItem<T>*& list_start, LinkedListItem<T>** list_end = nullptr) {
|
||||||
LinkedListItem<T>* list_cur = list_start;
|
LinkedListItem<T>* list_cur = list_start;
|
||||||
LinkedListItem<T>* prev = nullptr;
|
LinkedListItem<T>* prev = nullptr;
|
||||||
|
|
||||||
while (true)
|
while (true) {
|
||||||
{
|
|
||||||
u8 shouldExist = (list_cur ? 1 : 0);
|
u8 shouldExist = (list_cur ? 1 : 0);
|
||||||
Do(shouldExist);
|
Do(shouldExist);
|
||||||
if (shouldExist == 1)
|
if (shouldExist == 1) {
|
||||||
{
|
|
||||||
LinkedListItem<T>* cur = list_cur ? list_cur : TNew();
|
LinkedListItem<T>* cur = list_cur ? list_cur : TNew();
|
||||||
TDo(*this, (T*)cur);
|
TDo(*this, (T*)cur);
|
||||||
if (!list_cur)
|
if (!list_cur) {
|
||||||
{
|
if (mode == MODE_READ) {
|
||||||
if (mode == MODE_READ)
|
|
||||||
{
|
|
||||||
cur->next = nullptr;
|
cur->next = nullptr;
|
||||||
list_cur = cur;
|
list_cur = cur;
|
||||||
if (prev)
|
if (prev)
|
||||||
prev->next = cur;
|
prev->next = cur;
|
||||||
else
|
else
|
||||||
list_start = cur;
|
list_start = cur;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
TFree(cur);
|
TFree(cur);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
if (mode == MODE_READ) {
|
||||||
{
|
|
||||||
if (mode == MODE_READ)
|
|
||||||
{
|
|
||||||
if (prev)
|
if (prev)
|
||||||
prev->next = nullptr;
|
prev->next = nullptr;
|
||||||
if (list_end)
|
if (list_end)
|
||||||
*list_end = prev;
|
*list_end = prev;
|
||||||
if (list_cur)
|
if (list_cur) {
|
||||||
{
|
|
||||||
if (list_start == list_cur)
|
if (list_start == list_cur)
|
||||||
list_start = nullptr;
|
list_start = nullptr;
|
||||||
do
|
do {
|
||||||
{
|
|
||||||
LinkedListItem<T>* next = list_cur->next;
|
LinkedListItem<T>* next = list_cur->next;
|
||||||
TFree(list_cur);
|
TFree(list_cur);
|
||||||
list_cur = next;
|
list_cur = next;
|
||||||
}
|
} while (list_cur);
|
||||||
while (list_cur);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -634,13 +607,13 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DoMarker(const char* prevName, u32 arbitraryNumber=0x42)
|
void DoMarker(const char* prevName, u32 arbitraryNumber = 0x42) {
|
||||||
{
|
|
||||||
u32 cookie = arbitraryNumber;
|
u32 cookie = arbitraryNumber;
|
||||||
Do(cookie);
|
Do(cookie);
|
||||||
if(mode == PointerWrap::MODE_READ && cookie != arbitraryNumber)
|
if (mode == PointerWrap::MODE_READ && cookie != arbitraryNumber) {
|
||||||
{
|
LOG_ERROR(Common, "After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). "
|
||||||
LOG_ERROR(Common, "After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). Aborting savestate load...", prevName, cookie, cookie, arbitraryNumber, arbitraryNumber);
|
"Aborting savestate load...",
|
||||||
|
prevName, cookie, cookie, arbitraryNumber, arbitraryNumber);
|
||||||
SetError(ERROR_FAILURE);
|
SetError(ERROR_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,24 +14,28 @@
|
||||||
// having to prefix them with gen-> or something similar.
|
// having to prefix them with gen-> or something similar.
|
||||||
// Example implementation:
|
// Example implementation:
|
||||||
// class JIT : public CodeBlock<ARMXEmitter> {}
|
// class JIT : public CodeBlock<ARMXEmitter> {}
|
||||||
template<class T> class CodeBlock : public T, NonCopyable
|
template <class T>
|
||||||
{
|
class CodeBlock : public T, NonCopyable {
|
||||||
private:
|
private:
|
||||||
// A privately used function to set the executable RAM space to something invalid.
|
// A privately used function to set the executable RAM space to something invalid.
|
||||||
// For debugging usefulness it should be used to set the RAM to a host specific breakpoint instruction
|
// For debugging usefulness it should be used to set the RAM to a host specific breakpoint
|
||||||
|
// instruction
|
||||||
virtual void PoisonMemory() = 0;
|
virtual void PoisonMemory() = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
u8 *region;
|
u8* region;
|
||||||
size_t region_size;
|
size_t region_size;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CodeBlock() : region(nullptr), region_size(0) {}
|
CodeBlock() : region(nullptr), region_size(0) {
|
||||||
virtual ~CodeBlock() { if (region) FreeCodeSpace(); }
|
}
|
||||||
|
virtual ~CodeBlock() {
|
||||||
|
if (region)
|
||||||
|
FreeCodeSpace();
|
||||||
|
}
|
||||||
|
|
||||||
// Call this before you generate any code.
|
// Call this before you generate any code.
|
||||||
void AllocCodeSpace(int size)
|
void AllocCodeSpace(int size) {
|
||||||
{
|
|
||||||
region_size = size;
|
region_size = size;
|
||||||
region = (u8*)AllocateExecutableMemory(region_size);
|
region = (u8*)AllocateExecutableMemory(region_size);
|
||||||
T::SetCodePtr(region);
|
T::SetCodePtr(region);
|
||||||
|
@ -39,15 +43,13 @@ public:
|
||||||
|
|
||||||
// Always clear code space with breakpoints, so that if someone accidentally executes
|
// Always clear code space with breakpoints, so that if someone accidentally executes
|
||||||
// uninitialized, it just breaks into the debugger.
|
// uninitialized, it just breaks into the debugger.
|
||||||
void ClearCodeSpace()
|
void ClearCodeSpace() {
|
||||||
{
|
|
||||||
PoisonMemory();
|
PoisonMemory();
|
||||||
ResetCodePtr();
|
ResetCodePtr();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call this when shutting down. Don't rely on the destructor, even though it'll do the job.
|
// Call this when shutting down. Don't rely on the destructor, even though it'll do the job.
|
||||||
void FreeCodeSpace()
|
void FreeCodeSpace() {
|
||||||
{
|
|
||||||
#ifdef __SYMBIAN32__
|
#ifdef __SYMBIAN32__
|
||||||
ResetExecutableMemory(region);
|
ResetExecutableMemory(region);
|
||||||
#else
|
#else
|
||||||
|
@ -57,33 +59,29 @@ public:
|
||||||
region_size = 0;
|
region_size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsInSpace(const u8 *ptr)
|
bool IsInSpace(const u8* ptr) {
|
||||||
{
|
|
||||||
return (ptr >= region) && (ptr < (region + region_size));
|
return (ptr >= region) && (ptr < (region + region_size));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cannot currently be undone. Will write protect the entire code region.
|
// Cannot currently be undone. Will write protect the entire code region.
|
||||||
// Start over if you need to change the code (call FreeCodeSpace(), AllocCodeSpace()).
|
// Start over if you need to change the code (call FreeCodeSpace(), AllocCodeSpace()).
|
||||||
void WriteProtect()
|
void WriteProtect() {
|
||||||
{
|
|
||||||
WriteProtectMemory(region, region_size, true);
|
WriteProtectMemory(region, region_size, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResetCodePtr()
|
void ResetCodePtr() {
|
||||||
{
|
|
||||||
T::SetCodePtr(region);
|
T::SetCodePtr(region);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t GetSpaceLeft() const
|
size_t GetSpaceLeft() const {
|
||||||
{
|
|
||||||
return region_size - (T::GetCodePtr() - region);
|
return region_size - (T::GetCodePtr() - region);
|
||||||
}
|
}
|
||||||
|
|
||||||
u8 *GetBasePtr() {
|
u8* GetBasePtr() {
|
||||||
return region;
|
return region;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t GetOffset(const u8 *ptr) const {
|
size_t GetOffset(const u8* ptr) const {
|
||||||
return ptr - region;
|
return ptr - region;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -56,7 +56,7 @@ constexpr u8 Convert8To6(u8 value) {
|
||||||
* @return Result color decoded as Math::Vec4<u8>
|
* @return Result color decoded as Math::Vec4<u8>
|
||||||
*/
|
*/
|
||||||
inline const Math::Vec4<u8> DecodeRGBA8(const u8* bytes) {
|
inline const Math::Vec4<u8> DecodeRGBA8(const u8* bytes) {
|
||||||
return { bytes[3], bytes[2], bytes[1], bytes[0] };
|
return {bytes[3], bytes[2], bytes[1], bytes[0]};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,7 +65,7 @@ inline const Math::Vec4<u8> DecodeRGBA8(const u8* bytes) {
|
||||||
* @return Result color decoded as Math::Vec4<u8>
|
* @return Result color decoded as Math::Vec4<u8>
|
||||||
*/
|
*/
|
||||||
inline const Math::Vec4<u8> DecodeRGB8(const u8* bytes) {
|
inline const Math::Vec4<u8> DecodeRGB8(const u8* bytes) {
|
||||||
return { bytes[2], bytes[1], bytes[0], 255 };
|
return {bytes[2], bytes[1], bytes[0], 255};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,7 +74,7 @@ inline const Math::Vec4<u8> DecodeRGB8(const u8* bytes) {
|
||||||
* @return Result color decoded as Math::Vec4<u8>
|
* @return Result color decoded as Math::Vec4<u8>
|
||||||
*/
|
*/
|
||||||
inline const Math::Vec4<u8> DecodeRG8(const u8* bytes) {
|
inline const Math::Vec4<u8> DecodeRG8(const u8* bytes) {
|
||||||
return { bytes[1], bytes[0], 0, 255 };
|
return {bytes[1], bytes[0], 0, 255};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,8 +84,8 @@ inline const Math::Vec4<u8> DecodeRG8(const u8* bytes) {
|
||||||
*/
|
*/
|
||||||
inline const Math::Vec4<u8> DecodeRGB565(const u8* bytes) {
|
inline const Math::Vec4<u8> DecodeRGB565(const u8* bytes) {
|
||||||
const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes);
|
const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes);
|
||||||
return { Convert5To8((pixel >> 11) & 0x1F), Convert6To8((pixel >> 5) & 0x3F),
|
return {Convert5To8((pixel >> 11) & 0x1F), Convert6To8((pixel >> 5) & 0x3F),
|
||||||
Convert5To8(pixel & 0x1F), 255 };
|
Convert5To8(pixel & 0x1F), 255};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,8 +95,8 @@ inline const Math::Vec4<u8> DecodeRGB565(const u8* bytes) {
|
||||||
*/
|
*/
|
||||||
inline const Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
|
inline const Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
|
||||||
const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes);
|
const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes);
|
||||||
return { Convert5To8((pixel >> 11) & 0x1F), Convert5To8((pixel >> 6) & 0x1F),
|
return {Convert5To8((pixel >> 11) & 0x1F), Convert5To8((pixel >> 6) & 0x1F),
|
||||||
Convert5To8((pixel >> 1) & 0x1F), Convert1To8(pixel & 0x1) };
|
Convert5To8((pixel >> 1) & 0x1F), Convert1To8(pixel & 0x1)};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -106,8 +106,8 @@ inline const Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
|
||||||
*/
|
*/
|
||||||
inline const Math::Vec4<u8> DecodeRGBA4(const u8* bytes) {
|
inline const Math::Vec4<u8> DecodeRGBA4(const u8* bytes) {
|
||||||
const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes);
|
const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes);
|
||||||
return { Convert4To8((pixel >> 12) & 0xF), Convert4To8((pixel >> 8) & 0xF),
|
return {Convert4To8((pixel >> 12) & 0xF), Convert4To8((pixel >> 8) & 0xF),
|
||||||
Convert4To8((pixel >> 4) & 0xF), Convert4To8(pixel & 0xF) };
|
Convert4To8((pixel >> 4) & 0xF), Convert4To8(pixel & 0xF)};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -134,7 +134,7 @@ inline u32 DecodeD24(const u8* bytes) {
|
||||||
* @return Resulting values stored as a Math::Vec2
|
* @return Resulting values stored as a Math::Vec2
|
||||||
*/
|
*/
|
||||||
inline const Math::Vec2<u32> DecodeD24S8(const u8* bytes) {
|
inline const Math::Vec2<u32> DecodeD24S8(const u8* bytes) {
|
||||||
return { static_cast<u32>((bytes[2] << 16) | (bytes[1] << 8) | bytes[0]), bytes[3] };
|
return {static_cast<u32>((bytes[2] << 16) | (bytes[1] << 8) | bytes[0]), bytes[3]};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -175,8 +175,8 @@ inline void EncodeRG8(const Math::Vec4<u8>& color, u8* bytes) {
|
||||||
* @param bytes Destination pointer to store encoded color
|
* @param bytes Destination pointer to store encoded color
|
||||||
*/
|
*/
|
||||||
inline void EncodeRGB565(const Math::Vec4<u8>& color, u8* bytes) {
|
inline void EncodeRGB565(const Math::Vec4<u8>& color, u8* bytes) {
|
||||||
*reinterpret_cast<u16_le*>(bytes) = (Convert8To5(color.r()) << 11) |
|
*reinterpret_cast<u16_le*>(bytes) =
|
||||||
(Convert8To6(color.g()) << 5) | Convert8To5(color.b());
|
(Convert8To5(color.r()) << 11) | (Convert8To6(color.g()) << 5) | Convert8To5(color.b());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -186,7 +186,8 @@ inline void EncodeRGB565(const Math::Vec4<u8>& color, u8* bytes) {
|
||||||
*/
|
*/
|
||||||
inline void EncodeRGB5A1(const Math::Vec4<u8>& color, u8* bytes) {
|
inline void EncodeRGB5A1(const Math::Vec4<u8>& color, u8* bytes) {
|
||||||
*reinterpret_cast<u16_le*>(bytes) = (Convert8To5(color.r()) << 11) |
|
*reinterpret_cast<u16_le*>(bytes) = (Convert8To5(color.r()) << 11) |
|
||||||
(Convert8To5(color.g()) << 6) | (Convert8To5(color.b()) << 1) | Convert8To1(color.a());
|
(Convert8To5(color.g()) << 6) |
|
||||||
|
(Convert8To5(color.b()) << 1) | Convert8To1(color.a());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -196,7 +197,8 @@ inline void EncodeRGB5A1(const Math::Vec4<u8>& color, u8* bytes) {
|
||||||
*/
|
*/
|
||||||
inline void EncodeRGBA4(const Math::Vec4<u8>& color, u8* bytes) {
|
inline void EncodeRGBA4(const Math::Vec4<u8>& color, u8* bytes) {
|
||||||
*reinterpret_cast<u16_le*>(bytes) = (Convert8To4(color.r()) << 12) |
|
*reinterpret_cast<u16_le*>(bytes) = (Convert8To4(color.r()) << 12) |
|
||||||
(Convert8To4(color.g()) << 8) | (Convert8To4(color.b()) << 4) | Convert8To4(color.a());
|
(Convert8To4(color.g()) << 8) |
|
||||||
|
(Convert8To4(color.b()) << 4) | Convert8To4(color.a());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
/// Textually concatenates two tokens. The double-expansion is required by the C preprocessor.
|
/// Textually concatenates two tokens. The double-expansion is required by the C preprocessor.
|
||||||
#define CONCAT2(x, y) DO_CONCAT2(x, y)
|
#define CONCAT2(x, y) DO_CONCAT2(x, y)
|
||||||
#define DO_CONCAT2(x, y) x ## y
|
#define DO_CONCAT2(x, y) x##y
|
||||||
|
|
||||||
// helper macro to properly align structure members.
|
// helper macro to properly align structure members.
|
||||||
// Calling INSERT_PADDING_BYTES will add a new member variable with a name like "pad121",
|
// Calling INSERT_PADDING_BYTES will add a new member variable with a name like "pad121",
|
||||||
|
@ -24,9 +24,9 @@
|
||||||
|
|
||||||
// Inlining
|
// Inlining
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define FORCE_INLINE __forceinline
|
#define FORCE_INLINE __forceinline
|
||||||
#else
|
#else
|
||||||
#define FORCE_INLINE inline __attribute__((always_inline))
|
#define FORCE_INLINE inline __attribute__((always_inline))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef _MSC_VER
|
#ifndef _MSC_VER
|
||||||
|
@ -46,7 +46,8 @@
|
||||||
#else
|
#else
|
||||||
inline u32 rotl(u32 x, int shift) {
|
inline u32 rotl(u32 x, int shift) {
|
||||||
shift &= 31;
|
shift &= 31;
|
||||||
if (!shift) return x;
|
if (!shift)
|
||||||
|
return x;
|
||||||
return (x << shift) | (x >> (32 - shift));
|
return (x << shift) | (x >> (32 - shift));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -56,17 +57,18 @@ inline u32 rotl(u32 x, int shift) {
|
||||||
#else
|
#else
|
||||||
inline u32 rotr(u32 x, int shift) {
|
inline u32 rotr(u32 x, int shift) {
|
||||||
shift &= 31;
|
shift &= 31;
|
||||||
if (!shift) return x;
|
if (!shift)
|
||||||
|
return x;
|
||||||
return (x >> shift) | (x << (32 - shift));
|
return (x >> shift) | (x << (32 - shift));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
inline u64 _rotl64(u64 x, unsigned int shift){
|
inline u64 _rotl64(u64 x, unsigned int shift) {
|
||||||
unsigned int n = shift % 64;
|
unsigned int n = shift % 64;
|
||||||
return (x << n) | (x >> (64 - n));
|
return (x << n) | (x >> (64 - n));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline u64 _rotr64(u64 x, unsigned int shift){
|
inline u64 _rotr64(u64 x, unsigned int shift) {
|
||||||
unsigned int n = shift % 64;
|
unsigned int n = shift % 64;
|
||||||
return (x >> n) | (x << (64 - n));
|
return (x >> n) | (x << (64 - n));
|
||||||
}
|
}
|
||||||
|
@ -74,17 +76,18 @@ inline u64 _rotr64(u64 x, unsigned int shift){
|
||||||
#else // _MSC_VER
|
#else // _MSC_VER
|
||||||
|
|
||||||
#if (_MSC_VER < 1900)
|
#if (_MSC_VER < 1900)
|
||||||
// Function Cross-Compatibility
|
// Function Cross-Compatibility
|
||||||
#define snprintf _snprintf
|
#define snprintf _snprintf
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Locale Cross-Compatibility
|
// Locale Cross-Compatibility
|
||||||
#define locale_t _locale_t
|
#define locale_t _locale_t
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
__declspec(dllimport) void __stdcall DebugBreak(void);
|
__declspec(dllimport) void __stdcall DebugBreak(void);
|
||||||
}
|
}
|
||||||
#define Crash() {DebugBreak();}
|
#define Crash() \
|
||||||
|
{ DebugBreak(); }
|
||||||
|
|
||||||
// cstdlib provides these on MSVC
|
// cstdlib provides these on MSVC
|
||||||
#define rotr _rotr
|
#define rotr _rotr
|
||||||
|
|
|
@ -16,13 +16,13 @@
|
||||||
#define ROOT_DIR "."
|
#define ROOT_DIR "."
|
||||||
#define USERDATA_DIR "user"
|
#define USERDATA_DIR "user"
|
||||||
#ifdef USER_DIR
|
#ifdef USER_DIR
|
||||||
#define EMU_DATA_DIR USER_DIR
|
#define EMU_DATA_DIR USER_DIR
|
||||||
#else
|
#else
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define EMU_DATA_DIR "Citra Emulator"
|
#define EMU_DATA_DIR "Citra Emulator"
|
||||||
#else
|
#else
|
||||||
#define EMU_DATA_DIR "citra-emu"
|
#define EMU_DATA_DIR "citra-emu"
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Dirs in both User and Sys
|
// Dirs in both User and Sys
|
||||||
|
|
|
@ -44,18 +44,17 @@ void EmuWindow::CirclePadUpdated(float x, float y) {
|
||||||
*/
|
*/
|
||||||
static bool IsWithinTouchscreen(const EmuWindow::FramebufferLayout& layout, unsigned framebuffer_x,
|
static bool IsWithinTouchscreen(const EmuWindow::FramebufferLayout& layout, unsigned framebuffer_x,
|
||||||
unsigned framebuffer_y) {
|
unsigned framebuffer_y) {
|
||||||
return (framebuffer_y >= layout.bottom_screen.top &&
|
return (
|
||||||
framebuffer_y < layout.bottom_screen.bottom &&
|
framebuffer_y >= layout.bottom_screen.top && framebuffer_y < layout.bottom_screen.bottom &&
|
||||||
framebuffer_x >= layout.bottom_screen.left &&
|
framebuffer_x >= layout.bottom_screen.left && framebuffer_x < layout.bottom_screen.right);
|
||||||
framebuffer_x < layout.bottom_screen.right);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tuple<unsigned,unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsigned new_y) {
|
std::tuple<unsigned, unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsigned new_y) {
|
||||||
new_x = std::max(new_x, framebuffer_layout.bottom_screen.left);
|
new_x = std::max(new_x, framebuffer_layout.bottom_screen.left);
|
||||||
new_x = std::min(new_x, framebuffer_layout.bottom_screen.right-1);
|
new_x = std::min(new_x, framebuffer_layout.bottom_screen.right - 1);
|
||||||
|
|
||||||
new_y = std::max(new_y, framebuffer_layout.bottom_screen.top);
|
new_y = std::max(new_y, framebuffer_layout.bottom_screen.top);
|
||||||
new_y = std::min(new_y, framebuffer_layout.bottom_screen.bottom-1);
|
new_y = std::min(new_y, framebuffer_layout.bottom_screen.bottom - 1);
|
||||||
|
|
||||||
return std::make_tuple(new_x, new_y);
|
return std::make_tuple(new_x, new_y);
|
||||||
}
|
}
|
||||||
|
@ -64,9 +63,11 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
|
||||||
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
|
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
touch_x = VideoCore::kScreenBottomWidth * (framebuffer_x - framebuffer_layout.bottom_screen.left) /
|
touch_x = VideoCore::kScreenBottomWidth *
|
||||||
|
(framebuffer_x - framebuffer_layout.bottom_screen.left) /
|
||||||
(framebuffer_layout.bottom_screen.right - framebuffer_layout.bottom_screen.left);
|
(framebuffer_layout.bottom_screen.right - framebuffer_layout.bottom_screen.left);
|
||||||
touch_y = VideoCore::kScreenBottomHeight * (framebuffer_y - framebuffer_layout.bottom_screen.top) /
|
touch_y = VideoCore::kScreenBottomHeight *
|
||||||
|
(framebuffer_y - framebuffer_layout.bottom_screen.top) /
|
||||||
(framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top);
|
(framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top);
|
||||||
|
|
||||||
touch_pressed = true;
|
touch_pressed = true;
|
||||||
|
@ -90,16 +91,19 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
|
||||||
TouchPressed(framebuffer_x, framebuffer_y);
|
TouchPressed(framebuffer_x, framebuffer_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
EmuWindow::FramebufferLayout EmuWindow::FramebufferLayout::DefaultScreenLayout(unsigned width, unsigned height) {
|
EmuWindow::FramebufferLayout EmuWindow::FramebufferLayout::DefaultScreenLayout(unsigned width,
|
||||||
|
unsigned height) {
|
||||||
// When hiding the widget, the function receives a size of 0
|
// When hiding the widget, the function receives a size of 0
|
||||||
if (width == 0) width = 1;
|
if (width == 0)
|
||||||
if (height == 0) height = 1;
|
width = 1;
|
||||||
|
if (height == 0)
|
||||||
|
height = 1;
|
||||||
|
|
||||||
EmuWindow::FramebufferLayout res = { width, height, {}, {} };
|
EmuWindow::FramebufferLayout res = {width, height, {}, {}};
|
||||||
|
|
||||||
float window_aspect_ratio = static_cast<float>(height) / width;
|
float window_aspect_ratio = static_cast<float>(height) / width;
|
||||||
float emulation_aspect_ratio = static_cast<float>(VideoCore::kScreenTopHeight * 2) /
|
float emulation_aspect_ratio =
|
||||||
VideoCore::kScreenTopWidth;
|
static_cast<float>(VideoCore::kScreenTopHeight * 2) / VideoCore::kScreenTopWidth;
|
||||||
|
|
||||||
if (window_aspect_ratio > emulation_aspect_ratio) {
|
if (window_aspect_ratio > emulation_aspect_ratio) {
|
||||||
// Window is narrower than the emulation content => apply borders to the top and bottom
|
// Window is narrower than the emulation content => apply borders to the top and bottom
|
||||||
|
@ -110,8 +114,9 @@ EmuWindow::FramebufferLayout EmuWindow::FramebufferLayout::DefaultScreenLayout(u
|
||||||
res.top_screen.top = (height - viewport_height) / 2;
|
res.top_screen.top = (height - viewport_height) / 2;
|
||||||
res.top_screen.bottom = res.top_screen.top + viewport_height / 2;
|
res.top_screen.bottom = res.top_screen.top + viewport_height / 2;
|
||||||
|
|
||||||
int bottom_width = static_cast<int>((static_cast<float>(VideoCore::kScreenBottomWidth) /
|
int bottom_width = static_cast<int>(
|
||||||
VideoCore::kScreenTopWidth) * (res.top_screen.right - res.top_screen.left));
|
(static_cast<float>(VideoCore::kScreenBottomWidth) / VideoCore::kScreenTopWidth) *
|
||||||
|
(res.top_screen.right - res.top_screen.left));
|
||||||
int bottom_border = ((res.top_screen.right - res.top_screen.left) - bottom_width) / 2;
|
int bottom_border = ((res.top_screen.right - res.top_screen.left) - bottom_width) / 2;
|
||||||
|
|
||||||
res.bottom_screen.left = bottom_border;
|
res.bottom_screen.left = bottom_border;
|
||||||
|
@ -127,8 +132,9 @@ EmuWindow::FramebufferLayout EmuWindow::FramebufferLayout::DefaultScreenLayout(u
|
||||||
res.top_screen.top = 0;
|
res.top_screen.top = 0;
|
||||||
res.top_screen.bottom = res.top_screen.top + height / 2;
|
res.top_screen.bottom = res.top_screen.top + height / 2;
|
||||||
|
|
||||||
int bottom_width = static_cast<int>((static_cast<float>(VideoCore::kScreenBottomWidth) /
|
int bottom_width = static_cast<int>(
|
||||||
VideoCore::kScreenTopWidth) * (res.top_screen.right - res.top_screen.left));
|
(static_cast<float>(VideoCore::kScreenBottomWidth) / VideoCore::kScreenTopWidth) *
|
||||||
|
(res.top_screen.right - res.top_screen.left));
|
||||||
int bottom_border = ((res.top_screen.right - res.top_screen.left) - bottom_width) / 2;
|
int bottom_border = ((res.top_screen.right - res.top_screen.left) - bottom_width) / 2;
|
||||||
|
|
||||||
res.bottom_screen.left = res.top_screen.left + bottom_border;
|
res.bottom_screen.left = res.top_screen.left + bottom_border;
|
||||||
|
|
|
@ -30,15 +30,14 @@
|
||||||
* - DO NOT TREAT THIS CLASS AS A GUI TOOLKIT ABSTRACTION LAYER. That's not what it is. Please
|
* - DO NOT TREAT THIS CLASS AS A GUI TOOLKIT ABSTRACTION LAYER. That's not what it is. Please
|
||||||
* re-read the upper points again and think about it if you don't see this.
|
* re-read the upper points again and think about it if you don't see this.
|
||||||
*/
|
*/
|
||||||
class EmuWindow
|
class EmuWindow {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
/// Data structure to store emuwindow configuration
|
/// Data structure to store emuwindow configuration
|
||||||
struct WindowConfig {
|
struct WindowConfig {
|
||||||
bool fullscreen;
|
bool fullscreen;
|
||||||
int res_width;
|
int res_width;
|
||||||
int res_height;
|
int res_height;
|
||||||
std::pair<unsigned,unsigned> min_client_area_size;
|
std::pair<unsigned, unsigned> min_client_area_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Describes the layout of the window framebuffer (size and top/bottom screen positions)
|
/// Describes the layout of the window framebuffer (size and top/bottom screen positions)
|
||||||
|
@ -193,15 +192,18 @@ public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns currently active configuration.
|
* Returns currently active configuration.
|
||||||
* @note Accesses to the returned object need not be consistent because it may be modified in another thread
|
* @note Accesses to the returned object need not be consistent because it may be modified in
|
||||||
|
* another thread
|
||||||
*/
|
*/
|
||||||
const WindowConfig& GetActiveConfig() const {
|
const WindowConfig& GetActiveConfig() const {
|
||||||
return active_config;
|
return active_config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests the internal configuration to be replaced by the specified argument at some point in the future.
|
* Requests the internal configuration to be replaced by the specified argument at some point in
|
||||||
* @note This method is thread-safe, because it delays configuration changes to the GUI event loop. Hence there is no guarantee on when the requested configuration will be active.
|
* the future.
|
||||||
|
* @note This method is thread-safe, because it delays configuration changes to the GUI event
|
||||||
|
* loop. Hence there is no guarantee on when the requested configuration will be active.
|
||||||
*/
|
*/
|
||||||
void SetConfig(const WindowConfig& val) {
|
void SetConfig(const WindowConfig& val) {
|
||||||
config = val;
|
config = val;
|
||||||
|
@ -227,7 +229,8 @@ protected:
|
||||||
circle_pad_y = 0;
|
circle_pad_y = 0;
|
||||||
touch_pressed = false;
|
touch_pressed = false;
|
||||||
}
|
}
|
||||||
virtual ~EmuWindow() {}
|
virtual ~EmuWindow() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes any pending configuration changes from the last SetConfig call.
|
* Processes any pending configuration changes from the last SetConfig call.
|
||||||
|
@ -258,7 +261,7 @@ protected:
|
||||||
* Update internal client area size with the given parameter.
|
* Update internal client area size with the given parameter.
|
||||||
* @note EmuWindow implementations will usually use this in window resize event handlers.
|
* @note EmuWindow implementations will usually use this in window resize event handlers.
|
||||||
*/
|
*/
|
||||||
void NotifyClientAreaSizeChanged(const std::pair<unsigned,unsigned>& size) {
|
void NotifyClientAreaSizeChanged(const std::pair<unsigned, unsigned>& size) {
|
||||||
client_area_width = size.first;
|
client_area_width = size.first;
|
||||||
client_area_height = size.second;
|
client_area_height = size.second;
|
||||||
}
|
}
|
||||||
|
@ -266,9 +269,11 @@ protected:
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Handler called when the minimal client area was requested to be changed via SetConfig.
|
* Handler called when the minimal client area was requested to be changed via SetConfig.
|
||||||
* For the request to be honored, EmuWindow implementations will usually reimplement this function.
|
* For the request to be honored, EmuWindow implementations will usually reimplement this
|
||||||
|
* function.
|
||||||
*/
|
*/
|
||||||
virtual void OnMinimalClientAreaChangeRequest(const std::pair<unsigned,unsigned>& minimal_size) {
|
virtual void
|
||||||
|
OnMinimalClientAreaChangeRequest(const std::pair<unsigned, unsigned>& minimal_size) {
|
||||||
// By default, ignore this request and do nothing.
|
// By default, ignore this request and do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,7 +282,8 @@ private:
|
||||||
unsigned client_area_width; ///< Current client width, should be set by window impl.
|
unsigned client_area_width; ///< Current client width, should be set by window impl.
|
||||||
unsigned client_area_height; ///< Current client height, should be set by window impl.
|
unsigned client_area_height; ///< Current client height, should be set by window impl.
|
||||||
|
|
||||||
WindowConfig config; ///< Internal configuration (changes pending for being applied in ProcessConfigurationChanges)
|
WindowConfig config; ///< Internal configuration (changes pending for being applied in
|
||||||
|
/// ProcessConfigurationChanges)
|
||||||
WindowConfig active_config; ///< Internal active configuration
|
WindowConfig active_config; ///< Internal active configuration
|
||||||
|
|
||||||
bool touch_pressed; ///< True if touchpad area is currently pressed, otherwise false
|
bool touch_pressed; ///< True if touchpad area is currently pressed, otherwise false
|
||||||
|
@ -291,7 +297,7 @@ private:
|
||||||
/**
|
/**
|
||||||
* Clip the provided coordinates to be inside the touchscreen area.
|
* Clip the provided coordinates to be inside the touchscreen area.
|
||||||
*/
|
*/
|
||||||
std::tuple<unsigned,unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y);
|
std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y);
|
||||||
|
|
||||||
Service::HID::PadState pad_state;
|
Service::HID::PadState pad_state;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,73 +2,70 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/file_util.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "common/common_paths.h"
|
#include "common/common_paths.h"
|
||||||
#include "common/file_util.h"
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <shlobj.h> // for SHGetFolderPath
|
#include <commdlg.h> // for GetSaveFileName
|
||||||
#include <shellapi.h>
|
#include <direct.h> // getcwd
|
||||||
#include <commdlg.h> // for GetSaveFileName
|
#include <io.h>
|
||||||
#include <io.h>
|
#include <shellapi.h>
|
||||||
#include <direct.h> // getcwd
|
#include <shlobj.h> // for SHGetFolderPath
|
||||||
#include <tchar.h>
|
#include <tchar.h>
|
||||||
|
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
|
|
||||||
// 64 bit offsets for windows
|
// 64 bit offsets for windows
|
||||||
#define fseeko _fseeki64
|
#define fseeko _fseeki64
|
||||||
#define ftello _ftelli64
|
#define ftello _ftelli64
|
||||||
#define atoll _atoi64
|
#define atoll _atoi64
|
||||||
#define stat64 _stat64
|
#define stat64 _stat64
|
||||||
#define fstat64 _fstat64
|
#define fstat64 _fstat64
|
||||||
#define fileno _fileno
|
#define fileno _fileno
|
||||||
#else
|
#else
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
#endif
|
#endif
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
#include <CoreFoundation/CFString.h>
|
#include <CoreFoundation/CFBundle.h>
|
||||||
#include <CoreFoundation/CFURL.h>
|
#include <CoreFoundation/CFString.h>
|
||||||
#include <CoreFoundation/CFBundle.h>
|
#include <CoreFoundation/CFURL.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#ifndef S_ISDIR
|
#ifndef S_ISDIR
|
||||||
#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
|
#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef BSD4_4
|
#ifdef BSD4_4
|
||||||
#define stat64 stat
|
#define stat64 stat
|
||||||
#define fstat64 fstat
|
#define fstat64 fstat
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// This namespace has various generic functions related to files and paths.
|
// This namespace has various generic functions related to files and paths.
|
||||||
// The code still needs a ton of cleanup.
|
// The code still needs a ton of cleanup.
|
||||||
// REMEMBER: strdup considered harmful!
|
// REMEMBER: strdup considered harmful!
|
||||||
namespace FileUtil
|
namespace FileUtil {
|
||||||
{
|
|
||||||
|
|
||||||
// Remove any ending forward slashes from directory paths
|
// Remove any ending forward slashes from directory paths
|
||||||
// Modifies argument.
|
// Modifies argument.
|
||||||
static void StripTailDirSlashes(std::string &fname)
|
static void StripTailDirSlashes(std::string& fname) {
|
||||||
{
|
if (fname.length() > 1) {
|
||||||
if (fname.length() > 1)
|
|
||||||
{
|
|
||||||
size_t i = fname.length();
|
size_t i = fname.length();
|
||||||
while (i > 0 && fname[i - 1] == DIR_SEP_CHR)
|
while (i > 0 && fname[i - 1] == DIR_SEP_CHR)
|
||||||
--i;
|
--i;
|
||||||
|
@ -78,8 +75,7 @@ static void StripTailDirSlashes(std::string &fname)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if file filename exists
|
// Returns true if file filename exists
|
||||||
bool Exists(const std::string &filename)
|
bool Exists(const std::string& filename) {
|
||||||
{
|
|
||||||
struct stat64 file_info;
|
struct stat64 file_info;
|
||||||
|
|
||||||
std::string copy(filename);
|
std::string copy(filename);
|
||||||
|
@ -99,8 +95,7 @@ bool Exists(const std::string &filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if filename is a directory
|
// Returns true if filename is a directory
|
||||||
bool IsDirectory(const std::string &filename)
|
bool IsDirectory(const std::string& filename) {
|
||||||
{
|
|
||||||
struct stat64 file_info;
|
struct stat64 file_info;
|
||||||
|
|
||||||
std::string copy(filename);
|
std::string copy(filename);
|
||||||
|
@ -117,8 +112,8 @@ bool IsDirectory(const std::string &filename)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
LOG_WARNING(Common_Filesystem, "stat failed on %s: %s",
|
LOG_WARNING(Common_Filesystem, "stat failed on %s: %s", filename.c_str(),
|
||||||
filename.c_str(), GetLastErrorMsg());
|
GetLastErrorMsg());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,36 +122,32 @@ bool IsDirectory(const std::string &filename)
|
||||||
|
|
||||||
// Deletes a given filename, return true on success
|
// Deletes a given filename, return true on success
|
||||||
// Doesn't supports deleting a directory
|
// Doesn't supports deleting a directory
|
||||||
bool Delete(const std::string &filename)
|
bool Delete(const std::string& filename) {
|
||||||
{
|
|
||||||
LOG_INFO(Common_Filesystem, "file %s", filename.c_str());
|
LOG_INFO(Common_Filesystem, "file %s", filename.c_str());
|
||||||
|
|
||||||
// Return true because we care about the file no
|
// Return true because we care about the file no
|
||||||
// being there, not the actual delete.
|
// being there, not the actual delete.
|
||||||
if (!Exists(filename))
|
if (!Exists(filename)) {
|
||||||
{
|
|
||||||
LOG_WARNING(Common_Filesystem, "%s does not exist", filename.c_str());
|
LOG_WARNING(Common_Filesystem, "%s does not exist", filename.c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can't delete a directory
|
// We can't delete a directory
|
||||||
if (IsDirectory(filename))
|
if (IsDirectory(filename)) {
|
||||||
{
|
|
||||||
LOG_ERROR(Common_Filesystem, "Failed: %s is a directory", filename.c_str());
|
LOG_ERROR(Common_Filesystem, "Failed: %s is a directory", filename.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str()))
|
if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) {
|
||||||
{
|
LOG_ERROR(Common_Filesystem, "DeleteFile failed on %s: %s", filename.c_str(),
|
||||||
LOG_ERROR(Common_Filesystem, "DeleteFile failed on %s: %s",
|
GetLastErrorMsg());
|
||||||
filename.c_str(), GetLastErrorMsg());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (unlink(filename.c_str()) == -1) {
|
if (unlink(filename.c_str()) == -1) {
|
||||||
LOG_ERROR(Common_Filesystem, "unlink failed on %s: %s",
|
LOG_ERROR(Common_Filesystem, "unlink failed on %s: %s", filename.c_str(),
|
||||||
filename.c_str(), GetLastErrorMsg());
|
GetLastErrorMsg());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -165,16 +156,15 @@ bool Delete(const std::string &filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if successful, or path already exists.
|
// Returns true if successful, or path already exists.
|
||||||
bool CreateDir(const std::string &path)
|
bool CreateDir(const std::string& path) {
|
||||||
{
|
|
||||||
LOG_TRACE(Common_Filesystem, "directory %s", path.c_str());
|
LOG_TRACE(Common_Filesystem, "directory %s", path.c_str());
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr))
|
if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr))
|
||||||
return true;
|
return true;
|
||||||
DWORD error = GetLastError();
|
DWORD error = GetLastError();
|
||||||
if (error == ERROR_ALREADY_EXISTS)
|
if (error == ERROR_ALREADY_EXISTS) {
|
||||||
{
|
LOG_WARNING(Common_Filesystem, "CreateDirectory failed on %s: already exists",
|
||||||
LOG_WARNING(Common_Filesystem, "CreateDirectory failed on %s: already exists", path.c_str());
|
path.c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
LOG_ERROR(Common_Filesystem, "CreateDirectory failed on %s: %i", path.c_str(), error);
|
LOG_ERROR(Common_Filesystem, "CreateDirectory failed on %s: %i", path.c_str(), error);
|
||||||
|
@ -185,8 +175,7 @@ bool CreateDir(const std::string &path)
|
||||||
|
|
||||||
int err = errno;
|
int err = errno;
|
||||||
|
|
||||||
if (err == EEXIST)
|
if (err == EEXIST) {
|
||||||
{
|
|
||||||
LOG_WARNING(Common_Filesystem, "mkdir failed on %s: already exists", path.c_str());
|
LOG_WARNING(Common_Filesystem, "mkdir failed on %s: already exists", path.c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -197,20 +186,17 @@ bool CreateDir(const std::string &path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates the full path of fullPath returns true on success
|
// Creates the full path of fullPath returns true on success
|
||||||
bool CreateFullPath(const std::string &fullPath)
|
bool CreateFullPath(const std::string& fullPath) {
|
||||||
{
|
|
||||||
int panicCounter = 100;
|
int panicCounter = 100;
|
||||||
LOG_TRACE(Common_Filesystem, "path %s", fullPath.c_str());
|
LOG_TRACE(Common_Filesystem, "path %s", fullPath.c_str());
|
||||||
|
|
||||||
if (FileUtil::Exists(fullPath))
|
if (FileUtil::Exists(fullPath)) {
|
||||||
{
|
|
||||||
LOG_WARNING(Common_Filesystem, "path exists %s", fullPath.c_str());
|
LOG_WARNING(Common_Filesystem, "path exists %s", fullPath.c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t position = 0;
|
size_t position = 0;
|
||||||
while (true)
|
while (true) {
|
||||||
{
|
|
||||||
// Find next sub path
|
// Find next sub path
|
||||||
position = fullPath.find(DIR_SEP_CHR, position);
|
position = fullPath.find(DIR_SEP_CHR, position);
|
||||||
|
|
||||||
|
@ -227,8 +213,7 @@ bool CreateFullPath(const std::string &fullPath)
|
||||||
|
|
||||||
// A safety check
|
// A safety check
|
||||||
panicCounter--;
|
panicCounter--;
|
||||||
if (panicCounter <= 0)
|
if (panicCounter <= 0) {
|
||||||
{
|
|
||||||
LOG_ERROR(Common, "CreateFullPath: directory structure is too deep");
|
LOG_ERROR(Common, "CreateFullPath: directory structure is too deep");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -236,15 +221,12 @@ bool CreateFullPath(const std::string &fullPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Deletes a directory filename, returns true on success
|
// Deletes a directory filename, returns true on success
|
||||||
bool DeleteDir(const std::string &filename)
|
bool DeleteDir(const std::string& filename) {
|
||||||
{
|
|
||||||
LOG_INFO(Common_Filesystem, "directory %s", filename.c_str());
|
LOG_INFO(Common_Filesystem, "directory %s", filename.c_str());
|
||||||
|
|
||||||
// check if a directory
|
// check if a directory
|
||||||
if (!FileUtil::IsDirectory(filename))
|
if (!FileUtil::IsDirectory(filename)) {
|
||||||
{
|
|
||||||
LOG_ERROR(Common_Filesystem, "Not a directory %s", filename.c_str());
|
LOG_ERROR(Common_Filesystem, "Not a directory %s", filename.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -262,71 +244,63 @@ bool DeleteDir(const std::string &filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// renames file srcFilename to destFilename, returns true on success
|
// renames file srcFilename to destFilename, returns true on success
|
||||||
bool Rename(const std::string &srcFilename, const std::string &destFilename)
|
bool Rename(const std::string& srcFilename, const std::string& destFilename) {
|
||||||
{
|
LOG_TRACE(Common_Filesystem, "%s --> %s", srcFilename.c_str(), destFilename.c_str());
|
||||||
LOG_TRACE(Common_Filesystem, "%s --> %s",
|
|
||||||
srcFilename.c_str(), destFilename.c_str());
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(), Common::UTF8ToUTF16W(destFilename).c_str()) == 0)
|
if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(),
|
||||||
|
Common::UTF8ToUTF16W(destFilename).c_str()) == 0)
|
||||||
return true;
|
return true;
|
||||||
#else
|
#else
|
||||||
if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
|
if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
|
||||||
return true;
|
return true;
|
||||||
#endif
|
#endif
|
||||||
LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s",
|
LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(),
|
||||||
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
|
GetLastErrorMsg());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// copies file srcFilename to destFilename, returns true on success
|
// copies file srcFilename to destFilename, returns true on success
|
||||||
bool Copy(const std::string &srcFilename, const std::string &destFilename)
|
bool Copy(const std::string& srcFilename, const std::string& destFilename) {
|
||||||
{
|
LOG_TRACE(Common_Filesystem, "%s --> %s", srcFilename.c_str(), destFilename.c_str());
|
||||||
LOG_TRACE(Common_Filesystem, "%s --> %s",
|
|
||||||
srcFilename.c_str(), destFilename.c_str());
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(), Common::UTF8ToUTF16W(destFilename).c_str(), FALSE))
|
if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(),
|
||||||
|
Common::UTF8ToUTF16W(destFilename).c_str(), FALSE))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s",
|
LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(),
|
||||||
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
|
GetLastErrorMsg());
|
||||||
return false;
|
return false;
|
||||||
#else
|
#else
|
||||||
|
|
||||||
// buffer size
|
// buffer size
|
||||||
#define BSIZE 1024
|
#define BSIZE 1024
|
||||||
|
|
||||||
char buffer[BSIZE];
|
char buffer[BSIZE];
|
||||||
|
|
||||||
// Open input file
|
// Open input file
|
||||||
FILE *input = fopen(srcFilename.c_str(), "rb");
|
FILE* input = fopen(srcFilename.c_str(), "rb");
|
||||||
if (!input)
|
if (!input) {
|
||||||
{
|
LOG_ERROR(Common_Filesystem, "opening input failed %s --> %s: %s", srcFilename.c_str(),
|
||||||
LOG_ERROR(Common_Filesystem, "opening input failed %s --> %s: %s",
|
destFilename.c_str(), GetLastErrorMsg());
|
||||||
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// open output file
|
// open output file
|
||||||
FILE *output = fopen(destFilename.c_str(), "wb");
|
FILE* output = fopen(destFilename.c_str(), "wb");
|
||||||
if (!output)
|
if (!output) {
|
||||||
{
|
|
||||||
fclose(input);
|
fclose(input);
|
||||||
LOG_ERROR(Common_Filesystem, "opening output failed %s --> %s: %s",
|
LOG_ERROR(Common_Filesystem, "opening output failed %s --> %s: %s", srcFilename.c_str(),
|
||||||
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
|
destFilename.c_str(), GetLastErrorMsg());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy loop
|
// copy loop
|
||||||
while (!feof(input))
|
while (!feof(input)) {
|
||||||
{
|
|
||||||
// read input
|
// read input
|
||||||
int rnum = fread(buffer, sizeof(char), BSIZE, input);
|
int rnum = fread(buffer, sizeof(char), BSIZE, input);
|
||||||
if (rnum != BSIZE)
|
if (rnum != BSIZE) {
|
||||||
{
|
if (ferror(input) != 0) {
|
||||||
if (ferror(input) != 0)
|
LOG_ERROR(Common_Filesystem, "failed reading from source, %s --> %s: %s",
|
||||||
{
|
|
||||||
LOG_ERROR(Common_Filesystem,
|
|
||||||
"failed reading from source, %s --> %s: %s",
|
|
||||||
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
|
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
|
||||||
goto bail;
|
goto bail;
|
||||||
}
|
}
|
||||||
|
@ -334,10 +308,8 @@ bool Copy(const std::string &srcFilename, const std::string &destFilename)
|
||||||
|
|
||||||
// write output
|
// write output
|
||||||
int wnum = fwrite(buffer, sizeof(char), rnum, output);
|
int wnum = fwrite(buffer, sizeof(char), rnum, output);
|
||||||
if (wnum != rnum)
|
if (wnum != rnum) {
|
||||||
{
|
LOG_ERROR(Common_Filesystem, "failed writing to output, %s --> %s: %s",
|
||||||
LOG_ERROR(Common_Filesystem,
|
|
||||||
"failed writing to output, %s --> %s: %s",
|
|
||||||
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
|
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
|
||||||
goto bail;
|
goto bail;
|
||||||
}
|
}
|
||||||
|
@ -356,16 +328,13 @@ bail:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the size of filename (64bit)
|
// Returns the size of filename (64bit)
|
||||||
u64 GetSize(const std::string &filename)
|
u64 GetSize(const std::string& filename) {
|
||||||
{
|
if (!Exists(filename)) {
|
||||||
if (!Exists(filename))
|
|
||||||
{
|
|
||||||
LOG_ERROR(Common_Filesystem, "failed %s: No such file", filename.c_str());
|
LOG_ERROR(Common_Filesystem, "failed %s: No such file", filename.c_str());
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsDirectory(filename))
|
if (IsDirectory(filename)) {
|
||||||
{
|
|
||||||
LOG_ERROR(Common_Filesystem, "failed %s: is a directory", filename.c_str());
|
LOG_ERROR(Common_Filesystem, "failed %s: is a directory", filename.c_str());
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -377,65 +346,54 @@ u64 GetSize(const std::string &filename)
|
||||||
if (stat64(filename.c_str(), &buf) == 0)
|
if (stat64(filename.c_str(), &buf) == 0)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
LOG_TRACE(Common_Filesystem, "%s: %lld",
|
LOG_TRACE(Common_Filesystem, "%s: %lld", filename.c_str(), (long long)buf.st_size);
|
||||||
filename.c_str(), (long long)buf.st_size);
|
|
||||||
return buf.st_size;
|
return buf.st_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_ERROR(Common_Filesystem, "Stat failed %s: %s",
|
LOG_ERROR(Common_Filesystem, "Stat failed %s: %s", filename.c_str(), GetLastErrorMsg());
|
||||||
filename.c_str(), GetLastErrorMsg());
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overloaded GetSize, accepts file descriptor
|
// Overloaded GetSize, accepts file descriptor
|
||||||
u64 GetSize(const int fd)
|
u64 GetSize(const int fd) {
|
||||||
{
|
|
||||||
struct stat64 buf;
|
struct stat64 buf;
|
||||||
if (fstat64(fd, &buf) != 0) {
|
if (fstat64(fd, &buf) != 0) {
|
||||||
LOG_ERROR(Common_Filesystem, "GetSize: stat failed %i: %s",
|
LOG_ERROR(Common_Filesystem, "GetSize: stat failed %i: %s", fd, GetLastErrorMsg());
|
||||||
fd, GetLastErrorMsg());
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return buf.st_size;
|
return buf.st_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overloaded GetSize, accepts FILE*
|
// Overloaded GetSize, accepts FILE*
|
||||||
u64 GetSize(FILE *f)
|
u64 GetSize(FILE* f) {
|
||||||
{
|
|
||||||
// can't use off_t here because it can be 32-bit
|
// can't use off_t here because it can be 32-bit
|
||||||
u64 pos = ftello(f);
|
u64 pos = ftello(f);
|
||||||
if (fseeko(f, 0, SEEK_END) != 0) {
|
if (fseeko(f, 0, SEEK_END) != 0) {
|
||||||
LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s",
|
LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s", f, GetLastErrorMsg());
|
||||||
f, GetLastErrorMsg());
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
u64 size = ftello(f);
|
u64 size = ftello(f);
|
||||||
if ((size != pos) && (fseeko(f, pos, SEEK_SET) != 0)) {
|
if ((size != pos) && (fseeko(f, pos, SEEK_SET) != 0)) {
|
||||||
LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s",
|
LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s", f, GetLastErrorMsg());
|
||||||
f, GetLastErrorMsg());
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates an empty file filename, returns true on success
|
// creates an empty file filename, returns true on success
|
||||||
bool CreateEmptyFile(const std::string &filename)
|
bool CreateEmptyFile(const std::string& filename) {
|
||||||
{
|
|
||||||
LOG_TRACE(Common_Filesystem, "%s", filename.c_str());
|
LOG_TRACE(Common_Filesystem, "%s", filename.c_str());
|
||||||
|
|
||||||
if (!FileUtil::IOFile(filename, "wb"))
|
if (!FileUtil::IOFile(filename, "wb")) {
|
||||||
{
|
LOG_ERROR(Common_Filesystem, "failed %s: %s", filename.c_str(), GetLastErrorMsg());
|
||||||
LOG_ERROR(Common_Filesystem, "failed %s: %s",
|
|
||||||
filename.c_str(), GetLastErrorMsg());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string& directory,
|
||||||
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback)
|
DirectoryEntryCallable callback) {
|
||||||
{
|
|
||||||
LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str());
|
LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str());
|
||||||
|
|
||||||
// How many files + directories we found
|
// How many files + directories we found
|
||||||
|
@ -457,7 +415,7 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo
|
||||||
do {
|
do {
|
||||||
const std::string virtual_name(Common::UTF16ToUTF8(ffd.cFileName));
|
const std::string virtual_name(Common::UTF16ToUTF8(ffd.cFileName));
|
||||||
#else
|
#else
|
||||||
DIR *dirp = opendir(directory.c_str());
|
DIR* dirp = opendir(directory.c_str());
|
||||||
if (!dirp)
|
if (!dirp)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -493,8 +451,8 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry, unsigned int recursion)
|
unsigned ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
|
||||||
{
|
unsigned int recursion) {
|
||||||
const auto callback = [recursion, &parent_entry](unsigned* num_entries_out,
|
const auto callback = [recursion, &parent_entry](unsigned* num_entries_out,
|
||||||
const std::string& directory,
|
const std::string& directory,
|
||||||
const std::string& virtual_name) -> bool {
|
const std::string& virtual_name) -> bool {
|
||||||
|
@ -526,11 +484,8 @@ unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry,
|
||||||
return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
|
return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) {
|
||||||
bool DeleteDirRecursively(const std::string &directory, unsigned int recursion)
|
const auto callback = [recursion](unsigned* num_entries_out, const std::string& directory,
|
||||||
{
|
|
||||||
const auto callback = [recursion](unsigned* num_entries_out,
|
|
||||||
const std::string& directory,
|
|
||||||
const std::string& virtual_name) -> bool {
|
const std::string& virtual_name) -> bool {
|
||||||
std::string new_path = directory + DIR_SEP_CHR + virtual_name;
|
std::string new_path = directory + DIR_SEP_CHR + virtual_name;
|
||||||
|
|
||||||
|
@ -551,53 +506,53 @@ bool DeleteDirRecursively(const std::string &directory, unsigned int recursion)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create directory and copy contents (does not overwrite existing files)
|
// Create directory and copy contents (does not overwrite existing files)
|
||||||
void CopyDir(const std::string &source_path, const std::string &dest_path)
|
void CopyDir(const std::string& source_path, const std::string& dest_path) {
|
||||||
{
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
if (source_path == dest_path) return;
|
if (source_path == dest_path)
|
||||||
if (!FileUtil::Exists(source_path)) return;
|
return;
|
||||||
if (!FileUtil::Exists(dest_path)) FileUtil::CreateFullPath(dest_path);
|
if (!FileUtil::Exists(source_path))
|
||||||
|
return;
|
||||||
|
if (!FileUtil::Exists(dest_path))
|
||||||
|
FileUtil::CreateFullPath(dest_path);
|
||||||
|
|
||||||
DIR *dirp = opendir(source_path.c_str());
|
DIR* dirp = opendir(source_path.c_str());
|
||||||
if (!dirp) return;
|
if (!dirp)
|
||||||
|
return;
|
||||||
|
|
||||||
while (struct dirent* result = readdir(dirp)) {
|
while (struct dirent* result = readdir(dirp)) {
|
||||||
const std::string virtualName(result->d_name);
|
const std::string virtualName(result->d_name);
|
||||||
// check for "." and ".."
|
// check for "." and ".."
|
||||||
if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
|
if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
|
||||||
((virtualName[0] == '.') && (virtualName[1] == '.') &&
|
((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0')))
|
||||||
(virtualName[2] == '\0')))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
std::string source, dest;
|
std::string source, dest;
|
||||||
source = source_path + virtualName;
|
source = source_path + virtualName;
|
||||||
dest = dest_path + virtualName;
|
dest = dest_path + virtualName;
|
||||||
if (IsDirectory(source))
|
if (IsDirectory(source)) {
|
||||||
{
|
|
||||||
source += '/';
|
source += '/';
|
||||||
dest += '/';
|
dest += '/';
|
||||||
if (!FileUtil::Exists(dest)) FileUtil::CreateFullPath(dest);
|
if (!FileUtil::Exists(dest))
|
||||||
|
FileUtil::CreateFullPath(dest);
|
||||||
CopyDir(source, dest);
|
CopyDir(source, dest);
|
||||||
}
|
} else if (!FileUtil::Exists(dest))
|
||||||
else if (!FileUtil::Exists(dest)) FileUtil::Copy(source, dest);
|
FileUtil::Copy(source, dest);
|
||||||
}
|
}
|
||||||
closedir(dirp);
|
closedir(dirp);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the current directory
|
// Returns the current directory
|
||||||
std::string GetCurrentDir()
|
std::string GetCurrentDir() {
|
||||||
{
|
// Get the current working directory (getcwd uses malloc)
|
||||||
// Get the current working directory (getcwd uses malloc)
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
wchar_t *dir;
|
wchar_t* dir;
|
||||||
if (!(dir = _wgetcwd(nullptr, 0))) {
|
if (!(dir = _wgetcwd(nullptr, 0))) {
|
||||||
#else
|
#else
|
||||||
char *dir;
|
char* dir;
|
||||||
if (!(dir = getcwd(nullptr, 0))) {
|
if (!(dir = getcwd(nullptr, 0))) {
|
||||||
#endif
|
#endif
|
||||||
LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: %s",
|
LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: %s", GetLastErrorMsg());
|
||||||
GetLastErrorMsg());
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -610,8 +565,7 @@ std::string GetCurrentDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the current directory to the given directory
|
// Sets the current directory to the given directory
|
||||||
bool SetCurrentDir(const std::string &directory)
|
bool SetCurrentDir(const std::string& directory) {
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0;
|
return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0;
|
||||||
#else
|
#else
|
||||||
|
@ -620,8 +574,7 @@ bool SetCurrentDir(const std::string &directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
std::string GetBundleDirectory()
|
std::string GetBundleDirectory() {
|
||||||
{
|
|
||||||
CFURLRef BundleRef;
|
CFURLRef BundleRef;
|
||||||
char AppBundlePath[MAXPATHLEN];
|
char AppBundlePath[MAXPATHLEN];
|
||||||
// Get the main bundle for the app
|
// Get the main bundle for the app
|
||||||
|
@ -636,11 +589,9 @@ std::string GetBundleDirectory()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
std::string& GetExeDirectory()
|
std::string& GetExeDirectory() {
|
||||||
{
|
|
||||||
static std::string exe_path;
|
static std::string exe_path;
|
||||||
if (exe_path.empty())
|
if (exe_path.empty()) {
|
||||||
{
|
|
||||||
wchar_t wchar_exe_path[2048];
|
wchar_t wchar_exe_path[2048];
|
||||||
GetModuleFileNameW(nullptr, wchar_exe_path, 2048);
|
GetModuleFileNameW(nullptr, wchar_exe_path, 2048);
|
||||||
exe_path = Common::UTF16ToUTF8(wchar_exe_path);
|
exe_path = Common::UTF16ToUTF8(wchar_exe_path);
|
||||||
|
@ -660,7 +611,8 @@ static const std::string& GetHomeDirectory() {
|
||||||
home_path = envvar;
|
home_path = envvar;
|
||||||
} else {
|
} else {
|
||||||
auto pw = getpwuid(getuid());
|
auto pw = getpwuid(getuid());
|
||||||
ASSERT_MSG(pw, "$HOME isn’t defined, and the current user can’t be found in /etc/passwd.");
|
ASSERT_MSG(pw,
|
||||||
|
"$HOME isn’t defined, and the current user can’t be found in /etc/passwd.");
|
||||||
home_path = pw->pw_dir;
|
home_path = pw->pw_dir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -699,11 +651,10 @@ static const std::string GetUserDirectory(const std::string& envvar) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string GetSysDirectory()
|
std::string GetSysDirectory() {
|
||||||
{
|
|
||||||
std::string sysDir;
|
std::string sysDir;
|
||||||
|
|
||||||
#if defined (__APPLE__)
|
#if defined(__APPLE__)
|
||||||
sysDir = GetBundleDirectory();
|
sysDir = GetBundleDirectory();
|
||||||
sysDir += DIR_SEP;
|
sysDir += DIR_SEP;
|
||||||
sysDir += SYSDATA_DIR;
|
sysDir += SYSDATA_DIR;
|
||||||
|
@ -718,13 +669,11 @@ std::string GetSysDirectory()
|
||||||
|
|
||||||
// Returns a string with a Citra data dir or file in the user's home
|
// Returns a string with a Citra data dir or file in the user's home
|
||||||
// directory. To be used in "multi-user" mode (that is, installed).
|
// directory. To be used in "multi-user" mode (that is, installed).
|
||||||
const std::string& GetUserPath(const unsigned int DirIDX, const std::string &newPath)
|
const std::string& GetUserPath(const unsigned int DirIDX, const std::string& newPath) {
|
||||||
{
|
|
||||||
static std::string paths[NUM_PATH_INDICES];
|
static std::string paths[NUM_PATH_INDICES];
|
||||||
|
|
||||||
// Set up all paths and files on the first run
|
// Set up all paths and files on the first run
|
||||||
if (paths[D_USER_IDX].empty())
|
if (paths[D_USER_IDX].empty()) {
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
paths[D_USER_IDX] = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP;
|
paths[D_USER_IDX] = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP;
|
||||||
paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP;
|
paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP;
|
||||||
|
@ -764,20 +713,15 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string &new
|
||||||
paths[F_MAINLOG_IDX] = paths[D_LOGS_IDX] + MAIN_LOG;
|
paths[F_MAINLOG_IDX] = paths[D_LOGS_IDX] + MAIN_LOG;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newPath.empty())
|
if (!newPath.empty()) {
|
||||||
{
|
if (!FileUtil::IsDirectory(newPath)) {
|
||||||
if (!FileUtil::IsDirectory(newPath))
|
|
||||||
{
|
|
||||||
LOG_ERROR(Common_Filesystem, "Invalid path specified %s", newPath.c_str());
|
LOG_ERROR(Common_Filesystem, "Invalid path specified %s", newPath.c_str());
|
||||||
return paths[DirIDX];
|
return paths[DirIDX];
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
paths[DirIDX] = newPath;
|
paths[DirIDX] = newPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (DirIDX)
|
switch (DirIDX) {
|
||||||
{
|
|
||||||
case D_ROOT_IDX:
|
case D_ROOT_IDX:
|
||||||
paths[D_USER_IDX] = paths[D_ROOT_IDX] + DIR_SEP;
|
paths[D_USER_IDX] = paths[D_ROOT_IDX] + DIR_SEP;
|
||||||
paths[D_SYSCONF_IDX] = paths[D_USER_IDX] + SYSCONF_DIR + DIR_SEP;
|
paths[D_SYSCONF_IDX] = paths[D_USER_IDX] + SYSCONF_DIR + DIR_SEP;
|
||||||
|
@ -828,13 +772,11 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string &new
|
||||||
return paths[DirIDX];
|
return paths[DirIDX];
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t WriteStringToFile(bool text_file, const std::string &str, const char *filename)
|
size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) {
|
||||||
{
|
|
||||||
return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size());
|
return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ReadFileToString(bool text_file, const char *filename, std::string &str)
|
size_t ReadFileToString(bool text_file, const char* filename, std::string& str) {
|
||||||
{
|
|
||||||
IOFile file(filename, text_file ? "r" : "rb");
|
IOFile file(filename, text_file ? "r" : "rb");
|
||||||
|
|
||||||
if (!file)
|
if (!file)
|
||||||
|
@ -886,42 +828,36 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IOFile::IOFile()
|
IOFile::IOFile() {
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IOFile::IOFile(const std::string& filename, const char openmode[])
|
IOFile::IOFile(const std::string& filename, const char openmode[]) {
|
||||||
{
|
|
||||||
Open(filename, openmode);
|
Open(filename, openmode);
|
||||||
}
|
}
|
||||||
|
|
||||||
IOFile::~IOFile()
|
IOFile::~IOFile() {
|
||||||
{
|
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
IOFile::IOFile(IOFile&& other)
|
IOFile::IOFile(IOFile&& other) {
|
||||||
{
|
|
||||||
Swap(other);
|
Swap(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
IOFile& IOFile::operator=(IOFile&& other)
|
IOFile& IOFile::operator=(IOFile&& other) {
|
||||||
{
|
|
||||||
Swap(other);
|
Swap(other);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IOFile::Swap(IOFile& other)
|
void IOFile::Swap(IOFile& other) {
|
||||||
{
|
|
||||||
std::swap(m_file, other.m_file);
|
std::swap(m_file, other.m_file);
|
||||||
std::swap(m_good, other.m_good);
|
std::swap(m_good, other.m_good);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IOFile::Open(const std::string& filename, const char openmode[])
|
bool IOFile::Open(const std::string& filename, const char openmode[]) {
|
||||||
{
|
|
||||||
Close();
|
Close();
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
_wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(), Common::UTF8ToUTF16W(openmode).c_str());
|
_wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(),
|
||||||
|
Common::UTF8ToUTF16W(openmode).c_str());
|
||||||
#else
|
#else
|
||||||
m_file = fopen(filename.c_str(), openmode);
|
m_file = fopen(filename.c_str(), openmode);
|
||||||
#endif
|
#endif
|
||||||
|
@ -930,8 +866,7 @@ bool IOFile::Open(const std::string& filename, const char openmode[])
|
||||||
return m_good;
|
return m_good;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IOFile::Close()
|
bool IOFile::Close() {
|
||||||
{
|
|
||||||
if (!IsOpen() || 0 != std::fclose(m_file))
|
if (!IsOpen() || 0 != std::fclose(m_file))
|
||||||
m_good = false;
|
m_good = false;
|
||||||
|
|
||||||
|
@ -939,41 +874,37 @@ bool IOFile::Close()
|
||||||
return m_good;
|
return m_good;
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 IOFile::GetSize() const
|
u64 IOFile::GetSize() const {
|
||||||
{
|
|
||||||
if (IsOpen())
|
if (IsOpen())
|
||||||
return FileUtil::GetSize(m_file);
|
return FileUtil::GetSize(m_file);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IOFile::Seek(s64 off, int origin)
|
bool IOFile::Seek(s64 off, int origin) {
|
||||||
{
|
|
||||||
if (!IsOpen() || 0 != fseeko(m_file, off, origin))
|
if (!IsOpen() || 0 != fseeko(m_file, off, origin))
|
||||||
m_good = false;
|
m_good = false;
|
||||||
|
|
||||||
return m_good;
|
return m_good;
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 IOFile::Tell() const
|
u64 IOFile::Tell() const {
|
||||||
{
|
|
||||||
if (IsOpen())
|
if (IsOpen())
|
||||||
return ftello(m_file);
|
return ftello(m_file);
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IOFile::Flush()
|
bool IOFile::Flush() {
|
||||||
{
|
|
||||||
if (!IsOpen() || 0 != std::fflush(m_file))
|
if (!IsOpen() || 0 != std::fflush(m_file))
|
||||||
m_good = false;
|
m_good = false;
|
||||||
|
|
||||||
return m_good;
|
return m_good;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IOFile::Resize(u64 size)
|
bool IOFile::Resize(u64 size) {
|
||||||
{
|
if (!IsOpen() ||
|
||||||
if (!IsOpen() || 0 !=
|
0 !=
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// ector: _chsize sucks, not 64-bit safe
|
// ector: _chsize sucks, not 64-bit safe
|
||||||
// F|RES: changed to _chsize_s. i think it is 64-bit safe
|
// F|RES: changed to _chsize_s. i think it is 64-bit safe
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cstdio>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <cstdio>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -51,12 +51,10 @@ enum {
|
||||||
NUM_PATH_INDICES
|
NUM_PATH_INDICES
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace FileUtil
|
namespace FileUtil {
|
||||||
{
|
|
||||||
|
|
||||||
// FileSystem tree node/
|
// FileSystem tree node/
|
||||||
struct FSTEntry
|
struct FSTEntry {
|
||||||
{
|
|
||||||
bool isDirectory;
|
bool isDirectory;
|
||||||
u64 size; // file length or number of entries from children
|
u64 size; // file length or number of entries from children
|
||||||
std::string physicalName; // name on disk
|
std::string physicalName; // name on disk
|
||||||
|
@ -65,61 +63,63 @@ struct FSTEntry
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns true if file filename exists
|
// Returns true if file filename exists
|
||||||
bool Exists(const std::string &filename);
|
bool Exists(const std::string& filename);
|
||||||
|
|
||||||
// Returns true if filename is a directory
|
// Returns true if filename is a directory
|
||||||
bool IsDirectory(const std::string &filename);
|
bool IsDirectory(const std::string& filename);
|
||||||
|
|
||||||
// Returns the size of filename (64bit)
|
// Returns the size of filename (64bit)
|
||||||
u64 GetSize(const std::string &filename);
|
u64 GetSize(const std::string& filename);
|
||||||
|
|
||||||
// Overloaded GetSize, accepts file descriptor
|
// Overloaded GetSize, accepts file descriptor
|
||||||
u64 GetSize(const int fd);
|
u64 GetSize(const int fd);
|
||||||
|
|
||||||
// Overloaded GetSize, accepts FILE*
|
// Overloaded GetSize, accepts FILE*
|
||||||
u64 GetSize(FILE *f);
|
u64 GetSize(FILE* f);
|
||||||
|
|
||||||
// Returns true if successful, or path already exists.
|
// Returns true if successful, or path already exists.
|
||||||
bool CreateDir(const std::string &filename);
|
bool CreateDir(const std::string& filename);
|
||||||
|
|
||||||
// Creates the full path of fullPath returns true on success
|
// Creates the full path of fullPath returns true on success
|
||||||
bool CreateFullPath(const std::string &fullPath);
|
bool CreateFullPath(const std::string& fullPath);
|
||||||
|
|
||||||
// Deletes a given filename, return true on success
|
// Deletes a given filename, return true on success
|
||||||
// Doesn't supports deleting a directory
|
// Doesn't supports deleting a directory
|
||||||
bool Delete(const std::string &filename);
|
bool Delete(const std::string& filename);
|
||||||
|
|
||||||
// Deletes a directory filename, returns true on success
|
// Deletes a directory filename, returns true on success
|
||||||
bool DeleteDir(const std::string &filename);
|
bool DeleteDir(const std::string& filename);
|
||||||
|
|
||||||
// renames file srcFilename to destFilename, returns true on success
|
// renames file srcFilename to destFilename, returns true on success
|
||||||
bool Rename(const std::string &srcFilename, const std::string &destFilename);
|
bool Rename(const std::string& srcFilename, const std::string& destFilename);
|
||||||
|
|
||||||
// copies file srcFilename to destFilename, returns true on success
|
// copies file srcFilename to destFilename, returns true on success
|
||||||
bool Copy(const std::string &srcFilename, const std::string &destFilename);
|
bool Copy(const std::string& srcFilename, const std::string& destFilename);
|
||||||
|
|
||||||
// creates an empty file filename, returns true on success
|
// creates an empty file filename, returns true on success
|
||||||
bool CreateEmptyFile(const std::string &filename);
|
bool CreateEmptyFile(const std::string& filename);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param num_entries_out to be assigned by the callable with the number of iterated directory entries, never null
|
* @param num_entries_out to be assigned by the callable with the number of iterated directory
|
||||||
|
* entries, never null
|
||||||
* @param directory the path to the enclosing directory
|
* @param directory the path to the enclosing directory
|
||||||
* @param virtual_name the entry name, without any preceding directory info
|
* @param virtual_name the entry name, without any preceding directory info
|
||||||
* @return whether handling the entry succeeded
|
* @return whether handling the entry succeeded
|
||||||
*/
|
*/
|
||||||
using DirectoryEntryCallable = std::function<bool(unsigned* num_entries_out,
|
using DirectoryEntryCallable = std::function<bool(
|
||||||
const std::string& directory,
|
unsigned* num_entries_out, const std::string& directory, const std::string& virtual_name)>;
|
||||||
const std::string& virtual_name)>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scans a directory, calling the callback for each file/directory contained within.
|
* Scans a directory, calling the callback for each file/directory contained within.
|
||||||
* If the callback returns failure, scanning halts and this function returns failure as well
|
* If the callback returns failure, scanning halts and this function returns failure as well
|
||||||
* @param num_entries_out assigned by the function with the number of iterated directory entries, can be null
|
* @param num_entries_out assigned by the function with the number of iterated directory entries,
|
||||||
|
* can be null
|
||||||
* @param directory the directory to scan
|
* @param directory the directory to scan
|
||||||
* @param callback The callback which will be called for each entry
|
* @param callback The callback which will be called for each entry
|
||||||
* @return whether scanning the directory succeeded
|
* @return whether scanning the directory succeeded
|
||||||
*/
|
*/
|
||||||
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback);
|
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string& directory,
|
||||||
|
DirectoryEntryCallable callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scans the directory tree, storing the results.
|
* Scans the directory tree, storing the results.
|
||||||
|
@ -128,23 +128,24 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo
|
||||||
* @param recursion Number of children directories to read before giving up.
|
* @param recursion Number of children directories to read before giving up.
|
||||||
* @return the total number of files/directories found
|
* @return the total number of files/directories found
|
||||||
*/
|
*/
|
||||||
unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry, unsigned int recursion = 0);
|
unsigned ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
|
||||||
|
unsigned int recursion = 0);
|
||||||
|
|
||||||
// deletes the given directory and anything under it. Returns true on success.
|
// deletes the given directory and anything under it. Returns true on success.
|
||||||
bool DeleteDirRecursively(const std::string &directory, unsigned int recursion = 256);
|
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256);
|
||||||
|
|
||||||
// Returns the current directory
|
// Returns the current directory
|
||||||
std::string GetCurrentDir();
|
std::string GetCurrentDir();
|
||||||
|
|
||||||
// Create directory and copy contents (does not overwrite existing files)
|
// Create directory and copy contents (does not overwrite existing files)
|
||||||
void CopyDir(const std::string &source_path, const std::string &dest_path);
|
void CopyDir(const std::string& source_path, const std::string& dest_path);
|
||||||
|
|
||||||
// Set the current directory to given directory
|
// Set the current directory to given directory
|
||||||
bool SetCurrentDir(const std::string &directory);
|
bool SetCurrentDir(const std::string& directory);
|
||||||
|
|
||||||
// Returns a pointer to a string with a Citra data dir in the user's home
|
// Returns a pointer to a string with a Citra data dir in the user's home
|
||||||
// directory. To be used in "multi-user" mode (that is, installed).
|
// directory. To be used in "multi-user" mode (that is, installed).
|
||||||
const std::string& GetUserPath(const unsigned int DirIDX, const std::string &newPath="");
|
const std::string& GetUserPath(const unsigned int DirIDX, const std::string& newPath = "");
|
||||||
|
|
||||||
// Returns the path to where the sys file are
|
// Returns the path to where the sys file are
|
||||||
std::string GetSysDirectory();
|
std::string GetSysDirectory();
|
||||||
|
@ -154,11 +155,11 @@ std::string GetBundleDirectory();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
std::string &GetExeDirectory();
|
std::string& GetExeDirectory();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
size_t WriteStringToFile(bool text_file, const std::string &str, const char *filename);
|
size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename);
|
||||||
size_t ReadFileToString(bool text_file, const char *filename, std::string &str);
|
size_t ReadFileToString(bool text_file, const char* filename, std::string& str);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Splits the filename into 8.3 format
|
* Splits the filename into 8.3 format
|
||||||
|
@ -173,8 +174,7 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
|
||||||
// simple wrapper for cstdlib file functions to
|
// simple wrapper for cstdlib file functions to
|
||||||
// hopefully will make error checking easier
|
// hopefully will make error checking easier
|
||||||
// and make forgetting an fclose() harder
|
// and make forgetting an fclose() harder
|
||||||
class IOFile : public NonCopyable
|
class IOFile : public NonCopyable {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
IOFile();
|
IOFile();
|
||||||
IOFile(const std::string& filename, const char openmode[]);
|
IOFile(const std::string& filename, const char openmode[]);
|
||||||
|
@ -190,11 +190,12 @@ public:
|
||||||
bool Close();
|
bool Close();
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
size_t ReadArray(T* data, size_t length)
|
size_t ReadArray(T* data, size_t length) {
|
||||||
{
|
static_assert(std::is_standard_layout<T>(),
|
||||||
static_assert(std::is_standard_layout<T>(), "Given array does not consist of standard layout objects");
|
"Given array does not consist of standard layout objects");
|
||||||
#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
|
#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
|
||||||
static_assert(std::is_trivially_copyable<T>(), "Given array does not consist of trivially copyable objects");
|
static_assert(std::is_trivially_copyable<T>(),
|
||||||
|
"Given array does not consist of trivially copyable objects");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!IsOpen()) {
|
if (!IsOpen()) {
|
||||||
|
@ -210,11 +211,12 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
size_t WriteArray(const T* data, size_t length)
|
size_t WriteArray(const T* data, size_t length) {
|
||||||
{
|
static_assert(std::is_standard_layout<T>(),
|
||||||
static_assert(std::is_standard_layout<T>(), "Given array does not consist of standard layout objects");
|
"Given array does not consist of standard layout objects");
|
||||||
#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
|
#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
|
||||||
static_assert(std::is_trivially_copyable<T>(), "Given array does not consist of trivially copyable objects");
|
static_assert(std::is_trivially_copyable<T>(),
|
||||||
|
"Given array does not consist of trivially copyable objects");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!IsOpen()) {
|
if (!IsOpen()) {
|
||||||
|
@ -229,27 +231,31 @@ public:
|
||||||
return items_written;
|
return items_written;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ReadBytes(void* data, size_t length)
|
size_t ReadBytes(void* data, size_t length) {
|
||||||
{
|
|
||||||
return ReadArray(reinterpret_cast<char*>(data), length);
|
return ReadArray(reinterpret_cast<char*>(data), length);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t WriteBytes(const void* data, size_t length)
|
size_t WriteBytes(const void* data, size_t length) {
|
||||||
{
|
|
||||||
return WriteArray(reinterpret_cast<const char*>(data), length);
|
return WriteArray(reinterpret_cast<const char*>(data), length);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template <typename T>
|
||||||
size_t WriteObject(const T& object) {
|
size_t WriteObject(const T& object) {
|
||||||
static_assert(!std::is_pointer<T>::value, "Given object is a pointer");
|
static_assert(!std::is_pointer<T>::value, "Given object is a pointer");
|
||||||
return WriteArray(&object, 1);
|
return WriteArray(&object, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsOpen() const { return nullptr != m_file; }
|
bool IsOpen() const {
|
||||||
|
return nullptr != m_file;
|
||||||
|
}
|
||||||
|
|
||||||
// m_good is set to false when a read, write or other function fails
|
// m_good is set to false when a read, write or other function fails
|
||||||
bool IsGood() const { return m_good; }
|
bool IsGood() const {
|
||||||
explicit operator bool() const { return IsGood(); }
|
return m_good;
|
||||||
|
}
|
||||||
|
explicit operator bool() const {
|
||||||
|
return IsGood();
|
||||||
|
}
|
||||||
|
|
||||||
bool Seek(s64 off, int origin);
|
bool Seek(s64 off, int origin);
|
||||||
u64 Tell() const;
|
u64 Tell() const;
|
||||||
|
@ -258,7 +264,10 @@ public:
|
||||||
bool Flush();
|
bool Flush();
|
||||||
|
|
||||||
// clear error state
|
// clear error state
|
||||||
void Clear() { m_good = true; std::clearerr(m_file); }
|
void Clear() {
|
||||||
|
m_good = true;
|
||||||
|
std::clearerr(m_file);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::FILE* m_file = nullptr;
|
std::FILE* m_file = nullptr;
|
||||||
|
@ -269,8 +278,7 @@ private:
|
||||||
|
|
||||||
// To deal with Windows being dumb at unicode:
|
// To deal with Windows being dumb at unicode:
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode)
|
void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
|
||||||
{
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
fstream.open(Common::UTF8ToTStr(filename).c_str(), openmode);
|
fstream.open(Common::UTF8ToTStr(filename).c_str(), openmode);
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -36,7 +36,7 @@ static FORCE_INLINE u64 fmix64(u64 k) {
|
||||||
// platforms (MurmurHash3_x64_128). It was taken from:
|
// platforms (MurmurHash3_x64_128). It was taken from:
|
||||||
// https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp
|
// https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp
|
||||||
void MurmurHash3_128(const void* key, int len, u32 seed, void* out) {
|
void MurmurHash3_128(const void* key, int len, u32 seed, void* out) {
|
||||||
const u8 * data = (const u8*)key;
|
const u8* data = (const u8*)key;
|
||||||
const int nblocks = len / 16;
|
const int nblocks = len / 16;
|
||||||
|
|
||||||
u64 h1 = seed;
|
u64 h1 = seed;
|
||||||
|
@ -47,52 +47,84 @@ void MurmurHash3_128(const void* key, int len, u32 seed, void* out) {
|
||||||
|
|
||||||
// Body
|
// Body
|
||||||
|
|
||||||
const u64 * blocks = (const u64 *)(data);
|
const u64* blocks = (const u64*)(data);
|
||||||
|
|
||||||
for (int i = 0; i < nblocks; i++) {
|
for (int i = 0; i < nblocks; i++) {
|
||||||
u64 k1 = getblock64(blocks,i*2+0);
|
u64 k1 = getblock64(blocks, i * 2 + 0);
|
||||||
u64 k2 = getblock64(blocks,i*2+1);
|
u64 k2 = getblock64(blocks, i * 2 + 1);
|
||||||
|
|
||||||
k1 *= c1; k1 = _rotl64(k1,31); k1 *= c2; h1 ^= k1;
|
k1 *= c1;
|
||||||
|
k1 = _rotl64(k1, 31);
|
||||||
|
k1 *= c2;
|
||||||
|
h1 ^= k1;
|
||||||
|
|
||||||
h1 = _rotl64(h1,27); h1 += h2; h1 = h1*5+0x52dce729;
|
h1 = _rotl64(h1, 27);
|
||||||
|
h1 += h2;
|
||||||
|
h1 = h1 * 5 + 0x52dce729;
|
||||||
|
|
||||||
k2 *= c2; k2 = _rotl64(k2,33); k2 *= c1; h2 ^= k2;
|
k2 *= c2;
|
||||||
|
k2 = _rotl64(k2, 33);
|
||||||
|
k2 *= c1;
|
||||||
|
h2 ^= k2;
|
||||||
|
|
||||||
h2 = _rotl64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5;
|
h2 = _rotl64(h2, 31);
|
||||||
|
h2 += h1;
|
||||||
|
h2 = h2 * 5 + 0x38495ab5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tail
|
// Tail
|
||||||
|
|
||||||
const u8 * tail = (const u8*)(data + nblocks*16);
|
const u8* tail = (const u8*)(data + nblocks * 16);
|
||||||
|
|
||||||
u64 k1 = 0;
|
u64 k1 = 0;
|
||||||
u64 k2 = 0;
|
u64 k2 = 0;
|
||||||
|
|
||||||
switch (len & 15) {
|
switch (len & 15) {
|
||||||
case 15: k2 ^= ((u64)tail[14]) << 48;
|
case 15:
|
||||||
case 14: k2 ^= ((u64)tail[13]) << 40;
|
k2 ^= ((u64)tail[14]) << 48;
|
||||||
case 13: k2 ^= ((u64)tail[12]) << 32;
|
case 14:
|
||||||
case 12: k2 ^= ((u64)tail[11]) << 24;
|
k2 ^= ((u64)tail[13]) << 40;
|
||||||
case 11: k2 ^= ((u64)tail[10]) << 16;
|
case 13:
|
||||||
case 10: k2 ^= ((u64)tail[ 9]) << 8;
|
k2 ^= ((u64)tail[12]) << 32;
|
||||||
case 9: k2 ^= ((u64)tail[ 8]) << 0;
|
case 12:
|
||||||
k2 *= c2; k2 = _rotl64(k2,33); k2 *= c1; h2 ^= k2;
|
k2 ^= ((u64)tail[11]) << 24;
|
||||||
|
case 11:
|
||||||
|
k2 ^= ((u64)tail[10]) << 16;
|
||||||
|
case 10:
|
||||||
|
k2 ^= ((u64)tail[9]) << 8;
|
||||||
|
case 9:
|
||||||
|
k2 ^= ((u64)tail[8]) << 0;
|
||||||
|
k2 *= c2;
|
||||||
|
k2 = _rotl64(k2, 33);
|
||||||
|
k2 *= c1;
|
||||||
|
h2 ^= k2;
|
||||||
|
|
||||||
case 8: k1 ^= ((u64)tail[ 7]) << 56;
|
case 8:
|
||||||
case 7: k1 ^= ((u64)tail[ 6]) << 48;
|
k1 ^= ((u64)tail[7]) << 56;
|
||||||
case 6: k1 ^= ((u64)tail[ 5]) << 40;
|
case 7:
|
||||||
case 5: k1 ^= ((u64)tail[ 4]) << 32;
|
k1 ^= ((u64)tail[6]) << 48;
|
||||||
case 4: k1 ^= ((u64)tail[ 3]) << 24;
|
case 6:
|
||||||
case 3: k1 ^= ((u64)tail[ 2]) << 16;
|
k1 ^= ((u64)tail[5]) << 40;
|
||||||
case 2: k1 ^= ((u64)tail[ 1]) << 8;
|
case 5:
|
||||||
case 1: k1 ^= ((u64)tail[ 0]) << 0;
|
k1 ^= ((u64)tail[4]) << 32;
|
||||||
k1 *= c1; k1 = _rotl64(k1,31); k1 *= c2; h1 ^= k1;
|
case 4:
|
||||||
|
k1 ^= ((u64)tail[3]) << 24;
|
||||||
|
case 3:
|
||||||
|
k1 ^= ((u64)tail[2]) << 16;
|
||||||
|
case 2:
|
||||||
|
k1 ^= ((u64)tail[1]) << 8;
|
||||||
|
case 1:
|
||||||
|
k1 ^= ((u64)tail[0]) << 0;
|
||||||
|
k1 *= c1;
|
||||||
|
k1 = _rotl64(k1, 31);
|
||||||
|
k1 *= c2;
|
||||||
|
h1 ^= k1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Finalization
|
// Finalization
|
||||||
|
|
||||||
h1 ^= len; h2 ^= len;
|
h1 ^= len;
|
||||||
|
h2 ^= len;
|
||||||
|
|
||||||
h1 += h2;
|
h1 += h2;
|
||||||
h2 += h1;
|
h2 += h1;
|
||||||
|
|
|
@ -13,11 +13,25 @@ namespace KeyMap {
|
||||||
// and map it directly to EmuWindow::ButtonPressed.
|
// and map it directly to EmuWindow::ButtonPressed.
|
||||||
// It should go the analog input way like circle pad does.
|
// It should go the analog input way like circle pad does.
|
||||||
const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets = {{
|
const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets = {{
|
||||||
Service::HID::PAD_A, Service::HID::PAD_B, Service::HID::PAD_X, Service::HID::PAD_Y,
|
Service::HID::PAD_A,
|
||||||
Service::HID::PAD_L, Service::HID::PAD_R, Service::HID::PAD_ZL, Service::HID::PAD_ZR,
|
Service::HID::PAD_B,
|
||||||
Service::HID::PAD_START, Service::HID::PAD_SELECT, Service::HID::PAD_NONE,
|
Service::HID::PAD_X,
|
||||||
Service::HID::PAD_UP, Service::HID::PAD_DOWN, Service::HID::PAD_LEFT, Service::HID::PAD_RIGHT,
|
Service::HID::PAD_Y,
|
||||||
Service::HID::PAD_C_UP, Service::HID::PAD_C_DOWN, Service::HID::PAD_C_LEFT, Service::HID::PAD_C_RIGHT,
|
Service::HID::PAD_L,
|
||||||
|
Service::HID::PAD_R,
|
||||||
|
Service::HID::PAD_ZL,
|
||||||
|
Service::HID::PAD_ZR,
|
||||||
|
Service::HID::PAD_START,
|
||||||
|
Service::HID::PAD_SELECT,
|
||||||
|
Service::HID::PAD_NONE,
|
||||||
|
Service::HID::PAD_UP,
|
||||||
|
Service::HID::PAD_DOWN,
|
||||||
|
Service::HID::PAD_LEFT,
|
||||||
|
Service::HID::PAD_RIGHT,
|
||||||
|
Service::HID::PAD_C_UP,
|
||||||
|
Service::HID::PAD_C_DOWN,
|
||||||
|
Service::HID::PAD_C_LEFT,
|
||||||
|
Service::HID::PAD_C_RIGHT,
|
||||||
|
|
||||||
IndirectTarget::CirclePadUp,
|
IndirectTarget::CirclePadUp,
|
||||||
IndirectTarget::CirclePadDown,
|
IndirectTarget::CirclePadDown,
|
||||||
|
@ -49,7 +63,8 @@ static void UpdateCirclePad(EmuWindow& emu_window) {
|
||||||
--y;
|
--y;
|
||||||
|
|
||||||
float modifier = circle_pad_modifier ? Settings::values.pad_circle_modifier_scale : 1.0;
|
float modifier = circle_pad_modifier ? Settings::values.pad_circle_modifier_scale : 1.0;
|
||||||
emu_window.CirclePadUpdated(x * modifier * (y == 0 ? 1.0 : SQRT_HALF), y * modifier * (x == 0 ? 1.0 : SQRT_HALF));
|
emu_window.CirclePadUpdated(x * modifier * (y == 0 ? 1.0 : SQRT_HALF),
|
||||||
|
y * modifier * (x == 0 ? 1.0 : SQRT_HALF));
|
||||||
}
|
}
|
||||||
|
|
||||||
int NewDeviceId() {
|
int NewDeviceId() {
|
||||||
|
@ -103,7 +118,7 @@ void PressKey(EmuWindow& emu_window, HostDeviceKey key) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReleaseKey(EmuWindow& emu_window,HostDeviceKey key) {
|
void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key) {
|
||||||
auto target = key_map.find(key);
|
auto target = key_map.find(key);
|
||||||
if (target == key_map.end())
|
if (target == key_map.end())
|
||||||
return;
|
return;
|
||||||
|
@ -135,5 +150,4 @@ void ReleaseKey(EmuWindow& emu_window,HostDeviceKey key) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,14 +55,12 @@ struct HostDeviceKey {
|
||||||
int key_code;
|
int key_code;
|
||||||
int device_id; ///< Uniquely identifies a host device
|
int device_id; ///< Uniquely identifies a host device
|
||||||
|
|
||||||
bool operator<(const HostDeviceKey &other) const {
|
bool operator<(const HostDeviceKey& other) const {
|
||||||
return std::tie(key_code, device_id) <
|
return std::tie(key_code, device_id) < std::tie(other.key_code, other.device_id);
|
||||||
std::tie(other.key_code, other.device_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const HostDeviceKey &other) const {
|
bool operator==(const HostDeviceKey& other) const {
|
||||||
return std::tie(key_code, device_id) ==
|
return std::tie(key_code, device_id) == std::tie(other.key_code, other.device_id);
|
||||||
std::tie(other.key_code, other.device_id);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -92,5 +90,4 @@ void PressKey(EmuWindow& emu_window, HostDeviceKey key);
|
||||||
* Maps a key release action and call the corresponding function in EmuWindow
|
* Maps a key release action and call the corresponding function in EmuWindow
|
||||||
*/
|
*/
|
||||||
void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key);
|
void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue