diff options
Diffstat (limited to 'src')
130 files changed, 3704 insertions, 1000 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index de4fe716a..1e1245160 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(common) add_subdirectory(core) add_subdirectory(video_core) add_subdirectory(audio_core) +add_subdirectory(tests) if (ENABLE_SDL2) add_subdirectory(citra) endif() diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 5a2747e78..a72a907ef 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -3,9 +3,12 @@ set(SRCS codec.cpp hle/dsp.cpp hle/filter.cpp + hle/mixers.cpp hle/pipe.cpp + hle/source.cpp interpolate.cpp sink_details.cpp + time_stretch.cpp ) set(HEADERS @@ -14,16 +17,30 @@ set(HEADERS hle/common.h hle/dsp.h hle/filter.h + hle/mixers.h hle/pipe.h + hle/source.h interpolate.h null_sink.h sink.h sink_details.h + time_stretch.h ) 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..0640e1eff 100644 --- a/src/audio_core/hle/dsp.cpp +++ b/src/audio_core/hle/dsp.cpp @@ -2,15 +2,21 @@ // 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/mixers.h" #include "audio_core/hle/pipe.h" +#include "audio_core/hle/source.h" #include "audio_core/sink.h" +#include "audio_core/time_stretch.h" namespace DSP { namespace HLE { +// Region management + std::array<SharedMemory, 2> g_regions; static size_t CurrentRegionIndex() { @@ -38,21 +44,96 @@ static SharedMemory& WriteRegion() { return g_regions[1 - CurrentRegionIndex()]; } +// Audio processing and mixing + +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 Mixers mixers; + +static StereoFrame16 GenerateCurrentFrame() { + SharedMemory& read = ReadRegion(); + SharedMemory& write = WriteRegion(); + + std::array<QuadFrame32, 3> intermediate_mixes = {}; + + // Generate 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); + } + } + + // Generate final mix + write.dsp_status = mixers.Tick(read.dsp_configuration, read.intermediate_mix_samples, write.intermediate_mix_samples, intermediate_mixes); + + StereoFrame16 output_frame = mixers.GetOutput(); + + // Write current output frame to the shared memory region + for (size_t samplei = 0; samplei < output_frame.size(); samplei++) { + for (size_t channeli = 0; channeli < output_frame[0].size(); channeli++) { + write.final_samples.pcm16[samplei][channeli] = s16_le(output_frame[samplei][channeli]); + } + } + + return output_frame; +} + +// Audio output + static std::unique_ptr<AudioCore::Sink> sink; +static AudioCore::TimeStretcher time_stretcher; + +static void OutputCurrentFrame(const StereoFrame16& frame) { + time_stretcher.AddSamples(&frame[0][0], frame.size()); + sink->EnqueueSamples(time_stretcher.Process(sink->SamplesInQueue())); +} + +// Public Interface void Init() { DSP::HLE::ResetPipes(); + + for (auto& source : sources) { + source.Reset(); + } + + mixers.Reset(); + + time_stretcher.Reset(); + if (sink) { + time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); + } } void Shutdown() { + time_stretcher.Flush(); + while (true) { + std::vector<s16> residual_audio = time_stretcher.Process(sink->SamplesInQueue()); + if (residual_audio.empty()) + break; + sink->EnqueueSamples(residual_audio); + } } bool Tick() { + StereoFrame16 current_frame = {}; + + // TODO: Check dsp::DSP semaphore (which indicates emulated application has finished writing to shared memory region) + current_frame = GenerateCurrentFrame(); + + OutputCurrentFrame(current_frame); + return true; } void SetSink(std::unique_ptr<AudioCore::Sink> sink_) { sink = std::move(sink_); + time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); } } // namespace HLE diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/dsp.h index 4f2410c27..9275cd7de 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); }; @@ -432,7 +428,7 @@ ASSERT_DSP_STRUCT(DspStatus, 32); /// Final mixed output in PCM16 stereo format, what you hear out of the speakers. /// When the application writes to this region it has no effect. struct FinalMixSamples { - s16_le pcm16[2 * samples_per_frame]; + s16_le pcm16[samples_per_frame][2]; }; ASSERT_DSP_STRUCT(FinalMixSamples, 640); @@ -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/mixers.cpp b/src/audio_core/hle/mixers.cpp new file mode 100644 index 000000000..18335f7f0 --- /dev/null +++ b/src/audio_core/hle/mixers.cpp @@ -0,0 +1,201 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstddef> + +#include "audio_core/hle/common.h" +#include "audio_core/hle/dsp.h" +#include "audio_core/hle/mixers.h" + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/math_util.h" + +namespace DSP { +namespace HLE { + +void Mixers::Reset() { + current_frame.fill({}); + state = {}; +} + +DspStatus Mixers::Tick(DspConfiguration& config, + const IntermediateMixSamples& read_samples, + IntermediateMixSamples& write_samples, + const std::array<QuadFrame32, 3>& input) +{ + ParseConfig(config); + + AuxReturn(read_samples); + AuxSend(write_samples, input); + + MixCurrentFrame(); + + return GetCurrentStatus(); +} + +void Mixers::ParseConfig(DspConfiguration& config) { + if (!config.dirty_raw) { + return; + } + + if (config.mixer1_enabled_dirty) { + config.mixer1_enabled_dirty.Assign(0); + state.mixer1_enabled = config.mixer1_enabled != 0; + LOG_TRACE(Audio_DSP, "mixers mixer1_enabled = %hu", config.mixer1_enabled); + } + + if (config.mixer2_enabled_dirty) { + config.mixer2_enabled_dirty.Assign(0); + state.mixer2_enabled = config.mixer2_enabled != 0; + LOG_TRACE(Audio_DSP, "mixers mixer2_enabled = %hu", config.mixer2_enabled); + } + + if (config.volume_0_dirty) { + config.volume_0_dirty.Assign(0); + state.intermediate_mixer_volume[0] = config.volume[0]; + LOG_TRACE(Audio_DSP, "mixers volume[0] = %f", config.volume[0]); + } + + if (config.volume_1_dirty) { + config.volume_1_dirty.Assign(0); + state.intermediate_mixer_volume[1] = config.volume[1]; + LOG_TRACE(Audio_DSP, "mixers volume[1] = %f", config.volume[1]); + } + + if (config.volume_2_dirty) { + config.volume_2_dirty.Assign(0); + state.intermediate_mixer_volume[2] = config.volume[2]; + LOG_TRACE(Audio_DSP, "mixers volume[2] = %f", config.volume[2]); + } + + if (config.output_format_dirty) { + config.output_format_dirty.Assign(0); + state.output_format = config.output_format; + LOG_TRACE(Audio_DSP, "mixers output_format = %zu", static_cast<size_t>(config.output_format)); + } + + if (config.headphones_connected_dirty) { + config.headphones_connected_dirty.Assign(0); + // Do nothing. + // (Note: Whether headphones are connected does affect coefficients used for surround sound.) + LOG_TRACE(Audio_DSP, "mixers headphones_connected=%hu", config.headphones_connected); + } + + if (config.dirty_raw) { + LOG_DEBUG(Audio_DSP, "mixers remaining_dirty=%x", config.dirty_raw); + } + + config.dirty_raw = 0; +} + +static s16 ClampToS16(s32 value) { + 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) { + return { + ClampToS16(static_cast<s32>(a[0]) + static_cast<s32>(b[0])), + ClampToS16(static_cast<s32>(a[1]) + static_cast<s32>(b[1])) + }; +} + +void Mixers::DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& samples) { + // TODO(merry): Limiter. (Currently we're performing final mixing assuming a disabled limiter.) + + switch (state.output_format) { + case OutputFormat::Mono: + std::transform(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 + s16 mono = ClampToS16(static_cast<s32>((gain * sample[0] + gain * sample[1] + gain * sample[2] + gain * sample[3]) / 2)); + // Mix into current frame + return AddAndClampToS16(accumulator, { mono, mono }); + }); + return; + + case OutputFormat::Surround: + // TODO(merry): Implement surround sound. + // fallthrough + + case OutputFormat::Stereo: + std::transform(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 + s16 left = ClampToS16(static_cast<s32>(gain * sample[0] + gain * sample[2])); + s16 right = ClampToS16(static_cast<s32>(gain * sample[1] + gain * sample[3])); + // Mix into current frame + return AddAndClampToS16(accumulator, { left, right }); + }); + return; + } + + UNREACHABLE_MSG("Invalid output_format %zu", static_cast<size_t>(state.output_format)); +} + +void Mixers::AuxReturn(const IntermediateMixSamples& read_samples) { + // NOTE: read_samples.mix{1,2}.pcm32 annoyingly have their dimensions in reverse order to QuadFrame32. + + if (state.mixer1_enabled) { + for (size_t sample = 0; sample < samples_per_frame; sample++) { + for (size_t channel = 0; channel < 4; channel++) { + state.intermediate_mix_buffer[1][sample][channel] = read_samples.mix1.pcm32[channel][sample]; + } + } + } + + if (state.mixer2_enabled) { + for (size_t sample = 0; sample < samples_per_frame; sample++) { + for (size_t channel = 0; channel < 4; channel++) { + 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) { + // NOTE: read_samples.mix{1,2}.pcm32 annoyingly have their dimensions in reverse order to QuadFrame32. + + state.intermediate_mix_buffer[0] = input[0]; + + if (state.mixer1_enabled) { + for (size_t sample = 0; sample < samples_per_frame; sample++) { + for (size_t channel = 0; channel < 4; channel++) { + write_samples.mix1.pcm32[channel][sample] = input[1][sample][channel]; + } + } + } else { + state.intermediate_mix_buffer[1] = input[1]; + } + + if (state.mixer2_enabled) { + for (size_t sample = 0; sample < samples_per_frame; sample++) { + for (size_t channel = 0; channel < 4; channel++) { + write_samples.mix2.pcm32[channel][sample] = input[2][sample][channel]; + } + } + } else { + state.intermediate_mix_buffer[2] = input[2]; + } +} + +void Mixers::MixCurrentFrame() { + current_frame.fill({}); + + for (size_t mix = 0; mix < 3; mix++) { + DownmixAndMixIntoCurrentFrame(state.intermediate_mixer_volume[mix], state.intermediate_mix_buffer[mix]); + } + + // TODO(merry): Compressor. (We currently assume a disabled compressor.) +} + +DspStatus Mixers::GetCurrentStatus() const { + DspStatus status; + status.unknown = 0; + status.dropped_frames = 0; + return status; +} + +} // namespace HLE +} // namespace DSP diff --git a/src/audio_core/hle/mixers.h b/src/audio_core/hle/mixers.h new file mode 100644 index 000000000..b52952eb5 --- /dev/null +++ b/src/audio_core/hle/mixers.h @@ -0,0 +1,63 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "audio_core/hle/common.h" +#include "audio_core/hle/dsp.h" + +namespace DSP { +namespace HLE { + +class Mixers final { +public: + Mixers() { + Reset(); + } + + void Reset(); + + DspStatus Tick(DspConfiguration& config, + const IntermediateMixSamples& read_samples, + IntermediateMixSamples& write_samples, + const std::array<QuadFrame32, 3>& input); + + StereoFrame16 GetOutput() const { + return current_frame; + } + +private: + StereoFrame16 current_frame = {}; + + using OutputFormat = DspConfiguration::OutputFormat; + + struct { + std::array<float, 3> intermediate_mixer_volume = {}; + + bool mixer1_enabled = false; + bool mixer2_enabled = false; + std::array<QuadFrame32, 3> intermediate_mix_buffer = {}; + + OutputFormat output_format = OutputFormat::Stereo; + + } state; + + /// INTERNAL: Update our internal state based on the current config. + void ParseConfig(DspConfiguration& config); + /// INTERNAL: Read samples from shared memory that have been modified by the ARM11. + void AuxReturn(const IntermediateMixSamples& read_samples); + /// INTERNAL: Write samples to shared memory for the ARM11 to modify. + void AuxSend(IntermediateMixSamples& write_samples, const std::array<QuadFrame32, 3>& input); + /// INTERNAL: Mix current_frame. + void MixCurrentFrame(); + /// INTERNAL: Downmix from quadraphonic to stereo based on status.output_format and accumulate into current_frame. + void DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& samples); + /// INTERNAL: Generate DspStatus based on internal state. + DspStatus GetCurrentStatus() const; +}; + +} // namespace HLE +} // namespace DSP diff --git a/src/audio_core/hle/pipe.cpp b/src/audio_core/hle/pipe.cpp index 03280780f..44dff1345 100644 --- a/src/audio_core/hle/pipe.cpp +++ b/src/audio_core/hle/pipe.cpp @@ -36,12 +36,17 @@ std::vector<u8> PipeRead(DspPipe pipe_number, u32 length) { return {}; } + if (length > UINT16_MAX) { // Can only read at most UINT16_MAX from the pipe + LOG_ERROR(Audio_DSP, "length of %u greater than max of %u", length, UINT16_MAX); + return {}; + } + std::vector<u8>& data = pipe_data[pipe_index]; if (length > data.size()) { LOG_WARNING(Audio_DSP, "pipe_number = %zu is out of data, application requested read of %u but %zu remain", pipe_index, length, data.size()); - length = data.size(); + length = static_cast<u32>(data.size()); } if (length == 0) @@ -94,7 +99,7 @@ static void AudioPipeWriteStructAddresses() { }; // Begin with a u16 denoting the number of structs. - WriteU16(DspPipe::Audio, struct_addresses.size()); + WriteU16(DspPipe::Audio, static_cast<u16>(struct_addresses.size())); // Then write the struct addresses. for (u16 addr : struct_addresses) { WriteU16(DspPipe::Audio, addr); diff --git a/src/audio_core/hle/pipe.h b/src/audio_core/hle/pipe.h index 64d97f8ba..b714c0496 100644 --- a/src/audio_core/hle/pipe.h +++ b/src/audio_core/hle/pipe.h @@ -24,10 +24,14 @@ enum class DspPipe { constexpr size_t NUM_DSP_PIPE = 8; /** - * Read a DSP pipe. - * @param pipe_number The Pipe ID - * @param length How much data to request. - * @return The data read from the pipe. The size of this vector can be less than the length requested. + * 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 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 greater than the amount of data available, this function will only read the available amount. + * @param pipe_number a `DspPipe` + * @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. */ std::vector<u8> PipeRead(DspPipe pipe_number, u32 length); diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp new file mode 100644 index 000000000..30552fe26 --- /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", source_id); + } + + 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", source_id); + } + + 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/audio_core/time_stretch.cpp b/src/audio_core/time_stretch.cpp new file mode 100644 index 000000000..ea38f40d0 --- /dev/null +++ b/src/audio_core/time_stretch.cpp @@ -0,0 +1,144 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <chrono> +#include <cmath> +#include <vector> + +#include <SoundTouch.h> + +#include "audio_core/audio_core.h" +#include "audio_core/time_stretch.h" + +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/math_util.h" + +using steady_clock = std::chrono::steady_clock; + +namespace AudioCore { + +constexpr double MIN_RATIO = 0.1; +constexpr double MAX_RATIO = 100.0; + +static double ClampRatio(double ratio) { + return MathUtil::Clamp(ratio, MIN_RATIO, MAX_RATIO); +} + +constexpr double MIN_DELAY_TIME = 0.05; // Units: seconds +constexpr double MAX_DELAY_TIME = 0.25; // Units: seconds +constexpr size_t DROP_FRAMES_SAMPLE_DELAY = 16000; // Units: samples + +constexpr double SMOOTHING_FACTOR = 0.007; + +struct TimeStretcher::Impl { + soundtouch::SoundTouch soundtouch; + + steady_clock::time_point frame_timer = steady_clock::now(); + size_t samples_queued = 0; + + double smoothed_ratio = 1.0; + + double sample_rate = static_cast<double>(native_sample_rate); +}; + +std::vector<s16> TimeStretcher::Process(size_t samples_in_queue) { + // This is a very simple algorithm without any fancy control theory. It works and is stable. + + double ratio = CalculateCurrentRatio(); + ratio = CorrectForUnderAndOverflow(ratio, samples_in_queue); + impl->smoothed_ratio = (1.0 - SMOOTHING_FACTOR) * impl->smoothed_ratio + SMOOTHING_FACTOR * ratio; + impl->smoothed_ratio = ClampRatio(impl->smoothed_ratio); + + // SoundTouch's tempo definition the inverse of our ratio definition. + impl->soundtouch.setTempo(1.0 / impl->smoothed_ratio); + + std::vector<s16> samples = GetSamples(); + if (samples_in_queue >= DROP_FRAMES_SAMPLE_DELAY) { + samples.clear(); + LOG_DEBUG(Audio, "Dropping frames!"); + } + return samples; +} + +TimeStretcher::TimeStretcher() : impl(std::make_unique<Impl>()) { + impl->soundtouch.setPitch(1.0); + impl->soundtouch.setChannels(2); + impl->soundtouch.setSampleRate(native_sample_rate); + Reset(); +} + +TimeStretcher::~TimeStretcher() { + impl->soundtouch.clear(); +} + +void TimeStretcher::SetOutputSampleRate(unsigned int sample_rate) { + impl->sample_rate = static_cast<double>(sample_rate); + impl->soundtouch.setRate(static_cast<double>(native_sample_rate) / impl->sample_rate); +} + +void TimeStretcher::AddSamples(const s16* buffer, size_t num_samples) { + impl->soundtouch.putSamples(buffer, static_cast<uint>(num_samples)); + impl->samples_queued += num_samples; +} + +void TimeStretcher::Flush() { + impl->soundtouch.flush(); +} + +void TimeStretcher::Reset() { + impl->soundtouch.setTempo(1.0); + impl->soundtouch.clear(); + impl->smoothed_ratio = 1.0; + impl->frame_timer = steady_clock::now(); + impl->samples_queued = 0; + SetOutputSampleRate(native_sample_rate); +} + +double TimeStretcher::CalculateCurrentRatio() { + const steady_clock::time_point now = steady_clock::now(); + 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 actual_time = duration.count(); + + double ratio; + if (expected_time != 0) { + ratio = ClampRatio(actual_time / expected_time); + } else { + ratio = impl->smoothed_ratio; + } + + impl->frame_timer = now; + impl->samples_queued = 0; + + return ratio; +} + +double TimeStretcher::CorrectForUnderAndOverflow(double ratio, size_t sample_delay) const { + const size_t min_sample_delay = static_cast<size_t>(MIN_DELAY_TIME * impl->sample_rate); + const size_t max_sample_delay = static_cast<size_t>(MAX_DELAY_TIME * impl->sample_rate); + + if (sample_delay < min_sample_delay) { + // Make the ratio bigger. + ratio = ratio > 1.0 ? ratio * ratio : sqrt(ratio); + } else if (sample_delay > max_sample_delay) { + // Make the ratio smaller. + ratio = ratio > 1.0 ? sqrt(ratio) : ratio * ratio; + } + + return ClampRatio(ratio); +} + +std::vector<s16> TimeStretcher::GetSamples() { + uint available = impl->soundtouch.numSamples(); + + std::vector<s16> output(static_cast<size_t>(available) * 2); + + impl->soundtouch.receiveSamples(output.data(), available); + + return output; +} + +} // namespace AudioCore diff --git a/src/audio_core/time_stretch.h b/src/audio_core/time_stretch.h new file mode 100644 index 000000000..1fde3f72a --- /dev/null +++ b/src/audio_core/time_stretch.h @@ -0,0 +1,57 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstddef> +#include <memory> +#include <vector> + +#include "common/common_types.h" + +namespace AudioCore { + +class TimeStretcher final { +public: + TimeStretcher(); + ~TimeStretcher(); + + /** + * Set sample rate for the samples that Process returns. + * @param sample_rate The sample rate. + */ + void SetOutputSampleRate(unsigned int sample_rate); + + /** + * Add samples to be processed. + * @param sample_buffer Buffer of samples in interleaved stereo PCM16 format. + * @param num_sample Number of samples. + */ + void AddSamples(const s16* sample_buffer, size_t num_samples); + + /// Flush audio remaining in internal buffers. + void Flush(); + + /// Resets internal state and clears buffers. + void Reset(); + + /** + * Does audio stretching and produces the time-stretched samples. + * 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. + * @return Samples to play in interleaved stereo PCM16 format. + */ + std::vector<s16> Process(size_t sample_delay); + +private: + struct Impl; + std::unique_ptr<Impl> impl; + + /// INTERNAL: ratio = wallclock time / emulated time + double CalculateCurrentRatio(); + /// 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; + /// INTERNAL: Gets the time-stretched samples from SoundTouch. + std::vector<s16> GetSamples(); +}; + +} // namespace AudioCore diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index b4501eb2e..e01216734 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -114,7 +114,13 @@ int main(int argc, char **argv) { System::Init(emu_window.get()); SCOPE_EXIT({ System::Shutdown(); }); - Loader::ResultStatus load_result = Loader::LoadFile(boot_filename); + std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(boot_filename); + if (!loader) { + LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", boot_filename.c_str()); + return -1; + } + + Loader::ResultStatus load_result = loader->Load(); if (Loader::ResultStatus::Success != load_result) { LOG_CRITICAL(Frontend, "Failed to load ROM (Error %i)!", load_result); return -1; diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 0d17c80bf..c64de8e22 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -77,15 +77,16 @@ void Config::ReadValues() { // Data Storage Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); - // System Region - Settings::values.region_value = sdl2_config->GetInteger("System Region", "region_value", 1); + // System + Settings::values.is_new_3ds = sdl2_config->GetBoolean("System", "is_new_3ds", false); + Settings::values.region_value = sdl2_config->GetInteger("System", "region_value", 1); // Miscellaneous Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Info"); // Debugging Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); - Settings::values.gdbstub_port = sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689); + Settings::values.gdbstub_port = static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); } void Config::Reload() { 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..0a5d4624b 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -20,6 +20,7 @@ set(SRCS util/spinbox.cpp util/util.cpp bootmanager.cpp + configure_audio.cpp configure_debug.cpp configure_dialog.cpp configure_general.cpp @@ -51,10 +52,12 @@ set(HEADERS util/spinbox.h util/util.h bootmanager.h + configure_audio.h configure_debug.h configure_dialog.h configure_general.h game_list.h + game_list_p.h hotkeys.h main.h ui_settings.h @@ -68,6 +71,7 @@ set(UIS debugger/profiler.ui debugger/registers.ui configure.ui + configure_audio.ui configure_debug.ui configure_general.ui hotkeys.ui diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index b5bb75537..6e4ba3907 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -60,7 +60,8 @@ void Config::ReadValues() { Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool(); qt_config->endGroup(); - qt_config->beginGroup("System Region"); + qt_config->beginGroup("System"); + Settings::values.is_new_3ds = qt_config->value("is_new_3ds", false).toBool(); Settings::values.region_value = qt_config->value("region_value", 1).toInt(); qt_config->endGroup(); @@ -150,7 +151,8 @@ void Config::SaveValues() { qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd); qt_config->endGroup(); - qt_config->beginGroup("System Region"); + qt_config->beginGroup("System"); + qt_config->setValue("is_new_3ds", Settings::values.is_new_3ds); qt_config->setValue("region_value", Settings::values.region_value); qt_config->endGroup(); diff --git a/src/citra_qt/configure.ui b/src/citra_qt/configure.ui index 6ae056ff9..e1624bbef 100644 --- a/src/citra_qt/configure.ui +++ b/src/citra_qt/configure.ui @@ -29,6 +29,11 @@ <string>Input</string> </attribute> </widget> + <widget class="ConfigureAudio" name="audioTab"> + <attribute name="title"> + <string>Audio</string> + </attribute> + </widget> <widget class="ConfigureDebug" name="debugTab"> <attribute name="title"> <string>Debug</string> @@ -53,6 +58,12 @@ <container>1</container> </customwidget> <customwidget> + <class>ConfigureAudio</class> + <extends>QWidget</extends> + <header>configure_audio.h</header> + <container>1</container> + </customwidget> + <customwidget> <class>ConfigureDebug</class> <extends>QWidget</extends> <header>configure_debug.h</header> diff --git a/src/citra_qt/configure_audio.cpp b/src/citra_qt/configure_audio.cpp new file mode 100644 index 000000000..cedfa2f2a --- /dev/null +++ b/src/citra_qt/configure_audio.cpp @@ -0,0 +1,44 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/sink_details.h" + +#include "citra_qt/configure_audio.h" +#include "ui_configure_audio.h" + +#include "core/settings.h" + +ConfigureAudio::ConfigureAudio(QWidget* parent) : + QWidget(parent), + ui(std::make_unique<Ui::ConfigureAudio>()) +{ + ui->setupUi(this); + + ui->output_sink_combo_box->clear(); + ui->output_sink_combo_box->addItem("auto"); + for (const auto& sink_detail : AudioCore::g_sink_details) { + ui->output_sink_combo_box->addItem(sink_detail.id); + } + + this->setConfiguration(); +} + +ConfigureAudio::~ConfigureAudio() { +} + +void ConfigureAudio::setConfiguration() { + int new_sink_index = 0; + for (int index = 0; index < ui->output_sink_combo_box->count(); index++) { + if (ui->output_sink_combo_box->itemText(index).toStdString() == Settings::values.sink_id) { + new_sink_index = index; + break; + } + } + ui->output_sink_combo_box->setCurrentIndex(new_sink_index); +} + +void ConfigureAudio::applyConfiguration() { + Settings::values.sink_id = ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()).toStdString(); + Settings::Apply(); +} diff --git a/src/citra_qt/configure_audio.h b/src/citra_qt/configure_audio.h new file mode 100644 index 000000000..51df2e27b --- /dev/null +++ b/src/citra_qt/configure_audio.h @@ -0,0 +1,27 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QWidget> + +namespace Ui { +class ConfigureAudio; +} + +class ConfigureAudio : public QWidget { + Q_OBJECT + +public: + explicit ConfigureAudio(QWidget* parent = nullptr); + ~ConfigureAudio(); + + void applyConfiguration(); + +private: + void setConfiguration(); + + std::unique_ptr<Ui::ConfigureAudio> ui; +}; diff --git a/src/citra_qt/configure_audio.ui b/src/citra_qt/configure_audio.ui new file mode 100644 index 000000000..d7f6946ca --- /dev/null +++ b/src/citra_qt/configure_audio.ui @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> + +<ui version="4.0"> + <class>ConfigureAudio</class> + <widget class="QWidget" name="ConfigureAudio"> + <layout class="QVBoxLayout"> + <item> + <widget class="QGroupBox"> + <property name="title"> + <string>Audio</string> + </property> + <layout class="QVBoxLayout"> + <item> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel"> + <property name="text"> + <string>Output Engine:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="output_sink_combo_box"> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources /> + <connections /> +</ui> diff --git a/src/citra_qt/configure_dialog.cpp b/src/citra_qt/configure_dialog.cpp index 87c26c715..2f0317fe0 100644 --- a/src/citra_qt/configure_dialog.cpp +++ b/src/citra_qt/configure_dialog.cpp @@ -25,5 +25,6 @@ void ConfigureDialog::setConfiguration() { void ConfigureDialog::applyConfiguration() { ui->generalTab->applyConfiguration(); + ui->audioTab->applyConfiguration(); ui->debugTab->applyConfiguration(); } 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_tracing.cpp b/src/citra_qt/debugger/graphics_tracing.cpp index 1402f8e79..9c80f7ec9 100644 --- a/src/citra_qt/debugger/graphics_tracing.cpp +++ b/src/citra_qt/debugger/graphics_tracing.cpp @@ -74,7 +74,7 @@ void GraphicsTracingWidget::StartRecording() { std::array<u32, 4 * 16> default_attributes; for (unsigned i = 0; i < 16; ++i) { 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()); } } diff --git a/src/citra_qt/debugger/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics_vertex_shader.cpp index d648d4640..391666d35 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 @@ -501,7 +501,7 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d info.labels.insert({ entry_point, "main" }); // Generate debug information - debug_data = Pica::Shader::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 for (int attr = 0; attr < num_attributes; ++attr) { @@ -515,7 +515,7 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d } // Initialize debug info text for current cycle count - cycle_index->setMaximum(debug_data.records.size() - 1); + cycle_index->setMaximum(static_cast<int>(debug_data.records.size() - 1)); OnCycleIndexChanged(cycle_index->value()); model->endResetModel(); diff --git a/src/citra_qt/debugger/profiler.cpp b/src/citra_qt/debugger/profiler.cpp index 7bb010f77..585ac049a 100644 --- a/src/citra_qt/debugger/profiler.cpp +++ b/src/citra_qt/debugger/profiler.cpp @@ -151,6 +151,8 @@ private: /// This timer is used to redraw the widget's contents continuously. To save resources, it only /// runs while the widget is visible. QTimer update_timer; + /// Scale the coordinate system appropriately when physical DPI != logical DPI. + qreal x_scale, y_scale; }; #endif @@ -220,11 +222,17 @@ MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) { MicroProfileInitUI(); connect(&update_timer, SIGNAL(timeout()), SLOT(update())); + + QPainter painter(this); + x_scale = qreal(painter.device()->physicalDpiX()) / qreal(painter.device()->logicalDpiX()); + y_scale = qreal(painter.device()->physicalDpiY()) / qreal(painter.device()->logicalDpiY()); } void MicroProfileWidget::paintEvent(QPaintEvent* ev) { QPainter painter(this); + painter.scale(x_scale, y_scale); + painter.setBackground(Qt::black); painter.eraseRect(rect()); @@ -248,24 +256,24 @@ void MicroProfileWidget::hideEvent(QHideEvent* ev) { } void MicroProfileWidget::mouseMoveEvent(QMouseEvent* ev) { - MicroProfileMousePosition(ev->x(), ev->y(), 0); + MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); ev->accept(); } void MicroProfileWidget::mousePressEvent(QMouseEvent* ev) { - MicroProfileMousePosition(ev->x(), ev->y(), 0); + MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); ev->accept(); } void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* ev) { - MicroProfileMousePosition(ev->x(), ev->y(), 0); + MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); ev->accept(); } void MicroProfileWidget::wheelEvent(QWheelEvent* ev) { - MicroProfileMousePosition(ev->x(), ev->y(), ev->delta() / 120); + MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, ev->delta() / 120); ev->accept(); } diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index d14532102..570647539 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()); } @@ -128,24 +132,16 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool d if (deep_scan && FileUtil::IsDirectory(physical_name)) { AddFstEntriesToGameList(physical_name, true); } else { - std::string filename_filename, filename_extension; - Common::SplitPath(physical_name, nullptr, &filename_filename, &filename_extension); - - Loader::FileType guessed_filetype = Loader::GuessFromExtension(filename_extension); - if (guessed_filetype == Loader::FileType::Unknown) - return true; - Loader::FileType filetype = Loader::IdentifyFile(physical_name); - if (filetype == Loader::FileType::Unknown) { - LOG_WARNING(Frontend, "File %s is of indeterminate type and is possibly corrupted.", physical_name.c_str()); + std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); + if (!loader) return true; - } - if (guessed_filetype != filetype) { - LOG_WARNING(Frontend, "Filetype and extension of file %s do not match.", physical_name.c_str()); - } + + std::vector<u8> smdh; + loader->ReadIcon(smdh); emit EntryReady({ - new GameListItem(QString::fromStdString(Loader::GetFileTypeString(filetype))), - new GameListItemPath(QString::fromStdString(physical_name)), + new GameListItemPath(QString::fromStdString(physical_name), smdh), + new GameListItem(QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), 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..121f90b0c 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -6,13 +6,54 @@ #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/smdh.h" + +#include "video_core/utils.h" + +/** + * 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 GetQPixmapFromSMDH(const Loader::SMDH& smdh, bool large) { + std::vector<u16> icon_data = smdh.GetIcon(large); + const uchar* data = reinterpret_cast<const uchar*>(icon_data.data()); + int size = large ? 48 : 24; + QImage icon(data, size, size, QImage::Format::Format_RGB16); + return QPixmap::fromImage(icon); +} + +/** + * 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 GetQStringShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) { + return QString::fromUtf16(smdh.GetShortTitle(language).data()); +} class GameListItem : public QStandardItem { @@ -27,29 +68,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 (!Loader::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(GetQPixmapFromSMDH(smdh, true), Qt::DecorationRole); + + // Get title form SMDH + setData(GetQStringShortTitleFromSMDH(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..6239160bc 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) { @@ -261,7 +272,15 @@ bool GMainWindow::InitializeSystem() { } bool GMainWindow::LoadROM(const std::string& filename) { - Loader::ResultStatus result = Loader::LoadFile(filename); + std::unique_ptr<Loader::AppLoader> app_loader = Loader::GetLoader(filename); + if (!app_loader) { + LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", filename.c_str()); + QMessageBox::critical(this, tr("Error while loading ROM!"), + tr("The ROM format is not supported.")); + return false; + } + + Loader::ResultStatus result = app_loader->Load(); if (Loader::ResultStatus::Success != result) { LOG_CRITICAL(Frontend, "Failed to load ROM!"); System::Shutdown(); diff --git a/src/citra_qt/util/util.cpp b/src/citra_qt/util/util.cpp index 8734a8efd..2f9beb5cc 100644 --- a/src/citra_qt/util/util.cpp +++ b/src/citra_qt/util/util.cpp @@ -19,7 +19,7 @@ QString ReadableByteSize(qulonglong size) { static const std::array<const char*, 6> units = { "B", "KiB", "MiB", "GiB", "TiB", "PiB" }; if (size == 0) return "0"; - int digit_groups = std::min<int>((int)(std::log10(size) / std::log10(1024)), units.size()); + int digit_groups = std::min<int>(static_cast<int>(std::log10(size) / std::log10(1024)), static_cast<int>(units.size())); return QString("%L1 %2").arg(size / std::pow(1024, digit_groups), 0, 'f', 1) .arg(units[digit_groups]); } diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index ab3515683..4633897ce 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -72,18 +72,24 @@ inline u64 _rotr64(u64 x, unsigned int shift){ } #else // _MSC_VER - #if (_MSC_VER < 1900) - // Function Cross-Compatibility - #define snprintf _snprintf - #endif - - // Locale Cross-Compatibility - #define locale_t _locale_t - - extern "C" { - __declspec(dllimport) void __stdcall DebugBreak(void); - } - #define Crash() {DebugBreak();} + +#if (_MSC_VER < 1900) + // Function Cross-Compatibility + #define snprintf _snprintf +#endif + +// Locale Cross-Compatibility +#define locale_t _locale_t + +extern "C" { + __declspec(dllimport) void __stdcall DebugBreak(void); +} +#define Crash() {DebugBreak();} + +// cstdlib provides these on MSVC +#define rotr _rotr +#define rotl _rotl + #endif // _MSC_VER ndef // Generic function to get last error message. 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/common/swap.h b/src/common/swap.h index a7c37bc44..1749bd7a4 100644 --- a/src/common/swap.h +++ b/src/common/swap.h @@ -25,6 +25,8 @@ #include <sys/endian.h> #endif +#include <cstring> + #include "common/common_types.h" // GCC 4.6+ @@ -58,9 +60,6 @@ namespace Common { -inline u8 swap8(u8 _data) {return _data;} -inline u32 swap24(const u8* _data) {return (_data[0] << 16) | (_data[1] << 8) | _data[2];} - #ifdef _MSC_VER inline u16 swap16(u16 _data) {return _byteswap_ushort(_data);} inline u32 swap32(u32 _data) {return _byteswap_ulong (_data);} @@ -92,52 +91,29 @@ inline u64 swap64(u64 data) {return ((u64)swap32(data) << 32) | swap32(data >> 3 #endif inline float swapf(float f) { - union { - float f; - unsigned int u32; - } dat1, dat2; - - dat1.f = f; - dat2.u32 = swap32(dat1.u32); + static_assert(sizeof(u32) == sizeof(float), + "float must be the same size as uint32_t."); - return dat2.f; -} - -inline double swapd(double f) { - union { - double f; - unsigned long long u64; - } dat1, dat2; + u32 value; + std::memcpy(&value, &f, sizeof(u32)); - dat1.f = f; - dat2.u64 = swap64(dat1.u64); + value = swap32(value); + std::memcpy(&f, &value, sizeof(u32)); - return dat2.f; + return f; } -inline u16 swap16(const u8* _pData) {return swap16(*(const u16*)_pData);} -inline u32 swap32(const u8* _pData) {return swap32(*(const u32*)_pData);} -inline u64 swap64(const u8* _pData) {return swap64(*(const u64*)_pData);} - -template <int count> -void swap(u8*); +inline double swapd(double f) { + static_assert(sizeof(u64) == sizeof(double), + "double must be the same size as uint64_t."); -template <> -inline void swap<1>(u8* data) { } + u64 value; + std::memcpy(&value, &f, sizeof(u64)); -template <> -inline void swap<2>(u8* data) { - *reinterpret_cast<u16*>(data) = swap16(data); -} - -template <> -inline void swap<4>(u8* data) { - *reinterpret_cast<u32*>(data) = swap32(data); -} + value = swap64(value); + std::memcpy(&f, &value, sizeof(u64)); -template <> -inline void swap<8>(u8* data) { - *reinterpret_cast<u64*>(data) = swap64(data); + return f; } } // Namespace Common @@ -534,35 +510,35 @@ bool operator==(const S &p, const swap_struct_t<T, F> v) { template <typename T> struct swap_64_t { static T swap(T x) { - return (T)Common::swap64(*(u64 *)&x); + return static_cast<T>(Common::swap64(x)); } }; template <typename T> struct swap_32_t { static T swap(T x) { - return (T)Common::swap32(*(u32 *)&x); + return static_cast<T>(Common::swap32(x)); } }; template <typename T> struct swap_16_t { static T swap(T x) { - return (T)Common::swap16(*(u16 *)&x); + return static_cast<T>(Common::swap16(x)); } }; template <typename T> struct swap_float_t { static T swap(T x) { - return (T)Common::swapf(*(float *)&x); + return static_cast<T>(Common::swapf(x)); } }; template <typename T> struct swap_double_t { static T swap(T x) { - return (T)Common::swapd(*(double *)&x); + return static_cast<T>(Common::swapd(x)); } }; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a8d891689..ed80cf0e4 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -42,6 +42,7 @@ set(SRCS hle/kernel/timer.cpp hle/kernel/vm_manager.cpp hle/service/ac_u.cpp + hle/service/act_a.cpp hle/service/act_u.cpp hle/service/am/am.cpp hle/service/am/am_app.cpp @@ -52,6 +53,7 @@ set(SRCS hle/service/apt/apt_a.cpp hle/service/apt/apt_s.cpp hle/service/apt/apt_u.cpp + hle/service/apt/bcfnt/bcfnt.cpp hle/service/boss/boss.cpp hle/service/boss/boss_p.cpp hle/service/boss/boss_u.cpp @@ -119,6 +121,7 @@ set(SRCS loader/elf.cpp loader/loader.cpp loader/ncch.cpp + loader/smdh.cpp tracer/recorder.cpp memory.cpp settings.cpp @@ -175,6 +178,7 @@ set(HEADERS hle/kernel/vm_manager.h hle/result.h hle/service/ac_u.h + hle/service/act_a.h hle/service/act_u.h hle/service/am/am.h hle/service/am/am_app.h @@ -185,6 +189,7 @@ set(HEADERS hle/service/apt/apt_a.h hle/service/apt/apt_s.h hle/service/apt/apt_u.h + hle/service/apt/bcfnt/bcfnt.h hle/service/boss/boss.h hle/service/boss/boss_p.h hle/service/boss/boss_u.h @@ -252,6 +257,7 @@ set(HEADERS loader/elf.h loader/loader.h loader/ncch.h + loader/smdh.h tracer/recorder.h tracer/citrace.h memory.h diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index 533067d4f..d8abe5aeb 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -6,6 +6,7 @@ #include "common/common_types.h" #include "core/arm/skyeye_common/arm_regformat.h" +#include "core/arm/skyeye_common/vfp/asm_vfp.h" namespace Core { struct ThreadContext; diff --git a/src/core/arm/dyncom/arm_dyncom.cpp b/src/core/arm/dyncom/arm_dyncom.cpp index a3581132c..13492a08b 100644 --- a/src/core/arm/dyncom/arm_dyncom.cpp +++ b/src/core/arm/dyncom/arm_dyncom.cpp @@ -93,7 +93,7 @@ void ARM_DynCom::ResetContext(Core::ThreadContext& context, u32 stack_top, u32 e context.cpu_registers[0] = arg; context.pc = entry_point; context.sp = stack_top; - context.cpsr = 0x1F | ((entry_point & 1) << 5); // Usermode and THUMB mode + context.cpsr = USER32MODE | ((entry_point & 1) << 5); // Usermode and THUMB mode } void ARM_DynCom::SaveContext(Core::ThreadContext& ctx) { diff --git a/src/core/arm/dyncom/arm_dyncom_dec.cpp b/src/core/arm/dyncom/arm_dyncom_dec.cpp index 8cd6755cb..247d379e3 100644 --- a/src/core/arm/dyncom/arm_dyncom_dec.cpp +++ b/src/core/arm/dyncom/arm_dyncom_dec.cpp @@ -422,6 +422,10 @@ ARMDecodeStatus DecodeARMInstruction(u32 instr, s32* idx) { n = arm_instruction[i].attribute_value; base = 0; + // 3DS has no VFP3 support + if (arm_instruction[i].version == ARMVFP3) + continue; + while (n) { if (arm_instruction[i].content[base + 1] == 31 && arm_instruction[i].content[base] == 0) { // clrex diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index 8d4b26815..cfc67287f 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -5527,28 +5527,32 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { // SMUAD and SMLAD if (BIT(op2, 1) == 0) { - RD = (product1 + product2); + u32 rd_val = (product1 + product2); if (inst_cream->Ra != 15) { - RD += cpu->Reg[inst_cream->Ra]; + rd_val += cpu->Reg[inst_cream->Ra]; if (ARMul_AddOverflowQ(product1 + product2, cpu->Reg[inst_cream->Ra])) cpu->Cpsr |= (1 << 27); } + RD = rd_val; + if (ARMul_AddOverflowQ(product1, product2)) cpu->Cpsr |= (1 << 27); } // SMUSD and SMLSD else { - RD = (product1 - product2); + u32 rd_val = (product1 - product2); if (inst_cream->Ra != 15) { - RD += cpu->Reg[inst_cream->Ra]; + rd_val += cpu->Reg[inst_cream->Ra]; if (ARMul_AddOverflowQ(product1 - product2, cpu->Reg[inst_cream->Ra])) cpu->Cpsr |= (1 << 27); } + + RD = rd_val; } } diff --git a/src/core/arm/skyeye_common/vfp/vfp_helper.h b/src/core/arm/skyeye_common/vfp/vfp_helper.h index 210972917..68714800c 100644 --- a/src/core/arm/skyeye_common/vfp/vfp_helper.h +++ b/src/core/arm/skyeye_common/vfp/vfp_helper.h @@ -271,8 +271,9 @@ inline int vfp_single_type(const vfp_single* s) // Unpack a single-precision float. Note that this returns the magnitude // of the single-precision float mantissa with the 1. if necessary, // aligned to bit 30. -inline void vfp_single_unpack(vfp_single* s, s32 val, u32* fpscr) +inline u32 vfp_single_unpack(vfp_single* s, s32 val, u32 fpscr) { + u32 exceptions = 0; s->sign = vfp_single_packed_sign(val) >> 16, s->exponent = vfp_single_packed_exponent(val); @@ -283,12 +284,13 @@ inline void vfp_single_unpack(vfp_single* s, s32 val, u32* fpscr) // If flush-to-zero mode is enabled, turn the denormal into zero. // On a VFPv2 architecture, the sign of the zero is always positive. - if ((*fpscr & FPSCR_FLUSH_TO_ZERO) != 0 && (vfp_single_type(s) & VFP_DENORMAL) != 0) { + if ((fpscr & FPSCR_FLUSH_TO_ZERO) != 0 && (vfp_single_type(s) & VFP_DENORMAL) != 0) { s->sign = 0; s->exponent = 0; s->significand = 0; - *fpscr |= FPSCR_IDC; + exceptions |= FPSCR_IDC; } + return exceptions; } // Re-pack a single-precision float. This assumes that the float is @@ -302,7 +304,7 @@ inline s32 vfp_single_pack(const vfp_single* s) } -u32 vfp_single_normaliseround(ARMul_State* state, int sd, vfp_single* vs, u32 fpscr, u32 exceptions, const char* func); +u32 vfp_single_normaliseround(ARMul_State* state, int sd, vfp_single* vs, u32 fpscr, const char* func); // Double-precision struct vfp_double { @@ -357,8 +359,9 @@ inline int vfp_double_type(const vfp_double* s) // Unpack a double-precision float. Note that this returns the magnitude // of the double-precision float mantissa with the 1. if necessary, // aligned to bit 62. -inline void vfp_double_unpack(vfp_double* s, s64 val, u32* fpscr) +inline u32 vfp_double_unpack(vfp_double* s, s64 val, u32 fpscr) { + u32 exceptions = 0; s->sign = vfp_double_packed_sign(val) >> 48; s->exponent = vfp_double_packed_exponent(val); @@ -369,12 +372,13 @@ inline void vfp_double_unpack(vfp_double* s, s64 val, u32* fpscr) // If flush-to-zero mode is enabled, turn the denormal into zero. // On a VFPv2 architecture, the sign of the zero is always positive. - if ((*fpscr & FPSCR_FLUSH_TO_ZERO) != 0 && (vfp_double_type(s) & VFP_DENORMAL) != 0) { + if ((fpscr & FPSCR_FLUSH_TO_ZERO) != 0 && (vfp_double_type(s) & VFP_DENORMAL) != 0) { s->sign = 0; s->exponent = 0; s->significand = 0; - *fpscr |= FPSCR_IDC; + exceptions |= FPSCR_IDC; } + return exceptions; } // Re-pack a double-precision float. This assumes that the float is @@ -447,4 +451,4 @@ inline u32 fls(u32 x) u32 vfp_double_multiply(vfp_double* vdd, vfp_double* vdn, vfp_double* vdm, u32 fpscr); u32 vfp_double_add(vfp_double* vdd, vfp_double* vdn, vfp_double *vdm, u32 fpscr); -u32 vfp_double_normaliseround(ARMul_State* state, int dd, vfp_double* vd, u32 fpscr, u32 exceptions, const char* func); +u32 vfp_double_normaliseround(ARMul_State* state, int dd, vfp_double* vd, u32 fpscr, const char* func); diff --git a/src/core/arm/skyeye_common/vfp/vfpdouble.cpp b/src/core/arm/skyeye_common/vfp/vfpdouble.cpp index 45914d479..1d5641810 100644 --- a/src/core/arm/skyeye_common/vfp/vfpdouble.cpp +++ b/src/core/arm/skyeye_common/vfp/vfpdouble.cpp @@ -85,11 +85,12 @@ static void vfp_double_normalise_denormal(struct vfp_double *vd) vfp_double_dump("normalise_denormal: out", vd); } -u32 vfp_double_normaliseround(ARMul_State* state, int dd, struct vfp_double *vd, u32 fpscr, u32 exceptions, const char *func) +u32 vfp_double_normaliseround(ARMul_State* state, int dd, struct vfp_double *vd, u32 fpscr, const char *func) { u64 significand, incr; int exponent, shift, underflow; u32 rmode; + u32 exceptions = 0; vfp_double_dump("pack: in", vd); @@ -291,8 +292,9 @@ static u32 vfp_double_fsqrt(ARMul_State* state, int dd, int unused, int dm, u32 LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); vfp_double vdm, vdd, *vdp; int ret, tm; + u32 exceptions = 0; - vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); + exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr); tm = vfp_double_type(&vdm); if (tm & (VFP_NAN|VFP_INFINITY)) { @@ -369,7 +371,8 @@ sqrt_invalid: } vdd.significand = vfp_shiftright64jamming(vdd.significand, 1); - return vfp_double_normaliseround(state, dd, &vdd, fpscr, 0, "fsqrt"); + exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fsqrt"); + return exceptions; } /* @@ -475,7 +478,7 @@ static u32 vfp_double_fcvts(ARMul_State* state, int sd, int unused, int dm, u32 u32 exceptions = 0; LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); - vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); + exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr); tm = vfp_double_type(&vdm); @@ -504,7 +507,8 @@ static u32 vfp_double_fcvts(ARMul_State* state, int sd, int unused, int dm, u32 else vsd.exponent = vdm.exponent - (1023 - 127); - return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fcvts"); + exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fcvts"); + return exceptions; pack_nan: vfp_put_float(state, vfp_single_pack(&vsd), sd); @@ -514,6 +518,7 @@ pack_nan: static u32 vfp_double_fuito(ARMul_State* state, int dd, int unused, int dm, u32 fpscr) { struct vfp_double vdm; + u32 exceptions = 0; u32 m = vfp_get_float(state, dm); LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); @@ -521,12 +526,14 @@ static u32 vfp_double_fuito(ARMul_State* state, int dd, int unused, int dm, u32 vdm.exponent = 1023 + 63 - 1; vdm.significand = (u64)m; - return vfp_double_normaliseround(state, dd, &vdm, fpscr, 0, "fuito"); + exceptions |= vfp_double_normaliseround(state, dd, &vdm, fpscr, "fuito"); + return exceptions; } static u32 vfp_double_fsito(ARMul_State* state, int dd, int unused, int dm, u32 fpscr) { struct vfp_double vdm; + u32 exceptions = 0; u32 m = vfp_get_float(state, dm); LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); @@ -534,7 +541,8 @@ static u32 vfp_double_fsito(ARMul_State* state, int dd, int unused, int dm, u32 vdm.exponent = 1023 + 63 - 1; vdm.significand = vdm.sign ? (~m + 1) : m; - return vfp_double_normaliseround(state, dd, &vdm, fpscr, 0, "fsito"); + exceptions |= vfp_double_normaliseround(state, dd, &vdm, fpscr, "fsito"); + return exceptions; } static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32 fpscr) @@ -545,7 +553,7 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32 int tm; LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); - vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); + exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr); /* * Do we have a denormalised number? @@ -560,7 +568,7 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32 if (vdm.exponent >= 1023 + 32) { d = vdm.sign ? 0 : 0xffffffff; exceptions = FPSCR_IOC; - } else if (vdm.exponent >= 1023 - 1) { + } else if (vdm.exponent >= 1023) { int shift = 1023 + 63 - vdm.exponent; u64 rem, incr = 0; @@ -595,12 +603,20 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32 } else { d = 0; if (vdm.exponent | vdm.significand) { - exceptions |= FPSCR_IXC; - if (rmode == FPSCR_ROUND_PLUSINF && vdm.sign == 0) + if (rmode == FPSCR_ROUND_NEAREST) { + if (vdm.exponent >= 1022) { + d = vdm.sign ? 0 : 1; + exceptions |= vdm.sign ? FPSCR_IOC : FPSCR_IXC; + } else { + exceptions |= FPSCR_IXC; + } + } else if (rmode == FPSCR_ROUND_PLUSINF && vdm.sign == 0) { d = 1; - else if (rmode == FPSCR_ROUND_MINUSINF && vdm.sign) { - d = 0; - exceptions |= FPSCR_IOC; + exceptions |= FPSCR_IXC; + } else if (rmode == FPSCR_ROUND_MINUSINF) { + exceptions |= vdm.sign ? FPSCR_IOC : FPSCR_IXC; + } else { + exceptions |= FPSCR_IXC; } } } @@ -615,7 +631,7 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32 static u32 vfp_double_ftouiz(ARMul_State* state, int sd, int unused, int dm, u32 fpscr) { LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); - return vfp_double_ftoui(state, sd, unused, dm, FPSCR_ROUND_TOZERO); + return vfp_double_ftoui(state, sd, unused, dm, (fpscr & ~FPSCR_RMODE_MASK) | FPSCR_ROUND_TOZERO); } static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32 fpscr) @@ -626,7 +642,7 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32 int tm; LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); - vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); + exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr); vfp_double_dump("VDM", &vdm); /* @@ -639,12 +655,12 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32 if (tm & VFP_NAN) { d = 0; exceptions |= FPSCR_IOC; - } else if (vdm.exponent >= 1023 + 32) { + } else if (vdm.exponent >= 1023 + 31) { d = 0x7fffffff; if (vdm.sign) d = ~d; exceptions |= FPSCR_IOC; - } else if (vdm.exponent >= 1023 - 1) { + } else if (vdm.exponent >= 1023) { int shift = 1023 + 63 - vdm.exponent; /* 58 */ u64 rem, incr = 0; @@ -675,10 +691,17 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32 d = 0; if (vdm.exponent | vdm.significand) { exceptions |= FPSCR_IXC; - if (rmode == FPSCR_ROUND_PLUSINF && vdm.sign == 0) + if (rmode == FPSCR_ROUND_NEAREST) { + if (vdm.exponent >= 1022) { + d = vdm.sign ? 0xffffffff : 1; + } else { + d = 0; + } + } else if (rmode == FPSCR_ROUND_PLUSINF && vdm.sign == 0) { d = 1; - else if (rmode == FPSCR_ROUND_MINUSINF && vdm.sign) - d = -1; + } else if (rmode == FPSCR_ROUND_MINUSINF && vdm.sign) { + d = 0xffffffff; + } } } @@ -692,7 +715,7 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32 static u32 vfp_double_ftosiz(ARMul_State* state, int dd, int unused, int dm, u32 fpscr) { LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); - return vfp_double_ftosi(state, dd, unused, dm, FPSCR_ROUND_TOZERO); + return vfp_double_ftosi(state, dd, unused, dm, (fpscr & ~FPSCR_RMODE_MASK) | FPSCR_ROUND_TOZERO); } static struct op fops_ext[] = { @@ -892,21 +915,21 @@ static u32 vfp_double_multiply_accumulate(ARMul_State* state, int dd, int dn, int dm, u32 fpscr, u32 negate, const char *func) { struct vfp_double vdd, vdp, vdn, vdm; - u32 exceptions; + u32 exceptions = 0; - vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr); + exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr); if (vdn.exponent == 0 && vdn.significand) vfp_double_normalise_denormal(&vdn); - vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); + exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr); if (vdm.exponent == 0 && vdm.significand) vfp_double_normalise_denormal(&vdm); - exceptions = vfp_double_multiply(&vdp, &vdn, &vdm, fpscr); + exceptions |= vfp_double_multiply(&vdp, &vdn, &vdm, fpscr); if (negate & NEG_MULTIPLY) vdp.sign = vfp_sign_negate(vdp.sign); - vfp_double_unpack(&vdn, vfp_get_double(state, dd), &fpscr); + exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dd), fpscr); if (vdn.exponent == 0 && vdn.significand != 0) vfp_double_normalise_denormal(&vdn); @@ -915,7 +938,8 @@ vfp_double_multiply_accumulate(ARMul_State* state, int dd, int dn, int dm, u32 f exceptions |= vfp_double_add(&vdd, &vdn, &vdp, fpscr); - return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, func); + exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, func); + return exceptions; } /* @@ -964,19 +988,21 @@ static u32 vfp_double_fnmsc(ARMul_State* state, int dd, int dn, int dm, u32 fpsc static u32 vfp_double_fmul(ARMul_State* state, int dd, int dn, int dm, u32 fpscr) { struct vfp_double vdd, vdn, vdm; - u32 exceptions; + u32 exceptions = 0; LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); - vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr); + exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr); if (vdn.exponent == 0 && vdn.significand) vfp_double_normalise_denormal(&vdn); - vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); + exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr); if (vdm.exponent == 0 && vdm.significand) vfp_double_normalise_denormal(&vdm); - exceptions = vfp_double_multiply(&vdd, &vdn, &vdm, fpscr); - return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fmul"); + exceptions |= vfp_double_multiply(&vdd, &vdn, &vdm, fpscr); + + exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fmul"); + return exceptions; } /* @@ -985,21 +1011,22 @@ static u32 vfp_double_fmul(ARMul_State* state, int dd, int dn, int dm, u32 fpscr static u32 vfp_double_fnmul(ARMul_State* state, int dd, int dn, int dm, u32 fpscr) { struct vfp_double vdd, vdn, vdm; - u32 exceptions; + u32 exceptions = 0; LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); - vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr); + exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr); if (vdn.exponent == 0 && vdn.significand) vfp_double_normalise_denormal(&vdn); - vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); + exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr); if (vdm.exponent == 0 && vdm.significand) vfp_double_normalise_denormal(&vdm); - exceptions = vfp_double_multiply(&vdd, &vdn, &vdm, fpscr); + exceptions |= vfp_double_multiply(&vdd, &vdn, &vdm, fpscr); vdd.sign = vfp_sign_negate(vdd.sign); - return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fnmul"); + exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fnmul"); + return exceptions; } /* @@ -1008,20 +1035,21 @@ static u32 vfp_double_fnmul(ARMul_State* state, int dd, int dn, int dm, u32 fpsc static u32 vfp_double_fadd(ARMul_State* state, int dd, int dn, int dm, u32 fpscr) { struct vfp_double vdd, vdn, vdm; - u32 exceptions; + u32 exceptions = 0; LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); - vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr); + exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr); if (vdn.exponent == 0 && vdn.significand) vfp_double_normalise_denormal(&vdn); - vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); + exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr); if (vdm.exponent == 0 && vdm.significand) vfp_double_normalise_denormal(&vdm); - exceptions = vfp_double_add(&vdd, &vdn, &vdm, fpscr); + exceptions |= vfp_double_add(&vdd, &vdn, &vdm, fpscr); - return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fadd"); + exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fadd"); + return exceptions; } /* @@ -1030,14 +1058,14 @@ static u32 vfp_double_fadd(ARMul_State* state, int dd, int dn, int dm, u32 fpscr static u32 vfp_double_fsub(ARMul_State* state, int dd, int dn, int dm, u32 fpscr) { struct vfp_double vdd, vdn, vdm; - u32 exceptions; + u32 exceptions = 0; LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); - vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr); + exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr); if (vdn.exponent == 0 && vdn.significand) vfp_double_normalise_denormal(&vdn); - vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); + exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr); if (vdm.exponent == 0 && vdm.significand) vfp_double_normalise_denormal(&vdm); @@ -1046,9 +1074,10 @@ static u32 vfp_double_fsub(ARMul_State* state, int dd, int dn, int dm, u32 fpscr */ vdm.sign = vfp_sign_negate(vdm.sign); - exceptions = vfp_double_add(&vdd, &vdn, &vdm, fpscr); + exceptions |= vfp_double_add(&vdd, &vdn, &vdm, fpscr); - return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fsub"); + exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fsub"); + return exceptions; } /* @@ -1061,8 +1090,8 @@ static u32 vfp_double_fdiv(ARMul_State* state, int dd, int dn, int dm, u32 fpscr int tm, tn; LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); - vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr); - vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); + exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr); + exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr); vdd.sign = vdn.sign ^ vdm.sign; @@ -1131,16 +1160,18 @@ static u32 vfp_double_fdiv(ARMul_State* state, int dd, int dn, int dm, u32 fpscr } vdd.significand |= (reml != 0); } - return vfp_double_normaliseround(state, dd, &vdd, fpscr, 0, "fdiv"); + + exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fdiv"); + return exceptions; vdn_nan: - exceptions = vfp_propagate_nan(&vdd, &vdn, &vdm, fpscr); + exceptions |= vfp_propagate_nan(&vdd, &vdn, &vdm, fpscr); pack: vfp_put_double(state, vfp_double_pack(&vdd), dd); return exceptions; vdm_nan: - exceptions = vfp_propagate_nan(&vdd, &vdm, &vdn, fpscr); + exceptions |= vfp_propagate_nan(&vdd, &vdm, &vdn, fpscr); goto pack; zero: @@ -1149,7 +1180,7 @@ zero: goto pack; divzero: - exceptions = FPSCR_DZC; + exceptions |= FPSCR_DZC; infinity: vdd.exponent = 2047; vdd.significand = 0; @@ -1157,7 +1188,8 @@ infinity: invalid: vfp_put_double(state, vfp_double_pack(&vfp_double_default_qnan), dd); - return FPSCR_IOC; + exceptions |= FPSCR_IOC; + return exceptions; } static struct op fops[] = { diff --git a/src/core/arm/skyeye_common/vfp/vfpsingle.cpp b/src/core/arm/skyeye_common/vfp/vfpsingle.cpp index e47ad2760..60264f9b3 100644 --- a/src/core/arm/skyeye_common/vfp/vfpsingle.cpp +++ b/src/core/arm/skyeye_common/vfp/vfpsingle.cpp @@ -89,10 +89,11 @@ static void vfp_single_normalise_denormal(struct vfp_single *vs) } -u32 vfp_single_normaliseround(ARMul_State* state, int sd, struct vfp_single *vs, u32 fpscr, u32 exceptions, const char *func) +u32 vfp_single_normaliseround(ARMul_State* state, int sd, struct vfp_single *vs, u32 fpscr, const char *func) { u32 significand, incr, rmode; int exponent, shift, underflow; + u32 exceptions = 0; vfp_single_dump("pack: in", vs); @@ -334,8 +335,9 @@ static u32 vfp_single_fsqrt(ARMul_State* state, int sd, int unused, s32 m, u32 f { struct vfp_single vsm, vsd, *vsp; int ret, tm; + u32 exceptions = 0; - vfp_single_unpack(&vsm, m, &fpscr); + exceptions |= vfp_single_unpack(&vsm, m, fpscr); tm = vfp_single_type(&vsm); if (tm & (VFP_NAN|VFP_INFINITY)) { vsp = &vsd; @@ -408,7 +410,8 @@ sqrt_invalid: } vsd.significand = vfp_shiftright32jamming(vsd.significand, 1); - return vfp_single_normaliseround(state, sd, &vsd, fpscr, 0, "fsqrt"); + exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fsqrt"); + return exceptions; } /* @@ -503,7 +506,7 @@ static u32 vfp_single_fcvtd(ARMul_State* state, int dd, int unused, s32 m, u32 f int tm; u32 exceptions = 0; - vfp_single_unpack(&vsm, m, &fpscr); + exceptions |= vfp_single_unpack(&vsm, m, fpscr); tm = vfp_single_type(&vsm); @@ -511,7 +514,7 @@ static u32 vfp_single_fcvtd(ARMul_State* state, int dd, int unused, s32 m, u32 f * If we have a signalling NaN, signal invalid operation. */ if (tm == VFP_SNAN) - exceptions = FPSCR_IOC; + exceptions |= FPSCR_IOC; if (tm & VFP_DENORMAL) vfp_single_normalise_denormal(&vsm); @@ -532,7 +535,8 @@ static u32 vfp_single_fcvtd(ARMul_State* state, int dd, int unused, s32 m, u32 f else vdd.exponent = vsm.exponent + (1023 - 127); - return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fcvtd"); + exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fcvtd"); + return exceptions; pack_nan: vfp_put_double(state, vfp_double_pack(&vdd), dd); @@ -542,23 +546,27 @@ pack_nan: static u32 vfp_single_fuito(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr) { struct vfp_single vs; + u32 exceptions = 0; vs.sign = 0; vs.exponent = 127 + 31 - 1; vs.significand = (u32)m; - return vfp_single_normaliseround(state, sd, &vs, fpscr, 0, "fuito"); + exceptions |= vfp_single_normaliseround(state, sd, &vs, fpscr, "fuito"); + return exceptions; } static u32 vfp_single_fsito(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr) { struct vfp_single vs; + u32 exceptions = 0; vs.sign = (m & 0x80000000) >> 16; vs.exponent = 127 + 31 - 1; vs.significand = vs.sign ? -m : m; - return vfp_single_normaliseround(state, sd, &vs, fpscr, 0, "fsito"); + exceptions |= vfp_single_normaliseround(state, sd, &vs, fpscr, "fsito"); + return exceptions; } static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr) @@ -568,7 +576,7 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f int rmode = fpscr & FPSCR_RMODE_MASK; int tm; - vfp_single_unpack(&vsm, m, &fpscr); + exceptions |= vfp_single_unpack(&vsm, m, fpscr); vfp_single_dump("VSM", &vsm); /* @@ -583,7 +591,7 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f if (vsm.exponent >= 127 + 32) { d = vsm.sign ? 0 : 0xffffffff; - exceptions = FPSCR_IOC; + exceptions |= FPSCR_IOC; } else if (vsm.exponent >= 127) { int shift = 127 + 31 - vsm.exponent; u32 rem, incr = 0; @@ -592,7 +600,11 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f * 2^0 <= m < 2^32-2^8 */ d = (vsm.significand << 1) >> shift; - rem = vsm.significand << (33 - shift); + if (shift > 0) { + rem = (vsm.significand << 1) << (32 - shift); + } else { + rem = 0; + } if (rmode == FPSCR_ROUND_NEAREST) { incr = 0x80000000; @@ -619,12 +631,20 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f } else { d = 0; if (vsm.exponent | vsm.significand) { - exceptions |= FPSCR_IXC; - if (rmode == FPSCR_ROUND_PLUSINF && vsm.sign == 0) + if (rmode == FPSCR_ROUND_NEAREST) { + if (vsm.exponent >= 126) { + d = vsm.sign ? 0 : 1; + exceptions |= vsm.sign ? FPSCR_IOC : FPSCR_IXC; + } else { + exceptions |= FPSCR_IXC; + } + } else if (rmode == FPSCR_ROUND_PLUSINF && vsm.sign == 0) { d = 1; - else if (rmode == FPSCR_ROUND_MINUSINF && vsm.sign) { - d = 0; - exceptions |= FPSCR_IOC; + exceptions |= FPSCR_IXC; + } else if (rmode == FPSCR_ROUND_MINUSINF) { + exceptions |= vsm.sign ? FPSCR_IOC : FPSCR_IXC; + } else { + exceptions |= FPSCR_IXC; } } } @@ -638,7 +658,7 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f static u32 vfp_single_ftouiz(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr) { - return vfp_single_ftoui(state, sd, unused, m, FPSCR_ROUND_TOZERO); + return vfp_single_ftoui(state, sd, unused, m, (fpscr & ~FPSCR_RMODE_MASK) | FPSCR_ROUND_TOZERO); } static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr) @@ -648,7 +668,7 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f int rmode = fpscr & FPSCR_RMODE_MASK; int tm; - vfp_single_unpack(&vsm, m, &fpscr); + exceptions |= vfp_single_unpack(&vsm, m, fpscr); vfp_single_dump("VSM", &vsm); /* @@ -661,7 +681,7 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f if (tm & VFP_NAN) { d = 0; exceptions |= FPSCR_IOC; - } else if (vsm.exponent >= 127 + 32) { + } else if (vsm.exponent >= 127 + 31) { /* * m >= 2^31-2^7: invalid */ @@ -675,7 +695,7 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f /* 2^0 <= m <= 2^31-2^7 */ d = (vsm.significand << 1) >> shift; - rem = vsm.significand << (33 - shift); + rem = (vsm.significand << 1) << (32 - shift); if (rmode == FPSCR_ROUND_NEAREST) { incr = 0x80000000; @@ -701,10 +721,14 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f d = 0; if (vsm.exponent | vsm.significand) { exceptions |= FPSCR_IXC; - if (rmode == FPSCR_ROUND_PLUSINF && vsm.sign == 0) + if (rmode == FPSCR_ROUND_NEAREST) { + if (vsm.exponent >= 126) + d = vsm.sign ? 0xffffffff : 1; + } else if (rmode == FPSCR_ROUND_PLUSINF && vsm.sign == 0) { d = 1; - else if (rmode == FPSCR_ROUND_MINUSINF && vsm.sign) - d = -1; + } else if (rmode == FPSCR_ROUND_MINUSINF && vsm.sign) { + d = 0xffffffff; + } } } @@ -717,7 +741,7 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f static u32 vfp_single_ftosiz(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr) { - return vfp_single_ftosi(state, sd, unused, m, FPSCR_ROUND_TOZERO); + return vfp_single_ftosi(state, sd, unused, m, (fpscr & ~FPSCR_RMODE_MASK) | FPSCR_ROUND_TOZERO); } static struct op fops_ext[] = { @@ -774,7 +798,7 @@ vfp_single_fadd_nonnumber(struct vfp_single *vsd, struct vfp_single *vsn, /* * different signs -> invalid */ - exceptions = FPSCR_IOC; + exceptions |= FPSCR_IOC; vsp = &vfp_single_default_qnan; } else { /* @@ -921,27 +945,27 @@ static u32 vfp_single_multiply_accumulate(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr, u32 negate, const char *func) { vfp_single vsd, vsp, vsn, vsm; - u32 exceptions; + u32 exceptions = 0; s32 v; v = vfp_get_float(state, sn); LOG_TRACE(Core_ARM11, "s%u = %08x", sn, v); - vfp_single_unpack(&vsn, v, &fpscr); + exceptions |= vfp_single_unpack(&vsn, v, fpscr); if (vsn.exponent == 0 && vsn.significand) vfp_single_normalise_denormal(&vsn); - vfp_single_unpack(&vsm, m, &fpscr); + exceptions |= vfp_single_unpack(&vsm, m, fpscr); if (vsm.exponent == 0 && vsm.significand) vfp_single_normalise_denormal(&vsm); - exceptions = vfp_single_multiply(&vsp, &vsn, &vsm, fpscr); + exceptions |= vfp_single_multiply(&vsp, &vsn, &vsm, fpscr); if (negate & NEG_MULTIPLY) vsp.sign = vfp_sign_negate(vsp.sign); v = vfp_get_float(state, sd); LOG_TRACE(Core_ARM11, "s%u = %08x", sd, v); - vfp_single_unpack(&vsn, v, &fpscr); + exceptions |= vfp_single_unpack(&vsn, v, fpscr); if (vsn.exponent == 0 && vsn.significand != 0) vfp_single_normalise_denormal(&vsn); @@ -950,7 +974,8 @@ vfp_single_multiply_accumulate(ARMul_State* state, int sd, int sn, s32 m, u32 fp exceptions |= vfp_single_add(&vsd, &vsn, &vsp, fpscr); - return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, func); + exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, func); + return exceptions; } /* @@ -962,8 +987,10 @@ vfp_single_multiply_accumulate(ARMul_State* state, int sd, int sn, s32 m, u32 fp */ static u32 vfp_single_fmac(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr) { + u32 exceptions = 0; LOG_TRACE(Core_ARM11, "s%u = %08x", sn, sd); - return vfp_single_multiply_accumulate(state, sd, sn, m, fpscr, 0, "fmac"); + exceptions |= vfp_single_multiply_accumulate(state, sd, sn, m, fpscr, 0, "fmac"); + return exceptions; } /* @@ -1000,21 +1027,23 @@ static u32 vfp_single_fnmsc(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr static u32 vfp_single_fmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr) { struct vfp_single vsd, vsn, vsm; - u32 exceptions; + u32 exceptions = 0; s32 n = vfp_get_float(state, sn); LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n); - vfp_single_unpack(&vsn, n, &fpscr); + exceptions |= vfp_single_unpack(&vsn, n, fpscr); if (vsn.exponent == 0 && vsn.significand) vfp_single_normalise_denormal(&vsn); - vfp_single_unpack(&vsm, m, &fpscr); + exceptions |= vfp_single_unpack(&vsm, m, fpscr); if (vsm.exponent == 0 && vsm.significand) vfp_single_normalise_denormal(&vsm); - exceptions = vfp_single_multiply(&vsd, &vsn, &vsm, fpscr); - return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fmul"); + exceptions |= vfp_single_multiply(&vsd, &vsn, &vsm, fpscr); + + exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fmul"); + return exceptions; } /* @@ -1023,22 +1052,24 @@ static u32 vfp_single_fmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr) static u32 vfp_single_fnmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr) { struct vfp_single vsd, vsn, vsm; - u32 exceptions; + u32 exceptions = 0; s32 n = vfp_get_float(state, sn); LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n); - vfp_single_unpack(&vsn, n, &fpscr); + exceptions |= vfp_single_unpack(&vsn, n, fpscr); if (vsn.exponent == 0 && vsn.significand) vfp_single_normalise_denormal(&vsn); - vfp_single_unpack(&vsm, m, &fpscr); + exceptions |= vfp_single_unpack(&vsm, m, fpscr); if (vsm.exponent == 0 && vsm.significand) vfp_single_normalise_denormal(&vsm); - exceptions = vfp_single_multiply(&vsd, &vsn, &vsm, fpscr); + exceptions |= vfp_single_multiply(&vsd, &vsn, &vsm, fpscr); vsd.sign = vfp_sign_negate(vsd.sign); - return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fnmul"); + + exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fnmul"); + return exceptions; } /* @@ -1047,7 +1078,7 @@ static u32 vfp_single_fnmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr static u32 vfp_single_fadd(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr) { struct vfp_single vsd, vsn, vsm; - u32 exceptions; + u32 exceptions = 0; s32 n = vfp_get_float(state, sn); LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n); @@ -1055,17 +1086,18 @@ static u32 vfp_single_fadd(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr) /* * Unpack and normalise denormals. */ - vfp_single_unpack(&vsn, n, &fpscr); + exceptions |= vfp_single_unpack(&vsn, n, fpscr); if (vsn.exponent == 0 && vsn.significand) vfp_single_normalise_denormal(&vsn); - vfp_single_unpack(&vsm, m, &fpscr); + exceptions |= vfp_single_unpack(&vsm, m, fpscr); if (vsm.exponent == 0 && vsm.significand) vfp_single_normalise_denormal(&vsm); - exceptions = vfp_single_add(&vsd, &vsn, &vsm, fpscr); + exceptions |= vfp_single_add(&vsd, &vsn, &vsm, fpscr); - return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fadd"); + exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fadd"); + return exceptions; } /* @@ -1095,8 +1127,8 @@ static u32 vfp_single_fdiv(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr) LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n); - vfp_single_unpack(&vsn, n, &fpscr); - vfp_single_unpack(&vsm, m, &fpscr); + exceptions |= vfp_single_unpack(&vsn, n, fpscr); + exceptions |= vfp_single_unpack(&vsm, m, fpscr); vsd.sign = vsn.sign ^ vsm.sign; @@ -1162,16 +1194,17 @@ static u32 vfp_single_fdiv(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr) if ((vsd.significand & 0x3f) == 0) vsd.significand |= ((u64)vsm.significand * vsd.significand != (u64)vsn.significand << 32); - return vfp_single_normaliseround(state, sd, &vsd, fpscr, 0, "fdiv"); + exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fdiv"); + return exceptions; vsn_nan: - exceptions = vfp_propagate_nan(&vsd, &vsn, &vsm, fpscr); + exceptions |= vfp_propagate_nan(&vsd, &vsn, &vsm, fpscr); pack: vfp_put_float(state, vfp_single_pack(&vsd), sd); return exceptions; vsm_nan: - exceptions = vfp_propagate_nan(&vsd, &vsm, &vsn, fpscr); + exceptions |= vfp_propagate_nan(&vsd, &vsm, &vsn, fpscr); goto pack; zero: @@ -1180,7 +1213,7 @@ zero: goto pack; divzero: - exceptions = FPSCR_DZC; + exceptions |= FPSCR_DZC; infinity: vsd.exponent = 255; vsd.significand = 0; @@ -1188,7 +1221,8 @@ infinity: invalid: vfp_put_float(state, vfp_single_pack(&vfp_single_default_qnan), sd); - return FPSCR_IOC; + exceptions |= FPSCR_IOC; + return exceptions; } static struct op fops[] = { 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/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index ae0c116ef..820b19e1a 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -374,7 +374,7 @@ static void SendReply(const char* reply) { memset(command_buffer, 0, sizeof(command_buffer)); - command_length = strlen(reply); + command_length = static_cast<u32>(strlen(reply)); if (command_length + 4 > sizeof(command_buffer)) { LOG_ERROR(Debug_GDBStub, "command_buffer overflow in SendReply"); return; @@ -437,7 +437,7 @@ static void HandleSetThread() { * * @param signal Signal to be sent to client. */ -void SendSignal(u32 signal) { +static void SendSignal(u32 signal) { if (gdbserver_socket == -1) { return; } @@ -515,7 +515,7 @@ static bool IsDataAvailable() { return false; } - return FD_ISSET(gdbserver_socket, &fd_socket); + return FD_ISSET(gdbserver_socket, &fd_socket) != 0; } /// Send requested register to gdb client. @@ -633,10 +633,10 @@ static void ReadMemory() { auto start_offset = command_buffer+1; auto addr_pos = std::find(start_offset, command_buffer+command_length, ','); - PAddr addr = HexToInt(start_offset, addr_pos - start_offset); + PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); start_offset = addr_pos+1; - u32 len = HexToInt(start_offset, (command_buffer + command_length) - start_offset); + u32 len = HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset)); LOG_DEBUG(Debug_GDBStub, "gdb: addr: %08x len: %08x\n", addr, len); @@ -658,11 +658,11 @@ static void ReadMemory() { static void WriteMemory() { auto start_offset = command_buffer+1; auto addr_pos = std::find(start_offset, command_buffer+command_length, ','); - PAddr addr = HexToInt(start_offset, addr_pos - start_offset); + PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); start_offset = addr_pos+1; auto len_pos = std::find(start_offset, command_buffer+command_length, ':'); - u32 len = HexToInt(start_offset, len_pos - start_offset); + u32 len = HexToInt(start_offset, static_cast<u32>(len_pos - start_offset)); u8* dst = Memory::GetPointer(addr); if (!dst) { @@ -713,7 +713,7 @@ static void Continue() { * @param addr Address of breakpoint. * @param len Length of breakpoint. */ -bool CommitBreakpoint(BreakpointType type, PAddr addr, u32 len) { +static bool CommitBreakpoint(BreakpointType type, PAddr addr, u32 len) { std::map<u32, Breakpoint>& p = GetBreakpointList(type); Breakpoint breakpoint; @@ -752,10 +752,10 @@ static void AddBreakpoint() { auto start_offset = command_buffer+3; auto addr_pos = std::find(start_offset, command_buffer+command_length, ','); - PAddr addr = HexToInt(start_offset, addr_pos - start_offset); + PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); start_offset = addr_pos+1; - u32 len = HexToInt(start_offset, (command_buffer + command_length) - start_offset); + u32 len = HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset)); if (type == BreakpointType::Access) { // Access is made up of Read and Write types, so add both breakpoints @@ -800,10 +800,10 @@ static void RemoveBreakpoint() { auto start_offset = command_buffer+3; auto addr_pos = std::find(start_offset, command_buffer+command_length, ','); - PAddr addr = HexToInt(start_offset, addr_pos - start_offset); + PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); start_offset = addr_pos+1; - u32 len = HexToInt(start_offset, (command_buffer + command_length) - start_offset); + u32 len = HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset)); if (type == BreakpointType::Access) { // Access is made up of Read and Write types, so add both breakpoints @@ -907,7 +907,7 @@ void ToggleServer(bool status) { } } -void Init(u16 port) { +static void Init(u16 port) { if (!g_server_enabled) { // Set the halt loop to false in case the user enabled the gdbstub mid-execution. // This way the CPU can still execute normally. diff --git a/src/core/hle/applets/applet.h b/src/core/hle/applets/applet.h index af442f81d..754c6f7db 100644 --- a/src/core/hle/applets/applet.h +++ b/src/core/hle/applets/applet.h @@ -65,6 +65,7 @@ protected: virtual ResultCode StartImpl(const Service::APT::AppletStartupParameter& parameter) = 0; Service::APT::AppletId id; ///< Id of this Applet + std::shared_ptr<std::vector<u8>> heap_memory; ///< Heap memory for this Applet }; /// Returns whether a library applet is currently running diff --git a/src/core/hle/applets/mii_selector.cpp b/src/core/hle/applets/mii_selector.cpp index 708d2f630..bf39eca22 100644 --- a/src/core/hle/applets/mii_selector.cpp +++ b/src/core/hle/applets/mii_selector.cpp @@ -21,13 +21,6 @@ namespace HLE { namespace Applets { -MiiSelector::MiiSelector(Service::APT::AppletId id) : Applet(id), started(false) { - // Create the SharedMemory that will hold the framebuffer data - // TODO(Subv): What size should we use here? - using Kernel::MemoryPermission; - framebuffer_memory = Kernel::SharedMemory::Create(0x1000, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, "MiiSelector Memory"); -} - ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& parameter) { if (parameter.signal != static_cast<u32>(Service::APT::SignalType::LibAppJustStarted)) { LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal); @@ -36,8 +29,23 @@ ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& p return ResultCode(-1); } + // The LibAppJustStarted message contains a buffer with the size of the framebuffer shared memory. + // Create the SharedMemory that will hold the framebuffer data + Service::APT::CaptureBufferInfo capture_info; + ASSERT(sizeof(capture_info) == parameter.buffer_size); + + memcpy(&capture_info, parameter.data, sizeof(capture_info)); + + using Kernel::MemoryPermission; + // Allocate a heap block of the required size for this applet. + heap_memory = std::make_shared<std::vector<u8>>(capture_info.size); + // Create a SharedMemory that directly points to this heap block. + framebuffer_memory = Kernel::SharedMemory::CreateForApplet(heap_memory, 0, heap_memory->size(), + MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, + "MiiSelector Memory"); + + // Send the response message with the newly created SharedMemory Service::APT::MessageParameter result; - // The buffer passed in parameter contains the data returned by GSPGPU::ImportDisplayCaptureInfo result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished); result.data = nullptr; result.buffer_size = 0; @@ -55,6 +63,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 6a3e7c8eb..be6b04642 100644 --- a/src/core/hle/applets/mii_selector.h +++ b/src/core/hle/applets/mii_selector.h @@ -16,17 +16,61 @@ 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); + MiiSelector(Service::APT::AppletId id) : Applet(id), started(false) { } ResultCode ReceiveParameter(const Service::APT::MessageParameter& parameter) override; ResultCode StartImpl(const Service::APT::AppletStartupParameter& parameter) override; void Update() override; bool IsRunning() const override { return started; } - /// TODO(Subv): Find out what this is actually used for. - /// It is believed that the application stores the current screen image here. + /// This SharedMemory will be created when we receive the LibAppJustStarted message. + /// It holds the framebuffer info retrieved by the application with GSPGPU::ImportDisplayCaptureInfo Kernel::SharedPtr<Kernel::SharedMemory> framebuffer_memory; /// Whether this applet is currently running instead of the host application or not. diff --git a/src/core/hle/applets/swkbd.cpp b/src/core/hle/applets/swkbd.cpp index 1db6b5a17..90c6adc65 100644 --- a/src/core/hle/applets/swkbd.cpp +++ b/src/core/hle/applets/swkbd.cpp @@ -24,13 +24,6 @@ namespace HLE { namespace Applets { -SoftwareKeyboard::SoftwareKeyboard(Service::APT::AppletId id) : Applet(id), started(false) { - // Create the SharedMemory that will hold the framebuffer data - // TODO(Subv): What size should we use here? - using Kernel::MemoryPermission; - framebuffer_memory = Kernel::SharedMemory::Create(0x1000, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, "SoftwareKeyboard Memory"); -} - ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter const& parameter) { if (parameter.signal != static_cast<u32>(Service::APT::SignalType::LibAppJustStarted)) { LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal); @@ -39,8 +32,23 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con return ResultCode(-1); } + // The LibAppJustStarted message contains a buffer with the size of the framebuffer shared memory. + // Create the SharedMemory that will hold the framebuffer data + Service::APT::CaptureBufferInfo capture_info; + ASSERT(sizeof(capture_info) == parameter.buffer_size); + + memcpy(&capture_info, parameter.data, sizeof(capture_info)); + + using Kernel::MemoryPermission; + // Allocate a heap block of the required size for this applet. + heap_memory = std::make_shared<std::vector<u8>>(capture_info.size); + // Create a SharedMemory that directly points to this heap block. + framebuffer_memory = Kernel::SharedMemory::CreateForApplet(heap_memory, 0, heap_memory->size(), + MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, + "SoftwareKeyboard Memory"); + + // Send the response message with the newly created SharedMemory Service::APT::MessageParameter result; - // The buffer passed in parameter contains the data returned by GSPGPU::ImportDisplayCaptureInfo result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished); result.data = nullptr; result.buffer_size = 0; diff --git a/src/core/hle/applets/swkbd.h b/src/core/hle/applets/swkbd.h index cb95b8d90..cf26a8fb7 100644 --- a/src/core/hle/applets/swkbd.h +++ b/src/core/hle/applets/swkbd.h @@ -53,8 +53,7 @@ static_assert(sizeof(SoftwareKeyboardConfig) == 0x400, "Software Keyboard Config class SoftwareKeyboard final : public Applet { public: - SoftwareKeyboard(Service::APT::AppletId id); - ~SoftwareKeyboard() {} + SoftwareKeyboard(Service::APT::AppletId id) : Applet(id), started(false) { } ResultCode ReceiveParameter(const Service::APT::MessageParameter& parameter) override; ResultCode StartImpl(const Service::APT::AppletStartupParameter& parameter) override; @@ -72,8 +71,8 @@ public: */ void Finalize(); - /// TODO(Subv): Find out what this is actually used for. - /// It is believed that the application stores the current screen image here. + /// This SharedMemory will be created when we receive the LibAppJustStarted message. + /// It holds the framebuffer info retrieved by the application with GSPGPU::ImportDisplayCaptureInfo Kernel::SharedPtr<Kernel::SharedMemory> framebuffer_memory; /// SharedMemory where the output text will be stored diff --git a/src/core/hle/function_wrappers.h b/src/core/hle/function_wrappers.h index 4d718b681..bf7f875b6 100644 --- a/src/core/hle/function_wrappers.h +++ b/src/core/hle/function_wrappers.h @@ -170,7 +170,8 @@ template<ResultCode func(s64*, u32, s32)> void Wrap() { template<ResultCode func(u32*, u32, u32, u32, u32)> void Wrap() { u32 param_1 = 0; - u32 retval = func(¶m_1, PARAM(1), PARAM(2), PARAM(3), PARAM(4)).raw; + // The last parameter is passed in R0 instead of R4 + u32 retval = func(¶m_1, PARAM(1), PARAM(2), PARAM(3), PARAM(0)).raw; Core::g_app_core->SetReg(1, param_1); FuncReturn(retval); } 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/memory.cpp b/src/core/hle/kernel/memory.cpp index 862643448..17ae87aef 100644 --- a/src/core/hle/kernel/memory.cpp +++ b/src/core/hle/kernel/memory.cpp @@ -55,6 +55,9 @@ void MemoryInit(u32 mem_type) { memory_regions[i].size = memory_region_sizes[mem_type][i]; memory_regions[i].used = 0; memory_regions[i].linear_heap_memory = std::make_shared<std::vector<u8>>(); + // Reserve enough space for this region of FCRAM. + // We do not want this block of memory to be relocated when allocating from it. + memory_regions[i].linear_heap_memory->reserve(memory_regions[i].size); base += memory_regions[i].size; } @@ -107,9 +110,7 @@ struct MemoryArea { // We don't declare the IO regions in here since its handled by other means. static MemoryArea memory_areas[] = { - {SHARED_MEMORY_VADDR, SHARED_MEMORY_SIZE, "Shared Memory"}, // Shared memory {VRAM_VADDR, VRAM_SIZE, "VRAM"}, // Video memory (VRAM) - {TLS_AREA_VADDR, TLS_AREA_SIZE, "TLS Area"}, // TLS memory }; } diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 0546f6e16..69302cc82 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -209,7 +209,7 @@ ResultVal<VAddr> Process::LinearAllocate(VAddr target, u32 size, VMAPermission p return ERR_INVALID_ADDRESS; } - // Expansion of the linear heap is only allowed if you do an allocation immediatelly at its + // Expansion of the linear heap is only allowed if you do an allocation immediately at its // end. It's possible to free gaps in the middle of the heap and then reallocate them later, // but expansions are only allowed at the end. if (target == heap_end) { diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 6d2ca96a2..d781ef32c 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -107,6 +107,8 @@ public: ProcessFlags flags; /// Kernel compatibility version for this process u16 kernel_version = 0; + /// The default CPU for this process, threads are scheduled on this cpu by default. + u8 ideal_processor = 0; /// The id of this process u32 process_id = next_process_id++; @@ -140,8 +142,11 @@ public: MemoryRegionInfo* memory_region = nullptr; - /// Bitmask of the used TLS slots - std::bitset<300> used_tls_slots; + /// The Thread Local Storage area is allocated as processes create threads, + /// each TLS area is 0x200 bytes, so one page (0x1000) is split up in 8 parts, and each part + /// holds the TLS for a specific thread. This vector contains which parts are in use for each page as a bitmask. + /// This vector will grow as more pages are allocated for new threads. + std::vector<std::bitset<8>> tls_slots; VAddr GetLinearHeapAreaAddress() const; VAddr GetLinearHeapBase() const; diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index d90f0f00f..6a22c8986 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -7,6 +7,7 @@ #include "common/logging/log.h" #include "core/memory.h" +#include "core/hle/kernel/memory.h" #include "core/hle/kernel/shared_memory.h" namespace Kernel { @@ -14,93 +15,157 @@ namespace Kernel { SharedMemory::SharedMemory() {} SharedMemory::~SharedMemory() {} -SharedPtr<SharedMemory> SharedMemory::Create(u32 size, MemoryPermission permissions, - MemoryPermission other_permissions, std::string name) { +SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u32 size, MemoryPermission permissions, + MemoryPermission other_permissions, VAddr address, MemoryRegion region, std::string name) { SharedPtr<SharedMemory> shared_memory(new SharedMemory); + shared_memory->owner_process = owner_process; shared_memory->name = std::move(name); - shared_memory->base_address = 0x0; - shared_memory->fixed_address = 0x0; shared_memory->size = size; shared_memory->permissions = permissions; shared_memory->other_permissions = other_permissions; + if (address == 0) { + // We need to allocate a block from the Linear Heap ourselves. + // We'll manually allocate some memory from the linear heap in the specified region. + MemoryRegionInfo* memory_region = GetMemoryRegion(region); + auto& linheap_memory = memory_region->linear_heap_memory; + + ASSERT_MSG(linheap_memory->size() + size <= memory_region->size, "Not enough space in region to allocate shared memory!"); + + shared_memory->backing_block = linheap_memory; + shared_memory->backing_block_offset = linheap_memory->size(); + // Allocate some memory from the end of the linear heap for this region. + linheap_memory->insert(linheap_memory->end(), size, 0); + memory_region->used += size; + + shared_memory->linear_heap_phys_address = Memory::FCRAM_PADDR + memory_region->base + shared_memory->backing_block_offset; + + // Increase the amount of used linear heap memory for the owner process. + if (shared_memory->owner_process != nullptr) { + shared_memory->owner_process->linear_heap_used += size; + } + + // Refresh the address mappings for the current process. + if (Kernel::g_current_process != nullptr) { + Kernel::g_current_process->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); + } + } else { + // TODO(Subv): What happens if an application tries to create multiple memory blocks pointing to the same address? + auto& vm_manager = shared_memory->owner_process->vm_manager; + // The memory is already available and mapped in the owner process. + auto vma = vm_manager.FindVMA(address)->second; + // Copy it over to our own storage + shared_memory->backing_block = std::make_shared<std::vector<u8>>(vma.backing_block->data() + vma.offset, + vma.backing_block->data() + vma.offset + size); + shared_memory->backing_block_offset = 0; + // Unmap the existing pages + vm_manager.UnmapRange(address, size); + // Map our own block into the address space + vm_manager.MapMemoryBlock(address, shared_memory->backing_block, 0, size, MemoryState::Shared); + // Reprotect the block with the new permissions + vm_manager.ReprotectRange(address, size, ConvertPermissions(permissions)); + } + + shared_memory->base_address = address; return shared_memory; } -ResultCode SharedMemory::Map(VAddr address, MemoryPermission permissions, - MemoryPermission other_permissions) { +SharedPtr<SharedMemory> SharedMemory::CreateForApplet(std::shared_ptr<std::vector<u8>> heap_block, u32 offset, u32 size, + MemoryPermission permissions, MemoryPermission other_permissions, std::string name) { + SharedPtr<SharedMemory> shared_memory(new SharedMemory); - if (base_address != 0) { - LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s: already mapped at 0x%08X!", - GetObjectId(), address, name.c_str(), base_address); - // TODO: Verify error code with hardware - return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel, - ErrorSummary::InvalidArgument, ErrorLevel::Permanent); - } + shared_memory->owner_process = nullptr; + shared_memory->name = std::move(name); + shared_memory->size = size; + shared_memory->permissions = permissions; + shared_memory->other_permissions = other_permissions; + shared_memory->backing_block = heap_block; + shared_memory->backing_block_offset = offset; + shared_memory->base_address = Memory::HEAP_VADDR + offset; - // TODO(Subv): Return E0E01BEE when permissions and other_permissions don't - // match what was specified when the memory block was created. + return shared_memory; +} - // TODO(Subv): Return E0E01BEE when address should be 0. - // Note: Find out when that's the case. +ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermission permissions, + MemoryPermission other_permissions) { - if (fixed_address != 0) { - if (address != 0 && address != fixed_address) { - LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s: fixed_addres is 0x%08X!", - GetObjectId(), address, name.c_str(), fixed_address); - // TODO: Verify error code with hardware - return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel, - ErrorSummary::InvalidArgument, ErrorLevel::Permanent); - } + MemoryPermission own_other_permissions = target_process == owner_process ? this->permissions : this->other_permissions; - // HACK(yuriks): This is only here to support the APT shared font mapping right now. - // Later, this should actually map the memory block onto the address space. - return RESULT_SUCCESS; + // Automatically allocated memory blocks can only be mapped with other_permissions = DontCare + if (base_address == 0 && other_permissions != MemoryPermission::DontCare) { + return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); } - if (address < Memory::SHARED_MEMORY_VADDR || address + size >= Memory::SHARED_MEMORY_VADDR_END) { - LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s outside of shared mem bounds!", - GetObjectId(), address, name.c_str()); - // TODO: Verify error code with hardware - return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel, - ErrorSummary::InvalidArgument, ErrorLevel::Permanent); + // Error out if the requested permissions don't match what the creator process allows. + if (static_cast<u32>(permissions) & ~static_cast<u32>(own_other_permissions)) { + LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, permissions don't match", + GetObjectId(), address, name.c_str()); + return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); } - // TODO: Test permissions + // Heap-backed memory blocks can not be mapped with other_permissions = DontCare + if (base_address != 0 && other_permissions == MemoryPermission::DontCare) { + LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, permissions don't match", + GetObjectId(), address, name.c_str()); + return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); + } - // HACK: Since there's no way to write to the memory block without mapping it onto the game - // process yet, at least initialize memory the first time it's mapped. - if (address != this->base_address) { - std::memset(Memory::GetPointer(address), 0, size); + // Error out if the provided permissions are not compatible with what the creator process needs. + if (other_permissions != MemoryPermission::DontCare && + static_cast<u32>(this->permissions) & ~static_cast<u32>(other_permissions)) { + LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, permissions don't match", + GetObjectId(), address, name.c_str()); + return ResultCode(ErrorDescription::WrongPermission, ErrorModule::OS, ErrorSummary::WrongArgument, ErrorLevel::Permanent); } - this->base_address = address; + // TODO(Subv): Check for the Shared Device Mem flag in the creator process. + /*if (was_created_with_shared_device_mem && address != 0) { + return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); + }*/ - return RESULT_SUCCESS; -} + // TODO(Subv): The same process that created a SharedMemory object + // can not map it in its own address space unless it was created with addr=0, result 0xD900182C. -ResultCode SharedMemory::Unmap(VAddr address) { - if (base_address == 0) { - // TODO(Subv): Verify what actually happens when you want to unmap a memory block that - // was originally mapped with address = 0 - return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); + if (address != 0) { + if (address < Memory::HEAP_VADDR || address + size >= Memory::SHARED_MEMORY_VADDR_END) { + LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, invalid address", + GetObjectId(), address, name.c_str()); + return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); + } } - if (base_address != address) - return ResultCode(ErrorDescription::WrongAddress, ErrorModule::OS, ErrorSummary::InvalidState, ErrorLevel::Usage); + VAddr target_address = address; - base_address = 0; + if (base_address == 0 && target_address == 0) { + // Calculate the address at which to map the memory block. + target_address = Memory::PhysicalToVirtualAddress(linear_heap_phys_address); + } + + // Map the memory block into the target process + auto result = target_process->vm_manager.MapMemoryBlock(target_address, backing_block, backing_block_offset, size, MemoryState::Shared); + if (result.Failed()) { + LOG_ERROR(Kernel, "cannot map id=%u, target_address=0x%08X name=%s, error mapping to virtual memory", + GetObjectId(), target_address, name.c_str()); + return result.Code(); + } - return RESULT_SUCCESS; + return target_process->vm_manager.ReprotectRange(target_address, size, ConvertPermissions(permissions)); } -u8* SharedMemory::GetPointer(u32 offset) { - if (base_address != 0) - return Memory::GetPointer(base_address + offset); +ResultCode SharedMemory::Unmap(Process* target_process, VAddr address) { + // TODO(Subv): Verify what happens if the application tries to unmap an address that is not mapped to a SharedMemory. + return target_process->vm_manager.UnmapRange(address, size); +} + +VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) { + u32 masked_permissions = static_cast<u32>(permission) & static_cast<u32>(MemoryPermission::ReadWriteExecute); + return static_cast<VMAPermission>(masked_permissions); +}; - LOG_ERROR(Kernel_SVC, "memory block id=%u not mapped!", GetObjectId()); - return nullptr; +u8* SharedMemory::GetPointer(u32 offset) { + return backing_block->data() + backing_block_offset + offset; } } // namespace diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h index b51049ad0..0c404a9f8 100644 --- a/src/core/hle/kernel/shared_memory.h +++ b/src/core/hle/kernel/shared_memory.h @@ -9,6 +9,7 @@ #include "common/common_types.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" #include "core/hle/result.h" namespace Kernel { @@ -29,14 +30,29 @@ enum class MemoryPermission : u32 { class SharedMemory final : public Object { public: /** - * Creates a shared memory object + * Creates a shared memory object. + * @param owner_process Process that created this shared memory object. * @param size Size of the memory block. Must be page-aligned. * @param permissions Permission restrictions applied to the process which created the block. * @param other_permissions Permission restrictions applied to other processes mapping the block. + * @param address The address from which to map the Shared Memory. + * @param region If the address is 0, the shared memory will be allocated in this region of the linear heap. * @param name Optional object name, used for debugging purposes. */ - static SharedPtr<SharedMemory> Create(u32 size, MemoryPermission permissions, - MemoryPermission other_permissions, std::string name = "Unknown"); + static SharedPtr<SharedMemory> Create(SharedPtr<Process> owner_process, u32 size, MemoryPermission permissions, + MemoryPermission other_permissions, VAddr address = 0, MemoryRegion region = MemoryRegion::BASE, std::string name = "Unknown"); + + /** + * Creates a shared memory object from a block of memory managed by an HLE applet. + * @param heap_block Heap block of the HLE applet. + * @param offset The offset into the heap block that the SharedMemory will map. + * @param size Size of the memory block. Must be page-aligned. + * @param permissions Permission restrictions applied to the process which created the block. + * @param other_permissions Permission restrictions applied to other processes mapping the block. + * @param name Optional object name, used for debugging purposes. + */ + static SharedPtr<SharedMemory> CreateForApplet(std::shared_ptr<std::vector<u8>> heap_block, u32 offset, u32 size, + MemoryPermission permissions, MemoryPermission other_permissions, std::string name = "Unknown Applet"); std::string GetTypeName() const override { return "SharedMemory"; } std::string GetName() const override { return name; } @@ -45,19 +61,27 @@ public: HandleType GetHandleType() const override { return HANDLE_TYPE; } /** - * Maps a shared memory block to an address in system memory + * Converts the specified MemoryPermission into the equivalent VMAPermission. + * @param permission The MemoryPermission to convert. + */ + static VMAPermission ConvertPermissions(MemoryPermission permission); + + /** + * Maps a shared memory block to an address in the target process' address space + * @param target_process Process on which to map the memory block. * @param address Address in system memory to map shared memory block to * @param permissions Memory block map permissions (specified by SVC field) * @param other_permissions Memory block map other permissions (specified by SVC field) */ - ResultCode Map(VAddr address, MemoryPermission permissions, MemoryPermission other_permissions); + ResultCode Map(Process* target_process, VAddr address, MemoryPermission permissions, MemoryPermission other_permissions); /** * Unmaps a shared memory block from the specified address in system memory + * @param target_process Process from which to umap the memory block. * @param address Address in system memory where the shared memory block is mapped * @return Result code of the unmap operation */ - ResultCode Unmap(VAddr address); + ResultCode Unmap(Process* target_process, VAddr address); /** * Gets a pointer to the shared memory block @@ -66,10 +90,16 @@ public: */ u8* GetPointer(u32 offset = 0); - /// Address of shared memory block in the process. + /// Process that created this shared memory block. + SharedPtr<Process> owner_process; + /// Address of shared memory block in the owner process if specified. VAddr base_address; - /// Fixed address to allow mapping to. Used for blocks created from the linear heap. - VAddr fixed_address; + /// Physical address of the shared memory block in the linear heap if no address was specified during creation. + PAddr linear_heap_phys_address; + /// Backing memory for this shared memory block. + std::shared_ptr<std::vector<u8>> backing_block; + /// Offset into the backing block for this shared memory. + u32 backing_block_offset; /// Size of the memory block. Page-aligned. u32 size; /// Permission restrictions applied to the process which created the block. diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index bf32f653d..43def6146 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -117,9 +117,10 @@ void Thread::Stop() { } wait_objects.clear(); - Kernel::g_current_process->used_tls_slots[tls_index] = false; - g_current_process->misc_memory_used -= Memory::TLS_ENTRY_SIZE; - g_current_process->memory_region->used -= Memory::TLS_ENTRY_SIZE; + // Mark the TLS slot in the thread's page as free. + u32 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE; + u32 tls_slot = ((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE; + Kernel::g_current_process->tls_slots[tls_page].reset(tls_slot); HLE::Reschedule(__func__); } @@ -366,6 +367,31 @@ static void DebugThreadQueue() { } } +/** + * Finds a free location for the TLS section of a thread. + * @param tls_slots The TLS page array of the thread's owner process. + * Returns a tuple of (page, slot, alloc_needed) where: + * page: The index of the first allocated TLS page that has free slots. + * slot: The index of the first free slot in the indicated page. + * alloc_needed: Whether there's a need to allocate a new TLS page (All pages are full). + */ +std::tuple<u32, u32, bool> GetFreeThreadLocalSlot(std::vector<std::bitset<8>>& tls_slots) { + // Iterate over all the allocated pages, and try to find one where not all slots are used. + for (unsigned page = 0; page < tls_slots.size(); ++page) { + const auto& page_tls_slots = tls_slots[page]; + if (!page_tls_slots.all()) { + // We found a page with at least one free slot, find which slot it is + for (unsigned slot = 0; slot < page_tls_slots.size(); ++slot) { + if (!page_tls_slots.test(slot)) { + return std::make_tuple(page, slot, false); + } + } + } + } + + return std::make_tuple(0, 0, true); +} + ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, s32 priority, u32 arg, s32 processor_id, VAddr stack_top) { if (priority < THREADPRIO_HIGHEST || priority > THREADPRIO_LOWEST) { @@ -403,22 +429,50 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, thread->name = std::move(name); thread->callback_handle = wakeup_callback_handle_table.Create(thread).MoveFrom(); thread->owner_process = g_current_process; - thread->tls_index = -1; thread->waitsynch_waited = false; // Find the next available TLS index, and mark it as used - auto& used_tls_slots = Kernel::g_current_process->used_tls_slots; - for (unsigned int i = 0; i < used_tls_slots.size(); ++i) { - if (used_tls_slots[i] == false) { - thread->tls_index = i; - used_tls_slots[i] = true; - break; + auto& tls_slots = Kernel::g_current_process->tls_slots; + bool needs_allocation = true; + u32 available_page; // Which allocated page has free space + u32 available_slot; // Which slot within the page is free + + std::tie(available_page, available_slot, needs_allocation) = GetFreeThreadLocalSlot(tls_slots); + + if (needs_allocation) { + // There are no already-allocated pages with free slots, lets allocate a new one. + // TLS pages are allocated from the BASE region in the linear heap. + MemoryRegionInfo* memory_region = GetMemoryRegion(MemoryRegion::BASE); + auto& linheap_memory = memory_region->linear_heap_memory; + + if (linheap_memory->size() + Memory::PAGE_SIZE > memory_region->size) { + LOG_ERROR(Kernel_SVC, "Not enough space in region to allocate a new TLS page for thread"); + return ResultCode(ErrorDescription::OutOfMemory, ErrorModule::Kernel, ErrorSummary::OutOfResource, ErrorLevel::Permanent); } + + u32 offset = linheap_memory->size(); + + // Allocate some memory from the end of the linear heap for this region. + linheap_memory->insert(linheap_memory->end(), Memory::PAGE_SIZE, 0); + memory_region->used += Memory::PAGE_SIZE; + Kernel::g_current_process->linear_heap_used += Memory::PAGE_SIZE; + + tls_slots.emplace_back(0); // The page is completely available at the start + available_page = tls_slots.size() - 1; + available_slot = 0; // Use the first slot in the new page + + auto& vm_manager = Kernel::g_current_process->vm_manager; + vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); + + // Map the page to the current process' address space. + // TODO(Subv): Find the correct MemoryState for this region. + vm_manager.MapMemoryBlock(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE, + linheap_memory, offset, Memory::PAGE_SIZE, MemoryState::Private); } - ASSERT_MSG(thread->tls_index != -1, "Out of TLS space"); - g_current_process->misc_memory_used += Memory::TLS_ENTRY_SIZE; - g_current_process->memory_region->used += Memory::TLS_ENTRY_SIZE; + // Mark the slot as used + tls_slots[available_page].set(available_slot); + thread->tls_address = Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE + available_slot * Memory::TLS_ENTRY_SIZE; // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used // to initialize the context @@ -472,6 +526,8 @@ SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) { SharedPtr<Thread> thread = thread_res.MoveFrom(); + thread->context.fpscr = FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO | FPSCR_IXC; // 0x03C00010 + // Run new "main" thread SwitchContext(thread.get()); @@ -483,7 +539,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) @@ -508,10 +565,6 @@ void Thread::SetWaitSynchronizationOutput(s32 output) { context.cpu_registers[1] = output; } -VAddr Thread::GetTLSAddress() const { - return Memory::TLS_AREA_VADDR + tls_index * Memory::TLS_ENTRY_SIZE; -} - //////////////////////////////////////////////////////////////////////////////////////////////////// void ThreadingInit() { diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 97ba57fc5..deab5d5a6 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -127,7 +127,7 @@ public: * Returns the Thread Local Storage address of the current thread * @returns VAddr of the thread's TLS */ - VAddr GetTLSAddress() const; + VAddr GetTLSAddress() const { return tls_address; } Core::ThreadContext context; @@ -144,7 +144,7 @@ public: s32 processor_id; - s32 tls_index; ///< Index of the Thread Local Storage of the thread + VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread bool waitsynch_waited; ///< Set to true if the last svcWaitSynch call caused the thread to wait diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 1e289f38a..066146cff 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -7,6 +7,7 @@ #include "common/assert.h" #include "core/hle/kernel/vm_manager.h" +#include "core/memory.h" #include "core/memory_setup.h" #include "core/mmio.h" diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 3fc1ab4ee..bfb3327ce 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -17,6 +17,7 @@ /// Detailed description of the error. This listing is likely incomplete. enum class ErrorDescription : u32 { Success = 0, + WrongPermission = 46, OS_InvalidBufferDescriptor = 48, WrongAddress = 53, FS_NotFound = 120, diff --git a/src/core/hle/service/act_a.cpp b/src/core/hle/service/act_a.cpp new file mode 100644 index 000000000..3a775fa90 --- /dev/null +++ b/src/core/hle/service/act_a.cpp @@ -0,0 +1,26 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/act_a.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace ACT_A + +namespace ACT_A { + +const Interface::FunctionInfo FunctionTable[] = { + {0x041300C2, nullptr, "UpdateMiiImage"}, + {0x041B0142, nullptr, "AgreeEula"}, + {0x04210042, nullptr, "UploadMii"}, + {0x04230082, nullptr, "ValidateMailAddress"}, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Interface class + +Interface::Interface() { + Register(FunctionTable); +} + +} // namespace diff --git a/src/core/hle/service/act_a.h b/src/core/hle/service/act_a.h new file mode 100644 index 000000000..765cae644 --- /dev/null +++ b/src/core/hle/service/act_a.h @@ -0,0 +1,23 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace ACT_A + +namespace ACT_A { + +class Interface : public Service::Interface { +public: + Interface(); + + std::string GetPortName() const override { + return "act:a"; + } +}; + +} // namespace diff --git a/src/core/hle/service/act_u.cpp b/src/core/hle/service/act_u.cpp index b23d17fba..05de4d002 100644 --- a/src/core/hle/service/act_u.cpp +++ b/src/core/hle/service/act_u.cpp @@ -10,7 +10,10 @@ namespace ACT_U { const Interface::FunctionInfo FunctionTable[] = { + {0x00010084, nullptr, "Initialize"}, + {0x00020040, nullptr, "GetErrorCode"}, {0x000600C2, nullptr, "GetAccountDataBlock"}, + {0x000D0040, nullptr, "GenerateUuid"}, }; //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 6d72e8188..bbf170b71 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -12,7 +12,9 @@ #include "core/hle/service/apt/apt_a.h" #include "core/hle/service/apt/apt_s.h" #include "core/hle/service/apt/apt_u.h" +#include "core/hle/service/apt/bcfnt/bcfnt.h" #include "core/hle/service/fs/archive.h" +#include "core/hle/service/ptm/ptm.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/mutex.h" @@ -22,25 +24,19 @@ namespace Service { namespace APT { -// Address used for shared font (as observed on HW) -// TODO(bunnei): This is the hard-coded address where we currently dump the shared font from via -// https://github.com/citra-emu/3dsutils. This is technically a hack, and will not work at any -// address other than 0x18000000 due to internal pointers in the shared font dump that would need to -// be relocated. This might be fixed by dumping the shared font @ address 0x00000000 and then -// correctly mapping it in Citra, however we still do not understand how the mapping is determined. -static const VAddr SHARED_FONT_VADDR = 0x18000000; - /// Handle to shared memory region designated to for shared system font static Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem; +static bool shared_font_relocated = false; static Kernel::SharedPtr<Kernel::Mutex> lock; static Kernel::SharedPtr<Kernel::Event> notification_event; ///< APT notification event static Kernel::SharedPtr<Kernel::Event> parameter_event; ///< APT parameter event -static std::shared_ptr<std::vector<u8>> shared_font; - static u32 cpu_percent; ///< CPU time available to the running application +// APT::CheckNew3DSApp will check this unknown_ns_state_field to determine processing mode +static u8 unknown_ns_state_field; + /// Parameter data to be returned in the next call to Glance/ReceiveParameter static MessageParameter next_parameter; @@ -74,23 +70,25 @@ void Initialize(Service::Interface* self) { void GetSharedFont(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - if (shared_font != nullptr) { - // TODO(yuriks): This is a hack to keep this working right now even with our completely - // broken shared memory system. - shared_font_mem->fixed_address = SHARED_FONT_VADDR; - Kernel::g_current_process->vm_manager.MapMemoryBlock(shared_font_mem->fixed_address, - shared_font, 0, shared_font_mem->size, Kernel::MemoryState::Shared); - - cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = SHARED_FONT_VADDR; - cmd_buff[3] = IPC::MoveHandleDesc(); - cmd_buff[4] = Kernel::g_handle_table.Create(shared_font_mem).MoveFrom(); - } else { - cmd_buff[0] = IPC::MakeHeader(0x44, 1, 0); - cmd_buff[1] = -1; // Generic error (not really possible to verify this on hardware) - LOG_ERROR(Kernel_SVC, "called, but %s has not been loaded!", SHARED_FONT); + // The shared font has to be relocated to the new address before being passed to the application. + VAddr target_address = Memory::PhysicalToVirtualAddress(shared_font_mem->linear_heap_phys_address); + // The shared font dumped by 3dsutils (https://github.com/citra-emu/3dsutils) uses this address as base, + // so we relocate it from there to our real address. + // TODO(Subv): This address is wrong if the shared font is dumped from a n3DS, + // we need a way to automatically calculate the original address of the font from the file. + static const VAddr SHARED_FONT_VADDR = 0x18000000; + if (!shared_font_relocated) { + BCFNT::RelocateSharedFont(shared_font_mem, SHARED_FONT_VADDR, target_address); + shared_font_relocated = true; } + cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + // Since the SharedMemory interface doesn't provide the address at which the memory was allocated, + // the real APT service calculates this address by scanning the entire address space (using svcQueryMemory) + // and searches for an allocation of the same size as the Shared Font. + cmd_buff[2] = target_address; + cmd_buff[3] = IPC::MoveHandleDesc(); + cmd_buff[4] = Kernel::g_handle_table.Create(shared_font_mem).MoveFrom(); } void NotifyToWait(Service::Interface* self) { @@ -264,6 +262,10 @@ void PrepareToStartApplication(Service::Interface* self) { u32 title_info4 = cmd_buff[4]; u32 flags = cmd_buff[5]; + if (flags & 0x00000100) { + unknown_ns_state_field = 1; + } + cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_APT, "(STUBBED) called title_info1=0x%08X, title_info2=0x%08X, title_info3=0x%08X," @@ -379,6 +381,25 @@ void StartLibraryApplet(Service::Interface* self) { cmd_buff[1] = applet->Start(parameter).raw; } +void SetNSStateField(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + unknown_ns_state_field = cmd_buff[1]; + + cmd_buff[0] = IPC::MakeHeader(0x55, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_WARNING(Service_APT, "(STUBBED) unknown_ns_state_field=%u", unknown_ns_state_field); +} + +void GetNSStateField(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x56, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[8] = unknown_ns_state_field; + LOG_WARNING(Service_APT, "(STUBBED) unknown_ns_state_field=%u", unknown_ns_state_field); +} + void GetAppletInfo(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); auto app_id = static_cast<AppletId>(cmd_buff[1]); @@ -414,6 +435,29 @@ void GetStartupArgument(Service::Interface* self) { cmd_buff[2] = (parameter_size > 0) ? 1 : 0; } +void CheckNew3DSApp(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + if (unknown_ns_state_field) { + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = 0; + } else { + PTM::CheckNew3DS(self); + } + + cmd_buff[0] = IPC::MakeHeader(0x101, 2, 0); + LOG_WARNING(Service_APT, "(STUBBED) called"); +} + +void CheckNew3DS(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + PTM::CheckNew3DS(self); + + cmd_buff[0] = IPC::MakeHeader(0x102, 2, 0); + LOG_WARNING(Service_APT, "(STUBBED) called"); +} + void Init() { AddService(new APT_A_Interface); AddService(new APT_S_Interface); @@ -433,14 +477,12 @@ void Init() { FileUtil::IOFile file(filepath, "rb"); if (file.IsOpen()) { - // Read shared font data - shared_font = std::make_shared<std::vector<u8>>((size_t)file.GetSize()); - file.ReadBytes(shared_font->data(), shared_font->size()); - // Create shared font memory object using Kernel::MemoryPermission; - shared_font_mem = Kernel::SharedMemory::Create(3 * 1024 * 1024, // 3MB - MemoryPermission::ReadWrite, MemoryPermission::Read, "APT_U:shared_font_mem"); + shared_font_mem = Kernel::SharedMemory::Create(nullptr, 0x332000, // 3272 KB + MemoryPermission::ReadWrite, MemoryPermission::Read, 0, Kernel::MemoryRegion::SYSTEM, "APT:SharedFont"); + // Read shared font data + file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize()); } else { LOG_WARNING(Service_APT, "Unable to load shared font: %s", filepath.c_str()); shared_font_mem = nullptr; @@ -449,6 +491,7 @@ void Init() { lock = Kernel::Mutex::Create(false, "APT_U:Lock"); cpu_percent = 0; + unknown_ns_state_field = 0; // TODO(bunnei): Check if these are created in Initialize or on APT process startup. notification_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Notification"); @@ -459,8 +502,8 @@ void Init() { } void Shutdown() { - shared_font = nullptr; shared_font_mem = nullptr; + shared_font_relocated = false; lock = nullptr; notification_event = nullptr; parameter_event = nullptr; diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 668b4a66f..ed7c47cca 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -5,6 +5,7 @@ #pragma once #include "common/common_types.h" +#include "common/swap.h" #include "core/hle/kernel/kernel.h" @@ -31,6 +32,20 @@ struct AppletStartupParameter { u8* data = nullptr; }; +/// Used by the application to pass information about the current framebuffer to applets. +struct CaptureBufferInfo { + u32_le size; + u8 is_3d; + INSERT_PADDING_BYTES(0x3); // Padding for alignment + u32_le top_screen_left_offset; + u32_le top_screen_right_offset; + u32_le top_screen_format; + u32_le bottom_screen_left_offset; + u32_le bottom_screen_right_offset; + u32_le bottom_screen_format; +}; +static_assert(sizeof(CaptureBufferInfo) == 0x20, "CaptureBufferInfo struct has incorrect size"); + /// Signals used by APT functions enum class SignalType : u32 { None = 0x0, @@ -361,6 +376,50 @@ void StartLibraryApplet(Service::Interface* self); */ void GetStartupArgument(Service::Interface* self); +/** + * APT::SetNSStateField service function + * Inputs: + * 1 : u8 NS state field + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * Note: + * This writes the input u8 to a NS state field. + */ +void SetNSStateField(Service::Interface* self); + +/** + * APT::GetNSStateField service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 8 : u8 NS state field + * Note: + * This returns a u8 NS state field(which can be set by cmd 0x00550040), at cmdreply+8. + */ +void GetNSStateField(Service::Interface* self); + +/** + * APT::CheckNew3DSApp service function + * Outputs: + * 1: Result code, 0 on success, otherwise error code + * 2: u8 output: 0 = Old3DS, 1 = New3DS. + * Note: + * This uses PTMSYSM:CheckNew3DS. + * When a certain NS state field is non-zero, the output value is zero, + * Otherwise the output is from PTMSYSM:CheckNew3DS. + * Normally this NS state field is zero, however this state field is set to 1 + * when APT:PrepareToStartApplication is used with flags bit8 is set. + */ +void CheckNew3DSApp(Service::Interface* self); + +/** + * Wrapper for PTMSYSM:CheckNew3DS + * APT::CheckNew3DS service function + * Outputs: + * 1: Result code, 0 on success, otherwise error code + * 2: u8 output: 0 = Old3DS, 1 = New3DS. + */ +void CheckNew3DS(Service::Interface* self); + /// Initialize the APT service void Init(); diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp index 9ff47701a..223c0a8bd 100644 --- a/src/core/hle/service/apt/apt_a.cpp +++ b/src/core/hle/service/apt/apt_a.cpp @@ -21,6 +21,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x000D0080, ReceiveParameter, "ReceiveParameter"}, {0x000E0080, GlanceParameter, "GlanceParameter"}, {0x000F0100, CancelParameter, "CancelParameter"}, + {0x00150140, PrepareToStartApplication, "PrepareToStartApplication"}, {0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"}, {0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"}, {0x001E0084, StartLibraryApplet, "StartLibraryApplet"}, @@ -32,7 +33,10 @@ const Interface::FunctionInfo FunctionTable[] = { {0x004F0080, SetAppCpuTimeLimit, "SetAppCpuTimeLimit"}, {0x00500040, GetAppCpuTimeLimit, "GetAppCpuTimeLimit"}, {0x00510080, GetStartupArgument, "GetStartupArgument"}, - {0x00550040, nullptr, "WriteInputToNsState?"}, + {0x00550040, SetNSStateField, "SetNSStateField?"}, + {0x00560000, GetNSStateField, "GetNSStateField?"}, + {0x01010000, CheckNew3DSApp, "CheckNew3DSApp"}, + {0x01020000, CheckNew3DS, "CheckNew3DS"} }; APT_A_Interface::APT_A_Interface() { diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp index ca54e593c..f5c52fa3d 100644 --- a/src/core/hle/service/apt/apt_s.cpp +++ b/src/core/hle/service/apt/apt_s.cpp @@ -29,7 +29,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"}, {0x00130000, nullptr, "GetPreparationState"}, {0x00140040, nullptr, "SetPreparationState"}, - {0x00150140, nullptr, "PrepareToStartApplication"}, + {0x00150140, PrepareToStartApplication, "PrepareToStartApplication"}, {0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"}, {0x00170040, nullptr, "FinishPreloadingLibraryApplet"}, {0x00180040, PrepareToStartLibraryApplet,"PrepareToStartLibraryApplet"}, @@ -92,9 +92,11 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00510080, GetStartupArgument, "GetStartupArgument"}, {0x00520104, nullptr, "Wrap1"}, {0x00530104, nullptr, "Unwrap1"}, + {0x00550040, SetNSStateField, "SetNSStateField?" }, + {0x00560000, GetNSStateField, "GetNSStateField?" }, {0x00580002, nullptr, "GetProgramID"}, - {0x01010000, nullptr, "CheckNew3DSApp"}, - {0x01020000, nullptr, "CheckNew3DS"} + {0x01010000, CheckNew3DSApp, "CheckNew3DSApp"}, + {0x01020000, CheckNew3DS, "CheckNew3DS"} }; APT_S_Interface::APT_S_Interface() { diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp index 0e85c6d08..0e60bd34f 100644 --- a/src/core/hle/service/apt/apt_u.cpp +++ b/src/core/hle/service/apt/apt_u.cpp @@ -29,7 +29,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"}, {0x00130000, nullptr, "GetPreparationState"}, {0x00140040, nullptr, "SetPreparationState"}, - {0x00150140, nullptr, "PrepareToStartApplication"}, + {0x00150140, PrepareToStartApplication, "PrepareToStartApplication"}, {0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"}, {0x00170040, nullptr, "FinishPreloadingLibraryApplet"}, {0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"}, @@ -92,9 +92,11 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00510080, GetStartupArgument, "GetStartupArgument"}, {0x00520104, nullptr, "Wrap1"}, {0x00530104, nullptr, "Unwrap1"}, + {0x00550040, SetNSStateField, "SetNSStateField?"}, + {0x00560000, GetNSStateField, "GetNSStateField?"}, {0x00580002, nullptr, "GetProgramID"}, - {0x01010000, nullptr, "CheckNew3DSApp"}, - {0x01020000, nullptr, "CheckNew3DS"} + {0x01010000, CheckNew3DSApp, "CheckNew3DSApp"}, + {0x01020000, CheckNew3DS, "CheckNew3DS"} }; APT_U_Interface::APT_U_Interface() { diff --git a/src/core/hle/service/apt/bcfnt/bcfnt.cpp b/src/core/hle/service/apt/bcfnt/bcfnt.cpp new file mode 100644 index 000000000..b0d39d4a5 --- /dev/null +++ b/src/core/hle/service/apt/bcfnt/bcfnt.cpp @@ -0,0 +1,71 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/apt/bcfnt/bcfnt.h" +#include "core/hle/service/service.h" + +namespace Service { +namespace APT { +namespace BCFNT { + +void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr previous_address, VAddr new_address) { + static const u32 SharedFontStartOffset = 0x80; + u8* data = shared_font->GetPointer(SharedFontStartOffset); + + CFNT cfnt; + memcpy(&cfnt, data, sizeof(cfnt)); + + // Advance past the header + data = shared_font->GetPointer(SharedFontStartOffset + cfnt.header_size); + + for (unsigned block = 0; block < cfnt.num_blocks; ++block) { + + u32 section_size = 0; + if (memcmp(data, "FINF", 4) == 0) { + BCFNT::FINF finf; + memcpy(&finf, data, sizeof(finf)); + section_size = finf.section_size; + + // Relocate the offsets in the FINF section + finf.cmap_offset += new_address - previous_address; + finf.cwdh_offset += new_address - previous_address; + finf.tglp_offset += new_address - previous_address; + + memcpy(data, &finf, sizeof(finf)); + } else if (memcmp(data, "CMAP", 4) == 0) { + BCFNT::CMAP cmap; + memcpy(&cmap, data, sizeof(cmap)); + section_size = cmap.section_size; + + // Relocate the offsets in the CMAP section + cmap.next_cmap_offset += new_address - previous_address; + + memcpy(data, &cmap, sizeof(cmap)); + } else if (memcmp(data, "CWDH", 4) == 0) { + BCFNT::CWDH cwdh; + memcpy(&cwdh, data, sizeof(cwdh)); + section_size = cwdh.section_size; + + // Relocate the offsets in the CWDH section + cwdh.next_cwdh_offset += new_address - previous_address; + + memcpy(data, &cwdh, sizeof(cwdh)); + } else if (memcmp(data, "TGLP", 4) == 0) { + BCFNT::TGLP tglp; + memcpy(&tglp, data, sizeof(tglp)); + section_size = tglp.section_size; + + // Relocate the offsets in the TGLP section + tglp.sheet_data_offset += new_address - previous_address; + + memcpy(data, &tglp, sizeof(tglp)); + } + + data += section_size; + } +} + +} // namespace BCFNT +} // namespace APT +} // namespace Service
\ No newline at end of file diff --git a/src/core/hle/service/apt/bcfnt/bcfnt.h b/src/core/hle/service/apt/bcfnt/bcfnt.h new file mode 100644 index 000000000..388c6bea0 --- /dev/null +++ b/src/core/hle/service/apt/bcfnt/bcfnt.h @@ -0,0 +1,87 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/swap.h" + +#include "core/hle/kernel/shared_memory.h" +#include "core/hle/service/service.h" + +namespace Service { +namespace APT { +namespace BCFNT { ///< BCFNT Shared Font file structures + +struct CFNT { + u8 magic[4]; + u16_le endianness; + u16_le header_size; + u32_le version; + u32_le file_size; + u32_le num_blocks; +}; + +struct FINF { + u8 magic[4]; + u32_le section_size; + u8 font_type; + u8 line_feed; + u16_le alter_char_index; + u8 default_width[3]; + u8 encoding; + u32_le tglp_offset; + u32_le cwdh_offset; + u32_le cmap_offset; + u8 height; + u8 width; + u8 ascent; + u8 reserved; +}; + +struct TGLP { + u8 magic[4]; + u32_le section_size; + u8 cell_width; + u8 cell_height; + u8 baseline_position; + u8 max_character_width; + u32_le sheet_size; + u16_le num_sheets; + u16_le sheet_image_format; + u16_le num_columns; + u16_le num_rows; + u16_le sheet_width; + u16_le sheet_height; + u32_le sheet_data_offset; +}; + +struct CMAP { + u8 magic[4]; + u32_le section_size; + u16_le code_begin; + u16_le code_end; + u16_le mapping_method; + u16_le reserved; + u32_le next_cmap_offset; +}; + +struct CWDH { + u8 magic[4]; + u32_le section_size; + u16_le start_index; + u16_le end_index; + u32_le next_cwdh_offset; +}; + +/** + * Relocates the internal addresses of the BCFNT Shared Font to the new base. + * @param shared_font SharedMemory object that contains the Shared Font + * @param previous_address Previous address at which the offsets in the structure were based. + * @param new_address New base for the offsets in the structure. + */ +void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr previous_address, VAddr new_address); + +} // namespace BCFNT +} // namespace APT +} // namespace Service diff --git a/src/core/hle/service/csnd_snd.cpp b/src/core/hle/service/csnd_snd.cpp index 6318bf2a7..d2bb8941c 100644 --- a/src/core/hle/service/csnd_snd.cpp +++ b/src/core/hle/service/csnd_snd.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <cstring> +#include "common/alignment.h" #include "core/hle/hle.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/shared_memory.h" @@ -41,14 +42,16 @@ static Kernel::SharedPtr<Kernel::Mutex> mutex = nullptr; void Initialize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - shared_memory = Kernel::SharedMemory::Create(cmd_buff[1], - Kernel::MemoryPermission::ReadWrite, - Kernel::MemoryPermission::ReadWrite, "CSNDSharedMem"); + u32 size = Common::AlignUp(cmd_buff[1], Memory::PAGE_SIZE); + using Kernel::MemoryPermission; + shared_memory = Kernel::SharedMemory::Create(nullptr, size, + MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, + 0, Kernel::MemoryRegion::BASE, "CSND:SharedMemory"); mutex = Kernel::Mutex::Create(false); - cmd_buff[1] = 0; - cmd_buff[2] = 0x4000000; + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = IPC::MoveHandleDesc(2); cmd_buff[3] = Kernel::g_handle_table.Create(mutex).MoveFrom(); cmd_buff[4] = Kernel::g_handle_table.Create(shared_memory).MoveFrom(); } diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp index 995bee3f9..10730d7ac 100644 --- a/src/core/hle/service/dsp_dsp.cpp +++ b/src/core/hle/service/dsp_dsp.cpp @@ -288,7 +288,7 @@ static void WriteProcessPipe(Service::Interface* self) { ASSERT_MSG(Memory::GetPointer(buffer) != nullptr, "Invalid Buffer: pipe=%u, size=0x%X, buffer=0x%08X", pipe_index, size, buffer); std::vector<u8> message(size); - for (size_t i = 0; i < size; i++) { + for (u32 i = 0; i < size; i++) { message[i] = Memory::Read8(buffer + i); } @@ -403,7 +403,7 @@ static void GetPipeReadableSize(Service::Interface* self) { cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = DSP::HLE::GetPipeReadableSize(pipe); + cmd_buff[2] = static_cast<u32>(DSP::HLE::GetPipeReadableSize(pipe)); LOG_DEBUG(Service_DSP, "pipe=%u, unknown=0x%08X, return cmd_buff[2]=0x%08X", pipe_index, unknown, cmd_buff[2]); } @@ -440,9 +440,9 @@ static void GetHeadphoneStatus(Service::Interface* self) { cmd_buff[0] = IPC::MakeHeader(0x1F, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 0; // Not using headphones? + cmd_buff[2] = 0; // Not using headphones - LOG_WARNING(Service_DSP, "(STUBBED) called"); + LOG_DEBUG(Service_DSP, "called"); } /** diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index b4c146e08..8ded9b09b 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -335,8 +335,9 @@ static void RegisterInterruptRelayQueue(Service::Interface* self) { g_interrupt_event->name = "GSP_GPU::interrupt_event"; using Kernel::MemoryPermission; - g_shared_memory = Kernel::SharedMemory::Create(0x1000, MemoryPermission::ReadWrite, - MemoryPermission::ReadWrite, "GSPSharedMem"); + g_shared_memory = Kernel::SharedMemory::Create(nullptr, 0x1000, + MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, + 0, Kernel::MemoryRegion::BASE, "GSP:SharedMemory"); Handle shmem_handle = Kernel::g_handle_table.Create(g_shared_memory).MoveFrom(); diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 1053d0f40..d216cecb4 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -280,8 +280,9 @@ void Init() { AddService(new HID_SPVR_Interface); using Kernel::MemoryPermission; - shared_mem = SharedMemory::Create(0x1000, MemoryPermission::ReadWrite, - MemoryPermission::Read, "HID:SharedMem"); + shared_mem = SharedMemory::Create(nullptr, 0x1000, + MemoryPermission::ReadWrite, MemoryPermission::Read, + 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory"); next_pad_index = 0; next_touch_index = 0; diff --git a/src/core/hle/service/ir/ir.cpp b/src/core/hle/service/ir/ir.cpp index 505c441c6..079a87e48 100644 --- a/src/core/hle/service/ir/ir.cpp +++ b/src/core/hle/service/ir/ir.cpp @@ -94,8 +94,9 @@ void Init() { AddService(new IR_User_Interface); using Kernel::MemoryPermission; - shared_memory = SharedMemory::Create(0x1000, Kernel::MemoryPermission::ReadWrite, - Kernel::MemoryPermission::ReadWrite, "IR:SharedMemory"); + shared_memory = SharedMemory::Create(nullptr, 0x1000, + Kernel::MemoryPermission::ReadWrite, Kernel::MemoryPermission::ReadWrite, + 0, Kernel::MemoryRegion::BASE, "IR:SharedMemory"); transfer_shared_memory = nullptr; // Create event handle(s) diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index 94f494690..e2c17d93b 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -3,7 +3,7 @@ // Refer to the license.txt file included. #include "common/logging/log.h" - +#include "core/settings.h" #include "core/file_sys/file_backend.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/ptm/ptm.h" @@ -89,6 +89,20 @@ void IsLegacyPowerOff(Service::Interface* self) { LOG_WARNING(Service_PTM, "(STUBBED) called"); } +void CheckNew3DS(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + const bool is_new_3ds = Settings::values.is_new_3ds; + + if (is_new_3ds) { + LOG_CRITICAL(Service_PTM, "The option 'is_new_3ds' is enabled as part of the 'System' settings. Citra does not fully support New 3DS emulation yet!"); + } + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = is_new_3ds ? 1 : 0; + + LOG_WARNING(Service_PTM, "(STUBBED) called isNew3DS = 0x%08x", static_cast<u32>(is_new_3ds)); +} + void Init() { AddService(new PTM_Play_Interface); AddService(new PTM_Sysm_Interface); diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h index 4cf7383d1..7ef8877c7 100644 --- a/src/core/hle/service/ptm/ptm.h +++ b/src/core/hle/service/ptm/ptm.h @@ -88,6 +88,14 @@ void GetTotalStepCount(Interface* self); */ void IsLegacyPowerOff(Interface* self); +/** + * PTM::CheckNew3DS service function + * Outputs: + * 1: Result code, 0 on success, otherwise error code + * 2: u8 output: 0 = Old3DS, 1 = New3DS. + */ +void CheckNew3DS(Interface* self); + /// Initialize the PTM service void Init(); diff --git a/src/core/hle/service/ptm/ptm_sysm.cpp b/src/core/hle/service/ptm/ptm_sysm.cpp index fe76dd108..cc4ef1101 100644 --- a/src/core/hle/service/ptm/ptm_sysm.cpp +++ b/src/core/hle/service/ptm/ptm_sysm.cpp @@ -18,7 +18,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x040700C0, nullptr, "ShutdownAsync"}, {0x04080000, nullptr, "Awake"}, {0x04090080, nullptr, "RebootAsync"}, - {0x040A0000, nullptr, "CheckNew3DS"}, + {0x040A0000, CheckNew3DS, "CheckNew3DS"}, {0x08010640, nullptr, "SetInfoLEDPattern"}, {0x08020040, nullptr, "SetInfoLEDPatternHeader"}, {0x08030000, nullptr, "GetInfoLEDStatus"}, @@ -35,7 +35,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x080E0140, nullptr, "NotifyPlayEvent"}, {0x080F0000, IsLegacyPowerOff, "IsLegacyPowerOff"}, {0x08100000, nullptr, "ClearLegacyPowerOff"}, - {0x08110000, nullptr, "GetShellStatus"}, + {0x08110000, GetShellState, "GetShellState"}, {0x08120000, nullptr, "IsShutdownByBatteryEmpty"}, {0x08130000, nullptr, "FormatSavedata"}, {0x08140000, nullptr, "GetLegacyJumpProhibitedFlag"}, diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 0fe3a4d7a..d7e7d4fe3 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -7,6 +7,7 @@ #include "core/hle/service/service.h" #include "core/hle/service/ac_u.h" +#include "core/hle/service/act_a.h" #include "core/hle/service/act_u.h" #include "core/hle/service/csnd_snd.h" #include "core/hle/service/dlp_srvr.h" @@ -119,6 +120,7 @@ void Init() { Service::PTM::Init(); AddService(new AC_U::Interface); + AddService(new ACT_A::Interface); AddService(new ACT_U::Interface); AddService(new CSND_SND::Interface); AddService(new DLP_SRVR::Interface); diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index fb2aecbf2..0ce72de87 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -6,6 +6,7 @@ #include "common/logging/log.h" #include "common/microprofile.h" +#include "common/scope_exit.h" #include "common/string_util.h" #include "common/symbols.h" @@ -99,6 +100,7 @@ static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 add switch (operation & MEMOP_OPERATION_MASK) { case MEMOP_FREE: { + // TODO(Subv): What happens if an application tries to FREE a block of memory that has a SharedMemory pointing to it? if (addr0 >= Memory::HEAP_VADDR && addr0 < Memory::HEAP_VADDR_END) { ResultCode result = process.HeapFree(addr0, size); if (result.IsError()) return result; @@ -160,8 +162,6 @@ static ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 o LOG_TRACE(Kernel_SVC, "called memblock=0x%08X, addr=0x%08X, mypermissions=0x%08X, otherpermission=%d", handle, addr, permissions, other_permissions); - // TODO(Subv): The same process that created a SharedMemory object can not map it in its own address space - SharedPtr<SharedMemory> shared_memory = Kernel::g_handle_table.Get<SharedMemory>(handle); if (shared_memory == nullptr) return ERR_INVALID_HANDLE; @@ -176,7 +176,7 @@ static ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 o case MemoryPermission::WriteExecute: case MemoryPermission::ReadWriteExecute: case MemoryPermission::DontCare: - return shared_memory->Map(addr, permissions_type, + return shared_memory->Map(Kernel::g_current_process.get(), addr, permissions_type, static_cast<MemoryPermission>(other_permissions)); default: LOG_ERROR(Kernel_SVC, "unknown permissions=0x%08X", permissions); @@ -196,7 +196,7 @@ static ResultCode UnmapMemoryBlock(Handle handle, u32 addr) { if (shared_memory == nullptr) return ERR_INVALID_HANDLE; - return shared_memory->Unmap(addr); + return shared_memory->Unmap(Kernel::g_current_process.get(), addr); } /// Connect to an OS service given the port name, returns the handle to the port to out @@ -327,9 +327,9 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou } } - HLE::Reschedule(__func__); + SCOPE_EXIT({HLE::Reschedule("WaitSynchronizationN");}); // Reschedule after putting the threads to sleep. - // If thread should wait, then set its state to waiting and then reschedule... + // If thread should wait, then set its state to waiting if (wait_thread) { // Actually wait the current thread on each object if we decided to wait... @@ -496,8 +496,16 @@ static ResultCode CreateThread(Handle* out_handle, s32 priority, u32 entry_point break; } + if (processor_id == THREADPROCESSORID_1 || processor_id == THREADPROCESSORID_ALL || + (processor_id == THREADPROCESSORID_DEFAULT && Kernel::g_current_process->ideal_processor == THREADPROCESSORID_1)) { + LOG_WARNING(Kernel_SVC, "Newly created thread is allowed to be run in the SysCore, unimplemented."); + } + CASCADE_RESULT(SharedPtr<Thread> thread, Kernel::Thread::Create( name, entry_point, priority, arg, processor_id, stack_top)); + + thread->context.fpscr = FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO; // 0x03C00000 + CASCADE_RESULT(*out_handle, Kernel::g_handle_table.Create(std::move(thread))); LOG_TRACE(Kernel_SVC, "called entrypoint=0x%08X (%s), arg=0x%08X, stacktop=0x%08X, " @@ -785,18 +793,44 @@ static ResultCode CreateMemoryBlock(Handle* out_handle, u32 addr, u32 size, u32 if (size % Memory::PAGE_SIZE != 0) return ResultCode(ErrorDescription::MisalignedSize, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); - // TODO(Subv): Return E0A01BF5 if the address is not in the application's heap - - // TODO(Subv): Implement this function properly + SharedPtr<SharedMemory> shared_memory = nullptr; using Kernel::MemoryPermission; - SharedPtr<SharedMemory> shared_memory = SharedMemory::Create(size, - (MemoryPermission)my_permission, (MemoryPermission)other_permission); - // Map the SharedMemory to the specified address - shared_memory->base_address = addr; + auto VerifyPermissions = [](MemoryPermission permission) { + // SharedMemory blocks can not be created with Execute permissions + switch (permission) { + case MemoryPermission::None: + case MemoryPermission::Read: + case MemoryPermission::Write: + case MemoryPermission::ReadWrite: + case MemoryPermission::DontCare: + return true; + default: + return false; + } + }; + + if (!VerifyPermissions(static_cast<MemoryPermission>(my_permission)) || + !VerifyPermissions(static_cast<MemoryPermission>(other_permission))) + return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); + + if (addr < Memory::PROCESS_IMAGE_VADDR || addr + size > Memory::SHARED_MEMORY_VADDR_END) { + return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); + } + + // When trying to create a memory block with address = 0, + // if the process has the Shared Device Memory flag in the exheader, + // then we have to allocate from the same region as the caller process instead of the BASE region. + Kernel::MemoryRegion region = Kernel::MemoryRegion::BASE; + if (addr == 0 && Kernel::g_current_process->flags.shared_device_mem) + region = Kernel::g_current_process->flags.memory_region; + + shared_memory = SharedMemory::Create(Kernel::g_current_process, size, + static_cast<MemoryPermission>(my_permission), static_cast<MemoryPermission>(other_permission), addr, region); CASCADE_RESULT(*out_handle, Kernel::g_handle_table.Create(std::move(shared_memory))); - LOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x%08X", addr); + LOG_WARNING(Kernel_SVC, "called addr=0x%08X", addr); return RESULT_SUCCESS; } diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index 2fe856293..a4dfb7e43 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -188,10 +188,10 @@ inline void Write(u32 addr, const T data) { u32 output_gap = config.texture_copy.output_gap * 16; size_t contiguous_input_size = config.texture_copy.size / input_width * (input_width + input_gap); - Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), contiguous_input_size); + Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), static_cast<u32>(contiguous_input_size)); size_t contiguous_output_size = config.texture_copy.size / output_width * (output_width + output_gap); - Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), contiguous_output_size); + Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), static_cast<u32>(contiguous_output_size)); u32 remaining_size = config.texture_copy.size; u32 remaining_input = input_width; diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 5fb3b9e2b..a16411e14 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -10,6 +10,7 @@ #include "core/file_sys/archive_romfs.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" +#include "core/hle/service/fs/archive.h" #include "core/loader/3dsx.h" #include "core/memory.h" @@ -178,11 +179,11 @@ static THREEDSX_Error Load3DSXFile(FileUtil::IOFile& file, u32 base_addr, Shared for (unsigned current_inprogress = 0; current_inprogress < remaining && pos < end_pos; current_inprogress++) { const auto& table = reloc_table[current_inprogress]; LOG_TRACE(Loader, "(t=%d,skip=%u,patch=%u)", current_segment_reloc_table, - (u32)table.skip, (u32)table.patch); + static_cast<u32>(table.skip), static_cast<u32>(table.patch)); pos += table.skip; s32 num_patches = table.patch; while (0 < num_patches && pos < end_pos) { - u32 in_addr = (u8*)pos - program_image.data(); + u32 in_addr = static_cast<u32>(reinterpret_cast<u8*>(pos) - program_image.data()); u32 addr = TranslateAddr(*pos, &loadinfo, offsets); LOG_TRACE(Loader, "Patching %08X <-- rel(%08X,%d) (%08X)", base_addr + in_addr, addr, current_segment_reloc_table, *pos); @@ -263,6 +264,8 @@ ResultStatus AppLoader_THREEDSX::Load() { Kernel::g_current_process->Run(48, Kernel::DEFAULT_STACK_SIZE); + Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this), Service::FS::ArchiveIdCode::RomFS); + is_loaded = true; return ResultStatus::Success; } @@ -284,7 +287,7 @@ ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& ro // Check if the 3DSX has a RomFS... if (hdr.fs_offset != 0) { u32 romfs_offset = hdr.fs_offset; - u32 romfs_size = file.GetSize() - hdr.fs_offset; + u32 romfs_size = static_cast<u32>(file.GetSize()) - hdr.fs_offset; LOG_DEBUG(Loader, "RomFS offset: 0x%08X", romfs_offset); LOG_DEBUG(Loader, "RomFS size: 0x%08X", romfs_size); @@ -303,4 +306,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..90b20c61c 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) {} /** @@ -28,12 +28,27 @@ public: static FileType IdentifyType(FileUtil::IOFile& file); /** + * Returns the type of this file + * @return FileType corresponding to the loaded file + */ + FileType GetFileType() override { + return IdentifyType(file); + } + + /** * Load the bootable file * @return ResultStatus result of function */ 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/elf.h b/src/core/loader/elf.h index c6a5ebe99..cb3724f9d 100644 --- a/src/core/loader/elf.h +++ b/src/core/loader/elf.h @@ -28,6 +28,14 @@ public: static FileType IdentifyType(FileUtil::IOFile& file); /** + * Returns the type of this file + * @return FileType corresponding to the loaded file + */ + FileType GetFileType() override { + return IdentifyType(file); + } + + /** * Load the bootable file * @return ResultStatus result of function */ diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 886501c41..9719d30d5 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -8,9 +8,7 @@ #include "common/logging/log.h" #include "common/string_util.h" -#include "core/file_sys/archive_romfs.h" #include "core/hle/kernel/process.h" -#include "core/hle/service/fs/archive.h" #include "core/loader/3dsx.h" #include "core/loader/elf.h" #include "core/loader/ncch.h" @@ -67,6 +65,9 @@ FileType GuessFromExtension(const std::string& extension_) { if (extension == ".3dsx") return FileType::THREEDSX; + if (extension == ".cia") + return FileType::CIA; + return FileType::Unknown; } @@ -90,11 +91,41 @@ const char* GetFileTypeString(FileType type) { return "unknown"; } -ResultStatus LoadFile(const std::string& filename) { +/** + * Get a loader for a file with a specific type + * @param file The file to load + * @param type The type of the file + * @param filename the file name (without path) + * @param filepath the file full path (with name) + * @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type + */ +static std::unique_ptr<AppLoader> GetFileLoader(FileUtil::IOFile&& file, FileType type, + const std::string& filename, const std::string& filepath) { + switch (type) { + + // 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 nullptr; + } +} + +std::unique_ptr<AppLoader> GetLoader(const std::string& filename) { FileUtil::IOFile file(filename, "rb"); if (!file.IsOpen()) { LOG_ERROR(Loader, "Failed to load file %s", filename.c_str()); - return ResultStatus::Error; + return nullptr; } std::string filename_filename, filename_extension; @@ -111,53 +142,7 @@ ResultStatus LoadFile(const std::string& filename) { LOG_INFO(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type)); - switch (type) { - - //3DSX file format... - 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(); - if (ResultStatus::Success == result) { - Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(app_loader), Service::FS::ArchiveIdCode::RomFS); - } - return result; - } - - // CIA file format... - case FileType::CIA: - return ResultStatus::ErrorNotImplemented; - - // Error occurred durring IdentifyFile... - case FileType::Error: - - // IdentifyFile could know identify file type... - case FileType::Unknown: - { - LOG_CRITICAL(Loader, "File %s is of unknown type.", filename.c_str()); - return ResultStatus::ErrorInvalidFormat; - } - } - return ResultStatus::Error; + return GetFileLoader(std::move(file), type, filename_filename, filename); } } // namespace Loader diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 84a4ce5fc..77d87afe1 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -85,6 +85,12 @@ public: virtual ~AppLoader() { } /** + * Returns the type of this file + * @return FileType corresponding to the loaded file + */ + virtual FileType GetFileType() = 0; + + /** * Load the application * @return ResultStatus result of function */ @@ -150,10 +156,10 @@ protected: extern const std::initializer_list<Kernel::AddressMapping> default_address_mappings; /** - * Identifies and loads a bootable file + * Identifies a bootable file and return a suitable loader * @param filename String filename of bootable file - * @return ResultStatus result of function + * @return best loader for this file */ -ResultStatus LoadFile(const std::string& filename); +std::unique_ptr<AppLoader> GetLoader(const std::string& filename); } // namespace diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 066e91a9e..fca091ff9 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -10,8 +10,10 @@ #include "common/string_util.h" #include "common/swap.h" +#include "core/file_sys/archive_romfs.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" +#include "core/hle/service/fs/archive.h" #include "core/loader/ncch.h" #include "core/memory.h" @@ -156,6 +158,9 @@ ResultStatus AppLoader_NCCH::LoadExec() { Kernel::g_current_process->resource_limit = Kernel::ResourceLimit::GetForCategory( static_cast<Kernel::ResourceLimitCategory>(exheader_header.arm11_system_local_caps.resource_limit_category)); + // Set the default CPU core for this process + Kernel::g_current_process->ideal_processor = exheader_header.arm11_system_local_caps.ideal_processor; + // Copy data while converting endianess std::array<u32, ARRAY_SIZE(exheader_header.arm11_kernel_caps.descriptors)> kernel_caps; std::copy_n(exheader_header.arm11_kernel_caps.descriptors, kernel_caps.size(), begin(kernel_caps)); @@ -173,6 +178,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 +224,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,9 +291,26 @@ 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 + result = LoadExec(); // Load the executable into memory for booting + if (ResultStatus::Success != result) + return result; + + Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this), Service::FS::ArchiveIdCode::RomFS); + return ResultStatus::Success; } ResultStatus AppLoader_NCCH::ReadCode(std::vector<u8>& buffer) { diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index ca6772a78..75609ee57 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -174,6 +174,14 @@ public: static FileType IdentifyType(FileUtil::IOFile& file); /** + * Returns the type of this file + * @return FileType corresponding to the loaded file + */ + FileType GetFileType() override { + return IdentifyType(file); + } + + /** * Load the application * @return ResultStatus result of function */ @@ -232,6 +240,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/core/loader/smdh.cpp b/src/core/loader/smdh.cpp new file mode 100644 index 000000000..2d014054a --- /dev/null +++ b/src/core/loader/smdh.cpp @@ -0,0 +1,54 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include <vector> + +#include "common/common_types.h" + +#include "core/loader/loader.h" +#include "core/loader/smdh.h" + +#include "video_core/utils.h" + +namespace Loader { + +bool IsValidSMDH(const std::vector<u8>& smdh_data) { + if (smdh_data.size() < sizeof(Loader::SMDH)) + return false; + + u32 magic; + memcpy(&magic, smdh_data.data(), sizeof(u32)); + + return Loader::MakeMagic('S', 'M', 'D', 'H') == magic; +} + +std::vector<u16> SMDH::GetIcon(bool large) const { + u32 size; + const u8* icon_data; + + if (large) { + size = 48; + icon_data = large_icon.data(); + } else { + size = 24; + icon_data = small_icon.data(); + } + + std::vector<u16> icon(size * size); + for (u32 x = 0; x < size; ++x) { + for (u32 y = 0; y < size; ++y) { + u32 coarse_y = y & ~7; + const u8* pixel = icon_data + VideoCore::GetMortonOffset(x, y, 2) + coarse_y * size * 2; + icon[x + size * y] = (pixel[1] << 8) + pixel[0]; + } + } + return icon; +} + +std::array<u16, 0x40> SMDH::GetShortTitle(Loader::SMDH::TitleLanguage language) const { + return titles[static_cast<int>(language)].short_title; +} + +} // namespace diff --git a/src/core/loader/smdh.h b/src/core/loader/smdh.h new file mode 100644 index 000000000..2011abda2 --- /dev/null +++ b/src/core/loader/smdh.h @@ -0,0 +1,82 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <vector> + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Loader { + +/** + * Tests if data is a valid SMDH by its length and magic number. + * @param smdh_data data buffer to test + * @return bool test result + */ +bool IsValidSMDH(const std::vector<u8>& smdh_data); + +/// SMDH data structure that contains titles, icons etc. See https://www.3dbrew.org/wiki/SMDH +struct SMDH { + u32_le magic; + u16_le version; + INSERT_PADDING_BYTES(2); + + struct Title { + std::array<u16, 0x40> short_title; + std::array<u16, 0x80> long_title; + std::array<u16, 0x40> publisher; + }; + std::array<Title, 16> titles; + + std::array<u8, 16> ratings; + u32_le region_lockout; + u32_le match_maker_id; + u64_le match_maker_bit_id; + u32_le flags; + u16_le eula_version; + INSERT_PADDING_BYTES(2); + float_le banner_animation_frame; + u32_le cec_id; + INSERT_PADDING_BYTES(8); + + std::array<u8, 0x480> small_icon; + std::array<u8, 0x1200> large_icon; + + /// indicates the language used for each title entry + enum class TitleLanguage { + Japanese = 0, + English = 1, + French = 2, + German = 3, + Italian = 4, + Spanish = 5, + SimplifiedChinese = 6, + Korean= 7, + Dutch = 8, + Portuguese = 9, + Russian = 10, + TraditionalChinese = 11 + }; + + /** + * Gets game icon from SMDH + * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) + * @return vector of RGB565 data + */ + std::vector<u16> GetIcon(bool large) const; + + /** + * Gets the short game title from SMDH + * @param language title language + * @return UTF-16 array of the short title + */ + std::array<u16, 0x40> GetShortTitle(Loader::SMDH::TitleLanguage language) const; +}; +static_assert(sizeof(SMDH) == 0x36C0, "SMDH structure size is wrong"); + +} // namespace diff --git a/src/core/memory.h b/src/core/memory.h index 9caa3c3f5..126d60471 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -100,15 +100,9 @@ enum : VAddr { SHARED_PAGE_SIZE = 0x00001000, SHARED_PAGE_VADDR_END = SHARED_PAGE_VADDR + SHARED_PAGE_SIZE, - // TODO(yuriks): The size of this area is dynamic, the kernel grows - // it as more and more threads are created. For now we'll just use a - // hardcoded value. /// Area where TLS (Thread-Local Storage) buffers are allocated. TLS_AREA_VADDR = 0x1FF82000, TLS_ENTRY_SIZE = 0x200, - TLS_AREA_SIZE = 300 * TLS_ENTRY_SIZE + 0x800, // Space for up to 300 threads + round to page size - TLS_AREA_VADDR_END = TLS_AREA_VADDR + TLS_AREA_SIZE, - /// Equivalent to LINEAR_HEAP_VADDR, but expanded to cover the extra memory in the New 3DS. NEW_LINEAR_HEAP_VADDR = 0x30000000, diff --git a/src/core/memory_setup.h b/src/core/memory_setup.h index 05f70a1fe..ee8ea7857 100644 --- a/src/core/memory_setup.h +++ b/src/core/memory_setup.h @@ -6,7 +6,7 @@ #include "common/common_types.h" -#include "core/memory.h" +#include "core/mmio.h" namespace Memory { diff --git a/src/core/settings.h b/src/core/settings.h index ce2a31164..ea72f4d9c 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -41,6 +41,9 @@ static const std::array<Values, NUM_INPUTS> All = {{ struct Values { + // CheckNew3DS + bool is_new_3ds; + // Controls std::array<int, NativeInput::NUM_INPUTS> input_mappings; diff --git a/src/core/tracer/recorder.cpp b/src/core/tracer/recorder.cpp index c6dc35c83..7abaacf70 100644 --- a/src/core/tracer/recorder.cpp +++ b/src/core/tracer/recorder.cpp @@ -26,17 +26,17 @@ void Recorder::Finish(const std::string& filename) { // Calculate file offsets auto& initial = header.initial_state_offsets; - initial.gpu_registers_size = initial_state.gpu_registers.size(); - initial.lcd_registers_size = initial_state.lcd_registers.size(); - initial.pica_registers_size = initial_state.pica_registers.size(); - initial.default_attributes_size = initial_state.default_attributes.size(); - initial.vs_program_binary_size = initial_state.vs_program_binary.size(); - initial.vs_swizzle_data_size = initial_state.vs_swizzle_data.size(); - initial.vs_float_uniforms_size = initial_state.vs_float_uniforms.size(); - initial.gs_program_binary_size = initial_state.gs_program_binary.size(); - initial.gs_swizzle_data_size = initial_state.gs_swizzle_data.size(); - initial.gs_float_uniforms_size = initial_state.gs_float_uniforms.size(); - header.stream_size = stream.size(); + initial.gpu_registers_size = static_cast<u32>(initial_state.gpu_registers.size()); + initial.lcd_registers_size = static_cast<u32>(initial_state.lcd_registers.size()); + initial.pica_registers_size = static_cast<u32>(initial_state.pica_registers.size()); + initial.default_attributes_size = static_cast<u32>(initial_state.default_attributes.size()); + initial.vs_program_binary_size = static_cast<u32>(initial_state.vs_program_binary.size()); + initial.vs_swizzle_data_size = static_cast<u32>(initial_state.vs_swizzle_data.size()); + initial.vs_float_uniforms_size = static_cast<u32>(initial_state.vs_float_uniforms.size()); + initial.gs_program_binary_size = static_cast<u32>(initial_state.gs_program_binary.size()); + initial.gs_swizzle_data_size = static_cast<u32>(initial_state.gs_swizzle_data.size()); + initial.gs_float_uniforms_size = static_cast<u32>(initial_state.gs_float_uniforms.size()); + header.stream_size = static_cast<u32>(stream.size()); initial.gpu_registers = sizeof(header); initial.lcd_registers = initial.gpu_registers + initial.gpu_registers_size * sizeof(u32); @@ -68,7 +68,7 @@ void Recorder::Finish(const std::string& filename) { DEBUG_ASSERT(stream_element.extra_data.size() == 0); break; } - header.stream_offset += stream_element.extra_data.size(); + header.stream_offset += static_cast<u32>(stream_element.extra_data.size()); } try { diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt new file mode 100644 index 000000000..457c55571 --- /dev/null +++ b/src/tests/CMakeLists.txt @@ -0,0 +1,16 @@ +set(SRCS + tests.cpp + ) + +set(HEADERS + ) + +create_directory_groups(${SRCS} ${HEADERS}) + +include_directories(../../externals/catch/single_include/) + +add_executable(tests ${SRCS} ${HEADERS}) +target_link_libraries(tests core video_core audio_core common) +target_link_libraries(tests ${PLATFORM_LIBRARIES}) + +add_test(NAME tests COMMAND $<TARGET_FILE:tests>) diff --git a/src/tests/tests.cpp b/src/tests/tests.cpp new file mode 100644 index 000000000..73978676f --- /dev/null +++ b/src/tests/tests.cpp @@ -0,0 +1,9 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#define CATCH_CONFIG_MAIN +#include <catch.hpp> + +// Catch provides the main function since we've given it the +// CATCH_CONFIG_MAIN preprocessor directive. diff --git a/src/video_core/clipper.cpp b/src/video_core/clipper.cpp index 2bc747102..db99ce666 100644 --- a/src/video_core/clipper.cpp +++ b/src/video_core/clipper.cpp @@ -75,8 +75,6 @@ static void InitScreenCoordinates(OutputVertex& vtx) viewport.halfsize_y = float24::FromRaw(regs.viewport_size_y); viewport.offset_x = float24::FromFloat32(static_cast<float>(regs.viewport_corner.x)); viewport.offset_y = float24::FromFloat32(static_cast<float>(regs.viewport_corner.y)); - viewport.zscale = float24::FromRaw(regs.viewport_depth_range); - viewport.offset_z = float24::FromRaw(regs.viewport_depth_far_plane); float24 inv_w = float24::FromFloat32(1.f) / vtx.pos.w; vtx.color *= inv_w; @@ -89,7 +87,7 @@ static void InitScreenCoordinates(OutputVertex& vtx) vtx.screenpos[0] = (vtx.pos.x * inv_w + float24::FromFloat32(1.0)) * viewport.halfsize_x + viewport.offset_x; vtx.screenpos[1] = (vtx.pos.y * inv_w + float24::FromFloat32(1.0)) * viewport.halfsize_y + viewport.offset_y; - vtx.screenpos[2] = viewport.offset_z + vtx.pos.z * inv_w * viewport.zscale; + vtx.screenpos[2] = vtx.pos.z * inv_w; } void ProcessTriangle(const OutputVertex &v0, const OutputVertex &v1, const OutputVertex &v2) { diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index be1a936b2..bf4664f9e 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -128,7 +128,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { // TODO: Verify that this actually modifies the register! if (setup.index < 15) { - g_state.vs.default_attributes[setup.index] = attribute; + g_state.vs_default_attributes[setup.index] = attribute; setup.index++; } else { // Put each attribute into an immediate input buffer. @@ -144,13 +144,12 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { immediate_attribute_id = 0; Shader::UnitState<false> shader_unit; - Shader::Setup(); - - if (g_debug_context) - g_debug_context->OnEvent(DebugContext::Event::VertexLoaded, static_cast<void*>(&immediate_input)); + g_state.vs.Setup(); // Send to vertex shader - Shader::OutputVertex output = Shader::Run(shader_unit, immediate_input, regs.vs.num_input_attributes+1); + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, static_cast<void*>(&immediate_input)); + Shader::OutputVertex output = g_state.vs.Run(shader_unit, immediate_input, regs.vs.num_input_attributes+1); // Send to renderer using Pica::Shader::OutputVertex; @@ -200,9 +199,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { // Processes information about internal vertex attributes to figure out how a vertex is loaded. // Later, these can be compiled and cached. - VertexLoader loader; const u32 base_address = regs.vertex_attributes.GetPhysicalBaseAddress(); - loader.Setup(regs); + VertexLoader loader(regs); // Load vertices bool is_indexed = (id == PICA_REG_INDEX(trigger_draw_indexed)); @@ -238,7 +236,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { vertex_cache_ids.fill(-1); Shader::UnitState<false> shader_unit; - Shader::Setup(); + g_state.vs.Setup(); for (unsigned int index = 0; index < regs.num_vertices; ++index) { @@ -272,11 +270,10 @@ 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 - output = Shader::Run(shader_unit, input, loader.GetNumTotalAttributes()); + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, (void*)&input); + output = g_state.vs.Run(shader_unit, input, loader.GetNumTotalAttributes()); if (is_indexed) { vertex_cache[vertex_cache_pos] = output; diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index fb20f81dd..871368323 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -208,11 +208,12 @@ void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, c // TODO: Reduce the amount of binary code written to relevant portions dvlp.binary_offset = write_offset - dvlp_offset; - dvlp.binary_size_words = setup.program_code.size(); - QueueForWriting(reinterpret_cast<const u8*>(setup.program_code.data()), setup.program_code.size() * sizeof(u32)); + dvlp.binary_size_words = static_cast<uint32_t>(setup.program_code.size()); + QueueForWriting(reinterpret_cast<const u8*>(setup.program_code.data()), + static_cast<u32>(setup.program_code.size()) * sizeof(u32)); dvlp.swizzle_info_offset = write_offset - dvlp_offset; - dvlp.swizzle_info_num_entries = setup.swizzle_data.size(); + dvlp.swizzle_info_num_entries = static_cast<uint32_t>(setup.swizzle_data.size()); u32 dummy = 0; for (unsigned int i = 0; i < setup.swizzle_data.size(); ++i) { QueueForWriting(reinterpret_cast<const u8*>(&setup.swizzle_data[i]), sizeof(setup.swizzle_data[i])); @@ -264,7 +265,7 @@ void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, c constant_table.emplace_back(constant); } dvle.constant_table_offset = write_offset - dvlb.dvle_offset; - dvle.constant_table_size = constant_table.size(); + dvle.constant_table_size = static_cast<uint32_t>(constant_table.size()); for (const auto& constant : constant_table) { QueueForWriting(reinterpret_cast<const u8*>(&constant), sizeof(constant)); } @@ -695,106 +696,125 @@ finalise: #endif } -void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages) -{ +static std::string ReplacePattern(const std::string& input, const std::string& pattern, const std::string& replacement) { + size_t start = input.find(pattern); + if (start == std::string::npos) + return input; + + std::string ret = input; + ret.replace(start, pattern.length(), replacement); + return ret; +} + +static std::string GetTevStageConfigSourceString(const Pica::Regs::TevStageConfig::Source& source) { using Source = Pica::Regs::TevStageConfig::Source; + static const std::map<Source, std::string> source_map = { + { Source::PrimaryColor, "PrimaryColor" }, + { Source::PrimaryFragmentColor, "PrimaryFragmentColor" }, + { Source::SecondaryFragmentColor, "SecondaryFragmentColor" }, + { Source::Texture0, "Texture0" }, + { Source::Texture1, "Texture1" }, + { Source::Texture2, "Texture2" }, + { Source::Texture3, "Texture3" }, + { Source::PreviousBuffer, "PreviousBuffer" }, + { Source::Constant, "Constant" }, + { Source::Previous, "Previous" }, + }; + + const auto src_it = source_map.find(source); + if (src_it == source_map.end()) + return "Unknown"; + + return src_it->second; +} + +static std::string GetTevStageConfigColorSourceString(const Pica::Regs::TevStageConfig::Source& source, const Pica::Regs::TevStageConfig::ColorModifier modifier) { using ColorModifier = Pica::Regs::TevStageConfig::ColorModifier; + static const std::map<ColorModifier, std::string> color_modifier_map = { + { ColorModifier::SourceColor, "%source.rgb" }, + { ColorModifier::OneMinusSourceColor, "(1.0 - %source.rgb)" }, + { ColorModifier::SourceAlpha, "%source.aaa" }, + { ColorModifier::OneMinusSourceAlpha, "(1.0 - %source.aaa)" }, + { ColorModifier::SourceRed, "%source.rrr" }, + { ColorModifier::OneMinusSourceRed, "(1.0 - %source.rrr)" }, + { ColorModifier::SourceGreen, "%source.ggg" }, + { ColorModifier::OneMinusSourceGreen, "(1.0 - %source.ggg)" }, + { ColorModifier::SourceBlue, "%source.bbb" }, + { ColorModifier::OneMinusSourceBlue, "(1.0 - %source.bbb)" }, + }; + + auto src_str = GetTevStageConfigSourceString(source); + auto modifier_it = color_modifier_map.find(modifier); + std::string modifier_str = "%source.????"; + if (modifier_it != color_modifier_map.end()) + modifier_str = modifier_it->second; + + return ReplacePattern(modifier_str, "%source", src_str); +} + +static std::string GetTevStageConfigAlphaSourceString(const Pica::Regs::TevStageConfig::Source& source, const Pica::Regs::TevStageConfig::AlphaModifier modifier) { using AlphaModifier = Pica::Regs::TevStageConfig::AlphaModifier; + static const std::map<AlphaModifier, std::string> alpha_modifier_map = { + { AlphaModifier::SourceAlpha, "%source.a" }, + { AlphaModifier::OneMinusSourceAlpha, "(1.0 - %source.a)" }, + { AlphaModifier::SourceRed, "%source.r" }, + { AlphaModifier::OneMinusSourceRed, "(1.0 - %source.r)" }, + { AlphaModifier::SourceGreen, "%source.g" }, + { AlphaModifier::OneMinusSourceGreen, "(1.0 - %source.g)" }, + { AlphaModifier::SourceBlue, "%source.b" }, + { AlphaModifier::OneMinusSourceBlue, "(1.0 - %source.b)" }, + }; + + auto src_str = GetTevStageConfigSourceString(source); + auto modifier_it = alpha_modifier_map.find(modifier); + std::string modifier_str = "%source.????"; + if (modifier_it != alpha_modifier_map.end()) + modifier_str = modifier_it->second; + + return ReplacePattern(modifier_str, "%source", src_str); +} + +static std::string GetTevStageConfigOperationString(const Pica::Regs::TevStageConfig::Operation& operation) { using Operation = Pica::Regs::TevStageConfig::Operation; + static const std::map<Operation, std::string> combiner_map = { + { Operation::Replace, "%source1" }, + { Operation::Modulate, "(%source1 * %source2)" }, + { Operation::Add, "(%source1 + %source2)" }, + { Operation::AddSigned, "(%source1 + %source2) - 0.5" }, + { Operation::Lerp, "lerp(%source1, %source2, %source3)" }, + { Operation::Subtract, "(%source1 - %source2)" }, + { Operation::Dot3_RGB, "dot(%source1, %source2)" }, + { Operation::MultiplyThenAdd, "((%source1 * %source2) + %source3)" }, + { Operation::AddThenMultiply, "((%source1 + %source2) * %source3)" }, + }; - std::string stage_info = "Tev setup:\n"; - for (size_t index = 0; index < stages.size(); ++index) { - const auto& tev_stage = stages[index]; + const auto op_it = combiner_map.find(operation); + if (op_it == combiner_map.end()) + return "Unknown op (%source1, %source2, %source3)"; - static const std::map<Source, std::string> source_map = { - { Source::PrimaryColor, "PrimaryColor" }, - { Source::Texture0, "Texture0" }, - { Source::Texture1, "Texture1" }, - { Source::Texture2, "Texture2" }, - { Source::Constant, "Constant" }, - { Source::Previous, "Previous" }, - }; + return op_it->second; +} - static const std::map<ColorModifier, std::string> color_modifier_map = { - { ColorModifier::SourceColor, { "%source.rgb" } }, - { ColorModifier::SourceAlpha, { "%source.aaa" } }, - }; - static const std::map<AlphaModifier, std::string> alpha_modifier_map = { - { AlphaModifier::SourceAlpha, "%source.a" }, - { AlphaModifier::OneMinusSourceAlpha, "(255 - %source.a)" }, - }; +std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfig& tev_stage) { + auto op_str = GetTevStageConfigOperationString(tev_stage.color_op); + op_str = ReplacePattern(op_str, "%source1", GetTevStageConfigColorSourceString(tev_stage.color_source1, tev_stage.color_modifier1)); + op_str = ReplacePattern(op_str, "%source2", GetTevStageConfigColorSourceString(tev_stage.color_source2, tev_stage.color_modifier2)); + return ReplacePattern(op_str, "%source3", GetTevStageConfigColorSourceString(tev_stage.color_source3, tev_stage.color_modifier3)); +} - static const std::map<Operation, std::string> combiner_map = { - { Operation::Replace, "%source1" }, - { Operation::Modulate, "(%source1 * %source2) / 255" }, - { Operation::Add, "(%source1 + %source2)" }, - { Operation::Lerp, "lerp(%source1, %source2, %source3)" }, - }; +std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfig& tev_stage) { + auto op_str = GetTevStageConfigOperationString(tev_stage.alpha_op); + op_str = ReplacePattern(op_str, "%source1", GetTevStageConfigAlphaSourceString(tev_stage.alpha_source1, tev_stage.alpha_modifier1)); + op_str = ReplacePattern(op_str, "%source2", GetTevStageConfigAlphaSourceString(tev_stage.alpha_source2, tev_stage.alpha_modifier2)); + return ReplacePattern(op_str, "%source3", GetTevStageConfigAlphaSourceString(tev_stage.alpha_source3, tev_stage.alpha_modifier3)); +} - static auto ReplacePattern = - [](const std::string& input, const std::string& pattern, const std::string& replacement) -> std::string { - size_t start = input.find(pattern); - if (start == std::string::npos) - return input; - - std::string ret = input; - ret.replace(start, pattern.length(), replacement); - return ret; - }; - static auto GetColorSourceStr = - [](const Source& src, const ColorModifier& modifier) { - auto src_it = source_map.find(src); - std::string src_str = "Unknown"; - if (src_it != source_map.end()) - src_str = src_it->second; - - auto modifier_it = color_modifier_map.find(modifier); - std::string modifier_str = "%source.????"; - if (modifier_it != color_modifier_map.end()) - modifier_str = modifier_it->second; - - return ReplacePattern(modifier_str, "%source", src_str); - }; - static auto GetColorCombinerStr = - [](const Regs::TevStageConfig& tev_stage) { - auto op_it = combiner_map.find(tev_stage.color_op); - std::string op_str = "Unknown op (%source1, %source2, %source3)"; - if (op_it != combiner_map.end()) - op_str = op_it->second; - - op_str = ReplacePattern(op_str, "%source1", GetColorSourceStr(tev_stage.color_source1, tev_stage.color_modifier1)); - op_str = ReplacePattern(op_str, "%source2", GetColorSourceStr(tev_stage.color_source2, tev_stage.color_modifier2)); - return ReplacePattern(op_str, "%source3", GetColorSourceStr(tev_stage.color_source3, tev_stage.color_modifier3)); - }; - static auto GetAlphaSourceStr = - [](const Source& src, const AlphaModifier& modifier) { - auto src_it = source_map.find(src); - std::string src_str = "Unknown"; - if (src_it != source_map.end()) - src_str = src_it->second; - - auto modifier_it = alpha_modifier_map.find(modifier); - std::string modifier_str = "%source.????"; - if (modifier_it != alpha_modifier_map.end()) - modifier_str = modifier_it->second; - - return ReplacePattern(modifier_str, "%source", src_str); - }; - static auto GetAlphaCombinerStr = - [](const Regs::TevStageConfig& tev_stage) { - auto op_it = combiner_map.find(tev_stage.alpha_op); - std::string op_str = "Unknown op (%source1, %source2, %source3)"; - if (op_it != combiner_map.end()) - op_str = op_it->second; - - op_str = ReplacePattern(op_str, "%source1", GetAlphaSourceStr(tev_stage.alpha_source1, tev_stage.alpha_modifier1)); - op_str = ReplacePattern(op_str, "%source2", GetAlphaSourceStr(tev_stage.alpha_source2, tev_stage.alpha_modifier2)); - return ReplacePattern(op_str, "%source3", GetAlphaSourceStr(tev_stage.alpha_source3, tev_stage.alpha_modifier3)); - }; - - stage_info += "Stage " + std::to_string(index) + ": " + GetColorCombinerStr(tev_stage) + " " + GetAlphaCombinerStr(tev_stage) + "\n"; +void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig, 6>& stages) { + std::string stage_info = "Tev setup:\n"; + for (size_t index = 0; index < stages.size(); ++index) { + const auto& tev_stage = stages[index]; + stage_info += "Stage " + std::to_string(index) + ": " + GetTevStageConfigColorCombinerString(tev_stage) + " " + GetTevStageConfigAlphaCombinerString(tev_stage) + "\n"; } - LOG_TRACE(HW_GPU, "%s", stage_info.c_str()); } diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index be2d0301a..92e9734ae 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, @@ -224,7 +224,11 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int s, int t, const Texture void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data); -void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages); +std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfig& tev_stage); +std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfig& tev_stage); + +/// Dumps the Tev stage config to log at trace level +void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig, 6>& stages); /** * Used in the vertex loader to merge access records. TODO: Investigate if actually useful. diff --git a/src/video_core/pica.cpp b/src/video_core/pica.cpp index be82cf4b5..ec78f9593 100644 --- a/src/video_core/pica.cpp +++ b/src/video_core/pica.cpp @@ -500,7 +500,7 @@ void Init() { } void Shutdown() { - Shader::Shutdown(); + Shader::ClearCache(); } template <typename T> diff --git a/src/video_core/pica.h b/src/video_core/pica.h index 5891fb72a..544ea037f 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -70,7 +70,7 @@ struct Regs { INSERT_PADDING_WORDS(0x9); BitField<0, 24, u32> viewport_depth_range; // float24 - BitField<0, 24, u32> viewport_depth_far_plane; // float24 + BitField<0, 24, u32> viewport_depth_near_plane; // float24 BitField<0, 3, u32> vs_output_total; @@ -122,9 +122,31 @@ struct Regs { BitField<16, 10, s32> y; } viewport_corner; - INSERT_PADDING_WORDS(0x17); + INSERT_PADDING_WORDS(0x1); + + //TODO: early depth + INSERT_PADDING_WORDS(0x1); + + INSERT_PADDING_WORDS(0x2); + + enum DepthBuffering : u32 { + WBuffering = 0, + ZBuffering = 1, + }; + BitField< 0, 1, DepthBuffering> depthmap_enable; + + INSERT_PADDING_WORDS(0x12); struct TextureConfig { + enum TextureType : u32 { + Texture2D = 0, + TextureCube = 1, + Shadow2D = 2, + Projection2D = 3, + ShadowCube = 4, + Disabled = 5, + }; + enum WrapMode : u32 { ClampToEdge = 0, ClampToBorder = 1, @@ -155,6 +177,7 @@ struct Regs { BitField< 2, 1, TextureFilter> min_filter; BitField< 8, 2, WrapMode> wrap_t; BitField<12, 2, WrapMode> wrap_s; + BitField<28, 2, TextureType> type; ///< @note Only valid for texture 0 according to 3DBrew. }; INSERT_PADDING_WORDS(0x1); @@ -764,23 +787,21 @@ struct Regs { LightColor diffuse; // material.diffuse * light.diffuse LightColor ambient; // material.ambient * light.ambient - struct { - // Encoded as 16-bit floating point - union { - BitField< 0, 16, u32> x; - BitField<16, 16, u32> y; - }; - union { - BitField< 0, 16, u32> z; - }; + // Encoded as 16-bit floating point + union { + BitField< 0, 16, u32> x; + BitField<16, 16, u32> y; + }; + union { + BitField< 0, 16, u32> z; + }; - INSERT_PADDING_WORDS(0x3); + INSERT_PADDING_WORDS(0x3); - union { - BitField<0, 1, u32> directional; - BitField<1, 1, u32> two_sided_diffuse; // When disabled, clamp dot-product to 0 - }; - }; + union { + BitField<0, 1, u32> directional; + BitField<1, 1, u32> two_sided_diffuse; // When disabled, clamp dot-product to 0 + } config; BitField<0, 20, u32> dist_atten_bias; BitField<0, 20, u32> dist_atten_scale; @@ -801,7 +822,7 @@ struct Regs { BitField<27, 1, u32> clamp_highlights; BitField<28, 2, LightingBumpMode> bump_mode; BitField<30, 1, u32> disable_bump_renorm; - }; + } config0; union { BitField<16, 1, u32> disable_lut_d0; @@ -822,13 +843,13 @@ struct Regs { BitField<29, 1, u32> disable_dist_atten_light_5; BitField<30, 1, u32> disable_dist_atten_light_6; BitField<31, 1, u32> disable_dist_atten_light_7; - }; + } config1; bool IsDistAttenDisabled(unsigned index) const { - const unsigned disable[] = { disable_dist_atten_light_0, disable_dist_atten_light_1, - disable_dist_atten_light_2, disable_dist_atten_light_3, - disable_dist_atten_light_4, disable_dist_atten_light_5, - disable_dist_atten_light_6, disable_dist_atten_light_7 }; + const unsigned disable[] = { config1.disable_dist_atten_light_0, config1.disable_dist_atten_light_1, + config1.disable_dist_atten_light_2, config1.disable_dist_atten_light_3, + config1.disable_dist_atten_light_4, config1.disable_dist_atten_light_5, + config1.disable_dist_atten_light_6, config1.disable_dist_atten_light_7 }; return disable[index] != 0; } @@ -1279,10 +1300,11 @@ ASSERT_REG_POSITION(cull_mode, 0x40); ASSERT_REG_POSITION(viewport_size_x, 0x41); ASSERT_REG_POSITION(viewport_size_y, 0x43); ASSERT_REG_POSITION(viewport_depth_range, 0x4d); -ASSERT_REG_POSITION(viewport_depth_far_plane, 0x4e); +ASSERT_REG_POSITION(viewport_depth_near_plane, 0x4e); ASSERT_REG_POSITION(vs_output_attributes[0], 0x50); ASSERT_REG_POSITION(vs_output_attributes[1], 0x51); ASSERT_REG_POSITION(viewport_corner, 0x68); +ASSERT_REG_POSITION(depthmap_enable, 0x6D); ASSERT_REG_POSITION(texture0_enable, 0x80); ASSERT_REG_POSITION(texture0, 0x81); ASSERT_REG_POSITION(texture0_format, 0x8e); diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h index bbecad850..495174c25 100644 --- a/src/video_core/pica_state.h +++ b/src/video_core/pica_state.h @@ -25,6 +25,8 @@ struct State { Shader::ShaderSetup vs; Shader::ShaderSetup gs; + std::array<Math::Vec4<float24>, 16> vs_default_attributes; + struct { union LutEntry { // Used for raw access @@ -56,7 +58,7 @@ struct State { // Used to buffer partial vertices for immediate-mode rendering. Shader::InputVertex input_vertex; // Index of the next attribute to be loaded into `input_vertex`. - int current_attribute = 0; + u32 current_attribute = 0; } immediate; // This is constructed with a dummy triangle topology diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp index df67b9081..65168f05a 100644 --- a/src/video_core/rasterizer.cpp +++ b/src/video_core/rasterizer.cpp @@ -442,8 +442,33 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, DEBUG_ASSERT(0 != texture.config.address); - int s = (int)(uv[i].u() * float24::FromFloat32(static_cast<float>(texture.config.width))).ToFloat32(); - int t = (int)(uv[i].v() * float24::FromFloat32(static_cast<float>(texture.config.height))).ToFloat32(); + float24 u = uv[i].u(); + float24 v = uv[i].v(); + + // Only unit 0 respects the texturing type (according to 3DBrew) + // TODO: Refactor so cubemaps and shadowmaps can be handled + if (i == 0) { + switch(texture.config.type) { + case Regs::TextureConfig::Texture2D: + break; + case Regs::TextureConfig::Projection2D: { + auto tc0_w = GetInterpolatedAttribute(v0.tc0_w, v1.tc0_w, v2.tc0_w); + u /= tc0_w; + v /= tc0_w; + break; + } + default: + // TODO: Change to LOG_ERROR when more types are handled. + LOG_DEBUG(HW_GPU, "Unhandled texture type %x", (int)texture.config.type); + UNIMPLEMENTED(); + break; + } + } + + int s = (int)(u * float24::FromFloat32(static_cast<float>(texture.config.width))).ToFloat32(); + int t = (int)(v * float24::FromFloat32(static_cast<float>(texture.config.height))).ToFloat32(); + + static auto GetWrappedTexCoord = [](Regs::TextureConfig::WrapMode mode, int val, unsigned size) { switch (mode) { case Regs::TextureConfig::ClampToEdge: @@ -862,10 +887,30 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, } } + // interpolated_z = z / w + float interpolated_z_over_w = (v0.screenpos[2].ToFloat32() * w0 + + v1.screenpos[2].ToFloat32() * w1 + + v2.screenpos[2].ToFloat32() * w2) / wsum; + + // Not fully accurate. About 3 bits in precision are missing. + // Z-Buffer (z / w * scale + offset) + float depth_scale = float24::FromRaw(regs.viewport_depth_range).ToFloat32(); + float depth_offset = float24::FromRaw(regs.viewport_depth_near_plane).ToFloat32(); + float depth = interpolated_z_over_w * depth_scale + depth_offset; + + // Potentially switch to W-Buffer + if (regs.depthmap_enable == Pica::Regs::DepthBuffering::WBuffering) { + + // W-Buffer (z * scale + w * offset = (z / w * scale + offset) * w) + depth *= interpolated_w_inverse.ToFloat32() * wsum; + } + + // Clamp the result + depth = MathUtil::Clamp(depth, 0.0f, 1.0f); + + // Convert float to integer unsigned num_bits = Regs::DepthBitsPerPixel(regs.framebuffer.depth_format); - u32 z = (u32)((v0.screenpos[2].ToFloat32() * w0 + - v1.screenpos[2].ToFloat32() * w1 + - v2.screenpos[2].ToFloat32() * w2) * ((1 << num_bits) - 1) / wsum); + u32 z = (u32)(depth * ((1 << num_bits) - 1)); if (output_merger.depth_test_enable) { u32 ref_z = GetDepth(x >> 4, y >> 4); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 519d81aeb..931c34a37 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -76,6 +76,9 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORD1); glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORD2); + glVertexAttribPointer(GLShader::ATTRIBUTE_TEXCOORD0_W, 1, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord0_w)); + glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORD0_W); + glVertexAttribPointer(GLShader::ATTRIBUTE_NORMQUAT, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, normquat)); glEnableVertexAttribArray(GLShader::ATTRIBUTE_NORMQUAT); @@ -93,7 +96,7 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { state.Apply(); for (size_t i = 0; i < lighting_luts.size(); ++i) { - glActiveTexture(GL_TEXTURE3 + i); + glActiveTexture(static_cast<GLenum>(GL_TEXTURE3 + i)); glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, nullptr); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -101,7 +104,6 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { // Sync fixed function OpenGL state SyncCullMode(); - SyncDepthModifiers(); SyncBlendEnabled(); SyncBlendFuncs(); SyncBlendColor(); @@ -256,8 +258,15 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { // Depth modifiers case PICA_REG_INDEX(viewport_depth_range): - case PICA_REG_INDEX(viewport_depth_far_plane): - SyncDepthModifiers(); + SyncDepthScale(); + break; + case PICA_REG_INDEX(viewport_depth_near_plane): + SyncDepthOffset(); + break; + + // Depth buffering + case PICA_REG_INDEX(depthmap_enable): + shader_dirty = true; break; // Blending @@ -314,6 +323,11 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { SyncLogicOp(); break; + // Texture 0 type + case PICA_REG_INDEX(texture0.type): + shader_dirty = true; + break; + // TEV stages case PICA_REG_INDEX(tev_stage0.color_source1): case PICA_REG_INDEX(tev_stage0.color_modifier1): @@ -366,6 +380,17 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { SyncCombinerColor(); break; + // Fragment lighting switches + case PICA_REG_INDEX(lighting.disable): + case PICA_REG_INDEX(lighting.num_lights): + case PICA_REG_INDEX(lighting.config0): + case PICA_REG_INDEX(lighting.config1): + case PICA_REG_INDEX(lighting.abs_lut_input): + case PICA_REG_INDEX(lighting.lut_input): + case PICA_REG_INDEX(lighting.lut_scale): + case PICA_REG_INDEX(lighting.light_enable): + break; + // Fragment lighting specular 0 color case PICA_REG_INDEX_WORKAROUND(lighting.light[0].specular_0, 0x140 + 0 * 0x10): SyncLightSpecular0(0); @@ -504,6 +529,70 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { SyncLightPosition(7); break; + // Fragment lighting light source config + case PICA_REG_INDEX_WORKAROUND(lighting.light[0].config, 0x149 + 0 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[1].config, 0x149 + 1 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[2].config, 0x149 + 2 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[3].config, 0x149 + 3 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[4].config, 0x149 + 4 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[5].config, 0x149 + 5 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[6].config, 0x149 + 6 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[7].config, 0x149 + 7 * 0x10): + shader_dirty = true; + break; + + // Fragment lighting distance attenuation bias + case PICA_REG_INDEX_WORKAROUND(lighting.light[0].dist_atten_bias, 0x014A + 0 * 0x10): + SyncLightDistanceAttenuationBias(0); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[1].dist_atten_bias, 0x014A + 1 * 0x10): + SyncLightDistanceAttenuationBias(1); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[2].dist_atten_bias, 0x014A + 2 * 0x10): + SyncLightDistanceAttenuationBias(2); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[3].dist_atten_bias, 0x014A + 3 * 0x10): + SyncLightDistanceAttenuationBias(3); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[4].dist_atten_bias, 0x014A + 4 * 0x10): + SyncLightDistanceAttenuationBias(4); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[5].dist_atten_bias, 0x014A + 5 * 0x10): + SyncLightDistanceAttenuationBias(5); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[6].dist_atten_bias, 0x014A + 6 * 0x10): + SyncLightDistanceAttenuationBias(6); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[7].dist_atten_bias, 0x014A + 7 * 0x10): + SyncLightDistanceAttenuationBias(7); + break; + + // Fragment lighting distance attenuation scale + case PICA_REG_INDEX_WORKAROUND(lighting.light[0].dist_atten_scale, 0x014B + 0 * 0x10): + SyncLightDistanceAttenuationScale(0); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[1].dist_atten_scale, 0x014B + 1 * 0x10): + SyncLightDistanceAttenuationScale(1); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[2].dist_atten_scale, 0x014B + 2 * 0x10): + SyncLightDistanceAttenuationScale(2); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[3].dist_atten_scale, 0x014B + 3 * 0x10): + SyncLightDistanceAttenuationScale(3); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[4].dist_atten_scale, 0x014B + 4 * 0x10): + SyncLightDistanceAttenuationScale(4); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[5].dist_atten_scale, 0x014B + 5 * 0x10): + SyncLightDistanceAttenuationScale(5); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[6].dist_atten_scale, 0x014B + 6 * 0x10): + SyncLightDistanceAttenuationScale(6); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[7].dist_atten_scale, 0x014B + 7 * 0x10): + SyncLightDistanceAttenuationScale(7); + break; + // Fragment lighting global ambient color (emission + ambient * ambient) case PICA_REG_INDEX_WORKAROUND(lighting.global_ambient, 0x1c0): SyncGlobalAmbient(); @@ -867,6 +956,8 @@ void RasterizerOpenGL::SetShader() { glUniformBlockBinding(current_shader->shader.handle, block_index, 0); // Update uniforms + SyncDepthScale(); + SyncDepthOffset(); SyncAlphaTest(); SyncCombinerColor(); auto& tev_stages = Pica::g_state.regs.GetTevStages(); @@ -880,6 +971,8 @@ void RasterizerOpenGL::SetShader() { SyncLightDiffuse(light_index); SyncLightAmbient(light_index); SyncLightPosition(light_index); + SyncLightDistanceAttenuationBias(light_index); + SyncLightDistanceAttenuationScale(light_index); } } } @@ -909,13 +1002,20 @@ void RasterizerOpenGL::SyncCullMode() { } } -void RasterizerOpenGL::SyncDepthModifiers() { - float depth_scale = -Pica::float24::FromRaw(Pica::g_state.regs.viewport_depth_range).ToFloat32(); - float depth_offset = Pica::float24::FromRaw(Pica::g_state.regs.viewport_depth_far_plane).ToFloat32() / 2.0f; +void RasterizerOpenGL::SyncDepthScale() { + float depth_scale = Pica::float24::FromRaw(Pica::g_state.regs.viewport_depth_range).ToFloat32(); + if (depth_scale != uniform_block_data.data.depth_scale) { + uniform_block_data.data.depth_scale = depth_scale; + uniform_block_data.dirty = true; + } +} - // TODO: Implement scale modifier - uniform_block_data.data.depth_offset = depth_offset; - uniform_block_data.dirty = true; +void RasterizerOpenGL::SyncDepthOffset() { + float depth_offset = Pica::float24::FromRaw(Pica::g_state.regs.viewport_depth_near_plane).ToFloat32(); + if (depth_offset != uniform_block_data.data.depth_offset) { + uniform_block_data.data.depth_offset = depth_offset; + uniform_block_data.dirty = true; + } } void RasterizerOpenGL::SyncBlendEnabled() { @@ -924,6 +1024,8 @@ void RasterizerOpenGL::SyncBlendEnabled() { void RasterizerOpenGL::SyncBlendFuncs() { const auto& regs = Pica::g_state.regs; + state.blend.rgb_equation = PicaToGL::BlendEquation(regs.output_merger.alpha_blending.blend_equation_rgb); + state.blend.a_equation = PicaToGL::BlendEquation(regs.output_merger.alpha_blending.blend_equation_a); state.blend.src_rgb_func = PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_source_rgb); state.blend.dst_rgb_func = PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_dest_rgb); state.blend.src_a_func = PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_source_a); @@ -1080,3 +1182,21 @@ void RasterizerOpenGL::SyncLightPosition(int light_index) { uniform_block_data.dirty = true; } } + +void RasterizerOpenGL::SyncLightDistanceAttenuationBias(int light_index) { + GLfloat dist_atten_bias = Pica::float20::FromRaw(Pica::g_state.regs.lighting.light[light_index].dist_atten_bias).ToFloat32(); + + if (dist_atten_bias != uniform_block_data.data.light_src[light_index].dist_atten_bias) { + uniform_block_data.data.light_src[light_index].dist_atten_bias = dist_atten_bias; + uniform_block_data.dirty = true; + } +} + +void RasterizerOpenGL::SyncLightDistanceAttenuationScale(int light_index) { + GLfloat dist_atten_scale = Pica::float20::FromRaw(Pica::g_state.regs.lighting.light[light_index].dist_atten_scale).ToFloat32(); + + if (dist_atten_scale != uniform_block_data.data.light_src[light_index].dist_atten_scale) { + uniform_block_data.data.light_src[light_index].dist_atten_scale = dist_atten_scale; + uniform_block_data.dirty = true; + } +} diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 82fa61742..bb7f20161 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -39,140 +39,181 @@ struct ScreenInfo; * directly accessing Pica registers. This should reduce the risk of bugs in shader generation where * Pica state is not being captured in the shader cache key, thereby resulting in (what should be) * two separate shaders sharing the same key. + * + * We use a union because "implicitly-defined copy/move constructor for a union X copies the object representation of X." + * and "implicitly-defined copy assignment operator for a union X copies the object representation (3.9) of X." + * = Bytewise copy instead of memberwise copy. + * This is important because the padding bytes are included in the hash and comparison between objects. */ -struct PicaShaderConfig { +union PicaShaderConfig { + /// Construct a PicaShaderConfig with the current Pica register configuration. static PicaShaderConfig CurrentConfig() { PicaShaderConfig res; + + auto& state = res.state; + std::memset(&state, 0, sizeof(PicaShaderConfig::State)); + const auto& regs = Pica::g_state.regs; - res.alpha_test_func = regs.output_merger.alpha_test.enable ? + state.depthmap_enable = regs.depthmap_enable; + + state.alpha_test_func = regs.output_merger.alpha_test.enable ? regs.output_merger.alpha_test.func.Value() : Pica::Regs::CompareFunc::Always; + state.texture0_type = regs.texture0.type; + // Copy relevant tev stages fields. // We don't sync const_color here because of the high variance, it is a // shader uniform instead. const auto& tev_stages = regs.GetTevStages(); - DEBUG_ASSERT(res.tev_stages.size() == tev_stages.size()); + DEBUG_ASSERT(state.tev_stages.size() == tev_stages.size()); for (size_t i = 0; i < tev_stages.size(); i++) { const auto& tev_stage = tev_stages[i]; - res.tev_stages[i].sources_raw = tev_stage.sources_raw; - res.tev_stages[i].modifiers_raw = tev_stage.modifiers_raw; - res.tev_stages[i].ops_raw = tev_stage.ops_raw; - res.tev_stages[i].scales_raw = tev_stage.scales_raw; + state.tev_stages[i].sources_raw = tev_stage.sources_raw; + state.tev_stages[i].modifiers_raw = tev_stage.modifiers_raw; + state.tev_stages[i].ops_raw = tev_stage.ops_raw; + state.tev_stages[i].scales_raw = tev_stage.scales_raw; } - res.combiner_buffer_input = + state.combiner_buffer_input = regs.tev_combiner_buffer_input.update_mask_rgb.Value() | regs.tev_combiner_buffer_input.update_mask_a.Value() << 4; // Fragment lighting - res.lighting.enable = !regs.lighting.disable; - res.lighting.src_num = regs.lighting.num_lights + 1; + state.lighting.enable = !regs.lighting.disable; + state.lighting.src_num = regs.lighting.num_lights + 1; - for (unsigned light_index = 0; light_index < res.lighting.src_num; ++light_index) { + for (unsigned light_index = 0; light_index < state.lighting.src_num; ++light_index) { unsigned num = regs.lighting.light_enable.GetNum(light_index); const auto& light = regs.lighting.light[num]; - res.lighting.light[light_index].num = num; - res.lighting.light[light_index].directional = light.directional != 0; - res.lighting.light[light_index].two_sided_diffuse = light.two_sided_diffuse != 0; - res.lighting.light[light_index].dist_atten_enable = !regs.lighting.IsDistAttenDisabled(num); - res.lighting.light[light_index].dist_atten_bias = Pica::float20::FromRaw(light.dist_atten_bias).ToFloat32(); - res.lighting.light[light_index].dist_atten_scale = Pica::float20::FromRaw(light.dist_atten_scale).ToFloat32(); + state.lighting.light[light_index].num = num; + state.lighting.light[light_index].directional = light.config.directional != 0; + state.lighting.light[light_index].two_sided_diffuse = light.config.two_sided_diffuse != 0; + state.lighting.light[light_index].dist_atten_enable = !regs.lighting.IsDistAttenDisabled(num); } - res.lighting.lut_d0.enable = regs.lighting.disable_lut_d0 == 0; - res.lighting.lut_d0.abs_input = regs.lighting.abs_lut_input.disable_d0 == 0; - res.lighting.lut_d0.type = regs.lighting.lut_input.d0.Value(); - res.lighting.lut_d0.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d0); - - res.lighting.lut_d1.enable = regs.lighting.disable_lut_d1 == 0; - res.lighting.lut_d1.abs_input = regs.lighting.abs_lut_input.disable_d1 == 0; - res.lighting.lut_d1.type = regs.lighting.lut_input.d1.Value(); - res.lighting.lut_d1.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d1); - - res.lighting.lut_fr.enable = regs.lighting.disable_lut_fr == 0; - res.lighting.lut_fr.abs_input = regs.lighting.abs_lut_input.disable_fr == 0; - res.lighting.lut_fr.type = regs.lighting.lut_input.fr.Value(); - res.lighting.lut_fr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.fr); - - res.lighting.lut_rr.enable = regs.lighting.disable_lut_rr == 0; - res.lighting.lut_rr.abs_input = regs.lighting.abs_lut_input.disable_rr == 0; - res.lighting.lut_rr.type = regs.lighting.lut_input.rr.Value(); - res.lighting.lut_rr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rr); - - res.lighting.lut_rg.enable = regs.lighting.disable_lut_rg == 0; - res.lighting.lut_rg.abs_input = regs.lighting.abs_lut_input.disable_rg == 0; - res.lighting.lut_rg.type = regs.lighting.lut_input.rg.Value(); - res.lighting.lut_rg.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rg); - - res.lighting.lut_rb.enable = regs.lighting.disable_lut_rb == 0; - res.lighting.lut_rb.abs_input = regs.lighting.abs_lut_input.disable_rb == 0; - res.lighting.lut_rb.type = regs.lighting.lut_input.rb.Value(); - res.lighting.lut_rb.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rb); - - res.lighting.config = regs.lighting.config; - res.lighting.fresnel_selector = regs.lighting.fresnel_selector; - res.lighting.bump_mode = regs.lighting.bump_mode; - res.lighting.bump_selector = regs.lighting.bump_selector; - res.lighting.bump_renorm = regs.lighting.disable_bump_renorm == 0; - res.lighting.clamp_highlights = regs.lighting.clamp_highlights != 0; + state.lighting.lut_d0.enable = regs.lighting.config1.disable_lut_d0 == 0; + state.lighting.lut_d0.abs_input = regs.lighting.abs_lut_input.disable_d0 == 0; + state.lighting.lut_d0.type = regs.lighting.lut_input.d0.Value(); + state.lighting.lut_d0.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d0); + + state.lighting.lut_d1.enable = regs.lighting.config1.disable_lut_d1 == 0; + state.lighting.lut_d1.abs_input = regs.lighting.abs_lut_input.disable_d1 == 0; + state.lighting.lut_d1.type = regs.lighting.lut_input.d1.Value(); + state.lighting.lut_d1.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d1); + + state.lighting.lut_fr.enable = regs.lighting.config1.disable_lut_fr == 0; + state.lighting.lut_fr.abs_input = regs.lighting.abs_lut_input.disable_fr == 0; + state.lighting.lut_fr.type = regs.lighting.lut_input.fr.Value(); + state.lighting.lut_fr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.fr); + + state.lighting.lut_rr.enable = regs.lighting.config1.disable_lut_rr == 0; + state.lighting.lut_rr.abs_input = regs.lighting.abs_lut_input.disable_rr == 0; + state.lighting.lut_rr.type = regs.lighting.lut_input.rr.Value(); + state.lighting.lut_rr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rr); + + state.lighting.lut_rg.enable = regs.lighting.config1.disable_lut_rg == 0; + state.lighting.lut_rg.abs_input = regs.lighting.abs_lut_input.disable_rg == 0; + state.lighting.lut_rg.type = regs.lighting.lut_input.rg.Value(); + state.lighting.lut_rg.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rg); + + state.lighting.lut_rb.enable = regs.lighting.config1.disable_lut_rb == 0; + state.lighting.lut_rb.abs_input = regs.lighting.abs_lut_input.disable_rb == 0; + state.lighting.lut_rb.type = regs.lighting.lut_input.rb.Value(); + state.lighting.lut_rb.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rb); + + state.lighting.config = regs.lighting.config0.config; + state.lighting.fresnel_selector = regs.lighting.config0.fresnel_selector; + state.lighting.bump_mode = regs.lighting.config0.bump_mode; + state.lighting.bump_selector = regs.lighting.config0.bump_selector; + state.lighting.bump_renorm = regs.lighting.config0.disable_bump_renorm == 0; + state.lighting.clamp_highlights = regs.lighting.config0.clamp_highlights != 0; return res; } bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const { - return (stage_index < 4) && (combiner_buffer_input & (1 << stage_index)); + return (stage_index < 4) && (state.combiner_buffer_input & (1 << stage_index)); } bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const { - return (stage_index < 4) && ((combiner_buffer_input >> 4) & (1 << stage_index)); + return (stage_index < 4) && ((state.combiner_buffer_input >> 4) & (1 << stage_index)); } bool operator ==(const PicaShaderConfig& o) const { - return std::memcmp(this, &o, sizeof(PicaShaderConfig)) == 0; + return std::memcmp(&state, &o.state, sizeof(PicaShaderConfig::State)) == 0; + }; + + // NOTE: MSVC15 (Update 2) doesn't think `delete`'d constructors and operators are TC. + // This makes BitField not TC when used in a union or struct so we have to resort + // to this ugly hack. + // Once that bug is fixed we can use Pica::Regs::TevStageConfig here. + // Doesn't include const_color because we don't sync it, see comment in CurrentConfig() + struct TevStageConfigRaw { + u32 sources_raw; + u32 modifiers_raw; + u32 ops_raw; + u32 scales_raw; + explicit operator Pica::Regs::TevStageConfig() const noexcept { + Pica::Regs::TevStageConfig stage; + stage.sources_raw = sources_raw; + stage.modifiers_raw = modifiers_raw; + stage.ops_raw = ops_raw; + stage.const_color = 0; + stage.scales_raw = scales_raw; + return stage; + } }; - Pica::Regs::CompareFunc alpha_test_func = Pica::Regs::CompareFunc::Never; - std::array<Pica::Regs::TevStageConfig, 6> tev_stages = {}; - u8 combiner_buffer_input = 0; + struct State { - struct { - struct { - unsigned num = 0; - bool directional = false; - bool two_sided_diffuse = false; - bool dist_atten_enable = false; - GLfloat dist_atten_scale = 0.0f; - GLfloat dist_atten_bias = 0.0f; - } light[8]; - - bool enable = false; - unsigned src_num = 0; - Pica::Regs::LightingBumpMode bump_mode = Pica::Regs::LightingBumpMode::None; - unsigned bump_selector = 0; - bool bump_renorm = false; - bool clamp_highlights = false; - - Pica::Regs::LightingConfig config = Pica::Regs::LightingConfig::Config0; - Pica::Regs::LightingFresnelSelector fresnel_selector = Pica::Regs::LightingFresnelSelector::None; + Pica::Regs::CompareFunc alpha_test_func; + Pica::Regs::TextureConfig::TextureType texture0_type; + std::array<TevStageConfigRaw, 6> tev_stages; + u8 combiner_buffer_input; + + Pica::Regs::DepthBuffering depthmap_enable; struct { - bool enable = false; - bool abs_input = false; - Pica::Regs::LightingLutInput type = Pica::Regs::LightingLutInput::NH; - float scale = 1.0f; - } lut_d0, lut_d1, lut_fr, lut_rr, lut_rg, lut_rb; - } lighting; + struct { + unsigned num; + bool directional; + bool two_sided_diffuse; + bool dist_atten_enable; + } light[8]; + + bool enable; + unsigned src_num; + Pica::Regs::LightingBumpMode bump_mode; + unsigned bump_selector; + bool bump_renorm; + bool clamp_highlights; + + Pica::Regs::LightingConfig config; + Pica::Regs::LightingFresnelSelector fresnel_selector; + + struct { + bool enable; + bool abs_input; + Pica::Regs::LightingLutInput type; + float scale; + } lut_d0, lut_d1, lut_fr, lut_rr, lut_rg, lut_rb; + } lighting; + + } state; }; +#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER) +static_assert(std::is_trivially_copyable<PicaShaderConfig::State>::value, "PicaShaderConfig::State must be trivially copyable"); +#endif namespace std { template <> struct hash<PicaShaderConfig> { size_t operator()(const PicaShaderConfig& k) const { - return Common::ComputeHash64(&k, sizeof(PicaShaderConfig)); + return Common::ComputeHash64(&k.state, sizeof(PicaShaderConfig::State)); } }; @@ -239,6 +280,7 @@ private: tex_coord1[1] = v.tc1.y.ToFloat32(); tex_coord2[0] = v.tc2.x.ToFloat32(); tex_coord2[1] = v.tc2.y.ToFloat32(); + tex_coord0_w = v.tc0_w.ToFloat32(); normquat[0] = v.quat.x.ToFloat32(); normquat[1] = v.quat.y.ToFloat32(); normquat[2] = v.quat.z.ToFloat32(); @@ -259,6 +301,7 @@ private: GLfloat tex_coord0[2]; GLfloat tex_coord1[2]; GLfloat tex_coord2[2]; + GLfloat tex_coord0_w; GLfloat normquat[4]; GLfloat view[3]; }; @@ -269,6 +312,8 @@ private: alignas(16) GLvec3 diffuse; alignas(16) GLvec3 ambient; alignas(16) GLvec3 position; + GLfloat dist_atten_bias; + GLfloat dist_atten_scale; }; /// Uniform structure for the Uniform Buffer Object, all members must be 16-byte aligned @@ -277,12 +322,13 @@ private: GLvec4 const_color[6]; GLvec4 tev_combiner_buffer_color; GLint alphatest_ref; + GLfloat depth_scale; GLfloat depth_offset; alignas(16) GLvec3 lighting_global_ambient; LightSrc light_src[8]; }; - static_assert(sizeof(UniformData) == 0x310, "The size of the UniformData structure has changed, update the structure in the shader"); + static_assert(sizeof(UniformData) == 0x390, "The size of the UniformData structure has changed, update the structure in the shader"); static_assert(sizeof(UniformData) < 16384, "UniformData structure must be less than 16kb as per the OpenGL spec"); /// Sets the OpenGL shader in accordance with the current PICA register state @@ -291,8 +337,11 @@ private: /// Syncs the cull mode to match the PICA register void SyncCullMode(); - /// Syncs the depth scale and offset to match the PICA registers - void SyncDepthModifiers(); + /// Syncs the depth scale to match the PICA register + void SyncDepthScale(); + + /// Syncs the depth offset to match the PICA register + void SyncDepthOffset(); /// Syncs the blend enabled status to match the PICA register void SyncBlendEnabled(); @@ -351,6 +400,12 @@ private: /// Syncs the specified light's position to match the PICA register void SyncLightPosition(int light_index); + /// Syncs the specified light's distance attenuation bias to match the PICA register + void SyncLightDistanceAttenuationBias(int light_index); + + /// Syncs the specified light's distance attenuation scale to match the PICA register + void SyncLightDistanceAttenuationScale(int light_index); + OpenGLState state; RasterizerCacheOpenGL res_cache; @@ -365,7 +420,7 @@ private: UniformData data; bool lut_dirty[6]; bool dirty; - } uniform_block_data; + } uniform_block_data = {}; std::array<SamplerInfo, 3> texture_samplers; OGLVertexArray vertex_array; @@ -374,5 +429,5 @@ private: OGLFramebuffer framebuffer; std::array<OGLTexture, 6> lighting_luts; - std::array<std::array<GLvec4, 256>, 6> lighting_lut_data; + std::array<std::array<GLvec4, 256>, 6> lighting_lut_data{}; }; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 9011caa39..8332e722d 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -32,8 +32,9 @@ static bool IsPassThroughTevStage(const TevStageConfig& stage) { } /// Writes the specified TEV stage source component(s) -static void AppendSource(std::string& out, TevStageConfig::Source source, +static void AppendSource(std::string& out, const PicaShaderConfig& config, TevStageConfig::Source source, const std::string& index_name) { + const auto& state = config.state; using Source = TevStageConfig::Source; switch (source) { case Source::PrimaryColor: @@ -46,7 +47,20 @@ static void AppendSource(std::string& out, TevStageConfig::Source source, out += "secondary_fragment_color"; break; case Source::Texture0: - out += "texture(tex[0], texcoord[0])"; + // Only unit 0 respects the texturing type (according to 3DBrew) + switch(state.texture0_type) { + case Pica::Regs::TextureConfig::Texture2D: + out += "texture(tex[0], texcoord[0])"; + break; + case Pica::Regs::TextureConfig::Projection2D: + out += "textureProj(tex[0], vec3(texcoord[0], texcoord0_w))"; + break; + default: + out += "texture(tex[0], texcoord[0])"; + LOG_CRITICAL(HW_GPU, "Unhandled texture type %x", static_cast<int>(state.texture0_type)); + UNIMPLEMENTED(); + break; + } break; case Source::Texture1: out += "texture(tex[1], texcoord[1])"; @@ -71,53 +85,53 @@ static void AppendSource(std::string& out, TevStageConfig::Source source, } /// Writes the color components to use for the specified TEV stage color modifier -static void AppendColorModifier(std::string& out, TevStageConfig::ColorModifier modifier, +static void AppendColorModifier(std::string& out, const PicaShaderConfig& config, TevStageConfig::ColorModifier modifier, TevStageConfig::Source source, const std::string& index_name) { using ColorModifier = TevStageConfig::ColorModifier; switch (modifier) { case ColorModifier::SourceColor: - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".rgb"; break; case ColorModifier::OneMinusSourceColor: out += "vec3(1.0) - "; - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".rgb"; break; case ColorModifier::SourceAlpha: - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".aaa"; break; case ColorModifier::OneMinusSourceAlpha: out += "vec3(1.0) - "; - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".aaa"; break; case ColorModifier::SourceRed: - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".rrr"; break; case ColorModifier::OneMinusSourceRed: out += "vec3(1.0) - "; - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".rrr"; break; case ColorModifier::SourceGreen: - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".ggg"; break; case ColorModifier::OneMinusSourceGreen: out += "vec3(1.0) - "; - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".ggg"; break; case ColorModifier::SourceBlue: - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".bbb"; break; case ColorModifier::OneMinusSourceBlue: out += "vec3(1.0) - "; - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".bbb"; break; default: @@ -128,44 +142,44 @@ static void AppendColorModifier(std::string& out, TevStageConfig::ColorModifier } /// Writes the alpha component to use for the specified TEV stage alpha modifier -static void AppendAlphaModifier(std::string& out, TevStageConfig::AlphaModifier modifier, +static void AppendAlphaModifier(std::string& out, const PicaShaderConfig& config, TevStageConfig::AlphaModifier modifier, TevStageConfig::Source source, const std::string& index_name) { using AlphaModifier = TevStageConfig::AlphaModifier; switch (modifier) { case AlphaModifier::SourceAlpha: - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".a"; break; case AlphaModifier::OneMinusSourceAlpha: out += "1.0 - "; - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".a"; break; case AlphaModifier::SourceRed: - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".r"; break; case AlphaModifier::OneMinusSourceRed: out += "1.0 - "; - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".r"; break; case AlphaModifier::SourceGreen: - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".g"; break; case AlphaModifier::OneMinusSourceGreen: out += "1.0 - "; - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".g"; break; case AlphaModifier::SourceBlue: - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".b"; break; case AlphaModifier::OneMinusSourceBlue: out += "1.0 - "; - AppendSource(out, source, index_name); + AppendSource(out, config, source, index_name); out += ".b"; break; default: @@ -287,16 +301,16 @@ static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) { /// Writes the code to emulate the specified TEV stage static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsigned index) { - auto& stage = config.tev_stages[index]; + const auto stage = static_cast<const Pica::Regs::TevStageConfig>(config.state.tev_stages[index]); if (!IsPassThroughTevStage(stage)) { std::string index_name = std::to_string(index); out += "vec3 color_results_" + index_name + "[3] = vec3[3]("; - AppendColorModifier(out, stage.color_modifier1, stage.color_source1, index_name); + AppendColorModifier(out, config, stage.color_modifier1, stage.color_source1, index_name); out += ", "; - AppendColorModifier(out, stage.color_modifier2, stage.color_source2, index_name); + AppendColorModifier(out, config, stage.color_modifier2, stage.color_source2, index_name); out += ", "; - AppendColorModifier(out, stage.color_modifier3, stage.color_source3, index_name); + AppendColorModifier(out, config, stage.color_modifier3, stage.color_source3, index_name); out += ");\n"; out += "vec3 color_output_" + index_name + " = "; @@ -304,11 +318,11 @@ static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsi out += ";\n"; out += "float alpha_results_" + index_name + "[3] = float[3]("; - AppendAlphaModifier(out, stage.alpha_modifier1, stage.alpha_source1, index_name); + AppendAlphaModifier(out, config, stage.alpha_modifier1, stage.alpha_source1, index_name); out += ", "; - AppendAlphaModifier(out, stage.alpha_modifier2, stage.alpha_source2, index_name); + AppendAlphaModifier(out, config, stage.alpha_modifier2, stage.alpha_source2, index_name); out += ", "; - AppendAlphaModifier(out, stage.alpha_modifier3, stage.alpha_source3, index_name); + AppendAlphaModifier(out, config, stage.alpha_modifier3, stage.alpha_source3, index_name); out += ");\n"; out += "float alpha_output_" + index_name + " = "; @@ -331,6 +345,8 @@ static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsi /// Writes the code to emulate fragment lighting static void WriteLighting(std::string& out, const PicaShaderConfig& config) { + const auto& lighting = config.state.lighting; + // Define lighting globals out += "vec4 diffuse_sum = vec4(0.0, 0.0, 0.0, 1.0);\n" "vec4 specular_sum = vec4(0.0, 0.0, 0.0, 1.0);\n" @@ -338,17 +354,17 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { "vec3 refl_value = vec3(0.0);\n"; // Compute fragment normals - if (config.lighting.bump_mode == Pica::Regs::LightingBumpMode::NormalMap) { + if (lighting.bump_mode == Pica::Regs::LightingBumpMode::NormalMap) { // Bump mapping is enabled using a normal map, read perturbation vector from the selected texture - std::string bump_selector = std::to_string(config.lighting.bump_selector); + std::string bump_selector = std::to_string(lighting.bump_selector); out += "vec3 surface_normal = 2.0 * texture(tex[" + bump_selector + "], texcoord[" + bump_selector + "]).rgb - 1.0;\n"; // Recompute Z-component of perturbation if 'renorm' is enabled, this provides a higher precision result - if (config.lighting.bump_renorm) { + if (lighting.bump_renorm) { std::string val = "(1.0 - (surface_normal.x*surface_normal.x + surface_normal.y*surface_normal.y))"; out += "surface_normal.z = sqrt(max(" + val + ", 0.0));\n"; } - } else if (config.lighting.bump_mode == Pica::Regs::LightingBumpMode::TangentMap) { + } else if (lighting.bump_mode == Pica::Regs::LightingBumpMode::TangentMap) { // Bump mapping is enabled using a tangent map LOG_CRITICAL(HW_GPU, "unimplemented bump mapping mode (tangent mapping)"); UNIMPLEMENTED(); @@ -361,7 +377,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { out += "vec3 normal = normalize(quaternion_rotate(normquat, surface_normal));\n"; // Gets the index into the specified lookup table for specular lighting - auto GetLutIndex = [config](unsigned light_num, Regs::LightingLutInput input, bool abs) { + auto GetLutIndex = [&lighting](unsigned light_num, Regs::LightingLutInput input, bool abs) { const std::string half_angle = "normalize(normalize(view) + light_vector)"; std::string index; switch (input) { @@ -389,7 +405,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { if (abs) { // LUT index is in the range of (0.0, 1.0) - index = config.lighting.light[light_num].two_sided_diffuse ? "abs(" + index + ")" : "max(" + index + ", 0.f)"; + index = lighting.light[light_num].two_sided_diffuse ? "abs(" + index + ")" : "max(" + index + ", 0.f)"; return "(FLOAT_255 * clamp(" + index + ", 0.0, 1.0))"; } else { // LUT index is in the range of (-1.0, 1.0) @@ -407,8 +423,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { }; // Write the code to emulate each enabled light - for (unsigned light_index = 0; light_index < config.lighting.src_num; ++light_index) { - const auto& light_config = config.lighting.light[light_index]; + for (unsigned light_index = 0; light_index < lighting.src_num; ++light_index) { + const auto& light_config = lighting.light[light_index]; std::string light_src = "light_src[" + std::to_string(light_config.num) + "]"; // Compute light vector (directional or positional) @@ -423,48 +439,46 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { // If enabled, compute distance attenuation value std::string dist_atten = "1.0"; if (light_config.dist_atten_enable) { - std::string scale = std::to_string(light_config.dist_atten_scale); - std::string bias = std::to_string(light_config.dist_atten_bias); - std::string index = "(" + scale + " * length(-view - " + light_src + ".position) + " + bias + ")"; + std::string index = "(" + light_src + ".dist_atten_scale * length(-view - " + light_src + ".position) + " + light_src + ".dist_atten_bias)"; index = "((clamp(" + index + ", 0.0, FLOAT_255)))"; const unsigned lut_num = ((unsigned)Regs::LightingSampler::DistanceAttenuation + light_config.num); dist_atten = GetLutValue((Regs::LightingSampler)lut_num, index); } // If enabled, clamp specular component if lighting result is negative - std::string clamp_highlights = config.lighting.clamp_highlights ? "(dot(light_vector, normal) <= 0.0 ? 0.0 : 1.0)" : "1.0"; + std::string clamp_highlights = lighting.clamp_highlights ? "(dot(light_vector, normal) <= 0.0 ? 0.0 : 1.0)" : "1.0"; // Specular 0 component std::string d0_lut_value = "1.0"; - if (config.lighting.lut_d0.enable && Pica::Regs::IsLightingSamplerSupported(config.lighting.config, Pica::Regs::LightingSampler::Distribution0)) { + if (lighting.lut_d0.enable && Pica::Regs::IsLightingSamplerSupported(lighting.config, Pica::Regs::LightingSampler::Distribution0)) { // Lookup specular "distribution 0" LUT value - std::string index = GetLutIndex(light_config.num, config.lighting.lut_d0.type, config.lighting.lut_d0.abs_input); - d0_lut_value = "(" + std::to_string(config.lighting.lut_d0.scale) + " * " + GetLutValue(Regs::LightingSampler::Distribution0, index) + ")"; + std::string index = GetLutIndex(light_config.num, lighting.lut_d0.type, lighting.lut_d0.abs_input); + d0_lut_value = "(" + std::to_string(lighting.lut_d0.scale) + " * " + GetLutValue(Regs::LightingSampler::Distribution0, index) + ")"; } std::string specular_0 = "(" + d0_lut_value + " * " + light_src + ".specular_0)"; // If enabled, lookup ReflectRed value, otherwise, 1.0 is used - if (config.lighting.lut_rr.enable && Pica::Regs::IsLightingSamplerSupported(config.lighting.config, Pica::Regs::LightingSampler::ReflectRed)) { - std::string index = GetLutIndex(light_config.num, config.lighting.lut_rr.type, config.lighting.lut_rr.abs_input); - std::string value = "(" + std::to_string(config.lighting.lut_rr.scale) + " * " + GetLutValue(Regs::LightingSampler::ReflectRed, index) + ")"; + if (lighting.lut_rr.enable && Pica::Regs::IsLightingSamplerSupported(lighting.config, Pica::Regs::LightingSampler::ReflectRed)) { + std::string index = GetLutIndex(light_config.num, lighting.lut_rr.type, lighting.lut_rr.abs_input); + std::string value = "(" + std::to_string(lighting.lut_rr.scale) + " * " + GetLutValue(Regs::LightingSampler::ReflectRed, index) + ")"; out += "refl_value.r = " + value + ";\n"; } else { out += "refl_value.r = 1.0;\n"; } // If enabled, lookup ReflectGreen value, otherwise, ReflectRed value is used - if (config.lighting.lut_rg.enable && Pica::Regs::IsLightingSamplerSupported(config.lighting.config, Pica::Regs::LightingSampler::ReflectGreen)) { - std::string index = GetLutIndex(light_config.num, config.lighting.lut_rg.type, config.lighting.lut_rg.abs_input); - std::string value = "(" + std::to_string(config.lighting.lut_rg.scale) + " * " + GetLutValue(Regs::LightingSampler::ReflectGreen, index) + ")"; + if (lighting.lut_rg.enable && Pica::Regs::IsLightingSamplerSupported(lighting.config, Pica::Regs::LightingSampler::ReflectGreen)) { + std::string index = GetLutIndex(light_config.num, lighting.lut_rg.type, lighting.lut_rg.abs_input); + std::string value = "(" + std::to_string(lighting.lut_rg.scale) + " * " + GetLutValue(Regs::LightingSampler::ReflectGreen, index) + ")"; out += "refl_value.g = " + value + ";\n"; } else { out += "refl_value.g = refl_value.r;\n"; } // If enabled, lookup ReflectBlue value, otherwise, ReflectRed value is used - if (config.lighting.lut_rb.enable && Pica::Regs::IsLightingSamplerSupported(config.lighting.config, Pica::Regs::LightingSampler::ReflectBlue)) { - std::string index = GetLutIndex(light_config.num, config.lighting.lut_rb.type, config.lighting.lut_rb.abs_input); - std::string value = "(" + std::to_string(config.lighting.lut_rb.scale) + " * " + GetLutValue(Regs::LightingSampler::ReflectBlue, index) + ")"; + if (lighting.lut_rb.enable && Pica::Regs::IsLightingSamplerSupported(lighting.config, Pica::Regs::LightingSampler::ReflectBlue)) { + std::string index = GetLutIndex(light_config.num, lighting.lut_rb.type, lighting.lut_rb.abs_input); + std::string value = "(" + std::to_string(lighting.lut_rb.scale) + " * " + GetLutValue(Regs::LightingSampler::ReflectBlue, index) + ")"; out += "refl_value.b = " + value + ";\n"; } else { out += "refl_value.b = refl_value.r;\n"; @@ -472,27 +486,27 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { // Specular 1 component std::string d1_lut_value = "1.0"; - if (config.lighting.lut_d1.enable && Pica::Regs::IsLightingSamplerSupported(config.lighting.config, Pica::Regs::LightingSampler::Distribution1)) { + if (lighting.lut_d1.enable && Pica::Regs::IsLightingSamplerSupported(lighting.config, Pica::Regs::LightingSampler::Distribution1)) { // Lookup specular "distribution 1" LUT value - std::string index = GetLutIndex(light_config.num, config.lighting.lut_d1.type, config.lighting.lut_d1.abs_input); - d1_lut_value = "(" + std::to_string(config.lighting.lut_d1.scale) + " * " + GetLutValue(Regs::LightingSampler::Distribution1, index) + ")"; + std::string index = GetLutIndex(light_config.num, lighting.lut_d1.type, lighting.lut_d1.abs_input); + d1_lut_value = "(" + std::to_string(lighting.lut_d1.scale) + " * " + GetLutValue(Regs::LightingSampler::Distribution1, index) + ")"; } std::string specular_1 = "(" + d1_lut_value + " * refl_value * " + light_src + ".specular_1)"; // Fresnel - if (config.lighting.lut_fr.enable && Pica::Regs::IsLightingSamplerSupported(config.lighting.config, Pica::Regs::LightingSampler::Fresnel)) { + if (lighting.lut_fr.enable && Pica::Regs::IsLightingSamplerSupported(lighting.config, Pica::Regs::LightingSampler::Fresnel)) { // Lookup fresnel LUT value - std::string index = GetLutIndex(light_config.num, config.lighting.lut_fr.type, config.lighting.lut_fr.abs_input); - std::string value = "(" + std::to_string(config.lighting.lut_fr.scale) + " * " + GetLutValue(Regs::LightingSampler::Fresnel, index) + ")"; + std::string index = GetLutIndex(light_config.num, lighting.lut_fr.type, lighting.lut_fr.abs_input); + std::string value = "(" + std::to_string(lighting.lut_fr.scale) + " * " + GetLutValue(Regs::LightingSampler::Fresnel, index) + ")"; // Enabled for difffuse lighting alpha component - if (config.lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::PrimaryAlpha || - config.lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::Both) + if (lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::PrimaryAlpha || + lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::Both) out += "diffuse_sum.a *= " + value + ";\n"; // Enabled for the specular lighting alpha component - if (config.lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::SecondaryAlpha || - config.lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::Both) + if (lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::SecondaryAlpha || + lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::Both) out += "specular_sum.a *= " + value + ";\n"; } @@ -510,6 +524,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { } std::string GenerateFragmentShader(const PicaShaderConfig& config) { + const auto& state = config.state; + std::string out = R"( #version 330 core #define NUM_TEV_STAGES 6 @@ -519,6 +535,7 @@ std::string GenerateFragmentShader(const PicaShaderConfig& config) { in vec4 primary_color; in vec2 texcoord[3]; +in float texcoord0_w; in vec4 normquat; in vec3 view; @@ -530,12 +547,15 @@ struct LightSrc { vec3 diffuse; vec3 ambient; vec3 position; + float dist_atten_bias; + float dist_atten_scale; }; layout (std140) uniform shader_data { vec4 const_color[NUM_TEV_STAGES]; vec4 tev_combiner_buffer_color; int alphatest_ref; + float depth_scale; float depth_offset; vec3 lighting_global_ambient; LightSrc light_src[NUM_LIGHTS]; @@ -555,29 +575,37 @@ vec4 secondary_fragment_color = vec4(0.0); )"; // Do not do any sort of processing if it's obvious we're not going to pass the alpha test - if (config.alpha_test_func == Regs::CompareFunc::Never) { + if (state.alpha_test_func == Regs::CompareFunc::Never) { out += "discard; }"; return out; } - if (config.lighting.enable) + if (state.lighting.enable) WriteLighting(out, config); out += "vec4 combiner_buffer = vec4(0.0);\n"; out += "vec4 next_combiner_buffer = tev_combiner_buffer_color;\n"; out += "vec4 last_tex_env_out = vec4(0.0);\n"; - for (size_t index = 0; index < config.tev_stages.size(); ++index) + for (size_t index = 0; index < state.tev_stages.size(); ++index) WriteTevStage(out, config, (unsigned)index); - if (config.alpha_test_func != Regs::CompareFunc::Always) { + if (state.alpha_test_func != Regs::CompareFunc::Always) { out += "if ("; - AppendAlphaTestCondition(out, config.alpha_test_func); + AppendAlphaTestCondition(out, state.alpha_test_func); out += ") discard;\n"; } out += "color = last_tex_env_out;\n"; - out += "gl_FragDepth = gl_FragCoord.z + depth_offset;\n}"; + + out += "float z_over_w = 1.0 - gl_FragCoord.z * 2.0;\n"; + out += "float depth = z_over_w * depth_scale + depth_offset;\n"; + if (state.depthmap_enable == Pica::Regs::DepthBuffering::WBuffering) { + out += "depth /= gl_FragCoord.w;\n"; + } + out += "gl_FragDepth = depth;\n"; + + out += "}"; return out; } @@ -585,17 +613,19 @@ vec4 secondary_fragment_color = vec4(0.0); std::string GenerateVertexShader() { std::string out = "#version 330 core\n"; - out += "layout(location = " + std::to_string((int)ATTRIBUTE_POSITION) + ") in vec4 vert_position;\n"; - out += "layout(location = " + std::to_string((int)ATTRIBUTE_COLOR) + ") in vec4 vert_color;\n"; - out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD0) + ") in vec2 vert_texcoord0;\n"; - out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD1) + ") in vec2 vert_texcoord1;\n"; - out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD2) + ") in vec2 vert_texcoord2;\n"; - out += "layout(location = " + std::to_string((int)ATTRIBUTE_NORMQUAT) + ") in vec4 vert_normquat;\n"; - out += "layout(location = " + std::to_string((int)ATTRIBUTE_VIEW) + ") in vec3 vert_view;\n"; + out += "layout(location = " + std::to_string((int)ATTRIBUTE_POSITION) + ") in vec4 vert_position;\n"; + out += "layout(location = " + std::to_string((int)ATTRIBUTE_COLOR) + ") in vec4 vert_color;\n"; + out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD0) + ") in vec2 vert_texcoord0;\n"; + out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD1) + ") in vec2 vert_texcoord1;\n"; + out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD2) + ") in vec2 vert_texcoord2;\n"; + out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD0_W) + ") in float vert_texcoord0_w;\n"; + out += "layout(location = " + std::to_string((int)ATTRIBUTE_NORMQUAT) + ") in vec4 vert_normquat;\n"; + out += "layout(location = " + std::to_string((int)ATTRIBUTE_VIEW) + ") in vec3 vert_view;\n"; out += R"( out vec4 primary_color; out vec2 texcoord[3]; +out float texcoord0_w; out vec4 normquat; out vec3 view; @@ -604,6 +634,7 @@ void main() { texcoord[0] = vert_texcoord0; texcoord[1] = vert_texcoord1; texcoord[2] = vert_texcoord2; + texcoord0_w = vert_texcoord0_w; normquat = vert_normquat; view = vert_view; gl_Position = vec4(vert_position.x, vert_position.y, -vert_position.z, vert_position.w); diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index 3eb07d57a..bef3249cf 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -6,7 +6,7 @@ #include <string> -struct PicaShaderConfig; +union PicaShaderConfig; namespace GLShader { diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h index 097242f6f..f59912f79 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.h +++ b/src/video_core/renderer_opengl/gl_shader_util.h @@ -14,6 +14,7 @@ enum Attributes { ATTRIBUTE_TEXCOORD0, ATTRIBUTE_TEXCOORD1, ATTRIBUTE_TEXCOORD2, + ATTRIBUTE_TEXCOORD0_W, ATTRIBUTE_NORMQUAT, ATTRIBUTE_VIEW, }; diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 02cd9f417..fa141fc9a 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -36,6 +36,8 @@ OpenGLState::OpenGLState() { stencil.action_stencil_fail = GL_KEEP; blend.enabled = false; + blend.rgb_equation = GL_FUNC_ADD; + blend.a_equation = GL_FUNC_ADD; blend.src_rgb_func = GL_ONE; blend.dst_rgb_func = GL_ZERO; blend.src_a_func = GL_ONE; @@ -165,6 +167,11 @@ void OpenGLState::Apply() const { blend.src_a_func, blend.dst_a_func); } + if (blend.rgb_equation != cur_state.blend.rgb_equation || + blend.a_equation != cur_state.blend.a_equation) { + glBlendEquationSeparate(blend.rgb_equation, blend.a_equation); + } + if (logic_op != cur_state.logic_op) { glLogicOp(logic_op); } diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index 24f20e47c..228727054 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -40,6 +40,8 @@ public: struct { bool enabled; // GL_BLEND + GLenum rgb_equation; // GL_BLEND_EQUATION_RGB + GLenum a_equation; // GL_BLEND_EQUATION_ALPHA GLenum src_rgb_func; // GL_BLEND_SRC_RGB GLenum dst_rgb_func; // GL_BLEND_DST_RGB GLenum src_a_func; // GL_BLEND_SRC_ALPHA diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h index 976d1f364..6dc2758c5 100644 --- a/src/video_core/renderer_opengl/pica_to_gl.h +++ b/src/video_core/renderer_opengl/pica_to_gl.h @@ -78,6 +78,26 @@ inline GLenum WrapMode(Pica::Regs::TextureConfig::WrapMode mode) { return gl_mode; } +inline GLenum BlendEquation(Pica::Regs::BlendEquation equation) { + static const GLenum blend_equation_table[] = { + GL_FUNC_ADD, // BlendEquation::Add + GL_FUNC_SUBTRACT, // BlendEquation::Subtract + GL_FUNC_REVERSE_SUBTRACT, // BlendEquation::ReverseSubtract + GL_MIN, // BlendEquation::Min + GL_MAX, // BlendEquation::Max + }; + + // Range check table for input + if (static_cast<size_t>(equation) >= ARRAY_SIZE(blend_equation_table)) { + LOG_CRITICAL(Render_OpenGL, "Unknown blend equation %d", equation); + UNREACHABLE(); + + return GL_FUNC_ADD; + } + + return blend_equation_table[(unsigned)equation]; +} + inline GLenum BlendFunc(Pica::Regs::BlendFactor factor) { static const GLenum blend_func_table[] = { GL_ZERO, // BlendFactor::Zero diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 0e9a0be8b..8f424a435 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -192,7 +192,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& fram // only allows rows to have a memory alignement of 4. ASSERT(pixel_stride % 4 == 0); - if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, pixel_stride, screen_info)) { + if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, static_cast<u32>(pixel_stride), screen_info)) { // Reset the screen info's display texture to its own permanent texture screen_info.display_texture = screen_info.texture.resource.handle; screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f); @@ -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); diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp index 65dcc9156..161097610 100644 --- a/src/video_core/shader/shader.cpp +++ b/src/video_core/shader/shader.cpp @@ -35,7 +35,13 @@ static std::unordered_map<u64, std::unique_ptr<JitShader>> shader_map; static const JitShader* jit_shader; #endif // ARCHITECTURE_x86_64 -void Setup() { +void ClearCache() { +#ifdef ARCHITECTURE_x86_64 + shader_map.clear(); +#endif // ARCHITECTURE_x86_64 +} + +void ShaderSetup::Setup() { #ifdef ARCHITECTURE_x86_64 if (VideoCore::g_shader_jit_enabled) { u64 cache_key = (Common::ComputeHash64(&g_state.vs.program_code, sizeof(g_state.vs.program_code)) ^ @@ -54,20 +60,14 @@ void Setup() { #endif // ARCHITECTURE_x86_64 } -void Shutdown() { -#ifdef ARCHITECTURE_x86_64 - shader_map.clear(); -#endif // ARCHITECTURE_x86_64 -} - -MICROPROFILE_DEFINE(GPU_VertexShader, "GPU", "Vertex Shader", MP_RGB(50, 50, 240)); +MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240)); -OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attributes) { +OutputVertex ShaderSetup::Run(UnitState<false>& state, const InputVertex& input, int num_attributes) { auto& config = g_state.regs.vs; + auto& setup = g_state.vs; - MICROPROFILE_SCOPE(GPU_VertexShader); + MICROPROFILE_SCOPE(GPU_Shader); - state.program_counter = config.main_offset; state.debug.max_offset = 0; state.debug.max_opdesc_id = 0; @@ -82,11 +82,11 @@ OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attr #ifdef ARCHITECTURE_x86_64 if (VideoCore::g_shader_jit_enabled) - jit_shader->Run(&state.registers, g_state.regs.vs.main_offset); + jit_shader->Run(setup, state, config.main_offset); else - RunInterpreter(state); + RunInterpreter(setup, state, config.main_offset); #else - RunInterpreter(state); + RunInterpreter(setup, state, config.main_offset); #endif // ARCHITECTURE_x86_64 // Setup output data @@ -140,10 +140,9 @@ OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attr return ret; } -DebugData<true> ProduceDebugInfo(const InputVertex& input, int num_attributes, const Regs::ShaderConfig& config, const ShaderSetup& setup) { +DebugData<true> ShaderSetup::ProduceDebugInfo(const InputVertex& input, int num_attributes, const Regs::ShaderConfig& config, const ShaderSetup& setup) { UnitState<true> state; - state.program_counter = config.main_offset; state.debug.max_offset = 0; state.debug.max_opdesc_id = 0; @@ -158,7 +157,7 @@ DebugData<true> ProduceDebugInfo(const InputVertex& input, int num_attributes, c state.conditional_code[0] = false; state.conditional_code[1] = false; - RunInterpreter(state); + RunInterpreter(setup, state, config.main_offset); return state.debug; } diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h index 56b83bfeb..84898f21c 100644 --- a/src/video_core/shader/shader.h +++ b/src/video_core/shader/shader.h @@ -43,7 +43,8 @@ struct OutputVertex { Math::Vec4<float24> color; Math::Vec2<float24> tc0; Math::Vec2<float24> tc1; - INSERT_PADDING_WORDS(2); + float24 tc0_w; + INSERT_PADDING_WORDS(1); Math::Vec3<float24> view; INSERT_PADDING_WORDS(1); Math::Vec2<float24> tc2; @@ -83,23 +84,6 @@ struct OutputVertex { static_assert(std::is_pod<OutputVertex>::value, "Structure is not POD"); static_assert(sizeof(OutputVertex) == 32 * sizeof(float), "OutputVertex has invalid size"); -/// Vertex shader memory -struct ShaderSetup { - struct { - // The float uniforms are accessed by the shader JIT using SSE instructions, and are - // therefore required to be 16-byte aligned. - alignas(16) Math::Vec4<float24> f[96]; - - std::array<bool, 16> b; - std::array<Math::Vec4<u8>, 4> i; - } uniforms; - - Math::Vec4<float24> default_attributes[16]; - - std::array<u32, 1024> program_code; - std::array<u32, 1024> swizzle_data; -}; - // Helper structure used to keep track of data useful for inspection of shader emulation template<bool full_debugging> struct DebugData; @@ -288,38 +272,21 @@ struct UnitState { } registers; static_assert(std::is_pod<Registers>::value, "Structure is not POD"); - u32 program_counter; bool conditional_code[2]; // Two Address registers and one loop counter // TODO: How many bits do these actually have? s32 address_registers[3]; - enum { - INVALID_ADDRESS = 0xFFFFFFFF - }; - - struct CallStackElement { - u32 final_address; // Address upon which we jump to return_address - u32 return_address; // Where to jump when leaving scope - u8 repeat_counter; // How often to repeat until this call stack element is removed - u8 loop_increment; // Which value to add to the loop counter after an iteration - // TODO: Should this be a signed value? Does it even matter? - u32 loop_address; // The address where we'll return to after each loop iteration - }; - - // TODO: Is there a maximal size for this? - boost::container::static_vector<CallStackElement, 16> call_stack; - DebugData<Debug> debug; static size_t InputOffset(const SourceRegister& reg) { switch (reg.GetRegisterType()) { case RegisterType::Input: - return offsetof(UnitState::Registers, input) + reg.GetIndex()*sizeof(Math::Vec4<float24>); + return offsetof(UnitState, registers.input) + reg.GetIndex()*sizeof(Math::Vec4<float24>); case RegisterType::Temporary: - return offsetof(UnitState::Registers, temporary) + reg.GetIndex()*sizeof(Math::Vec4<float24>); + return offsetof(UnitState, registers.temporary) + reg.GetIndex()*sizeof(Math::Vec4<float24>); default: UNREACHABLE(); @@ -330,10 +297,10 @@ struct UnitState { static size_t OutputOffset(const DestRegister& reg) { switch (reg.GetRegisterType()) { case RegisterType::Output: - return offsetof(UnitState::Registers, output) + reg.GetIndex()*sizeof(Math::Vec4<float24>); + return offsetof(UnitState, registers.output) + reg.GetIndex()*sizeof(Math::Vec4<float24>); case RegisterType::Temporary: - return offsetof(UnitState::Registers, temporary) + reg.GetIndex()*sizeof(Math::Vec4<float24>); + return offsetof(UnitState, registers.temporary) + reg.GetIndex()*sizeof(Math::Vec4<float24>); default: UNREACHABLE(); @@ -342,33 +309,66 @@ struct UnitState { } }; -/** - * Performs any shader unit setup that only needs to happen once per shader (as opposed to once per - * vertex, which would happen within the `Run` function). - */ -void Setup(); +/// Clears the shader cache +void ClearCache(); -/// Performs any cleanup when the emulator is shutdown -void Shutdown(); +struct ShaderSetup { -/** - * Runs the currently setup shader - * @param state Shader unit state, must be setup per shader and per shader unit - * @param input Input vertex into the shader - * @param num_attributes The number of vertex shader attributes - * @return The output vertex, after having been processed by the vertex shader - */ -OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attributes); + struct { + // The float uniforms are accessed by the shader JIT using SSE instructions, and are + // therefore required to be 16-byte aligned. + alignas(16) Math::Vec4<float24> f[96]; -/** - * Produce debug information based on the given shader and input vertex - * @param input Input vertex into the shader - * @param num_attributes The number of vertex shader attributes - * @param config Configuration object for the shader pipeline - * @param setup Setup object for the shader pipeline - * @return Debug information for this shader with regards to the given vertex - */ -DebugData<true> ProduceDebugInfo(const InputVertex& input, int num_attributes, const Regs::ShaderConfig& config, const ShaderSetup& setup); + std::array<bool, 16> b; + std::array<Math::Vec4<u8>, 4> i; + } uniforms; + + static size_t UniformOffset(RegisterType type, unsigned index) { + switch (type) { + case RegisterType::FloatUniform: + return offsetof(ShaderSetup, uniforms.f) + index*sizeof(Math::Vec4<float24>); + + case RegisterType::BoolUniform: + return offsetof(ShaderSetup, uniforms.b) + index*sizeof(bool); + + case RegisterType::IntUniform: + return offsetof(ShaderSetup, uniforms.i) + index*sizeof(Math::Vec4<u8>); + + default: + UNREACHABLE(); + return 0; + } + } + + std::array<u32, 1024> program_code; + std::array<u32, 1024> swizzle_data; + + /** + * Performs any shader unit setup that only needs to happen once per shader (as opposed to once per + * vertex, which would happen within the `Run` function). + */ + void Setup(); + + /** + * Runs the currently setup shader + * @param state Shader unit state, must be setup per shader and per shader unit + * @param input Input vertex into the shader + * @param num_attributes The number of vertex shader attributes + * @return The output vertex, after having been processed by the vertex shader + */ + OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attributes); + + /** + * Produce debug information based on the given shader and input vertex + * @param input Input vertex into the shader + * @param num_attributes The number of vertex shader attributes + * @param config Configuration object for the shader pipeline + * @param setup Setup object for the shader pipeline + * @return Debug information for this shader with regards to the given vertex + */ + DebugData<true> ProduceDebugInfo(const InputVertex& input, int num_attributes, const Regs::ShaderConfig& config, const ShaderSetup& setup); + +}; } // namespace Shader diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp index 7710f7fbc..714e8bfd5 100644 --- a/src/video_core/shader/shader_interpreter.cpp +++ b/src/video_core/shader/shader_interpreter.cpp @@ -29,8 +29,24 @@ namespace Pica { namespace Shader { +constexpr u32 INVALID_ADDRESS = 0xFFFFFFFF; + +struct CallStackElement { + u32 final_address; // Address upon which we jump to return_address + u32 return_address; // Where to jump when leaving scope + u8 repeat_counter; // How often to repeat until this call stack element is removed + u8 loop_increment; // Which value to add to the loop counter after an iteration + // TODO: Should this be a signed value? Does it even matter? + u32 loop_address; // The address where we'll return to after each loop iteration +}; + template<bool Debug> -void RunInterpreter(UnitState<Debug>& state) { +void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned offset) { + // TODO: Is there a maximal size for this? + boost::container::static_vector<CallStackElement, 16> call_stack; + + u32 program_counter = offset; + const auto& uniforms = g_state.vs.uniforms; const auto& swizzle_data = g_state.vs.swizzle_data; const auto& program_code = g_state.vs.program_code; @@ -41,16 +57,16 @@ void RunInterpreter(UnitState<Debug>& state) { unsigned iteration = 0; bool exit_loop = false; while (!exit_loop) { - if (!state.call_stack.empty()) { - auto& top = state.call_stack.back(); - if (state.program_counter == top.final_address) { + if (!call_stack.empty()) { + auto& top = call_stack.back(); + if (program_counter == top.final_address) { state.address_registers[2] += top.loop_increment; if (top.repeat_counter-- == 0) { - state.program_counter = top.return_address; - state.call_stack.pop_back(); + program_counter = top.return_address; + call_stack.pop_back(); } else { - state.program_counter = top.loop_address; + program_counter = top.loop_address; } // TODO: Is "trying again" accurate to hardware? @@ -58,20 +74,20 @@ void RunInterpreter(UnitState<Debug>& state) { } } - const Instruction instr = { program_code[state.program_counter] }; + const Instruction instr = { program_code[program_counter] }; const SwizzlePattern swizzle = { swizzle_data[instr.common.operand_desc_id] }; - static auto call = [](UnitState<Debug>& state, u32 offset, u32 num_instructions, + static auto call = [&program_counter, &call_stack](UnitState<Debug>& state, u32 offset, u32 num_instructions, u32 return_offset, u8 repeat_count, u8 loop_increment) { - state.program_counter = offset - 1; // -1 to make sure when incrementing the PC we end up at the correct offset - ASSERT(state.call_stack.size() < state.call_stack.capacity()); - state.call_stack.push_back({ offset + num_instructions, return_offset, repeat_count, loop_increment, offset }); + program_counter = offset - 1; // -1 to make sure when incrementing the PC we end up at the correct offset + ASSERT(call_stack.size() < call_stack.capacity()); + call_stack.push_back({ offset + num_instructions, return_offset, repeat_count, loop_increment, offset }); }; - Record<DebugDataRecord::CUR_INSTR>(state.debug, iteration, state.program_counter); + Record<DebugDataRecord::CUR_INSTR>(state.debug, iteration, program_counter); if (iteration > 0) - Record<DebugDataRecord::NEXT_INSTR>(state.debug, iteration - 1, state.program_counter); + Record<DebugDataRecord::NEXT_INSTR>(state.debug, iteration - 1, program_counter); - state.debug.max_offset = std::max<u32>(state.debug.max_offset, 1 + state.program_counter); + state.debug.max_offset = std::max<u32>(state.debug.max_offset, 1 + program_counter); auto LookupSourceRegister = [&](const SourceRegister& source_reg) -> const float24* { switch (source_reg.GetRegisterType()) { @@ -519,7 +535,7 @@ void RunInterpreter(UnitState<Debug>& state) { case OpCode::Id::JMPC: Record<DebugDataRecord::COND_CMP_IN>(state.debug, iteration, state.conditional_code); if (evaluate_condition(state, instr.flow_control.refx, instr.flow_control.refy, instr.flow_control)) { - state.program_counter = instr.flow_control.dest_offset - 1; + program_counter = instr.flow_control.dest_offset - 1; } break; @@ -527,7 +543,7 @@ void RunInterpreter(UnitState<Debug>& state) { Record<DebugDataRecord::COND_BOOL_IN>(state.debug, iteration, uniforms.b[instr.flow_control.bool_uniform_id]); if (uniforms.b[instr.flow_control.bool_uniform_id] == !(instr.flow_control.num_instructions & 1)) { - state.program_counter = instr.flow_control.dest_offset - 1; + program_counter = instr.flow_control.dest_offset - 1; } break; @@ -535,7 +551,7 @@ void RunInterpreter(UnitState<Debug>& state) { call(state, instr.flow_control.dest_offset, instr.flow_control.num_instructions, - state.program_counter + 1, 0, 0); + program_counter + 1, 0, 0); break; case OpCode::Id::CALLU: @@ -544,7 +560,7 @@ void RunInterpreter(UnitState<Debug>& state) { call(state, instr.flow_control.dest_offset, instr.flow_control.num_instructions, - state.program_counter + 1, 0, 0); + program_counter + 1, 0, 0); } break; @@ -554,7 +570,7 @@ void RunInterpreter(UnitState<Debug>& state) { call(state, instr.flow_control.dest_offset, instr.flow_control.num_instructions, - state.program_counter + 1, 0, 0); + program_counter + 1, 0, 0); } break; @@ -565,8 +581,8 @@ void RunInterpreter(UnitState<Debug>& state) { Record<DebugDataRecord::COND_BOOL_IN>(state.debug, iteration, uniforms.b[instr.flow_control.bool_uniform_id]); if (uniforms.b[instr.flow_control.bool_uniform_id]) { call(state, - state.program_counter + 1, - instr.flow_control.dest_offset - state.program_counter - 1, + program_counter + 1, + instr.flow_control.dest_offset - program_counter - 1, instr.flow_control.dest_offset + instr.flow_control.num_instructions, 0, 0); } else { call(state, @@ -584,8 +600,8 @@ void RunInterpreter(UnitState<Debug>& state) { Record<DebugDataRecord::COND_CMP_IN>(state.debug, iteration, state.conditional_code); if (evaluate_condition(state, instr.flow_control.refx, instr.flow_control.refy, instr.flow_control)) { call(state, - state.program_counter + 1, - instr.flow_control.dest_offset - state.program_counter - 1, + program_counter + 1, + instr.flow_control.dest_offset - program_counter - 1, instr.flow_control.dest_offset + instr.flow_control.num_instructions, 0, 0); } else { call(state, @@ -607,8 +623,8 @@ void RunInterpreter(UnitState<Debug>& state) { Record<DebugDataRecord::LOOP_INT_IN>(state.debug, iteration, loop_param); call(state, - state.program_counter + 1, - instr.flow_control.dest_offset - state.program_counter + 1, + program_counter + 1, + instr.flow_control.dest_offset - program_counter + 1, instr.flow_control.dest_offset + 1, loop_param.x, loop_param.z); @@ -625,14 +641,14 @@ void RunInterpreter(UnitState<Debug>& state) { } } - ++state.program_counter; + ++program_counter; ++iteration; } } // Explicit instantiation -template void RunInterpreter(UnitState<false>& state); -template void RunInterpreter(UnitState<true>& state); +template void RunInterpreter(const ShaderSetup& setup, UnitState<false>& state, unsigned offset); +template void RunInterpreter(const ShaderSetup& setup, UnitState<true>& state, unsigned offset); } // namespace diff --git a/src/video_core/shader/shader_interpreter.h b/src/video_core/shader/shader_interpreter.h index 6048cdf3a..bb3ce1c6e 100644 --- a/src/video_core/shader/shader_interpreter.h +++ b/src/video_core/shader/shader_interpreter.h @@ -11,7 +11,7 @@ namespace Shader { template <bool Debug> struct UnitState; template<bool Debug> -void RunInterpreter(UnitState<Debug>& state); +void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned offset); } // namespace diff --git a/src/video_core/shader/shader_jit_x64.cpp b/src/video_core/shader/shader_jit_x64.cpp index 99f6c51eb..43e7e6b4c 100644 --- a/src/video_core/shader/shader_jit_x64.cpp +++ b/src/video_core/shader/shader_jit_x64.cpp @@ -102,7 +102,7 @@ const JitFunction instr_table[64] = { // purposes, as documented below: /// Pointer to the uniform memory -static const X64Reg UNIFORMS = R9; +static const X64Reg SETUP = R9; /// The two 32-bit VS address offset registers set by the MOVA instruction static const X64Reg ADDROFFS_REG_0 = R10; static const X64Reg ADDROFFS_REG_1 = R11; @@ -117,7 +117,7 @@ static const X64Reg COND0 = R13; /// Result of the previous CMP instruction for the Y-component comparison static const X64Reg COND1 = R14; /// Pointer to the UnitState instance for the current VS unit -static const X64Reg REGISTERS = R15; +static const X64Reg STATE = R15; /// SIMD scratch register static const X64Reg SCRATCH = XMM0; /// Loaded with the first swizzled source register, otherwise can be used as a scratch register @@ -136,7 +136,7 @@ static const X64Reg NEGBIT = XMM15; // State registers that must not be modified by external functions calls // Scratch registers, e.g., SRC1 and SCRATCH, have to be saved on the side if needed static const BitSet32 persistent_regs = { - UNIFORMS, REGISTERS, // Pointers to register blocks + SETUP, STATE, // Pointers to register blocks ADDROFFS_REG_0, ADDROFFS_REG_1, LOOPCOUNT_REG, COND0, COND1, // Cached registers ONE+16, NEGBIT+16, // Constants }; @@ -177,10 +177,10 @@ void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRe size_t src_offset; if (src_reg.GetRegisterType() == RegisterType::FloatUniform) { - src_ptr = UNIFORMS; - src_offset = src_reg.GetIndex() * sizeof(float24) * 4; + src_ptr = SETUP; + src_offset = ShaderSetup::UniformOffset(RegisterType::FloatUniform, src_reg.GetIndex()); } else { - src_ptr = REGISTERS; + src_ptr = STATE; src_offset = UnitState<false>::InputOffset(src_reg); } @@ -264,11 +264,11 @@ void JitShader::Compile_DestEnable(Instruction instr,X64Reg src) { // If all components are enabled, write the result to the destination register if (swiz.dest_mask == NO_DEST_REG_MASK) { // Store dest back to memory - MOVAPS(MDisp(REGISTERS, dest_offset_disp), src); + MOVAPS(MDisp(STATE, dest_offset_disp), src); } else { // Not all components are enabled, so mask the result when storing to the destination register... - MOVAPS(SCRATCH, MDisp(REGISTERS, dest_offset_disp)); + MOVAPS(SCRATCH, MDisp(STATE, dest_offset_disp)); if (Common::GetCPUCaps().sse4_1) { u8 mask = ((swiz.dest_mask & 1) << 3) | ((swiz.dest_mask & 8) >> 3) | ((swiz.dest_mask & 2) << 1) | ((swiz.dest_mask & 4) >> 1); @@ -287,7 +287,7 @@ void JitShader::Compile_DestEnable(Instruction instr,X64Reg src) { } // Store dest back to memory - MOVAPS(MDisp(REGISTERS, dest_offset_disp), SCRATCH); + MOVAPS(MDisp(STATE, dest_offset_disp), SCRATCH); } } @@ -336,8 +336,8 @@ void JitShader::Compile_EvaluateCondition(Instruction instr) { } void JitShader::Compile_UniformCondition(Instruction instr) { - int offset = offsetof(decltype(g_state.vs.uniforms), b) + (instr.flow_control.bool_uniform_id * sizeof(bool)); - CMP(sizeof(bool) * 8, MDisp(UNIFORMS, offset), Imm8(0)); + int offset = ShaderSetup::UniformOffset(RegisterType::BoolUniform, instr.flow_control.bool_uniform_id); + CMP(sizeof(bool) * 8, MDisp(SETUP, offset), Imm8(0)); } BitSet32 JitShader::PersistentCallerSavedRegs() { @@ -714,8 +714,8 @@ void JitShader::Compile_LOOP(Instruction instr) { looping = true; - int offset = offsetof(decltype(g_state.vs.uniforms), i) + (instr.flow_control.int_uniform_id * sizeof(Math::Vec4<u8>)); - MOV(32, R(LOOPCOUNT), MDisp(UNIFORMS, offset)); + int offset = ShaderSetup::UniformOffset(RegisterType::IntUniform, instr.flow_control.int_uniform_id); + MOV(32, R(LOOPCOUNT), MDisp(SETUP, offset)); MOV(32, R(LOOPCOUNT_REG), R(LOOPCOUNT)); SHR(32, R(LOOPCOUNT_REG), Imm8(8)); AND(32, R(LOOPCOUNT_REG), Imm32(0xff)); // Y-component is the start @@ -826,8 +826,8 @@ void JitShader::Compile() { // The stack pointer is 8 modulo 16 at the entry of a procedure ABI_PushRegistersAndAdjustStack(ABI_ALL_CALLEE_SAVED, 8); - MOV(PTRBITS, R(REGISTERS), R(ABI_PARAM1)); - MOV(PTRBITS, R(UNIFORMS), ImmPtr(&g_state.vs.uniforms)); + MOV(PTRBITS, R(SETUP), R(ABI_PARAM1)); + MOV(PTRBITS, R(STATE), R(ABI_PARAM2)); // Zero address/loop registers XOR(64, R(ADDROFFS_REG_0), R(ADDROFFS_REG_0)); @@ -845,7 +845,7 @@ void JitShader::Compile() { MOVAPS(NEGBIT, MatR(RAX)); // Jump to start of the shader program - JMPptr(R(ABI_PARAM2)); + JMPptr(R(ABI_PARAM3)); // Compile entire program Compile_Block(static_cast<unsigned>(g_state.vs.program_code.size())); diff --git a/src/video_core/shader/shader_jit_x64.h b/src/video_core/shader/shader_jit_x64.h index 30aa7ff30..5468459d4 100644 --- a/src/video_core/shader/shader_jit_x64.h +++ b/src/video_core/shader/shader_jit_x64.h @@ -36,8 +36,8 @@ class JitShader : public Gen::XCodeBlock { public: JitShader(); - void Run(void* registers, unsigned offset) const { - program(registers, code_ptr[offset]); + void Run(const ShaderSetup& setup, UnitState<false>& state, unsigned offset) const { + program(&setup, &state, code_ptr[offset]); } void Compile(); @@ -117,7 +117,7 @@ private: /// Branches that need to be fixed up once the entire shader program is compiled std::vector<std::pair<Gen::FixupBranch, unsigned>> fixup_branches; - using CompiledShader = void(void* registers, const u8* start_addr); + using CompiledShader = void(const void* setup, void* state, const u8* start_addr); CompiledShader* program = nullptr; }; diff --git a/src/video_core/vertex_loader.cpp b/src/video_core/vertex_loader.cpp index 21ae52949..e40f0f1ee 100644 --- a/src/video_core/vertex_loader.cpp +++ b/src/video_core/vertex_loader.cpp @@ -2,8 +2,8 @@ #include <boost/range/algorithm/fill.hpp> -#include "common/assert.h" #include "common/alignment.h" +#include "common/assert.h" #include "common/bit_field.h" #include "common/common_types.h" #include "common/logging/log.h" @@ -21,6 +21,8 @@ namespace Pica { void VertexLoader::Setup(const Pica::Regs& regs) { + ASSERT_MSG(!is_setup, "VertexLoader is not intended to be setup more than once."); + const auto& attribute_config = regs.vertex_attributes; num_total_attributes = attribute_config.GetNumTotalAttributes(); @@ -60,9 +62,13 @@ void VertexLoader::Setup(const Pica::Regs& regs) { } } } + + is_setup = true; } void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, DebugUtils::MemoryAccessTracker& memory_accesses) { + ASSERT_MSG(is_setup, "A VertexLoader needs to be setup before loading vertices."); + for (int i = 0; i < num_total_attributes; ++i) { if (vertex_attribute_elements[i] != 0) { // Load per-vertex data from the loader arrays @@ -124,7 +130,7 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::I input.attr[i][0].ToFloat32(), input.attr[i][1].ToFloat32(), input.attr[i][2].ToFloat32(), input.attr[i][3].ToFloat32()); } else if (vertex_attribute_is_default[i]) { // Load the default attribute if we're configured to do so - input.attr[i] = g_state.vs.default_attributes[i]; + input.attr[i] = g_state.vs_default_attributes[i]; LOG_TRACE(HW_GPU, "Loaded default attribute %x for vertex %x (index %x): (%f, %f, %f, %f)", i, vertex, index, input.attr[i][0].ToFloat32(), input.attr[i][1].ToFloat32(), diff --git a/src/video_core/vertex_loader.h b/src/video_core/vertex_loader.h index becf5a403..ac162c254 100644 --- a/src/video_core/vertex_loader.h +++ b/src/video_core/vertex_loader.h @@ -1,7 +1,8 @@ #pragma once -#include "common/common_types.h" +#include <array> +#include "common/common_types.h" #include "video_core/pica.h" namespace Pica { @@ -11,23 +12,29 @@ class MemoryAccessTracker; } namespace Shader { -class InputVertex; +struct InputVertex; } class VertexLoader { public: + VertexLoader() = default; + explicit VertexLoader(const Pica::Regs& regs) { + Setup(regs); + } + void Setup(const Pica::Regs& regs); void LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, DebugUtils::MemoryAccessTracker& memory_accesses); int GetNumTotalAttributes() const { return num_total_attributes; } private: - u32 vertex_attribute_sources[16]; - u32 vertex_attribute_strides[16] = {}; - Regs::VertexAttributeFormat vertex_attribute_formats[16] = {}; - u32 vertex_attribute_elements[16] = {}; - bool vertex_attribute_is_default[16]; - int num_total_attributes; + std::array<u32, 16> vertex_attribute_sources; + std::array<u32, 16> vertex_attribute_strides{}; + std::array<Regs::VertexAttributeFormat, 16> vertex_attribute_formats; + std::array<u32, 16> vertex_attribute_elements{}; + std::array<bool, 16> vertex_attribute_is_default; + int num_total_attributes = 0; + bool is_setup = false; }; } // namespace Pica |