diff options
Diffstat (limited to 'src')
146 files changed, 3742 insertions, 2016 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 4cd7aba67..a72a907ef 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -3,10 +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 @@ -15,17 +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/dsp.cpp b/src/audio_core/hle/dsp.cpp index 0cdbdb06a..0640e1eff 100644 --- a/src/audio_core/hle/dsp.cpp +++ b/src/audio_core/hle/dsp.cpp @@ -6,13 +6,17 @@ #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() { @@ -40,43 +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() { - SharedMemory& read = ReadRegion(); - SharedMemory& write = WriteRegion(); + StereoFrame16 current_frame = {}; - std::array<QuadFrame32, 3> intermediate_mixes = {}; + // TODO: Check dsp::DSP semaphore (which indicates emulated application has finished writing to shared memory region) + current_frame = GenerateCurrentFrame(); - 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); - } - } + 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 4459a5668..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 @@ -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/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 index daaf6e3f3..30552fe26 100644 --- a/src/audio_core/hle/source.cpp +++ b/src/audio_core/hle/source.cpp @@ -126,13 +126,13 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_l 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"); + 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"); + LOG_TRACE(Audio_DSP, "source_id=%zu biquad filter update", source_id); } if (config.interpolation_dirty) { 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 2c7e80106..43a766053 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -2,8 +2,6 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(SRCS - config/controller_config.cpp - config/controller_config_util.cpp config.cpp debugger/callstack.cpp debugger/disassembler.cpp @@ -20,6 +18,7 @@ set(SRCS util/spinbox.cpp util/util.cpp bootmanager.cpp + configure_audio.cpp configure_debug.cpp configure_dialog.cpp configure_general.cpp @@ -32,8 +31,6 @@ set(SRCS ) set(HEADERS - config/controller_config.h - config/controller_config_util.h config.h debugger/callstack.h debugger/disassembler.h @@ -51,6 +48,7 @@ set(HEADERS util/spinbox.h util/util.h bootmanager.h + configure_audio.h configure_debug.h configure_dialog.h configure_general.h @@ -63,12 +61,12 @@ set(HEADERS ) set(UIS - config/controller_config.ui debugger/callstack.ui debugger/disassembler.ui 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/config/controller_config.cpp b/src/citra_qt/config/controller_config.cpp deleted file mode 100644 index 512879f1b..000000000 --- a/src/citra_qt/config/controller_config.cpp +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <QDialogButtonBox> - -#include "controller_config.h" -#include "controller_config_util.h" - -/* TODO(bunnei): ImplementMe - -using common::Config; - -GControllerConfig::GControllerConfig(common::Config::ControllerPort* initial_config, QWidget* parent) : QWidget(parent) -{ - ui.setupUi(this); - ((QGridLayout*)ui.mainStickTab->layout())->addWidget(new GStickConfig(Config::ANALOG_LEFT, Config::ANALOG_RIGHT, Config::ANALOG_UP, Config::ANALOG_DOWN, this, this), 1, 1); - ((QGridLayout*)ui.cStickTab->layout())->addWidget(new GStickConfig(Config::C_LEFT, Config::C_RIGHT, Config::C_UP, Config::C_DOWN, this, this), 1, 1); - ((QGridLayout*)ui.dPadTab->layout())->addWidget(new GStickConfig(Config::DPAD_LEFT, Config::DPAD_RIGHT, Config::DPAD_UP, Config::DPAD_DOWN, this, this), 1, 1); - - // TODO: Arrange these more compactly? - QVBoxLayout* layout = (QVBoxLayout*)ui.buttonsTab->layout(); - layout->addWidget(new GButtonConfigGroup("A Button", Config::BUTTON_A, this, ui.buttonsTab)); - layout->addWidget(new GButtonConfigGroup("B Button", Config::BUTTON_B, this, ui.buttonsTab)); - layout->addWidget(new GButtonConfigGroup("X Button", Config::BUTTON_X, this, ui.buttonsTab)); - layout->addWidget(new GButtonConfigGroup("Y Button", Config::BUTTON_Y, this, ui.buttonsTab)); - layout->addWidget(new GButtonConfigGroup("Z Button", Config::BUTTON_Z, this, ui.buttonsTab)); - layout->addWidget(new GButtonConfigGroup("L Trigger", Config::TRIGGER_L, this, ui.buttonsTab)); - layout->addWidget(new GButtonConfigGroup("R Trigger", Config::TRIGGER_R, this, ui.buttonsTab)); - layout->addWidget(new GButtonConfigGroup("Start Button", Config::BUTTON_START, this, ui.buttonsTab)); - - memcpy(config, initial_config, sizeof(config)); - - emit ActivePortChanged(config[0]); -} - -void GControllerConfig::OnKeyConfigChanged(common::Config::Control id, int key, const QString& name) -{ - if (InputSourceJoypad()) - { - config[GetActiveController()].pads.key_code[id] = key; - } - else - { - config[GetActiveController()].keys.key_code[id] = key; - } - emit ActivePortChanged(config[GetActiveController()]); -} - -int GControllerConfig::GetActiveController() -{ - return ui.activeControllerCB->currentIndex(); -} - -bool GControllerConfig::InputSourceJoypad() -{ - return ui.inputSourceCB->currentIndex() == 1; -} - -GControllerConfigDialog::GControllerConfigDialog(common::Config::ControllerPort* controller_ports, QWidget* parent) : QDialog(parent), config_ptr(controller_ports) -{ - setWindowTitle(tr("Input configuration")); - - QVBoxLayout* layout = new QVBoxLayout(this); - config_widget = new GControllerConfig(controller_ports, this); - layout->addWidget(config_widget); - - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - layout->addWidget(buttons); - - connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); - connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); - - connect(this, SIGNAL(accepted()), this, SLOT(EnableChanges())); - - layout->setSizeConstraint(QLayout::SetFixedSize); - setLayout(layout); - setModal(true); - show(); -} - -void GControllerConfigDialog::EnableChanges() -{ - for (unsigned int i = 0; i < 4; ++i) - { - memcpy(&config_ptr[i], &config_widget->GetControllerConfig(i), sizeof(common::Config::ControllerPort)); - - if (common::g_config) { - // Apply changes if running a game - memcpy(&common::g_config->controller_ports(i), &config_widget->GetControllerConfig(i), sizeof(common::Config::ControllerPort)); - } - } -} - -*/ diff --git a/src/citra_qt/config/controller_config.h b/src/citra_qt/config/controller_config.h deleted file mode 100644 index 451593de1..000000000 --- a/src/citra_qt/config/controller_config.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#ifndef _CONTROLLER_CONFIG_HXX_ -#define _CONTROLLER_CONFIG_HXX_ - -#include <QDialog> - -//#include "ui_controller_config.h" - -/* TODO(bunnei): ImplementMe - -#include "config.h" - -class GControllerConfig : public QWidget -{ - Q_OBJECT - -public: - GControllerConfig(common::Config::ControllerPort* initial_config, QWidget* parent = NULL); - - const common::Config::ControllerPort& GetControllerConfig(int index) const { return config[index]; } - -signals: - void ActivePortChanged(const common::Config::ControllerPort&); - -public slots: - void OnKeyConfigChanged(common::Config::Control id, int key, const QString& name); - -private: - int GetActiveController(); - bool InputSourceJoypad(); - - Ui::ControllerConfig ui; - common::Config::ControllerPort config[4]; -}; - -class GControllerConfigDialog : public QDialog -{ - Q_OBJECT - -public: - GControllerConfigDialog(common::Config::ControllerPort* controller_ports, QWidget* parent = NULL); - -public slots: - void EnableChanges(); - -private: - GControllerConfig* config_widget; - common::Config::ControllerPort* config_ptr; -}; - -*/ - -#endif // _CONTROLLER_CONFIG_HXX_ diff --git a/src/citra_qt/config/controller_config.ui b/src/citra_qt/config/controller_config.ui deleted file mode 100644 index 9f650047b..000000000 --- a/src/citra_qt/config/controller_config.ui +++ /dev/null @@ -1,308 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ControllerConfig</class> - <widget class="QWidget" name="ControllerConfig"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>503</width> - <height>293</height> - </rect> - </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="windowTitle"> - <string>Controller Configuration</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <property name="sizeConstraint"> - <enum>QLayout::SetFixedSize</enum> - </property> - <item> - <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="1"> - <widget class="QComboBox" name="activeControllerCB"> - <item> - <property name="text"> - <string>Controller 1</string> - </property> - </item> - <item> - <property name="text"> - <string>Controller 2</string> - </property> - </item> - <item> - <property name="text"> - <string>Controller 3</string> - </property> - </item> - <item> - <property name="text"> - <string>Controller 4</string> - </property> - </item> - </widget> - </item> - <item row="0" column="2"> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="2"> - <widget class="QCheckBox" name="checkBox"> - <property name="text"> - <string>Enabled</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QComboBox" name="inputSourceCB"> - <item> - <property name="text"> - <string>Keyboard</string> - </property> - </item> - <item> - <property name="text"> - <string>Joypad</string> - </property> - </item> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Active Controller:</string> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Input Source:</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="mainStickTab"> - <attribute name="title"> - <string>Main Stick</string> - </attribute> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="2" column="2"> - <spacer name="verticalSpacer_2"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="2"> - <spacer name="verticalSpacer_3"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="0"> - <spacer name="horizontalSpacer_4"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="4"> - <spacer name="horizontalSpacer_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - <widget class="QWidget" name="cStickTab"> - <attribute name="title"> - <string>C-Stick</string> - </attribute> - <layout class="QGridLayout" name="gridLayout_4"> - <item row="1" column="0"> - <spacer name="horizontalSpacer_6"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>0</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="1"> - <spacer name="verticalSpacer_5"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="2" column="1"> - <spacer name="verticalSpacer_4"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="2"> - <spacer name="horizontalSpacer_5"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - <widget class="QWidget" name="dPadTab"> - <attribute name="title"> - <string>D-Pad</string> - </attribute> - <layout class="QGridLayout" name="gridLayout_5"> - <item row="1" column="2"> - <spacer name="horizontalSpacer_7"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="1"> - <spacer name="verticalSpacer_7"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="2" column="1"> - <spacer name="verticalSpacer_6"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="0"> - <spacer name="horizontalSpacer_8"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - <widget class="QWidget" name="buttonsTab"> - <attribute name="title"> - <string>Buttons</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_2"/> - </widget> - </widget> - </item> - </layout> - </widget> - <resources/> - <connections/> - <slots> - <signal>ControlsChanged()</signal> - <signal>MainStickCleared()</signal> - <signal>CStickCleared()</signal> - <signal>DPadCleared()</signal> - <signal>ButtonsCleared()</signal> - <slot>OnControlsChanged()</slot> - <slot>OnMainStickCleared()</slot> - <slot>OnCStickCleared()</slot> - <slot>OnDPadCleared()</slot> - <slot>OnButtonsCleared()</slot> - </slots> -</ui> diff --git a/src/citra_qt/config/controller_config_util.cpp b/src/citra_qt/config/controller_config_util.cpp deleted file mode 100644 index d68b119df..000000000 --- a/src/citra_qt/config/controller_config_util.cpp +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <QPushButton> -#include <QStyle> -#include <QGridLayout> -#include <QKeyEvent> -#include <QHBoxLayout> -#include <QLabel> - -#include "controller_config_util.h" - -/* TODO(bunnei): ImplementMe -GStickConfig::GStickConfig(common::Config::Control leftid, common::Config::Control rightid, common::Config::Control upid, common::Config::Control downid, QObject* change_receiver, QWidget* parent) : QWidget(parent) -{ - left = new GKeyConfigButton(leftid, style()->standardIcon(QStyle::SP_ArrowLeft), QString(), change_receiver, this); - right = new GKeyConfigButton(rightid, style()->standardIcon(QStyle::SP_ArrowRight), QString(), change_receiver, this); - up = new GKeyConfigButton(upid, style()->standardIcon(QStyle::SP_ArrowUp), QString(), change_receiver, this); - down = new GKeyConfigButton(downid, style()->standardIcon(QStyle::SP_ArrowDown), QString(), change_receiver, this); - clear = new QPushButton(tr("Clear"), this); - - QGridLayout* layout = new QGridLayout(this); - layout->addWidget(left, 1, 0); - layout->addWidget(right, 1, 2); - layout->addWidget(up, 0, 1); - layout->addWidget(down, 2, 1); - layout->addWidget(clear, 1, 1); - - setLayout(layout); -} - -GKeyConfigButton::GKeyConfigButton(common::Config::Control id, const QIcon& icon, const QString& text, QObject* change_receiver, QWidget* parent) : QPushButton(icon, text, parent), id(id), inputGrabbed(false) -{ - connect(this, SIGNAL(clicked()), this, SLOT(OnClicked())); - connect(this, SIGNAL(KeyAssigned(common::Config::Control, int, const QString&)), change_receiver, SLOT(OnKeyConfigChanged(common::Config::Control, int, const QString&))); - connect(change_receiver, SIGNAL(ActivePortChanged(const common::Config::ControllerPort&)), this, SLOT(OnActivePortChanged(const common::Config::ControllerPort&))); -} - -GKeyConfigButton::GKeyConfigButton(common::Config::Control id, const QString& text, QObject* change_receiver, QWidget* parent) : QPushButton(text, parent), id(id), inputGrabbed(false) -{ - connect(this, SIGNAL(clicked()), this, SLOT(OnClicked())); - connect(this, SIGNAL(KeyAssigned(common::Config::Control, int, const QString&)), change_receiver, SLOT(OnKeyConfigChanged(common::Config::Control, int, const QString&))); - connect(change_receiver, SIGNAL(ActivePortChanged(const common::Config::ControllerPort&)), this, SLOT(OnActivePortChanged(const common::Config::ControllerPort&))); -} - -void GKeyConfigButton::OnActivePortChanged(const common::Config::ControllerPort& config) -{ - // TODO: Doesn't use joypad struct if that's the input source... - QString text = QKeySequence(config.keys.key_code[id]).toString(); // has a nicer format - if (config.keys.key_code[id] == Qt::Key_Shift) text = tr("Shift"); - else if (config.keys.key_code[id] == Qt::Key_Control) text = tr("Control"); - else if (config.keys.key_code[id] == Qt::Key_Alt) text = tr("Alt"); - else if (config.keys.key_code[id] == Qt::Key_Meta) text = tr("Meta"); - setText(text); -} - -void GKeyConfigButton::OnClicked() -{ - grabKeyboard(); - grabMouse(); - inputGrabbed = true; - - old_text = text(); - setText(tr("Input...")); -} - -void GKeyConfigButton::keyPressEvent(QKeyEvent* event) -{ - if (inputGrabbed) - { - releaseKeyboard(); - releaseMouse(); - setText(QString()); - - // TODO: Doesn't capture "return" key - // TODO: This doesn't quite work well, yet... find a better way - QString text = QKeySequence(event->key()).toString(); // has a nicer format than event->text() - int key = event->key(); - if (event->modifiers() == Qt::ShiftModifier) { text = tr("Shift"); key = Qt::Key_Shift; } - else if (event->modifiers() == Qt::ControlModifier) { text = tr("Ctrl"); key = Qt::Key_Control; } - else if (event->modifiers() == Qt::AltModifier) { text = tr("Alt"); key = Qt::Key_Alt; } - else if (event->modifiers() == Qt::MetaModifier) { text = tr("Meta"); key = Qt::Key_Meta; } - - setText(old_text); - emit KeyAssigned(id, key, text); - - inputGrabbed = false; - - // TODO: Keys like "return" cause another keyPressEvent to be generated after this one... - } - - QPushButton::keyPressEvent(event); // TODO: Necessary? -} - -void GKeyConfigButton::mousePressEvent(QMouseEvent* event) -{ - // Abort key assignment - if (inputGrabbed) - { - releaseKeyboard(); - releaseMouse(); - setText(old_text); - inputGrabbed = false; - } - - QAbstractButton::mousePressEvent(event); -} - -GButtonConfigGroup::GButtonConfigGroup(const QString& name, common::Config::Control id, QObject* change_receiver, QWidget* parent) : QWidget(parent), id(id) -{ - QHBoxLayout* layout = new QHBoxLayout(this); - - QPushButton* clear_button = new QPushButton(tr("Clear")); - - layout->addWidget(new QLabel(name, this)); - layout->addWidget(config_button = new GKeyConfigButton(id, QString(), change_receiver, this)); - layout->addWidget(clear_button); - - // TODO: connect config_button, clear_button - - setLayout(layout); -} - -*/ diff --git a/src/citra_qt/config/controller_config_util.h b/src/citra_qt/config/controller_config_util.h deleted file mode 100644 index 15e025b57..000000000 --- a/src/citra_qt/config/controller_config_util.h +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#ifndef _CONTROLLER_CONFIG_UTIL_HXX_ -#define _CONTROLLER_CONFIG_UTIL_HXX_ - -#include <QWidget> -#include <QPushButton> - -/* TODO(bunnei): ImplementMe - -#include "config.h" - -class GStickConfig : public QWidget -{ - Q_OBJECT - -public: - // change_receiver needs to have a OnKeyConfigChanged(common::Config::Control, int, const QString&) slot! - GStickConfig(common::Config::Control leftid, common::Config::Control rightid, common::Config::Control upid, common::Config::Control downid, QObject* change_receiver, QWidget* parent = NULL); - -signals: - void LeftChanged(); - void RightChanged(); - void UpChanged(); - void DownChanged(); - -private: - QPushButton* left; - QPushButton* right; - QPushButton* up; - QPushButton* down; - - QPushButton* clear; -}; - -class GKeyConfigButton : public QPushButton -{ - Q_OBJECT - -public: - // TODO: change_receiver also needs to have an ActivePortChanged(const common::Config::ControllerPort&) signal - // change_receiver needs to have a OnKeyConfigChanged(common::Config::Control, int, const QString&) slot! - GKeyConfigButton(common::Config::Control id, const QIcon& icon, const QString& text, QObject* change_receiver, QWidget* parent); - GKeyConfigButton(common::Config::Control id, const QString& text, QObject* change_receiver, QWidget* parent); - -signals: - void KeyAssigned(common::Config::Control id, int key, const QString& text); - -private slots: - void OnActivePortChanged(const common::Config::ControllerPort& config); - - void OnClicked(); - - void keyPressEvent(QKeyEvent* event); // TODO: bGrabbed? - void mousePressEvent(QMouseEvent* event); - -private: - common::Config::Control id; - bool inputGrabbed; - - QString old_text; -}; - -class GButtonConfigGroup : public QWidget -{ - Q_OBJECT - -public: - // change_receiver needs to have a OnKeyConfigChanged(common::Config::Control, int, const QString&) slot! - GButtonConfigGroup(const QString& name, common::Config::Control id, QObject* change_receiver, QWidget* parent = NULL); - -private: - GKeyConfigButton* config_button; - - common::Config::Control id; -}; - -*/ - -#endif // _CONTROLLER_CONFIG_HXX_ 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/callstack.cpp b/src/citra_qt/debugger/callstack.cpp index 793944639..1a3077495 100644 --- a/src/citra_qt/debugger/callstack.cpp +++ b/src/citra_qt/debugger/callstack.cpp @@ -37,10 +37,13 @@ void CallstackWidget::OnDebugModeEntered() int counter = 0; for (u32 addr = 0x10000000; addr >= sp; addr -= 4) { + if (!Memory::IsValidVirtualAddress(addr)) + break; + const u32 ret_addr = Memory::Read32(addr); const u32 call_addr = ret_addr - 4; //get call address??? - if (Memory::GetPointer(call_addr) == nullptr) + if (!Memory::IsValidVirtualAddress(call_addr)) break; /* TODO (mattvail) clean me, move to debugger interface */ 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 6e8d7ef42..391666d35 100644 --- a/src/citra_qt/debugger/graphics_vertex_shader.cpp +++ b/src/citra_qt/debugger/graphics_vertex_shader.cpp @@ -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 d4ac9c96e..15484fae3 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -118,46 +118,33 @@ void GameList::LoadInterfaceLayout() item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); } -void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool deep_scan) +void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { const auto callback = [&](unsigned* num_entries_out, const std::string& directory, - const std::string& virtual_name) -> bool { + const std::string& virtual_name, + unsigned int recursion) -> bool { std::string physical_name = directory + DIR_SEP + virtual_name; if (stop_processing) return false; // Breaks the callback loop. - 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()); + if (!FileUtil::IsDirectory(physical_name)) { + 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; - std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(FileUtil::IOFile(physical_name, "rb"), filetype, filename_filename, physical_name); - - if (loader) - loader->ReadIcon(smdh); + loader->ReadIcon(smdh); emit EntryReady({ new GameListItemPath(QString::fromStdString(physical_name), smdh), - new GameListItem(QString::fromStdString(Loader::GetFileTypeString(filetype))), + new GameListItem(QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), new GameListItemSize(FileUtil::GetSize(physical_name)), }); + } else if (recursion > 0) { + AddFstEntriesToGameList(physical_name, recursion - 1); } return true; @@ -169,7 +156,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool d void GameListWorker::run() { stop_processing = false; - AddFstEntriesToGameList(dir_path.toStdString(), deep_scan); + AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); emit Finished(); } diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index 284f5da81..353b2d1e2 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -15,52 +15,21 @@ #include "common/string_util.h" #include "common/color.h" -#include "core/loader/loader.h" +#include "core/loader/smdh.h" #include "video_core/utils.h" /** - * Tests if data is a valid SMDH by its length and magic number. - * @param smdh_data data buffer to test - * @return bool test result - */ -static bool IsValidSMDH(const std::vector<u8>& smdh_data) { - if (smdh_data.size() < sizeof(Loader::SMDH)) - return false; - - u32 magic; - memcpy(&magic, smdh_data.data(), 4); - - return Loader::MakeMagic('S', 'M', 'D', 'H') == magic; -} - -/** * Gets game icon from SMDH * @param sdmh SMDH data * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) * @return QPixmap game icon */ -static QPixmap GetIconFromSMDH(const Loader::SMDH& smdh, bool large) { - u32 size; - const u8* icon_data; - - if (large) { - size = 48; - icon_data = smdh.large_icon.data(); - } else { - size = 24; - icon_data = smdh.small_icon.data(); - } - - QImage icon(size, size, QImage::Format::Format_RGB888); - for (u32 x = 0; x < size; ++x) { - for (u32 y = 0; y < size; ++y) { - u32 coarse_y = y & ~7; - auto v = Color::DecodeRGB565( - icon_data + VideoCore::GetMortonOffset(x, y, 2) + coarse_y * size * 2); - icon.setPixel(x, y, qRgb(v.r(), v.g(), v.b())); - } - } +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); } @@ -82,8 +51,8 @@ static QPixmap GetDefaultIcon(bool large) { * @param language title language * @return QString short title */ -static QString GetShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) { - return QString::fromUtf16(smdh.titles[static_cast<int>(language)].short_title.data()); +static QString GetQStringShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) { + return QString::fromUtf16(smdh.GetShortTitle(language).data()); } class GameListItem : public QStandardItem { @@ -112,7 +81,7 @@ public: { setData(game_path, FullPathRole); - if (!IsValidSMDH(smdh_data)) { + if (!Loader::IsValidSMDH(smdh_data)) { // SMDH is not valid, set a default icon setData(GetDefaultIcon(true), Qt::DecorationRole); return; @@ -122,10 +91,10 @@ public: memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH)); // Get icon from SMDH - setData(GetIconFromSMDH(smdh, true), Qt::DecorationRole); + setData(GetQPixmapFromSMDH(smdh, true), Qt::DecorationRole); // Get title form SMDH - setData(GetShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English), TitleRole); + setData(GetQStringShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English), TitleRole); } QVariant data(int role) const override { @@ -212,5 +181,5 @@ private: bool deep_scan; std::atomic_bool stop_processing; - void AddFstEntriesToGameList(const std::string& dir_path, bool deep_scan); + void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); }; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index dfc7c0752..0ed1ffa5a 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/file_util.cpp b/src/common/file_util.cpp index 6e2867658..17af7c385 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -434,7 +434,7 @@ bool CreateEmptyFile(const std::string &filename) } -bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback) +bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback, unsigned int recursion) { LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str()); @@ -472,7 +472,7 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo continue; unsigned ret_entries = 0; - if (!callback(&ret_entries, directory, virtual_name)) { + if (!callback(&ret_entries, directory, virtual_name, recursion)) { callback_error = true; break; } @@ -486,30 +486,34 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo closedir(dirp); #endif - if (!callback_error) { - // num_entries_out is allowed to be specified nullptr, in which case we shouldn't try to set it - if (num_entries_out != nullptr) - *num_entries_out = found_entries; - return true; - } else { + if (callback_error) return false; - } + + // num_entries_out is allowed to be specified nullptr, in which case we shouldn't try to set it + if (num_entries_out != nullptr) + *num_entries_out = found_entries; + return true; } -unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry) +unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry, unsigned int recursion) { const auto callback = [&parent_entry](unsigned* num_entries_out, const std::string& directory, - const std::string& virtual_name) -> bool { + const std::string& virtual_name, + unsigned int recursion) -> bool { FSTEntry entry; entry.virtualName = virtual_name; entry.physicalName = directory + DIR_SEP + virtual_name; if (IsDirectory(entry.physicalName)) { entry.isDirectory = true; - // is a directory, lets go inside - entry.size = ScanDirectoryTree(entry.physicalName, entry); - *num_entries_out += (int)entry.size; + // is a directory, lets go inside if we didn't recurse to often + if (recursion > 0) { + entry.size = ScanDirectoryTree(entry.physicalName, entry, recursion - 1); + *num_entries_out += (int)entry.size; + } else { + entry.size = 0; + } } else { // is a file entry.isDirectory = false; entry.size = GetSize(entry.physicalName); @@ -522,23 +526,27 @@ unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry) }; unsigned num_entries; - return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0; + return ForeachDirectoryEntry(&num_entries, directory, callback, recursion) ? num_entries : 0; } -bool DeleteDirRecursively(const std::string &directory) +bool DeleteDirRecursively(const std::string &directory, unsigned int recursion) { const static auto callback = [](unsigned* num_entries_out, const std::string& directory, - const std::string& virtual_name) -> bool { + const std::string& virtual_name, + unsigned int recursion) -> bool { std::string new_path = directory + DIR_SEP_CHR + virtual_name; - if (IsDirectory(new_path)) - return DeleteDirRecursively(new_path); + if (IsDirectory(new_path)) { + if (recursion == 0) + return false; + return DeleteDirRecursively(new_path, recursion - 1); + } return Delete(new_path); }; - if (!ForeachDirectoryEntry(nullptr, directory, callback)) + if (!ForeachDirectoryEntry(nullptr, directory, callback, recursion)) return false; // Delete the outermost directory diff --git a/src/common/file_util.h b/src/common/file_util.h index c6a8694ce..32ae2dc57 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -105,11 +105,13 @@ bool CreateEmptyFile(const std::string &filename); * @param num_entries_out to be assigned by the callable with the number of iterated directory entries, never null * @param directory the path to the enclosing directory * @param virtual_name the entry name, without any preceding directory info + * @param recursion Number of children directory to read before giving up * @return whether handling the entry succeeded */ using DirectoryEntryCallable = std::function<bool(unsigned* num_entries_out, const std::string& directory, - const std::string& virtual_name)>; + const std::string& virtual_name, + unsigned int recursion)>; /** * Scans a directory, calling the callback for each file/directory contained within. @@ -117,20 +119,22 @@ using DirectoryEntryCallable = std::function<bool(unsigned* num_entries_out, * @param num_entries_out assigned by the function with the number of iterated directory entries, can be null * @param directory the directory to scan * @param callback The callback which will be called for each entry + * @param recursion Number of children directories to read before giving up * @return whether scanning the directory succeeded */ -bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback); +bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback, unsigned int recursion = 0); /** * Scans the directory tree, storing the results. * @param directory the parent directory to start scanning from * @param parent_entry FSTEntry where the filesystem tree results will be stored. + * @param recursion Number of children directories to read before giving up. * @return the total number of files/directories found */ -unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry); +unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry, unsigned int recursion = 0); // deletes the given directory and anything under it. Returns true on success. -bool DeleteDirRecursively(const std::string &directory); +bool DeleteDirRecursively(const std::string &directory, unsigned int recursion = 256); // Returns the current directory std::string GetCurrentDir(); 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..e9b04098b 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 @@ -68,7 +70,10 @@ set(SRCS hle/service/cfg/cfg_s.cpp hle/service/cfg/cfg_u.cpp hle/service/csnd_snd.cpp - hle/service/dlp_srvr.cpp + hle/service/dlp/dlp.cpp + hle/service/dlp/dlp_clnt.cpp + hle/service/dlp/dlp_fkcl.cpp + hle/service/dlp/dlp_srvr.cpp hle/service/dsp_dsp.cpp hle/service/err_f.cpp hle/service/frd/frd.cpp @@ -119,6 +124,7 @@ set(SRCS loader/elf.cpp loader/loader.cpp loader/ncch.cpp + loader/smdh.cpp tracer/recorder.cpp memory.cpp settings.cpp @@ -175,6 +181,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 +192,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 @@ -201,7 +209,10 @@ set(HEADERS hle/service/cfg/cfg_s.h hle/service/cfg/cfg_u.h hle/service/csnd_snd.h - hle/service/dlp_srvr.h + hle/service/dlp/dlp.h + hle/service/dlp/dlp_clnt.h + hle/service/dlp/dlp_fkcl.h + hle/service/dlp/dlp_srvr.h hle/service/dsp_dsp.h hle/service/err_f.h hle/service/frd/frd.h @@ -252,6 +263,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/file_sys/archive_backend.cpp b/src/core/file_sys/archive_backend.cpp index 97adf0e12..cc0aa7022 100644 --- a/src/core/file_sys/archive_backend.cpp +++ b/src/core/file_sys/archive_backend.cpp @@ -19,22 +19,22 @@ Path::Path(LowPathType type, u32 size, u32 pointer) : type(type) { switch (type) { case Binary: { - u8* data = Memory::GetPointer(pointer); - binary = std::vector<u8>(data, data + size); + binary.resize(size); + Memory::ReadBlock(pointer, binary.data(), binary.size()); break; } case Char: { - const char* data = reinterpret_cast<const char*>(Memory::GetPointer(pointer)); - string = std::string(data, size - 1); // Data is always null-terminated. + string.resize(size - 1); // Data is always null-terminated. + Memory::ReadBlock(pointer, &string[0], string.size()); break; } case Wchar: { - const char16_t* data = reinterpret_cast<const char16_t*>(Memory::GetPointer(pointer)); - u16str = std::u16string(data, size/2 - 1); // Data is always null-terminated. + u16str.resize(size / 2 - 1); // Data is always null-terminated. + Memory::ReadBlock(pointer, &u16str[0], u16str.size() * sizeof(char16_t)); break; } 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 5191c821d..77f01d208 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,11 +29,25 @@ 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.buffer.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; + result.buffer.clear(); result.destination_id = static_cast<u32>(Service::APT::AppletId::Application); result.sender_id = static_cast<u32>(id); result.object = framebuffer_memory; @@ -55,15 +62,17 @@ 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)); - } + memcpy(&config, parameter.buffer.data(), parameter.buffer.size()); + + // TODO(Subv): Find more about this structure, result code 0 is enough to let most games continue. + MiiResult result; + memset(&result, 0, sizeof(result)); + result.result_code = 0; // Let the application know that we're closing Service::APT::MessageParameter message; - message.buffer_size = parameter.buffer_size; - message.data = parameter.data; + message.buffer.resize(sizeof(MiiResult)); + std::memcpy(message.buffer.data(), &result, message.buffer.size()); message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed); message.destination_id = static_cast<u32>(Service::APT::AppletId::Application); message.sender_id = static_cast<u32>(id); diff --git a/src/core/hle/applets/mii_selector.h b/src/core/hle/applets/mii_selector.h index c02dded4a..24e8e721d 100644 --- a/src/core/hle/applets/mii_selector.h +++ b/src/core/hle/applets/mii_selector.h @@ -24,7 +24,7 @@ struct MiiConfig { u8 unk_004; INSERT_PADDING_BYTES(3); u16 unk_008; - INSERT_PADDING_BYTES(0x8C - 0xA); + INSERT_PADDING_BYTES(0x82); u8 unk_08C; INSERT_PADDING_BYTES(3); u16 unk_090; @@ -62,19 +62,21 @@ ASSERT_REG_POSITION(unk_6C, 0x6C); 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. bool started; + + MiiConfig config; }; } diff --git a/src/core/hle/applets/swkbd.cpp b/src/core/hle/applets/swkbd.cpp index 1db6b5a17..d87bf3d57 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,11 +32,25 @@ 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.buffer.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; + result.buffer.clear(); result.destination_id = static_cast<u32>(Service::APT::AppletId::Application); result.sender_id = static_cast<u32>(id); result.object = framebuffer_memory; @@ -53,9 +60,9 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con } ResultCode SoftwareKeyboard::StartImpl(Service::APT::AppletStartupParameter const& parameter) { - ASSERT_MSG(parameter.buffer_size == sizeof(config), "The size of the parameter (SoftwareKeyboardConfig) is wrong"); + ASSERT_MSG(parameter.buffer.size() == sizeof(config), "The size of the parameter (SoftwareKeyboardConfig) is wrong"); - memcpy(&config, parameter.data, parameter.buffer_size); + memcpy(&config, parameter.buffer.data(), parameter.buffer.size()); text_memory = boost::static_pointer_cast<Kernel::SharedMemory, Kernel::Object>(parameter.object); // TODO(Subv): Verify if this is the correct behavior @@ -91,7 +98,7 @@ void SoftwareKeyboard::DrawScreenKeyboard() { auto info = bottom_screen->framebuffer_info[bottom_screen->index]; // TODO(Subv): Draw the HLE keyboard, for now just zero-fill the framebuffer - memset(Memory::GetPointer(info.address_left), 0, info.stride * 320); + Memory::ZeroBlock(info.address_left, info.stride * 320); GSP_GPU::SetBufferSwap(1, info); } @@ -99,8 +106,8 @@ void SoftwareKeyboard::DrawScreenKeyboard() { void SoftwareKeyboard::Finalize() { // Let the application know that we're closing Service::APT::MessageParameter message; - message.buffer_size = sizeof(SoftwareKeyboardConfig); - message.data = reinterpret_cast<u8*>(&config); + message.buffer.resize(sizeof(SoftwareKeyboardConfig)); + std::memcpy(message.buffer.data(), &config, message.buffer.size()); message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed); message.destination_id = static_cast<u32>(Service::APT::AppletId::Application); message.sender_id = static_cast<u32>(id); 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/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 6dc95d0f1..3f6bec5fa 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) { @@ -377,7 +403,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, priority = new_priority; } - if (!Memory::GetPointer(entry_point)) { + if (!Memory::IsValidVirtualAddress(entry_point)) { LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %08x", name.c_str(), entry_point); // TODO: Verify error return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel, @@ -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()); @@ -509,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..57dedcb22 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, @@ -25,6 +26,7 @@ enum class ErrorDescription : u32 { FS_NotAFile = 250, FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive OutofRangeOrMisalignedAddress = 513, // TODO(purpasmart): Check if this name fits its actual usage + GPU_FirstInitialization = 519, FS_InvalidPath = 702, InvalidSection = 1000, TooLarge = 1001, 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/dlp_srvr.h b/src/core/hle/service/act_a.h index d65d00814..765cae644 100644 --- a/src/core/hle/service/dlp_srvr.h +++ b/src/core/hle/service/act_a.h @@ -7,16 +7,16 @@ #include "core/hle/service/service.h" //////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace DLP_SRVR +// Namespace ACT_A -namespace DLP_SRVR { +namespace ACT_A { class Interface : public Service::Interface { public: Interface(); std::string GetPortName() const override { - return "dlp:SRVR"; + return "act:a"; } }; 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..1e54a53dd 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) { @@ -182,12 +180,12 @@ void SendParameter(Service::Interface* self) { } MessageParameter param; - param.buffer_size = buffer_size; param.destination_id = dst_app_id; param.sender_id = src_app_id; param.object = Kernel::g_handle_table.GetGeneric(handle); param.signal = signal_type; - param.data = Memory::GetPointer(buffer); + param.buffer.resize(buffer_size); + Memory::ReadBlock(buffer, param.buffer.data(), param.buffer.size()); cmd_buff[1] = dest_applet->ReceiveParameter(param).raw; @@ -205,16 +203,15 @@ void ReceiveParameter(Service::Interface* self) { cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = next_parameter.sender_id; cmd_buff[3] = next_parameter.signal; // Signal type - cmd_buff[4] = next_parameter.buffer_size; // Parameter buffer size + cmd_buff[4] = next_parameter.buffer.size(); // Parameter buffer size cmd_buff[5] = 0x10; cmd_buff[6] = 0; if (next_parameter.object != nullptr) cmd_buff[6] = Kernel::g_handle_table.Create(next_parameter.object).MoveFrom(); - cmd_buff[7] = (next_parameter.buffer_size << 14) | 2; + cmd_buff[7] = (next_parameter.buffer.size() << 14) | 2; cmd_buff[8] = buffer; - if (next_parameter.data) - memcpy(Memory::GetPointer(buffer), next_parameter.data, std::min(buffer_size, next_parameter.buffer_size)); + Memory::WriteBlock(buffer, next_parameter.buffer.data(), next_parameter.buffer.size()); LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); } @@ -228,16 +225,15 @@ void GlanceParameter(Service::Interface* self) { cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = next_parameter.sender_id; cmd_buff[3] = next_parameter.signal; // Signal type - cmd_buff[4] = next_parameter.buffer_size; // Parameter buffer size + cmd_buff[4] = next_parameter.buffer.size(); // Parameter buffer size cmd_buff[5] = 0x10; cmd_buff[6] = 0; if (next_parameter.object != nullptr) cmd_buff[6] = Kernel::g_handle_table.Create(next_parameter.object).MoveFrom(); - cmd_buff[7] = (next_parameter.buffer_size << 14) | 2; + cmd_buff[7] = (next_parameter.buffer.size() << 14) | 2; cmd_buff[8] = buffer; - if (next_parameter.data) - memcpy(Memory::GetPointer(buffer), next_parameter.data, std::min(buffer_size, next_parameter.buffer_size)); + Memory::WriteBlock(buffer, next_parameter.buffer.data(), std::min(static_cast<size_t>(buffer_size), next_parameter.buffer.size())); LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); } @@ -264,6 +260,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," @@ -371,14 +371,36 @@ void StartLibraryApplet(Service::Interface* self) { return; } + size_t buffer_size = cmd_buff[2]; + VAddr buffer_addr = cmd_buff[6]; + AppletStartupParameter parameter; - parameter.buffer_size = cmd_buff[2]; parameter.object = Kernel::g_handle_table.GetGeneric(cmd_buff[4]); - parameter.data = Memory::GetPointer(cmd_buff[6]); + parameter.buffer.resize(buffer_size); + Memory::ReadBlock(buffer_addr, parameter.buffer.data(), parameter.buffer.size()); 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 +436,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 +478,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 +492,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 +503,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..76b3a3807 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" @@ -19,18 +20,30 @@ struct MessageParameter { u32 sender_id = 0; u32 destination_id = 0; u32 signal = 0; - u32 buffer_size = 0; Kernel::SharedPtr<Kernel::Object> object = nullptr; - u8* data = nullptr; + std::vector<u8> buffer; }; /// Holds information about the parameters used in StartLibraryApplet struct AppletStartupParameter { - u32 buffer_size = 0; Kernel::SharedPtr<Kernel::Object> object = nullptr; - u8* data = nullptr; + std::vector<u8> buffer; }; +/// 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 +374,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/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index b9322c55d..e067db645 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -47,6 +47,12 @@ struct UsernameBlock { }; static_assert(sizeof(UsernameBlock) == 0x1C, "UsernameBlock must be exactly 0x1C bytes"); +struct BirthdayBlock { + u8 month; ///< The month of the birthday + u8 day; ///< The day of the birthday +}; +static_assert(sizeof(BirthdayBlock) == 2, "BirthdayBlock must be exactly 2 bytes"); + struct ConsoleModelInfo { u8 model; ///< The console model (3DS, 2DS, etc) u8 unknown[3]; ///< Unknown data @@ -65,9 +71,8 @@ static const u64 CFG_SAVE_ID = 0x00010017; static const u64 CONSOLE_UNIQUE_ID = 0xDEADC0DE; static const ConsoleModelInfo CONSOLE_MODEL = { NINTENDO_3DS_XL, { 0, 0, 0 } }; static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN; -static const char CONSOLE_USERNAME[0x14] = "CITRA"; -/// This will be initialized in Init, and will be used when creating the block -static UsernameBlock CONSOLE_USERNAME_BLOCK; +static const UsernameBlock CONSOLE_USERNAME_BLOCK = { u"CITRA", 0, 0 }; +static const BirthdayBlock PROFILE_BIRTHDAY = { 3, 25 }; // March 25th, 2014 /// TODO(Subv): Find out what this actually is static const u8 SOUND_OUTPUT_MODE = 2; static const u8 UNITED_STATES_COUNTRY_ID = 49; @@ -191,28 +196,32 @@ void GetConfigInfoBlk2(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 size = cmd_buff[1]; u32 block_id = cmd_buff[2]; - u8* data_pointer = Memory::GetPointer(cmd_buff[4]); + VAddr data_pointer = cmd_buff[4]; - if (data_pointer == nullptr) { + if (!Memory::IsValidVirtualAddress(data_pointer)) { cmd_buff[1] = -1; // TODO(Subv): Find the right error code return; } - cmd_buff[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x2, data_pointer).raw; + std::vector<u8> data(size); + cmd_buff[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x2, data.data()).raw; + Memory::WriteBlock(data_pointer, data.data(), data.size()); } void GetConfigInfoBlk8(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 size = cmd_buff[1]; u32 block_id = cmd_buff[2]; - u8* data_pointer = Memory::GetPointer(cmd_buff[4]); + VAddr data_pointer = cmd_buff[4]; - if (data_pointer == nullptr) { + if (!Memory::IsValidVirtualAddress(data_pointer)) { cmd_buff[1] = -1; // TODO(Subv): Find the right error code return; } - cmd_buff[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x8, data_pointer).raw; + std::vector<u8> data(size); + cmd_buff[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x8, data.data()).raw; + Memory::WriteBlock(data_pointer, data.data(), data.size()); } void UpdateConfigNANDSavegame(Service::Interface* self) { @@ -329,32 +338,22 @@ ResultCode FormatConfig() { res = CreateConfigInfoBlk(0x00050005, sizeof(STEREO_CAMERA_SETTINGS), 0xE, STEREO_CAMERA_SETTINGS.data()); if (!res.IsSuccess()) return res; + res = CreateConfigInfoBlk(0x00070001, sizeof(SOUND_OUTPUT_MODE), 0xE, &SOUND_OUTPUT_MODE); if (!res.IsSuccess()) return res; + res = CreateConfigInfoBlk(0x00090001, sizeof(CONSOLE_UNIQUE_ID), 0xE, &CONSOLE_UNIQUE_ID); if (!res.IsSuccess()) return res; - res = CreateConfigInfoBlk(0x000A0000, sizeof(CONSOLE_USERNAME_BLOCK), 0xE, &CONSOLE_USERNAME_BLOCK); - if (!res.IsSuccess()) return res; - // 0x000A0000 - Profile username - struct { - u16_le username[10]; - u8 unused[4]; - u32_le wordfilter_version; // Unused by Citra - } profile_username = {}; - - std::u16string username_string = Common::UTF8ToUTF16("Citra"); - std::copy(username_string.cbegin(), username_string.cend(), profile_username.username); - res = CreateConfigInfoBlk(0x000A0000, sizeof(profile_username), 0xE, &profile_username); + res = CreateConfigInfoBlk(0x000A0000, sizeof(CONSOLE_USERNAME_BLOCK), 0xE, &CONSOLE_USERNAME_BLOCK); if (!res.IsSuccess()) return res; - // 0x000A0001 - Profile birthday - const u8 profile_birthday[2] = {3, 25}; // March 25th, 2014 - res = CreateConfigInfoBlk(0x000A0001, sizeof(profile_birthday), 0xE, profile_birthday); + res = CreateConfigInfoBlk(0x000A0001, sizeof(PROFILE_BIRTHDAY), 0xE, &PROFILE_BIRTHDAY); if (!res.IsSuccess()) return res; res = CreateConfigInfoBlk(0x000A0002, sizeof(CONSOLE_LANGUAGE), 0xE, &CONSOLE_LANGUAGE); if (!res.IsSuccess()) return res; + res = CreateConfigInfoBlk(0x000B0000, sizeof(COUNTRY_INFO), 0xE, &COUNTRY_INFO); if (!res.IsSuccess()) return res; @@ -435,17 +434,6 @@ void Init() { return; } - // Initialize the Username block - // TODO(Subv): Initialize this directly in the variable when MSVC supports char16_t string literals - memset(&CONSOLE_USERNAME_BLOCK, 0, sizeof(CONSOLE_USERNAME_BLOCK)); - CONSOLE_USERNAME_BLOCK.ng_word = 0; - CONSOLE_USERNAME_BLOCK.zero = 0; - - // Copy string to buffer and pad with zeros at the end - auto size = Common::UTF8ToUTF16(CONSOLE_USERNAME).copy(CONSOLE_USERNAME_BLOCK.username, 0x14); - std::fill(std::begin(CONSOLE_USERNAME_BLOCK.username) + size, - std::end(CONSOLE_USERNAME_BLOCK.username), 0); - FormatConfig(); } 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/dlp/dlp.cpp b/src/core/hle/service/dlp/dlp.cpp new file mode 100644 index 000000000..7c8db794b --- /dev/null +++ b/src/core/hle/service/dlp/dlp.cpp @@ -0,0 +1,24 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/service.h" +#include "core/hle/service/dlp/dlp.h" +#include "core/hle/service/dlp/dlp_clnt.h" +#include "core/hle/service/dlp/dlp_fkcl.h" +#include "core/hle/service/dlp/dlp_srvr.h" + +namespace Service { +namespace DLP { + +void Init() { + AddService(new DLP_CLNT_Interface); + AddService(new DLP_FKCL_Interface); + AddService(new DLP_SRVR_Interface); +} + +void Shutdown() { +} + +} // namespace DLP +} // namespace Service diff --git a/src/core/hle/service/dlp/dlp.h b/src/core/hle/service/dlp/dlp.h new file mode 100644 index 000000000..ec2fe46e8 --- /dev/null +++ b/src/core/hle/service/dlp/dlp.h @@ -0,0 +1,15 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +namespace Service { +namespace DLP { + +/// Initializes the DLP services. +void Init(); + +/// Shuts down the DLP services. +void Shutdown(); + +} // namespace DLP +} // namespace Service diff --git a/src/core/hle/service/dlp/dlp_clnt.cpp b/src/core/hle/service/dlp/dlp_clnt.cpp new file mode 100644 index 000000000..0b31d47df --- /dev/null +++ b/src/core/hle/service/dlp/dlp_clnt.cpp @@ -0,0 +1,20 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/dlp/dlp_clnt.h" + +namespace Service { +namespace DLP { + +const Interface::FunctionInfo FunctionTable[] = { + {0x000100C3, nullptr, "Initialize"}, + {0x00110000, nullptr, "GetWirelessRebootPassphrase"}, +}; + +DLP_CLNT_Interface::DLP_CLNT_Interface() { + Register(FunctionTable); +} + +} // namespace DLP +} // namespace Service diff --git a/src/core/hle/service/dlp/dlp_clnt.h b/src/core/hle/service/dlp/dlp_clnt.h new file mode 100644 index 000000000..067f11e37 --- /dev/null +++ b/src/core/hle/service/dlp/dlp_clnt.h @@ -0,0 +1,22 @@ +// 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 Service { +namespace DLP { + +class DLP_CLNT_Interface final : public Interface { +public: + DLP_CLNT_Interface(); + + std::string GetPortName() const override { + return "dlp:CLNT"; + } +}; + +} // namespace DLP +} // namespace Service diff --git a/src/core/hle/service/dlp/dlp_fkcl.cpp b/src/core/hle/service/dlp/dlp_fkcl.cpp new file mode 100644 index 000000000..a845260e5 --- /dev/null +++ b/src/core/hle/service/dlp/dlp_fkcl.cpp @@ -0,0 +1,20 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/dlp/dlp_fkcl.h" + +namespace Service { +namespace DLP { + +const Interface::FunctionInfo FunctionTable[] = { + {0x00010083, nullptr, "Initialize"}, + {0x000F0000, nullptr, "GetWirelessRebootPassphrase"}, +}; + +DLP_FKCL_Interface::DLP_FKCL_Interface() { + Register(FunctionTable); +} + +} // namespace DLP +} // namespace Service diff --git a/src/core/hle/service/dlp/dlp_fkcl.h b/src/core/hle/service/dlp/dlp_fkcl.h new file mode 100644 index 000000000..e4837a167 --- /dev/null +++ b/src/core/hle/service/dlp/dlp_fkcl.h @@ -0,0 +1,22 @@ +// 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 Service { +namespace DLP { + +class DLP_FKCL_Interface final : public Interface { +public: + DLP_FKCL_Interface(); + + std::string GetPortName() const override { + return "dlp:FKCL"; + } +}; + +} // namespace DLP +} // namespace Service diff --git a/src/core/hle/service/dlp_srvr.cpp b/src/core/hle/service/dlp/dlp_srvr.cpp index 1f30188da..da9b30f56 100644 --- a/src/core/hle/service/dlp_srvr.cpp +++ b/src/core/hle/service/dlp/dlp_srvr.cpp @@ -2,16 +2,15 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/common_types.h" #include "common/logging/log.h" -#include "core/hle/hle.h" -#include "core/hle/service/dlp_srvr.h" +#include "core/hle/result.h" +#include "core/hle/service/dlp/dlp_srvr.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace DLP_SRVR +namespace Service { +namespace DLP { -namespace DLP_SRVR { - -static void unk_0x000E0040(Service::Interface* self) { +static void unk_0x000E0040(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; @@ -23,14 +22,13 @@ static void unk_0x000E0040(Service::Interface* self) { const Interface::FunctionInfo FunctionTable[] = { {0x00010183, nullptr, "Initialize"}, {0x00020000, nullptr, "Finalize"}, + {0x000800C0, nullptr, "SendWirelessRebootPassphrase"}, {0x000E0040, unk_0x000E0040, "unk_0x000E0040"}, }; -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Interface class - -Interface::Interface() { +DLP_SRVR_Interface::DLP_SRVR_Interface() { Register(FunctionTable); } -} // namespace +} // namespace DLP +} // namespace Service diff --git a/src/core/hle/service/dlp/dlp_srvr.h b/src/core/hle/service/dlp/dlp_srvr.h new file mode 100644 index 000000000..19fe17840 --- /dev/null +++ b/src/core/hle/service/dlp/dlp_srvr.h @@ -0,0 +1,22 @@ +// 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 Service { +namespace DLP { + +class DLP_SRVR_Interface final : public Interface { +public: + DLP_SRVR_Interface(); + + std::string GetPortName() const override { + return "dlp:SRVR"; + } +}; + +} // namespace DLP +} // namespace Service diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp index 995bee3f9..c8aadd9db 100644 --- a/src/core/hle/service/dsp_dsp.cpp +++ b/src/core/hle/service/dsp_dsp.cpp @@ -140,12 +140,15 @@ static void LoadComponent(Service::Interface* self) { // TODO(bunnei): Implement real DSP firmware loading - ASSERT(Memory::GetPointer(buffer) != nullptr); - ASSERT(size > 0x37C); + ASSERT(Memory::IsValidVirtualAddress(buffer)); + + std::vector<u8> component_data(size); + Memory::ReadBlock(buffer, component_data.data(), component_data.size()); - LOG_INFO(Service_DSP, "Firmware hash: %#" PRIx64, Common::ComputeHash64(Memory::GetPointer(buffer), size)); + LOG_INFO(Service_DSP, "Firmware hash: %#" PRIx64, Common::ComputeHash64(component_data.data(), component_data.size())); // Some versions of the firmware have the location of DSP structures listed here. - LOG_INFO(Service_DSP, "Structures hash: %#" PRIx64, Common::ComputeHash64(Memory::GetPointer(buffer) + 0x340, 60)); + ASSERT(size > 0x37C); + LOG_INFO(Service_DSP, "Structures hash: %#" PRIx64, Common::ComputeHash64(component_data.data() + 0x340, 60)); LOG_WARNING(Service_DSP, "(STUBBED) called size=0x%X, prog_mask=0x%08X, data_mask=0x%08X, buffer=0x%08X", size, prog_mask, data_mask, buffer); @@ -285,10 +288,10 @@ static void WriteProcessPipe(Service::Interface* self) { return; } - ASSERT_MSG(Memory::GetPointer(buffer) != nullptr, "Invalid Buffer: pipe=%u, size=0x%X, buffer=0x%08X", pipe_index, size, buffer); + ASSERT_MSG(Memory::IsValidVirtualAddress(buffer), "Invalid Buffer: pipe=%u, size=0x%X, buffer=0x%08X", pipe, 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); } @@ -324,7 +327,7 @@ static void ReadPipeIfPossible(Service::Interface* self) { DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index); - ASSERT_MSG(Memory::GetPointer(addr) != nullptr, "Invalid addr: pipe=%u, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe_index, unknown, size, addr); + ASSERT_MSG(Memory::IsValidVirtualAddress(addr), "Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe, unknown, size, addr); cmd_buff[0] = IPC::MakeHeader(0x10, 1, 2); cmd_buff[1] = RESULT_SUCCESS.raw; // No error @@ -364,7 +367,7 @@ static void ReadPipe(Service::Interface* self) { DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index); - ASSERT_MSG(Memory::GetPointer(addr) != nullptr, "Invalid addr: pipe=%u, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe_index, unknown, size, addr); + ASSERT_MSG(Memory::IsValidVirtualAddress(addr), "Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe, unknown, size, addr); if (DSP::HLE::GetPipeReadableSize(pipe) >= size) { std::vector<u8> response = DSP::HLE::PipeRead(pipe, size); @@ -403,7 +406,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 +443,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/frd/frd.cpp b/src/core/hle/service/frd/frd.cpp index 15d604bb6..29d144365 100644 --- a/src/core/hle/service/frd/frd.cpp +++ b/src/core/hle/service/frd/frd.cpp @@ -23,7 +23,7 @@ void GetMyPresence(Service::Interface* self) { ASSERT(shifted_out_size == ((sizeof(MyPresence) << 14) | 2)); - Memory::WriteBlock(my_presence_addr, reinterpret_cast<const u8*>(&my_presence), sizeof(MyPresence)); + Memory::WriteBlock(my_presence_addr, &my_presence, sizeof(MyPresence)); cmd_buff[1] = RESULT_SUCCESS.raw; // No error @@ -39,8 +39,7 @@ void GetFriendKeyList(Service::Interface* self) { FriendKey zero_key = {}; for (u32 i = 0; i < frd_count; ++i) { - Memory::WriteBlock(frd_key_addr + i * sizeof(FriendKey), - reinterpret_cast<const u8*>(&zero_key), sizeof(FriendKey)); + Memory::WriteBlock(frd_key_addr + i * sizeof(FriendKey), &zero_key, sizeof(FriendKey)); } cmd_buff[1] = RESULT_SUCCESS.raw; // No error @@ -58,8 +57,7 @@ void GetFriendProfile(Service::Interface* self) { Profile zero_profile = {}; for (u32 i = 0; i < count; ++i) { - Memory::WriteBlock(profiles_addr + i * sizeof(Profile), - reinterpret_cast<const u8*>(&zero_profile), sizeof(Profile)); + Memory::WriteBlock(profiles_addr + i * sizeof(Profile), &zero_profile, sizeof(Profile)); } cmd_buff[1] = RESULT_SUCCESS.raw; // No error @@ -88,7 +86,7 @@ void GetMyFriendKey(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; // No error - Memory::WriteBlock(cmd_buff[2], reinterpret_cast<const u8*>(&my_friend_key), sizeof(FriendKey)); + Memory::WriteBlock(cmd_buff[2], &my_friend_key, sizeof(FriendKey)); LOG_WARNING(Service_FRD, "(STUBBED) called"); } diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index cc51ede0c..81b9abe4c 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -108,13 +108,14 @@ ResultVal<bool> File::SyncRequest() { offset, length, backend->GetSize()); } - ResultVal<size_t> read = backend->Read(offset, length, Memory::GetPointer(address)); + std::vector<u8> data(length); + ResultVal<size_t> read = backend->Read(offset, data.size(), data.data()); if (read.Failed()) { cmd_buff[1] = read.Code().raw; return read.Code(); } + Memory::WriteBlock(address, data.data(), *read); cmd_buff[2] = static_cast<u32>(*read); - Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(address), length); break; } @@ -128,7 +129,9 @@ ResultVal<bool> File::SyncRequest() { LOG_TRACE(Service_FS, "Write %s %s: offset=0x%llx length=%d address=0x%x, flush=0x%x", GetTypeName().c_str(), GetName().c_str(), offset, length, address, flush); - ResultVal<size_t> written = backend->Write(offset, length, flush != 0, Memory::GetPointer(address)); + std::vector<u8> data(length); + Memory::ReadBlock(address, data.data(), data.size()); + ResultVal<size_t> written = backend->Write(offset, data.size(), flush != 0, data.data()); if (written.Failed()) { cmd_buff[1] = written.Code().raw; return written.Code(); @@ -216,12 +219,14 @@ ResultVal<bool> Directory::SyncRequest() { { u32 count = cmd_buff[1]; u32 address = cmd_buff[3]; - auto entries = reinterpret_cast<FileSys::Entry*>(Memory::GetPointer(address)); + std::vector<FileSys::Entry> entries(count); LOG_TRACE(Service_FS, "Read %s %s: count=%d", GetTypeName().c_str(), GetName().c_str(), count); // Number of entries actually read - cmd_buff[2] = backend->Read(count, entries); + u32 read = backend->Read(entries.size(), entries.data()); + cmd_buff[2] = read; + Memory::WriteBlock(address, entries.data(), read * sizeof(FileSys::Entry)); break; } @@ -456,11 +461,12 @@ ResultCode CreateExtSaveData(MediaType media_type, u32 high, u32 low, VAddr icon if (result.IsError()) return result; - u8* smdh_icon = Memory::GetPointer(icon_buffer); - if (!smdh_icon) + if (!Memory::IsValidVirtualAddress(icon_buffer)) return ResultCode(-1); // TODO(Subv): Find the right error code - ext_savedata->WriteIcon(path, smdh_icon, icon_size); + std::vector<u8> smdh_icon(icon_size); + Memory::ReadBlock(icon_buffer, smdh_icon.data(), smdh_icon.size()); + ext_savedata->WriteIcon(path, smdh_icon.data(), smdh_icon.size()); return RESULT_SUCCESS; } diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index b4c146e08..ec565f46d 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -44,7 +44,7 @@ Kernel::SharedPtr<Kernel::SharedMemory> g_shared_memory; u32 g_thread_id = 0; static bool gpu_right_acquired = false; - +static bool first_initialization = true; /// Gets a pointer to a thread command buffer in GSP shared memory static inline u8* GetCommandBuffer(u32 thread_id) { return g_shared_memory->GetPointer(0x800 + (thread_id * sizeof(CommandBuffer))); @@ -66,14 +66,26 @@ static inline InterruptRelayQueue* GetInterruptRelayQueue(u32 thread_id) { } /** + * Writes a single GSP GPU hardware registers with a single u32 value + * (For internal use.) + * + * @param base_address The address of the register in question + * @param data Data to be written + */ +static void WriteSingleHWReg(u32 base_address, u32 data) { + DEBUG_ASSERT_MSG((base_address & 3) == 0 && base_address < 0x420000, "Write address out of range or misaligned"); + HW::Write<u32>(base_address + REGS_BEGIN, data); +} + +/** * Writes sequential GSP GPU hardware registers using an array of source data * * @param base_address The address of the first register in the sequence * @param size_in_bytes The number of registers to update (size of data) - * @param data A pointer to the source data + * @param data_vaddr A pointer to the source data * @return RESULT_SUCCESS if the parameters are valid, error code otherwise */ -static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, const u32* data) { +static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, VAddr data_vaddr) { // This magic number is verified to be done by the gsp module const u32 max_size_in_bytes = 0x80; @@ -87,10 +99,10 @@ static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, const u32* da return ERR_GSP_REGS_MISALIGNED; } else { while (size_in_bytes > 0) { - HW::Write<u32>(base_address + REGS_BEGIN, *data); + WriteSingleHWReg(base_address, Memory::Read32(data_vaddr)); size_in_bytes -= 4; - ++data; + data_vaddr += 4; base_address += 4; } return RESULT_SUCCESS; @@ -112,7 +124,7 @@ static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, const u32* da * @param masks A pointer to the masks * @return RESULT_SUCCESS if the parameters are valid, error code otherwise */ -static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, const u32* data, const u32* masks) { +static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, VAddr data_vaddr, VAddr masks_vaddr) { // This magic number is verified to be done by the gsp module const u32 max_size_in_bytes = 0x80; @@ -131,14 +143,17 @@ static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, const u32 reg_value; HW::Read<u32>(reg_value, reg_address); + u32 data = Memory::Read32(data_vaddr); + u32 mask = Memory::Read32(masks_vaddr); + // Update the current value of the register only for set mask bits - reg_value = (reg_value & ~*masks) | (*data | *masks); + reg_value = (reg_value & ~mask) | (data | mask); - HW::Write<u32>(reg_address, reg_value); + WriteSingleHWReg(base_address, reg_value); size_in_bytes -= 4; - ++data; - ++masks; + data_vaddr += 4; + masks_vaddr += 4; base_address += 4; } return RESULT_SUCCESS; @@ -164,8 +179,7 @@ static void WriteHWRegs(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 reg_addr = cmd_buff[1]; u32 size = cmd_buff[2]; - - u32* src = (u32*)Memory::GetPointer(cmd_buff[4]); + VAddr src = cmd_buff[4]; cmd_buff[1] = WriteHWRegs(reg_addr, size, src).raw; } @@ -186,8 +200,8 @@ static void WriteHWRegsWithMask(Service::Interface* self) { u32 reg_addr = cmd_buff[1]; u32 size = cmd_buff[2]; - u32* src_data = (u32*)Memory::GetPointer(cmd_buff[4]); - u32* mask_data = (u32*)Memory::GetPointer(cmd_buff[6]); + VAddr src_data = cmd_buff[4]; + VAddr mask_data = cmd_buff[6]; cmd_buff[1] = WriteHWRegsWithMask(reg_addr, size, src_data, mask_data).raw; } @@ -210,13 +224,16 @@ static void ReadHWRegs(Service::Interface* self) { return; } - u32* dst = (u32*)Memory::GetPointer(cmd_buff[0x41]); + VAddr dst_vaddr = cmd_buff[0x41]; while (size > 0) { - HW::Read<u32>(*dst, reg_addr + REGS_BEGIN); + u32 value; + HW::Read<u32>(value, reg_addr + REGS_BEGIN); + + Memory::Write32(dst_vaddr, value); size -= 4; - ++dst; + dst_vaddr += 4; reg_addr += 4; } } @@ -226,22 +243,22 @@ ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) { PAddr phys_address_left = Memory::VirtualToPhysicalAddress(info.address_left); PAddr phys_address_right = Memory::VirtualToPhysicalAddress(info.address_right); if (info.active_fb == 0) { - WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left1)), - 4, &phys_address_left); - WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right1)), - 4, &phys_address_right); + WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left1)), + phys_address_left); + WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right1)), + phys_address_right); } else { - WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left2)), - 4, &phys_address_left); - WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right2)), - 4, &phys_address_right); + WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left2)), + phys_address_left); + WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right2)), + phys_address_right); } - WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].stride)), - 4, &info.stride); - WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].color_format)), - 4, &info.format); - WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].active_fb)), - 4, &info.shown_fb); + WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].stride)), + info.stride); + WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].color_format)), + info.format); + WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].active_fb)), + info.shown_fb); if (Pica::g_debug_context) Pica::g_debug_context->OnEvent(Pica::DebugContext::Event::BufferSwapped, nullptr); @@ -330,23 +347,25 @@ static void RegisterInterruptRelayQueue(Service::Interface* self) { u32 flags = cmd_buff[1]; g_interrupt_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[3]); + // TODO(mailwl): return right error code instead assert ASSERT_MSG((g_interrupt_event != nullptr), "handle is not valid!"); g_interrupt_event->name = "GSP_GPU::interrupt_event"; - using Kernel::MemoryPermission; - g_shared_memory = Kernel::SharedMemory::Create(0x1000, MemoryPermission::ReadWrite, - MemoryPermission::ReadWrite, "GSPSharedMem"); - - Handle shmem_handle = Kernel::g_handle_table.Create(g_shared_memory).MoveFrom(); - - // This specific code is required for a successful initialization, rather than 0 - cmd_buff[1] = ResultCode((ErrorDescription)519, ErrorModule::GX, - ErrorSummary::Success, ErrorLevel::Success).raw; + if (first_initialization) { + // This specific code is required for a successful initialization, rather than 0 + first_initialization = false; + cmd_buff[1] = ResultCode(ErrorDescription::GPU_FirstInitialization, ErrorModule::GX, + ErrorSummary::Success, ErrorLevel::Success).raw; + } else { + cmd_buff[1] = RESULT_SUCCESS.raw; + } cmd_buff[2] = g_thread_id++; // Thread ID - cmd_buff[4] = shmem_handle; // GSP shared memory + cmd_buff[4] = Kernel::g_handle_table.Create(g_shared_memory).MoveFrom(); // GSP shared memory g_interrupt_event->Signal(); // TODO(bunnei): Is this correct? + + LOG_WARNING(Service_GSP, "called, flags=0x%08X", flags); } /** @@ -357,12 +376,12 @@ static void RegisterInterruptRelayQueue(Service::Interface* self) { static void UnregisterInterruptRelayQueue(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - g_shared_memory = nullptr; + g_thread_id = 0; g_interrupt_event = nullptr; cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_GSP, "called"); + LOG_WARNING(Service_GSP, "(STUBBED) called"); } /** @@ -431,9 +450,9 @@ static void ExecuteCommand(const Command& command, u32 thread_id) { Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(command.dma_request.dest_address), command.dma_request.size); - memcpy(Memory::GetPointer(command.dma_request.dest_address), - Memory::GetPointer(command.dma_request.source_address), - command.dma_request.size); + // TODO(Subv): These memory accesses should not go through the application's memory mapping. + // They should go through the GSP module's memory mapping. + Memory::CopyBlock(command.dma_request.dest_address, command.dma_request.source_address, command.dma_request.size); SignalInterrupt(InterruptId::DMA); break; } @@ -700,10 +719,15 @@ Interface::Interface() { Register(FunctionTable); g_interrupt_event = nullptr; - g_shared_memory = nullptr; + + using Kernel::MemoryPermission; + g_shared_memory = Kernel::SharedMemory::Create(nullptr, 0x1000, + MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, + 0, Kernel::MemoryRegion::BASE, "GSP:SharedMemory"); g_thread_id = 0; gpu_right_acquired = false; + first_initialization = true; } Interface::~Interface() { 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..395880843 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -7,9 +7,9 @@ #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" #include "core/hle/service/dsp_dsp.h" #include "core/hle/service/err_f.h" #include "core/hle/service/gsp_gpu.h" @@ -30,6 +30,7 @@ #include "core/hle/service/boss/boss.h" #include "core/hle/service/cam/cam.h" #include "core/hle/service/cecd/cecd.h" +#include "core/hle/service/dlp/dlp.h" #include "core/hle/service/frd/frd.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/cfg/cfg.h" @@ -110,6 +111,7 @@ void Init() { Service::CAM::Init(); Service::CECD::Init(); Service::CFG::Init(); + Service::DLP::Init(); Service::FRD::Init(); Service::HID::Init(); Service::IR::Init(); @@ -119,9 +121,9 @@ 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); AddService(new DSP_DSP::Interface); AddService(new GSP_GPU::Interface); AddService(new GSP_LCD::Interface); @@ -148,6 +150,7 @@ void Shutdown() { Service::IR::Shutdown(); Service::HID::Shutdown(); Service::FRD::Shutdown(); + Service::DLP::Shutdown(); Service::CFG::Shutdown(); Service::CECD::Shutdown(); Service::CAM::Shutdown(); diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp index d3e5d4bca..9b285567b 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -373,14 +373,18 @@ static void Bind(Service::Interface* self) { u32* cmd_buffer = Kernel::GetCommandBuffer(); u32 socket_handle = cmd_buffer[1]; u32 len = cmd_buffer[2]; - CTRSockAddr* ctr_sock_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[6])); - if (ctr_sock_addr == nullptr) { + // Virtual address of the sock_addr structure + VAddr sock_addr_addr = cmd_buffer[6]; + if (!Memory::IsValidVirtualAddress(sock_addr_addr)) { cmd_buffer[1] = -1; // TODO(Subv): Correct code return; } - sockaddr sock_addr = CTRSockAddr::ToPlatform(*ctr_sock_addr); + CTRSockAddr ctr_sock_addr; + Memory::ReadBlock(sock_addr_addr, reinterpret_cast<u8*>(&ctr_sock_addr), sizeof(CTRSockAddr)); + + sockaddr sock_addr = CTRSockAddr::ToPlatform(ctr_sock_addr); int res = ::bind(socket_handle, &sock_addr, std::max<u32>(sizeof(sock_addr), len)); @@ -496,7 +500,7 @@ static void Accept(Service::Interface* self) { result = TranslateError(GET_ERRNO); } else { CTRSockAddr ctr_addr = CTRSockAddr::FromPlatform(addr); - Memory::WriteBlock(cmd_buffer[0x104 >> 2], (const u8*)&ctr_addr, max_addr_len); + Memory::WriteBlock(cmd_buffer[0x104 >> 2], &ctr_addr, sizeof(ctr_addr)); } cmd_buffer[0] = IPC::MakeHeader(4, 2, 2); @@ -547,20 +551,31 @@ static void SendTo(Service::Interface* self) { u32 flags = cmd_buffer[3]; u32 addr_len = cmd_buffer[4]; - u8* input_buff = Memory::GetPointer(cmd_buffer[8]); - CTRSockAddr* ctr_dest_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[10])); + VAddr input_buff_address = cmd_buffer[8]; + if (!Memory::IsValidVirtualAddress(input_buff_address)) { + cmd_buffer[1] = -1; // TODO(Subv): Find the right error code + return; + } - if (ctr_dest_addr == nullptr) { + // Memory address of the dest_addr structure + VAddr dest_addr_addr = cmd_buffer[10]; + if (!Memory::IsValidVirtualAddress(dest_addr_addr)) { cmd_buffer[1] = -1; // TODO(Subv): Find the right error code return; } + std::vector<u8> input_buff(len); + Memory::ReadBlock(input_buff_address, input_buff.data(), input_buff.size()); + + CTRSockAddr ctr_dest_addr; + Memory::ReadBlock(dest_addr_addr, &ctr_dest_addr, sizeof(ctr_dest_addr)); + int ret = -1; if (addr_len > 0) { - sockaddr dest_addr = CTRSockAddr::ToPlatform(*ctr_dest_addr); - ret = ::sendto(socket_handle, (const char*)input_buff, len, flags, &dest_addr, sizeof(dest_addr)); + sockaddr dest_addr = CTRSockAddr::ToPlatform(ctr_dest_addr); + ret = ::sendto(socket_handle, reinterpret_cast<const char*>(input_buff.data()), len, flags, &dest_addr, sizeof(dest_addr)); } else { - ret = ::sendto(socket_handle, (const char*)input_buff, len, flags, nullptr, 0); + ret = ::sendto(socket_handle, reinterpret_cast<const char*>(input_buff.data()), len, flags, nullptr, 0); } int result = 0; @@ -591,14 +606,24 @@ static void RecvFrom(Service::Interface* self) { std::memcpy(&buffer_parameters, &cmd_buffer[64], sizeof(buffer_parameters)); - u8* output_buff = Memory::GetPointer(buffer_parameters.output_buffer_addr); + if (!Memory::IsValidVirtualAddress(buffer_parameters.output_buffer_addr)) { + cmd_buffer[1] = -1; // TODO(Subv): Find the right error code + return; + } + + if (!Memory::IsValidVirtualAddress(buffer_parameters.output_src_address_buffer)) { + cmd_buffer[1] = -1; // TODO(Subv): Find the right error code + return; + } + + std::vector<u8> output_buff(len); sockaddr src_addr; socklen_t src_addr_len = sizeof(src_addr); - int ret = ::recvfrom(socket_handle, (char*)output_buff, len, flags, &src_addr, &src_addr_len); + int ret = ::recvfrom(socket_handle, reinterpret_cast<char*>(output_buff.data()), len, flags, &src_addr, &src_addr_len); if (ret >= 0 && buffer_parameters.output_src_address_buffer != 0 && src_addr_len > 0) { - CTRSockAddr* ctr_src_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(buffer_parameters.output_src_address_buffer)); - *ctr_src_addr = CTRSockAddr::FromPlatform(src_addr); + CTRSockAddr ctr_src_addr = CTRSockAddr::FromPlatform(src_addr); + Memory::WriteBlock(buffer_parameters.output_src_address_buffer, &ctr_src_addr, sizeof(ctr_src_addr)); } int result = 0; @@ -606,6 +631,9 @@ static void RecvFrom(Service::Interface* self) { if (ret == SOCKET_ERROR_VALUE) { result = TranslateError(GET_ERRNO); total_received = 0; + } else { + // Write only the data we received to avoid overwriting parts of the buffer with zeros + Memory::WriteBlock(buffer_parameters.output_buffer_addr, output_buff.data(), total_received); } cmd_buffer[1] = result; @@ -617,18 +645,28 @@ static void Poll(Service::Interface* self) { u32* cmd_buffer = Kernel::GetCommandBuffer(); u32 nfds = cmd_buffer[1]; int timeout = cmd_buffer[2]; - CTRPollFD* input_fds = reinterpret_cast<CTRPollFD*>(Memory::GetPointer(cmd_buffer[6])); - CTRPollFD* output_fds = reinterpret_cast<CTRPollFD*>(Memory::GetPointer(cmd_buffer[0x104 >> 2])); + + VAddr input_fds_addr = cmd_buffer[6]; + VAddr output_fds_addr = cmd_buffer[0x104 >> 2]; + if (!Memory::IsValidVirtualAddress(input_fds_addr) || !Memory::IsValidVirtualAddress(output_fds_addr)) { + cmd_buffer[1] = -1; // TODO(Subv): Find correct error code. + return; + } + + std::vector<CTRPollFD> ctr_fds(nfds); + Memory::ReadBlock(input_fds_addr, ctr_fds.data(), nfds * sizeof(CTRPollFD)); // The 3ds_pollfd and the pollfd structures may be different (Windows/Linux have different sizes) // so we have to copy the data std::vector<pollfd> platform_pollfd(nfds); - std::transform(input_fds, input_fds + nfds, platform_pollfd.begin(), CTRPollFD::ToPlatform); + std::transform(ctr_fds.begin(), ctr_fds.end(), platform_pollfd.begin(), CTRPollFD::ToPlatform); const int ret = ::poll(platform_pollfd.data(), nfds, timeout); // Now update the output pollfd structure - std::transform(platform_pollfd.begin(), platform_pollfd.end(), output_fds, CTRPollFD::FromPlatform); + std::transform(platform_pollfd.begin(), platform_pollfd.end(), ctr_fds.begin(), CTRPollFD::FromPlatform); + + Memory::WriteBlock(output_fds_addr, ctr_fds.data(), nfds * sizeof(CTRPollFD)); int result = 0; if (ret == SOCKET_ERROR_VALUE) @@ -643,14 +681,16 @@ static void GetSockName(Service::Interface* self) { u32 socket_handle = cmd_buffer[1]; socklen_t ctr_len = cmd_buffer[2]; - CTRSockAddr* ctr_dest_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[0x104 >> 2])); + // Memory address of the ctr_dest_addr structure + VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2]; sockaddr dest_addr; socklen_t dest_addr_len = sizeof(dest_addr); int ret = ::getsockname(socket_handle, &dest_addr, &dest_addr_len); - if (ctr_dest_addr != nullptr) { - *ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr); + if (ctr_dest_addr_addr != 0 && Memory::IsValidVirtualAddress(ctr_dest_addr_addr)) { + CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr); + Memory::WriteBlock(ctr_dest_addr_addr, &ctr_dest_addr, sizeof(ctr_dest_addr)); } else { cmd_buffer[1] = -1; // TODO(Subv): Verify error return; @@ -682,14 +722,16 @@ static void GetPeerName(Service::Interface* self) { u32 socket_handle = cmd_buffer[1]; socklen_t len = cmd_buffer[2]; - CTRSockAddr* ctr_dest_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[0x104 >> 2])); + // Memory address of the ctr_dest_addr structure + VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2]; sockaddr dest_addr; socklen_t dest_addr_len = sizeof(dest_addr); int ret = ::getpeername(socket_handle, &dest_addr, &dest_addr_len); - if (ctr_dest_addr != nullptr) { - *ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr); + if (ctr_dest_addr_addr != 0 && Memory::IsValidVirtualAddress(ctr_dest_addr_addr)) { + CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr); + Memory::WriteBlock(ctr_dest_addr_addr, &ctr_dest_addr, sizeof(ctr_dest_addr)); } else { cmd_buffer[1] = -1; return; @@ -711,13 +753,17 @@ static void Connect(Service::Interface* self) { u32 socket_handle = cmd_buffer[1]; socklen_t len = cmd_buffer[2]; - CTRSockAddr* ctr_input_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[6])); - if (ctr_input_addr == nullptr) { + // Memory address of the ctr_input_addr structure + VAddr ctr_input_addr_addr = cmd_buffer[6]; + if (!Memory::IsValidVirtualAddress(ctr_input_addr_addr)) { cmd_buffer[1] = -1; // TODO(Subv): Verify error return; } - sockaddr input_addr = CTRSockAddr::ToPlatform(*ctr_input_addr); + CTRSockAddr ctr_input_addr; + Memory::ReadBlock(ctr_input_addr_addr, &ctr_input_addr, sizeof(ctr_input_addr)); + + sockaddr input_addr = CTRSockAddr::ToPlatform(ctr_input_addr); int ret = ::connect(socket_handle, &input_addr, sizeof(input_addr)); int result = 0; if (ret != 0) diff --git a/src/core/hle/service/ssl_c.cpp b/src/core/hle/service/ssl_c.cpp index 14a4e98ec..a8aff1abf 100644 --- a/src/core/hle/service/ssl_c.cpp +++ b/src/core/hle/service/ssl_c.cpp @@ -31,7 +31,6 @@ static void GenerateRandomData(Service::Interface* self) { u32 size = cmd_buff[1]; VAddr address = cmd_buff[3]; - u8* output_buff = Memory::GetPointer(address); // Fill the output buffer with random data. u32 data = 0; @@ -44,13 +43,13 @@ static void GenerateRandomData(Service::Interface* self) { if (size > 4) { // Use up the entire 4 bytes of the random data for as long as possible - *(u32*)(output_buff + i) = data; + Memory::Write32(address + i, data); i += 4; } else if (size == 2) { - *(u16*)(output_buff + i) = (u16)(data & 0xffff); + Memory::Write16(address + i, static_cast<u16>(data & 0xffff)); i += 2; } else { - *(u8*)(output_buff + i) = (u8)(data & 0xff); + Memory::Write8(address + i, static_cast<u8>(data & 0xff)); i++; } } 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 48a11ef81..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); diff --git a/src/core/loader/3dsx.h b/src/core/loader/3dsx.h index 3ee686703..90b20c61c 100644 --- a/src/core/loader/3dsx.h +++ b/src/core/loader/3dsx.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/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 0d4c1d351..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,7 +91,15 @@ const char* GetFileTypeString(FileType type) { return "unknown"; } -std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type, +/** + * 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) { @@ -108,15 +117,15 @@ std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type, return std::make_unique<AppLoader_NCCH>(std::move(file), filepath); default: - return std::unique_ptr<AppLoader>(); + return nullptr; } } -ResultStatus LoadFile(const std::string& filename) { +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; @@ -133,43 +142,7 @@ ResultStatus LoadFile(const std::string& filename) { LOG_INFO(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type)); - std::unique_ptr<AppLoader> app_loader = GetLoader(std::move(file), type, filename_filename, filename); - - switch (type) { - - // 3DSX file format... - // or NCCH/NCSD container formats... - case FileType::THREEDSX: - case FileType::CXI: - case FileType::CCI: - { - // 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 app_loader->Load(); - - // 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 9d3e9ed3b..77d87afe1 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -10,10 +10,8 @@ #include <string> #include <vector> -#include "common/common_funcs.h" #include "common/common_types.h" #include "common/file_util.h" -#include "common/swap.h" namespace Kernel { struct AddressMapping; @@ -80,51 +78,6 @@ constexpr u32 MakeMagic(char a, char b, char c, char d) { return a | b << 8 | c << 16 | d << 24; } -/// SMDH data structure that contains titles, icons etc. See https://www.3dbrew.org/wiki/SMDH -struct SMDH { - u32_le magic; - u16_le version; - INSERT_PADDING_BYTES(2); - - struct Title { - std::array<u16, 0x40> short_title; - std::array<u16, 0x80> long_title; - std::array<u16, 0x40> publisher; - }; - std::array<Title, 16> titles; - - std::array<u8, 16> ratings; - u32_le region_lockout; - u32_le match_maker_id; - u64_le match_maker_bit_id; - u32_le flags; - u16_le eula_version; - INSERT_PADDING_BYTES(2); - float_le banner_animation_frame; - u32_le cec_id; - INSERT_PADDING_BYTES(8); - - std::array<u8, 0x480> small_icon; - std::array<u8, 0x1200> large_icon; - - /// indicates the language used for each title entry - enum class TitleLanguage { - Japanese = 0, - English = 1, - French = 2, - German = 3, - Italian = 4, - Spanish = 5, - SimplifiedChinese = 6, - Korean= 7, - Dutch = 8, - Portuguese = 9, - Russian = 10, - TraditionalChinese = 11 - }; -}; -static_assert(sizeof(SMDH) == 0x36C0, "SMDH structure size is wrong"); - /// Interface for loading an application class AppLoader : NonCopyable { public: @@ -132,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 */ @@ -197,20 +156,10 @@ protected: extern const std::initializer_list<Kernel::AddressMapping> default_address_mappings; /** - * Get a loader for a file with a specific type - * @param file The file to load - * @param type The type of the file - * @param filename the file name (without path) - * @param filepath the file full path (with name) - * @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type - */ -std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type, const std::string& filename, const std::string& filepath); - -/** - * Identifies and loads a bootable file + * 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 d362a4419..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)); @@ -300,7 +305,12 @@ ResultStatus AppLoader_NCCH::Load() { 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 fd852c3de..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 */ 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.cpp b/src/core/memory.cpp index ee9b69f81..8c9e5d46d 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -246,6 +246,26 @@ void Write(const VAddr vaddr, const T data) { } } +bool IsValidVirtualAddress(const VAddr vaddr) { + const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; + if (page_pointer) + return true; + + if (current_page_table->attributes[vaddr >> PAGE_BITS] != PageType::Special) + return false; + + MMIORegionPointer mmio_region = GetMMIOHandler(vaddr); + if (mmio_region) { + return mmio_region->IsValidAddress(vaddr); + } + + return false; +} + +bool IsValidPhysicalAddress(const PAddr paddr) { + return IsValidVirtualAddress(PhysicalToVirtualAddress(paddr)); +} + u8* GetPointer(const VAddr vaddr) { u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; if (page_pointer) { @@ -261,6 +281,7 @@ u8* GetPointer(const VAddr vaddr) { } u8* GetPhysicalPointer(PAddr address) { + // TODO(Subv): This call should not go through the application's memory mapping. return GetPointer(PhysicalToVirtualAddress(address)); } @@ -343,6 +364,59 @@ u64 Read64(const VAddr addr) { return Read<u64_le>(addr); } +void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) { + size_t remaining_size = size; + size_t page_index = src_addr >> PAGE_BITS; + size_t page_offset = src_addr & PAGE_MASK; + + while (remaining_size > 0) { + const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size); + const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset; + + switch (current_page_table->attributes[page_index]) { + case PageType::Unmapped: { + LOG_ERROR(HW_Memory, "unmapped ReadBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, src_addr, size); + std::memset(dest_buffer, 0, copy_amount); + break; + } + case PageType::Memory: { + DEBUG_ASSERT(current_page_table->pointers[page_index]); + + const u8* src_ptr = current_page_table->pointers[page_index] + page_offset; + std::memcpy(dest_buffer, src_ptr, copy_amount); + break; + } + case PageType::Special: { + DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); + + GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, dest_buffer, copy_amount); + break; + } + case PageType::RasterizerCachedMemory: { + RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount); + + std::memcpy(dest_buffer, GetPointerFromVMA(current_vaddr), copy_amount); + break; + } + case PageType::RasterizerCachedSpecial: { + DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); + + RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount); + + GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, dest_buffer, copy_amount); + break; + } + default: + UNREACHABLE(); + } + + page_index++; + page_offset = 0; + dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount; + remaining_size -= copy_amount; + } +} + void Write8(const VAddr addr, const u8 data) { Write<u8>(addr, data); } @@ -359,9 +433,165 @@ void Write64(const VAddr addr, const u64 data) { Write<u64_le>(addr, data); } -void WriteBlock(const VAddr addr, const u8* data, const size_t size) { - for (u32 offset = 0; offset < size; offset++) { - Write8(addr + offset, data[offset]); +void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size) { + size_t remaining_size = size; + size_t page_index = dest_addr >> PAGE_BITS; + size_t page_offset = dest_addr & PAGE_MASK; + + while (remaining_size > 0) { + const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size); + const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset; + + switch (current_page_table->attributes[page_index]) { + case PageType::Unmapped: { + LOG_ERROR(HW_Memory, "unmapped WriteBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, dest_addr, size); + break; + } + case PageType::Memory: { + DEBUG_ASSERT(current_page_table->pointers[page_index]); + + u8* dest_ptr = current_page_table->pointers[page_index] + page_offset; + std::memcpy(dest_ptr, src_buffer, copy_amount); + break; + } + case PageType::Special: { + DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); + + GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, src_buffer, copy_amount); + break; + } + case PageType::RasterizerCachedMemory: { + RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount); + + std::memcpy(GetPointerFromVMA(current_vaddr), src_buffer, copy_amount); + break; + } + case PageType::RasterizerCachedSpecial: { + DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); + + RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount); + + GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, src_buffer, copy_amount); + break; + } + default: + UNREACHABLE(); + } + + page_index++; + page_offset = 0; + src_buffer = static_cast<const u8*>(src_buffer) + copy_amount; + remaining_size -= copy_amount; + } +} + +void ZeroBlock(const VAddr dest_addr, const size_t size) { + size_t remaining_size = size; + size_t page_index = dest_addr >> PAGE_BITS; + size_t page_offset = dest_addr & PAGE_MASK; + + static const std::array<u8, PAGE_SIZE> zeros = {}; + + while (remaining_size > 0) { + const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size); + const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset; + + switch (current_page_table->attributes[page_index]) { + case PageType::Unmapped: { + LOG_ERROR(HW_Memory, "unmapped ZeroBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, dest_addr, size); + break; + } + case PageType::Memory: { + DEBUG_ASSERT(current_page_table->pointers[page_index]); + + u8* dest_ptr = current_page_table->pointers[page_index] + page_offset; + std::memset(dest_ptr, 0, copy_amount); + break; + } + case PageType::Special: { + DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); + + GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount); + break; + } + case PageType::RasterizerCachedMemory: { + RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount); + + std::memset(GetPointerFromVMA(current_vaddr), 0, copy_amount); + break; + } + case PageType::RasterizerCachedSpecial: { + DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); + + RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount); + + GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount); + break; + } + default: + UNREACHABLE(); + } + + page_index++; + page_offset = 0; + remaining_size -= copy_amount; + } +} + +void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) { + size_t remaining_size = size; + size_t page_index = src_addr >> PAGE_BITS; + size_t page_offset = src_addr & PAGE_MASK; + + while (remaining_size > 0) { + const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size); + const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset; + + switch (current_page_table->attributes[page_index]) { + case PageType::Unmapped: { + LOG_ERROR(HW_Memory, "unmapped CopyBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, src_addr, size); + ZeroBlock(dest_addr, copy_amount); + break; + } + case PageType::Memory: { + DEBUG_ASSERT(current_page_table->pointers[page_index]); + const u8* src_ptr = current_page_table->pointers[page_index] + page_offset; + WriteBlock(dest_addr, src_ptr, copy_amount); + break; + } + case PageType::Special: { + DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); + + std::vector<u8> buffer(copy_amount); + GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size()); + WriteBlock(dest_addr, buffer.data(), buffer.size()); + break; + } + case PageType::RasterizerCachedMemory: { + RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount); + + WriteBlock(dest_addr, GetPointerFromVMA(current_vaddr), copy_amount); + break; + } + case PageType::RasterizerCachedSpecial: { + DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); + + RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount); + + std::vector<u8> buffer(copy_amount); + GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size()); + WriteBlock(dest_addr, buffer.data(), buffer.size()); + break; + } + default: + UNREACHABLE(); + } + + page_index++; + page_offset = 0; + dest_addr += copy_amount; + src_addr += copy_amount; + remaining_size -= copy_amount; } } diff --git a/src/core/memory.h b/src/core/memory.h index 9caa3c3f5..ae5588dee 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, @@ -116,6 +110,9 @@ enum : VAddr { NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE, }; +bool IsValidVirtualAddress(const VAddr addr); +bool IsValidPhysicalAddress(const PAddr addr); + u8 Read8(VAddr addr); u16 Read16(VAddr addr); u32 Read32(VAddr addr); @@ -126,7 +123,10 @@ void Write16(VAddr addr, u16 data); void Write32(VAddr addr, u32 data); void Write64(VAddr addr, u64 data); -void WriteBlock(VAddr addr, const u8* data, size_t size); +void ReadBlock(const VAddr src_addr, void* dest_buffer, size_t size); +void WriteBlock(const VAddr dest_addr, const void* src_buffer, size_t size); +void ZeroBlock(const VAddr dest_addr, const size_t size); +void CopyBlock(VAddr dest_addr, VAddr src_addr, size_t size); u8* GetPointer(VAddr virtual_address); 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/mmio.h b/src/core/mmio.h index 06b555e98..d76f005d8 100644 --- a/src/core/mmio.h +++ b/src/core/mmio.h @@ -18,15 +18,21 @@ class MMIORegion { public: virtual ~MMIORegion() = default; + virtual bool IsValidAddress(VAddr addr) = 0; + virtual u8 Read8(VAddr addr) = 0; virtual u16 Read16(VAddr addr) = 0; virtual u32 Read32(VAddr addr) = 0; virtual u64 Read64(VAddr addr) = 0; + virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, size_t size) = 0; + virtual void Write8(VAddr addr, u8 data) = 0; virtual void Write16(VAddr addr, u16 data) = 0; virtual void Write32(VAddr addr, u32 data) = 0; virtual void Write64(VAddr addr, u64 data) = 0; + + virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size) = 0; }; using MMIORegionPointer = std::shared_ptr<MMIORegion>; 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 dd1379503..19e03adf4 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,12 +144,13 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { immediate_attribute_id = 0; Shader::UnitState<false> shader_unit; - Shader::Setup(); + g_state.vs.Setup(); // Send to vertex shader if (g_debug_context) g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, static_cast<void*>(&immediate_input)); - Shader::OutputVertex output = Shader::Run(shader_unit, immediate_input, regs.vs.num_input_attributes+1); + g_state.vs.Run(shader_unit, immediate_input, regs.vs.num_input_attributes+1); + Shader::OutputVertex output_vertex = shader_unit.output_registers.ToVertex(regs.vs); // Send to renderer using Pica::Shader::OutputVertex; @@ -157,7 +158,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2); }; - g_state.primitive_assembler.SubmitVertex(output, AddTriangle); + g_state.primitive_assembler.SubmitVertex(output_vertex, AddTriangle); } } } @@ -199,9 +200,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)); @@ -231,13 +231,13 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { // The size has been tuned for optimal balance between hit-rate and the cost of lookup const size_t VERTEX_CACHE_SIZE = 32; std::array<u16, VERTEX_CACHE_SIZE> vertex_cache_ids; - std::array<Shader::OutputVertex, VERTEX_CACHE_SIZE> vertex_cache; + std::array<Shader::OutputRegisters, VERTEX_CACHE_SIZE> vertex_cache; unsigned int vertex_cache_pos = 0; 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) { @@ -249,7 +249,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { ASSERT(vertex != -1); bool vertex_cache_hit = false; - Shader::OutputVertex output; + Shader::OutputRegisters output_registers; if (is_indexed) { if (g_debug_context && Pica::g_debug_context->recorder) { @@ -259,7 +259,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { for (unsigned int i = 0; i < VERTEX_CACHE_SIZE; ++i) { if (vertex == vertex_cache_ids[i]) { - output = vertex_cache[i]; + output_registers = vertex_cache[i]; vertex_cache_hit = true; break; } @@ -274,15 +274,19 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { // Send to vertex shader if (g_debug_context) g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, (void*)&input); - output = Shader::Run(shader_unit, input, loader.GetNumTotalAttributes()); + g_state.vs.Run(shader_unit, input, loader.GetNumTotalAttributes()); + output_registers = shader_unit.output_registers; if (is_indexed) { - vertex_cache[vertex_cache_pos] = output; + vertex_cache[vertex_cache_pos] = output_registers; vertex_cache_ids[vertex_cache_pos] = vertex; vertex_cache_pos = (vertex_cache_pos + 1) % VERTEX_CACHE_SIZE; } } + // Retreive vertex from register data + Shader::OutputVertex output_vertex = output_registers.ToVertex(regs.vs); + // Send to renderer using Pica::Shader::OutputVertex; auto AddTriangle = []( @@ -290,7 +294,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2); }; - primitive_assembler.SubmitVertex(output, AddTriangle); + primitive_assembler.SubmitVertex(output_vertex, AddTriangle); } for (auto& range : memory_accesses.ranges) { 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 f628292a4..92e9734ae 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -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..8410e0a64 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); @@ -450,7 +450,7 @@ static const char* GetType(GLenum type) { #undef RET } -static void DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, +static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* user_param) { Log::Level level; switch (severity) { @@ -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..f565e2c91 100644 --- a/src/video_core/shader/shader.cpp +++ b/src/video_core/shader/shader.cpp @@ -30,65 +30,7 @@ namespace Pica { namespace Shader { -#ifdef ARCHITECTURE_x86_64 -static std::unordered_map<u64, std::unique_ptr<JitShader>> shader_map; -static const JitShader* jit_shader; -#endif // ARCHITECTURE_x86_64 - -void 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)) ^ - Common::ComputeHash64(&g_state.vs.swizzle_data, sizeof(g_state.vs.swizzle_data))); - - auto iter = shader_map.find(cache_key); - if (iter != shader_map.end()) { - jit_shader = iter->second.get(); - } else { - auto shader = std::make_unique<JitShader>(); - shader->Compile(); - jit_shader = shader.get(); - shader_map[cache_key] = std::move(shader); - } - } -#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)); - -OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attributes) { - auto& config = g_state.regs.vs; - - MICROPROFILE_SCOPE(GPU_VertexShader); - - state.program_counter = config.main_offset; - state.debug.max_offset = 0; - state.debug.max_opdesc_id = 0; - - // Setup input register table - const auto& attribute_register_map = config.input_register_map; - - for (unsigned i = 0; i < num_attributes; i++) - state.registers.input[attribute_register_map.GetRegisterForAttribute(i)] = input.attr[i]; - - state.conditional_code[0] = false; - state.conditional_code[1] = false; - -#ifdef ARCHITECTURE_x86_64 - if (VideoCore::g_shader_jit_enabled) - jit_shader->Run(&state.registers, g_state.regs.vs.main_offset); - else - RunInterpreter(state); -#else - RunInterpreter(state); -#endif // ARCHITECTURE_x86_64 - +OutputVertex OutputRegisters::ToVertex(const Regs::ShaderConfig& config) { // Setup output data OutputVertex ret; // TODO(neobrain): Under some circumstances, up to 16 attributes may be output. We need to @@ -99,10 +41,10 @@ OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attr if (index >= g_state.regs.vs_output_total) break; - if ((g_state.regs.vs.output_mask & (1 << i)) == 0) + if ((config.output_mask & (1 << i)) == 0) continue; - const auto& output_register_map = g_state.regs.vs_output_attributes[index]; // TODO: Don't hardcode VS here + const auto& output_register_map = g_state.regs.vs_output_attributes[index]; u32 semantics[4] = { output_register_map.map_x, output_register_map.map_y, @@ -112,7 +54,7 @@ OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attr for (unsigned comp = 0; comp < 4; ++comp) { float24* out = ((float24*)&ret) + semantics[comp]; if (semantics[comp] != Regs::VSOutputAttributes::INVALID) { - *out = state.registers.output[i][comp]; + *out = value[i][comp]; } else { // Zero output so that attributes which aren't output won't have denormals in them, // which would slow us down later. @@ -140,10 +82,70 @@ 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) { +#ifdef ARCHITECTURE_x86_64 +static std::unordered_map<u64, std::unique_ptr<JitShader>> shader_map; +static const JitShader* jit_shader; +#endif // ARCHITECTURE_x86_64 + +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)) ^ + Common::ComputeHash64(&g_state.vs.swizzle_data, sizeof(g_state.vs.swizzle_data))); + + auto iter = shader_map.find(cache_key); + if (iter != shader_map.end()) { + jit_shader = iter->second.get(); + } else { + auto shader = std::make_unique<JitShader>(); + shader->Compile(); + jit_shader = shader.get(); + shader_map[cache_key] = std::move(shader); + } + } +#endif // ARCHITECTURE_x86_64 +} + +MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240)); + +void 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_Shader); + + state.debug.max_offset = 0; + state.debug.max_opdesc_id = 0; + + // Setup input register table + const auto& attribute_register_map = config.input_register_map; + + for (unsigned i = 0; i < num_attributes; i++) + state.registers.input[attribute_register_map.GetRegisterForAttribute(i)] = input.attr[i]; + + state.conditional_code[0] = false; + state.conditional_code[1] = false; + +#ifdef ARCHITECTURE_x86_64 + if (VideoCore::g_shader_jit_enabled) + jit_shader->Run(setup, state, config.main_offset); + else + RunInterpreter(setup, state, config.main_offset); +#else + RunInterpreter(setup, state, config.main_offset); +#endif // ARCHITECTURE_x86_64 + +} + +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 +160,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..fee16df62 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,22 +84,14 @@ 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; +struct OutputRegisters { + OutputRegisters() = default; - Math::Vec4<float24> default_attributes[16]; + alignas(16) Math::Vec4<float24> value[16]; - std::array<u32, 1024> program_code; - std::array<u32, 1024> swizzle_data; + OutputVertex ToVertex(const Regs::ShaderConfig& config); }; +static_assert(std::is_pod<OutputRegisters>::value, "Structure is not POD"); // Helper structure used to keep track of data useful for inspection of shader emulation template<bool full_debugging> @@ -283,43 +276,27 @@ struct UnitState { // The registers are accessed by the shader JIT using SSE instructions, and are therefore // required to be 16-byte aligned. alignas(16) Math::Vec4<float24> input[16]; - alignas(16) Math::Vec4<float24> output[16]; alignas(16) Math::Vec4<float24> temporary[16]; } registers; static_assert(std::is_pod<Registers>::value, "Structure is not POD"); - u32 program_counter; + OutputRegisters output_registers; + 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 +307,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, output_registers.value) + 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 +319,65 @@ 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 + */ + void 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..b1eadc071 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()) { @@ -128,7 +144,7 @@ void RunInterpreter(UnitState<Debug>& state) { src2[3] = src2[3] * float24::FromFloat32(-1); } - float24* dest = (instr.common.dest.Value() < 0x10) ? &state.registers.output[instr.common.dest.Value().GetIndex()][0] + float24* dest = (instr.common.dest.Value() < 0x10) ? &state.output_registers.value[instr.common.dest.Value().GetIndex()][0] : (instr.common.dest.Value() < 0x20) ? &state.registers.temporary[instr.common.dest.Value().GetIndex()][0] : dummy_vec4_float24; @@ -467,7 +483,7 @@ void RunInterpreter(UnitState<Debug>& state) { src3[3] = src3[3] * float24::FromFloat32(-1); } - float24* dest = (instr.mad.dest.Value() < 0x10) ? &state.registers.output[instr.mad.dest.Value().GetIndex()][0] + float24* dest = (instr.mad.dest.Value() < 0x10) ? &state.output_registers.value[instr.mad.dest.Value().GetIndex()][0] : (instr.mad.dest.Value() < 0x20) ? &state.registers.temporary[instr.mad.dest.Value().GetIndex()][0] : dummy_vec4_float24; @@ -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 |