summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt3
-rw-r--r--src/audio_core/CMakeLists.txt13
-rw-r--r--src/audio_core/hle/common.h2
-rw-r--r--src/audio_core/hle/dsp.cpp24
-rw-r--r--src/audio_core/hle/dsp.h14
-rw-r--r--src/audio_core/hle/filter.h1
-rw-r--r--src/audio_core/hle/source.cpp320
-rw-r--r--src/audio_core/hle/source.h144
-rw-r--r--src/audio_core/sdl2_sink.cpp126
-rw-r--r--src/audio_core/sdl2_sink.h30
-rw-r--r--src/audio_core/sink.h2
-rw-r--r--src/audio_core/sink_details.cpp7
-rw-r--r--src/citra/default_ini.h2
-rw-r--r--src/citra/emu_window/emu_window_sdl2.cpp7
-rw-r--r--src/citra_qt/CMakeLists.txt1
-rw-r--r--src/citra_qt/debugger/graphics_breakpoints.cpp2
-rw-r--r--src/citra_qt/debugger/graphics_vertex_shader.cpp4
-rw-r--r--src/citra_qt/game_list.cpp16
-rw-r--r--src/citra_qt/game_list.h2
-rw-r--r--src/citra_qt/game_list_p.h106
-rw-r--r--src/citra_qt/main.cpp11
-rw-r--r--src/common/logging/backend.cpp1
-rw-r--r--src/common/logging/log.h3
-rw-r--r--src/core/core.cpp2
-rw-r--r--src/core/hle/applets/mii_selector.cpp5
-rw-r--r--src/core/hle/applets/mii_selector.h44
-rw-r--r--src/core/hle/hle.cpp20
-rw-r--r--src/core/hle/hle.h4
-rw-r--r--src/core/hle/kernel/thread.cpp3
-rw-r--r--src/core/loader/3dsx.cpp27
-rw-r--r--src/core/loader/3dsx.h9
-rw-r--r--src/core/loader/loader.cpp53
-rw-r--r--src/core/loader/loader.h57
-rw-r--r--src/core/loader/ncch.cpp22
-rw-r--r--src/core/loader/ncch.h7
-rw-r--r--src/video_core/command_processor.cpp10
-rw-r--r--src/video_core/debug_utils/debug_utils.h2
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp6
38 files changed, 1038 insertions, 74 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d628ecc50..8f2898973 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -152,12 +152,15 @@ if (ENABLE_SDL2)
download_bundled_external("sdl2/" ${SDL2_VER} SDL2_PREFIX)
endif()
+ set(SDL2_FOUND YES)
set(SDL2_INCLUDE_DIR "${SDL2_PREFIX}/include" CACHE PATH "Path to SDL2 headers")
set(SDL2_LIBRARY "${SDL2_PREFIX}/lib/x64/SDL2.lib" CACHE PATH "Path to SDL2 library")
set(SDL2_DLL_DIR "${SDL2_PREFIX}/lib/x64/" CACHE PATH "Path to SDL2.dll")
else()
find_package(SDL2 REQUIRED)
endif()
+else()
+ set(SDL2_FOUND NO)
endif()
IF (APPLE)
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 5a2747e78..13b5e400e 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -4,6 +4,7 @@ set(SRCS
hle/dsp.cpp
hle/filter.cpp
hle/pipe.cpp
+ hle/source.cpp
interpolate.cpp
sink_details.cpp
)
@@ -15,6 +16,7 @@ set(HEADERS
hle/dsp.h
hle/filter.h
hle/pipe.h
+ hle/source.h
interpolate.h
null_sink.h
sink.h
@@ -23,7 +25,18 @@ set(HEADERS
include_directories(../../externals/soundtouch/include)
+if(SDL2_FOUND)
+ set(SRCS ${SRCS} sdl2_sink.cpp)
+ set(HEADERS ${HEADERS} sdl2_sink.h)
+ include_directories(${SDL2_INCLUDE_DIR})
+endif()
+
create_directory_groups(${SRCS} ${HEADERS})
add_library(audio_core STATIC ${SRCS} ${HEADERS})
target_link_libraries(audio_core SoundTouch)
+
+if(SDL2_FOUND)
+ target_link_libraries(audio_core ${SDL2_LIBRARY})
+ set_property(TARGET audio_core APPEND PROPERTY COMPILE_DEFINITIONS HAVE_SDL2)
+endif()
diff --git a/src/audio_core/hle/common.h b/src/audio_core/hle/common.h
index 7910f42ae..596b67eaf 100644
--- a/src/audio_core/hle/common.h
+++ b/src/audio_core/hle/common.h
@@ -27,7 +27,7 @@ using QuadFrame32 = std::array<std::array<s32, 4>, samples_per_frame>;
*/
template<typename FrameT, typename FilterT>
void FilterFrame(FrameT& frame, FilterT& filter) {
- std::transform(frame.begin(), frame.end(), frame.begin(), [&filter](const typename FrameT::value_type& sample) {
+ std::transform(frame.begin(), frame.end(), frame.begin(), [&filter](const auto& sample) {
return filter.ProcessSample(sample);
});
}
diff --git a/src/audio_core/hle/dsp.cpp b/src/audio_core/hle/dsp.cpp
index 4d44bd2d9..0cdbdb06a 100644
--- a/src/audio_core/hle/dsp.cpp
+++ b/src/audio_core/hle/dsp.cpp
@@ -2,10 +2,12 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
#include <memory>
#include "audio_core/hle/dsp.h"
#include "audio_core/hle/pipe.h"
+#include "audio_core/hle/source.h"
#include "audio_core/sink.h"
namespace DSP {
@@ -38,16 +40,38 @@ static SharedMemory& WriteRegion() {
return g_regions[1 - CurrentRegionIndex()];
}
+static std::array<Source, num_sources> sources = {
+ Source(0), Source(1), Source(2), Source(3), Source(4), Source(5),
+ Source(6), Source(7), Source(8), Source(9), Source(10), Source(11),
+ Source(12), Source(13), Source(14), Source(15), Source(16), Source(17),
+ Source(18), Source(19), Source(20), Source(21), Source(22), Source(23)
+};
+
static std::unique_ptr<AudioCore::Sink> sink;
void Init() {
DSP::HLE::ResetPipes();
+ for (auto& source : sources) {
+ source.Reset();
+ }
}
void Shutdown() {
}
bool Tick() {
+ SharedMemory& read = ReadRegion();
+ SharedMemory& write = WriteRegion();
+
+ std::array<QuadFrame32, 3> intermediate_mixes = {};
+
+ 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]);
+ for (size_t mix = 0; mix < 3; mix++) {
+ sources[i].MixInto(intermediate_mixes[mix], mix);
+ }
+ }
+
return true;
}
diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/dsp.h
index 4f2410c27..f6e53f68f 100644
--- a/src/audio_core/hle/dsp.h
+++ b/src/audio_core/hle/dsp.h
@@ -33,13 +33,9 @@ namespace HLE {
// double-buffer. The frame counter is located as the very last u16 of each region and is incremented
// each audio tick.
-struct SharedMemory;
-
constexpr VAddr region0_base = 0x1FF50000;
constexpr VAddr region1_base = 0x1FF70000;
-extern std::array<SharedMemory, 2> g_regions;
-
/**
* The DSP is native 16-bit. The DSP also appears to be big-endian. When reading 32-bit numbers from
* its memory regions, the higher and lower 16-bit halves are swapped compared to the little-endian
@@ -169,9 +165,9 @@ struct SourceConfiguration {
float_le rate_multiplier;
enum class InterpolationMode : u8 {
- None = 0,
+ Polyphase = 0,
Linear = 1,
- Polyphase = 2
+ None = 2
};
InterpolationMode interpolation_mode;
@@ -318,10 +314,10 @@ ASSERT_DSP_STRUCT(SourceConfiguration::Configuration::Buffer, 20);
struct SourceStatus {
struct Status {
u8 is_enabled; ///< Is this channel enabled? (Doesn't have to be playing anything.)
- u8 previous_buffer_id_dirty; ///< Non-zero when previous_buffer_id changes
+ u8 current_buffer_id_dirty; ///< Non-zero when current_buffer_id changes
u16_le sync; ///< Is set by the DSP to the value of SourceConfiguration::sync
u32_dsp buffer_position; ///< Number of samples into the current buffer
- u16_le previous_buffer_id; ///< Updated when a buffer finishes playing
+ u16_le current_buffer_id; ///< Updated when a buffer finishes playing
INSERT_PADDING_DSPWORDS(1);
};
@@ -507,6 +503,8 @@ struct SharedMemory {
};
ASSERT_DSP_STRUCT(SharedMemory, 0x8000);
+extern std::array<SharedMemory, 2> g_regions;
+
// 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, source_configurations) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
diff --git a/src/audio_core/hle/filter.h b/src/audio_core/hle/filter.h
index 75738f600..43d2035cd 100644
--- a/src/audio_core/hle/filter.h
+++ b/src/audio_core/hle/filter.h
@@ -16,6 +16,7 @@ namespace HLE {
/// Preprocessing filters. There is an independent set of filters for each Source.
class SourceFilters final {
+public:
SourceFilters() { Reset(); }
/// Reset internal state.
diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp
new file mode 100644
index 000000000..daaf6e3f3
--- /dev/null
+++ b/src/audio_core/hle/source.cpp
@@ -0,0 +1,320 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <array>
+
+#include "audio_core/codec.h"
+#include "audio_core/hle/common.h"
+#include "audio_core/hle/source.h"
+#include "audio_core/interpolate.h"
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+
+#include "core/memory.h"
+
+namespace DSP {
+namespace HLE {
+
+SourceStatus::Status Source::Tick(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]) {
+ ParseConfig(config, adpcm_coeffs);
+
+ if (state.enabled) {
+ GenerateFrame();
+ }
+
+ return GetCurrentStatus();
+}
+
+void Source::MixInto(QuadFrame32& dest, size_t intermediate_mix_id) const {
+ if (!state.enabled)
+ return;
+
+ const std::array<float, 4>& gains = state.gain.at(intermediate_mix_id);
+ for (size_t samplei = 0; samplei < samples_per_frame; samplei++) {
+ // Conversion from stereo (current_frame) to quadraphonic (dest) occurs here.
+ dest[samplei][0] += static_cast<s32>(gains[0] * current_frame[samplei][0]);
+ dest[samplei][1] += static_cast<s32>(gains[1] * current_frame[samplei][1]);
+ dest[samplei][2] += static_cast<s32>(gains[2] * current_frame[samplei][0]);
+ dest[samplei][3] += static_cast<s32>(gains[3] * current_frame[samplei][1]);
+ }
+}
+
+void Source::Reset() {
+ current_frame.fill({});
+ state = {};
+}
+
+void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]) {
+ if (!config.dirty_raw) {
+ return;
+ }
+
+ if (config.reset_flag) {
+ config.reset_flag.Assign(0);
+ Reset();
+ LOG_TRACE(Audio_DSP, "source_id=%zu reset", source_id);
+ }
+
+ if (config.partial_reset_flag) {
+ config.partial_reset_flag.Assign(0);
+ state.input_queue = std::priority_queue<Buffer, std::vector<Buffer>, BufferOrder>{};
+ LOG_TRACE(Audio_DSP, "source_id=%zu partial_reset", source_id);
+ }
+
+ if (config.enable_dirty) {
+ config.enable_dirty.Assign(0);
+ state.enabled = config.enable != 0;
+ LOG_TRACE(Audio_DSP, "source_id=%zu enable=%d", source_id, state.enabled);
+ }
+
+ if (config.sync_dirty) {
+ config.sync_dirty.Assign(0);
+ state.sync = config.sync;
+ LOG_TRACE(Audio_DSP, "source_id=%zu sync=%u", source_id, state.sync);
+ }
+
+ if (config.rate_multiplier_dirty) {
+ config.rate_multiplier_dirty.Assign(0);
+ state.rate_multiplier = config.rate_multiplier;
+ LOG_TRACE(Audio_DSP, "source_id=%zu rate=%f", source_id, state.rate_multiplier);
+
+ 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);
+ state.rate_multiplier = 1.0f;
+ // Note: Actual firmware starts producing garbage if this occurs.
+ }
+ }
+
+ if (config.adpcm_coefficients_dirty) {
+ config.adpcm_coefficients_dirty.Assign(0);
+ std::transform(adpcm_coeffs, adpcm_coeffs + state.adpcm_coeffs.size(), state.adpcm_coeffs.begin(),
+ [](const auto& coeff) { return static_cast<s16>(coeff); });
+ LOG_TRACE(Audio_DSP, "source_id=%zu adpcm update", source_id);
+ }
+
+ if (config.gain_0_dirty) {
+ config.gain_0_dirty.Assign(0);
+ std::transform(config.gain[0], config.gain[0] + state.gain[0].size(), state.gain[0].begin(),
+ [](const auto& coeff) { return static_cast<float>(coeff); });
+ LOG_TRACE(Audio_DSP, "source_id=%zu gain 0 update", source_id);
+ }
+
+ if (config.gain_1_dirty) {
+ config.gain_1_dirty.Assign(0);
+ std::transform(config.gain[1], config.gain[1] + state.gain[1].size(), state.gain[1].begin(),
+ [](const auto& coeff) { return static_cast<float>(coeff); });
+ LOG_TRACE(Audio_DSP, "source_id=%zu gain 1 update", source_id);
+ }
+
+ if (config.gain_2_dirty) {
+ config.gain_2_dirty.Assign(0);
+ std::transform(config.gain[2], config.gain[2] + state.gain[2].size(), state.gain[2].begin(),
+ [](const auto& coeff) { return static_cast<float>(coeff); });
+ LOG_TRACE(Audio_DSP, "source_id=%zu gain 2 update", source_id);
+ }
+
+ if (config.filters_enabled_dirty) {
+ config.filters_enabled_dirty.Assign(0);
+ state.filters.Enable(config.simple_filter_enabled.ToBool(), config.biquad_filter_enabled.ToBool());
+ 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) {
+ config.simple_filter_dirty.Assign(0);
+ state.filters.Configure(config.simple_filter);
+ LOG_TRACE(Audio_DSP, "source_id=%zu simple filter update");
+ }
+
+ if (config.biquad_filter_dirty) {
+ config.biquad_filter_dirty.Assign(0);
+ state.filters.Configure(config.biquad_filter);
+ LOG_TRACE(Audio_DSP, "source_id=%zu biquad filter update");
+ }
+
+ if (config.interpolation_dirty) {
+ config.interpolation_dirty.Assign(0);
+ 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));
+ }
+
+ if (config.format_dirty || config.embedded_buffer_dirty) {
+ config.format_dirty.Assign(0);
+ state.format = config.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) {
+ config.mono_or_stereo_dirty.Assign(0);
+ 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));
+ }
+
+ if (config.embedded_buffer_dirty) {
+ config.embedded_buffer_dirty.Assign(0);
+ state.input_queue.emplace(Buffer{
+ config.physical_address,
+ config.length,
+ static_cast<u8>(config.adpcm_ps),
+ { config.adpcm_yn[0], config.adpcm_yn[1] },
+ config.adpcm_dirty.ToBool(),
+ config.is_looping.ToBool(),
+ config.buffer_id,
+ state.mono_or_stereo,
+ state.format,
+ false
+ });
+ LOG_TRACE(Audio_DSP, "enqueuing embedded addr=0x%08x len=%u id=%hu", config.physical_address, config.length, config.buffer_id);
+ }
+
+ if (config.buffer_queue_dirty) {
+ config.buffer_queue_dirty.Assign(0);
+ for (size_t i = 0; i < 4; i++) {
+ if (config.buffers_dirty & (1 << i)) {
+ const auto& b = config.buffers[i];
+ state.input_queue.emplace(Buffer{
+ b.physical_address,
+ b.length,
+ static_cast<u8>(b.adpcm_ps),
+ { b.adpcm_yn[0], b.adpcm_yn[1] },
+ b.adpcm_dirty != 0,
+ b.is_looping != 0,
+ b.buffer_id,
+ state.mono_or_stereo,
+ state.format,
+ true
+ });
+ LOG_TRACE(Audio_DSP, "enqueuing queued %zu addr=0x%08x len=%u id=%hu", i, b.physical_address, b.length, b.buffer_id);
+ }
+ }
+ config.buffers_dirty = 0;
+ }
+
+ if (config.dirty_raw) {
+ LOG_DEBUG(Audio_DSP, "source_id=%zu remaining_dirty=%x", source_id, config.dirty_raw);
+ }
+
+ config.dirty_raw = 0;
+}
+
+void Source::GenerateFrame() {
+ current_frame.fill({});
+
+ if (state.current_buffer.empty() && !DequeueBuffer()) {
+ state.enabled = false;
+ state.buffer_update = true;
+ state.current_buffer_id = 0;
+ return;
+ }
+
+ size_t frame_position = 0;
+
+ state.current_sample_number = state.next_sample_number;
+ while (frame_position < current_frame.size()) {
+ if (state.current_buffer.empty() && !DequeueBuffer()) {
+ break;
+ }
+
+ 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);
+ state.current_buffer.erase(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy);
+
+ frame_position += size_to_copy;
+ state.next_sample_number += static_cast<u32>(size_to_copy);
+ }
+
+ state.filters.ProcessFrame(current_frame);
+}
+
+
+bool Source::DequeueBuffer() {
+ ASSERT_MSG(state.current_buffer.empty(), "Shouldn't dequeue; we still have data in current_buffer");
+
+ if (state.input_queue.empty())
+ return false;
+
+ const Buffer buf = state.input_queue.top();
+ state.input_queue.pop();
+
+ if (buf.adpcm_dirty) {
+ state.adpcm_state.yn1 = buf.adpcm_yn[0];
+ state.adpcm_state.yn2 = buf.adpcm_yn[1];
+ }
+
+ if (buf.is_looping) {
+ LOG_ERROR(Audio_DSP, "Looped buffers are unimplemented at the moment");
+ }
+
+ const u8* const memory = Memory::GetPhysicalPointer(buf.physical_address);
+ if (memory) {
+ const unsigned num_channels = buf.mono_or_stereo == MonoOrStereo::Stereo ? 2 : 1;
+ switch (buf.format) {
+ case Format::PCM8:
+ state.current_buffer = Codec::DecodePCM8(num_channels, memory, buf.length);
+ break;
+ case Format::PCM16:
+ state.current_buffer = Codec::DecodePCM16(num_channels, memory, buf.length);
+ break;
+ case Format::ADPCM:
+ DEBUG_ASSERT(num_channels == 1);
+ state.current_buffer = Codec::DecodeADPCM(memory, buf.length, state.adpcm_coeffs, state.adpcm_state);
+ break;
+ default:
+ UNIMPLEMENTED();
+ break;
+ }
+ } else {
+ 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);
+ state.current_buffer.clear();
+ return true;
+ }
+
+ switch (state.interpolation_mode) {
+ case InterpolationMode::None:
+ state.current_buffer = AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier);
+ break;
+ case InterpolationMode::Linear:
+ state.current_buffer = AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
+ break;
+ case InterpolationMode::Polyphase:
+ // TODO(merry): Implement polyphase interpolation
+ state.current_buffer = AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
+ break;
+ default:
+ UNIMPLEMENTED();
+ break;
+ }
+
+ state.current_sample_number = 0;
+ state.next_sample_number = 0;
+ state.current_buffer_id = buf.buffer_id;
+ state.buffer_update = buf.from_queue;
+
+ 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());
+ return true;
+}
+
+SourceStatus::Status Source::GetCurrentStatus() {
+ SourceStatus::Status ret;
+
+ // Applications depend on the correct emulation of
+ // current_buffer_id_dirty and current_buffer_id to synchronise
+ // audio with video.
+ ret.is_enabled = state.enabled;
+ ret.current_buffer_id_dirty = state.buffer_update ? 1 : 0;
+ state.buffer_update = false;
+ ret.current_buffer_id = state.current_buffer_id;
+ ret.buffer_position = state.current_sample_number;
+ ret.sync = state.sync;
+
+ return ret;
+}
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/hle/source.h b/src/audio_core/hle/source.h
new file mode 100644
index 000000000..7ee08d424
--- /dev/null
+++ b/src/audio_core/hle/source.h
@@ -0,0 +1,144 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <queue>
+#include <vector>
+
+#include "audio_core/codec.h"
+#include "audio_core/hle/common.h"
+#include "audio_core/hle/dsp.h"
+#include "audio_core/hle/filter.h"
+#include "audio_core/interpolate.h"
+
+#include "common/common_types.h"
+
+namespace DSP {
+namespace HLE {
+
+/**
+ * This module performs:
+ * - Buffer management
+ * - Decoding of buffers
+ * - Buffer resampling and interpolation
+ * - Per-source filtering (SimpleFilter, BiquadFilter)
+ * - Per-source gain
+ * - Other per-source processing
+ */
+class Source final {
+public:
+ explicit Source(size_t source_id_) : source_id(source_id_) {
+ Reset();
+ }
+
+ /// Resets internal state.
+ void Reset();
+
+ /**
+ * 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 adpcm_coeffs ADPCM coefficients to use if config tells us to use them (may contain 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]);
+
+ /**
+ * 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 intermediate_mix_id The id of the intermediate mix whose gains we are using.
+ */
+ void MixInto(QuadFrame32& dest, size_t intermediate_mix_id) const;
+
+private:
+ const size_t source_id;
+ StereoFrame16 current_frame;
+
+ using Format = SourceConfiguration::Configuration::Format;
+ using InterpolationMode = SourceConfiguration::Configuration::InterpolationMode;
+ using MonoOrStereo = SourceConfiguration::Configuration::MonoOrStereo;
+
+ /// Internal representation of a buffer for our buffer queue
+ struct Buffer {
+ PAddr physical_address;
+ u32 length;
+ u8 adpcm_ps;
+ std::array<u16, 2> adpcm_yn;
+ bool adpcm_dirty;
+ bool is_looping;
+ u16 buffer_id;
+
+ MonoOrStereo mono_or_stereo;
+ Format format;
+
+ bool from_queue;
+ };
+
+ struct BufferOrder {
+ bool operator() (const Buffer& a, const Buffer& b) const {
+ // Lower buffer_id comes first.
+ return a.buffer_id > b.buffer_id;
+ }
+ };
+
+ struct {
+
+ // State variables
+
+ bool enabled = false;
+ u16 sync = 0;
+
+ // Mixing
+
+ std::array<std::array<float, 4>, 3> gain = {};
+
+ // Buffer queue
+
+ std::priority_queue<Buffer, std::vector<Buffer>, BufferOrder> input_queue;
+ MonoOrStereo mono_or_stereo = MonoOrStereo::Mono;
+ Format format = Format::ADPCM;
+
+ // Current buffer
+
+ u32 current_sample_number = 0;
+ u32 next_sample_number = 0;
+ std::vector<std::array<s16, 2>> current_buffer;
+
+ // buffer_id state
+
+ bool buffer_update = false;
+ u32 current_buffer_id = 0;
+
+ // Decoding state
+
+ std::array<s16, 16> adpcm_coeffs = {};
+ Codec::ADPCMState adpcm_state = {};
+
+ // Resampling state
+
+ float rate_multiplier = 1.0;
+ InterpolationMode interpolation_mode = InterpolationMode::Polyphase;
+ AudioInterp::State interp_state = {};
+
+ // Filter state
+
+ SourceFilters filters;
+
+ } state;
+
+ // Internal functions
+
+ /// INTERNAL: Update our internal state based on the current config.
+ 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.
+ void GenerateFrame();
+ /// INTERNAL: Dequeues a buffer and does preprocessing on it (decoding, resampling). Puts it into current_buffer.
+ bool DequeueBuffer();
+ /// INTERNAL: Generates a SourceStatus::Status based on our internal state.
+ SourceStatus::Status GetCurrentStatus();
+};
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp
new file mode 100644
index 000000000..dc75c04ee
--- /dev/null
+++ b/src/audio_core/sdl2_sink.cpp
@@ -0,0 +1,126 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <list>
+#include <vector>
+
+#include <SDL.h>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/sdl2_sink.h"
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include <numeric>
+
+namespace AudioCore {
+
+struct SDL2Sink::Impl {
+ unsigned int sample_rate = 0;
+
+ SDL_AudioDeviceID audio_device_id = 0;
+
+ std::list<std::vector<s16>> queue;
+
+ static void Callback(void* impl_, u8* buffer, int buffer_size_in_bytes);
+};
+
+SDL2Sink::SDL2Sink() : impl(std::make_unique<Impl>()) {
+ if (SDL_Init(SDL_INIT_AUDIO) < 0) {
+ LOG_CRITICAL(Audio_Sink, "SDL_Init(SDL_INIT_AUDIO) failed");
+ impl->audio_device_id = 0;
+ return;
+ }
+
+ SDL_AudioSpec desired_audiospec;
+ SDL_zero(desired_audiospec);
+ desired_audiospec.format = AUDIO_S16;
+ desired_audiospec.channels = 2;
+ desired_audiospec.freq = native_sample_rate;
+ desired_audiospec.samples = 1024;
+ desired_audiospec.userdata = impl.get();
+ desired_audiospec.callback = &Impl::Callback;
+
+ SDL_AudioSpec obtained_audiospec;
+ SDL_zero(obtained_audiospec);
+
+ impl->audio_device_id = SDL_OpenAudioDevice(nullptr, false, &desired_audiospec, &obtained_audiospec, 0);
+ if (impl->audio_device_id <= 0) {
+ LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed");
+ return;
+ }
+
+ impl->sample_rate = obtained_audiospec.freq;
+
+ // SDL2 audio devices start out paused, unpause it:
+ SDL_PauseAudioDevice(impl->audio_device_id, 0);
+}
+
+SDL2Sink::~SDL2Sink() {
+ if (impl->audio_device_id <= 0)
+ return;
+
+ SDL_CloseAudioDevice(impl->audio_device_id);
+}
+
+unsigned int SDL2Sink::GetNativeSampleRate() const {
+ if (impl->audio_device_id <= 0)
+ return native_sample_rate;
+
+ return impl->sample_rate;
+}
+
+void SDL2Sink::EnqueueSamples(const std::vector<s16>& samples) {
+ if (impl->audio_device_id <= 0)
+ return;
+
+ ASSERT_MSG(samples.size() % 2 == 0, "Samples must be in interleaved stereo PCM16 format (size must be a multiple of two)");
+
+ SDL_LockAudioDevice(impl->audio_device_id);
+ impl->queue.emplace_back(samples);
+ SDL_UnlockAudioDevice(impl->audio_device_id);
+}
+
+size_t SDL2Sink::SamplesInQueue() const {
+ if (impl->audio_device_id <= 0)
+ return 0;
+
+ 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 sum, const auto& buffer) {
+ // Division by two because each stereo sample is made of two s16.
+ return sum + buffer.size() / 2;
+ });
+
+ SDL_UnlockAudioDevice(impl->audio_device_id);
+
+ return total_size;
+}
+
+void SDL2Sink::Impl::Callback(void* impl_, u8* buffer, int buffer_size_in_bytes) {
+ 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.
+
+ while (remaining_size > 0 && !impl->queue.empty()) {
+ if (impl->queue.front().size() <= remaining_size) {
+ memcpy(buffer, impl->queue.front().data(), impl->queue.front().size() * sizeof(s16));
+ buffer += impl->queue.front().size() * sizeof(s16);
+ remaining_size -= impl->queue.front().size();
+ impl->queue.pop_front();
+ } else {
+ memcpy(buffer, impl->queue.front().data(), remaining_size * sizeof(s16));
+ buffer += remaining_size * sizeof(s16);
+ impl->queue.front().erase(impl->queue.front().begin(), impl->queue.front().begin() + remaining_size);
+ remaining_size = 0;
+ }
+ }
+
+ if (remaining_size > 0) {
+ memset(buffer, 0, remaining_size * sizeof(s16));
+ }
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h
new file mode 100644
index 000000000..0f296b673
--- /dev/null
+++ b/src/audio_core/sdl2_sink.h
@@ -0,0 +1,30 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <memory>
+
+#include "audio_core/sink.h"
+
+namespace AudioCore {
+
+class SDL2Sink final : public Sink {
+public:
+ SDL2Sink();
+ ~SDL2Sink() override;
+
+ unsigned int GetNativeSampleRate() const override;
+
+ void EnqueueSamples(const std::vector<s16>& samples) override;
+
+ size_t SamplesInQueue() const override;
+
+private:
+ struct Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h
index cad21a85e..1c881c3d2 100644
--- a/src/audio_core/sink.h
+++ b/src/audio_core/sink.h
@@ -19,7 +19,7 @@ public:
virtual ~Sink() = default;
/// The native rate of this sink. The sink expects to be fed samples that respect this. (Units: samples/sec)
- virtual unsigned GetNativeSampleRate() const = 0;
+ virtual unsigned int GetNativeSampleRate() const = 0;
/**
* Feed stereo samples to sink.
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp
index d2cc74103..ba5e83d17 100644
--- a/src/audio_core/sink_details.cpp
+++ b/src/audio_core/sink_details.cpp
@@ -8,10 +8,17 @@
#include "audio_core/null_sink.h"
#include "audio_core/sink_details.h"
+#ifdef HAVE_SDL2
+#include "audio_core/sdl2_sink.h"
+#endif
+
namespace AudioCore {
// g_sink_details is ordered in terms of desirability, with the best choice at the top.
const std::vector<SinkDetails> g_sink_details = {
+#ifdef HAVE_SDL2
+ { "sdl2", []() { return std::make_unique<SDL2Sink>(); } },
+#endif
{ "null", []() { return std::make_unique<NullSink>(); } },
};
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index 0e6171736..49126356f 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -58,7 +58,7 @@ bg_green =
[Audio]
# Which audio output engine to use.
-# auto (default): Auto-select, null: No audio output
+# auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available)
output_engine =
[Data Storage]
diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp
index 924189f4c..12cdd9d95 100644
--- a/src/citra/emu_window/emu_window_sdl2.cpp
+++ b/src/citra/emu_window/emu_window_sdl2.cpp
@@ -9,6 +9,8 @@
#define SDL_MAIN_HANDLED
#include <SDL.h>
+#include <glad/glad.h>
+
#include "common/key_map.h"
#include "common/logging/log.h"
#include "common/scm_rev.h"
@@ -98,6 +100,11 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
exit(1);
}
+ if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
+ LOG_CRITICAL(Frontend, "Failed to initialize GL functions! Exiting...");
+ exit(1);
+ }
+
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index cc9e0c624..3f0099200 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -55,6 +55,7 @@ set(HEADERS
configure_dialog.h
configure_general.h
game_list.h
+ game_list_p.h
hotkeys.h
main.h
ui_settings.h
diff --git a/src/citra_qt/debugger/graphics_breakpoints.cpp b/src/citra_qt/debugger/graphics_breakpoints.cpp
index c8510128a..fe66918a8 100644
--- a/src/citra_qt/debugger/graphics_breakpoints.cpp
+++ b/src/citra_qt/debugger/graphics_breakpoints.cpp
@@ -44,7 +44,7 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const
{ Pica::DebugContext::Event::PicaCommandProcessed, tr("Pica command processed") },
{ Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incoming primitive batch") },
{ Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch") },
- { Pica::DebugContext::Event::VertexLoaded, tr("Vertex loaded") },
+ { Pica::DebugContext::Event::VertexShaderInvocation, tr("Vertex shader invocation") },
{ Pica::DebugContext::Event::IncomingDisplayTransfer, tr("Incoming display transfer") },
{ Pica::DebugContext::Event::GSPCommandProcessed, tr("GSP command processed") },
{ Pica::DebugContext::Event::BufferSwapped, tr("Buffers swapped") }
diff --git a/src/citra_qt/debugger/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics_vertex_shader.cpp
index d648d4640..6e8d7ef42 100644
--- a/src/citra_qt/debugger/graphics_vertex_shader.cpp
+++ b/src/citra_qt/debugger/graphics_vertex_shader.cpp
@@ -365,7 +365,7 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De
input_data[i]->setValidator(new QDoubleValidator(input_data[i]));
}
- breakpoint_warning = new QLabel(tr("(data only available at VertexLoaded 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
@@ -454,7 +454,7 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De
void GraphicsVertexShaderWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) {
auto input = static_cast<Pica::Shader::InputVertex*>(data);
- if (event == Pica::DebugContext::Event::VertexLoaded) {
+ if (event == Pica::DebugContext::Event::VertexShaderInvocation) {
Reload(true, data);
} else {
// No vertex data is retrievable => invalidate currently stored vertex data
diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp
index d14532102..d4ac9c96e 100644
--- a/src/citra_qt/game_list.cpp
+++ b/src/citra_qt/game_list.cpp
@@ -34,8 +34,8 @@ GameList::GameList(QWidget* parent)
tree_view->setUniformRowHeights(true);
item_model->insertColumns(0, COLUMN_COUNT);
- item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type");
item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name");
+ item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type");
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size");
connect(tree_view, SIGNAL(activated(const QModelIndex&)), this, SLOT(ValidateEntry(const QModelIndex&)));
@@ -109,7 +109,11 @@ void GameList::SaveInterfaceLayout()
void GameList::LoadInterfaceLayout()
{
auto header = tree_view->header();
- 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
+ // so make it as large as possible as default.
+ header->resizeSection(COLUMN_NAME, header->width());
+ }
item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder());
}
@@ -143,9 +147,15 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool d
LOG_WARNING(Frontend, "Filetype and extension of file %s do not match.", physical_name.c_str());
}
+ std::vector<u8> smdh;
+ std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(FileUtil::IOFile(physical_name, "rb"), filetype, filename_filename, physical_name);
+
+ if (loader)
+ loader->ReadIcon(smdh);
+
emit EntryReady({
+ new GameListItemPath(QString::fromStdString(physical_name), smdh),
new GameListItem(QString::fromStdString(Loader::GetFileTypeString(filetype))),
- new GameListItemPath(QString::fromStdString(physical_name)),
new GameListItemSize(FileUtil::GetSize(physical_name)),
});
}
diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h
index 48febdc60..198674f04 100644
--- a/src/citra_qt/game_list.h
+++ b/src/citra_qt/game_list.h
@@ -20,8 +20,8 @@ class GameList : public QWidget {
public:
enum {
- COLUMN_FILE_TYPE,
COLUMN_NAME,
+ COLUMN_FILE_TYPE,
COLUMN_SIZE,
COLUMN_COUNT, // Number of columns
};
diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h
index 820012bce..284f5da81 100644
--- a/src/citra_qt/game_list_p.h
+++ b/src/citra_qt/game_list_p.h
@@ -6,13 +6,85 @@
#include <atomic>
+#include <QImage>
#include <QRunnable>
#include <QStandardItem>
#include <QString>
#include "citra_qt/util/util.h"
#include "common/string_util.h"
+#include "common/color.h"
+#include "core/loader/loader.h"
+
+#include "video_core/utils.h"
+
+/**
+ * Tests if data is a valid SMDH by its length and magic number.
+ * @param smdh_data data buffer to test
+ * @return bool test result
+ */
+static bool IsValidSMDH(const std::vector<u8>& smdh_data) {
+ if (smdh_data.size() < sizeof(Loader::SMDH))
+ return false;
+
+ u32 magic;
+ memcpy(&magic, smdh_data.data(), 4);
+
+ return Loader::MakeMagic('S', 'M', 'D', 'H') == magic;
+}
+
+/**
+ * Gets game icon from SMDH
+ * @param sdmh SMDH data
+ * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
+ * @return QPixmap game icon
+ */
+static QPixmap GetIconFromSMDH(const Loader::SMDH& smdh, bool large) {
+ u32 size;
+ const u8* icon_data;
+
+ if (large) {
+ size = 48;
+ icon_data = smdh.large_icon.data();
+ } else {
+ size = 24;
+ icon_data = smdh.small_icon.data();
+ }
+
+ QImage icon(size, size, QImage::Format::Format_RGB888);
+ for (u32 x = 0; x < size; ++x) {
+ for (u32 y = 0; y < size; ++y) {
+ u32 coarse_y = y & ~7;
+ auto v = Color::DecodeRGB565(
+ icon_data + VideoCore::GetMortonOffset(x, y, 2) + coarse_y * size * 2);
+ icon.setPixel(x, y, qRgb(v.r(), v.g(), v.b()));
+ }
+ }
+ return QPixmap::fromImage(icon);
+}
+
+/**
+ * Gets the default icon (for games without valid SMDH)
+ * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
+ * @return QPixmap default icon
+ */
+static QPixmap GetDefaultIcon(bool large) {
+ int size = large ? 48 : 24;
+ QPixmap icon(size, size);
+ icon.fill(Qt::transparent);
+ return icon;
+}
+
+/**
+ * Gets the short game title fromn SMDH
+ * @param sdmh SMDH data
+ * @param language title language
+ * @return QString short title
+ */
+static QString GetShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) {
+ return QString::fromUtf16(smdh.titles[static_cast<int>(language)].short_title.data());
+}
class GameListItem : public QStandardItem {
@@ -27,29 +99,43 @@ public:
* A specialization of GameListItem for path values.
* This class ensures that for every full path value it holds, a correct string representation
* of just the filename (with no extension) will be displayed to the user.
+ * If this class recieves valid SMDH data, it will also display game icons and titles.
*/
class GameListItemPath : public GameListItem {
public:
static const int FullPathRole = Qt::UserRole + 1;
+ static const int TitleRole = Qt::UserRole + 2;
GameListItemPath(): GameListItem() {}
- GameListItemPath(const QString& game_path): GameListItem()
+ GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data): GameListItem()
{
setData(game_path, FullPathRole);
+
+ if (!IsValidSMDH(smdh_data)) {
+ // SMDH is not valid, set a default icon
+ setData(GetDefaultIcon(true), Qt::DecorationRole);
+ return;
+ }
+
+ Loader::SMDH smdh;
+ memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH));
+
+ // Get icon from SMDH
+ setData(GetIconFromSMDH(smdh, true), Qt::DecorationRole);
+
+ // Get title form SMDH
+ setData(GetShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English), TitleRole);
}
- void setData(const QVariant& value, int role) override
- {
- // By specializing setData for FullPathRole, we can ensure that the two string
- // representations of the data are always accurate and in the correct format.
- if (role == FullPathRole) {
+ QVariant data(int role) const override {
+ if (role == Qt::DisplayRole) {
std::string filename;
- Common::SplitPath(value.toString().toStdString(), nullptr, &filename, nullptr);
- GameListItem::setData(QString::fromStdString(filename), Qt::DisplayRole);
- GameListItem::setData(value, FullPathRole);
+ Common::SplitPath(data(FullPathRole).toString().toStdString(), nullptr, &filename, nullptr);
+ QString title = data(TitleRole).toString();
+ return QString::fromStdString(filename) + (title.isEmpty() ? "" : "\n " + title);
} else {
- GameListItem::setData(value, role);
+ return GameListItem::data(role);
}
}
};
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index f1ab29755..a85c94a4b 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -6,6 +6,9 @@
#include <memory>
#include <thread>
+#include <glad/glad.h>
+
+#define QT_NO_OPENGL
#include <QDesktopWidget>
#include <QtGui>
#include <QFileDialog>
@@ -240,6 +243,14 @@ bool GMainWindow::InitializeSystem() {
if (emu_thread != nullptr)
ShutdownGame();
+ render_window->MakeCurrent();
+ if (!gladLoadGL()) {
+ QMessageBox::critical(this, tr("Error while starting Citra!"),
+ 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."));
+ return false;
+ }
+
// Initialize the core emulation
System::Result system_result = System::Init(render_window);
if (System::Result::Success != system_result) {
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 3d39f94d5..d7008fc66 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -65,6 +65,7 @@ namespace Log {
SUB(Render, OpenGL) \
CLS(Audio) \
SUB(Audio, DSP) \
+ SUB(Audio, Sink) \
CLS(Loader)
// GetClassName is a macro defined by Windows.h, grrr...
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 521362317..c6910b1c7 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -78,8 +78,9 @@ enum class Class : ClassType {
Render, ///< Emulator video output and hardware acceleration
Render_Software, ///< Software renderer backend
Render_OpenGL, ///< OpenGL backend
- Audio, ///< Emulator audio output
+ Audio, ///< Audio emulation
Audio_DSP, ///< The HLE implementation of the DSP
+ Audio_Sink, ///< Emulator audio output backend
Loader, ///< ROM loader
Count ///< Total number of logging classes
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 3bb843aab..cabab744a 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -51,7 +51,7 @@ void RunLoop(int tight_loop) {
}
HW::Update();
- if (HLE::g_reschedule) {
+ if (HLE::IsReschedulePending()) {
Kernel::Reschedule();
}
}
diff --git a/src/core/hle/applets/mii_selector.cpp b/src/core/hle/applets/mii_selector.cpp
index 1dd6757f7..b4456ca90 100644
--- a/src/core/hle/applets/mii_selector.cpp
+++ b/src/core/hle/applets/mii_selector.cpp
@@ -58,6 +58,11 @@ ResultCode MiiSelector::StartImpl(const Service::APT::AppletStartupParameter& pa
// TODO(Subv): Set the expected fields in the response buffer before resending it to the application.
// TODO(Subv): Reverse the parameter format for the Mii Selector
+ if(parameter.buffer_size >= sizeof(u32)) {
+ // TODO: defaults return no error, but garbage in other unknown fields
+ memset(parameter.data, 0, sizeof(u32));
+ }
+
// Let the application know that we're closing
Service::APT::MessageParameter message;
message.buffer_size = parameter.buffer_size;
diff --git a/src/core/hle/applets/mii_selector.h b/src/core/hle/applets/mii_selector.h
index 5619f8399..be6b04642 100644
--- a/src/core/hle/applets/mii_selector.h
+++ b/src/core/hle/applets/mii_selector.h
@@ -16,6 +16,50 @@
namespace HLE {
namespace Applets {
+struct MiiConfig {
+ u8 unk_000;
+ u8 unk_001;
+ u8 unk_002;
+ u8 unk_003;
+ u8 unk_004;
+ INSERT_PADDING_BYTES(3);
+ u16 unk_008;
+ INSERT_PADDING_BYTES(0x8C - 0xA);
+ u8 unk_08C;
+ INSERT_PADDING_BYTES(3);
+ u16 unk_090;
+ INSERT_PADDING_BYTES(2);
+ u32 unk_094;
+ u16 unk_098;
+ u8 unk_09A[0x64];
+ u8 unk_0FE;
+ u8 unk_0FF;
+ u32 unk_100;
+};
+
+static_assert(sizeof(MiiConfig) == 0x104, "MiiConfig structure has incorrect size");
+#define ASSERT_REG_POSITION(field_name, position) static_assert(offsetof(MiiConfig, field_name) == position, "Field "#field_name" has invalid position")
+ASSERT_REG_POSITION(unk_008, 0x08);
+ASSERT_REG_POSITION(unk_08C, 0x8C);
+ASSERT_REG_POSITION(unk_090, 0x90);
+ASSERT_REG_POSITION(unk_094, 0x94);
+ASSERT_REG_POSITION(unk_0FE, 0xFE);
+#undef ASSERT_REG_POSITION
+
+struct MiiResult {
+ u32 result_code;
+ u8 unk_04;
+ INSERT_PADDING_BYTES(7);
+ u8 unk_0C[0x60];
+ u8 unk_6C[0x16];
+ INSERT_PADDING_BYTES(2);
+};
+static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size");
+#define ASSERT_REG_POSITION(field_name, position) static_assert(offsetof(MiiResult, field_name) == position, "Field "#field_name" has invalid position")
+ASSERT_REG_POSITION(unk_0C, 0x0C);
+ASSERT_REG_POSITION(unk_6C, 0x6C);
+#undef ASSERT_REG_POSITION
+
class MiiSelector final : public Applet {
public:
MiiSelector(Service::APT::AppletId id) : Applet(id), started(false) { }
diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp
index e545de3b5..5c5373517 100644
--- a/src/core/hle/hle.cpp
+++ b/src/core/hle/hle.cpp
@@ -12,9 +12,13 @@
////////////////////////////////////////////////////////////////////////////////////////////////////
-namespace HLE {
+namespace {
+
+bool reschedule; ///< If true, immediately reschedules the CPU to a new thread
-bool g_reschedule; ///< If true, immediately reschedules the CPU to a new thread
+}
+
+namespace HLE {
void Reschedule(const char *reason) {
DEBUG_ASSERT_MSG(reason != nullptr && strlen(reason) < 256, "Reschedule: Invalid or too long reason.");
@@ -27,13 +31,21 @@ void Reschedule(const char *reason) {
Core::g_app_core->PrepareReschedule();
- g_reschedule = true;
+ reschedule = true;
+}
+
+bool IsReschedulePending() {
+ return reschedule;
+}
+
+void DoneRescheduling() {
+ reschedule = false;
}
void Init() {
Service::Init();
- g_reschedule = false;
+ reschedule = false;
LOG_DEBUG(Kernel, "initialized OK");
}
diff --git a/src/core/hle/hle.h b/src/core/hle/hle.h
index e0b97797c..69ac0ade6 100644
--- a/src/core/hle/hle.h
+++ b/src/core/hle/hle.h
@@ -13,9 +13,9 @@ const Handle INVALID_HANDLE = 0;
namespace HLE {
-extern bool g_reschedule; ///< If true, immediately reschedules the CPU to a new thread
-
void Reschedule(const char *reason);
+bool IsReschedulePending();
+void DoneRescheduling();
void Init();
void Shutdown();
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index bf32f653d..6dc95d0f1 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -483,7 +483,8 @@ void Reschedule() {
Thread* cur = GetCurrentThread();
Thread* next = PopNextReadyThread();
- HLE::g_reschedule = false;
+
+ HLE::DoneRescheduling();
// Don't bother switching to the same thread
if (next == cur)
diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp
index 5fb3b9e2b..48a11ef81 100644
--- a/src/core/loader/3dsx.cpp
+++ b/src/core/loader/3dsx.cpp
@@ -303,4 +303,31 @@ ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& ro
return ResultStatus::ErrorNotUsed;
}
+ResultStatus AppLoader_THREEDSX::ReadIcon(std::vector<u8>& buffer) {
+ if (!file.IsOpen())
+ return ResultStatus::Error;
+
+ // Reset read pointer in case this file has been read before.
+ file.Seek(0, SEEK_SET);
+
+ THREEDSX_Header hdr;
+ if (file.ReadBytes(&hdr, sizeof(THREEDSX_Header)) != sizeof(THREEDSX_Header))
+ return ResultStatus::Error;
+
+ if (hdr.header_size != sizeof(THREEDSX_Header))
+ return ResultStatus::Error;
+
+ // Check if the 3DSX has a SMDH...
+ if (hdr.smdh_offset != 0) {
+ file.Seek(hdr.smdh_offset, SEEK_SET);
+ buffer.resize(hdr.smdh_size);
+
+ if (file.ReadBytes(&buffer[0], hdr.smdh_size) != hdr.smdh_size)
+ return ResultStatus::Error;
+
+ return ResultStatus::Success;
+ }
+ return ResultStatus::ErrorNotUsed;
+}
+
} // namespace Loader
diff --git a/src/core/loader/3dsx.h b/src/core/loader/3dsx.h
index 365ddb7a5..3ee686703 100644
--- a/src/core/loader/3dsx.h
+++ b/src/core/loader/3dsx.h
@@ -17,7 +17,7 @@ namespace Loader {
/// Loads an 3DSX file
class AppLoader_THREEDSX final : public AppLoader {
public:
- AppLoader_THREEDSX(FileUtil::IOFile&& file, std::string filename, const std::string& filepath)
+ AppLoader_THREEDSX(FileUtil::IOFile&& file, const std::string& filename, const std::string& filepath)
: AppLoader(std::move(file)), filename(std::move(filename)), filepath(filepath) {}
/**
@@ -34,6 +34,13 @@ public:
ResultStatus Load() override;
/**
+ * Get the icon (typically icon section) of the application
+ * @param buffer Reference to buffer to store data
+ * @return ResultStatus result of function
+ */
+ ResultStatus ReadIcon(std::vector<u8>& buffer) override;
+
+ /**
* Get the RomFS of the application
* @param romfs_file Reference to buffer to store data
* @param offset Offset in the file to the RomFS
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 886501c41..af3f62248 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -90,6 +90,28 @@ const char* GetFileTypeString(FileType type) {
return "unknown";
}
+std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type,
+ const std::string& filename, const std::string& filepath) {
+ switch (type) {
+
+ // 3DSX file format.
+ case FileType::THREEDSX:
+ return std::make_unique<AppLoader_THREEDSX>(std::move(file), filename, filepath);
+
+ // Standard ELF file format.
+ case FileType::ELF:
+ return std::make_unique<AppLoader_ELF>(std::move(file), filename);
+
+ // NCCH/NCSD container formats.
+ case FileType::CXI:
+ case FileType::CCI:
+ return std::make_unique<AppLoader_NCCH>(std::move(file), filepath);
+
+ default:
+ return std::unique_ptr<AppLoader>();
+ }
+}
+
ResultStatus LoadFile(const std::string& filename) {
FileUtil::IOFile file(filename, "rb");
if (!file.IsOpen()) {
@@ -111,38 +133,29 @@ ResultStatus LoadFile(const std::string& filename) {
LOG_INFO(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type));
+ std::unique_ptr<AppLoader> app_loader = GetLoader(std::move(file), type, filename_filename, filename);
+
switch (type) {
- //3DSX file format...
+ // 3DSX file format...
+ // or NCCH/NCSD container formats...
case FileType::THREEDSX:
- {
- AppLoader_THREEDSX app_loader(std::move(file), filename_filename, filename);
- // Load application and RomFS
- if (ResultStatus::Success == app_loader.Load()) {
- Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(app_loader), Service::FS::ArchiveIdCode::RomFS);
- return ResultStatus::Success;
- }
- break;
- }
-
- // Standard ELF file format...
- case FileType::ELF:
- return AppLoader_ELF(std::move(file), filename_filename).Load();
-
- // NCCH/NCSD container formats...
case FileType::CXI:
case FileType::CCI:
{
- AppLoader_NCCH app_loader(std::move(file), filename);
-
// Load application and RomFS
- ResultStatus result = app_loader.Load();
+ ResultStatus result = app_loader->Load();
if (ResultStatus::Success == result) {
- Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(app_loader), Service::FS::ArchiveIdCode::RomFS);
+ Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*app_loader), Service::FS::ArchiveIdCode::RomFS);
+ return ResultStatus::Success;
}
return result;
}
+ // Standard ELF file format...
+ case FileType::ELF:
+ return app_loader->Load();
+
// CIA file format...
case FileType::CIA:
return ResultStatus::ErrorNotImplemented;
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 84a4ce5fc..9d3e9ed3b 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -10,8 +10,10 @@
#include <string>
#include <vector>
+#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/file_util.h"
+#include "common/swap.h"
namespace Kernel {
struct AddressMapping;
@@ -78,6 +80,51 @@ constexpr u32 MakeMagic(char a, char b, char c, char d) {
return a | b << 8 | c << 16 | d << 24;
}
+/// SMDH data structure that contains titles, icons etc. See https://www.3dbrew.org/wiki/SMDH
+struct SMDH {
+ u32_le magic;
+ u16_le version;
+ INSERT_PADDING_BYTES(2);
+
+ struct Title {
+ std::array<u16, 0x40> short_title;
+ std::array<u16, 0x80> long_title;
+ std::array<u16, 0x40> publisher;
+ };
+ std::array<Title, 16> titles;
+
+ std::array<u8, 16> ratings;
+ u32_le region_lockout;
+ u32_le match_maker_id;
+ u64_le match_maker_bit_id;
+ u32_le flags;
+ u16_le eula_version;
+ INSERT_PADDING_BYTES(2);
+ float_le banner_animation_frame;
+ u32_le cec_id;
+ INSERT_PADDING_BYTES(8);
+
+ std::array<u8, 0x480> small_icon;
+ std::array<u8, 0x1200> large_icon;
+
+ /// indicates the language used for each title entry
+ enum class TitleLanguage {
+ Japanese = 0,
+ English = 1,
+ French = 2,
+ German = 3,
+ Italian = 4,
+ Spanish = 5,
+ SimplifiedChinese = 6,
+ Korean= 7,
+ Dutch = 8,
+ Portuguese = 9,
+ Russian = 10,
+ TraditionalChinese = 11
+ };
+};
+static_assert(sizeof(SMDH) == 0x36C0, "SMDH structure size is wrong");
+
/// Interface for loading an application
class AppLoader : NonCopyable {
public:
@@ -150,6 +197,16 @@ protected:
extern const std::initializer_list<Kernel::AddressMapping> default_address_mappings;
/**
+ * Get a loader for a file with a specific type
+ * @param file The file to load
+ * @param type The type of the file
+ * @param filename the file name (without path)
+ * @param filepath the file full path (with name)
+ * @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type
+ */
+std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type, const std::string& filename, const std::string& filepath);
+
+/**
* Identifies and loads a bootable file
* @param filename String filename of bootable file
* @return ResultStatus result of function
diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp
index 066e91a9e..d362a4419 100644
--- a/src/core/loader/ncch.cpp
+++ b/src/core/loader/ncch.cpp
@@ -173,6 +173,10 @@ ResultStatus AppLoader_NCCH::LoadSectionExeFS(const char* name, std::vector<u8>&
if (!file.IsOpen())
return ResultStatus::Error;
+ ResultStatus result = LoadExeFS();
+ if (result != ResultStatus::Success)
+ return result;
+
LOG_DEBUG(Loader, "%d sections:", kMaxSections);
// Iterate through the ExeFs archive until we find a section with the specified name...
for (unsigned section_number = 0; section_number < kMaxSections; section_number++) {
@@ -215,9 +219,9 @@ ResultStatus AppLoader_NCCH::LoadSectionExeFS(const char* name, std::vector<u8>&
return ResultStatus::ErrorNotUsed;
}
-ResultStatus AppLoader_NCCH::Load() {
- if (is_loaded)
- return ResultStatus::ErrorAlreadyLoaded;
+ResultStatus AppLoader_NCCH::LoadExeFS() {
+ if (is_exefs_loaded)
+ return ResultStatus::Success;
if (!file.IsOpen())
return ResultStatus::Error;
@@ -282,6 +286,18 @@ ResultStatus AppLoader_NCCH::Load() {
if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header))
return ResultStatus::Error;
+ is_exefs_loaded = true;
+ return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_NCCH::Load() {
+ if (is_loaded)
+ return ResultStatus::ErrorAlreadyLoaded;
+
+ ResultStatus result = LoadExeFS();
+ if (result != ResultStatus::Success)
+ return result;
+
is_loaded = true; // Set state to loaded
return LoadExec(); // Load the executable into memory for booting
diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h
index ca6772a78..fd852c3de 100644
--- a/src/core/loader/ncch.h
+++ b/src/core/loader/ncch.h
@@ -232,6 +232,13 @@ private:
*/
ResultStatus LoadExec();
+ /**
+ * Ensure ExeFS is loaded and ready for reading sections
+ * @return ResultStatus result of function
+ */
+ ResultStatus LoadExeFS();
+
+ bool is_exefs_loaded = false;
bool is_compressed = false;
u32 entry_point = 0;
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index be1a936b2..dd1379503 100644
--- a/src/video_core/command_processor.cpp
+++ b/src/video_core/command_processor.cpp
@@ -146,10 +146,9 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
Shader::UnitState<false> shader_unit;
Shader::Setup();
- if (g_debug_context)
- g_debug_context->OnEvent(DebugContext::Event::VertexLoaded, static_cast<void*>(&immediate_input));
-
// Send to vertex shader
+ if (g_debug_context)
+ g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, static_cast<void*>(&immediate_input));
Shader::OutputVertex output = Shader::Run(shader_unit, immediate_input, regs.vs.num_input_attributes+1);
// Send to renderer
@@ -272,10 +271,9 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
Shader::InputVertex input;
loader.LoadVertex(base_address, index, vertex, input, memory_accesses);
- if (g_debug_context)
- g_debug_context->OnEvent(DebugContext::Event::VertexLoaded, (void*)&input);
-
// Send to vertex shader
+ if (g_debug_context)
+ g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, (void*)&input);
output = Shader::Run(shader_unit, input, loader.GetNumTotalAttributes());
if (is_indexed) {
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h
index be2d0301a..f628292a4 100644
--- a/src/video_core/debug_utils/debug_utils.h
+++ b/src/video_core/debug_utils/debug_utils.h
@@ -40,7 +40,7 @@ public:
PicaCommandProcessed,
IncomingPrimitiveBatch,
FinishedPrimitiveBatch,
- VertexLoaded,
+ VertexShaderInvocation,
IncomingDisplayTransfer,
GSPCommandProcessed,
BufferSwapped,
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 0e9a0be8b..7fcd36409 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -473,12 +473,6 @@ static void DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity,
bool RendererOpenGL::Init() {
render_window->MakeCurrent();
- // TODO: Make frontends initialize this, so they can use gladLoadGLLoader with their own loaders
- if (!gladLoadGL()) {
- LOG_CRITICAL(Render_OpenGL, "Failed to initialize GL functions! Exiting...");
- exit(-1);
- }
-
if (GLAD_GL_KHR_debug) {
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(DebugHandler, nullptr);