diff options
Diffstat (limited to '')
204 files changed, 7875 insertions, 3011 deletions
diff --git a/.gitmodules b/.gitmodules index 598e4c64d..059512902 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "nihstro"] path = externals/nihstro url = https://github.com/neobrain/nihstro.git +[submodule "soundtouch"] + path = externals/soundtouch + url = https://github.com/citra-emu/soundtouch.git diff --git a/.travis-build.sh b/.travis-build.sh index 22a3a9fd6..e06a4299b 100755 --- a/.travis-build.sh +++ b/.travis-build.sh @@ -11,8 +11,8 @@ fi #if OS is linux or is not set if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then - export CC=gcc-4.9 - export CXX=g++-4.9 + export CC=gcc-5 + export CXX=g++-5 export PKG_CONFIG_PATH=$HOME/.local/lib/pkgconfig:$PKG_CONFIG_PATH mkdir build && cd build diff --git a/.travis-deps.sh b/.travis-deps.sh index eb99ead4f..4a79feb70 100755 --- a/.travis-deps.sh +++ b/.travis-deps.sh @@ -5,11 +5,11 @@ set -x #if OS is linux or is not set if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then - export CC=gcc-4.9 - export CXX=g++-4.9 + export CC=gcc-5 + export CXX=g++-5 mkdir -p $HOME/.local - curl -L http://www.cmake.org/files/v2.8/cmake-2.8.11-Linux-i386.tar.gz \ + curl -L http://www.cmake.org/files/v3.1/cmake-3.1.0-Linux-i386.tar.gz \ | tar -xz -C $HOME/.local --strip-components=1 ( @@ -20,6 +20,7 @@ if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then ) elif [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update > /dev/null # silence the very verbose output - brew install qt5 sdl2 dylibbundler + brew unlink cmake + brew install cmake31 qt5 sdl2 dylibbundler gem install xcpretty fi diff --git a/.travis.yml b/.travis.yml index 2e875cccf..8d86baece 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,8 +15,8 @@ addons: sources: - ubuntu-toolchain-r-test packages: - - gcc-4.9 - - g++-4.9 + - gcc-5 + - g++-5 - xorg-dev - lib32stdc++6 # For CMake - lftp # To upload builds diff --git a/CMakeLists.txt b/CMakeLists.txt index d6a4a915a..d628ecc50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ -# CMake 2.8.11 required for Qt5 settings to be applied automatically on -# dependent libraries. -cmake_minimum_required(VERSION 2.8.11) +# CMake 3.1 required for Qt5 settings to be applied automatically on +# dependent libraries and IMPORTED targets. +cmake_minimum_required(VERSION 3.1) function(download_bundled_external remote_path lib_name prefix_var) set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}") @@ -65,8 +65,8 @@ endif() message(STATUS "Target architecture: ${ARCHITECTURE}") if (NOT MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-attributes -pthread") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -Wno-attributes") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") if (ARCHITECTURE_x86_64) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.1") @@ -135,6 +135,10 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/cmake-modules") find_package(OpenGL REQUIRED) include_directories(${OPENGL_INCLUDE_DIR}) +# Prefer the -pthread flag on Linux. +set (THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + if (ENABLE_SDL2) if (CITRA_USE_BUNDLED_SDL2) # Detect toolchain and platform @@ -245,6 +249,9 @@ if(ENABLE_QT) include_directories(externals/qhexedit) add_subdirectory(externals/qhexedit) endif() + +add_subdirectory(externals/soundtouch) + add_subdirectory(src) # Install freedesktop.org metadata files, following those specifications: diff --git a/externals/boost b/externals/boost -Subproject d81b9269900ae183d0dc98403eea4c971590a80 +Subproject 2dcb9d979665b6aabb1635c617973e02914e60e diff --git a/externals/cmake-modules/FindSDL2.cmake b/externals/cmake-modules/FindSDL2.cmake index 0af86840a..9b8daa0d1 100644 --- a/externals/cmake-modules/FindSDL2.cmake +++ b/externals/cmake-modules/FindSDL2.cmake @@ -134,11 +134,17 @@ SET(SDL2_SEARCH_PATHS ${SDL2_PATH} ) +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(VC_LIB_PATH_SUFFIX lib/x64) +else() + set(VC_LIB_PATH_SUFFIX lib/x86) +endif() + FIND_LIBRARY(SDL2_LIBRARY_TEMP NAMES SDL2 HINTS $ENV{SDL2DIR} - PATH_SUFFIXES lib64 lib + PATH_SUFFIXES lib64 lib ${VC_LIB_PATH_SUFFIX} PATHS ${SDL2_SEARCH_PATHS} ) diff --git a/externals/microprofile/microprofileui.h b/externals/microprofile/microprofileui.h index eac1119a4..45bec8af6 100644 --- a/externals/microprofile/microprofileui.h +++ b/externals/microprofile/microprofileui.h @@ -879,7 +879,7 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY, static int64_t nRefCpu = 0, nRefGpu = 0; if(MicroProfileGetGpuTickReference(&nTickReferenceCpu, &nTickReferenceGpu)) { - if(0 == nRefCpu || abs(nRefCpu-nBaseTicksCpu) > abs(nTickReferenceCpu-nBaseTicksCpu)) + if(0 == nRefCpu || std::abs(nRefCpu-nBaseTicksCpu) > std::abs(nTickReferenceCpu-nBaseTicksCpu)) { nRefCpu = nTickReferenceCpu; nRefGpu = nTickReferenceGpu; @@ -1230,7 +1230,12 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY, char ThreadName[MicroProfileThreadLog::THREAD_MAX_LEN + 16]; const char* cLocal = MicroProfileIsLocalThread(nThreadId) ? "*": " "; +#if defined(WIN32) + // nThreadId is 32-bit on Windows int nStrLen = snprintf(ThreadName, sizeof(ThreadName)-1, "%04x: %s%s", nThreadId, cLocal, i < nNumThreadsBase ? &S.Pool[i]->ThreadName[0] : MICROPROFILE_THREAD_NAME_FROM_ID(nThreadId) ); +#else + int nStrLen = snprintf(ThreadName, sizeof(ThreadName)-1, "%04llx: %s%s", nThreadId, cLocal, i < nNumThreadsBase ? &S.Pool[i]->ThreadName[0] : MICROPROFILE_THREAD_NAME_FROM_ID(nThreadId) ); +#endif uint32_t nThreadColor = -1; if(nThreadId == nContextSwitchHoverThreadAfter || nThreadId == nContextSwitchHoverThreadBefore) nThreadColor = UI.nHoverColorShared|0x906060; diff --git a/externals/soundtouch b/externals/soundtouch new file mode 160000 +Subproject 5274ec4dec498bd88ccbcd28862a0f78a3b95ef diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index b0d1c7eb6..4cd7aba67 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -1,16 +1,31 @@ set(SRCS audio_core.cpp + codec.cpp hle/dsp.cpp + hle/filter.cpp hle/pipe.cpp + hle/source.cpp + interpolate.cpp + sink_details.cpp ) set(HEADERS audio_core.h + codec.h + hle/common.h hle/dsp.h + hle/filter.h hle/pipe.h + hle/source.h + interpolate.h + null_sink.h sink.h + sink_details.h ) +include_directories(../../externals/soundtouch/include) + create_directory_groups(${SRCS} ${HEADERS}) -add_library(audio_core STATIC ${SRCS} ${HEADERS})
\ No newline at end of file +add_library(audio_core STATIC ${SRCS} ${HEADERS}) +target_link_libraries(audio_core SoundTouch) diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index 894f46990..d42249ebd 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp @@ -2,8 +2,15 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <memory> +#include <string> + #include "audio_core/audio_core.h" #include "audio_core/hle/dsp.h" +#include "audio_core/hle/pipe.h" +#include "audio_core/null_sink.h" +#include "audio_core/sink.h" +#include "audio_core/sink_details.h" #include "core/core_timing.h" #include "core/hle/kernel/vm_manager.h" @@ -17,17 +24,16 @@ static constexpr u64 audio_frame_ticks = 1310252ull; ///< Units: ARM11 cycles static void AudioTickCallback(u64 /*userdata*/, int cycles_late) { if (DSP::HLE::Tick()) { - // HACK: We're not signaling the interrups when they should be, but just firing them all off together. - // It should be only (interrupt_id = 2, channel_id = 2) that's signalled here. - // TODO(merry): Understand when the other interrupts are fired. - DSP_DSP::SignalAllInterrupts(); + // TODO(merry): Signal all the other interrupts as appropriate. + DSP_DSP::SignalPipeInterrupt(DSP::HLE::DspPipe::Audio); + // HACK(merry): Added to prevent regressions. Will remove soon. + DSP_DSP::SignalPipeInterrupt(DSP::HLE::DspPipe::Binary); } // Reschedule recurrent event CoreTiming::ScheduleEvent(audio_frame_ticks - cycles_late, tick_event); } -/// Initialise Audio void Init() { DSP::HLE::Init(); @@ -35,19 +41,39 @@ void Init() { CoreTiming::ScheduleEvent(audio_frame_ticks, tick_event); } -/// Add DSP address spaces to Process's address space. void AddAddressSpace(Kernel::VMManager& address_space) { - auto r0_vma = address_space.MapBackingMemory(DSP::HLE::region0_base, reinterpret_cast<u8*>(&DSP::HLE::g_region0), sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO).MoveFrom(); + auto r0_vma = address_space.MapBackingMemory(DSP::HLE::region0_base, reinterpret_cast<u8*>(&DSP::HLE::g_regions[0]), sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO).MoveFrom(); address_space.Reprotect(r0_vma, Kernel::VMAPermission::ReadWrite); - auto r1_vma = address_space.MapBackingMemory(DSP::HLE::region1_base, reinterpret_cast<u8*>(&DSP::HLE::g_region1), sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO).MoveFrom(); + auto r1_vma = address_space.MapBackingMemory(DSP::HLE::region1_base, reinterpret_cast<u8*>(&DSP::HLE::g_regions[1]), sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO).MoveFrom(); address_space.Reprotect(r1_vma, Kernel::VMAPermission::ReadWrite); } -/// Shutdown Audio +void SelectSink(std::string sink_id) { + if (sink_id == "auto") { + // Auto-select. + // g_sink_details is ordered in terms of desirability, with the best choice at the front. + const auto& sink_detail = g_sink_details.front(); + DSP::HLE::SetSink(sink_detail.factory()); + return; + } + + auto iter = std::find_if(g_sink_details.begin(), g_sink_details.end(), [sink_id](const auto& sink_detail) { + return sink_detail.id == sink_id; + }); + + if (iter == g_sink_details.end()) { + LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id"); + DSP::HLE::SetSink(std::make_unique<NullSink>()); + return; + } + + DSP::HLE::SetSink(iter->factory()); +} + void Shutdown() { CoreTiming::UnscheduleEvent(tick_event, 0); DSP::HLE::Shutdown(); } -} //namespace +} // namespace AudioCore diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h index 64c330914..f618361f3 100644 --- a/src/audio_core/audio_core.h +++ b/src/audio_core/audio_core.h @@ -4,14 +4,14 @@ #pragma once +#include <string> + namespace Kernel { class VMManager; } namespace AudioCore { -constexpr int num_sources = 24; -constexpr int samples_per_frame = 160; ///< Samples per audio frame at native sample rate constexpr int native_sample_rate = 32728; ///< 32kHz /// Initialise Audio Core @@ -20,6 +20,9 @@ void Init(); /// Add DSP address spaces to a Process. void AddAddressSpace(Kernel::VMManager& vm_manager); +/// Select the sink to use based on sink id. +void SelectSink(std::string sink_id); + /// Shutdown Audio Core void Shutdown(); diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp new file mode 100644 index 000000000..ab65514b7 --- /dev/null +++ b/src/audio_core/codec.cpp @@ -0,0 +1,122 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <cstddef> +#include <cstring> +#include <vector> + +#include "audio_core/codec.h" + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/math_util.h" + +namespace Codec { + +StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count, const std::array<s16, 16>& adpcm_coeff, ADPCMState& state) { + // GC-ADPCM with scale factor and variable coefficients. + // Frames are 8 bytes long containing 14 samples each. + // Samples are 4 bits (one nibble) long. + + constexpr size_t FRAME_LEN = 8; + constexpr size_t SAMPLES_PER_FRAME = 14; + constexpr std::array<int, 16> SIGNED_NIBBLES {{ 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1 }}; + + const size_t ret_size = sample_count % 2 == 0 ? sample_count : sample_count + 1; // Ensure multiple of two. + StereoBuffer16 ret(ret_size); + + int yn1 = state.yn1, + yn2 = state.yn2; + + const size_t NUM_FRAMES = (sample_count + (SAMPLES_PER_FRAME - 1)) / SAMPLES_PER_FRAME; // Round up. + for (size_t framei = 0; framei < NUM_FRAMES; framei++) { + const int frame_header = data[framei * FRAME_LEN]; + const int scale = 1 << (frame_header & 0xF); + const int idx = (frame_header >> 4) & 0x7; + + // Coefficients are fixed point with 11 bits fractional part. + const int coef1 = adpcm_coeff[idx * 2 + 0]; + const int coef2 = adpcm_coeff[idx * 2 + 1]; + + // Decodes an audio sample. One nibble produces one sample. + const auto decode_sample = [&](const int nibble) -> s16 { + const int xn = nibble * scale; + // We first transform everything into 11 bit fixed point, perform the second order digital filter, then transform back. + // 0x400 == 0.5 in 11 bit fixed point. + // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2] + int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11; + // Clamp to output range. + val = MathUtil::Clamp(val, -32768, 32767); + // Advance output feedback. + yn2 = yn1; + yn1 = val; + return (s16)val; + }; + + size_t outputi = framei * SAMPLES_PER_FRAME; + size_t datai = framei * FRAME_LEN + 1; + for (size_t i = 0; i < SAMPLES_PER_FRAME && outputi < sample_count; i += 2) { + const s16 sample1 = decode_sample(SIGNED_NIBBLES[data[datai] & 0xF]); + ret[outputi].fill(sample1); + outputi++; + + const s16 sample2 = decode_sample(SIGNED_NIBBLES[data[datai] >> 4]); + ret[outputi].fill(sample2); + outputi++; + + datai++; + } + } + + state.yn1 = yn1; + state.yn2 = yn2; + + return ret; +} + +static s16 SignExtendS8(u8 x) { + // The data is actually signed PCM8. + // We sign extend this to signed PCM16. + return static_cast<s16>(static_cast<s8>(x)); +} + +StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data, const size_t sample_count) { + ASSERT(num_channels == 1 || num_channels == 2); + + StereoBuffer16 ret(sample_count); + + if (num_channels == 1) { + for (size_t i = 0; i < sample_count; i++) { + ret[i].fill(SignExtendS8(data[i])); + } + } else { + for (size_t i = 0; i < sample_count; i++) { + ret[i][0] = SignExtendS8(data[i * 2 + 0]); + ret[i][1] = SignExtendS8(data[i * 2 + 1]); + } + } + + return ret; +} + +StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data, const size_t sample_count) { + ASSERT(num_channels == 1 || num_channels == 2); + + StereoBuffer16 ret(sample_count); + + if (num_channels == 1) { + for (size_t i = 0; i < sample_count; i++) { + s16 sample; + std::memcpy(&sample, data + i * sizeof(s16), sizeof(s16)); + ret[i].fill(sample); + } + } else { + std::memcpy(ret.data(), data, sample_count * 2 * sizeof(u16)); + } + + return ret; +} + +}; diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h new file mode 100644 index 000000000..e695f2edc --- /dev/null +++ b/src/audio_core/codec.h @@ -0,0 +1,50 @@ +// 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_types.h" + +namespace Codec { + +/// A variable length buffer of signed PCM16 stereo samples. +using StereoBuffer16 = std::vector<std::array<s16, 2>>; + +/// See: Codec::DecodeADPCM +struct ADPCMState { + // Two historical samples from previous processed buffer, + // required for ADPCM decoding + s16 yn1; ///< y[n-1] + s16 yn2; ///< y[n-2] +}; + +/** + * @param data Pointer to buffer that contains ADPCM data to decode + * @param sample_count Length of buffer in terms of number of samples + * @param adpcm_coeff ADPCM coefficients + * @param state ADPCM state, this is updated with new state + * @return Decoded stereo signed PCM16 data, sample_count in length + */ +StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count, const std::array<s16, 16>& adpcm_coeff, ADPCMState& state); + +/** + * @param num_channels Number of channels + * @param data Pointer to buffer that contains PCM8 data to decode + * @param sample_count Length of buffer in terms of number of samples + * @return Decoded stereo signed PCM16 data, sample_count in length + */ +StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data, const size_t sample_count); + +/** + * @param num_channels Number of channels + * @param data Pointer to buffer that contains PCM16 data to decode + * @param sample_count Length of buffer in terms of number of samples + * @return Decoded stereo signed PCM16 data, sample_count in length + */ +StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data, const size_t sample_count); + +}; diff --git a/src/audio_core/hle/common.h b/src/audio_core/hle/common.h new file mode 100644 index 000000000..596b67eaf --- /dev/null +++ b/src/audio_core/hle/common.h @@ -0,0 +1,36 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <algorithm> +#include <array> + +#include "common/common_types.h" + +namespace DSP { +namespace HLE { + +constexpr int num_sources = 24; +constexpr int samples_per_frame = 160; ///< Samples per audio frame at native sample rate + +/// The final output to the speakers is stereo. Preprocessing output in Source is also stereo. +using StereoFrame16 = std::array<std::array<s16, 2>, samples_per_frame>; + +/// The DSP is quadraphonic internally. +using QuadFrame32 = std::array<std::array<s32, 4>, samples_per_frame>; + +/** + * This performs the filter operation defined by FilterT::ProcessSample on the frame in-place. + * FilterT::ProcessSample is called sequentially on the samples. + */ +template<typename FrameT, typename FilterT> +void FilterFrame(FrameT& frame, FilterT& filter) { + std::transform(frame.begin(), frame.end(), frame.begin(), [&filter](const auto& sample) { + return filter.ProcessSample(sample); + }); +} + +} // namespace HLE +} // namespace DSP diff --git a/src/audio_core/hle/dsp.cpp b/src/audio_core/hle/dsp.cpp index c89356edc..0cdbdb06a 100644 --- a/src/audio_core/hle/dsp.cpp +++ b/src/audio_core/hle/dsp.cpp @@ -2,40 +2,81 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> +#include <memory> + #include "audio_core/hle/dsp.h" #include "audio_core/hle/pipe.h" +#include "audio_core/hle/source.h" +#include "audio_core/sink.h" namespace DSP { namespace HLE { -SharedMemory g_region0; -SharedMemory g_region1; +std::array<SharedMemory, 2> g_regions; + +static size_t CurrentRegionIndex() { + // The region with the higher frame counter is chosen unless there is wraparound. + // This function only returns a 0 or 1. + + if (g_regions[0].frame_counter == 0xFFFFu && g_regions[1].frame_counter != 0xFFFEu) { + // Wraparound has occured. + return 1; + } + + if (g_regions[1].frame_counter == 0xFFFFu && g_regions[0].frame_counter != 0xFFFEu) { + // Wraparound has occured. + return 0; + } + + return (g_regions[0].frame_counter > g_regions[1].frame_counter) ? 0 : 1; +} + +static SharedMemory& ReadRegion() { + return g_regions[CurrentRegionIndex()]; +} + +static SharedMemory& WriteRegion() { + return g_regions[1 - CurrentRegionIndex()]; +} + +static std::array<Source, num_sources> sources = { + Source(0), Source(1), Source(2), Source(3), Source(4), Source(5), + Source(6), Source(7), Source(8), Source(9), Source(10), Source(11), + Source(12), Source(13), Source(14), Source(15), Source(16), Source(17), + Source(18), Source(19), Source(20), Source(21), Source(22), Source(23) +}; + +static std::unique_ptr<AudioCore::Sink> sink; void Init() { DSP::HLE::ResetPipes(); + for (auto& source : sources) { + source.Reset(); + } } void Shutdown() { } bool Tick() { - return true; -} + SharedMemory& read = ReadRegion(); + SharedMemory& write = WriteRegion(); -SharedMemory& CurrentRegion() { - // The region with the higher frame counter is chosen unless there is wraparound. + std::array<QuadFrame32, 3> intermediate_mixes = {}; - if (g_region0.frame_counter == 0xFFFFu && g_region1.frame_counter != 0xFFFEu) { - // Wraparound has occured. - return g_region1; + 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); + } } - if (g_region1.frame_counter == 0xFFFFu && g_region0.frame_counter != 0xFFFEu) { - // Wraparound has occured. - return g_region0; - } + return true; +} - return (g_region0.frame_counter > g_region1.frame_counter) ? g_region0 : g_region1; +void SetSink(std::unique_ptr<AudioCore::Sink> sink_) { + sink = std::move(sink_); } } // namespace HLE diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/dsp.h index 376436c29..4459a5668 100644 --- a/src/audio_core/hle/dsp.h +++ b/src/audio_core/hle/dsp.h @@ -4,16 +4,22 @@ #pragma once +#include <array> #include <cstddef> +#include <memory> #include <type_traits> -#include "audio_core/audio_core.h" +#include "audio_core/hle/common.h" #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" +namespace AudioCore { +class Sink; +} + namespace DSP { namespace HLE { @@ -30,10 +36,9 @@ namespace HLE { struct SharedMemory; constexpr VAddr region0_base = 0x1FF50000; -extern SharedMemory g_region0; - constexpr VAddr region1_base = 0x1FF70000; -extern SharedMemory g_region1; + +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 @@ -126,8 +131,11 @@ struct SourceConfiguration { union { u32_le dirty_raw; + BitField<0, 1, u32_le> format_dirty; + BitField<1, 1, u32_le> mono_or_stereo_dirty; BitField<2, 1, u32_le> adpcm_coefficients_dirty; BitField<3, 1, u32_le> partial_embedded_buffer_dirty; ///< Tends to be set when a looped buffer is queued. + BitField<4, 1, u32_le> partial_reset_flag; BitField<16, 1, u32_le> enable_dirty; BitField<17, 1, u32_le> interpolation_dirty; @@ -143,8 +151,7 @@ struct SourceConfiguration { BitField<27, 1, u32_le> gain_2_dirty; BitField<28, 1, u32_le> sync_dirty; BitField<29, 1, u32_le> reset_flag; - - BitField<31, 1, u32_le> embedded_buffer_dirty; + BitField<30, 1, u32_le> embedded_buffer_dirty; }; // Gain control @@ -162,9 +169,9 @@ struct SourceConfiguration { float_le rate_multiplier; enum class InterpolationMode : u8 { - None = 0, + Polyphase = 0, Linear = 1, - Polyphase = 2 + None = 2 }; InterpolationMode interpolation_mode; @@ -175,7 +182,8 @@ struct SourceConfiguration { /** * This is the simplest normalized first-order digital recursive filter. * The transfer function of this filter is: - * H(z) = b0 / (1 + a1 z^-1) + * H(z) = b0 / (1 - a1 z^-1) + * Note the feedbackward coefficient is negated. * Values are signed fixed point with 15 fractional bits. */ struct SimpleFilter { @@ -192,11 +200,11 @@ struct SourceConfiguration { * Values are signed fixed point with 14 fractional bits. */ struct BiquadFilter { - s16_le b0; - s16_le b1; - s16_le b2; - s16_le a1; s16_le a2; + s16_le a1; + s16_le b2; + s16_le b1; + s16_le b0; }; union { @@ -302,7 +310,7 @@ struct SourceConfiguration { u16_le buffer_id; }; - Configuration config[AudioCore::num_sources]; + Configuration config[num_sources]; }; ASSERT_DSP_STRUCT(SourceConfiguration::Configuration, 192); ASSERT_DSP_STRUCT(SourceConfiguration::Configuration::Buffer, 20); @@ -310,14 +318,14 @@ ASSERT_DSP_STRUCT(SourceConfiguration::Configuration::Buffer, 20); struct SourceStatus { struct Status { u8 is_enabled; ///< Is this channel enabled? (Doesn't have to be playing anything.) - u8 previous_buffer_id_dirty; ///< Non-zero when previous_buffer_id changes + u8 current_buffer_id_dirty; ///< Non-zero when current_buffer_id changes u16_le sync; ///< Is set by the DSP to the value of SourceConfiguration::sync u32_dsp buffer_position; ///< Number of samples into the current buffer - u16_le previous_buffer_id; ///< Updated when a buffer finishes playing + u16_le current_buffer_id; ///< Updated when a buffer finishes playing INSERT_PADDING_DSPWORDS(1); }; - Status status[AudioCore::num_sources]; + Status status[num_sources]; }; ASSERT_DSP_STRUCT(SourceStatus::Status, 12); @@ -410,7 +418,7 @@ ASSERT_DSP_STRUCT(DspConfiguration::ReverbEffect, 52); struct AdpcmCoefficients { /// Coefficients are signed fixed point with 11 fractional bits. /// Each source has 16 coefficients associated with it. - s16_le coeff[AudioCore::num_sources][16]; + s16_le coeff[num_sources][16]; }; ASSERT_DSP_STRUCT(AdpcmCoefficients, 768); @@ -424,7 +432,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 * AudioCore::samples_per_frame]; + s16_le pcm16[2 * samples_per_frame]; }; ASSERT_DSP_STRUCT(FinalMixSamples, 640); @@ -434,7 +442,7 @@ ASSERT_DSP_STRUCT(FinalMixSamples, 640); /// Values that exceed s16 range will be clipped by the DSP after further processing. struct IntermediateMixSamples { struct Samples { - s32_le pcm32[4][AudioCore::samples_per_frame]; ///< Little-endian as opposed to DSP middle-endian. + s32_le pcm32[4][samples_per_frame]; ///< Little-endian as opposed to DSP middle-endian. }; Samples mix1; @@ -532,8 +540,11 @@ void Shutdown(); */ bool Tick(); -/// Returns a mutable reference to the current region. Current region is selected based on the frame counter. -SharedMemory& CurrentRegion(); +/** + * Set the output sink. This must be called before calling Tick(). + * @param sink The sink to which audio will be output to. + */ +void SetSink(std::unique_ptr<AudioCore::Sink> sink); } // namespace HLE } // namespace DSP diff --git a/src/audio_core/hle/filter.cpp b/src/audio_core/hle/filter.cpp new file mode 100644 index 000000000..2c65ef026 --- /dev/null +++ b/src/audio_core/hle/filter.cpp @@ -0,0 +1,115 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <cstddef> + +#include "audio_core/hle/common.h" +#include "audio_core/hle/dsp.h" +#include "audio_core/hle/filter.h" + +#include "common/common_types.h" +#include "common/math_util.h" + +namespace DSP { +namespace HLE { + +void SourceFilters::Reset() { + Enable(false, false); +} + +void SourceFilters::Enable(bool simple, bool biquad) { + simple_filter_enabled = simple; + biquad_filter_enabled = biquad; + + if (!simple) + simple_filter.Reset(); + if (!biquad) + biquad_filter.Reset(); +} + +void SourceFilters::Configure(SourceConfiguration::Configuration::SimpleFilter config) { + simple_filter.Configure(config); +} + +void SourceFilters::Configure(SourceConfiguration::Configuration::BiquadFilter config) { + biquad_filter.Configure(config); +} + +void SourceFilters::ProcessFrame(StereoFrame16& frame) { + if (!simple_filter_enabled && !biquad_filter_enabled) + return; + + if (simple_filter_enabled) { + FilterFrame(frame, simple_filter); + } + + if (biquad_filter_enabled) { + FilterFrame(frame, biquad_filter); + } +} + +// SimpleFilter + +void SourceFilters::SimpleFilter::Reset() { + y1.fill(0); + // Configure as passthrough. + a1 = 0; + b0 = 1 << 15; +} + +void SourceFilters::SimpleFilter::Configure(SourceConfiguration::Configuration::SimpleFilter config) { + a1 = config.a1; + b0 = config.b0; +} + +std::array<s16, 2> SourceFilters::SimpleFilter::ProcessSample(const std::array<s16, 2>& x0) { + std::array<s16, 2> y0; + for (size_t i = 0; i < 2; i++) { + const s32 tmp = (b0 * x0[i] + a1 * y1[i]) >> 15; + y0[i] = MathUtil::Clamp(tmp, -32768, 32767); + } + + y1 = y0; + + return y0; +} + +// BiquadFilter + +void SourceFilters::BiquadFilter::Reset() { + x1.fill(0); + x2.fill(0); + y1.fill(0); + y2.fill(0); + // Configure as passthrough. + a1 = a2 = b1 = b2 = 0; + b0 = 1 << 14; +} + +void SourceFilters::BiquadFilter::Configure(SourceConfiguration::Configuration::BiquadFilter config) { + a1 = config.a1; + a2 = config.a2; + b0 = config.b0; + b1 = config.b1; + b2 = config.b2; +} + +std::array<s16, 2> SourceFilters::BiquadFilter::ProcessSample(const std::array<s16, 2>& x0) { + std::array<s16, 2> y0; + for (size_t i = 0; i < 2; i++) { + const s32 tmp = (b0 * x0[i] + b1 * x1[i] + b2 * x2[i] + a1 * y1[i] + a2 * y2[i]) >> 14; + y0[i] = MathUtil::Clamp(tmp, -32768, 32767); + } + + x2 = x1; + x1 = x0; + y2 = y1; + y1 = y0; + + return y0; +} + +} // namespace HLE +} // namespace DSP diff --git a/src/audio_core/hle/filter.h b/src/audio_core/hle/filter.h new file mode 100644 index 000000000..43d2035cd --- /dev/null +++ b/src/audio_core/hle/filter.h @@ -0,0 +1,113 @@ +// 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" + +#include "common/common_types.h" + +namespace DSP { +namespace HLE { + +/// Preprocessing filters. There is an independent set of filters for each Source. +class SourceFilters final { +public: + SourceFilters() { Reset(); } + + /// Reset internal state. + void Reset(); + + /** + * Enable/Disable filters + * See also: SourceConfiguration::Configuration::simple_filter_enabled, + * SourceConfiguration::Configuration::biquad_filter_enabled. + * @param simple If true, enables the simple filter. If false, disables it. + * @param simple If true, enables the biquad filter. If false, disables it. + */ + void Enable(bool simple, bool biquad); + + /** + * Configure simple filter. + * @param config Configuration from DSP shared memory. + */ + void Configure(SourceConfiguration::Configuration::SimpleFilter config); + + /** + * Configure biquad filter. + * @param config Configuration from DSP shared memory. + */ + void Configure(SourceConfiguration::Configuration::BiquadFilter config); + + /** + * Processes a frame in-place. + * @param frame Audio samples to process. Modified in-place. + */ + void ProcessFrame(StereoFrame16& frame); + +private: + bool simple_filter_enabled; + bool biquad_filter_enabled; + + struct SimpleFilter { + SimpleFilter() { Reset(); } + + /// Resets internal state. + void Reset(); + + /** + * Configures this filter with application settings. + * @param config Configuration from DSP shared memory. + */ + void Configure(SourceConfiguration::Configuration::SimpleFilter config); + + /** + * Processes a single stereo PCM16 sample. + * @param x0 Input sample + * @return Output sample + */ + std::array<s16, 2> ProcessSample(const std::array<s16, 2>& x0); + + private: + // Configuration + s32 a1, b0; + // Internal state + std::array<s16, 2> y1; + } simple_filter; + + struct BiquadFilter { + BiquadFilter() { Reset(); } + + /// Resets internal state. + void Reset(); + + /** + * Configures this filter with application settings. + * @param config Configuration from DSP shared memory. + */ + void Configure(SourceConfiguration::Configuration::BiquadFilter config); + + /** + * Processes a single stereo PCM16 sample. + * @param x0 Input sample + * @return Output sample + */ + std::array<s16, 2> ProcessSample(const std::array<s16, 2>& x0); + + private: + // Configuration + s32 a1, a2, b0, b1, b2; + // Internal state + std::array<s16, 2> x1; + std::array<s16, 2> x2; + std::array<s16, 2> y1; + std::array<s16, 2> y2; + } biquad_filter; +}; + +} // namespace HLE +} // namespace DSP diff --git a/src/audio_core/hle/pipe.cpp b/src/audio_core/hle/pipe.cpp index 9381883b4..03280780f 100644 --- a/src/audio_core/hle/pipe.cpp +++ b/src/audio_core/hle/pipe.cpp @@ -12,12 +12,14 @@ #include "common/common_types.h" #include "common/logging/log.h" +#include "core/hle/service/dsp_dsp.h" + namespace DSP { namespace HLE { static DspState dsp_state = DspState::Off; -static std::array<std::vector<u8>, static_cast<size_t>(DspPipe::DspPipe_MAX)> pipe_data; +static std::array<std::vector<u8>, NUM_DSP_PIPE> pipe_data; void ResetPipes() { for (auto& data : pipe_data) { @@ -27,16 +29,18 @@ void ResetPipes() { } std::vector<u8> PipeRead(DspPipe pipe_number, u32 length) { - if (pipe_number >= DspPipe::DspPipe_MAX) { - LOG_ERROR(Audio_DSP, "pipe_number = %u invalid", pipe_number); + const size_t pipe_index = static_cast<size_t>(pipe_number); + + if (pipe_index >= NUM_DSP_PIPE) { + LOG_ERROR(Audio_DSP, "pipe_number = %zu invalid", pipe_index); return {}; } - std::vector<u8>& data = pipe_data[static_cast<size_t>(pipe_number)]; + std::vector<u8>& data = pipe_data[pipe_index]; if (length > data.size()) { - LOG_WARNING(Audio_DSP, "pipe_number = %u is out of data, application requested read of %u but %zu remain", - pipe_number, 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(); } @@ -49,16 +53,20 @@ std::vector<u8> PipeRead(DspPipe pipe_number, u32 length) { } size_t GetPipeReadableSize(DspPipe pipe_number) { - if (pipe_number >= DspPipe::DspPipe_MAX) { - LOG_ERROR(Audio_DSP, "pipe_number = %u invalid", pipe_number); + const size_t pipe_index = static_cast<size_t>(pipe_number); + + if (pipe_index >= NUM_DSP_PIPE) { + LOG_ERROR(Audio_DSP, "pipe_number = %zu invalid", pipe_index); return 0; } - return pipe_data[static_cast<size_t>(pipe_number)].size(); + return pipe_data[pipe_index].size(); } static void WriteU16(DspPipe pipe_number, u16 value) { - std::vector<u8>& data = pipe_data[static_cast<size_t>(pipe_number)]; + const size_t pipe_index = static_cast<size_t>(pipe_number); + + std::vector<u8>& data = pipe_data.at(pipe_index); // Little endian data.emplace_back(value & 0xFF); data.emplace_back(value >> 8); @@ -91,6 +99,8 @@ static void AudioPipeWriteStructAddresses() { for (u16 addr : struct_addresses) { WriteU16(DspPipe::Audio, addr); } + // Signal that we have data on this pipe. + DSP_DSP::SignalPipeInterrupt(DspPipe::Audio); } void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) { @@ -145,7 +155,7 @@ void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) { return; } default: - LOG_CRITICAL(Audio_DSP, "pipe_number = %u unimplemented", pipe_number); + LOG_CRITICAL(Audio_DSP, "pipe_number = %zu unimplemented", static_cast<size_t>(pipe_number)); UNIMPLEMENTED(); return; } diff --git a/src/audio_core/hle/pipe.h b/src/audio_core/hle/pipe.h index 382d35e87..64d97f8ba 100644 --- a/src/audio_core/hle/pipe.h +++ b/src/audio_core/hle/pipe.h @@ -19,9 +19,9 @@ enum class DspPipe { Debug = 0, Dma = 1, Audio = 2, - Binary = 3, - DspPipe_MAX + Binary = 3 }; +constexpr size_t NUM_DSP_PIPE = 8; /** * Read a DSP pipe. diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp new file mode 100644 index 000000000..daaf6e3f3 --- /dev/null +++ b/src/audio_core/hle/source.cpp @@ -0,0 +1,320 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <array> + +#include "audio_core/codec.h" +#include "audio_core/hle/common.h" +#include "audio_core/hle/source.h" +#include "audio_core/interpolate.h" + +#include "common/assert.h" +#include "common/logging/log.h" + +#include "core/memory.h" + +namespace DSP { +namespace HLE { + +SourceStatus::Status Source::Tick(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]) { + ParseConfig(config, adpcm_coeffs); + + if (state.enabled) { + GenerateFrame(); + } + + return GetCurrentStatus(); +} + +void Source::MixInto(QuadFrame32& dest, size_t intermediate_mix_id) const { + if (!state.enabled) + return; + + const std::array<float, 4>& gains = state.gain.at(intermediate_mix_id); + for (size_t samplei = 0; samplei < samples_per_frame; samplei++) { + // Conversion from stereo (current_frame) to quadraphonic (dest) occurs here. + dest[samplei][0] += static_cast<s32>(gains[0] * current_frame[samplei][0]); + dest[samplei][1] += static_cast<s32>(gains[1] * current_frame[samplei][1]); + dest[samplei][2] += static_cast<s32>(gains[2] * current_frame[samplei][0]); + dest[samplei][3] += static_cast<s32>(gains[3] * current_frame[samplei][1]); + } +} + +void Source::Reset() { + current_frame.fill({}); + state = {}; +} + +void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]) { + if (!config.dirty_raw) { + return; + } + + if (config.reset_flag) { + config.reset_flag.Assign(0); + Reset(); + LOG_TRACE(Audio_DSP, "source_id=%zu reset", source_id); + } + + if (config.partial_reset_flag) { + config.partial_reset_flag.Assign(0); + state.input_queue = std::priority_queue<Buffer, std::vector<Buffer>, BufferOrder>{}; + LOG_TRACE(Audio_DSP, "source_id=%zu partial_reset", source_id); + } + + if (config.enable_dirty) { + config.enable_dirty.Assign(0); + state.enabled = config.enable != 0; + LOG_TRACE(Audio_DSP, "source_id=%zu enable=%d", source_id, state.enabled); + } + + if (config.sync_dirty) { + config.sync_dirty.Assign(0); + state.sync = config.sync; + LOG_TRACE(Audio_DSP, "source_id=%zu sync=%u", source_id, state.sync); + } + + if (config.rate_multiplier_dirty) { + config.rate_multiplier_dirty.Assign(0); + state.rate_multiplier = config.rate_multiplier; + LOG_TRACE(Audio_DSP, "source_id=%zu rate=%f", source_id, state.rate_multiplier); + + if (state.rate_multiplier <= 0) { + LOG_ERROR(Audio_DSP, "Was given an invalid rate multiplier: source_id=%zu rate=%f", source_id, state.rate_multiplier); + state.rate_multiplier = 1.0f; + // Note: Actual firmware starts producing garbage if this occurs. + } + } + + if (config.adpcm_coefficients_dirty) { + config.adpcm_coefficients_dirty.Assign(0); + std::transform(adpcm_coeffs, adpcm_coeffs + state.adpcm_coeffs.size(), state.adpcm_coeffs.begin(), + [](const auto& coeff) { return static_cast<s16>(coeff); }); + LOG_TRACE(Audio_DSP, "source_id=%zu adpcm update", source_id); + } + + if (config.gain_0_dirty) { + config.gain_0_dirty.Assign(0); + std::transform(config.gain[0], config.gain[0] + state.gain[0].size(), state.gain[0].begin(), + [](const auto& coeff) { return static_cast<float>(coeff); }); + LOG_TRACE(Audio_DSP, "source_id=%zu gain 0 update", source_id); + } + + if (config.gain_1_dirty) { + config.gain_1_dirty.Assign(0); + std::transform(config.gain[1], config.gain[1] + state.gain[1].size(), state.gain[1].begin(), + [](const auto& coeff) { return static_cast<float>(coeff); }); + LOG_TRACE(Audio_DSP, "source_id=%zu gain 1 update", source_id); + } + + if (config.gain_2_dirty) { + config.gain_2_dirty.Assign(0); + std::transform(config.gain[2], config.gain[2] + state.gain[2].size(), state.gain[2].begin(), + [](const auto& coeff) { return static_cast<float>(coeff); }); + LOG_TRACE(Audio_DSP, "source_id=%zu gain 2 update", source_id); + } + + if (config.filters_enabled_dirty) { + config.filters_enabled_dirty.Assign(0); + state.filters.Enable(config.simple_filter_enabled.ToBool(), config.biquad_filter_enabled.ToBool()); + LOG_TRACE(Audio_DSP, "source_id=%zu enable_simple=%hu enable_biquad=%hu", + source_id, config.simple_filter_enabled.Value(), config.biquad_filter_enabled.Value()); + } + + if (config.simple_filter_dirty) { + config.simple_filter_dirty.Assign(0); + state.filters.Configure(config.simple_filter); + LOG_TRACE(Audio_DSP, "source_id=%zu simple filter update"); + } + + if (config.biquad_filter_dirty) { + config.biquad_filter_dirty.Assign(0); + state.filters.Configure(config.biquad_filter); + LOG_TRACE(Audio_DSP, "source_id=%zu biquad filter update"); + } + + if (config.interpolation_dirty) { + config.interpolation_dirty.Assign(0); + state.interpolation_mode = config.interpolation_mode; + LOG_TRACE(Audio_DSP, "source_id=%zu interpolation_mode=%zu", source_id, static_cast<size_t>(state.interpolation_mode)); + } + + if (config.format_dirty || config.embedded_buffer_dirty) { + config.format_dirty.Assign(0); + state.format = config.format; + LOG_TRACE(Audio_DSP, "source_id=%zu format=%zu", source_id, static_cast<size_t>(state.format)); + } + + if (config.mono_or_stereo_dirty || config.embedded_buffer_dirty) { + config.mono_or_stereo_dirty.Assign(0); + state.mono_or_stereo = config.mono_or_stereo; + LOG_TRACE(Audio_DSP, "source_id=%zu mono_or_stereo=%zu", source_id, static_cast<size_t>(state.mono_or_stereo)); + } + + if (config.embedded_buffer_dirty) { + config.embedded_buffer_dirty.Assign(0); + state.input_queue.emplace(Buffer{ + config.physical_address, + config.length, + static_cast<u8>(config.adpcm_ps), + { config.adpcm_yn[0], config.adpcm_yn[1] }, + config.adpcm_dirty.ToBool(), + config.is_looping.ToBool(), + config.buffer_id, + state.mono_or_stereo, + state.format, + false + }); + LOG_TRACE(Audio_DSP, "enqueuing embedded addr=0x%08x len=%u id=%hu", config.physical_address, config.length, config.buffer_id); + } + + if (config.buffer_queue_dirty) { + config.buffer_queue_dirty.Assign(0); + for (size_t i = 0; i < 4; i++) { + if (config.buffers_dirty & (1 << i)) { + const auto& b = config.buffers[i]; + state.input_queue.emplace(Buffer{ + b.physical_address, + b.length, + static_cast<u8>(b.adpcm_ps), + { b.adpcm_yn[0], b.adpcm_yn[1] }, + b.adpcm_dirty != 0, + b.is_looping != 0, + b.buffer_id, + state.mono_or_stereo, + state.format, + true + }); + LOG_TRACE(Audio_DSP, "enqueuing queued %zu addr=0x%08x len=%u id=%hu", i, b.physical_address, b.length, b.buffer_id); + } + } + config.buffers_dirty = 0; + } + + if (config.dirty_raw) { + LOG_DEBUG(Audio_DSP, "source_id=%zu remaining_dirty=%x", source_id, config.dirty_raw); + } + + config.dirty_raw = 0; +} + +void Source::GenerateFrame() { + current_frame.fill({}); + + if (state.current_buffer.empty() && !DequeueBuffer()) { + state.enabled = false; + state.buffer_update = true; + state.current_buffer_id = 0; + return; + } + + size_t frame_position = 0; + + state.current_sample_number = state.next_sample_number; + while (frame_position < current_frame.size()) { + if (state.current_buffer.empty() && !DequeueBuffer()) { + break; + } + + const size_t size_to_copy = std::min(state.current_buffer.size(), current_frame.size() - frame_position); + + std::copy(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy, current_frame.begin() + frame_position); + state.current_buffer.erase(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy); + + frame_position += size_to_copy; + state.next_sample_number += static_cast<u32>(size_to_copy); + } + + state.filters.ProcessFrame(current_frame); +} + + +bool Source::DequeueBuffer() { + ASSERT_MSG(state.current_buffer.empty(), "Shouldn't dequeue; we still have data in current_buffer"); + + if (state.input_queue.empty()) + return false; + + const Buffer buf = state.input_queue.top(); + state.input_queue.pop(); + + if (buf.adpcm_dirty) { + state.adpcm_state.yn1 = buf.adpcm_yn[0]; + state.adpcm_state.yn2 = buf.adpcm_yn[1]; + } + + if (buf.is_looping) { + LOG_ERROR(Audio_DSP, "Looped buffers are unimplemented at the moment"); + } + + const u8* const memory = Memory::GetPhysicalPointer(buf.physical_address); + if (memory) { + const unsigned num_channels = buf.mono_or_stereo == MonoOrStereo::Stereo ? 2 : 1; + switch (buf.format) { + case Format::PCM8: + state.current_buffer = Codec::DecodePCM8(num_channels, memory, buf.length); + break; + case Format::PCM16: + state.current_buffer = Codec::DecodePCM16(num_channels, memory, buf.length); + break; + case Format::ADPCM: + DEBUG_ASSERT(num_channels == 1); + state.current_buffer = Codec::DecodeADPCM(memory, buf.length, state.adpcm_coeffs, state.adpcm_state); + break; + default: + UNIMPLEMENTED(); + break; + } + } else { + LOG_WARNING(Audio_DSP, "source_id=%zu buffer_id=%hu length=%u: Invalid physical address 0x%08X", + source_id, buf.buffer_id, buf.length, buf.physical_address); + state.current_buffer.clear(); + return true; + } + + switch (state.interpolation_mode) { + case InterpolationMode::None: + state.current_buffer = AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier); + break; + case InterpolationMode::Linear: + state.current_buffer = AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier); + break; + case InterpolationMode::Polyphase: + // TODO(merry): Implement polyphase interpolation + state.current_buffer = AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier); + break; + default: + UNIMPLEMENTED(); + break; + } + + state.current_sample_number = 0; + state.next_sample_number = 0; + state.current_buffer_id = buf.buffer_id; + state.buffer_update = buf.from_queue; + + LOG_TRACE(Audio_DSP, "source_id=%zu buffer_id=%hu from_queue=%s current_buffer.size()=%zu", + source_id, buf.buffer_id, buf.from_queue ? "true" : "false", state.current_buffer.size()); + return true; +} + +SourceStatus::Status Source::GetCurrentStatus() { + SourceStatus::Status ret; + + // Applications depend on the correct emulation of + // current_buffer_id_dirty and current_buffer_id to synchronise + // audio with video. + ret.is_enabled = state.enabled; + ret.current_buffer_id_dirty = state.buffer_update ? 1 : 0; + state.buffer_update = false; + ret.current_buffer_id = state.current_buffer_id; + ret.buffer_position = state.current_sample_number; + ret.sync = state.sync; + + return ret; +} + +} // namespace HLE +} // namespace DSP diff --git a/src/audio_core/hle/source.h b/src/audio_core/hle/source.h new file mode 100644 index 000000000..7ee08d424 --- /dev/null +++ b/src/audio_core/hle/source.h @@ -0,0 +1,144 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <queue> +#include <vector> + +#include "audio_core/codec.h" +#include "audio_core/hle/common.h" +#include "audio_core/hle/dsp.h" +#include "audio_core/hle/filter.h" +#include "audio_core/interpolate.h" + +#include "common/common_types.h" + +namespace DSP { +namespace HLE { + +/** + * This module performs: + * - Buffer management + * - Decoding of buffers + * - Buffer resampling and interpolation + * - Per-source filtering (SimpleFilter, BiquadFilter) + * - Per-source gain + * - Other per-source processing + */ +class Source final { +public: + explicit Source(size_t source_id_) : source_id(source_id_) { + Reset(); + } + + /// Resets internal state. + void Reset(); + + /** + * This is called once every audio frame. This performs per-source processing every frame. + * @param config The new configuration we've got for this Source from the application. + * @param adpcm_coeffs ADPCM coefficients to use if config tells us to use them (may contain invalid values otherwise). + * @return The current status of this Source. This is given back to the emulated application via SharedMemory. + */ + SourceStatus::Status Tick(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]); + + /** + * Mix this source's output into dest, using the gains for the `intermediate_mix_id`-th intermediate mixer. + * @param dest The QuadFrame32 to mix into. + * @param intermediate_mix_id The id of the intermediate mix whose gains we are using. + */ + void MixInto(QuadFrame32& dest, size_t intermediate_mix_id) const; + +private: + const size_t source_id; + StereoFrame16 current_frame; + + using Format = SourceConfiguration::Configuration::Format; + using InterpolationMode = SourceConfiguration::Configuration::InterpolationMode; + using MonoOrStereo = SourceConfiguration::Configuration::MonoOrStereo; + + /// Internal representation of a buffer for our buffer queue + struct Buffer { + PAddr physical_address; + u32 length; + u8 adpcm_ps; + std::array<u16, 2> adpcm_yn; + bool adpcm_dirty; + bool is_looping; + u16 buffer_id; + + MonoOrStereo mono_or_stereo; + Format format; + + bool from_queue; + }; + + struct BufferOrder { + bool operator() (const Buffer& a, const Buffer& b) const { + // Lower buffer_id comes first. + return a.buffer_id > b.buffer_id; + } + }; + + struct { + + // State variables + + bool enabled = false; + u16 sync = 0; + + // Mixing + + std::array<std::array<float, 4>, 3> gain = {}; + + // Buffer queue + + std::priority_queue<Buffer, std::vector<Buffer>, BufferOrder> input_queue; + MonoOrStereo mono_or_stereo = MonoOrStereo::Mono; + Format format = Format::ADPCM; + + // Current buffer + + u32 current_sample_number = 0; + u32 next_sample_number = 0; + std::vector<std::array<s16, 2>> current_buffer; + + // buffer_id state + + bool buffer_update = false; + u32 current_buffer_id = 0; + + // Decoding state + + std::array<s16, 16> adpcm_coeffs = {}; + Codec::ADPCMState adpcm_state = {}; + + // Resampling state + + float rate_multiplier = 1.0; + InterpolationMode interpolation_mode = InterpolationMode::Polyphase; + AudioInterp::State interp_state = {}; + + // Filter state + + SourceFilters filters; + + } state; + + // Internal functions + + /// INTERNAL: Update our internal state based on the current config. + void ParseConfig(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]); + /// INTERNAL: Generate the current audio output for this frame based on our internal state. + void GenerateFrame(); + /// INTERNAL: Dequeues a buffer and does preprocessing on it (decoding, resampling). Puts it into current_buffer. + bool DequeueBuffer(); + /// INTERNAL: Generates a SourceStatus::Status based on our internal state. + SourceStatus::Status GetCurrentStatus(); +}; + +} // namespace HLE +} // namespace DSP diff --git a/src/audio_core/interpolate.cpp b/src/audio_core/interpolate.cpp new file mode 100644 index 000000000..fcd3aa066 --- /dev/null +++ b/src/audio_core/interpolate.cpp @@ -0,0 +1,85 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/interpolate.h" + +#include "common/assert.h" +#include "common/math_util.h" + +namespace AudioInterp { + +// Calculations are done in fixed point with 24 fractional bits. +// (This is not verified. This was chosen for minimal error.) +constexpr u64 scale_factor = 1 << 24; +constexpr u64 scale_mask = scale_factor - 1; + +/// Here we step over the input in steps of rate_multiplier, until we consume all of the input. +/// Three adjacent samples are passed to fn each step. +template <typename Function> +static StereoBuffer16 StepOverSamples(State& state, const StereoBuffer16& input, float rate_multiplier, Function fn) { + ASSERT(rate_multiplier > 0); + + if (input.size() < 2) + return {}; + + StereoBuffer16 output; + output.reserve(static_cast<size_t>(input.size() / rate_multiplier)); + + u64 step_size = static_cast<u64>(rate_multiplier * scale_factor); + + u64 fposition = 0; + const u64 max_fposition = input.size() * scale_factor; + + while (fposition < 1 * scale_factor) { + u64 fraction = fposition & scale_mask; + + output.push_back(fn(fraction, state.xn2, state.xn1, input[0])); + + fposition += step_size; + } + + while (fposition < 2 * scale_factor) { + u64 fraction = fposition & scale_mask; + + output.push_back(fn(fraction, state.xn1, input[0], input[1])); + + fposition += step_size; + } + + while (fposition < max_fposition) { + u64 fraction = fposition & scale_mask; + + size_t index = static_cast<size_t>(fposition / scale_factor); + output.push_back(fn(fraction, input[index - 2], input[index - 1], input[index])); + + fposition += step_size; + } + + state.xn2 = input[input.size() - 2]; + state.xn1 = input[input.size() - 1]; + + return output; +} + +StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier) { + return StepOverSamples(state, input, rate_multiplier, [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { + return x0; + }); +} + +StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier) { + // Note on accuracy: Some values that this produces are +/- 1 from the actual firmware. + return StepOverSamples(state, input, rate_multiplier, [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { + // This is a saturated subtraction. (Verified by black-box fuzzing.) + s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767); + s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767); + + return std::array<s16, 2> { + static_cast<s16>(x0[0] + fraction * delta0 / scale_factor), + static_cast<s16>(x0[1] + fraction * delta1 / scale_factor) + }; + }); +} + +} // namespace AudioInterp diff --git a/src/audio_core/interpolate.h b/src/audio_core/interpolate.h new file mode 100644 index 000000000..a4c0a453d --- /dev/null +++ b/src/audio_core/interpolate.h @@ -0,0 +1,41 @@ +// 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_types.h" + +namespace AudioInterp { + +/// A variable length buffer of signed PCM16 stereo samples. +using StereoBuffer16 = std::vector<std::array<s16, 2>>; + +struct State { + // Two historical samples. + std::array<s16, 2> xn1 = {}; ///< x[n-1] + std::array<s16, 2> xn2 = {}; ///< x[n-2] +}; + +/** + * No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay. + * @param input Input buffer. + * @param rate_multiplier Stretch factor. Must be a positive non-zero value. + * rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 performs upsampling. + * @return The resampled audio buffer. + */ +StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier); + +/** + * Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay. + * @param input Input buffer. + * @param rate_multiplier Stretch factor. Must be a positive non-zero value. + * rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 performs upsampling. + * @return The resampled audio buffer. + */ +StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier); + +} // namespace AudioInterp diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h new file mode 100644 index 000000000..faf0ee4e1 --- /dev/null +++ b/src/audio_core/null_sink.h @@ -0,0 +1,29 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstddef> + +#include "audio_core/audio_core.h" +#include "audio_core/sink.h" + +namespace AudioCore { + +class NullSink final : public Sink { +public: + ~NullSink() override = default; + + unsigned int GetNativeSampleRate() const override { + return native_sample_rate; + } + + void EnqueueSamples(const std::vector<s16>&) override {} + + size_t SamplesInQueue() const override { + return 0; + } +}; + +} // namespace AudioCore diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp new file mode 100644 index 000000000..d2cc74103 --- /dev/null +++ b/src/audio_core/sink_details.cpp @@ -0,0 +1,18 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <vector> + +#include "audio_core/null_sink.h" +#include "audio_core/sink_details.h" + +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 = { + { "null", []() { return std::make_unique<NullSink>(); } }, +}; + +} // namespace AudioCore diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h new file mode 100644 index 000000000..4b30cf835 --- /dev/null +++ b/src/audio_core/sink_details.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 <functional> +#include <memory> +#include <vector> + +namespace AudioCore { + +class Sink; + +struct SinkDetails { + SinkDetails(const char* id_, std::function<std::unique_ptr<Sink>()> factory_) + : id(id_), factory(factory_) {} + + /// Name for this sink. + const char* id; + /// A method to call to construct an instance of this type of sink. + std::function<std::unique_ptr<Sink>()> factory; +}; + +extern const std::vector<SinkDetails> g_sink_details; + +} // namespace AudioCore diff --git a/src/citra/CMakeLists.txt b/src/citra/CMakeLists.txt index fa615deb9..43fa06b4e 100644 --- a/src/citra/CMakeLists.txt +++ b/src/citra/CMakeLists.txt @@ -21,7 +21,7 @@ target_link_libraries(citra ${SDL2_LIBRARY} ${OPENGL_gl_LIBRARY} inih glad) if (MSVC) target_link_libraries(citra getopt) endif() -target_link_libraries(citra ${PLATFORM_LIBRARIES}) +target_link_libraries(citra ${PLATFORM_LIBRARIES} Threads::Threads) if(${CMAKE_SYSTEM_NAME} MATCHES "Linux|FreeBSD|OpenBSD|NetBSD") install(TARGETS citra RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index 40e40f192..b4501eb2e 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -5,6 +5,7 @@ #include <string> #include <thread> #include <iostream> +#include <memory> // This needs to be included before getopt.h because the latter #defines symbols used by it #include "common/microprofile.h" @@ -19,7 +20,7 @@ #include "common/logging/log.h" #include "common/logging/backend.h" #include "common/logging/filter.h" -#include "common/make_unique.h" +#include "common/scm_rev.h" #include "common/scope_exit.h" #include "core/settings.h" @@ -34,26 +35,54 @@ #include "video_core/video_core.h" -static void PrintHelp() +static void PrintHelp(const char *argv0) { - std::cout << "Usage: citra <filename>" << std::endl; + std::cout << "Usage: " << argv0 << " [options] <filename>\n" + "-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n" + "-h, --help Display this help and exit\n" + "-v, --version Output version information and exit\n"; +} + +static void PrintVersion() +{ + std::cout << "Citra " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl; } /// Application entry point int main(int argc, char **argv) { + Config config; int option_index = 0; + bool use_gdbstub = Settings::values.use_gdbstub; + u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port); + char *endarg; std::string boot_filename; + static struct option long_options[] = { + { "gdbport", required_argument, 0, 'g' }, { "help", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'v' }, { 0, 0, 0, 0 } }; while (optind < argc) { - char arg = getopt_long(argc, argv, ":h", long_options, &option_index); + char arg = getopt_long(argc, argv, "g:hv", long_options, &option_index); if (arg != -1) { switch (arg) { + case 'g': + errno = 0; + gdb_port = strtoul(optarg, &endarg, 0); + use_gdbstub = true; + if (endarg == optarg) errno = EINVAL; + if (errno != 0) { + perror("--gdbport"); + exit(1); + } + break; case 'h': - PrintHelp(); + PrintHelp(argv[0]); + return 0; + case 'v': + PrintVersion(); return 0; } } else { @@ -73,16 +102,14 @@ int main(int argc, char **argv) { return -1; } - Config config; log_filter.ParseFilterString(Settings::values.log_filter); - GDBStub::ToggleServer(Settings::values.use_gdbstub); - GDBStub::SetServerPort(static_cast<u32>(Settings::values.gdbstub_port)); - - std::unique_ptr<EmuWindow_SDL2> emu_window = Common::make_unique<EmuWindow_SDL2>(); + // Apply the command line arguments + Settings::values.gdbstub_port = gdb_port; + Settings::values.use_gdbstub = use_gdbstub; + Settings::Apply(); - VideoCore::g_hw_renderer_enabled = Settings::values.use_hw_renderer; - VideoCore::g_shader_jit_enabled = Settings::values.use_shader_jit; + std::unique_ptr<EmuWindow_SDL2> emu_window = std::make_unique<EmuWindow_SDL2>(); System::Init(emu_window.get()); SCOPE_EXIT({ System::Shutdown(); }); diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 9034b188e..0d17c80bf 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <memory> + #include <inih/cpp/INIReader.h> #include <SDL.h> @@ -10,7 +12,6 @@ #include "common/file_util.h" #include "common/logging/log.h" -#include "common/make_unique.h" #include "core/settings.h" @@ -19,7 +20,7 @@ Config::Config() { // TODO: Don't hardcode the path; let the frontend decide where to put the config files. sdl2_config_loc = FileUtil::GetUserPath(D_CONFIG_IDX) + "sdl2-config.ini"; - sdl2_config = Common::make_unique<INIReader>(sdl2_config_loc); + sdl2_config = std::make_unique<INIReader>(sdl2_config_loc); Reload(); } @@ -31,7 +32,7 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) { LOG_WARNING(Config, "Failed to load %s. Creating file from defaults...", location); FileUtil::CreateFullPath(location); FileUtil::WriteStringToFile(true, default_contents, location); - sdl2_config = Common::make_unique<INIReader>(location); // Reopen file + sdl2_config = std::make_unique<INIReader>(location); // Reopen file return LoadINI(default_contents, false); } @@ -64,11 +65,15 @@ void Config::ReadValues() { // Renderer Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", false); Settings::values.use_shader_jit = sdl2_config->GetBoolean("Renderer", "use_shader_jit", true); + Settings::values.use_scaled_resolution = sdl2_config->GetBoolean("Renderer", "use_scaled_resolution", false); Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 1.0); Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 1.0); Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 1.0); + // Audio + Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); + // Data Storage Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index c9b490a00..0e6171736 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -46,12 +46,21 @@ use_hw_renderer = # 0 : Interpreter (slow), 1 (default): JIT (fast) use_shader_jit = +# Whether to use native 3DS screen resolution or to scale rendering resolution to the displayed screen size. +# 0 (default): Native, 1: Scaled +use_scaled_resolution = + # The clear color for the renderer. What shows up on the sides of the bottom screen. # Must be in range of 0.0-1.0. Defaults to 1.0 for all. bg_red = bg_blue = bg_green = +[Audio] +# Which audio output engine to use. +# auto (default): Auto-select, null: No audio output +output_engine = + [Data Storage] # Whether to create a virtual SD card. # 1 (default): Yes, 0: No diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 9b3eb2cd6..3f0099200 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -17,12 +17,16 @@ set(SRCS debugger/profiler.cpp debugger/ramview.cpp debugger/registers.cpp - game_list.cpp util/spinbox.cpp util/util.cpp bootmanager.cpp + configure_debug.cpp + configure_dialog.cpp + configure_general.cpp + game_list.cpp hotkeys.cpp main.cpp + ui_settings.cpp citra-qt.rc Info.plist ) @@ -44,12 +48,17 @@ set(HEADERS debugger/profiler.h debugger/ramview.h debugger/registers.h - game_list.h util/spinbox.h util/util.h bootmanager.h + configure_debug.h + configure_dialog.h + configure_general.h + game_list.h + game_list_p.h hotkeys.h main.h + ui_settings.h version.h ) @@ -59,6 +68,9 @@ set(UIS debugger/disassembler.ui debugger/profiler.ui debugger/registers.ui + configure.ui + configure_debug.ui + configure_general.ui hotkeys.ui main.ui ) @@ -81,7 +93,7 @@ else() endif() target_link_libraries(citra-qt core video_core audio_core common qhexedit) target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS}) -target_link_libraries(citra-qt ${PLATFORM_LIBRARIES}) +target_link_libraries(citra-qt ${PLATFORM_LIBRARIES} Threads::Threads) if(${CMAKE_SYSTEM_NAME} MATCHES "Linux|FreeBSD|OpenBSD|NetBSD") install(TARGETS citra-qt RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 8e60b9cad..01b81c11c 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -71,7 +71,9 @@ void EmuThread::run() { // Shutdown the core emulation System::Shutdown(); +#if MICROPROFILE_ENABLED MicroProfileOnThreadExit(); +#endif render_window->moveContext(); } diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index 8e247ff5c..b5bb75537 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -7,12 +7,12 @@ #include <QStringList> #include "citra_qt/config.h" +#include "citra_qt/ui_settings.h" #include "common/file_util.h" #include "core/settings.h" Config::Config() { - // TODO: Don't hardcode the path; let the frontend decide where to put the config files. qt_config_loc = FileUtil::GetUserPath(D_CONFIG_IDX) + "qt-config.ini"; FileUtil::CreateFullPath(qt_config_loc); @@ -45,12 +45,17 @@ void Config::ReadValues() { qt_config->beginGroup("Renderer"); Settings::values.use_hw_renderer = qt_config->value("use_hw_renderer", false).toBool(); Settings::values.use_shader_jit = qt_config->value("use_shader_jit", true).toBool(); + Settings::values.use_scaled_resolution = qt_config->value("use_scaled_resolution", false).toBool(); Settings::values.bg_red = qt_config->value("bg_red", 1.0).toFloat(); Settings::values.bg_green = qt_config->value("bg_green", 1.0).toFloat(); Settings::values.bg_blue = qt_config->value("bg_blue", 1.0).toFloat(); qt_config->endGroup(); + qt_config->beginGroup("Audio"); + Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString(); + qt_config->endGroup(); + qt_config->beginGroup("Data Storage"); Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool(); qt_config->endGroup(); @@ -67,6 +72,51 @@ void Config::ReadValues() { Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool(); Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt(); qt_config->endGroup(); + + qt_config->beginGroup("UI"); + + qt_config->beginGroup("UILayout"); + UISettings::values.geometry = qt_config->value("geometry").toByteArray(); + UISettings::values.state = qt_config->value("state").toByteArray(); + UISettings::values.renderwindow_geometry = qt_config->value("geometryRenderWindow").toByteArray(); + UISettings::values.gamelist_header_state = qt_config->value("gameListHeaderState").toByteArray(); + UISettings::values.microprofile_geometry = qt_config->value("microProfileDialogGeometry").toByteArray(); + UISettings::values.microprofile_visible = qt_config->value("microProfileDialogVisible", false).toBool(); + qt_config->endGroup(); + + qt_config->beginGroup("Paths"); + UISettings::values.roms_path = qt_config->value("romsPath").toString(); + UISettings::values.symbols_path = qt_config->value("symbolsPath").toString(); + UISettings::values.gamedir = qt_config->value("gameListRootDir", ".").toString(); + UISettings::values.gamedir_deepscan = qt_config->value("gameListDeepScan", false).toBool(); + UISettings::values.recent_files = qt_config->value("recentFiles").toStringList(); + qt_config->endGroup(); + + qt_config->beginGroup("Shortcuts"); + QStringList groups = qt_config->childGroups(); + for (auto group : groups) { + qt_config->beginGroup(group); + + QStringList hotkeys = qt_config->childGroups(); + for (auto hotkey : hotkeys) { + qt_config->beginGroup(hotkey); + UISettings::values.shortcuts.emplace_back( + UISettings::Shortcut(group + "/" + hotkey, + UISettings::ContextualShortcut(qt_config->value("KeySeq").toString(), + qt_config->value("Context").toInt()))); + qt_config->endGroup(); + } + + qt_config->endGroup(); + } + qt_config->endGroup(); + + UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); + UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool(); + UISettings::values.confirm_before_closing = qt_config->value("confirmClose",true).toBool(); + UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); + + qt_config->endGroup(); } void Config::SaveValues() { @@ -84,6 +134,7 @@ void Config::SaveValues() { qt_config->beginGroup("Renderer"); qt_config->setValue("use_hw_renderer", Settings::values.use_hw_renderer); qt_config->setValue("use_shader_jit", Settings::values.use_shader_jit); + qt_config->setValue("use_scaled_resolution", Settings::values.use_scaled_resolution); // Cast to double because Qt's written float values are not human-readable qt_config->setValue("bg_red", (double)Settings::values.bg_red); @@ -91,6 +142,10 @@ void Config::SaveValues() { qt_config->setValue("bg_blue", (double)Settings::values.bg_blue); qt_config->endGroup(); + qt_config->beginGroup("Audio"); + qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id)); + qt_config->endGroup(); + qt_config->beginGroup("Data Storage"); qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd); qt_config->endGroup(); @@ -107,10 +162,44 @@ void Config::SaveValues() { qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub); qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port); qt_config->endGroup(); + + qt_config->beginGroup("UI"); + + qt_config->beginGroup("UILayout"); + qt_config->setValue("geometry", UISettings::values.geometry); + qt_config->setValue("state", UISettings::values.state); + qt_config->setValue("geometryRenderWindow", UISettings::values.renderwindow_geometry); + qt_config->setValue("gameListHeaderState", UISettings::values.gamelist_header_state); + qt_config->setValue("microProfileDialogGeometry", UISettings::values.microprofile_geometry); + qt_config->setValue("microProfileDialogVisible", UISettings::values.microprofile_visible); + qt_config->endGroup(); + + qt_config->beginGroup("Paths"); + qt_config->setValue("romsPath", UISettings::values.roms_path); + qt_config->setValue("symbolsPath", UISettings::values.symbols_path); + qt_config->setValue("gameListRootDir", UISettings::values.gamedir); + qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan); + qt_config->setValue("recentFiles", UISettings::values.recent_files); + qt_config->endGroup(); + + qt_config->beginGroup("Shortcuts"); + for (auto shortcut : UISettings::values.shortcuts ) { + qt_config->setValue(shortcut.first + "/KeySeq", shortcut.second.first); + qt_config->setValue(shortcut.first + "/Context", shortcut.second.second); + } + qt_config->endGroup(); + + qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode); + qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); + qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); + qt_config->setValue("firstStart", UISettings::values.first_start); + + qt_config->endGroup(); } void Config::Reload() { ReadValues(); + Settings::Apply(); } void Config::Save() { diff --git a/src/citra_qt/configure.ui b/src/citra_qt/configure.ui new file mode 100644 index 000000000..6ae056ff9 --- /dev/null +++ b/src/citra_qt/configure.ui @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureDialog</class> + <widget class="QDialog" name="ConfigureDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>441</width> + <height>501</height> + </rect> + </property> + <property name="windowTitle"> + <string>Citra Configuration</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="ConfigureGeneral" name="generalTab"> + <attribute name="title"> + <string>General</string> + </attribute> + </widget> + <widget class="QWidget" name="inputTab"> + <attribute name="title"> + <string>Input</string> + </attribute> + </widget> + <widget class="ConfigureDebug" name="debugTab"> + <attribute name="title"> + <string>Debug</string> + </attribute> + </widget> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ConfigureGeneral</class> + <extends>QWidget</extends> + <header>configure_general.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ConfigureDebug</class> + <extends>QWidget</extends> + <header>configure_debug.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>220</x> + <y>380</y> + </hint> + <hint type="destinationlabel"> + <x>220</x> + <y>200</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>220</x> + <y>380</y> + </hint> + <hint type="destinationlabel"> + <x>220</x> + <y>200</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/citra_qt/configure_debug.cpp b/src/citra_qt/configure_debug.cpp new file mode 100644 index 000000000..dc3d7b906 --- /dev/null +++ b/src/citra_qt/configure_debug.cpp @@ -0,0 +1,31 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "citra_qt/configure_debug.h" +#include "ui_configure_debug.h" + +#include "core/settings.h" + +ConfigureDebug::ConfigureDebug(QWidget *parent) : + QWidget(parent), + ui(new Ui::ConfigureDebug) +{ + ui->setupUi(this); + this->setConfiguration(); +} + +ConfigureDebug::~ConfigureDebug() { +} + +void ConfigureDebug::setConfiguration() { + ui->toogle_gdbstub->setChecked(Settings::values.use_gdbstub); + ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub); + ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port); +} + +void ConfigureDebug::applyConfiguration() { + Settings::values.use_gdbstub = ui->toogle_gdbstub->isChecked(); + Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); + Settings::Apply(); +} diff --git a/src/citra_qt/configure_debug.h b/src/citra_qt/configure_debug.h new file mode 100644 index 000000000..ab58ebbdc --- /dev/null +++ b/src/citra_qt/configure_debug.h @@ -0,0 +1,29 @@ +// 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 ConfigureDebug; +} + +class ConfigureDebug : public QWidget +{ + Q_OBJECT + +public: + explicit ConfigureDebug(QWidget *parent = nullptr); + ~ConfigureDebug(); + + void applyConfiguration(); + +private: + void setConfiguration(); + +private: + std::unique_ptr<Ui::ConfigureDebug> ui; +}; diff --git a/src/citra_qt/configure_debug.ui b/src/citra_qt/configure_debug.ui new file mode 100644 index 000000000..3ba7f44da --- /dev/null +++ b/src/citra_qt/configure_debug.ui @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureDebug</class> + <widget class="QWidget" name="ConfigureDebug"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>GDB</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QCheckBox" name="toogle_gdbstub"> + <property name="text"> + <string>Enable GDB Stub</string> + </property> + </widget> + </item> + <item> + <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> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="gdbport_spinbox"> + <property name="maximum"> + <number>65536</number> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <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> + <connection> + <sender>toogle_gdbstub</sender> + <signal>toggled(bool)</signal> + <receiver>gdbport_spinbox</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>84</x> + <y>157</y> + </hint> + <hint type="destinationlabel"> + <x>342</x> + <y>158</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/citra_qt/configure_dialog.cpp b/src/citra_qt/configure_dialog.cpp new file mode 100644 index 000000000..87c26c715 --- /dev/null +++ b/src/citra_qt/configure_dialog.cpp @@ -0,0 +1,29 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "citra_qt/config.h" +#include "citra_qt/configure_dialog.h" +#include "ui_configure.h" + + +#include "core/settings.h" + +ConfigureDialog::ConfigureDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::ConfigureDialog) +{ + ui->setupUi(this); + this->setConfiguration(); +} + +ConfigureDialog::~ConfigureDialog() { +} + +void ConfigureDialog::setConfiguration() { +} + +void ConfigureDialog::applyConfiguration() { + ui->generalTab->applyConfiguration(); + ui->debugTab->applyConfiguration(); +} diff --git a/src/citra_qt/configure_dialog.h b/src/citra_qt/configure_dialog.h new file mode 100644 index 000000000..89020eeb4 --- /dev/null +++ b/src/citra_qt/configure_dialog.h @@ -0,0 +1,29 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDialog> + +namespace Ui { +class ConfigureDialog; +} + +class ConfigureDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ConfigureDialog(QWidget *parent = nullptr); + ~ConfigureDialog(); + + void applyConfiguration(); + +private: + void setConfiguration(); + +private: + std::unique_ptr<Ui::ConfigureDialog> ui; +}; diff --git a/src/citra_qt/configure_general.cpp b/src/citra_qt/configure_general.cpp new file mode 100644 index 000000000..62648e665 --- /dev/null +++ b/src/citra_qt/configure_general.cpp @@ -0,0 +1,39 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "citra_qt/configure_general.h" +#include "citra_qt/ui_settings.h" +#include "ui_configure_general.h" + +#include "core/settings.h" + +ConfigureGeneral::ConfigureGeneral(QWidget *parent) : + QWidget(parent), + ui(new Ui::ConfigureGeneral) +{ + ui->setupUi(this); + this->setConfiguration(); +} + +ConfigureGeneral::~ConfigureGeneral() { +} + +void ConfigureGeneral::setConfiguration() { + ui->toogle_deepscan->setChecked(UISettings::values.gamedir_deepscan); + ui->toogle_check_exit->setChecked(UISettings::values.confirm_before_closing); + ui->region_combobox->setCurrentIndex(Settings::values.region_value); + ui->toogle_hw_renderer->setChecked(Settings::values.use_hw_renderer); + ui->toogle_shader_jit->setChecked(Settings::values.use_shader_jit); + ui->toogle_scaled_resolution->setChecked(Settings::values.use_scaled_resolution); +} + +void ConfigureGeneral::applyConfiguration() { + UISettings::values.gamedir_deepscan = ui->toogle_deepscan->isChecked(); + UISettings::values.confirm_before_closing = ui->toogle_check_exit->isChecked(); + Settings::values.region_value = ui->region_combobox->currentIndex(); + Settings::values.use_hw_renderer = ui->toogle_hw_renderer->isChecked(); + Settings::values.use_shader_jit = ui->toogle_shader_jit->isChecked(); + Settings::values.use_scaled_resolution = ui->toogle_scaled_resolution->isChecked(); + Settings::Apply(); +} diff --git a/src/citra_qt/configure_general.h b/src/citra_qt/configure_general.h new file mode 100644 index 000000000..a6c68e62d --- /dev/null +++ b/src/citra_qt/configure_general.h @@ -0,0 +1,29 @@ +// 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 ConfigureGeneral; +} + +class ConfigureGeneral : public QWidget +{ + Q_OBJECT + +public: + explicit ConfigureGeneral(QWidget *parent = nullptr); + ~ConfigureGeneral(); + + void applyConfiguration(); + +private: + void setConfiguration(); + +private: + std::unique_ptr<Ui::ConfigureGeneral> ui; +}; diff --git a/src/citra_qt/configure_general.ui b/src/citra_qt/configure_general.ui new file mode 100644 index 000000000..5eb309793 --- /dev/null +++ b/src/citra_qt/configure_general.ui @@ -0,0 +1,173 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureGeneral</class> + <widget class="QWidget" name="ConfigureGeneral"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>300</width> + <height>377</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>General</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QCheckBox" name="toogle_deepscan"> + <property name="text"> + <string>Recursive scan for game folder</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="toogle_check_exit"> + <property name="text"> + <string>Confirm exit while emulation is running</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_4"> + <property name="title"> + <string>Emulation</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Region:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="region_combobox"> + <item> + <property name="text"> + <string notr="true">JPN</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">USA</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">EUR</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">AUS</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">CHN</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">KOR</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">TWN</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Performance</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QCheckBox" name="toogle_hw_renderer"> + <property name="text"> + <string>Enable hardware renderer</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="toogle_shader_jit"> + <property name="text"> + <string>Enable shader JIT</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="toogle_scaled_resolution"> + <property name="text"> + <string>Enable scaled resolution</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Hotkeys</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="GHotkeysDialog" name="widget" native="true"/> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>GHotkeysDialog</class> + <extends>QWidget</extends> + <header>hotkeys.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/src/citra_qt/debugger/graphics_breakpoints.cpp b/src/citra_qt/debugger/graphics_breakpoints.cpp index 819ec7707..fe66918a8 100644 --- a/src/citra_qt/debugger/graphics_breakpoints.cpp +++ b/src/citra_qt/debugger/graphics_breakpoints.cpp @@ -44,7 +44,7 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const { Pica::DebugContext::Event::PicaCommandProcessed, tr("Pica command processed") }, { Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incoming primitive batch") }, { Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch") }, - { Pica::DebugContext::Event::VertexLoaded, tr("Vertex loaded") }, + { Pica::DebugContext::Event::VertexShaderInvocation, tr("Vertex shader invocation") }, { Pica::DebugContext::Event::IncomingDisplayTransfer, tr("Incoming display transfer") }, { Pica::DebugContext::Event::GSPCommandProcessed, tr("GSP command processed") }, { Pica::DebugContext::Event::BufferSwapped, tr("Buffers swapped") } @@ -75,7 +75,7 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const case Role_IsEnabled: { auto context = context_weak.lock(); - return context && context->breakpoints[event].enabled; + return context && context->breakpoints[(int)event].enabled; } default: @@ -110,7 +110,7 @@ bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, i if (!context) return false; - context->breakpoints[event].enabled = value == Qt::Checked; + context->breakpoints[(int)event].enabled = value == Qt::Checked; QModelIndex changed_index = createIndex(index.row(), 0); emit dataChanged(changed_index, changed_index); return true; diff --git a/src/citra_qt/debugger/graphics_framebuffer.cpp b/src/citra_qt/debugger/graphics_framebuffer.cpp index c30e75933..68cff78b2 100644 --- a/src/citra_qt/debugger/graphics_framebuffer.cpp +++ b/src/citra_qt/debugger/graphics_framebuffer.cpp @@ -346,5 +346,11 @@ u32 GraphicsFramebufferWidget::BytesPerPixel(GraphicsFramebufferWidget::Format f case Format::RGBA4: case Format::D16: return 2; + default: + UNREACHABLE_MSG("GraphicsFramebufferWidget::BytesPerPixel: this " + "should not be reached as this function should " + "be given a format which is in " + "GraphicsFramebufferWidget::Format. Instead got %i", + static_cast<int>(format)); } } diff --git a/src/citra_qt/debugger/graphics_tracing.cpp b/src/citra_qt/debugger/graphics_tracing.cpp index e06498744..1402f8e79 100644 --- a/src/citra_qt/debugger/graphics_tracing.cpp +++ b/src/citra_qt/debugger/graphics_tracing.cpp @@ -2,6 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> +#include <array> +#include <iterator> #include <memory> #include <boost/range/algorithm/copy.hpp> @@ -18,6 +21,7 @@ #include "core/hw/gpu.h" #include "core/hw/lcd.h" +#include "core/tracer/recorder.h" #include "nihstro/float24.h" diff --git a/src/citra_qt/debugger/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics_vertex_shader.cpp index d648d4640..6e8d7ef42 100644 --- a/src/citra_qt/debugger/graphics_vertex_shader.cpp +++ b/src/citra_qt/debugger/graphics_vertex_shader.cpp @@ -365,7 +365,7 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De input_data[i]->setValidator(new QDoubleValidator(input_data[i])); } - breakpoint_warning = new QLabel(tr("(data only available at VertexLoaded breakpoints)")); + breakpoint_warning = new QLabel(tr("(data only available at vertex shader invocation breakpoints)")); // TODO: Add some button for jumping to the shader entry point @@ -454,7 +454,7 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De void GraphicsVertexShaderWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) { auto input = static_cast<Pica::Shader::InputVertex*>(data); - if (event == Pica::DebugContext::Event::VertexLoaded) { + if (event == Pica::DebugContext::Event::VertexShaderInvocation) { Reload(true, data); } else { // No vertex data is retrievable => invalidate currently stored vertex data diff --git a/src/citra_qt/debugger/profiler.cpp b/src/citra_qt/debugger/profiler.cpp index 4f6ba0e1f..7bb010f77 100644 --- a/src/citra_qt/debugger/profiler.cpp +++ b/src/citra_qt/debugger/profiler.cpp @@ -9,13 +9,16 @@ #include "citra_qt/debugger/profiler.h" #include "citra_qt/util/util.h" +#include "common/common_types.h" #include "common/microprofile.h" #include "common/profiler_reporting.h" // Include the implementation of the UI in this file. This isn't in microprofile.cpp because the // non-Qt frontends don't need it (and don't implement the UI drawing hooks either). +#if MICROPROFILE_ENABLED #define MICROPROFILEUI_IMPL 1 #include "common/microprofileui.h" +#endif using namespace Common::Profiling; @@ -34,21 +37,9 @@ static QVariant GetDataForColumn(int col, const AggregatedDuration& duration) } } -static const TimingCategoryInfo* GetCategoryInfo(int id) -{ - const auto& categories = GetProfilingManager().GetTimingCategoriesInfo(); - if ((size_t)id >= categories.size()) { - return nullptr; - } else { - return &categories[id]; - } -} - ProfilerModel::ProfilerModel(QObject* parent) : QAbstractItemModel(parent) { updateProfilingInfo(); - const auto& categories = GetProfilingManager().GetTimingCategoriesInfo(); - results.time_per_category.resize(categories.size()); } QVariant ProfilerModel::headerData(int section, Qt::Orientation orientation, int role) const @@ -85,7 +76,7 @@ int ProfilerModel::rowCount(const QModelIndex& parent) const if (parent.isValid()) { return 0; } else { - return static_cast<int>(results.time_per_category.size() + 2); + return 2; } } @@ -104,17 +95,6 @@ QVariant ProfilerModel::data(const QModelIndex& index, int role) const } else { return GetDataForColumn(index.column(), results.interframe_time); } - } else { - if (index.column() == 0) { - const TimingCategoryInfo* info = GetCategoryInfo(index.row() - 2); - return info != nullptr ? QString(info->name) : QVariant(); - } else { - if (index.row() - 2 < (int)results.time_per_category.size()) { - return GetDataForColumn(index.column(), results.time_per_category[index.row() - 2]); - } else { - return QVariant(); - } - } } } @@ -148,6 +128,8 @@ void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable) } } +#if MICROPROFILE_ENABLED + class MicroProfileWidget : public QWidget { public: MicroProfileWidget(QWidget* parent = nullptr); @@ -171,6 +153,8 @@ private: QTimer update_timer; }; +#endif + MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) { @@ -180,6 +164,8 @@ MicroProfileDialog::MicroProfileDialog(QWidget* parent) // Remove the "?" button from the titlebar and enable the maximize button setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint | Qt::WindowMaximizeButtonHint); +#if MICROPROFILE_ENABLED + MicroProfileWidget* widget = new MicroProfileWidget(this); QLayout* layout = new QVBoxLayout(this); @@ -191,6 +177,7 @@ MicroProfileDialog::MicroProfileDialog(QWidget* parent) setFocusProxy(widget); widget->setFocusPolicy(Qt::StrongFocus); widget->setFocus(); +#endif } QAction* MicroProfileDialog::toggleViewAction() { @@ -218,6 +205,9 @@ void MicroProfileDialog::hideEvent(QHideEvent* ev) { QWidget::hideEvent(ev); } + +#if MICROPROFILE_ENABLED + /// There's no way to pass a user pointer to MicroProfile, so this variable is used to make the /// QPainter available inside the drawing callbacks. static QPainter* mp_painter = nullptr; @@ -337,3 +327,4 @@ void MicroProfileDrawLine2D(u32 vertices_length, float* vertices, u32 hex_color) mp_painter->drawPolyline(point_buf.data(), vertices_length); point_buf.clear(); } +#endif diff --git a/src/citra_qt/debugger/profiler.h b/src/citra_qt/debugger/profiler.h index 036054740..3b38ed8ec 100644 --- a/src/citra_qt/debugger/profiler.h +++ b/src/citra_qt/debugger/profiler.h @@ -7,8 +7,10 @@ #include <QAbstractItemModel> #include <QDockWidget> #include <QTimer> + #include "ui_profiler.h" +#include "common/microprofile.h" #include "common/profiler_reporting.h" class ProfilerModel : public QAbstractItemModel @@ -49,6 +51,7 @@ private: QTimer update_timer; }; + class MicroProfileDialog : public QWidget { Q_OBJECT diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index 1f8d69a03..d4ac9c96e 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -8,6 +8,7 @@ #include "game_list.h" #include "game_list_p.h" +#include "ui_settings.h" #include "core/loader/loader.h" @@ -33,8 +34,8 @@ GameList::GameList(QWidget* parent) tree_view->setUniformRowHeights(true); item_model->insertColumns(0, COLUMN_COUNT); - item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type"); item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name"); + item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type"); item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size"); connect(tree_view, SIGNAL(activated(const QModelIndex&)), this, SLOT(ValidateEntry(const QModelIndex&))); @@ -66,7 +67,7 @@ void GameList::ValidateEntry(const QModelIndex& item) if (file_path.isEmpty()) return; - std::string std_file_path = file_path.toStdString(); + std::string std_file_path(file_path.toStdString()); if (!FileUtil::Exists(std_file_path) || FileUtil::IsDirectory(std_file_path)) return; emit GameChosen(file_path); @@ -100,19 +101,19 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) current_worker = std::move(worker); } -void GameList::SaveInterfaceLayout(QSettings& settings) +void GameList::SaveInterfaceLayout() { - settings.beginGroup("UILayout"); - settings.setValue("gameListHeaderState", tree_view->header()->saveState()); - settings.endGroup(); + UISettings::values.gamelist_header_state = tree_view->header()->saveState(); } -void GameList::LoadInterfaceLayout(QSettings& settings) +void GameList::LoadInterfaceLayout() { auto header = tree_view->header(); - settings.beginGroup("UILayout"); - header->restoreState(settings.value("gameListHeaderState").toByteArray()); - settings.endGroup(); + if (!header->restoreState(UISettings::values.gamelist_header_state)) { + // We are using the name column to display icons and titles + // so make it as large as possible as default. + header->resizeSection(COLUMN_NAME, header->width()); + } item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); } @@ -146,9 +147,15 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool d LOG_WARNING(Frontend, "Filetype and extension of file %s do not match.", physical_name.c_str()); } + std::vector<u8> smdh; + std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(FileUtil::IOFile(physical_name, "rb"), filetype, filename_filename, physical_name); + + if (loader) + loader->ReadIcon(smdh); + emit EntryReady({ + new GameListItemPath(QString::fromStdString(physical_name), smdh), new GameListItem(QString::fromStdString(Loader::GetFileTypeString(filetype))), - new GameListItemPath(QString::fromStdString(physical_name)), new GameListItemSize(FileUtil::GetSize(physical_name)), }); } diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index 0950d9622..198674f04 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -20,8 +20,8 @@ class GameList : public QWidget { public: enum { - COLUMN_FILE_TYPE, COLUMN_NAME, + COLUMN_FILE_TYPE, COLUMN_SIZE, COLUMN_COUNT, // Number of columns }; @@ -31,8 +31,8 @@ public: void PopulateAsync(const QString& dir_path, bool deep_scan); - void SaveInterfaceLayout(QSettings& settings); - void LoadInterfaceLayout(QSettings& settings); + void SaveInterfaceLayout(); + void LoadInterfaceLayout(); public slots: void AddEntry(QList<QStandardItem*> entry_items); diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index 820012bce..284f5da81 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -6,13 +6,85 @@ #include <atomic> +#include <QImage> #include <QRunnable> #include <QStandardItem> #include <QString> #include "citra_qt/util/util.h" #include "common/string_util.h" +#include "common/color.h" +#include "core/loader/loader.h" + +#include "video_core/utils.h" + +/** + * Tests if data is a valid SMDH by its length and magic number. + * @param smdh_data data buffer to test + * @return bool test result + */ +static bool IsValidSMDH(const std::vector<u8>& smdh_data) { + if (smdh_data.size() < sizeof(Loader::SMDH)) + return false; + + u32 magic; + memcpy(&magic, smdh_data.data(), 4); + + return Loader::MakeMagic('S', 'M', 'D', 'H') == magic; +} + +/** + * Gets game icon from SMDH + * @param sdmh SMDH data + * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) + * @return QPixmap game icon + */ +static QPixmap GetIconFromSMDH(const Loader::SMDH& smdh, bool large) { + u32 size; + const u8* icon_data; + + if (large) { + size = 48; + icon_data = smdh.large_icon.data(); + } else { + size = 24; + icon_data = smdh.small_icon.data(); + } + + QImage icon(size, size, QImage::Format::Format_RGB888); + for (u32 x = 0; x < size; ++x) { + for (u32 y = 0; y < size; ++y) { + u32 coarse_y = y & ~7; + auto v = Color::DecodeRGB565( + icon_data + VideoCore::GetMortonOffset(x, y, 2) + coarse_y * size * 2); + icon.setPixel(x, y, qRgb(v.r(), v.g(), v.b())); + } + } + return QPixmap::fromImage(icon); +} + +/** + * Gets the default icon (for games without valid SMDH) + * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) + * @return QPixmap default icon + */ +static QPixmap GetDefaultIcon(bool large) { + int size = large ? 48 : 24; + QPixmap icon(size, size); + icon.fill(Qt::transparent); + return icon; +} + +/** + * Gets the short game title fromn SMDH + * @param sdmh SMDH data + * @param language title language + * @return QString short title + */ +static QString GetShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) { + return QString::fromUtf16(smdh.titles[static_cast<int>(language)].short_title.data()); +} class GameListItem : public QStandardItem { @@ -27,29 +99,43 @@ public: * A specialization of GameListItem for path values. * This class ensures that for every full path value it holds, a correct string representation * of just the filename (with no extension) will be displayed to the user. + * If this class recieves valid SMDH data, it will also display game icons and titles. */ class GameListItemPath : public GameListItem { public: static const int FullPathRole = Qt::UserRole + 1; + static const int TitleRole = Qt::UserRole + 2; GameListItemPath(): GameListItem() {} - GameListItemPath(const QString& game_path): GameListItem() + GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data): GameListItem() { setData(game_path, FullPathRole); + + if (!IsValidSMDH(smdh_data)) { + // SMDH is not valid, set a default icon + setData(GetDefaultIcon(true), Qt::DecorationRole); + return; + } + + Loader::SMDH smdh; + memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH)); + + // Get icon from SMDH + setData(GetIconFromSMDH(smdh, true), Qt::DecorationRole); + + // Get title form SMDH + setData(GetShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English), TitleRole); } - void setData(const QVariant& value, int role) override - { - // By specializing setData for FullPathRole, we can ensure that the two string - // representations of the data are always accurate and in the correct format. - if (role == FullPathRole) { + QVariant data(int role) const override { + if (role == Qt::DisplayRole) { std::string filename; - Common::SplitPath(value.toString().toStdString(), nullptr, &filename, nullptr); - GameListItem::setData(QString::fromStdString(filename), Qt::DisplayRole); - GameListItem::setData(value, FullPathRole); + Common::SplitPath(data(FullPathRole).toString().toStdString(), nullptr, &filename, nullptr); + QString title = data(TitleRole).toString(); + return QString::fromStdString(filename) + (title.isEmpty() ? "" : "\n " + title); } else { - GameListItem::setData(value, role); + return GameListItem::data(role); } } }; diff --git a/src/citra_qt/hotkeys.cpp b/src/citra_qt/hotkeys.cpp index ed6b12fc4..41f95c63d 100644 --- a/src/citra_qt/hotkeys.cpp +++ b/src/citra_qt/hotkeys.cpp @@ -4,11 +4,12 @@ #include <map> +#include <QtGlobal> #include <QKeySequence> -#include <QSettings> #include <QShortcut> #include "citra_qt/hotkeys.h" +#include "citra_qt/ui_settings.h" struct Hotkey { @@ -24,54 +25,39 @@ typedef std::map<QString, HotkeyMap> HotkeyGroupMap; HotkeyGroupMap hotkey_groups; -void SaveHotkeys(QSettings& settings) +void SaveHotkeys() { - settings.beginGroup("Shortcuts"); - + UISettings::values.shortcuts.clear(); for (auto group : hotkey_groups) { - settings.beginGroup(group.first); for (auto hotkey : group.second) { - settings.beginGroup(hotkey.first); - settings.setValue(QString("KeySeq"), hotkey.second.keyseq.toString()); - settings.setValue(QString("Context"), hotkey.second.context); - settings.endGroup(); + UISettings::values.shortcuts.emplace_back( + UISettings::Shortcut(group.first + "/" + hotkey.first, + UISettings::ContextualShortcut(hotkey.second.keyseq.toString(), + hotkey.second.context))); } - settings.endGroup(); } - settings.endGroup(); } -void LoadHotkeys(QSettings& settings) +void LoadHotkeys() { - settings.beginGroup("Shortcuts"); - // Make sure NOT to use a reference here because it would become invalid once we call beginGroup() - QStringList groups = settings.childGroups(); - for (auto group : groups) + for (auto shortcut : UISettings::values.shortcuts) { - settings.beginGroup(group); + QStringList cat = shortcut.first.split("/"); + Q_ASSERT(cat.size() >= 2); - QStringList hotkeys = settings.childGroups(); - for (auto hotkey : hotkeys) + // RegisterHotkey assigns default keybindings, so use old values as default parameters + Hotkey& hk = hotkey_groups[cat[0]][cat[1]]; + if (!shortcut.second.first.isEmpty()) { - settings.beginGroup(hotkey); - - // RegisterHotkey assigns default keybindings, so use old values as default parameters - Hotkey& hk = hotkey_groups[group][hotkey]; - hk.keyseq = QKeySequence::fromString(settings.value("KeySeq", hk.keyseq.toString()).toString()); - hk.context = (Qt::ShortcutContext)settings.value("Context", hk.context).toInt(); - if (hk.shortcut) - hk.shortcut->setKey(hk.keyseq); - - settings.endGroup(); + hk.keyseq = QKeySequence::fromString(shortcut.second.first); + hk.context = (Qt::ShortcutContext)shortcut.second.second; } - - settings.endGroup(); + if (hk.shortcut) + hk.shortcut->setKey(hk.keyseq); } - - settings.endGroup(); } void RegisterHotkey(const QString& group, const QString& action, const QKeySequence& default_keyseq, Qt::ShortcutContext default_context) @@ -94,7 +80,7 @@ QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widge } -GHotkeysDialog::GHotkeysDialog(QWidget* parent): QDialog(parent) +GHotkeysDialog::GHotkeysDialog(QWidget* parent): QWidget(parent) { ui.setupUi(this); diff --git a/src/citra_qt/hotkeys.h b/src/citra_qt/hotkeys.h index 2fe635882..38aa5f012 100644 --- a/src/citra_qt/hotkeys.h +++ b/src/citra_qt/hotkeys.h @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#pragma once + #include "ui_hotkeys.h" class QDialog; @@ -33,16 +35,16 @@ QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widge * * @note Each hotkey group will be stored a settings group; For each hotkey inside that group, a settings group will be created to store the key sequence and the hotkey context. */ -void SaveHotkeys(QSettings& settings); +void SaveHotkeys(); /** * Loads hotkeys from the settings file. * * @note Yet unregistered hotkeys which are present in the settings will automatically be registered. */ -void LoadHotkeys(QSettings& settings); +void LoadHotkeys(); -class GHotkeysDialog : public QDialog +class GHotkeysDialog : public QWidget { Q_OBJECT diff --git a/src/citra_qt/hotkeys.ui b/src/citra_qt/hotkeys.ui index 38a9a14d1..050fe064e 100644 --- a/src/citra_qt/hotkeys.ui +++ b/src/citra_qt/hotkeys.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>hotkeys</class> - <widget class="QDialog" name="hotkeys"> + <widget class="QWidget" name="hotkeys"> <property name="geometry"> <rect> <x>0</x> @@ -39,51 +39,8 @@ </column> </widget> </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset</set> - </property> - </widget> - </item> </layout> </widget> <resources/> - <connections> - <connection> - <sender>buttonBox</sender> - <signal>accepted()</signal> - <receiver>hotkeys</receiver> - <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>248</x> - <y>254</y> - </hint> - <hint type="destinationlabel"> - <x>157</x> - <y>274</y> - </hint> - </hints> - </connection> - <connection> - <sender>buttonBox</sender> - <signal>rejected()</signal> - <receiver>hotkeys</receiver> - <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>316</x> - <y>260</y> - </hint> - <hint type="destinationlabel"> - <x>286</x> - <y>274</y> - </hint> - </hints> - </connection> - </connections> + <connections/> </ui> diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 32cceaf7e..f1ab29755 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <clocale> +#include <memory> #include <thread> #include <QDesktopWidget> @@ -13,9 +14,11 @@ #include "citra_qt/bootmanager.h" #include "citra_qt/config.h" +#include "citra_qt/configure_dialog.h" #include "citra_qt/game_list.h" #include "citra_qt/hotkeys.h" #include "citra_qt/main.h" +#include "citra_qt/ui_settings.h" // Debugger #include "citra_qt/debugger/callstack.h" @@ -30,7 +33,6 @@ #include "citra_qt/debugger/ramview.h" #include "citra_qt/debugger/registers.h" -#include "common/make_unique.h" #include "common/microprofile.h" #include "common/platform.h" #include "common/scm_rev.h" @@ -50,12 +52,10 @@ #include "video_core/video_core.h" -GMainWindow::GMainWindow() : emu_thread(nullptr) +GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { Pica::g_debug_context = Pica::DebugContext::Construct(); - Config config; - ui.setupUi(this); statusBar()->hide(); @@ -69,8 +69,10 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) addDockWidget(Qt::BottomDockWidgetArea, profilerWidget); profilerWidget->hide(); +#if MICROPROFILE_ENABLED microProfileDialog = new MicroProfileDialog(this); microProfileDialog->hide(); +#endif disasmWidget = new DisassemblerWidget(this, emu_thread.get()); addDockWidget(Qt::BottomDockWidgetArea, disasmWidget); @@ -110,7 +112,9 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); debug_menu->addAction(profilerWidget->toggleViewAction()); +#if MICROPROFILE_ENABLED debug_menu->addAction(microProfileDialog->toggleViewAction()); +#endif debug_menu->addAction(disasmWidget->toggleViewAction()); debug_menu->addAction(registersWidget->toggleViewAction()); debug_menu->addAction(callstackWidget->toggleViewAction()); @@ -133,33 +137,20 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) setGeometry(x, y, w, h); // Restore UI state - QSettings settings; - - settings.beginGroup("UILayout"); - restoreGeometry(settings.value("geometry").toByteArray()); - restoreState(settings.value("state").toByteArray()); - render_window->restoreGeometry(settings.value("geometryRenderWindow").toByteArray()); - microProfileDialog->restoreGeometry(settings.value("microProfileDialogGeometry").toByteArray()); - microProfileDialog->setVisible(settings.value("microProfileDialogVisible").toBool()); - settings.endGroup(); - - game_list->LoadInterfaceLayout(settings); - - ui.action_Use_Gdbstub->setChecked(Settings::values.use_gdbstub); - SetGdbstubEnabled(ui.action_Use_Gdbstub->isChecked()); - - GDBStub::SetServerPort(static_cast<u32>(Settings::values.gdbstub_port)); - - ui.action_Use_Hardware_Renderer->setChecked(Settings::values.use_hw_renderer); - SetHardwareRendererEnabled(ui.action_Use_Hardware_Renderer->isChecked()); + restoreGeometry(UISettings::values.geometry); + restoreState(UISettings::values.state); + render_window->restoreGeometry(UISettings::values.renderwindow_geometry); +#if MICROPROFILE_ENABLED + microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry); + microProfileDialog->setVisible(UISettings::values.microprofile_visible); +#endif - ui.action_Use_Shader_JIT->setChecked(Settings::values.use_shader_jit); - SetShaderJITEnabled(ui.action_Use_Shader_JIT->isChecked()); + game_list->LoadInterfaceLayout(); - ui.action_Single_Window_Mode->setChecked(settings.value("singleWindowMode", true).toBool()); + ui.action_Single_Window_Mode->setChecked(UISettings::values.single_window_mode); ToggleWindowMode(); - ui.actionDisplay_widget_title_bars->setChecked(settings.value("displayTitleBars", true).toBool()); + ui.actionDisplay_widget_title_bars->setChecked(UISettings::values.display_titlebar); OnDisplayTitleBars(ui.actionDisplay_widget_title_bars->isChecked()); // Prepare actions for recent files @@ -172,21 +163,16 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) } UpdateRecentFiles(); - confirm_before_closing = settings.value("confirmClose", true).toBool(); - // Setup connections - connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString))); - connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile())); + connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)), Qt::DirectConnection); + connect(ui.action_Configure, SIGNAL(triggered()), this, SLOT(OnConfigure())); + connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()),Qt::DirectConnection); connect(ui.action_Load_Symbol_Map, SIGNAL(triggered()), this, SLOT(OnMenuLoadSymbolMap())); connect(ui.action_Select_Game_List_Root, SIGNAL(triggered()), this, SLOT(OnMenuSelectGameListRoot())); connect(ui.action_Start, SIGNAL(triggered()), this, SLOT(OnStartGame())); connect(ui.action_Pause, SIGNAL(triggered()), this, SLOT(OnPauseGame())); connect(ui.action_Stop, SIGNAL(triggered()), this, SLOT(OnStopGame())); - connect(ui.action_Use_Hardware_Renderer, SIGNAL(triggered(bool)), this, SLOT(SetHardwareRendererEnabled(bool))); - connect(ui.action_Use_Shader_JIT, SIGNAL(triggered(bool)), this, SLOT(SetShaderJITEnabled(bool))); - connect(ui.action_Use_Gdbstub, SIGNAL(triggered(bool)), this, SLOT(SetGdbstubEnabled(bool))); connect(ui.action_Single_Window_Mode, SIGNAL(triggered(bool)), this, SLOT(ToggleWindowMode())); - connect(ui.action_Hotkeys, SIGNAL(triggered()), this, SLOT(OnOpenHotkeysDialog())); connect(this, SIGNAL(EmulationStarting(EmuThread*)), disasmWidget, SLOT(OnEmulationStarting(EmuThread*))); connect(this, SIGNAL(EmulationStopping()), disasmWidget, SLOT(OnEmulationStopping())); @@ -201,7 +187,7 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) // Setup hotkeys RegisterHotkey("Main Window", "Load File", QKeySequence::Open); RegisterHotkey("Main Window", "Start Emulation"); - LoadHotkeys(settings); + LoadHotkeys(); connect(GetHotkey("Main Window", "Load File", this), SIGNAL(activated()), this, SLOT(OnMenuLoadFile())); connect(GetHotkey("Main Window", "Start Emulation", this), SIGNAL(activated()), this, SLOT(OnStartGame())); @@ -211,7 +197,7 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) show(); - game_list->PopulateAsync(settings.value("gameListRootDir", ".").toString(), settings.value("gameListDeepScan", false).toBool()); + game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); QStringList args = QApplication::arguments(); if (args.length() >= 2) { @@ -319,7 +305,7 @@ void GMainWindow::BootGame(const std::string& filename) { return; // Create and start the emulation thread - emu_thread = Common::make_unique<EmuThread>(render_window); + emu_thread = std::make_unique<EmuThread>(render_window); emit EmulationStarting(emu_thread.get()); render_window->moveContext(); emu_thread->start(); @@ -375,32 +361,24 @@ void GMainWindow::ShutdownGame() { emulation_running = false; } -void GMainWindow::StoreRecentFile(const std::string& filename) -{ - QSettings settings; - QStringList recent_files = settings.value("recentFiles").toStringList(); - recent_files.prepend(QString::fromStdString(filename)); - recent_files.removeDuplicates(); - while (recent_files.size() > max_recent_files_item) { - recent_files.removeLast(); +void GMainWindow::StoreRecentFile(const std::string& filename) { + UISettings::values.recent_files.prepend(QString::fromStdString(filename)); + UISettings::values.recent_files.removeDuplicates(); + while (UISettings::values.recent_files.size() > max_recent_files_item) { + UISettings::values.recent_files.removeLast(); } - settings.setValue("recentFiles", recent_files); - UpdateRecentFiles(); } void GMainWindow::UpdateRecentFiles() { - QSettings settings; - QStringList recent_files = settings.value("recentFiles").toStringList(); - - unsigned int num_recent_files = std::min(recent_files.size(), static_cast<int>(max_recent_files_item)); + unsigned int num_recent_files = std::min(UISettings::values.recent_files.size(), static_cast<int>(max_recent_files_item)); for (unsigned int i = 0; i < num_recent_files; i++) { - QString text = QString("&%1. %2").arg(i + 1).arg(QFileInfo(recent_files[i]).fileName()); + QString text = QString("&%1. %2").arg(i + 1).arg(QFileInfo(UISettings::values.recent_files[i]).fileName()); actions_recent_files[i]->setText(text); - actions_recent_files[i]->setData(recent_files[i]); - actions_recent_files[i]->setToolTip(recent_files[i]); + actions_recent_files[i]->setData(UISettings::values.recent_files[i]); + actions_recent_files[i]->setToolTip(UISettings::values.recent_files[i]); actions_recent_files[i]->setVisible(true); } @@ -417,40 +395,32 @@ void GMainWindow::UpdateRecentFiles() { } void GMainWindow::OnGameListLoadFile(QString game_path) { - BootGame(game_path.toLocal8Bit().data()); + BootGame(game_path.toStdString()); } void GMainWindow::OnMenuLoadFile() { - QSettings settings; - QString rom_path = settings.value("romsPath", QString()).toString(); - - QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), rom_path, tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.cci *.cxi)")); + QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), UISettings::values.roms_path, tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.cci *.cxi)")); if (!filename.isEmpty()) { - settings.setValue("romsPath", QFileInfo(filename).path()); + UISettings::values.roms_path = QFileInfo(filename).path(); - BootGame(filename.toLocal8Bit().data()); + BootGame(filename.toStdString()); } } void GMainWindow::OnMenuLoadSymbolMap() { - QSettings settings; - QString symbol_path = settings.value("symbolsPath", QString()).toString(); - - QString filename = QFileDialog::getOpenFileName(this, tr("Load Symbol Map"), symbol_path, tr("Symbol map (*)")); + QString filename = QFileDialog::getOpenFileName(this, tr("Load Symbol Map"), UISettings::values.symbols_path, tr("Symbol map (*)")); if (!filename.isEmpty()) { - settings.setValue("symbolsPath", QFileInfo(filename).path()); + UISettings::values.symbols_path = QFileInfo(filename).path(); - LoadSymbolMap(filename.toLocal8Bit().data()); + LoadSymbolMap(filename.toStdString()); } } void GMainWindow::OnMenuSelectGameListRoot() { - QSettings settings; - QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); if (!dir_path.isEmpty()) { - settings.setValue("gameListRootDir", dir_path); - game_list->PopulateAsync(dir_path, settings.value("gameListDeepScan").toBool()); + UISettings::values.gamedir = dir_path; + game_list->PopulateAsync(dir_path, UISettings::values.gamedir_deepscan); } } @@ -461,15 +431,12 @@ void GMainWindow::OnMenuRecentFile() { QString filename = action->data().toString(); QFileInfo file_info(filename); if (file_info.exists()) { - BootGame(filename.toLocal8Bit().data()); + BootGame(filename.toStdString()); } else { // Display an error message and remove the file from the list. QMessageBox::information(this, tr("File not found"), tr("File \"%1\" not found").arg(filename)); - QSettings settings; - QStringList recent_files = settings.value("recentFiles").toStringList(); - recent_files.removeOne(filename); - settings.setValue("recentFiles", recent_files); + UISettings::values.recent_files.removeOne(filename); UpdateRecentFiles(); } } @@ -496,31 +463,6 @@ void GMainWindow::OnStopGame() { ShutdownGame(); } -void GMainWindow::OnOpenHotkeysDialog() { - GHotkeysDialog dialog(this); - dialog.exec(); -} - -void GMainWindow::SetHardwareRendererEnabled(bool enabled) { - VideoCore::g_hw_renderer_enabled = enabled; - - Config config; - Settings::values.use_hw_renderer = enabled; - config.Save(); -} - -void GMainWindow::SetGdbstubEnabled(bool enabled) { - GDBStub::ToggleServer(enabled); -} - -void GMainWindow::SetShaderJITEnabled(bool enabled) { - VideoCore::g_shader_jit_enabled = enabled; - - Config config; - Settings::values.use_shader_jit = enabled; - config.Save(); -} - void GMainWindow::ToggleWindowMode() { if (ui.action_Single_Window_Mode->isChecked()) { // Render in the main window... @@ -547,11 +489,17 @@ void GMainWindow::ToggleWindowMode() { } void GMainWindow::OnConfigure() { - //GControllerConfigDialog* dialog = new GControllerConfigDialog(controller_ports, this); + ConfigureDialog configureDialog(this); + auto result = configureDialog.exec(); + if (result == QDialog::Accepted) + { + configureDialog.applyConfiguration(); + config->Save(); + } } bool GMainWindow::ConfirmClose() { - if (emu_thread == nullptr || !confirm_before_closing) + if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) return true; auto answer = QMessageBox::question(this, tr("Citra"), @@ -566,23 +514,19 @@ void GMainWindow::closeEvent(QCloseEvent* event) { return; } - // Save window layout - QSettings settings(QSettings::IniFormat, QSettings::UserScope, "Citra team", "Citra"); - - settings.beginGroup("UILayout"); - settings.setValue("geometry", saveGeometry()); - settings.setValue("state", saveState()); - settings.setValue("geometryRenderWindow", render_window->saveGeometry()); - settings.setValue("microProfileDialogGeometry", microProfileDialog->saveGeometry()); - settings.setValue("microProfileDialogVisible", microProfileDialog->isVisible()); - settings.endGroup(); + UISettings::values.geometry = saveGeometry(); + UISettings::values.state = saveState(); + UISettings::values.renderwindow_geometry = render_window->saveGeometry(); +#if MICROPROFILE_ENABLED + UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry(); + UISettings::values.microprofile_visible = microProfileDialog->isVisible(); +#endif + UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); + UISettings::values.display_titlebar = ui.actionDisplay_widget_title_bars->isChecked(); + UISettings::values.first_start = false; - settings.setValue("singleWindowMode", ui.action_Single_Window_Mode->isChecked()); - settings.setValue("displayTitleBars", ui.actionDisplay_widget_title_bars->isChecked()); - settings.setValue("firstStart", false); - settings.setValue("confirmClose", confirm_before_closing); - game_list->SaveInterfaceLayout(settings); - SaveHotkeys(settings); + game_list->SaveInterfaceLayout(); + SaveHotkeys(); // Shutdown session if the emu thread is active... if (emu_thread != nullptr) @@ -607,7 +551,6 @@ int main(int argc, char* argv[]) { }); // Init settings params - QSettings::setDefaultFormat(QSettings::IniFormat); QCoreApplication::setOrganizationName("Citra team"); QCoreApplication::setApplicationName("Citra"); diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 6e4e56689..477db5c5c 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -10,6 +10,7 @@ #include "ui_main.h" +class Config; class GameList; class GImageInfo; class GRenderWindow; @@ -104,12 +105,8 @@ private slots: /// Called whenever a user selects the "File->Select Game List Root" menu item void OnMenuSelectGameListRoot(); void OnMenuRecentFile(); - void OnOpenHotkeysDialog(); void OnConfigure(); void OnDisplayTitleBars(bool); - void SetHardwareRendererEnabled(bool); - void SetGdbstubEnabled(bool); - void SetShaderJITEnabled(bool); void ToggleWindowMode(); private: @@ -118,6 +115,8 @@ private: GRenderWindow* render_window; GameList* game_list; + std::unique_ptr<Config> config; + // Whether emulation is currently running in Citra. bool emulation_running = false; std::unique_ptr<EmuThread> emu_thread; @@ -131,7 +130,6 @@ private: GPUCommandListWidget* graphicsCommandsWidget; QAction* actions_recent_files[max_recent_files_item]; - bool confirm_before_closing; }; #endif // _CITRA_QT_MAIN_HXX_ diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index 1e8a07cfb..441e0b81e 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -45,7 +45,7 @@ <x>0</x> <y>0</y> <width>1081</width> - <height>22</height> + <height>19</height> </rect> </property> <widget class="QMenu" name="menu_File"> @@ -73,9 +73,6 @@ <addaction name="action_Pause"/> <addaction name="action_Stop"/> <addaction name="separator"/> - <addaction name="action_Use_Hardware_Renderer"/> - <addaction name="action_Use_Shader_JIT"/> - <addaction name="action_Use_Gdbstub"/> <addaction name="action_Configure"/> </widget> <widget class="QMenu" name="menu_View"> @@ -84,7 +81,6 @@ </property> <addaction name="action_Single_Window_Mode"/> <addaction name="actionDisplay_widget_title_bars"/> - <addaction name="action_Hotkeys"/> </widget> <widget class="QMenu" name="menu_Help"> <property name="title"> @@ -150,35 +146,6 @@ <string>Single Window Mode</string> </property> </action> - <action name="action_Hotkeys"> - <property name="text"> - <string>Configure &Hotkeys ...</string> - </property> - </action> - <action name="action_Use_Hardware_Renderer"> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="text"> - <string>Use Hardware Renderer</string> - </property> - </action> - <action name="action_Use_Shader_JIT"> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="text"> - <string>Use Shader JIT</string> - </property> - </action> - <action name="action_Use_Gdbstub"> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="text"> - <string>Use Gdbstub</string> - </property> - </action> <action name="action_Configure"> <property name="text"> <string>Configure ...</string> @@ -220,22 +187,6 @@ </hints> </connection> <connection> - <sender>action_Configure</sender> - <signal>triggered()</signal> - <receiver>MainWindow</receiver> - <slot>OnConfigure()</slot> - <hints> - <hint type="sourcelabel"> - <x>-1</x> - <y>-1</y> - </hint> - <hint type="destinationlabel"> - <x>540</x> - <y>364</y> - </hint> - </hints> - </connection> - <connection> <sender>actionDisplay_widget_title_bars</sender> <signal>triggered(bool)</signal> <receiver>MainWindow</receiver> diff --git a/src/citra_qt/ui_settings.cpp b/src/citra_qt/ui_settings.cpp new file mode 100644 index 000000000..5f2215899 --- /dev/null +++ b/src/citra_qt/ui_settings.cpp @@ -0,0 +1,11 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "ui_settings.h" + +namespace UISettings { + +Values values = {}; + +} diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h new file mode 100644 index 000000000..62db4a73e --- /dev/null +++ b/src/citra_qt/ui_settings.h @@ -0,0 +1,47 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QByteArray> +#include <QStringList> +#include <QString> + +#include <vector> + +namespace UISettings { + +using ContextualShortcut = std::pair<QString, int> ; +using Shortcut = std::pair<QString, ContextualShortcut>; + +struct Values { + QByteArray geometry; + QByteArray state; + + QByteArray renderwindow_geometry; + + QByteArray gamelist_header_state; + + QByteArray microprofile_geometry; + bool microprofile_visible; + + bool single_window_mode; + bool display_titlebar; + + bool confirm_before_closing; + bool first_start; + + QString roms_path; + QString symbols_path; + QString gamedir; + bool gamedir_deepscan; + QStringList recent_files; + + // Shortcut name <Shortcut, context> + std::vector<Shortcut> shortcuts; +}; + +extern Values values; + +} diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 1c9be718f..aa6eee2a3 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -42,13 +42,11 @@ set(HEADERS logging/filter.h logging/log.h logging/backend.h - make_unique.h math_util.h memory_util.h microprofile.h microprofileui.h platform.h - profiler.h profiler_reporting.h scm_rev.h scope_exit.h diff --git a/src/common/assert.h b/src/common/assert.h index 6849778b7..cd9b819a9 100644 --- a/src/common/assert.h +++ b/src/common/assert.h @@ -39,6 +39,7 @@ static void assert_noinline_call(const Fn& fn) { }); } while (0) #define UNREACHABLE() ASSERT_MSG(false, "Unreachable code!") +#define UNREACHABLE_MSG(...) ASSERT_MSG(false, __VA_ARGS__) #ifdef _DEBUG #define DEBUG_ASSERT(_a_) ASSERT(_a_) @@ -49,3 +50,4 @@ static void assert_noinline_call(const Fn& fn) { #endif #define UNIMPLEMENTED() DEBUG_ASSERT_MSG(false, "Unimplemented code!") +#define UNIMPLEMENTED_MSG(_a_, ...) ASSERT_MSG(false, _a_, __VA_ARGS__)
\ No newline at end of file diff --git a/src/common/bit_field.h b/src/common/bit_field.h index 371eb17a1..4748999ed 100644 --- a/src/common/bit_field.h +++ b/src/common/bit_field.h @@ -186,5 +186,5 @@ private: #pragma pack() #if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER) -static_assert(std::is_trivially_copyable<BitField<0, 1, u32>>::value, "BitField must be trivially copyable"); +static_assert(std::is_trivially_copyable<BitField<0, 1, unsigned>>::value, "BitField must be trivially copyable"); #endif diff --git a/src/common/bit_set.h b/src/common/bit_set.h index 85f91e786..7f5de8df2 100644 --- a/src/common/bit_set.h +++ b/src/common/bit_set.h @@ -7,6 +7,7 @@ #include <intrin.h> #endif #include <initializer_list> +#include <new> #include <type_traits> #include "common/common_types.h" @@ -186,4 +187,4 @@ public: typedef Common::BitSet<u8> BitSet8; typedef Common::BitSet<u16> BitSet16; typedef Common::BitSet<u32> BitSet32; -typedef Common::BitSet<u64> BitSet64;
\ No newline at end of file +typedef Common::BitSet<u64> BitSet64; diff --git a/src/common/code_block.h b/src/common/code_block.h index 9ef7296d3..2fa4a0090 100644 --- a/src/common/code_block.h +++ b/src/common/code_block.h @@ -4,8 +4,10 @@ #pragma once -#include "common_types.h" -#include "memory_util.h" +#include <cstddef> + +#include "common/common_types.h" +#include "common/memory_util.h" // Everything that needs to generate code should inherit from this. // You get memory management for free, plus, you can use all emitter functions without diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index aa6aff7b9..ab3515683 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -4,6 +4,10 @@ #pragma once +#if !defined(ARCHITECTURE_x86_64) && !defined(_M_ARM) +#include <cstdlib> // for exit +#endif + #include "common_types.h" #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) diff --git a/src/common/emu_window.h b/src/common/emu_window.h index a0ae4c9fa..7c3486dea 100644 --- a/src/common/emu_window.h +++ b/src/common/emu_window.h @@ -105,7 +105,7 @@ public: * @todo Fix this function to be thread-safe. * @return PadState object indicating the current pad state */ - const Service::HID::PadState GetPadState() const { + Service::HID::PadState GetPadState() const { return pad_state; } @@ -116,11 +116,59 @@ public: * @return std::tuple of (x, y, pressed) where `x` and `y` are the touch coordinates and * `pressed` is true if the touch screen is currently being pressed */ - const std::tuple<u16, u16, bool> GetTouchState() const { + std::tuple<u16, u16, bool> GetTouchState() const { return std::make_tuple(touch_x, touch_y, touch_pressed); } /** + * Gets the current accelerometer state (acceleration along each three axis). + * Axis explained: + * +x is the same direction as LEFT on D-pad. + * +y is normal to the touch screen, pointing outward. + * +z is the same direction as UP on D-pad. + * Units: + * 1 unit of return value = 1/512 g (measured by hw test), + * where g is the gravitational acceleration (9.8 m/sec2). + * @note This should be called by the core emu thread to get a state set by the window thread. + * @todo Implement accelerometer input in front-end. + * @return std::tuple of (x, y, z) + */ + std::tuple<s16, s16, s16> GetAccelerometerState() const { + // stubbed + return std::make_tuple(0, -512, 0); + } + + /** + * Gets the current gyroscope state (angular rates about each three axis). + * Axis explained: + * +x is the same direction as LEFT on D-pad. + * +y is normal to the touch screen, pointing outward. + * +z is the same direction as UP on D-pad. + * Orientation is determined by right-hand rule. + * Units: + * 1 unit of return value = (1/coef) deg/sec, + * where coef is the return value of GetGyroscopeRawToDpsCoefficient(). + * @note This should be called by the core emu thread to get a state set by the window thread. + * @todo Implement gyroscope input in front-end. + * @return std::tuple of (x, y, z) + */ + std::tuple<s16, s16, s16> GetGyroscopeState() const { + // stubbed + return std::make_tuple(0, 0, 0); + } + + /** + * Gets the coefficient for units conversion of gyroscope state. + * The conversion formula is r = coefficient * v, + * where v is angular rate in deg/sec, + * and r is the gyroscope state. + * @return float-type coefficient + */ + f32 GetGyroscopeRawToDpsCoefficient() const { + return 14.375f; // taken from hw test, and gyroscope's document + } + + /** * Returns currently active configuration. * @note Accesses to the returned object need not be consistent because it may be modified in another thread */ diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index c3061479a..6e2867658 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -69,9 +69,10 @@ static void StripTailDirSlashes(std::string &fname) { if (fname.length() > 1) { - size_t i = fname.length() - 1; - while (fname[i] == DIR_SEP_CHR) - fname[i--] = '\0'; + size_t i = fname.length(); + while (i > 0 && fname[i - 1] == DIR_SEP_CHR) + --i; + fname.resize(i); } return; } @@ -85,7 +86,11 @@ bool Exists(const std::string &filename) StripTailDirSlashes(copy); #ifdef _WIN32 - int result = _tstat64(Common::UTF8ToTStr(copy).c_str(), &file_info); + // Windows needs a slash to identify a driver root + if (copy.size() != 0 && copy.back() == ':') + copy += DIR_SEP_CHR; + + int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info); #else int result = stat64(copy.c_str(), &file_info); #endif @@ -102,7 +107,11 @@ bool IsDirectory(const std::string &filename) StripTailDirSlashes(copy); #ifdef _WIN32 - int result = _tstat64(Common::UTF8ToTStr(copy).c_str(), &file_info); + // Windows needs a slash to identify a driver root + if (copy.size() != 0 && copy.back() == ':') + copy += DIR_SEP_CHR; + + int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info); #else int result = stat64(copy.c_str(), &file_info); #endif @@ -138,7 +147,7 @@ bool Delete(const std::string &filename) } #ifdef _WIN32 - if (!DeleteFile(Common::UTF8ToTStr(filename).c_str())) + if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) { LOG_ERROR(Common_Filesystem, "DeleteFile failed on %s: %s", filename.c_str(), GetLastErrorMsg()); @@ -160,7 +169,7 @@ bool CreateDir(const std::string &path) { LOG_TRACE(Common_Filesystem, "directory %s", path.c_str()); #ifdef _WIN32 - if (::CreateDirectory(Common::UTF8ToTStr(path).c_str(), nullptr)) + if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr)) return true; DWORD error = GetLastError(); if (error == ERROR_ALREADY_EXISTS) @@ -241,7 +250,7 @@ bool DeleteDir(const std::string &filename) } #ifdef _WIN32 - if (::RemoveDirectory(Common::UTF8ToTStr(filename).c_str())) + if (::RemoveDirectoryW(Common::UTF8ToUTF16W(filename).c_str())) return true; #else if (rmdir(filename.c_str()) == 0) @@ -257,8 +266,13 @@ bool Rename(const std::string &srcFilename, const std::string &destFilename) { LOG_TRACE(Common_Filesystem, "%s --> %s", srcFilename.c_str(), destFilename.c_str()); +#ifdef _WIN32 + if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(), Common::UTF8ToUTF16W(destFilename).c_str()) == 0) + return true; +#else if (rename(srcFilename.c_str(), destFilename.c_str()) == 0) return true; +#endif LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg()); return false; @@ -270,7 +284,7 @@ bool Copy(const std::string &srcFilename, const std::string &destFilename) LOG_TRACE(Common_Filesystem, "%s --> %s", srcFilename.c_str(), destFilename.c_str()); #ifdef _WIN32 - if (CopyFile(Common::UTF8ToTStr(srcFilename).c_str(), Common::UTF8ToTStr(destFilename).c_str(), FALSE)) + if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(), Common::UTF8ToUTF16W(destFilename).c_str(), FALSE)) return true; LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s", @@ -358,7 +372,7 @@ u64 GetSize(const std::string &filename) struct stat64 buf; #ifdef _WIN32 - if (_tstat64(Common::UTF8ToTStr(filename).c_str(), &buf) == 0) + if (_wstat64(Common::UTF8ToUTF16W(filename).c_str(), &buf) == 0) #else if (stat64(filename.c_str(), &buf) == 0) #endif @@ -432,16 +446,16 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo #ifdef _WIN32 // Find the first file in the directory. - WIN32_FIND_DATA ffd; + WIN32_FIND_DATAW ffd; - HANDLE handle_find = FindFirstFile(Common::UTF8ToTStr(directory + "\\*").c_str(), &ffd); + HANDLE handle_find = FindFirstFileW(Common::UTF8ToUTF16W(directory + "\\*").c_str(), &ffd); if (handle_find == INVALID_HANDLE_VALUE) { FindClose(handle_find); return false; } // windows loop do { - const std::string virtual_name(Common::TStrToUTF8(ffd.cFileName)); + const std::string virtual_name(Common::UTF16ToUTF8(ffd.cFileName)); #else struct dirent dirent, *result = nullptr; @@ -465,7 +479,7 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo found_entries += ret_entries; #ifdef _WIN32 - } while (FindNextFile(handle_find, &ffd) != 0); + } while (FindNextFileW(handle_find, &ffd) != 0); FindClose(handle_find); #else } @@ -572,15 +586,23 @@ void CopyDir(const std::string &source_path, const std::string &dest_path) // Returns the current directory std::string GetCurrentDir() { - char *dir; // Get the current working directory (getcwd uses malloc) +#ifdef _WIN32 + wchar_t *dir; + if (!(dir = _wgetcwd(nullptr, 0))) { +#else + char *dir; if (!(dir = getcwd(nullptr, 0))) { - +#endif LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: %s", GetLastErrorMsg()); return nullptr; } +#ifdef _WIN32 + std::string strDir = Common::UTF16ToUTF8(dir); +#else std::string strDir = dir; +#endif free(dir); return strDir; } @@ -588,7 +610,11 @@ std::string GetCurrentDir() // Sets the current directory to the given directory bool SetCurrentDir(const std::string &directory) { +#ifdef _WIN32 + return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0; +#else return chdir(directory.c_str()) == 0; +#endif } #if defined(__APPLE__) @@ -613,9 +639,9 @@ std::string& GetExeDirectory() static std::string exe_path; if (exe_path.empty()) { - TCHAR tchar_exe_path[2048]; - GetModuleFileName(nullptr, tchar_exe_path, 2048); - exe_path = Common::TStrToUTF8(tchar_exe_path); + wchar_t wchar_exe_path[2048]; + GetModuleFileNameW(nullptr, wchar_exe_path, 2048); + exe_path = Common::UTF16ToUTF8(wchar_exe_path); exe_path = exe_path.substr(0, exe_path.find_last_of('\\')); } return exe_path; @@ -807,13 +833,12 @@ size_t WriteStringToFile(bool text_file, const std::string &str, const char *fil size_t ReadFileToString(bool text_file, const char *filename, std::string &str) { - FileUtil::IOFile file(filename, text_file ? "r" : "rb"); - auto const f = file.GetHandle(); + IOFile file(filename, text_file ? "r" : "rb"); - if (!f) + if (!file) return false; - str.resize(static_cast<u32>(GetSize(f))); + str.resize(static_cast<u32>(file.GetSize())); return file.ReadArray(&str[0], str.size()); } @@ -860,15 +885,10 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam } IOFile::IOFile() - : m_file(nullptr), m_good(true) -{} - -IOFile::IOFile(std::FILE* file) - : m_file(file), m_good(true) -{} +{ +} IOFile::IOFile(const std::string& filename, const char openmode[]) - : m_file(nullptr), m_good(true) { Open(filename, openmode); } @@ -879,7 +899,6 @@ IOFile::~IOFile() } IOFile::IOFile(IOFile&& other) - : m_file(nullptr), m_good(true) { Swap(other); } @@ -900,7 +919,7 @@ bool IOFile::Open(const std::string& filename, const char openmode[]) { Close(); #ifdef _WIN32 - _tfopen_s(&m_file, Common::UTF8ToTStr(filename).c_str(), Common::UTF8ToTStr(openmode).c_str()); + _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(), Common::UTF8ToUTF16W(openmode).c_str()); #else m_file = fopen(filename.c_str(), openmode); #endif @@ -918,26 +937,12 @@ bool IOFile::Close() return m_good; } -std::FILE* IOFile::ReleaseHandle() -{ - std::FILE* const ret = m_file; - m_file = nullptr; - return ret; -} - -void IOFile::SetHandle(std::FILE* file) -{ - Close(); - Clear(); - m_file = file; -} - -u64 IOFile::GetSize() +u64 IOFile::GetSize() const { if (IsOpen()) return FileUtil::GetSize(m_file); - else - return 0; + + return 0; } bool IOFile::Seek(s64 off, int origin) @@ -948,12 +953,12 @@ bool IOFile::Seek(s64 off, int origin) return m_good; } -u64 IOFile::Tell() +u64 IOFile::Tell() const { if (IsOpen()) return ftello(m_file); - else - return -1; + + return -1; } bool IOFile::Flush() diff --git a/src/common/file_util.h b/src/common/file_util.h index a85121aa6..c6a8694ce 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -7,13 +7,17 @@ #include <array> #include <fstream> #include <functional> -#include <cstddef> #include <cstdio> #include <string> +#include <type_traits> #include <vector> #include "common/common_types.h" +#ifdef _MSC_VER +#include "common/string_util.h" +#endif + // User directory indices for GetUserPath enum { D_USER_IDX, @@ -172,7 +176,6 @@ class IOFile : public NonCopyable { public: IOFile(); - IOFile(std::FILE* file); IOFile(const std::string& filename, const char openmode[]); ~IOFile(); @@ -188,6 +191,11 @@ public: template <typename T> size_t ReadArray(T* data, size_t length) { + static_assert(std::is_standard_layout<T>(), "Given array does not consist of standard layout objects"); +#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER) + static_assert(std::is_trivially_copyable<T>(), "Given array does not consist of trivially copyable objects"); +#endif + if (!IsOpen()) { m_good = false; return -1; @@ -203,9 +211,10 @@ public: template <typename T> size_t WriteArray(const T* data, size_t length) { - static_assert(std::is_standard_layout<T>::value, "Given array does not consist of standard layout objects"); - // TODO: gcc 4.8 does not support is_trivially_copyable, but we really should check for it here. - //static_assert(std::is_trivially_copyable<T>::value, "Given array does not consist of trivially copyable objects"); + static_assert(std::is_standard_layout<T>(), "Given array does not consist of standard layout objects"); +#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER) + static_assert(std::is_trivially_copyable<T>(), "Given array does not consist of trivially copyable objects"); +#endif if (!IsOpen()) { m_good = false; @@ -235,32 +244,24 @@ public: return WriteArray(&object, 1); } - bool IsOpen() { return nullptr != m_file; } + bool IsOpen() const { return nullptr != m_file; } // m_good is set to false when a read, write or other function fails - bool IsGood() { return m_good; } - operator void*() { return m_good ? m_file : nullptr; } - - std::FILE* ReleaseHandle(); - - std::FILE* GetHandle() { return m_file; } - - void SetHandle(std::FILE* file); + bool IsGood() const { return m_good; } + explicit operator bool() const { return IsGood(); } bool Seek(s64 off, int origin); - u64 Tell(); - u64 GetSize(); + u64 Tell() const; + u64 GetSize() const; bool Resize(u64 size); bool Flush(); // clear error state void Clear() { m_good = true; std::clearerr(m_file); } - std::FILE* m_file; - bool m_good; private: - IOFile(IOFile&); - IOFile& operator=(IOFile& other); + std::FILE* m_file = nullptr; + bool m_good = true; }; } // namespace diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 4c86151ab..3d39f94d5 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -34,6 +34,7 @@ namespace Log { SUB(Kernel, SVC) \ CLS(Service) \ SUB(Service, SRV) \ + SUB(Service, FRD) \ SUB(Service, FS) \ SUB(Service, ERR) \ SUB(Service, APT) \ @@ -46,8 +47,10 @@ namespace Log { SUB(Service, NIM) \ SUB(Service, NWM) \ SUB(Service, CAM) \ + SUB(Service, CECD) \ SUB(Service, CFG) \ SUB(Service, DSP) \ + SUB(Service, DLP) \ SUB(Service, HID) \ SUB(Service, SOC) \ SUB(Service, IR) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index e4c39c308..521362317 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -49,6 +49,7 @@ enum class Class : ClassType { Service, ///< HLE implementation of system services. Each major service /// should have its own subclass. Service_SRV, ///< The SRV (Service Directory) implementation + Service_FRD, ///< The FRD (Friends) service Service_FS, ///< The FS (Filesystem) service implementation Service_ERR, ///< The ERR (Error) port implementation Service_APT, ///< The APT (Applets) service @@ -61,8 +62,10 @@ enum class Class : ClassType { Service_NIM, ///< The NIM (Network interface manager) service Service_NWM, ///< The NWM (Network wlan manager) service Service_CAM, ///< The CAM (Camera) service + Service_CECD, ///< The CECD (StreetPass) service Service_CFG, ///< The CFG (Configuration) service Service_DSP, ///< The DSP (DSP control) service + Service_DLP, ///< The DLP (Download Play) service Service_HID, ///< The HID (Human interface device) service Service_SOC, ///< The SOC (Socket) service Service_IR, ///< The IR service diff --git a/src/common/make_unique.h b/src/common/make_unique.h deleted file mode 100644 index f6e7f017c..000000000 --- a/src/common/make_unique.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <algorithm> -#include <memory> - -namespace Common { - -template <typename T, typename... Args> -std::unique_ptr<T> make_unique(Args&&... args) { - return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); -} - -} // namespace diff --git a/src/common/microprofile.h b/src/common/microprofile.h index d3b6cb97c..ef312c6e1 100644 --- a/src/common/microprofile.h +++ b/src/common/microprofile.h @@ -4,6 +4,10 @@ #pragma once +// Uncomment this to disable microprofile. This will get you cleaner profiles when using +// external sampling profilers like "Very Sleepy", and will improve performance somewhat. +// #define MICROPROFILE_ENABLED 0 + // Customized Citra settings. // This file wraps the MicroProfile header so that these are consistent everywhere. #define MICROPROFILE_WEBSERVER 0 diff --git a/src/common/microprofileui.h b/src/common/microprofileui.h index 97c369bd9..41abe6b75 100644 --- a/src/common/microprofileui.h +++ b/src/common/microprofileui.h @@ -13,4 +13,7 @@ #define MICROPROFILE_HELP_ALT "Right-Click" #define MICROPROFILE_HELP_MOD "Ctrl" +// This isn't included by microprofileui.h :( +#include <cstdlib> // For std::abs + #include <microprofileui.h> diff --git a/src/common/profiler.cpp b/src/common/profiler.cpp index 7792edd2f..49eb3f40c 100644 --- a/src/common/profiler.cpp +++ b/src/common/profiler.cpp @@ -7,71 +7,16 @@ #include <vector> #include "common/assert.h" -#include "common/profiler.h" #include "common/profiler_reporting.h" #include "common/synchronized_wrapper.h" -#if defined(_MSC_VER) && _MSC_VER <= 1800 // MSVC 2013. - #define WIN32_LEAN_AND_MEAN - #include <Windows.h> // For QueryPerformanceCounter/Frequency -#endif - namespace Common { namespace Profiling { -#if ENABLE_PROFILING -thread_local Timer* Timer::current_timer = nullptr; -#endif - -#if defined(_MSC_VER) && _MSC_VER <= 1800 // MSVC 2013 -QPCClock::time_point QPCClock::now() { - static LARGE_INTEGER freq; - // Use this dummy local static to ensure this gets initialized once. - static BOOL dummy = QueryPerformanceFrequency(&freq); - - LARGE_INTEGER ticks; - QueryPerformanceCounter(&ticks); - - // This is prone to overflow when multiplying, which is why I'm using micro instead of nano. The - // correct way to approach this would be to just return ticks as a time_point and then subtract - // and do this conversion when creating a duration from two time_points, however, as far as I - // could tell the C++ requirements for these types are incompatible with this approach. - return time_point(duration(ticks.QuadPart * std::micro::den / freq.QuadPart)); -} -#endif - -TimingCategory::TimingCategory(const char* name, TimingCategory* parent) - : accumulated_duration(0) { - - ProfilingManager& manager = GetProfilingManager(); - category_id = manager.RegisterTimingCategory(this, name); - if (parent != nullptr) - manager.SetTimingCategoryParent(category_id, parent->category_id); -} - ProfilingManager::ProfilingManager() : last_frame_end(Clock::now()), this_frame_start(Clock::now()) { } -unsigned int ProfilingManager::RegisterTimingCategory(TimingCategory* category, const char* name) { - TimingCategoryInfo info; - info.category = category; - info.name = name; - info.parent = TimingCategoryInfo::NO_PARENT; - - unsigned int id = (unsigned int)timing_categories.size(); - timing_categories.push_back(std::move(info)); - - return id; -} - -void ProfilingManager::SetTimingCategoryParent(unsigned int category, unsigned int parent) { - ASSERT(category < timing_categories.size()); - ASSERT(parent < timing_categories.size()); - - timing_categories[category].parent = parent; -} - void ProfilingManager::BeginFrame() { this_frame_start = Clock::now(); } @@ -82,11 +27,6 @@ void ProfilingManager::FinishFrame() { results.interframe_time = now - last_frame_end; results.frame_time = now - this_frame_start; - results.time_per_category.resize(timing_categories.size()); - for (size_t i = 0; i < timing_categories.size(); ++i) { - results.time_per_category[i] = timing_categories[i].category->GetAccumulatedTime(); - } - last_frame_end = now; } @@ -100,26 +40,9 @@ void TimingResultsAggregator::Clear() { window_size = cursor = 0; } -void TimingResultsAggregator::SetNumberOfCategories(size_t n) { - size_t old_size = times_per_category.size(); - if (n == old_size) - return; - - times_per_category.resize(n); - - for (size_t i = old_size; i < n; ++i) { - times_per_category[i].resize(max_window_size, Duration::zero()); - } -} - void TimingResultsAggregator::AddFrame(const ProfilingFrameResult& frame_result) { - SetNumberOfCategories(frame_result.time_per_category.size()); - interframe_times[cursor] = frame_result.interframe_time; frame_times[cursor] = frame_result.frame_time; - for (size_t i = 0; i < frame_result.time_per_category.size(); ++i) { - times_per_category[i][cursor] = frame_result.time_per_category[i]; - } ++cursor; if (cursor == max_window_size) @@ -162,11 +85,6 @@ AggregatedFrameResult TimingResultsAggregator::GetAggregatedResults() const { result.fps = 0.0f; } - result.time_per_category.resize(times_per_category.size()); - for (size_t i = 0; i < times_per_category.size(); ++i) { - result.time_per_category[i] = AggregateField(times_per_category[i], window_size); - } - return result; } diff --git a/src/common/profiler.h b/src/common/profiler.h deleted file mode 100644 index 3e967b4bc..000000000 --- a/src/common/profiler.h +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <atomic> -#include <chrono> - -#include "common/assert.h" -#include "common/thread.h" - -namespace Common { -namespace Profiling { - -// If this is defined to 0, it turns all Timers into no-ops. -#ifndef ENABLE_PROFILING -#define ENABLE_PROFILING 1 -#endif - -#if defined(_MSC_VER) && _MSC_VER <= 1800 // MSVC 2013 -// MSVC up to 2013 doesn't use QueryPerformanceCounter for high_resolution_clock, so it has bad -// precision. We manually implement a clock based on QPC to get good results. - -struct QPCClock { - using duration = std::chrono::microseconds; - using time_point = std::chrono::time_point<QPCClock>; - using rep = duration::rep; - using period = duration::period; - static const bool is_steady = false; - - static time_point now(); -}; - -using Clock = QPCClock; -#else -using Clock = std::chrono::high_resolution_clock; -#endif - -using Duration = Clock::duration; - -/** - * Represents a timing category that measured time can be accounted towards. Should be declared as a - * global variable and passed to Timers. - */ -class TimingCategory final { -public: - TimingCategory(const char* name, TimingCategory* parent = nullptr); - - unsigned int GetCategoryId() const { - return category_id; - } - - /// Adds some time to this category. Can safely be called from multiple threads at the same time. - void AddTime(Duration amount) { - std::atomic_fetch_add_explicit( - &accumulated_duration, amount.count(), - std::memory_order_relaxed); - } - - /** - * Atomically retrieves the accumulated measured time for this category and resets the counter - * to zero. Can be safely called concurrently with AddTime. - */ - Duration GetAccumulatedTime() { - return Duration(std::atomic_exchange_explicit( - &accumulated_duration, (Duration::rep)0, - std::memory_order_relaxed)); - } - -private: - unsigned int category_id; - std::atomic<Duration::rep> accumulated_duration; -}; - -/** - * Measures time elapsed between a call to Start and a call to Stop and attributes it to the given - * TimingCategory. Start/Stop can be called multiple times on the same timer, but each call must be - * appropriately paired. - * - * When a Timer is started, it automatically pauses a previously running timer on the same thread, - * which is resumed when it is stopped. As such, no special action needs to be taken to avoid - * double-accounting of time on two categories. - */ -class Timer { -public: - Timer(TimingCategory& category) : category(category) { - } - - void Start() { -#if ENABLE_PROFILING - ASSERT(!running); - previous_timer = current_timer; - current_timer = this; - if (previous_timer != nullptr) - previous_timer->StopTiming(); - - StartTiming(); -#endif - } - - void Stop() { -#if ENABLE_PROFILING - ASSERT(running); - StopTiming(); - - if (previous_timer != nullptr) - previous_timer->StartTiming(); - current_timer = previous_timer; -#endif - } - -private: -#if ENABLE_PROFILING - void StartTiming() { - start = Clock::now(); - running = true; - } - - void StopTiming() { - auto duration = Clock::now() - start; - running = false; - category.AddTime(std::chrono::duration_cast<Duration>(duration)); - } - - Clock::time_point start; - bool running = false; - - Timer* previous_timer; - static thread_local Timer* current_timer; -#endif - - TimingCategory& category; -}; - -/** - * A Timer that automatically starts timing when created and stops at the end of the scope. Should - * be used in the majority of cases. - */ -class ScopeTimer : public Timer { -public: - ScopeTimer(TimingCategory& category) : Timer(category) { - Start(); - } - - ~ScopeTimer() { - Stop(); - } -}; - -} // namespace Profiling -} // namespace Common diff --git a/src/common/profiler_reporting.h b/src/common/profiler_reporting.h index df98e05b7..fa1ac883f 100644 --- a/src/common/profiler_reporting.h +++ b/src/common/profiler_reporting.h @@ -4,22 +4,17 @@ #pragma once +#include <chrono> #include <cstddef> #include <vector> -#include "common/profiler.h" #include "common/synchronized_wrapper.h" namespace Common { namespace Profiling { -struct TimingCategoryInfo { - static const unsigned int NO_PARENT = -1; - - TimingCategory* category; - const char* name; - unsigned int parent; -}; +using Clock = std::chrono::high_resolution_clock; +using Duration = Clock::duration; struct ProfilingFrameResult { /// Time since the last delivered frame @@ -27,22 +22,12 @@ struct ProfilingFrameResult { /// Time spent processing a frame, excluding VSync Duration frame_time; - - /// Total amount of time spent inside each category in this frame. Indexed by the category id - std::vector<Duration> time_per_category; }; class ProfilingManager final { public: ProfilingManager(); - unsigned int RegisterTimingCategory(TimingCategory* category, const char* name); - void SetTimingCategoryParent(unsigned int category, unsigned int parent); - - const std::vector<TimingCategoryInfo>& GetTimingCategoriesInfo() const { - return timing_categories; - } - /// This should be called after swapping screen buffers. void BeginFrame(); /// This should be called before swapping screen buffers. @@ -54,7 +39,6 @@ public: } private: - std::vector<TimingCategoryInfo> timing_categories; Clock::time_point last_frame_end; Clock::time_point this_frame_start; @@ -73,9 +57,6 @@ struct AggregatedFrameResult { AggregatedDuration frame_time; float fps; - - /// Total amount of time spent inside each category in this frame. Indexed by the category id - std::vector<AggregatedDuration> time_per_category; }; class TimingResultsAggregator final { @@ -83,7 +64,6 @@ public: TimingResultsAggregator(size_t window_size); void Clear(); - void SetNumberOfCategories(size_t n); void AddFrame(const ProfilingFrameResult& frame_result); @@ -95,7 +75,6 @@ public: std::vector<Duration> interframe_times; std::vector<Duration> frame_times; - std::vector<std::vector<Duration>> times_per_category; }; ProfilingManager& GetProfilingManager(); diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 6d6fc591f..f0aa072db 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -320,27 +320,27 @@ std::u16string UTF8ToUTF16(const std::string& input) #endif } -static std::string UTF16ToUTF8(const std::wstring& input) +static std::wstring CPToUTF16(u32 code_page, const std::string& input) { - auto const size = WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(input.size()), nullptr, 0, nullptr, nullptr); + auto const size = MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0); - std::string output; + std::wstring output; output.resize(size); - if (size == 0 || size != WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(input.size()), &output[0], static_cast<int>(output.size()), nullptr, nullptr)) + if (size == 0 || size != MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), &output[0], static_cast<int>(output.size()))) output.clear(); return output; } -static std::wstring CPToUTF16(u32 code_page, const std::string& input) +std::string UTF16ToUTF8(const std::wstring& input) { - auto const size = MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0); + auto const size = WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(input.size()), nullptr, 0, nullptr, nullptr); - std::wstring output; + std::string output; output.resize(size); - if (size == 0 || size != MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), &output[0], static_cast<int>(output.size()))) + if (size == 0 || size != WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(input.size()), &output[0], static_cast<int>(output.size()), nullptr, nullptr)) output.clear(); return output; diff --git a/src/common/string_util.h b/src/common/string_util.h index c5c474c6f..89d9f133e 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -95,7 +95,7 @@ std::string CP1252ToUTF8(const std::string& str); std::string SHIFTJISToUTF8(const std::string& str); #ifdef _WIN32 - +std::string UTF16ToUTF8(const std::wstring& input); std::wstring UTF8ToUTF16W(const std::string& str); #ifdef _UNICODE diff --git a/src/common/thread.h b/src/common/thread.h index 8255ee6d3..bbfa8befa 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -30,8 +30,7 @@ # endif #endif -namespace Common -{ +namespace Common { int CurrentThreadId(); @@ -43,55 +42,55 @@ public: Event() : is_set(false) {} void Set() { - std::lock_guard<std::mutex> lk(m_mutex); + std::lock_guard<std::mutex> lk(mutex); if (!is_set) { is_set = true; - m_condvar.notify_one(); + condvar.notify_one(); } } void Wait() { - std::unique_lock<std::mutex> lk(m_mutex); - m_condvar.wait(lk, [&]{ return is_set; }); + std::unique_lock<std::mutex> lk(mutex); + condvar.wait(lk, [&]{ return is_set; }); is_set = false; } void Reset() { - std::unique_lock<std::mutex> lk(m_mutex); + std::unique_lock<std::mutex> lk(mutex); // no other action required, since wait loops on the predicate and any lingering signal will get cleared on the first iteration is_set = false; } private: bool is_set; - std::condition_variable m_condvar; - std::mutex m_mutex; + std::condition_variable condvar; + std::mutex mutex; }; class Barrier { public: - Barrier(size_t count) : m_count(count), m_waiting(0) {} + explicit Barrier(size_t count_) : count(count_), waiting(0), generation(0) {} /// Blocks until all "count" threads have called Sync() void Sync() { - std::unique_lock<std::mutex> lk(m_mutex); + std::unique_lock<std::mutex> lk(mutex); + const size_t current_generation = generation; - // TODO: broken when next round of Sync()s - // is entered before all waiting threads return from the notify_all - - if (++m_waiting == m_count) { - m_waiting = 0; - m_condvar.notify_all(); + if (++waiting == count) { + generation++; + waiting = 0; + condvar.notify_all(); } else { - m_condvar.wait(lk, [&]{ return m_waiting == 0; }); + condvar.wait(lk, [this, current_generation]{ return current_generation != generation; }); } } private: - std::condition_variable m_condvar; - std::mutex m_mutex; - const size_t m_count; - size_t m_waiting; + std::condition_variable condvar; + std::mutex mutex; + const size_t count; + size_t waiting; + size_t generation; // Incremented once each time the barrier is used }; void SleepCurrentThread(int ms); @@ -100,8 +99,7 @@ void SwitchCurrentThread(); // On Linux, this is equal to sleep 1ms // Use this function during a spin-wait to make the current thread // relax while another thread is working. This may be more efficient // than using events because event functions use kernel calls. -inline void YieldCPU() -{ +inline void YieldCPU() { std::this_thread::yield(); } diff --git a/src/common/x64/emitter.cpp b/src/common/x64/emitter.cpp index 1dcf2416c..5662f7f86 100644 --- a/src/common/x64/emitter.cpp +++ b/src/common/x64/emitter.cpp @@ -455,6 +455,18 @@ void XEmitter::CALL(const void* fnptr) Write32(u32(distance)); } +FixupBranch XEmitter::CALL() +{ + FixupBranch branch; + branch.type = 1; + branch.ptr = code + 5; + + Write8(0xE8); + Write32(0); + + return branch; +} + FixupBranch XEmitter::J(bool force5bytes) { FixupBranch branch; @@ -531,6 +543,22 @@ void XEmitter::SetJumpTarget(const FixupBranch& branch) } } +void XEmitter::SetJumpTarget(const FixupBranch& branch, const u8* target) +{ + if (branch.type == 0) + { + s64 distance = (s64)(target - branch.ptr); + ASSERT_MSG(distance >= -0x80 && distance < 0x80, "Jump target too far away, needs force5Bytes = true"); + branch.ptr[-1] = (u8)(s8)distance; + } + else if (branch.type == 1) + { + s64 distance = (s64)(target - branch.ptr); + ASSERT_MSG(distance >= -0x80000000LL && distance < 0x80000000LL, "Jump target too far away, needs indirect register"); + ((s32*)branch.ptr)[-1] = (s32)distance; + } +} + //Single byte opcodes //There is no PUSHAD/POPAD in 64-bit mode. void XEmitter::INT3() {Write8(0xCC);} diff --git a/src/common/x64/emitter.h b/src/common/x64/emitter.h index 7c6548fb5..60a77dfe1 100644 --- a/src/common/x64/emitter.h +++ b/src/common/x64/emitter.h @@ -17,6 +17,8 @@ #pragma once +#include <cstddef> + #include "common/assert.h" #include "common/bit_set.h" #include "common/common_types.h" @@ -425,12 +427,14 @@ public: #undef CALL #endif void CALL(const void* fnptr); + FixupBranch CALL(); void CALLptr(OpArg arg); FixupBranch J_CC(CCFlags conditionCode, bool force5bytes = false); void J_CC(CCFlags conditionCode, const u8* addr, bool force5Bytes = false); void SetJumpTarget(const FixupBranch& branch); + void SetJumpTarget(const FixupBranch& branch, const u8* target); void SETcc(CCFlags flag, OpArg dest); // Note: CMOV brings small if any benefit on current cpus. diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3473e2f5b..a8d891689 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -68,6 +68,7 @@ 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/dsp_dsp.cpp hle/service/err_f.cpp hle/service/frd/frd.cpp @@ -200,6 +201,7 @@ 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/dsp_dsp.h hle/service/err_f.h hle/service/frd/frd.h diff --git a/src/core/arm/dyncom/arm_dyncom.cpp b/src/core/arm/dyncom/arm_dyncom.cpp index f3be2c857..a3581132c 100644 --- a/src/core/arm/dyncom/arm_dyncom.cpp +++ b/src/core/arm/dyncom/arm_dyncom.cpp @@ -3,8 +3,7 @@ // Refer to the license.txt file included. #include <cstring> - -#include "common/make_unique.h" +#include <memory> #include "core/arm/skyeye_common/armstate.h" #include "core/arm/skyeye_common/armsupp.h" @@ -18,7 +17,7 @@ #include "core/core_timing.h" ARM_DynCom::ARM_DynCom(PrivilegeMode initial_mode) { - state = Common::make_unique<ARMul_State>(initial_mode); + state = std::make_unique<ARMul_State>(initial_mode); } ARM_DynCom::~ARM_DynCom() { @@ -94,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; // Usermode + context.cpsr = 0x1F | ((entry_point & 1) << 5); // Usermode and THUMB mode } void ARM_DynCom::SaveContext(Core::ThreadContext& ctx) { diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index 5f8826034..8d4b26815 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -10,7 +10,6 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "common/microprofile.h" -#include "common/profiler.h" #include "core/memory.h" #include "core/hle/svc.h" @@ -25,9 +24,6 @@ #include "core/gdbstub/gdbstub.h" -Common::Profiling::TimingCategory profile_execute("DynCom::Execute"); -Common::Profiling::TimingCategory profile_decode("DynCom::Decode"); - enum { COND = (1 << 0), NON_BRANCH = (1 << 1), @@ -36,7 +32,8 @@ enum { CALL = (1 << 4), RET = (1 << 5), END_OF_PAGE = (1 << 6), - THUMB = (1 << 7) + THUMB = (1 << 7), + SINGLE_STEP = (1 << 8) }; #define RM BITS(sht_oper, 0, 3) @@ -3466,8 +3463,35 @@ enum { MICROPROFILE_DEFINE(DynCom_Decode, "DynCom", "Decode", MP_RGB(255, 64, 64)); -static int InterpreterTranslate(ARMul_State* cpu, int& bb_start, u32 addr) { - Common::Profiling::ScopeTimer timer_decode(profile_decode); +static unsigned int InterpreterTranslateInstruction(const ARMul_State* cpu, const u32 phys_addr, ARM_INST_PTR& inst_base) { + unsigned int inst_size = 4; + unsigned int inst = Memory::Read32(phys_addr & 0xFFFFFFFC); + + // If we are in Thumb mode, we'll translate one Thumb instruction to the corresponding ARM instruction + if (cpu->TFlag) { + u32 arm_inst; + ThumbDecodeStatus state = DecodeThumbInstruction(inst, phys_addr, &arm_inst, &inst_size, &inst_base); + + // We have translated the Thumb branch instruction in the Thumb decoder + if (state == ThumbDecodeStatus::BRANCH) { + return inst_size; + } + inst = arm_inst; + } + + int idx; + if (DecodeARMInstruction(inst, &idx) == ARMDecodeStatus::FAILURE) { + std::string disasm = ARM_Disasm::Disassemble(phys_addr, inst); + LOG_ERROR(Core_ARM11, "Decode failure.\tPC : [0x%x]\tInstruction : %s [%x]", phys_addr, disasm.c_str(), inst); + LOG_ERROR(Core_ARM11, "cpsr=0x%x, cpu->TFlag=%d, r15=0x%x", cpu->Cpsr, cpu->TFlag, cpu->Reg[15]); + CITRA_IGNORE_EXIT(-1); + } + inst_base = arm_instruction_trans[idx](inst, idx); + + return inst_size; +} + +static int InterpreterTranslateBlock(ARMul_State* cpu, int& bb_start, u32 addr) { MICROPROFILE_SCOPE(DynCom_Decode); // Decode instruction, get index @@ -3475,8 +3499,6 @@ static int InterpreterTranslate(ARMul_State* cpu, int& bb_start, u32 addr) { // Go on next, until terminal instruction // Save start addr of basicblock in CreamCache ARM_INST_PTR inst_base = nullptr; - unsigned int inst, inst_size = 4; - int idx; int ret = NON_BRANCH; int size = 0; // instruction size of basic block bb_start = top; @@ -3485,30 +3507,10 @@ static int InterpreterTranslate(ARMul_State* cpu, int& bb_start, u32 addr) { u32 pc_start = cpu->Reg[15]; while (ret == NON_BRANCH) { - inst = Memory::Read32(phys_addr & 0xFFFFFFFC); + unsigned int inst_size = InterpreterTranslateInstruction(cpu, phys_addr, inst_base); size++; - // If we are in Thumb mode, we'll translate one Thumb instruction to the corresponding ARM instruction - if (cpu->TFlag) { - u32 arm_inst; - ThumbDecodeStatus state = DecodeThumbInstruction(inst, phys_addr, &arm_inst, &inst_size, &inst_base); - - // We have translated the Thumb branch instruction in the Thumb decoder - if (state == ThumbDecodeStatus::BRANCH) { - goto translated; - } - inst = arm_inst; - } - if (DecodeARMInstruction(inst, &idx) == ARMDecodeStatus::FAILURE) { - std::string disasm = ARM_Disasm::Disassemble(phys_addr, inst); - LOG_ERROR(Core_ARM11, "Decode failure.\tPC : [0x%x]\tInstruction : %s [%x]", phys_addr, disasm.c_str(), inst); - LOG_ERROR(Core_ARM11, "cpsr=0x%x, cpu->TFlag=%d, r15=0x%x", cpu->Cpsr, cpu->TFlag, cpu->Reg[15]); - CITRA_IGNORE_EXIT(-1); - } - inst_base = arm_instruction_trans[idx](inst, idx); - -translated: phys_addr += inst_size; if ((phys_addr & 0xfff) == 0) { @@ -3522,6 +3524,26 @@ translated: return KEEP_GOING; } +static int InterpreterTranslateSingle(ARMul_State* cpu, int& bb_start, u32 addr) { + MICROPROFILE_SCOPE(DynCom_Decode); + + ARM_INST_PTR inst_base = nullptr; + bb_start = top; + + u32 phys_addr = addr; + u32 pc_start = cpu->Reg[15]; + + InterpreterTranslateInstruction(cpu, phys_addr, inst_base); + + if (inst_base->br == NON_BRANCH) { + inst_base->br = SINGLE_STEP; + } + + cpu->instruction_cache[pc_start] = bb_start; + + return KEEP_GOING; +} + static int clz(unsigned int x) { int n; if (x == 0) return (32); @@ -3537,7 +3559,6 @@ static int clz(unsigned int x) { MICROPROFILE_DEFINE(DynCom_Execute, "DynCom", "Execute", MP_RGB(255, 0, 0)); unsigned InterpreterMainLoop(ARMul_State* cpu) { - Common::Profiling::ScopeTimer timer_execute(profile_execute); MICROPROFILE_SCOPE(DynCom_Execute); GDBStub::BreakpointAddress breakpoint_data; @@ -3871,8 +3892,11 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { auto itr = cpu->instruction_cache.find(cpu->Reg[15]); if (itr != cpu->instruction_cache.end()) { ptr = itr->second; + } else if (cpu->NumInstrsToExecute != 1) { + if (InterpreterTranslateBlock(cpu, ptr, cpu->Reg[15]) == FETCH_EXCEPTION) + goto END; } else { - if (InterpreterTranslate(cpu, ptr, cpu->Reg[15]) == FETCH_EXCEPTION) + if (InterpreterTranslateSingle(cpu, ptr, cpu->Reg[15]) == FETCH_EXCEPTION) goto END; } @@ -3924,9 +3948,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { if (inst_base->cond == ConditionCode::AL || CondPassed(cpu, inst_base->cond)) { add_inst* const inst_cream = (add_inst*)inst_base->component; - u32 rn_val = RN; - if (inst_cream->Rn == 15) - rn_val += 2 * cpu->GetInstructionSize(); + u32 rn_val = CHECK_READ_REG15_WA(cpu, inst_cream->Rn); bool carry; bool overflow; @@ -4051,11 +4073,12 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { if ((inst_base->cond == ConditionCode::AL) || CondPassed(cpu, inst_base->cond)) { unsigned int inst = inst_cream->inst; if (BITS(inst, 20, 27) == 0x12 && BITS(inst, 4, 7) == 0x3) { + const u32 jump_address = cpu->Reg[inst_cream->val.Rm]; cpu->Reg[14] = (cpu->Reg[15] + cpu->GetInstructionSize()); if(cpu->TFlag) cpu->Reg[14] |= 0x1; - cpu->Reg[15] = cpu->Reg[inst_cream->val.Rm] & 0xfffffffe; - cpu->TFlag = cpu->Reg[inst_cream->val.Rm] & 0x1; + cpu->Reg[15] = jump_address & 0xfffffffe; + cpu->TFlag = jump_address & 0x1; } else { cpu->Reg[14] = (cpu->Reg[15] + cpu->GetInstructionSize()); cpu->TFlag = 0x1; @@ -6136,9 +6159,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { if (inst_base->cond == ConditionCode::AL || CondPassed(cpu, inst_base->cond)) { sub_inst* const inst_cream = (sub_inst*)inst_base->component; - u32 rn_val = RN; - if (inst_cream->Rn == 15) - rn_val += 2 * cpu->GetInstructionSize(); + u32 rn_val = CHECK_READ_REG15_WA(cpu, inst_cream->Rn); bool carry; bool overflow; diff --git a/src/core/arm/skyeye_common/armstate.cpp b/src/core/arm/skyeye_common/armstate.cpp index 2d814345a..5550c112e 100644 --- a/src/core/arm/skyeye_common/armstate.cpp +++ b/src/core/arm/skyeye_common/armstate.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include "common/swap.h" #include "common/logging/log.h" #include "core/memory.h" @@ -48,8 +49,7 @@ void ARMul_State::ChangePrivilegeMode(u32 new_mode) Spsr[UNDEFBANK] = Spsr_copy; break; case FIQ32MODE: - Reg_firq[0] = Reg[13]; - Reg_firq[1] = Reg[14]; + std::copy(Reg.begin() + 8, Reg.end() - 1, Reg_firq.begin()); Spsr[FIQBANK] = Spsr_copy; break; } @@ -85,8 +85,7 @@ void ARMul_State::ChangePrivilegeMode(u32 new_mode) Bank = UNDEFBANK; break; case FIQ32MODE: - Reg[13] = Reg_firq[0]; - Reg[14] = Reg_firq[1]; + std::copy(Reg_firq.begin(), Reg_firq.end(), Reg.begin() + 8); Spsr_copy = Spsr[FIQBANK]; Bank = FIQBANK; break; diff --git a/src/core/core.cpp b/src/core/core.cpp index a156682aa..cabab744a 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -4,7 +4,6 @@ #include <memory> -#include "common/make_unique.h" #include "common/logging/log.h" #include "core/core.h" @@ -74,8 +73,8 @@ void Stop() { /// Initialize the core void Init() { - g_sys_core = Common::make_unique<ARM_DynCom>(USER32MODE); - g_app_core = Common::make_unique<ARM_DynCom>(USER32MODE); + g_sys_core = std::make_unique<ARM_DynCom>(USER32MODE); + g_app_core = std::make_unique<ARM_DynCom>(USER32MODE); LOG_DEBUG(Core, "Initialized OK"); } diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp index 961264fe5..1d9eaefcb 100644 --- a/src/core/file_sys/archive_extsavedata.cpp +++ b/src/core/file_sys/archive_extsavedata.cpp @@ -3,12 +3,12 @@ // Refer to the license.txt file included. #include <algorithm> +#include <memory> #include <vector> #include "common/common_types.h" #include "common/file_util.h" #include "common/logging/log.h" -#include "common/make_unique.h" #include "common/string_util.h" #include "core/file_sys/archive_extsavedata.h" @@ -84,7 +84,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(cons ErrorSummary::InvalidState, ErrorLevel::Status); } } - auto archive = Common::make_unique<DiskArchive>(fullpath); + auto archive = std::make_unique<DiskArchive>(fullpath); return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); } diff --git a/src/core/file_sys/archive_extsavedata.h b/src/core/file_sys/archive_extsavedata.h index 287a6fee1..e9a72850d 100644 --- a/src/core/file_sys/archive_extsavedata.h +++ b/src/core/file_sys/archive_extsavedata.h @@ -45,13 +45,14 @@ public: void WriteIcon(const Path& path, const u8* icon_data, size_t icon_size); private: + bool shared; ///< Whether this archive represents an ExtSaveData archive or a SharedExtSaveData archive + /** * This holds the full directory path for this archive, it is only set after a successful call * to Open, this is formed as <base extsavedatapath>/<type>/<high>/<low>. * See GetExtSaveDataPath for the code that extracts this data from an archive path. */ std::string mount_point; - bool shared; ///< Whether this archive represents an ExtSaveData archive or a SharedExtSaveData archive }; /** diff --git a/src/core/file_sys/archive_romfs.cpp b/src/core/file_sys/archive_romfs.cpp index a9a29ebde..38828b546 100644 --- a/src/core/file_sys/archive_romfs.cpp +++ b/src/core/file_sys/archive_romfs.cpp @@ -7,7 +7,6 @@ #include "common/common_types.h" #include "common/logging/log.h" -#include "common/make_unique.h" #include "core/file_sys/archive_romfs.h" #include "core/file_sys/ivfc_archive.h" @@ -25,7 +24,7 @@ ArchiveFactory_RomFS::ArchiveFactory_RomFS(Loader::AppLoader& app_loader) { } ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_RomFS::Open(const Path& path) { - auto archive = Common::make_unique<IVFCArchive>(romfs_file, data_offset, data_size); + auto archive = std::make_unique<IVFCArchive>(romfs_file, data_offset, data_size); return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); } diff --git a/src/core/file_sys/archive_savedata.cpp b/src/core/file_sys/archive_savedata.cpp index fe020d21c..fd5711e14 100644 --- a/src/core/file_sys/archive_savedata.cpp +++ b/src/core/file_sys/archive_savedata.cpp @@ -3,11 +3,11 @@ // Refer to the license.txt file included. #include <algorithm> +#include <memory> #include "common/common_types.h" #include "common/file_util.h" #include "common/logging/log.h" -#include "common/make_unique.h" #include "common/string_util.h" #include "core/file_sys/archive_savedata.h" @@ -53,7 +53,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const P ErrorSummary::InvalidState, ErrorLevel::Status); } - auto archive = Common::make_unique<DiskArchive>(std::move(concrete_mount_point)); + auto archive = std::make_unique<DiskArchive>(std::move(concrete_mount_point)); return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); } diff --git a/src/core/file_sys/archive_savedatacheck.cpp b/src/core/file_sys/archive_savedatacheck.cpp index 3db11c500..9f65e5455 100644 --- a/src/core/file_sys/archive_savedatacheck.cpp +++ b/src/core/file_sys/archive_savedatacheck.cpp @@ -3,12 +3,12 @@ // Refer to the license.txt file included. #include <algorithm> +#include <memory> #include <vector> #include "common/common_types.h" #include "common/file_util.h" #include "common/logging/log.h" -#include "common/make_unique.h" #include "common/string_util.h" #include "core/file_sys/archive_savedatacheck.h" @@ -44,7 +44,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveDataCheck::Open(co } auto size = file->GetSize(); - auto archive = Common::make_unique<IVFCArchive>(file, 0, size); + auto archive = std::make_unique<IVFCArchive>(file, 0, size); return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); } diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp index 657221cbf..9b218af58 100644 --- a/src/core/file_sys/archive_sdmc.cpp +++ b/src/core/file_sys/archive_sdmc.cpp @@ -3,10 +3,10 @@ // Refer to the license.txt file included. #include <algorithm> +#include <memory> #include "common/file_util.h" #include "common/logging/log.h" -#include "common/make_unique.h" #include "core/file_sys/archive_sdmc.h" #include "core/file_sys/disk_archive.h" @@ -36,7 +36,7 @@ bool ArchiveFactory_SDMC::Initialize() { } ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMC::Open(const Path& path) { - auto archive = Common::make_unique<DiskArchive>(sdmc_directory); + auto archive = std::make_unique<DiskArchive>(sdmc_directory); return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); } diff --git a/src/core/file_sys/archive_systemsavedata.cpp b/src/core/file_sys/archive_systemsavedata.cpp index e1780de2f..1bcc228a1 100644 --- a/src/core/file_sys/archive_systemsavedata.cpp +++ b/src/core/file_sys/archive_systemsavedata.cpp @@ -3,11 +3,11 @@ // Refer to the license.txt file included. #include <algorithm> +#include <memory> #include <vector> #include "common/common_types.h" #include "common/file_util.h" -#include "common/make_unique.h" #include "common/string_util.h" #include "core/file_sys/archive_systemsavedata.h" @@ -59,7 +59,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(c return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, ErrorSummary::InvalidState, ErrorLevel::Status); } - auto archive = Common::make_unique<DiskArchive>(fullpath); + auto archive = std::make_unique<DiskArchive>(fullpath); return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); } diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp index 8e4ea01c5..489cc96fb 100644 --- a/src/core/file_sys/disk_archive.cpp +++ b/src/core/file_sys/disk_archive.cpp @@ -4,11 +4,11 @@ #include <algorithm> #include <cstdio> +#include <memory> #include "common/common_types.h" #include "common/file_util.h" #include "common/logging/log.h" -#include "common/make_unique.h" #include "core/file_sys/disk_archive.h" @@ -19,7 +19,7 @@ namespace FileSys { ResultVal<std::unique_ptr<FileBackend>> DiskArchive::OpenFile(const Path& path, const Mode mode) const { LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); - auto file = Common::make_unique<DiskFile>(*this, path, mode); + auto file = std::make_unique<DiskFile>(*this, path, mode); ResultCode result = file->Open(); if (result.IsError()) return result; @@ -83,7 +83,7 @@ bool DiskArchive::RenameDirectory(const Path& src_path, const Path& dest_path) c std::unique_ptr<DirectoryBackend> DiskArchive::OpenDirectory(const Path& path) const { LOG_DEBUG(Service_FS, "called path=%s", path.DebugStr().c_str()); - auto directory = Common::make_unique<DiskDirectory>(*this, path); + auto directory = std::make_unique<DiskDirectory>(*this, path); if (!directory->Open()) return nullptr; return std::move(directory); @@ -132,7 +132,7 @@ ResultCode DiskFile::Open() { // Open the file in binary mode, to avoid problems with CR/LF on Windows systems mode_string += "b"; - file = Common::make_unique<FileUtil::IOFile>(path, mode_string.c_str()); + file = std::make_unique<FileUtil::IOFile>(path, mode_string.c_str()); if (file->IsOpen()) return RESULT_SUCCESS; return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, ErrorLevel::Status); diff --git a/src/core/file_sys/ivfc_archive.cpp b/src/core/file_sys/ivfc_archive.cpp index a8e9a72ef..c61791ef7 100644 --- a/src/core/file_sys/ivfc_archive.cpp +++ b/src/core/file_sys/ivfc_archive.cpp @@ -7,7 +7,6 @@ #include "common/common_types.h" #include "common/logging/log.h" -#include "common/make_unique.h" #include "core/file_sys/ivfc_archive.h" @@ -21,7 +20,7 @@ std::string IVFCArchive::GetName() const { } ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path, const Mode mode) const { - return MakeResult<std::unique_ptr<FileBackend>>(Common::make_unique<IVFCFile>(romfs_file, data_offset, data_size)); + return MakeResult<std::unique_ptr<FileBackend>>(std::make_unique<IVFCFile>(romfs_file, data_offset, data_size)); } ResultCode IVFCArchive::DeleteFile(const Path& path) const { @@ -58,7 +57,7 @@ bool IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) c } std::unique_ptr<DirectoryBackend> IVFCArchive::OpenDirectory(const Path& path) const { - return Common::make_unique<IVFCDirectory>(); + return std::make_unique<IVFCDirectory>(); } u64 IVFCArchive::GetFreeBytes() const { diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index 3a2445241..ae0c116ef 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -60,6 +60,59 @@ const u32 R15_REGISTER = 15; const u32 CPSR_REGISTER = 25; const u32 FPSCR_REGISTER = 58; +// For sample XML files see the GDB source /gdb/features +// GDB also wants the l character at the start +// This XML defines what the registers are for this specific ARM device +static const char* target_xml = +R"(l<?xml version="1.0"?> +<!DOCTYPE target SYSTEM "gdb-target.dtd"> +<target version="1.0"> + <feature name="org.gnu.gdb.arm.core"> + <reg name="r0" bitsize="32"/> + <reg name="r1" bitsize="32"/> + <reg name="r2" bitsize="32"/> + <reg name="r3" bitsize="32"/> + <reg name="r4" bitsize="32"/> + <reg name="r5" bitsize="32"/> + <reg name="r6" bitsize="32"/> + <reg name="r7" bitsize="32"/> + <reg name="r8" bitsize="32"/> + <reg name="r9" bitsize="32"/> + <reg name="r10" bitsize="32"/> + <reg name="r11" bitsize="32"/> + <reg name="r12" bitsize="32"/> + <reg name="sp" bitsize="32" type="data_ptr"/> + <reg name="lr" bitsize="32"/> + <reg name="pc" bitsize="32" type="code_ptr"/> + + <!-- The CPSR is register 25, rather than register 16, because + the FPA registers historically were placed between the PC + and the CPSR in the "g" packet. --> + + <reg name="cpsr" bitsize="32" regnum="25"/> + </feature> + <feature name="org.gnu.gdb.arm.vfp"> + <reg name="d0" bitsize="64" type="float"/> + <reg name="d1" bitsize="64" type="float"/> + <reg name="d2" bitsize="64" type="float"/> + <reg name="d3" bitsize="64" type="float"/> + <reg name="d4" bitsize="64" type="float"/> + <reg name="d5" bitsize="64" type="float"/> + <reg name="d6" bitsize="64" type="float"/> + <reg name="d7" bitsize="64" type="float"/> + <reg name="d8" bitsize="64" type="float"/> + <reg name="d9" bitsize="64" type="float"/> + <reg name="d10" bitsize="64" type="float"/> + <reg name="d11" bitsize="64" type="float"/> + <reg name="d12" bitsize="64" type="float"/> + <reg name="d13" bitsize="64" type="float"/> + <reg name="d14" bitsize="64" type="float"/> + <reg name="d15" bitsize="64" type="float"/> + <reg name="fpscr" bitsize="32" type="int" group="float"/> + </feature> +</target> +)"; + namespace GDBStub { static int gdbserver_socket = -1; @@ -211,7 +264,7 @@ static u8 ReadByte() { } /// Calculate the checksum of the current command buffer. -static u8 CalculateChecksum(u8 *buffer, u32 length) { +static u8 CalculateChecksum(u8* buffer, u32 length) { return static_cast<u8>(std::accumulate(buffer, buffer + length, 0, std::plus<u8>())); } @@ -353,8 +406,15 @@ static void SendReply(const char* reply) { static void HandleQuery() { LOG_DEBUG(Debug_GDBStub, "gdb: query '%s'\n", command_buffer + 1); - if (!strcmp(reinterpret_cast<const char*>(command_buffer + 1), "TStatus")) { + const char* query = reinterpret_cast<const char*>(command_buffer + 1); + + if (strcmp(query, "TStatus") == 0 ) { SendReply("T0"); + } else if (strncmp(query, "Supported:", strlen("Supported:")) == 0) { + // PacketSize needs to be large enough for target xml + SendReply("PacketSize=800;qXfer:features:read+"); + } else if (strncmp(query, "Xfer:features:read:target.xml:", strlen("Xfer:features:read:target.xml:")) == 0) { + SendReply(target_xml); } else { SendReply(""); } @@ -469,7 +529,7 @@ static void ReadRegister() { id |= HexCharToValue(command_buffer[2]); } - if (id >= R0_REGISTER && id <= R15_REGISTER) { + if (id <= R15_REGISTER) { IntToGdbHex(reply, Core::g_app_core->GetReg(id)); } else if (id == CPSR_REGISTER) { IntToGdbHex(reply, Core::g_app_core->GetCPSR()); @@ -491,29 +551,25 @@ static void ReadRegisters() { memset(buffer, 0, sizeof(buffer)); u8* bufptr = buffer; - for (int i = 0, reg = 0; reg <= FPSCR_REGISTER; i++, reg++) { - if (reg <= R15_REGISTER) { - IntToGdbHex(bufptr + i * CHAR_BIT, Core::g_app_core->GetReg(reg)); - } else if (reg == CPSR_REGISTER) { - IntToGdbHex(bufptr + i * CHAR_BIT, Core::g_app_core->GetCPSR()); - } else if (reg == CPSR_REGISTER - 1) { - // Dummy FPA register, ignore - IntToGdbHex(bufptr + i * CHAR_BIT, 0); - } else if (reg < CPSR_REGISTER) { - // Dummy FPA registers, ignore - IntToGdbHex(bufptr + i * CHAR_BIT, 0); - IntToGdbHex(bufptr + (i + 1) * CHAR_BIT, 0); - IntToGdbHex(bufptr + (i + 2) * CHAR_BIT, 0); - i += 2; - } else if (reg > CPSR_REGISTER && reg < FPSCR_REGISTER) { - IntToGdbHex(bufptr + i * CHAR_BIT, Core::g_app_core->GetVFPReg(reg - CPSR_REGISTER - 1)); - IntToGdbHex(bufptr + (i + 1) * CHAR_BIT, 0); - i++; - } else if (reg == FPSCR_REGISTER) { - IntToGdbHex(bufptr + i * CHAR_BIT, Core::g_app_core->GetVFPSystemReg(VFP_FPSCR)); - } + + for (int reg = 0; reg <= R15_REGISTER; reg++) { + IntToGdbHex(bufptr + reg * CHAR_BIT, Core::g_app_core->GetReg(reg)); } + bufptr += (16 * CHAR_BIT); + + IntToGdbHex(bufptr, Core::g_app_core->GetCPSR()); + + bufptr += CHAR_BIT; + + for (int reg = 0; reg <= 31; reg++) { + IntToGdbHex(bufptr + reg * CHAR_BIT, Core::g_app_core->GetVFPReg(reg)); + } + + bufptr += (32 * CHAR_BIT); + + IntToGdbHex(bufptr, Core::g_app_core->GetVFPSystemReg(VFP_FPSCR)); + SendReply(reinterpret_cast<char*>(buffer)); } @@ -528,7 +584,7 @@ static void WriteRegister() { id |= HexCharToValue(command_buffer[2]); } - if (id >= R0_REGISTER && id <= R15_REGISTER) { + if (id <= R15_REGISTER) { Core::g_app_core->SetReg(id, GdbHexToInt(buffer_ptr)); } else if (id == CPSR_REGISTER) { Core::g_app_core->SetCPSR(GdbHexToInt(buffer_ptr)); @@ -885,6 +941,12 @@ void Init(u16 port) { LOG_ERROR(Debug_GDBStub, "Failed to create gdb socket"); } + // Set socket to SO_REUSEADDR so it can always bind on the same port + int reuse_enabled = 1; + if (setsockopt(tmpsock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_enabled, sizeof(reuse_enabled)) < 0) { + LOG_ERROR(Debug_GDBStub, "Failed to set gdb socket option"); + } + const sockaddr* server_addr = reinterpret_cast<const sockaddr*>(&saddr_server); socklen_t server_addrlen = sizeof(saddr_server); if (bind(tmpsock, server_addr, server_addrlen) < 0) { diff --git a/src/core/hle/applets/mii_selector.cpp b/src/core/hle/applets/mii_selector.cpp index 708d2f630..5191c821d 100644 --- a/src/core/hle/applets/mii_selector.cpp +++ b/src/core/hle/applets/mii_selector.cpp @@ -55,6 +55,11 @@ ResultCode MiiSelector::StartImpl(const Service::APT::AppletStartupParameter& pa // TODO(Subv): Set the expected fields in the response buffer before resending it to the application. // TODO(Subv): Reverse the parameter format for the Mii Selector + if(parameter.buffer_size >= sizeof(u32)) { + // TODO: defaults return no error, but garbage in other unknown fields + memset(parameter.data, 0, sizeof(u32)); + } + // Let the application know that we're closing Service::APT::MessageParameter message; message.buffer_size = parameter.buffer_size; diff --git a/src/core/hle/applets/mii_selector.h b/src/core/hle/applets/mii_selector.h index 6a3e7c8eb..c02dded4a 100644 --- a/src/core/hle/applets/mii_selector.h +++ b/src/core/hle/applets/mii_selector.h @@ -16,6 +16,50 @@ namespace HLE { namespace Applets { +struct MiiConfig { + u8 unk_000; + u8 unk_001; + u8 unk_002; + u8 unk_003; + u8 unk_004; + INSERT_PADDING_BYTES(3); + u16 unk_008; + INSERT_PADDING_BYTES(0x8C - 0xA); + u8 unk_08C; + INSERT_PADDING_BYTES(3); + u16 unk_090; + INSERT_PADDING_BYTES(2); + u32 unk_094; + u16 unk_098; + u8 unk_09A[0x64]; + u8 unk_0FE; + u8 unk_0FF; + u32 unk_100; +}; + +static_assert(sizeof(MiiConfig) == 0x104, "MiiConfig structure has incorrect size"); +#define ASSERT_REG_POSITION(field_name, position) static_assert(offsetof(MiiConfig, field_name) == position, "Field "#field_name" has invalid position") +ASSERT_REG_POSITION(unk_008, 0x08); +ASSERT_REG_POSITION(unk_08C, 0x8C); +ASSERT_REG_POSITION(unk_090, 0x90); +ASSERT_REG_POSITION(unk_094, 0x94); +ASSERT_REG_POSITION(unk_0FE, 0xFE); +#undef ASSERT_REG_POSITION + +struct MiiResult { + u32 result_code; + u8 unk_04; + INSERT_PADDING_BYTES(7); + u8 unk_0C[0x60]; + u8 unk_6C[0x16]; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size"); +#define ASSERT_REG_POSITION(field_name, position) static_assert(offsetof(MiiResult, field_name) == position, "Field "#field_name" has invalid position") +ASSERT_REG_POSITION(unk_0C, 0x0C); +ASSERT_REG_POSITION(unk_6C, 0x6C); +#undef ASSERT_REG_POSITION + class MiiSelector final : public Applet { public: MiiSelector(Service::APT::AppletId id); diff --git a/src/core/hle/config_mem.cpp b/src/core/hle/config_mem.cpp index b1a72dc0c..ccd73cfcb 100644 --- a/src/core/hle/config_mem.cpp +++ b/src/core/hle/config_mem.cpp @@ -3,13 +3,6 @@ // Refer to the license.txt file included. #include <cstring> - -#include "common/assert.h" -#include "common/common_types.h" -#include "common/common_funcs.h" - -#include "core/core.h" -#include "core/memory.h" #include "core/hle/config_mem.h" //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp index 96d3ec05b..5c5373517 100644 --- a/src/core/hle/hle.cpp +++ b/src/core/hle/hle.cpp @@ -8,8 +8,6 @@ #include "core/arm/arm_interface.h" #include "core/core.h" #include "core/hle/hle.h" -#include "core/hle/config_mem.h" -#include "core/hle/shared_page.h" #include "core/hle/service/service.h" //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 24b266eae..0546f6e16 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -2,10 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <memory> + #include "common/assert.h" #include "common/common_funcs.h" #include "common/logging/log.h" -#include "common/make_unique.h" #include "core/hle/kernel/memory.h" #include "core/hle/kernel/process.h" diff --git a/src/core/hle/kernel/session.h b/src/core/hle/kernel/session.h index adaffcafe..6ddaf970e 100644 --- a/src/core/hle/kernel/session.h +++ b/src/core/hle/kernel/session.h @@ -16,23 +16,23 @@ namespace IPC { -inline u32 MakeHeader(u16 command_id, unsigned int regular_params, unsigned int translate_params) { +constexpr u32 MakeHeader(u16 command_id, unsigned int regular_params, unsigned int translate_params) { return ((u32)command_id << 16) | (((u32)regular_params & 0x3F) << 6) | (((u32)translate_params & 0x3F) << 0); } -inline u32 MoveHandleDesc(unsigned int num_handles = 1) { +constexpr u32 MoveHandleDesc(unsigned int num_handles = 1) { return 0x0 | ((num_handles - 1) << 26); } -inline u32 CopyHandleDesc(unsigned int num_handles = 1) { +constexpr u32 CopyHandleDesc(unsigned int num_handles = 1) { return 0x10 | ((num_handles - 1) << 26); } -inline u32 CallingPidDesc() { +constexpr u32 CallingPidDesc() { return 0x20; } -inline u32 StaticBufferDesc(u32 size, unsigned int buffer_id) { +constexpr u32 StaticBufferDesc(u32 size, unsigned int buffer_id) { return 0x2 | (size << 14) | ((buffer_id & 0xF) << 10); } @@ -42,7 +42,7 @@ enum MappedBufferPermissions { RW = R | W, }; -inline u32 MappedBufferDesc(u32 size, MappedBufferPermissions perms) { +constexpr u32 MappedBufferDesc(u32 size, MappedBufferPermissions perms) { return 0x8 | (size << 4) | (u32)perms; } diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 0cb76ba1c..3fc1ab4ee 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -5,7 +5,6 @@ #pragma once #include <new> -#include <type_traits> #include <utility> #include "common/assert.h" @@ -18,12 +17,14 @@ /// Detailed description of the error. This listing is likely incomplete. enum class ErrorDescription : u32 { Success = 0, + OS_InvalidBufferDescriptor = 48, WrongAddress = 53, FS_NotFound = 120, FS_AlreadyExists = 190, FS_InvalidOpenFlags = 230, 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 FS_InvalidPath = 702, InvalidSection = 1000, TooLarge = 1001, diff --git a/src/core/hle/service/ac_u.cpp b/src/core/hle/service/ac_u.cpp index d67325506..5241dd3e7 100644 --- a/src/core/hle/service/ac_u.cpp +++ b/src/core/hle/service/ac_u.cpp @@ -3,6 +3,8 @@ // Refer to the license.txt file included. #include "common/logging/log.h" + +#include "core/hle/kernel/event.h" #include "core/hle/service/ac_u.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -11,6 +13,28 @@ namespace AC_U { /** + * AC_U::CloseAsync service function + * Inputs: + * 1 : Always 0x20 + * 3 : Always 0 + * 4 : Event handle, should be signaled when AC connection is closed + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void CloseAsync(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + auto evt = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); + + if (evt) { + evt->name = "AC_U:close_event"; + evt->Signal(); + } + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} +/** * AC_U::GetWifiStatus service function * Outputs: * 1 : Result of function, 0 on success, otherwise error code @@ -47,7 +71,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00010000, nullptr, "CreateDefaultConfig"}, {0x00040006, nullptr, "ConnectAsync"}, {0x00050002, nullptr, "GetConnectResult"}, - {0x00080004, nullptr, "CloseAsync"}, + {0x00080004, CloseAsync, "CloseAsync"}, {0x00090002, nullptr, "GetCloseResult"}, {0x000A0000, nullptr, "GetLastErrorCode"}, {0x000D0000, GetWifiStatus, "GetWifiStatus"}, diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 06be9940e..3f71e7f2b 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> + #include "common/logging/log.h" #include "core/hle/service/service.h" @@ -9,30 +11,119 @@ #include "core/hle/service/am/am_app.h" #include "core/hle/service/am/am_net.h" #include "core/hle/service/am/am_sys.h" +#include "core/hle/service/am/am_u.h" namespace Service { namespace AM { -void TitleIDListGetTotal(Service::Interface* self) { +static std::array<u32, 3> am_content_count = { 0, 0, 0 }; +static std::array<u32, 3> am_titles_count = { 0, 0, 0 }; +static std::array<u32, 3> am_titles_list_count = { 0, 0, 0 }; +static u32 am_ticket_count = 0; +static u32 am_ticket_list_count = 0; + +void GetTitleCount(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 media_type = cmd_buff[1] & 0xFF; cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + cmd_buff[2] = am_titles_count[media_type]; + LOG_WARNING(Service_AM, "(STUBBED) media_type=%u, title_count=0x%08x", media_type, am_titles_count[media_type]); +} + +void FindContentInfos(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 media_type = cmd_buff[1] & 0xFF; + u64 title_id = (static_cast<u64>(cmd_buff[3]) << 32) | cmd_buff[2]; + u32 content_ids_pointer = cmd_buff[6]; + u32 content_info_pointer = cmd_buff[8]; + + am_content_count[media_type] = cmd_buff[4]; - LOG_WARNING(Service_AM, "(STUBBED) media_type %u", media_type); + cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_WARNING(Service_AM, "(STUBBED) media_type=%u, title_id=0x%016llx, content_cound=%u, content_ids_pointer=0x%08x, content_info_pointer=0x%08x", + media_type, title_id, am_content_count[media_type], content_ids_pointer, content_info_pointer); } -void GetTitleIDList(Service::Interface* self) { +void ListContentInfos(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 num_titles = cmd_buff[1]; + u32 media_type = cmd_buff[2] & 0xFF; - u32 addr = cmd_buff[4]; + u64 title_id = (static_cast<u64>(cmd_buff[4]) << 32) | cmd_buff[3]; + u32 start_index = cmd_buff[5]; + u32 content_info_pointer = cmd_buff[7]; + + am_content_count[media_type] = cmd_buff[1]; cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + cmd_buff[2] = am_content_count[media_type]; + LOG_WARNING(Service_AM, "(STUBBED) media_type=%u, content_count=%u, title_id=0x%016" PRIx64 ", start_index=0x%08x, content_info_pointer=0x%08X", + media_type, am_content_count[media_type], title_id, start_index, content_info_pointer); +} + +void DeleteContents(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 media_type = cmd_buff[1] & 0xFF; + u64 title_id = (static_cast<u64>(cmd_buff[3]) << 32) | cmd_buff[2]; + u32 content_ids_pointer = cmd_buff[6]; - LOG_WARNING(Service_AM, "(STUBBED) Requested %u titles from media type %u. Address=0x%08X", num_titles, media_type, addr); + am_content_count[media_type] = cmd_buff[4]; + + cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_WARNING(Service_AM, "(STUBBED) media_type=%u, title_id=0x%016" PRIx64 ", content_count=%u, content_ids_pointer=0x%08x", + media_type, title_id, am_content_count[media_type], content_ids_pointer); +} + +void GetTitleList(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 media_type = cmd_buff[2] & 0xFF; + u32 title_ids_output_pointer = cmd_buff[4]; + + am_titles_list_count[media_type] = cmd_buff[1]; + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = am_titles_list_count[media_type]; + LOG_WARNING(Service_AM, "(STUBBED) media_type=%u, titles_list_count=0x%08X, title_ids_output_pointer=0x%08X", + media_type, am_titles_list_count[media_type], title_ids_output_pointer); +} + +void GetTitleInfo(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 media_type = cmd_buff[1] & 0xFF; + u32 title_id_list_pointer = cmd_buff[4]; + u32 title_list_pointer = cmd_buff[6]; + + am_titles_count[media_type] = cmd_buff[2]; + + cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_WARNING(Service_AM, "(STUBBED) media_type=%u, total_titles=0x%08X, title_id_list_pointer=0x%08X, title_list_pointer=0x%08X", + media_type, am_titles_count[media_type], title_id_list_pointer, title_list_pointer); +} + +void GetDataTitleInfos(Service::Interface* self) { + GetTitleInfo(self); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void ListDataTitleTicketInfos(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u64 title_id = (static_cast<u64>(cmd_buff[3]) << 32) | cmd_buff[2]; + u32 start_index = cmd_buff[4]; + u32 ticket_info_pointer = cmd_buff[6]; + + am_ticket_count = cmd_buff[1]; + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = am_ticket_count; + LOG_WARNING(Service_AM, "(STUBBED) ticket_count=0x%08X, title_id=0x%016" PRIx64 ", start_index=0x%08X, ticket_info_pointer=0x%08X", + am_ticket_count, title_id, start_index, ticket_info_pointer); } void GetNumContentInfos(Service::Interface* self) { @@ -40,16 +131,47 @@ void GetNumContentInfos(Service::Interface* self) { cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 1; // Number of content infos plus one - LOG_WARNING(Service_AM, "(STUBBED) called"); } +void DeleteTicket(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u64 title_id = (static_cast<u64>(cmd_buff[2]) << 32) | cmd_buff[1]; + + cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_WARNING(Service_AM, "(STUBBED) called title_id=0x%016" PRIx64 "",title_id); +} + +void GetTicketCount(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = am_ticket_count; + LOG_WARNING(Service_AM, "(STUBBED) called ticket_count=0x%08x",am_ticket_count); +} + +void GetTicketList(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 num_of_skip = cmd_buff[2]; + u32 ticket_list_pointer = cmd_buff[4]; + + am_ticket_list_count = cmd_buff[1]; + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = am_ticket_list_count; + LOG_WARNING(Service_AM, "(STUBBED) ticket_list_count=0x%08x, num_of_skip=0x%08x, ticket_list_pointer=0x%08x", + am_ticket_list_count, num_of_skip, ticket_list_pointer); +} + void Init() { using namespace Kernel; AddService(new AM_APP_Interface); AddService(new AM_NET_Interface); AddService(new AM_SYS_Interface); + AddService(new AM_U_Interface); } void Shutdown() { diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 15e63bc7b..5676cdd5f 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -11,7 +11,7 @@ class Interface; namespace AM { /** - * AM::TitleIDListGetTotal service function + * AM::GetTitleCount service function * Gets the number of installed titles in the requested media type * Inputs: * 0 : Command header (0x00010040) @@ -20,36 +20,140 @@ namespace AM { * 1 : Result, 0 on success, otherwise error code * 2 : The number of titles in the requested media type */ -void TitleIDListGetTotal(Service::Interface* self); +void GetTitleCount(Service::Interface* self); /** - * AM::GetTitleIDList service function + * AM::FindContentInfos service function + * Inputs: + * 1 : MediaType + * 2-3 : u64, Title ID + * 4 : Content count + * 6 : Content IDs pointer + * 8 : Content Infos pointer + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void FindContentInfos(Service::Interface* self); + +/** + * AM::ListContentInfos service function + * Inputs: + * 1 : Content count + * 2 : MediaType + * 3-4 : u64, Title ID + * 5 : Start Index + * 7 : Content Infos pointer + * Outputs: + * 1 : Result, 0 on success, otherwise error code + * 2 : Number of content infos returned + */ +void ListContentInfos(Service::Interface* self); + +/** + * AM::DeleteContents service function + * Inputs: + * 1 : MediaType + * 2-3 : u64, Title ID + * 4 : Content count + * 6 : Content IDs pointer + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void DeleteContents(Service::Interface* self); + +/** + * AM::GetTitleList service function * Loads information about the desired number of titles from the desired media type into an array * Inputs: - * 0 : Command header (0x00020082) - * 1 : The maximum number of titles to load + * 1 : Title count * 2 : Media type to load the titles from - * 3 : Descriptor of the output buffer pointer - * 4 : Address of the output buffer + * 4 : Title IDs output pointer * Outputs: * 1 : Result, 0 on success, otherwise error code * 2 : The number of titles loaded from the requested media type */ -void GetTitleIDList(Service::Interface* self); +void GetTitleList(Service::Interface* self); + +/** + * AM::GetTitleInfo service function + * Inputs: + * 1 : u8 Mediatype + * 2 : Total titles + * 4 : TitleIDList pointer + * 6 : TitleList pointer + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void GetTitleInfo(Service::Interface* self); + +/** + * AM::GetDataTitleInfos service function + * Wrapper for AM::GetTitleInfo + * Inputs: + * 1 : u8 Mediatype + * 2 : Total titles + * 4 : TitleIDList pointer + * 6 : TitleList pointer + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void GetDataTitleInfos(Service::Interface* self); + +/** + * AM::ListDataTitleTicketInfos service function + * Inputs: + * 1 : Ticket count + * 2-3 : u64, Title ID + * 4 : Start Index? + * 5 : (TicketCount * 24) << 8 | 0x4 + * 6 : Ticket Infos pointer + * Outputs: + * 1 : Result, 0 on success, otherwise error code + * 2 : Number of ticket infos returned + */ +void ListDataTitleTicketInfos(Service::Interface* self); /** * AM::GetNumContentInfos service function * Inputs: * 0 : Command header (0x100100C0) - * 1 : Unknown - * 2 : Unknown - * 3 : Unknown + * 1 : MediaType + * 2-3 : u64, Title ID * Outputs: * 1 : Result, 0 on success, otherwise error code * 2 : Number of content infos plus one */ void GetNumContentInfos(Service::Interface* self); +/** + * AM::DeleteTicket service function + * Inputs: + * 1-2 : u64, Title ID + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void DeleteTicket(Service::Interface* self); + +/** + * AM::GetTicketCount service function + * Outputs: + * 1 : Result, 0 on success, otherwise error code + * 2 : Total titles + */ +void GetTicketCount(Service::Interface* self); + +/** + * AM::GetTicketList service function + * Inputs: + * 1 : Number of TicketList + * 2 : Number to skip + * 4 : TicketList pointer + * Outputs: + * 1 : Result, 0 on success, otherwise error code + * 2 : Total TicketList + */ +void GetTicketList(Service::Interface* self); + /// Initialize AM service void Init(); diff --git a/src/core/hle/service/am/am_app.cpp b/src/core/hle/service/am/am_app.cpp index 16c76a1eb..d27b3defd 100644 --- a/src/core/hle/service/am/am_app.cpp +++ b/src/core/hle/service/am/am_app.cpp @@ -9,14 +9,14 @@ namespace Service { namespace AM { const Interface::FunctionInfo FunctionTable[] = { - {0x100100C0, GetNumContentInfos, "GetNumContentInfos"}, - {0x10020104, nullptr, "FindContentInfos"}, - {0x10030142, nullptr, "ListContentInfos"}, - {0x10040102, nullptr, "DeleteContents"}, - {0x10050084, nullptr, "GetDataTitleInfos"}, - {0x10070102, nullptr, "ListDataTitleTicketInfos"}, - {0x100900C0, nullptr, "IsDataTitleInUse"}, - {0x100A0000, nullptr, "IsExternalTitleDatabaseInitialized"}, + {0x100100C0, GetNumContentInfos, "GetNumContentInfos"}, + {0x10020104, FindContentInfos, "FindContentInfos"}, + {0x10030142, ListContentInfos, "ListContentInfos"}, + {0x10040102, DeleteContents, "DeleteContents"}, + {0x10050084, GetDataTitleInfos, "GetDataTitleInfos"}, + {0x10070102, ListDataTitleTicketInfos, "ListDataTitleTicketInfos"}, + {0x100900C0, nullptr, "IsDataTitleInUse"}, + {0x100A0000, nullptr, "IsExternalTitleDatabaseInitialized"}, }; AM_APP_Interface::AM_APP_Interface() { diff --git a/src/core/hle/service/am/am_net.cpp b/src/core/hle/service/am/am_net.cpp index 065e04118..e75755245 100644 --- a/src/core/hle/service/am/am_net.cpp +++ b/src/core/hle/service/am/am_net.cpp @@ -9,16 +9,20 @@ namespace Service { namespace AM { const Interface::FunctionInfo FunctionTable[] = { - {0x00010040, TitleIDListGetTotal, "TitleIDListGetTotal"}, - {0x00020082, GetTitleIDList, "GetTitleIDList"}, - {0x00030084, nullptr, "ListTitles"}, + {0x00010040, GetTitleCount, "GetTitleCount"}, + {0x00020082, GetTitleList, "GetTitleList"}, + {0x00030084, GetTitleInfo, "GetTitleInfo"}, {0x000400C0, nullptr, "DeleteApplicationTitle"}, {0x000500C0, nullptr, "GetTitleProductCode"}, - {0x00080000, nullptr, "TitleIDListGetTotal3"}, - {0x00090082, nullptr, "GetTitleIDList3"}, + {0x000600C0, nullptr, "GetTitleExtDataId"}, + {0x00070080, DeleteTicket, "DeleteTicket"}, + {0x00080000, GetTicketCount, "GetTicketCount"}, + {0x00090082, GetTicketList, "GetTicketList"}, {0x000A0000, nullptr, "GetDeviceID"}, - {0x000D0084, nullptr, "ListTitles2"}, - {0x00140040, nullptr, "FinishInstallToMedia"}, + {0x000D0084, nullptr, "GetPendingTitleInfo"}, + {0x000E00C0, nullptr, "DeletePendingTitle"}, + {0x00140040, nullptr, "FinalizePendingTitles"}, + {0x00150040, nullptr, "DeleteAllPendingTitles"}, {0x00180080, nullptr, "InitializeTitleDatabase"}, {0x00190040, nullptr, "ReloadDBS"}, {0x001A00C0, nullptr, "GetDSiWareExportSize"}, diff --git a/src/core/hle/service/am/am_sys.cpp b/src/core/hle/service/am/am_sys.cpp index e38812297..8bad5e1c9 100644 --- a/src/core/hle/service/am/am_sys.cpp +++ b/src/core/hle/service/am/am_sys.cpp @@ -9,23 +9,27 @@ namespace Service { namespace AM { const Interface::FunctionInfo FunctionTable[] = { - {0x00010040, TitleIDListGetTotal, "TitleIDListGetTotal"}, - {0x00020082, GetTitleIDList, "GetTitleIDList"}, - {0x00030084, nullptr, "ListTitles"}, + {0x00010040, GetTitleCount, "GetTitleCount"}, + {0x00020082, GetTitleList, "GetTitleList"}, + {0x00030084, GetTitleInfo, "GetTitleInfo"}, {0x000400C0, nullptr, "DeleteApplicationTitle"}, {0x000500C0, nullptr, "GetTitleProductCode"}, - {0x00080000, nullptr, "TitleIDListGetTotal3"}, - {0x00090082, nullptr, "GetTitleIDList3"}, + {0x000600C0, nullptr, "GetTitleExtDataId"}, + {0x00070080, DeleteTicket, "DeleteTicket"}, + {0x00080000, GetTicketCount, "GetTicketCount"}, + {0x00090082, GetTicketList, "GetTicketList"}, {0x000A0000, nullptr, "GetDeviceID"}, - {0x000D0084, nullptr, "ListTitles2"}, - {0x00140040, nullptr, "FinishInstallToMedia"}, + {0x000D0084, nullptr, "GetPendingTitleInfo"}, + {0x000E00C0, nullptr, "DeletePendingTitle"}, + {0x00140040, nullptr, "FinalizePendingTitles"}, + {0x00150040, nullptr, "DeleteAllPendingTitles"}, {0x00180080, nullptr, "InitializeTitleDatabase"}, {0x00190040, nullptr, "ReloadDBS"}, {0x001A00C0, nullptr, "GetDSiWareExportSize"}, {0x001B0144, nullptr, "ExportDSiWare"}, {0x001C0084, nullptr, "ImportDSiWare"}, - {0x00230080, nullptr, "TitleIDListGetTotal2"}, - {0x002400C2, nullptr, "GetTitleIDList2"} + {0x00230080, nullptr, "GetPendingTitleCount"}, + {0x002400C2, nullptr, "GetPendingTitleList"} }; AM_SYS_Interface::AM_SYS_Interface() { diff --git a/src/core/hle/service/am/am_u.cpp b/src/core/hle/service/am/am_u.cpp index c0392b754..d583dd9e6 100644 --- a/src/core/hle/service/am/am_u.cpp +++ b/src/core/hle/service/am/am_u.cpp @@ -9,16 +9,20 @@ namespace Service { namespace AM { const Interface::FunctionInfo FunctionTable[] = { - {0x00010040, TitleIDListGetTotal, "TitleIDListGetTotal"}, - {0x00020082, GetTitleIDList, "GetTitleIDList"}, - {0x00030084, nullptr, "ListTitles"}, + {0x00010040, GetTitleCount, "GetTitleCount"}, + {0x00020082, GetTitleList, "GetTitleList"}, + {0x00030084, GetTitleInfo, "GetTitleInfo"}, {0x000400C0, nullptr, "DeleteApplicationTitle"}, {0x000500C0, nullptr, "GetTitleProductCode"}, - {0x00080000, nullptr, "TitleIDListGetTotal3"}, - {0x00090082, nullptr, "GetTitleIDList3"}, + {0x000600C0, nullptr, "GetTitleExtDataId"}, + {0x00070080, DeleteTicket, "DeleteTicket"}, + {0x00080000, GetTicketCount, "GetTicketCount"}, + {0x00090082, GetTicketList, "GetTicketList"}, {0x000A0000, nullptr, "GetDeviceID"}, - {0x000D0084, nullptr, "ListTitles2"}, - {0x00140040, nullptr, "FinishInstallToMedia"}, + {0x000D0084, nullptr, "GetPendingTitleInfo"}, + {0x000E00C0, nullptr, "DeletePendingTitle"}, + {0x00140040, nullptr, "FinalizePendingTitles"}, + {0x00150040, nullptr, "DeleteAllPendingTitles"}, {0x00180080, nullptr, "InitializeTitleDatabase"}, {0x00190040, nullptr, "ReloadDBS"}, {0x001A00C0, nullptr, "GetDSiWareExportSize"}, diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index a49365287..6d72e8188 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -397,6 +397,23 @@ void GetAppletInfo(Service::Interface* self) { LOG_WARNING(Service_APT, "(stubbed) called appid=%u", app_id); } +void GetStartupArgument(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 parameter_size = cmd_buff[1]; + StartupArgumentType startup_argument_type = static_cast<StartupArgumentType>(cmd_buff[2]); + + if (parameter_size >= 0x300) { + LOG_ERROR(Service_APT, "Parameter size is outside the valid range (capped to 0x300): parameter_size=0x%08x", parameter_size); + return; + } + + LOG_WARNING(Service_APT,"(stubbed) called startup_argument_type=%u , parameter_size=0x%08x , parameter_value=0x%08x", + startup_argument_type, parameter_size, Memory::Read32(cmd_buff[41])); + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = (parameter_size > 0) ? 1 : 0; +} + void Init() { AddService(new APT_A_Interface); AddService(new APT_S_Interface); diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 47a97c1a1..668b4a66f 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -67,6 +67,12 @@ enum class AppletId : u32 { Ed2 = 0x402, }; +enum class StartupArgumentType : u32 { + OtherApp = 0, + Restart = 1, + OtherMedia = 2, +}; + /// Send a parameter to the currently-running application, which will read it via ReceiveParameter void SendParameter(const MessageParameter& parameter); @@ -344,6 +350,17 @@ void PreloadLibraryApplet(Service::Interface* self); */ void StartLibraryApplet(Service::Interface* self); +/** + * APT::GetStartupArgument service function + * Inputs: + * 1 : Parameter Size (capped to 0x300) + * 2 : StartupArgumentType + * Outputs: + * 0 : Return header + * 1 : u8, Exists (0 = does not exist, 1 = exists) + */ +void GetStartupArgument(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 0c6a77305..9ff47701a 100644 --- a/src/core/hle/service/apt/apt_a.cpp +++ b/src/core/hle/service/apt/apt_a.cpp @@ -13,9 +13,10 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00020080, Initialize, "Initialize?"}, {0x00030040, Enable, "Enable?"}, {0x00040040, nullptr, "Finalize?"}, - {0x00050040, nullptr, "GetAppletManInfo?"}, - {0x00060040, nullptr, "GetAppletInfo?"}, + {0x00050040, GetAppletManInfo, "GetAppletManInfo"}, + {0x00060040, GetAppletInfo, "GetAppletInfo"}, {0x00090040, IsRegistered, "IsRegistered"}, + {0x000B0040, InquireNotification, "InquireNotification"}, {0x000C0104, SendParameter, "SendParameter"}, {0x000D0080, ReceiveParameter, "ReceiveParameter"}, {0x000E0080, GlanceParameter, "GlanceParameter"}, @@ -24,9 +25,13 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"}, {0x001E0084, StartLibraryApplet, "StartLibraryApplet"}, {0x003B0040, nullptr, "CancelLibraryApplet?"}, + {0x003E0080, nullptr, "ReplySleepQuery"}, {0x00430040, NotifyToWait, "NotifyToWait?"}, {0x00440000, GetSharedFont, "GetSharedFont?"}, {0x004B00C2, AppletUtility, "AppletUtility?"}, + {0x004F0080, SetAppCpuTimeLimit, "SetAppCpuTimeLimit"}, + {0x00500040, GetAppCpuTimeLimit, "GetAppCpuTimeLimit"}, + {0x00510080, GetStartupArgument, "GetStartupArgument"}, {0x00550040, nullptr, "WriteInputToNsState?"}, }; diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp index 7f6e81a63..ca54e593c 100644 --- a/src/core/hle/service/apt/apt_s.cpp +++ b/src/core/hle/service/apt/apt_s.cpp @@ -13,8 +13,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00020080, Initialize, "Initialize"}, {0x00030040, Enable, "Enable"}, {0x00040040, nullptr, "Finalize"}, - {0x00050040, nullptr, "GetAppletManInfo"}, - {0x00060040, nullptr, "GetAppletInfo"}, + {0x00050040, GetAppletManInfo, "GetAppletManInfo"}, + {0x00060040, GetAppletInfo, "GetAppletInfo"}, {0x00070000, nullptr, "GetLastSignaledAppletId"}, {0x00080000, nullptr, "CountRegisteredApplet"}, {0x00090040, nullptr, "IsRegistered"}, @@ -87,9 +87,9 @@ const Interface::FunctionInfo FunctionTable[] = { {0x004C0000, nullptr, "SetFatalErrDispMode"}, {0x004D0080, nullptr, "GetAppletProgramInfo"}, {0x004E0000, nullptr, "HardwareResetAsync"}, - {0x004F0080, nullptr, "SetApplicationCpuTimeLimit"}, - {0x00500040, nullptr, "GetApplicationCpuTimeLimit"}, - {0x00510080, nullptr, "GetStartupArgument"}, + {0x004F0080, SetAppCpuTimeLimit, "SetAppCpuTimeLimit"}, + {0x00500040, GetAppCpuTimeLimit, "GetAppCpuTimeLimit"}, + {0x00510080, GetStartupArgument, "GetStartupArgument"}, {0x00520104, nullptr, "Wrap1"}, {0x00530104, nullptr, "Unwrap1"}, {0x00580002, nullptr, "GetProgramID"}, diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp index b13b51549..0e85c6d08 100644 --- a/src/core/hle/service/apt/apt_u.cpp +++ b/src/core/hle/service/apt/apt_u.cpp @@ -89,7 +89,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x004E0000, nullptr, "HardwareResetAsync"}, {0x004F0080, SetAppCpuTimeLimit, "SetAppCpuTimeLimit"}, {0x00500040, GetAppCpuTimeLimit, "GetAppCpuTimeLimit"}, - {0x00510080, nullptr, "GetStartupArgument"}, + {0x00510080, GetStartupArgument, "GetStartupArgument"}, {0x00520104, nullptr, "Wrap1"}, {0x00530104, nullptr, "Unwrap1"}, {0x00580002, nullptr, "GetProgramID"}, diff --git a/src/core/hle/service/cecd/cecd.cpp b/src/core/hle/service/cecd/cecd.cpp index 6d79ce9b4..50c03495e 100644 --- a/src/core/hle/service/cecd/cecd.cpp +++ b/src/core/hle/service/cecd/cecd.cpp @@ -4,6 +4,7 @@ #include "common/logging/log.h" +#include "core/hle/kernel/event.h" #include "core/hle/service/service.h" #include "core/hle/service/cecd/cecd.h" #include "core/hle/service/cecd/cecd_s.h" @@ -12,14 +13,47 @@ namespace Service { namespace CECD { -void Init() { - using namespace Kernel; +static Kernel::SharedPtr<Kernel::Event> cecinfo_event; +static Kernel::SharedPtr<Kernel::Event> change_state_event; + +void GetCecStateAbbreviated(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = static_cast<u32>(CecStateAbbreviated::CEC_STATE_ABBREV_IDLE); + + LOG_WARNING(Service_CECD, "(STUBBED) called"); +} + +void GetCecInfoEventHandle(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[3] = Kernel::g_handle_table.Create(cecinfo_event).MoveFrom(); // Event handle + + LOG_WARNING(Service_CECD, "(STUBBED) called"); +} + +void GetChangeStateEventHandle(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[3] = Kernel::g_handle_table.Create(change_state_event).MoveFrom(); // Event handle + + LOG_WARNING(Service_CECD, "(STUBBED) called"); +} + +void Init() { AddService(new CECD_S_Interface); AddService(new CECD_U_Interface); + + cecinfo_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "CECD_U::cecinfo_event"); + change_state_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "CECD_U::change_state_event"); } void Shutdown() { + cecinfo_event = nullptr; + change_state_event = nullptr; } } // namespace CECD diff --git a/src/core/hle/service/cecd/cecd.h b/src/core/hle/service/cecd/cecd.h index 9e158521b..435611363 100644 --- a/src/core/hle/service/cecd/cecd.h +++ b/src/core/hle/service/cecd/cecd.h @@ -5,8 +5,49 @@ #pragma once namespace Service { + +class Interface; + namespace CECD { +enum class CecStateAbbreviated { + CEC_STATE_ABBREV_IDLE = 1, ///< Corresponds to CEC_STATE_IDLE + CEC_STATE_ABBREV_NOT_LOCAL = 2, ///< Corresponds to CEC_STATEs *FINISH*, *POST, and OVER_BOSS + CEC_STATE_ABBREV_SCANNING = 3, ///< Corresponds to CEC_STATE_SCANNING + CEC_STATE_ABBREV_WLREADY = 4, ///< Corresponds to CEC_STATE_WIRELESS_READY when some unknown bool is true + CEC_STATE_ABBREV_OTHER = 5, ///< Corresponds to CEC_STATEs besides *FINISH*, *POST, and OVER_BOSS and those listed here +}; + +/** + * GetCecStateAbbreviated service function + * Inputs: + * 0: 0x000E0000 + * Outputs: + * 1: ResultCode + * 2: CecStateAbbreviated + */ +void GetCecStateAbbreviated(Service::Interface* self); + +/** + * GetCecInfoEventHandle service function + * Inputs: + * 0: 0x000F0000 + * Outputs: + * 1: ResultCode + * 3: Event Handle + */ +void GetCecInfoEventHandle(Service::Interface* self); + +/** + * GetChangeStateEventHandle service function + * Inputs: + * 0: 0x00100000 + * Outputs: + * 1: ResultCode + * 3: Event Handle + */ +void GetChangeStateEventHandle(Service::Interface* self); + /// Initialize CECD service(s) void Init(); diff --git a/src/core/hle/service/cecd/cecd_u.cpp b/src/core/hle/service/cecd/cecd_u.cpp index 9b720a738..be6d4d8f6 100644 --- a/src/core/hle/service/cecd/cecd_u.cpp +++ b/src/core/hle/service/cecd/cecd_u.cpp @@ -2,13 +2,17 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/service/cecd/cecd.h" #include "core/hle/service/cecd/cecd_u.h" namespace Service { namespace CECD { static const Interface::FunctionInfo FunctionTable[] = { - { 0x00120104, nullptr, "ReadSavedData" }, + {0x000E0000, GetCecStateAbbreviated, "GetCecStateAbbreviated"}, + {0x000F0000, GetCecInfoEventHandle, "GetCecInfoEventHandle"}, + {0x00100000, GetChangeStateEventHandle, "GetChangeStateEventHandle"}, + {0x00120104, nullptr, "ReadSavedData"}, }; CECD_U_Interface::CECD_U_Interface() { diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 525432957..b9322c55d 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -389,6 +389,10 @@ ResultCode FormatConfig() { res = CreateConfigInfoBlk(0x000F0004, sizeof(CONSOLE_MODEL), 0xC, &CONSOLE_MODEL); if (!res.IsSuccess()) return res; + // 0x00170000 - Unknown + res = CreateConfigInfoBlk(0x00170000, 0x4, 0xE, zero_buffer); + if (!res.IsSuccess()) return res; + // Save the buffer to the file res = UpdateConfigNANDSavegame(); if (!res.IsSuccess()) diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index 606ab99cf..c01806836 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -98,19 +98,6 @@ void GetCountryCodeString(Service::Interface* self); void GetCountryCodeID(Service::Interface* self); /** - * CFG::GetConfigInfoBlk2 service function - * Inputs: - * 0 : 0x00010082 - * 1 : Size - * 2 : Block ID - * 3 : Descriptor for the output buffer - * 4 : Output buffer pointer - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void GetConfigInfoBlk2(Service::Interface* self); - -/** * CFG::SecureInfoGetRegion service function * Inputs: * 1 : None diff --git a/src/core/hle/service/cfg/cfg_i.cpp b/src/core/hle/service/cfg/cfg_i.cpp index 0559a07b2..b18060f6d 100644 --- a/src/core/hle/service/cfg/cfg_i.cpp +++ b/src/core/hle/service/cfg/cfg_i.cpp @@ -9,6 +9,18 @@ namespace Service { namespace CFG { const Interface::FunctionInfo FunctionTable[] = { + // cfg common + {0x00010082, GetConfigInfoBlk2, "GetConfigInfoBlk2"}, + {0x00020000, SecureInfoGetRegion, "SecureInfoGetRegion"}, + {0x00030040, GenHashConsoleUnique, "GenHashConsoleUnique"}, + {0x00040000, GetRegionCanadaUSA, "GetRegionCanadaUSA"}, + {0x00050000, GetSystemModel, "GetSystemModel"}, + {0x00060000, GetModelNintendo2DS, "GetModelNintendo2DS"}, + {0x00070040, nullptr, "WriteToFirstByteCfgSavegame"}, + {0x00080080, nullptr, "GoThroughTable"}, + {0x00090040, GetCountryCodeString, "GetCountryCodeString"}, + {0x000A0040, GetCountryCodeID, "GetCountryCodeID"}, + // cfg:i {0x04010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"}, {0x04020082, nullptr, "SetConfigInfoBlk4"}, {0x04030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, diff --git a/src/core/hle/service/cfg/cfg_s.cpp b/src/core/hle/service/cfg/cfg_s.cpp index b03d290e5..e001f7687 100644 --- a/src/core/hle/service/cfg/cfg_s.cpp +++ b/src/core/hle/service/cfg/cfg_s.cpp @@ -9,10 +9,18 @@ namespace Service { namespace CFG { const Interface::FunctionInfo FunctionTable[] = { + // cfg common {0x00010082, GetConfigInfoBlk2, "GetConfigInfoBlk2"}, {0x00020000, SecureInfoGetRegion, "SecureInfoGetRegion"}, {0x00030040, GenHashConsoleUnique, "GenHashConsoleUnique"}, + {0x00040000, GetRegionCanadaUSA, "GetRegionCanadaUSA"}, {0x00050000, GetSystemModel, "GetSystemModel"}, + {0x00060000, GetModelNintendo2DS, "GetModelNintendo2DS"}, + {0x00070040, nullptr, "WriteToFirstByteCfgSavegame"}, + {0x00080080, nullptr, "GoThroughTable"}, + {0x00090040, GetCountryCodeString, "GetCountryCodeString"}, + {0x000A0040, GetCountryCodeID, "GetCountryCodeID"}, + // cfg:s {0x04010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"}, {0x04020082, nullptr, "SetConfigInfoBlk4"}, {0x04030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, diff --git a/src/core/hle/service/cfg/cfg_u.cpp b/src/core/hle/service/cfg/cfg_u.cpp index 89ae96c9e..606f7b2eb 100644 --- a/src/core/hle/service/cfg/cfg_u.cpp +++ b/src/core/hle/service/cfg/cfg_u.cpp @@ -9,6 +9,7 @@ namespace Service { namespace CFG { const Interface::FunctionInfo FunctionTable[] = { + // cfg common {0x00010082, GetConfigInfoBlk2, "GetConfigInfoBlk2"}, {0x00020000, SecureInfoGetRegion, "SecureInfoGetRegion"}, {0x00030040, GenHashConsoleUnique, "GenHashConsoleUnique"}, diff --git a/src/core/hle/service/dlp_srvr.cpp b/src/core/hle/service/dlp_srvr.cpp new file mode 100644 index 000000000..1f30188da --- /dev/null +++ b/src/core/hle/service/dlp_srvr.cpp @@ -0,0 +1,36 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/hle/hle.h" +#include "core/hle/service/dlp_srvr.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace DLP_SRVR + +namespace DLP_SRVR { + +static void unk_0x000E0040(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = 0; + + LOG_WARNING(Service_DLP, "(STUBBED) called"); +} + +const Interface::FunctionInfo FunctionTable[] = { + {0x00010183, nullptr, "Initialize"}, + {0x00020000, nullptr, "Finalize"}, + {0x000E0040, unk_0x000E0040, "unk_0x000E0040"}, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Interface class + +Interface::Interface() { + Register(FunctionTable); +} + +} // namespace diff --git a/src/core/hle/service/dlp_srvr.h b/src/core/hle/service/dlp_srvr.h new file mode 100644 index 000000000..d65d00814 --- /dev/null +++ b/src/core/hle/service/dlp_srvr.h @@ -0,0 +1,23 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace DLP_SRVR + +namespace DLP_SRVR { + +class Interface : public Service::Interface { +public: + Interface(); + + std::string GetPortName() const override { + return "dlp:SRVR"; + } +}; + +} // namespace diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp index 08e437125..995bee3f9 100644 --- a/src/core/hle/service/dsp_dsp.cpp +++ b/src/core/hle/service/dsp_dsp.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <cinttypes> #include "audio_core/hle/pipe.h" @@ -12,37 +13,80 @@ #include "core/hle/kernel/event.h" #include "core/hle/service/dsp_dsp.h" +using DspPipe = DSP::HLE::DspPipe; + //////////////////////////////////////////////////////////////////////////////////////////////////// // Namespace DSP_DSP namespace DSP_DSP { -static u32 read_pipe_count; static Kernel::SharedPtr<Kernel::Event> semaphore_event; -struct PairHash { - template <typename T, typename U> - std::size_t operator()(const std::pair<T, U> &x) const { - // TODO(yuriks): Replace with better hash combining function. - return std::hash<T>()(x.first) ^ std::hash<U>()(x.second); +/// There are three types of interrupts +enum class InterruptType { + Zero, One, Pipe +}; +constexpr size_t NUM_INTERRUPT_TYPE = 3; + +class InterruptEvents final { +public: + void Signal(InterruptType type, DspPipe pipe) { + Kernel::SharedPtr<Kernel::Event>& event = Get(type, pipe); + if (event) { + event->Signal(); + } } + + Kernel::SharedPtr<Kernel::Event>& Get(InterruptType type, DspPipe dsp_pipe) { + switch (type) { + case InterruptType::Zero: + return zero; + case InterruptType::One: + return one; + case InterruptType::Pipe: { + const size_t pipe_index = static_cast<size_t>(dsp_pipe); + ASSERT(pipe_index < DSP::HLE::NUM_DSP_PIPE); + return pipe[pipe_index]; + } + } + + UNREACHABLE_MSG("Invalid interrupt type = %zu", static_cast<size_t>(type)); + } + + bool HasTooManyEventsRegistered() const { + // Actual service implementation only has 6 'slots' for interrupts. + constexpr size_t max_number_of_interrupt_events = 6; + + size_t number = std::count_if(pipe.begin(), pipe.end(), [](const auto& evt) { + return evt != nullptr; + }); + + if (zero != nullptr) + number++; + if (one != nullptr) + number++; + + return number >= max_number_of_interrupt_events; + } + +private: + /// Currently unknown purpose + Kernel::SharedPtr<Kernel::Event> zero = nullptr; + /// Currently unknown purpose + Kernel::SharedPtr<Kernel::Event> one = nullptr; + /// Each DSP pipe has an associated interrupt + std::array<Kernel::SharedPtr<Kernel::Event>, DSP::HLE::NUM_DSP_PIPE> pipe = {{}}; }; -/// Map of (audio interrupt number, channel number) to Kernel::Events. See: RegisterInterruptEvents -static std::unordered_map<std::pair<u32, u32>, Kernel::SharedPtr<Kernel::Event>, PairHash> interrupt_events; +static InterruptEvents interrupt_events; // DSP Interrupts: -// Interrupt #2 occurs every frame tick. Userland programs normally have a thread that's waiting -// for an interrupt event. Immediately after this interrupt event, userland normally updates the -// state in the next region and increments the relevant frame counter by two. -void SignalAllInterrupts() { - // HACK: The other interrupts have currently unknown purpose, we trigger them each tick in any case. - for (auto& interrupt_event : interrupt_events) - interrupt_event.second->Signal(); -} - -void SignalInterrupt(u32 interrupt, u32 channel) { - interrupt_events[std::make_pair(interrupt, channel)]->Signal(); +// The audio-pipe interrupt occurs every frame tick. Userland programs normally have a thread +// that's waiting for an interrupt event. Immediately after this interrupt event, userland +// normally updates the state in the next region and increments the relevant frame counter by +// two. +void SignalPipeInterrupt(DspPipe pipe) { + interrupt_events.Signal(InterruptType::Pipe, pipe); } /** @@ -58,7 +102,10 @@ static void ConvertProcessAddressFromDspDram(Service::Interface* self) { u32 addr = cmd_buff[1]; + cmd_buff[0] = IPC::MakeHeader(0xC, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + // TODO(merry): There is a per-region offset missing in this calculation (that seems to be always zero). cmd_buff[2] = (addr << 1) + (Memory::DSP_RAM_VADDR + 0x40000); LOG_DEBUG(Service_DSP, "addr=0x%08X", addr); @@ -113,7 +160,9 @@ static void LoadComponent(Service::Interface* self) { static void GetSemaphoreEventHandle(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[0] = IPC::MakeHeader(0x16, 1, 2); cmd_buff[1] = RESULT_SUCCESS.raw; // No error + // cmd_buff[2] not set cmd_buff[3] = Kernel::g_handle_table.Create(semaphore_event).MoveFrom(); // Event handle LOG_WARNING(Service_DSP, "(STUBBED) called"); @@ -138,8 +187,7 @@ static void FlushDataCache(Service::Interface* self) { u32 size = cmd_buff[2]; u32 process = cmd_buff[4]; - // TODO(purpasmart96): Verify return header on HW - + cmd_buff[0] = IPC::MakeHeader(0x13, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_TRACE(Service_DSP, "called address=0x%08X, size=0x%X, process=0x%08X", address, size, process); @@ -148,8 +196,8 @@ static void FlushDataCache(Service::Interface* self) { /** * DSP_DSP::RegisterInterruptEvents service function * Inputs: - * 1 : Interrupt Number - * 2 : Channel Number + * 1 : Interrupt Type + * 2 : Pipe Number * 4 : Interrupt event handle * Outputs: * 1 : Result of function, 0 on success, otherwise error code @@ -157,23 +205,40 @@ static void FlushDataCache(Service::Interface* self) { static void RegisterInterruptEvents(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 interrupt = cmd_buff[1]; - u32 channel = cmd_buff[2]; + u32 type_index = cmd_buff[1]; + u32 pipe_index = cmd_buff[2]; u32 event_handle = cmd_buff[4]; + ASSERT_MSG(type_index < NUM_INTERRUPT_TYPE && pipe_index < DSP::HLE::NUM_DSP_PIPE, + "Invalid type or pipe: type = %u, pipe = %u", type_index, pipe_index); + + InterruptType type = static_cast<InterruptType>(cmd_buff[1]); + DspPipe pipe = static_cast<DspPipe>(cmd_buff[2]); + + cmd_buff[0] = IPC::MakeHeader(0x15, 1, 0); + if (event_handle) { auto evt = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); - if (evt) { - interrupt_events[std::make_pair(interrupt, channel)] = evt; - cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_INFO(Service_DSP, "Registered interrupt=%u, channel=%u, event_handle=0x%08X", interrupt, channel, event_handle); - } else { - LOG_CRITICAL(Service_DSP, "Invalid event handle! interrupt=%u, channel=%u, event_handle=0x%08X", interrupt, channel, event_handle); - ASSERT(false); // This should really be handled at a IPC translation layer. + + if (!evt) { + LOG_INFO(Service_DSP, "Invalid event handle! type=%u, pipe=%u, event_handle=0x%08X", type_index, pipe_index, event_handle); + ASSERT(false); // TODO: This should really be handled at an IPC translation layer. + } + + if (interrupt_events.HasTooManyEventsRegistered()) { + LOG_INFO(Service_DSP, "Ran out of space to register interrupts (Attempted to register type=%u, pipe=%u, event_handle=0x%08X)", + type_index, pipe_index, event_handle); + cmd_buff[1] = ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::DSP, ErrorSummary::OutOfResource, ErrorLevel::Status).raw; + return; } + + interrupt_events.Get(type, pipe) = evt; + LOG_INFO(Service_DSP, "Registered type=%u, pipe=%u, event_handle=0x%08X", type_index, pipe_index, event_handle); + cmd_buff[1] = RESULT_SUCCESS.raw; } else { - interrupt_events.erase(std::make_pair(interrupt, channel)); - LOG_INFO(Service_DSP, "Unregistered interrupt=%u, channel=%u, event_handle=0x%08X", interrupt, channel, event_handle); + interrupt_events.Get(type, pipe) = nullptr; + LOG_INFO(Service_DSP, "Unregistered interrupt=%u, channel=%u, event_handle=0x%08X", type_index, pipe_index, event_handle); + cmd_buff[1] = RESULT_SUCCESS.raw; } } @@ -187,6 +252,7 @@ static void RegisterInterruptEvents(Service::Interface* self) { static void SetSemaphore(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[0] = IPC::MakeHeader(0x7, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_DSP, "(STUBBED) called"); @@ -195,7 +261,7 @@ static void SetSemaphore(Service::Interface* self) { /** * DSP_DSP::WriteProcessPipe service function * Inputs: - * 1 : Channel + * 1 : Pipe Number * 2 : Size * 3 : (size << 14) | 0x402 * 4 : Buffer @@ -206,24 +272,32 @@ static void SetSemaphore(Service::Interface* self) { static void WriteProcessPipe(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(cmd_buff[1]); + u32 pipe_index = cmd_buff[1]; u32 size = cmd_buff[2]; u32 buffer = cmd_buff[4]; - ASSERT_MSG(IPC::StaticBufferDesc(size, 1) == cmd_buff[3], "IPC static buffer descriptor failed validation (0x%X). pipe=%u, size=0x%X, buffer=0x%08X", cmd_buff[3], pipe, size, buffer); - ASSERT_MSG(Memory::GetPointer(buffer) != nullptr, "Invalid Buffer: pipe=%u, size=0x%X, buffer=0x%08X", pipe, size, buffer); + DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index); - std::vector<u8> message(size); + if (IPC::StaticBufferDesc(size, 1) != cmd_buff[3]) { + LOG_ERROR(Service_DSP, "IPC static buffer descriptor failed validation (0x%X). pipe=%u, size=0x%X, buffer=0x%08X", cmd_buff[3], pipe_index, size, buffer); + cmd_buff[0] = IPC::MakeHeader(0, 1, 0); + cmd_buff[1] = ResultCode(ErrorDescription::OS_InvalidBufferDescriptor, ErrorModule::OS, ErrorSummary::WrongArgument, ErrorLevel::Permanent).raw; + return; + } + + ASSERT_MSG(Memory::GetPointer(buffer) != nullptr, "Invalid Buffer: pipe=%u, size=0x%X, buffer=0x%08X", pipe_index, size, buffer); + std::vector<u8> message(size); for (size_t i = 0; i < size; i++) { message[i] = Memory::Read8(buffer + i); } DSP::HLE::PipeWrite(pipe, message); + cmd_buff[0] = IPC::MakeHeader(0xD, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; // No error - LOG_DEBUG(Service_DSP, "pipe=%u, size=0x%X, buffer=0x%08X", pipe, size, buffer); + LOG_DEBUG(Service_DSP, "pipe=%u, size=0x%X, buffer=0x%08X", pipe_index, size, buffer); } /** @@ -243,13 +317,16 @@ static void WriteProcessPipe(Service::Interface* self) { static void ReadPipeIfPossible(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(cmd_buff[1]); + u32 pipe_index = cmd_buff[1]; u32 unknown = cmd_buff[2]; u32 size = cmd_buff[3] & 0xFFFF; // Lower 16 bits are size VAddr addr = cmd_buff[0x41]; - ASSERT_MSG(Memory::GetPointer(addr) != nullptr, "Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe, unknown, size, addr); + 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); + cmd_buff[0] = IPC::MakeHeader(0x10, 1, 2); cmd_buff[1] = RESULT_SUCCESS.raw; // No error if (DSP::HLE::GetPipeReadableSize(pipe) >= size) { std::vector<u8> response = DSP::HLE::PipeRead(pipe, size); @@ -260,8 +337,10 @@ static void ReadPipeIfPossible(Service::Interface* self) { } else { cmd_buff[2] = 0; // Return no data } + cmd_buff[3] = IPC::StaticBufferDesc(size, 0); + cmd_buff[4] = addr; - LOG_DEBUG(Service_DSP, "pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X, return cmd_buff[2]=0x%08X", pipe, unknown, size, addr, cmd_buff[2]); + LOG_DEBUG(Service_DSP, "pipe=%u, unknown=0x%08X, size=0x%X, buffer=0x%08X, return cmd_buff[2]=0x%08X", pipe_index, unknown, size, addr, cmd_buff[2]); } /** @@ -278,26 +357,31 @@ static void ReadPipeIfPossible(Service::Interface* self) { static void ReadPipe(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(cmd_buff[1]); + u32 pipe_index = cmd_buff[1]; u32 unknown = cmd_buff[2]; u32 size = cmd_buff[3] & 0xFFFF; // Lower 16 bits are size VAddr addr = cmd_buff[0x41]; - ASSERT_MSG(Memory::GetPointer(addr) != nullptr, "Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe, unknown, size, addr); + 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); if (DSP::HLE::GetPipeReadableSize(pipe) >= size) { std::vector<u8> response = DSP::HLE::PipeRead(pipe, size); Memory::WriteBlock(addr, response.data(), response.size()); + cmd_buff[0] = IPC::MakeHeader(0xE, 2, 2); cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = static_cast<u32>(response.size()); + cmd_buff[3] = IPC::StaticBufferDesc(size, 0); + cmd_buff[4] = addr; } else { // No more data is in pipe. Hardware hangs in this case; this should never happen. UNREACHABLE(); } - LOG_DEBUG(Service_DSP, "pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X, return cmd_buff[2]=0x%08X", pipe, unknown, size, addr, cmd_buff[2]); + LOG_DEBUG(Service_DSP, "pipe=%u, unknown=0x%08X, size=0x%X, buffer=0x%08X, return cmd_buff[2]=0x%08X", pipe_index, unknown, size, addr, cmd_buff[2]); } /** @@ -312,13 +396,16 @@ static void ReadPipe(Service::Interface* self) { static void GetPipeReadableSize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(cmd_buff[1]); + u32 pipe_index = cmd_buff[1]; u32 unknown = cmd_buff[2]; + DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index); + + cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = DSP::HLE::GetPipeReadableSize(pipe); - LOG_DEBUG(Service_DSP, "pipe=0x%08X, unknown=0x%08X, return cmd_buff[2]=0x%08X", pipe, unknown, cmd_buff[2]); + LOG_DEBUG(Service_DSP, "pipe=%u, unknown=0x%08X, return cmd_buff[2]=0x%08X", pipe_index, unknown, cmd_buff[2]); } /** @@ -333,6 +420,7 @@ static void SetSemaphoreMask(Service::Interface* self) { u32 mask = cmd_buff[1]; + cmd_buff[0] = IPC::MakeHeader(0x17, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_DSP, "(STUBBED) called mask=0x%08X", mask); @@ -350,6 +438,7 @@ static void SetSemaphoreMask(Service::Interface* self) { static void GetHeadphoneStatus(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[0] = IPC::MakeHeader(0x1F, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = 0; // Not using headphones? @@ -376,6 +465,7 @@ static void RecvData(Service::Interface* self) { // Application reads this after requesting DSP shutdown, to verify the DSP has indeed shutdown or slept. + cmd_buff[0] = IPC::MakeHeader(0x1, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; switch (DSP::HLE::GetDspState()) { case DSP::HLE::DspState::On: @@ -411,6 +501,7 @@ static void RecvDataIsReady(Service::Interface* self) { ASSERT_MSG(register_number == 0, "Unknown register_number %u", register_number); + cmd_buff[0] = IPC::MakeHeader(0x2, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 1; // Ready to read @@ -458,14 +549,14 @@ const Interface::FunctionInfo FunctionTable[] = { Interface::Interface() { semaphore_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "DSP_DSP::semaphore_event"); - read_pipe_count = 0; + interrupt_events = {}; Register(FunctionTable); } Interface::~Interface() { semaphore_event = nullptr; - interrupt_events.clear(); + interrupt_events = {}; } } // namespace diff --git a/src/core/hle/service/dsp_dsp.h b/src/core/hle/service/dsp_dsp.h index 32b89e9bb..22f6687cc 100644 --- a/src/core/hle/service/dsp_dsp.h +++ b/src/core/hle/service/dsp_dsp.h @@ -8,6 +8,12 @@ #include "core/hle/service/service.h" +namespace DSP { +namespace HLE { +enum class DspPipe; +} +} + //////////////////////////////////////////////////////////////////////////////////////////////////// // Namespace DSP_DSP @@ -23,15 +29,10 @@ public: } }; -/// Signal all audio related interrupts. -void SignalAllInterrupts(); - /** - * Signal a specific audio related interrupt based on interrupt id and channel id. - * @param interrupt_id The interrupt id - * @param channel_id The channel id - * The significance of various values of interrupt_id and channel_id is not yet known. + * Signal a specific DSP related interrupt of type == InterruptType::Pipe, pipe == pipe. + * @param pipe The DSP pipe for which to signal an interrupt for. */ -void SignalInterrupt(u32 interrupt_id, u32 channel_id); +void SignalPipeInterrupt(DSP::HLE::DspPipe pipe); -} // namespace +} // namespace DSP_DSP diff --git a/src/core/hle/service/frd/frd.cpp b/src/core/hle/service/frd/frd.cpp index c13ffd9d2..15d604bb6 100644 --- a/src/core/hle/service/frd/frd.cpp +++ b/src/core/hle/service/frd/frd.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/string_util.h" + #include "core/hle/service/service.h" #include "core/hle/service/frd/frd.h" #include "core/hle/service/frd/frd_a.h" @@ -10,6 +12,95 @@ namespace Service { namespace FRD { +static FriendKey my_friend_key = {0, 0, 0ull}; +static MyPresence my_presence = {}; + +void GetMyPresence(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 shifted_out_size = cmd_buff[64]; + u32 my_presence_addr = cmd_buff[65]; + + ASSERT(shifted_out_size == ((sizeof(MyPresence) << 14) | 2)); + + Memory::WriteBlock(my_presence_addr, reinterpret_cast<const u8*>(&my_presence), sizeof(MyPresence)); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_FRD, "(STUBBED) called"); +} + +void GetFriendKeyList(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 unknown = cmd_buff[1]; + u32 frd_count = cmd_buff[2]; + u32 frd_key_addr = cmd_buff[65]; + + 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)); + } + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 0; // 0 friends + LOG_WARNING(Service_FRD, "(STUBBED) called, unknown=%d, frd_count=%d, frd_key_addr=0x%08X", + unknown, frd_count, frd_key_addr); +} + +void GetFriendProfile(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 count = cmd_buff[1]; + u32 frd_key_addr = cmd_buff[3]; + u32 profiles_addr = cmd_buff[65]; + + 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)); + } + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_FRD, "(STUBBED) called, count=%d, frd_key_addr=0x%08X, profiles_addr=0x%08X", + count, frd_key_addr, profiles_addr); +} + +void GetFriendAttributeFlags(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 count = cmd_buff[1]; + u32 frd_key_addr = cmd_buff[3]; + u32 attr_flags_addr = cmd_buff[65]; + + for (u32 i = 0; i < count; ++i) { + //TODO:(mailwl) figure out AttributeFlag size and zero all buffer. Assume 1 byte + Memory::Write8(attr_flags_addr + i, 0); + } + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_FRD, "(STUBBED) called, count=%d, frd_key_addr=0x%08X, attr_flags_addr=0x%08X", + count, frd_key_addr, attr_flags_addr); +} + +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)); + LOG_WARNING(Service_FRD, "(STUBBED) called"); +} + +void GetMyScreenName(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + // TODO: (mailwl) get the name from config + Common::UTF8ToUTF16("Citra").copy(reinterpret_cast<char16_t*>(&cmd_buff[2]), 11); + LOG_WARNING(Service_FRD, "(STUBBED) called"); +} + void Init() { using namespace Kernel; diff --git a/src/core/hle/service/frd/frd.h b/src/core/hle/service/frd/frd.h index f9f88b444..c8283a7f3 100644 --- a/src/core/hle/service/frd/frd.h +++ b/src/core/hle/service/frd/frd.h @@ -4,9 +4,97 @@ #pragma once +#include "common/common_types.h" + namespace Service { + +class Interface; + namespace FRD { +struct FriendKey { + u32 friend_id; + u32 unknown; + u64 friend_code; +}; + +struct MyPresence { + u8 unknown[0x12C]; +}; + +struct Profile { + u8 region; + u8 country; + u8 area; + u8 language; + u32 unknown; +}; + +/** + * FRD::GetMyPresence service function + * Inputs: + * 64 : sizeof (MyPresence) << 14 | 2 + * 65 : Address of MyPresence structure + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void GetMyPresence(Service::Interface* self); + +/** + * FRD::GetFriendKeyList service function + * Inputs: + * 1 : Unknown + * 2 : Max friends count + * 65 : Address of FriendKey List + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : FriendKey count filled + */ +void GetFriendKeyList(Service::Interface* self); + +/** + * FRD::GetFriendProfile service function + * Inputs: + * 1 : Friends count + * 2 : Friends count << 18 | 2 + * 3 : Address of FriendKey List + * 64 : (count * sizeof (Profile)) << 10 | 2 + * 65 : Address of Profiles List + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void GetFriendProfile(Service::Interface* self); + +/** + * FRD::GetFriendAttributeFlags service function + * Inputs: + * 1 : Friends count + * 2 : Friends count << 18 | 2 + * 3 : Address of FriendKey List + * 65 : Address of AttributeFlags + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void GetFriendAttributeFlags(Service::Interface* self); + +/** + * FRD::GetMyFriendKey service function + * Inputs: + * none + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2-5 : FriendKey + */ +void GetMyFriendKey(Service::Interface* self); + +/** + * FRD::GetMyScreenName service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : UTF16 encoded name (max 11 symbols) + */ +void GetMyScreenName(Service::Interface* self); + /// Initialize FRD service(s) void Init(); diff --git a/src/core/hle/service/frd/frd_u.cpp b/src/core/hle/service/frd/frd_u.cpp index 2c6885377..db8666416 100644 --- a/src/core/hle/service/frd/frd_u.cpp +++ b/src/core/hle/service/frd/frd_u.cpp @@ -2,65 +2,66 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/service/frd/frd.h" #include "core/hle/service/frd/frd_u.h" namespace Service { namespace FRD { const Interface::FunctionInfo FunctionTable[] = { - {0x00010000, nullptr, "HasLoggedIn"}, - {0x00020000, nullptr, "IsOnline"}, - {0x00030000, nullptr, "Login"}, - {0x00040000, nullptr, "Logout"}, - {0x00050000, nullptr, "GetMyFriendKey"}, - {0x00060000, nullptr, "GetMyPreference"}, - {0x00070000, nullptr, "GetMyProfile"}, - {0x00080000, nullptr, "GetMyPresence"}, - {0x00090000, nullptr, "GetMyScreenName"}, - {0x000A0000, nullptr, "GetMyMii"}, - {0x000B0000, nullptr, "GetMyLocalAccountId"}, - {0x000C0000, nullptr, "GetMyPlayingGame"}, - {0x000D0000, nullptr, "GetMyFavoriteGame"}, - {0x000E0000, nullptr, "GetMyNcPrincipalId"}, - {0x000F0000, nullptr, "GetMyComment"}, - {0x00100040, nullptr, "GetMyPassword"}, - {0x00110080, nullptr, "GetFriendKeyList"}, - {0x00120042, nullptr, "GetFriendPresence"}, - {0x00130142, nullptr, "GetFriendScreenName"}, - {0x00140044, nullptr, "GetFriendMii"}, - {0x00150042, nullptr, "GetFriendProfile"}, - {0x00160042, nullptr, "GetFriendRelationship"}, - {0x00170042, nullptr, "GetFriendAttributeFlags"}, - {0x00180044, nullptr, "GetFriendPlayingGame"}, - {0x00190042, nullptr, "GetFriendFavoriteGame"}, - {0x001A00C4, nullptr, "GetFriendInfo"}, - {0x001B0080, nullptr, "IsIncludedInFriendList"}, - {0x001C0042, nullptr, "UnscrambleLocalFriendCode"}, - {0x001D0002, nullptr, "UpdateGameModeDescription"}, - {0x001E02C2, nullptr, "UpdateGameMode"}, - {0x001F0042, nullptr, "SendInvitation"}, - {0x00200002, nullptr, "AttachToEventNotification"}, - {0x00210040, nullptr, "SetNotificationMask"}, - {0x00220040, nullptr, "GetEventNotification"}, - {0x00230000, nullptr, "GetLastResponseResult"}, - {0x00240040, nullptr, "PrincipalIdToFriendCode"}, - {0x00250080, nullptr, "FriendCodeToPrincipalId"}, - {0x00260080, nullptr, "IsValidFriendCode"}, - {0x00270040, nullptr, "ResultToErrorCode"}, - {0x00280244, nullptr, "RequestGameAuthentication"}, - {0x00290000, nullptr, "GetGameAuthenticationData"}, - {0x002A0204, nullptr, "RequestServiceLocator"}, - {0x002B0000, nullptr, "GetServiceLocatorData"}, - {0x002C0002, nullptr, "DetectNatProperties"}, - {0x002D0000, nullptr, "GetNatProperties"}, - {0x002E0000, nullptr, "GetServerTimeInterval"}, - {0x002F0040, nullptr, "AllowHalfAwake"}, - {0x00300000, nullptr, "GetServerTypes"}, - {0x00310082, nullptr, "GetFriendComment"}, - {0x00320042, nullptr, "SetClientSdkVersion"}, - {0x00330000, nullptr, "GetMyApproachContext"}, - {0x00340046, nullptr, "AddFriendWithApproach"}, - {0x00350082, nullptr, "DecryptApproachContext"}, + {0x00010000, nullptr, "HasLoggedIn"}, + {0x00020000, nullptr, "IsOnline"}, + {0x00030000, nullptr, "Login"}, + {0x00040000, nullptr, "Logout"}, + {0x00050000, GetMyFriendKey, "GetMyFriendKey"}, + {0x00060000, nullptr, "GetMyPreference"}, + {0x00070000, nullptr, "GetMyProfile"}, + {0x00080000, GetMyPresence, "GetMyPresence"}, + {0x00090000, GetMyScreenName, "GetMyScreenName"}, + {0x000A0000, nullptr, "GetMyMii"}, + {0x000B0000, nullptr, "GetMyLocalAccountId"}, + {0x000C0000, nullptr, "GetMyPlayingGame"}, + {0x000D0000, nullptr, "GetMyFavoriteGame"}, + {0x000E0000, nullptr, "GetMyNcPrincipalId"}, + {0x000F0000, nullptr, "GetMyComment"}, + {0x00100040, nullptr, "GetMyPassword"}, + {0x00110080, GetFriendKeyList, "GetFriendKeyList"}, + {0x00120042, nullptr, "GetFriendPresence"}, + {0x00130142, nullptr, "GetFriendScreenName"}, + {0x00140044, nullptr, "GetFriendMii"}, + {0x00150042, GetFriendProfile, "GetFriendProfile"}, + {0x00160042, nullptr, "GetFriendRelationship"}, + {0x00170042, GetFriendAttributeFlags, "GetFriendAttributeFlags"}, + {0x00180044, nullptr, "GetFriendPlayingGame"}, + {0x00190042, nullptr, "GetFriendFavoriteGame"}, + {0x001A00C4, nullptr, "GetFriendInfo"}, + {0x001B0080, nullptr, "IsIncludedInFriendList"}, + {0x001C0042, nullptr, "UnscrambleLocalFriendCode"}, + {0x001D0002, nullptr, "UpdateGameModeDescription"}, + {0x001E02C2, nullptr, "UpdateGameMode"}, + {0x001F0042, nullptr, "SendInvitation"}, + {0x00200002, nullptr, "AttachToEventNotification"}, + {0x00210040, nullptr, "SetNotificationMask"}, + {0x00220040, nullptr, "GetEventNotification"}, + {0x00230000, nullptr, "GetLastResponseResult"}, + {0x00240040, nullptr, "PrincipalIdToFriendCode"}, + {0x00250080, nullptr, "FriendCodeToPrincipalId"}, + {0x00260080, nullptr, "IsValidFriendCode"}, + {0x00270040, nullptr, "ResultToErrorCode"}, + {0x00280244, nullptr, "RequestGameAuthentication"}, + {0x00290000, nullptr, "GetGameAuthenticationData"}, + {0x002A0204, nullptr, "RequestServiceLocator"}, + {0x002B0000, nullptr, "GetServiceLocatorData"}, + {0x002C0002, nullptr, "DetectNatProperties"}, + {0x002D0000, nullptr, "GetNatProperties"}, + {0x002E0000, nullptr, "GetServerTimeInterval"}, + {0x002F0040, nullptr, "AllowHalfAwake"}, + {0x00300000, nullptr, "GetServerTypes"}, + {0x00310082, nullptr, "GetFriendComment"}, + {0x00320042, nullptr, "SetClientSdkVersion"}, + {0x00330000, nullptr, "GetMyApproachContext"}, + {0x00340046, nullptr, "AddFriendWithApproach"}, + {0x00350082, nullptr, "DecryptApproachContext"}, }; FRD_U_Interface::FRD_U_Interface() { diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 590697e76..cc51ede0c 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -15,7 +15,6 @@ #include "common/common_types.h" #include "common/file_util.h" #include "common/logging/log.h" -#include "common/make_unique.h" #include "core/file_sys/archive_backend.h" #include "core/file_sys/archive_extsavedata.h" @@ -115,6 +114,7 @@ ResultVal<bool> File::SyncRequest() { return read.Code(); } cmd_buff[2] = static_cast<u32>(*read); + Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(address), length); break; } @@ -521,23 +521,23 @@ void ArchiveInit() { std::string sdmc_directory = FileUtil::GetUserPath(D_SDMC_IDX); std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX); - auto sdmc_factory = Common::make_unique<FileSys::ArchiveFactory_SDMC>(sdmc_directory); + auto sdmc_factory = std::make_unique<FileSys::ArchiveFactory_SDMC>(sdmc_directory); if (sdmc_factory->Initialize()) RegisterArchiveType(std::move(sdmc_factory), ArchiveIdCode::SDMC); else LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", sdmc_directory.c_str()); // Create the SaveData archive - auto savedata_factory = Common::make_unique<FileSys::ArchiveFactory_SaveData>(sdmc_directory); + auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sdmc_directory); RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData); - auto extsavedata_factory = Common::make_unique<FileSys::ArchiveFactory_ExtSaveData>(sdmc_directory, false); + auto extsavedata_factory = std::make_unique<FileSys::ArchiveFactory_ExtSaveData>(sdmc_directory, false); if (extsavedata_factory->Initialize()) RegisterArchiveType(std::move(extsavedata_factory), ArchiveIdCode::ExtSaveData); else LOG_ERROR(Service_FS, "Can't instantiate ExtSaveData archive with path %s", extsavedata_factory->GetMountPoint().c_str()); - auto sharedextsavedata_factory = Common::make_unique<FileSys::ArchiveFactory_ExtSaveData>(nand_directory, true); + auto sharedextsavedata_factory = std::make_unique<FileSys::ArchiveFactory_ExtSaveData>(nand_directory, true); if (sharedextsavedata_factory->Initialize()) RegisterArchiveType(std::move(sharedextsavedata_factory), ArchiveIdCode::SharedExtSaveData); else @@ -545,10 +545,10 @@ void ArchiveInit() { sharedextsavedata_factory->GetMountPoint().c_str()); // Create the SaveDataCheck archive, basically a small variation of the RomFS archive - auto savedatacheck_factory = Common::make_unique<FileSys::ArchiveFactory_SaveDataCheck>(nand_directory); + auto savedatacheck_factory = std::make_unique<FileSys::ArchiveFactory_SaveDataCheck>(nand_directory); RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::SaveDataCheck); - auto systemsavedata_factory = Common::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory); + auto systemsavedata_factory = std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory); RegisterArchiveType(std::move(systemsavedata_factory), ArchiveIdCode::SystemSaveData); } diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index 3ec7ceb30..7df7da5a4 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -250,7 +250,7 @@ static void CreateFile(Service::Interface* self) { FileSys::Path file_path(filename_type, filename_size, filename_ptr); - LOG_DEBUG(Service_FS, "type=%d size=%llu data=%s", filename_type, filename_size, file_path.DebugStr().c_str()); + LOG_DEBUG(Service_FS, "type=%d size=%llu data=%s", filename_type, file_size, file_path.DebugStr().c_str()); cmd_buff[1] = CreateFileInArchive(archive_handle, file_path, file_size).raw; } diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index 2ace2cade..b4c146e08 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -15,8 +15,6 @@ #include "video_core/gpu_debugger.h" #include "video_core/debug_utils/debug_utils.h" -#include "video_core/renderer_base.h" -#include "video_core/video_core.h" #include "gsp_gpu.h" @@ -31,6 +29,13 @@ const static u32 REGS_BEGIN = 0x1EB00000; namespace GSP_GPU { +const ResultCode ERR_GSP_REGS_OUTOFRANGE_OR_MISALIGNED(ErrorDescription::OutofRangeOrMisalignedAddress, ErrorModule::GX, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E02A01 +const ResultCode ERR_GSP_REGS_MISALIGNED(ErrorDescription::MisalignedSize, ErrorModule::GX, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E02BF2 +const ResultCode ERR_GSP_REGS_INVALID_SIZE(ErrorDescription::InvalidSize, ErrorModule::GX, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E02BEC + /// Event triggered when GSP interrupt has been signalled Kernel::SharedPtr<Kernel::Event> g_interrupt_event; /// GSP shared memoryings @@ -38,6 +43,8 @@ Kernel::SharedPtr<Kernel::SharedMemory> g_shared_memory; /// Thread index into interrupt relay queue u32 g_thread_id = 0; +static bool gpu_right_acquired = false; + /// 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))); @@ -59,47 +66,87 @@ static inline InterruptRelayQueue* GetInterruptRelayQueue(u32 thread_id) { } /** - * Checks if the parameters in a register write call are valid and logs in the case that - * they are not - * @param base_address The first address in the sequence of registers that will be written - * @param size_in_bytes The number of registers that will be written - * @return true if the parameters are valid, false otherwise + * 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 + * @return RESULT_SUCCESS if the parameters are valid, error code otherwise */ -static bool CheckWriteParameters(u32 base_address, u32 size_in_bytes) { - // TODO: Return proper error codes - if (base_address + size_in_bytes >= 0x420000) { - LOG_ERROR(Service_GSP, "Write address out of range! (address=0x%08x, size=0x%08x)", +static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, const u32* data) { + // This magic number is verified to be done by the gsp module + const u32 max_size_in_bytes = 0x80; + + if (base_address & 3 || base_address >= 0x420000) { + LOG_ERROR(Service_GSP, "Write address was out of range or misaligned! (address=0x%08x, size=0x%08x)", base_address, size_in_bytes); - return false; - } + return ERR_GSP_REGS_OUTOFRANGE_OR_MISALIGNED; + } else if (size_in_bytes <= max_size_in_bytes) { + if (size_in_bytes & 3) { + LOG_ERROR(Service_GSP, "Misaligned size 0x%08x", size_in_bytes); + return ERR_GSP_REGS_MISALIGNED; + } else { + while (size_in_bytes > 0) { + HW::Write<u32>(base_address + REGS_BEGIN, *data); + + size_in_bytes -= 4; + ++data; + base_address += 4; + } + return RESULT_SUCCESS; + } - // size should be word-aligned - if ((size_in_bytes % 4) != 0) { - LOG_ERROR(Service_GSP, "Invalid size 0x%08x", size_in_bytes); - return false; + } else { + LOG_ERROR(Service_GSP, "Out of range size 0x%08x", size_in_bytes); + return ERR_GSP_REGS_INVALID_SIZE; } - - return true; } /** - * Writes sequential GSP GPU hardware registers using an array of source data + * Updates sequential GSP GPU hardware registers using parallel arrays of source data and masks. + * For each register, the value is updated only where the mask is high * * @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 A pointer to the source data to use for updates + * @param masks A pointer to the masks + * @return RESULT_SUCCESS if the parameters are valid, error code otherwise */ -static void WriteHWRegs(u32 base_address, u32 size_in_bytes, const u32* data) { - // TODO: Return proper error codes - if (!CheckWriteParameters(base_address, size_in_bytes)) - return; +static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, const u32* data, const u32* masks) { + // This magic number is verified to be done by the gsp module + const u32 max_size_in_bytes = 0x80; - while (size_in_bytes > 0) { - HW::Write<u32>(base_address + REGS_BEGIN, *data); + if (base_address & 3 || base_address >= 0x420000) { + LOG_ERROR(Service_GSP, "Write address was out of range or misaligned! (address=0x%08x, size=0x%08x)", + base_address, size_in_bytes); + return ERR_GSP_REGS_OUTOFRANGE_OR_MISALIGNED; + } else if (size_in_bytes <= max_size_in_bytes) { + if (size_in_bytes & 3) { + LOG_ERROR(Service_GSP, "Misaligned size 0x%08x", size_in_bytes); + return ERR_GSP_REGS_MISALIGNED; + } else { + while (size_in_bytes > 0) { + const u32 reg_address = base_address + REGS_BEGIN; + + u32 reg_value; + HW::Read<u32>(reg_value, reg_address); + + // Update the current value of the register only for set mask bits + reg_value = (reg_value & ~*masks) | (*data | *masks); + + HW::Write<u32>(reg_address, reg_value); + + size_in_bytes -= 4; + ++data; + ++masks; + base_address += 4; + } + return RESULT_SUCCESS; + } - size_in_bytes -= 4; - ++data; - base_address += 4; + } else { + LOG_ERROR(Service_GSP, "Out of range size 0x%08x", size_in_bytes); + return ERR_GSP_REGS_INVALID_SIZE; } } @@ -120,39 +167,7 @@ static void WriteHWRegs(Service::Interface* self) { u32* src = (u32*)Memory::GetPointer(cmd_buff[4]); - WriteHWRegs(reg_addr, size, src); -} - -/** - * Updates sequential GSP GPU hardware registers using parallel arrays of source data and masks. - * For each register, the value is updated only where the mask is high - * - * @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 to use for updates - * @param masks A pointer to the masks - */ -static void WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, const u32* data, const u32* masks) { - // TODO: Return proper error codes - if (!CheckWriteParameters(base_address, size_in_bytes)) - return; - - while (size_in_bytes > 0) { - const u32 reg_address = base_address + REGS_BEGIN; - - u32 reg_value; - HW::Read<u32>(reg_value, reg_address); - - // Update the current value of the register only for set mask bits - reg_value = (reg_value & ~*masks) | (*data | *masks); - - HW::Write<u32>(reg_address, reg_value); - - size_in_bytes -= 4; - ++data; - ++masks; - base_address += 4; - } + cmd_buff[1] = WriteHWRegs(reg_addr, size, src).raw; } /** @@ -174,7 +189,7 @@ static void WriteHWRegsWithMask(Service::Interface* self) { u32* src_data = (u32*)Memory::GetPointer(cmd_buff[4]); u32* mask_data = (u32*)Memory::GetPointer(cmd_buff[6]); - WriteHWRegsWithMask(reg_addr, size, src_data, mask_data); + cmd_buff[1] = WriteHWRegsWithMask(reg_addr, size, src_data, mask_data).raw; } /// Read a GSP GPU hardware register @@ -206,27 +221,27 @@ static void ReadHWRegs(Service::Interface* self) { } } -void SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) { +ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) { u32 base_address = 0x400000; 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); + 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); } 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); + 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); } - 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); + 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); if (Pica::g_debug_context) Pica::g_debug_context->OnEvent(Pica::DebugContext::Event::BufferSwapped, nullptr); @@ -234,6 +249,8 @@ void SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) { if (screen_id == 0) { MicroProfileFlip(); } + + return RESULT_SUCCESS; } /** @@ -251,9 +268,8 @@ static void SetBufferSwap(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 screen_id = cmd_buff[1]; FrameBufferInfo* fb_info = (FrameBufferInfo*)&cmd_buff[2]; - SetBufferSwap(screen_id, *fb_info); - cmd_buff[1] = 0; // No error + cmd_buff[1] = SetBufferSwap(screen_id, *fb_info).raw; } /** @@ -275,8 +291,6 @@ static void FlushDataCache(Service::Interface* self) { u32 size = cmd_buff[2]; u32 process = cmd_buff[4]; - VideoCore::g_renderer->Rasterizer()->InvalidateRegion(Memory::VirtualToPhysicalAddress(address), size); - // TODO(purpasmart96): Verify return header on HW cmd_buff[1] = RESULT_SUCCESS.raw; // No error @@ -286,6 +300,22 @@ static void FlushDataCache(Service::Interface* self) { } /** + * GSP_GPU::SetAxiConfigQoSMode service function + * Inputs: + * 1 : Mode, unused in emulator + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void SetAxiConfigQoSMode(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 mode = cmd_buff[1]; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_GSP, "(STUBBED) called mode=0x%08X", mode); +} + +/** * GSP_GPU::RegisterInterruptRelayQueue service function * Inputs: * 1 : "Flags" field, purpose is unknown @@ -302,6 +332,12 @@ static void RegisterInterruptRelayQueue(Service::Interface* self) { g_interrupt_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[3]); 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 @@ -314,12 +350,31 @@ static void RegisterInterruptRelayQueue(Service::Interface* self) { } /** + * GSP_GPU::UnregisterInterruptRelayQueue service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void UnregisterInterruptRelayQueue(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + g_shared_memory = nullptr; + g_interrupt_event = nullptr; + + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_GSP, "called"); +} + +/** * Signals that the specified interrupt type has occurred to userland code * @param interrupt_id ID of interrupt that is being signalled * @todo This should probably take a thread_id parameter and only signal this thread? * @todo This probably does not belong in the GSP module, instead move to video_core */ void SignalInterrupt(InterruptId interrupt_id) { + if (!gpu_right_acquired) { + return; + } if (nullptr == g_interrupt_event) { LOG_WARNING(Service_GSP, "cannot synchronize until GSP event has been created!"); return; @@ -354,6 +409,8 @@ void SignalInterrupt(InterruptId interrupt_id) { g_interrupt_event->Signal(); } +MICROPROFILE_DEFINE(GPU_GSP_DMA, "GPU", "GSP DMA", MP_RGB(100, 0, 255)); + /// Executes the next GSP command static void ExecuteCommand(const Command& command, u32 thread_id) { // Utility function to convert register ID to address @@ -365,18 +422,21 @@ static void ExecuteCommand(const Command& command, u32 thread_id) { // GX request DMA - typically used for copying memory from GSP heap to VRAM case CommandId::REQUEST_DMA: - VideoCore::g_renderer->Rasterizer()->FlushRegion(Memory::VirtualToPhysicalAddress(command.dma_request.source_address), - command.dma_request.size); + { + MICROPROFILE_SCOPE(GPU_GSP_DMA); + + // TODO: Consider attempting rasterizer-accelerated surface blit if that usage is ever possible/likely + Memory::RasterizerFlushRegion(Memory::VirtualToPhysicalAddress(command.dma_request.source_address), + command.dma_request.size); + 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); SignalInterrupt(InterruptId::DMA); - - VideoCore::g_renderer->Rasterizer()->InvalidateRegion(Memory::VirtualToPhysicalAddress(command.dma_request.dest_address), - command.dma_request.size); break; - + } // TODO: This will need some rework in the future. (why?) case CommandId::SUBMIT_GPU_CMDLIST: { @@ -463,13 +523,8 @@ static void ExecuteCommand(const Command& command, u32 thread_id) { case CommandId::CACHE_FLUSH: { - for (auto& region : command.cache_flush.regions) { - if (region.size == 0) - break; - - VideoCore::g_renderer->Rasterizer()->InvalidateRegion( - Memory::VirtualToPhysicalAddress(region.address), region.size); - } + // NOTE: Rasterizer flushing handled elsewhere in CPU read/write and other GPU handlers + // Use command.cache_flush.regions to implement this handler break; } @@ -574,6 +629,35 @@ static void ImportDisplayCaptureInfo(Service::Interface* self) { LOG_WARNING(Service_GSP, "called"); } +/** + * GSP_GPU::AcquireRight service function + * Outputs: + * 1: Result code + */ +static void AcquireRight(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + gpu_right_acquired = true; + + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_GSP, "called"); +} + +/** + * GSP_GPU::ReleaseRight service function + * Outputs: + * 1: Result code + */ +static void ReleaseRight(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + gpu_right_acquired = false; + + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_GSP, "called"); +} const Interface::FunctionInfo FunctionTable[] = { {0x00010082, WriteHWRegs, "WriteHWRegs"}, @@ -591,14 +675,14 @@ const Interface::FunctionInfo FunctionTable[] = { {0x000D0140, nullptr, "SetDisplayTransfer"}, {0x000E0180, nullptr, "SetTextureCopy"}, {0x000F0200, nullptr, "SetMemoryFill"}, - {0x00100040, nullptr, "SetAxiConfigQoSMode"}, + {0x00100040, SetAxiConfigQoSMode, "SetAxiConfigQoSMode"}, {0x00110040, nullptr, "SetPerfLogMode"}, {0x00120000, nullptr, "GetPerfLog"}, {0x00130042, RegisterInterruptRelayQueue, "RegisterInterruptRelayQueue"}, - {0x00140000, nullptr, "UnregisterInterruptRelayQueue"}, + {0x00140000, UnregisterInterruptRelayQueue, "UnregisterInterruptRelayQueue"}, {0x00150002, nullptr, "TryAcquireRight"}, - {0x00160042, nullptr, "AcquireRight"}, - {0x00170000, nullptr, "ReleaseRight"}, + {0x00160042, AcquireRight, "AcquireRight"}, + {0x00170000, ReleaseRight, "ReleaseRight"}, {0x00180000, ImportDisplayCaptureInfo, "ImportDisplayCaptureInfo"}, {0x00190000, nullptr, "SaveVramSysArea"}, {0x001A0000, nullptr, "RestoreVramSysArea"}, @@ -616,17 +700,16 @@ Interface::Interface() { Register(FunctionTable); g_interrupt_event = nullptr; - - using Kernel::MemoryPermission; - g_shared_memory = Kernel::SharedMemory::Create(0x1000, MemoryPermission::ReadWrite, - MemoryPermission::ReadWrite, "GSPSharedMem"); + g_shared_memory = nullptr; g_thread_id = 0; + gpu_right_acquired = false; } Interface::~Interface() { g_interrupt_event = nullptr; g_shared_memory = nullptr; + gpu_right_acquired = false; } } // namespace diff --git a/src/core/hle/service/gsp_gpu.h b/src/core/hle/service/gsp_gpu.h index 0e2f7a21e..3b4b678a3 100644 --- a/src/core/hle/service/gsp_gpu.h +++ b/src/core/hle/service/gsp_gpu.h @@ -10,6 +10,7 @@ #include "common/bit_field.h" #include "common/common_types.h" +#include "core/hle/result.h" #include "core/hle/service/service.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -194,7 +195,7 @@ public: */ void SignalInterrupt(InterruptId interrupt_id); -void SetBufferSwap(u32 screen_id, const FrameBufferInfo& info); +ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info); /** * Retrieves the framebuffer info stored in the GSP shared memory for the diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index cb4fd38e2..1053d0f40 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -33,6 +33,11 @@ static Kernel::SharedPtr<Kernel::Event> event_debug_pad; static u32 next_pad_index; static u32 next_touch_index; +static u32 next_accelerometer_index; +static u32 next_gyroscope_index; + +static int enable_accelerometer_count = 0; // positive means enabled +static int enable_gyroscope_count = 0; // positive means enabled const std::array<Service::HID::PadState, Settings::NativeInput::NUM_INPUTS> pad_mapping = {{ Service::HID::PAD_A, Service::HID::PAD_B, Service::HID::PAD_X, Service::HID::PAD_Y, @@ -78,17 +83,17 @@ void Update() { PadState changed = { { (state.hex ^ old_state.hex) } }; // Get the current Pad entry - PadDataEntry* pad_entry = &mem->pad.entries[mem->pad.index]; + PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; // Update entry properties - pad_entry->current_state.hex = state.hex; - pad_entry->delta_additions.hex = changed.hex & state.hex; - pad_entry->delta_removals.hex = changed.hex & old_state.hex;; + pad_entry.current_state.hex = state.hex; + pad_entry.delta_additions.hex = changed.hex & state.hex; + pad_entry.delta_removals.hex = changed.hex & old_state.hex;; // Set circle Pad - pad_entry->circle_pad_x = state.circle_left ? -MAX_CIRCLEPAD_POS : + pad_entry.circle_pad_x = state.circle_left ? -MAX_CIRCLEPAD_POS : state.circle_right ? MAX_CIRCLEPAD_POS : 0x0; - pad_entry->circle_pad_y = state.circle_down ? -MAX_CIRCLEPAD_POS : + pad_entry.circle_pad_y = state.circle_down ? -MAX_CIRCLEPAD_POS : state.circle_up ? MAX_CIRCLEPAD_POS : 0x0; // If we just updated index 0, provide a new timestamp @@ -101,11 +106,11 @@ void Update() { next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); // Get the current touch entry - TouchDataEntry* touch_entry = &mem->touch.entries[mem->touch.index]; + TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; bool pressed = false; - std::tie(touch_entry->x, touch_entry->y, pressed) = VideoCore::g_emu_window->GetTouchState(); - touch_entry->valid.Assign(pressed ? 1 : 0); + std::tie(touch_entry.x, touch_entry.y, pressed) = VideoCore::g_emu_window->GetTouchState(); + touch_entry.valid.Assign(pressed ? 1 : 0); // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which // supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being @@ -120,6 +125,58 @@ void Update() { // Signal both handles when there's an update to Pad or touch event_pad_or_touch_1->Signal(); event_pad_or_touch_2->Signal(); + + // Update accelerometer + if (enable_accelerometer_count > 0) { + mem->accelerometer.index = next_accelerometer_index; + next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); + + AccelerometerDataEntry& accelerometer_entry = mem->accelerometer.entries[mem->accelerometer.index]; + std::tie(accelerometer_entry.x, accelerometer_entry.y, accelerometer_entry.z) + = VideoCore::g_emu_window->GetAccelerometerState(); + + // Make up "raw" entry + // TODO(wwylele): + // From hardware testing, the raw_entry values are approximately, + // but not exactly, as twice as corresponding entries (or with a minus sign). + // It may caused by system calibration to the accelerometer. + // Figure out how it works, or, if no game reads raw_entry, + // the following three lines can be removed and leave raw_entry unimplemented. + mem->accelerometer.raw_entry.x = -2 * accelerometer_entry.x; + mem->accelerometer.raw_entry.z = 2 * accelerometer_entry.y; + mem->accelerometer.raw_entry.y = -2 * accelerometer_entry.z; + + // If we just updated index 0, provide a new timestamp + if (mem->accelerometer.index == 0) { + mem->accelerometer.index_reset_ticks_previous = mem->accelerometer.index_reset_ticks; + mem->accelerometer.index_reset_ticks = (s64)CoreTiming::GetTicks(); + } + + event_accelerometer->Signal(); + } + + // Update gyroscope + if (enable_gyroscope_count > 0) { + mem->gyroscope.index = next_gyroscope_index; + next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size(); + + GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; + std::tie(gyroscope_entry.x, gyroscope_entry.y, gyroscope_entry.z) + = VideoCore::g_emu_window->GetGyroscopeState(); + + // Make up "raw" entry + mem->gyroscope.raw_entry.x = gyroscope_entry.x; + mem->gyroscope.raw_entry.z = -gyroscope_entry.y; + mem->gyroscope.raw_entry.y = gyroscope_entry.z; + + // If we just updated index 0, provide a new timestamp + if (mem->gyroscope.index == 0) { + mem->gyroscope.index_reset_ticks_previous = mem->gyroscope.index_reset_ticks; + mem->gyroscope.index_reset_ticks = (s64)CoreTiming::GetTicks(); + } + + event_gyroscope->Signal(); + } } void GetIPCHandles(Service::Interface* self) { @@ -139,40 +196,69 @@ void GetIPCHandles(Service::Interface* self) { void EnableAccelerometer(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + ++enable_accelerometer_count; event_accelerometer->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); } void DisableAccelerometer(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + --enable_accelerometer_count; event_accelerometer->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); } void EnableGyroscopeLow(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + ++enable_gyroscope_count; event_gyroscope->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); } void DisableGyroscopeLow(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + --enable_gyroscope_count; event_gyroscope->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_DEBUG(Service_HID, "called"); +} + +void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + + f32 coef = VideoCore::g_emu_window->GetGyroscopeRawToDpsCoefficient(); + memcpy(&cmd_buff[2], &coef, 4); +} + +void GetGyroscopeLowCalibrateParam(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + + const s16 param_unit = 6700; // an approximate value taken from hw + GyroscopeCalibrateParam param = { + { 0, param_unit, -param_unit }, + { 0, param_unit, -param_unit }, + { 0, param_unit, -param_unit }, + }; + memcpy(&cmd_buff[2], ¶m, sizeof(param)); + LOG_WARNING(Service_HID, "(STUBBED) called"); } diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 517f4f2ae..170d19ea8 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -78,6 +78,24 @@ struct TouchDataEntry { }; /** + * Structure of a single entry of accelerometer state history within HID shared memory + */ +struct AccelerometerDataEntry { + s16 x; + s16 y; + s16 z; +}; + +/** + * Structure of a single entry of gyroscope state history within HID shared memory + */ +struct GyroscopeDataEntry { + s16 x; + s16 y; + s16 z; +}; + +/** * Structure of data stored in HID shared memory */ struct SharedMem { @@ -112,6 +130,46 @@ struct SharedMem { std::array<TouchDataEntry, 8> entries; ///< Last 8 touch entries, in pixel coordinates } touch; + + /// Accelerometer data + struct { + s64 index_reset_ticks; ///< CPU tick count for when HID module updated entry index 0 + s64 index_reset_ticks_previous; ///< Previous `index_reset_ticks` + u32 index; ///< Index of the last updated accelerometer entry + + INSERT_PADDING_WORDS(0x1); + + AccelerometerDataEntry raw_entry; + INSERT_PADDING_BYTES(2); + + std::array<AccelerometerDataEntry, 8> entries; + } accelerometer; + + /// Gyroscope data + struct { + s64 index_reset_ticks; ///< CPU tick count for when HID module updated entry index 0 + s64 index_reset_ticks_previous; ///< Previous `index_reset_ticks` + u32 index; ///< Index of the last updated accelerometer entry + + INSERT_PADDING_WORDS(0x1); + + GyroscopeDataEntry raw_entry; + INSERT_PADDING_BYTES(2); + + std::array<GyroscopeDataEntry, 32> entries; + } gyroscope; +}; + +/** + * Structure of calibrate params that GetGyroscopeLowCalibrateParam returns + */ +struct GyroscopeCalibrateParam { + struct { + // TODO (wwylele): figure out the exact meaning of these params + s16 zero_point; + s16 positive_unit_point; + s16 negative_unit_point; + } x, y, z; }; // TODO: MSVC does not support using offsetof() on non-static data members even though this @@ -222,6 +280,26 @@ void DisableGyroscopeLow(Interface* self); */ void GetSoundVolume(Interface* self); +/** + * HID::GetGyroscopeLowRawToDpsCoefficient service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : float output value + */ +void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self); + +/** + * HID::GetGyroscopeLowCalibrateParam service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2~6 (18 bytes) : struct GyroscopeCalibrateParam + */ +void GetGyroscopeLowCalibrateParam(Service::Interface* self); + /// Checks for user input updates void Update(); diff --git a/src/core/hle/service/hid/hid_spvr.cpp b/src/core/hle/service/hid/hid_spvr.cpp index c50f597eb..046e65b11 100644 --- a/src/core/hle/service/hid/hid_spvr.cpp +++ b/src/core/hle/service/hid/hid_spvr.cpp @@ -9,16 +9,16 @@ namespace Service { namespace HID { const Interface::FunctionInfo FunctionTable[] = { - {0x000A0000, GetIPCHandles, "GetIPCHandles"}, - {0x000B0000, nullptr, "StartAnalogStickCalibration"}, - {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, - {0x00110000, EnableAccelerometer, "EnableAccelerometer"}, - {0x00120000, DisableAccelerometer, "DisableAccelerometer"}, - {0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, - {0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"}, - {0x00150000, nullptr, "GetGyroscopeLowRawToDpsCoefficient"}, - {0x00160000, nullptr, "GetGyroscopeLowCalibrateParam"}, - {0x00170000, GetSoundVolume, "GetSoundVolume"}, + {0x000A0000, GetIPCHandles, "GetIPCHandles"}, + {0x000B0000, nullptr, "StartAnalogStickCalibration"}, + {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, + {0x00110000, EnableAccelerometer, "EnableAccelerometer"}, + {0x00120000, DisableAccelerometer, "DisableAccelerometer"}, + {0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, + {0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"}, + {0x00150000, GetGyroscopeLowRawToDpsCoefficient, "GetGyroscopeLowRawToDpsCoefficient"}, + {0x00160000, GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"}, + {0x00170000, GetSoundVolume, "GetSoundVolume"}, }; HID_SPVR_Interface::HID_SPVR_Interface() { diff --git a/src/core/hle/service/hid/hid_user.cpp b/src/core/hle/service/hid/hid_user.cpp index bbdde2abb..bb157b83d 100644 --- a/src/core/hle/service/hid/hid_user.cpp +++ b/src/core/hle/service/hid/hid_user.cpp @@ -9,16 +9,16 @@ namespace Service { namespace HID { const Interface::FunctionInfo FunctionTable[] = { - {0x000A0000, GetIPCHandles, "GetIPCHandles"}, - {0x000B0000, nullptr, "StartAnalogStickCalibration"}, - {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, - {0x00110000, EnableAccelerometer, "EnableAccelerometer"}, - {0x00120000, DisableAccelerometer, "DisableAccelerometer"}, - {0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, - {0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"}, - {0x00150000, nullptr, "GetGyroscopeLowRawToDpsCoefficient"}, - {0x00160000, nullptr, "GetGyroscopeLowCalibrateParam"}, - {0x00170000, GetSoundVolume, "GetSoundVolume"}, + {0x000A0000, GetIPCHandles, "GetIPCHandles"}, + {0x000B0000, nullptr, "StartAnalogStickCalibration"}, + {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, + {0x00110000, EnableAccelerometer, "EnableAccelerometer"}, + {0x00120000, DisableAccelerometer, "DisableAccelerometer"}, + {0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, + {0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"}, + {0x00150000, GetGyroscopeLowRawToDpsCoefficient, "GetGyroscopeLowRawToDpsCoefficient"}, + {0x00160000, GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"}, + {0x00170000, GetSoundVolume, "GetSoundVolume"}, }; HID_U_Interface::HID_U_Interface() { diff --git a/src/core/hle/service/ndm/ndm.cpp b/src/core/hle/service/ndm/ndm.cpp index 47076a7b8..bc9c3413d 100644 --- a/src/core/hle/service/ndm/ndm.cpp +++ b/src/core/hle/service/ndm/ndm.cpp @@ -11,28 +11,217 @@ namespace Service { namespace NDM { -void SuspendDaemons(Service::Interface* self) { +enum : u32 { + DEFAULT_RETRY_INTERVAL = 10, + DEFAULT_SCAN_INTERVAL = 30 +}; + +static DaemonMask daemon_bit_mask = DaemonMask::Default; +static DaemonMask default_daemon_bit_mask = DaemonMask::Default; +static std::array<DaemonStatus, 4> daemon_status = { DaemonStatus::Idle, DaemonStatus::Idle, DaemonStatus::Idle, DaemonStatus::Idle }; +static ExclusiveState exclusive_state = ExclusiveState::None; +static u32 scan_interval = DEFAULT_SCAN_INTERVAL; +static u32 retry_interval = DEFAULT_RETRY_INTERVAL; +static bool daemon_lock_enabled = false; + +void EnterExclusiveState(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + exclusive_state = static_cast<ExclusiveState>(cmd_buff[1]); + + cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NDM, "(STUBBED) exclusive_state=0x%08X ", exclusive_state); +} + +void LeaveExclusiveState(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + exclusive_state = ExclusiveState::None; + + cmd_buff[0] = IPC::MakeHeader(0x2, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NDM, "(STUBBED) exclusive_state=0x%08X ", exclusive_state); +} + +void QueryExclusiveMode(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - LOG_WARNING(Service_NDM, "(STUBBED) bit_mask=0x%08X ", cmd_buff[1]); + cmd_buff[0] = IPC::MakeHeader(0x3, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = static_cast<u32>(exclusive_state); + LOG_WARNING(Service_NDM, "(STUBBED) exclusive_state=0x%08X ", exclusive_state); +} + +void LockState(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + daemon_lock_enabled = true; + + cmd_buff[0] = IPC::MakeHeader(0x4, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NDM, "(STUBBED) daemon_lock_enabled=0x%08X ", daemon_lock_enabled); +} + +void UnlockState(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + daemon_lock_enabled = false; + cmd_buff[0] = IPC::MakeHeader(0x5, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NDM, "(STUBBED) daemon_lock_enabled=0x%08X ", daemon_lock_enabled); +} + +void SuspendDaemons(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 bit_mask = cmd_buff[1] & 0xF; + daemon_bit_mask = static_cast<DaemonMask>(static_cast<u32>(default_daemon_bit_mask) & ~bit_mask); + for (size_t index = 0; index < daemon_status.size(); ++index) { + if (bit_mask & (1 << index)) { + daemon_status[index] = DaemonStatus::Suspended; + } + } + + cmd_buff[0] = IPC::MakeHeader(0x6, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NDM, "(STUBBED) daemon_bit_mask=0x%08X ", daemon_bit_mask); } void ResumeDaemons(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 bit_mask = cmd_buff[1] & 0xF; + daemon_bit_mask = static_cast<DaemonMask>(static_cast<u32>(daemon_bit_mask) | bit_mask); + for (size_t index = 0; index < daemon_status.size(); ++index) { + if (bit_mask & (1 << index)) { + daemon_status[index] = DaemonStatus::Idle; + } + } + + cmd_buff[0] = IPC::MakeHeader(0x7, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NDM, "(STUBBED) daemon_bit_mask=0x%08X ", daemon_bit_mask); +} + +void SuspendScheduler(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x8, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NDM, "(STUBBED) called"); +} + +void ResumeScheduler(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x9, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NDM, "(STUBBED) called"); +} + +void QueryStatus(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 daemon = cmd_buff[1] & 0xF; - LOG_WARNING(Service_NDM, "(STUBBED) bit_mask=0x%08X ", cmd_buff[1]); + cmd_buff[0] = IPC::MakeHeader(0xD, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = static_cast<u32>(daemon_status.at(daemon)); + LOG_WARNING(Service_NDM, "(STUBBED) daemon=0x%08X, daemon_status=0x%08X", daemon, cmd_buff[2]); +} + +void GetDaemonDisableCount(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 daemon = cmd_buff[1] & 0xF; + + cmd_buff[0] = IPC::MakeHeader(0xE, 3, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 0; + cmd_buff[3] = 0; + LOG_WARNING(Service_NDM, "(STUBBED) daemon=0x%08X", daemon); +} + +void GetSchedulerDisableCount(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0xF, 3, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 0; + cmd_buff[3] = 0; + LOG_WARNING(Service_NDM, "(STUBBED) called"); +} + +void SetScanInterval(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + scan_interval = cmd_buff[1]; + cmd_buff[0] = IPC::MakeHeader(0x10, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NDM, "(STUBBED) scan_interval=0x%08X ", scan_interval); +} + +void GetScanInterval(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x11, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = scan_interval; + LOG_WARNING(Service_NDM, "(STUBBED) scan_interval=0x%08X ", scan_interval); +} + +void SetRetryInterval(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + retry_interval = cmd_buff[1]; + + cmd_buff[0] = IPC::MakeHeader(0x12, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NDM, "(STUBBED) retry_interval=0x%08X ", retry_interval); +} + +void GetRetryInterval(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x13, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = retry_interval; + LOG_WARNING(Service_NDM, "(STUBBED) retry_interval=0x%08X ", retry_interval); } void OverrideDefaultDaemons(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 bit_mask = cmd_buff[1] & 0xF; + default_daemon_bit_mask = static_cast<DaemonMask>(bit_mask); + daemon_bit_mask = default_daemon_bit_mask; + for (size_t index = 0; index < daemon_status.size(); ++index) { + if (bit_mask & (1 << index)) { + daemon_status[index] = DaemonStatus::Idle; + } + } - LOG_WARNING(Service_NDM, "(STUBBED) bit_mask=0x%08X ", cmd_buff[1]); + cmd_buff[0] = IPC::MakeHeader(0x14, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NDM, "(STUBBED) default_daemon_bit_mask=0x%08X ", default_daemon_bit_mask); +} + +void ResetDefaultDaemons(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + default_daemon_bit_mask = DaemonMask::Default; + + cmd_buff[0] = IPC::MakeHeader(0x15, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NDM, "(STUBBED) default_daemon_bit_mask=0x%08X ", default_daemon_bit_mask); +} + +void GetDefaultDaemons(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x16, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = static_cast<u32>(default_daemon_bit_mask); + LOG_WARNING(Service_NDM, "(STUBBED) default_daemon_bit_mask=0x%08X ", default_daemon_bit_mask); +} + +void ClearHalfAwakeMacFilter(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[0] = IPC::MakeHeader(0x17, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NDM, "(STUBBED) called"); } void Init() { diff --git a/src/core/hle/service/ndm/ndm.h b/src/core/hle/service/ndm/ndm.h index 734730f8c..5c2b968dc 100644 --- a/src/core/hle/service/ndm/ndm.h +++ b/src/core/hle/service/ndm/ndm.h @@ -12,10 +12,91 @@ class Interface; namespace NDM { +enum class Daemon : u32 { + Cec = 0, + Boss = 1, + Nim = 2, + Friend = 3 +}; + +enum class DaemonMask : u32 { + None = 0, + Cec = (1 << static_cast<u32>(Daemon::Cec)), + Boss = (1 << static_cast<u32>(Daemon::Boss)), + Nim = (1 << static_cast<u32>(Daemon::Nim)), + Friend = (1 << static_cast<u32>(Daemon::Friend)), + Default = Cec | Friend, + All = Cec | Boss | Nim | Friend +}; + +enum class DaemonStatus : u32 { + Busy = 0, + Idle = 1, + Suspending = 2, + Suspended = 3 +}; + +enum class ExclusiveState : u32 { + None = 0, + Infrastructure = 1, + LocalCommunications = 2, + Streetpass = 3, + StreetpassData = 4, +}; + +/** + * NDM::EnterExclusiveState service function + * Inputs: + * 0 : Header code [0x00010042] + * 1 : Exclusive State + * 2 : 0x20 + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void EnterExclusiveState(Service::Interface* self); + +/** + * NDM::LeaveExclusiveState service function + * Inputs: + * 0 : Header code [0x00020002] + * 1 : 0x20 + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void LeaveExclusiveState(Service::Interface* self); + +/** + * NDM::QueryExclusiveMode service function + * Inputs: + * 0 : Header code [0x00030000] + * Outputs: + * 1 : Result, 0 on success, otherwise error code + * 2 : Current Exclusive State + */ +void QueryExclusiveMode(Service::Interface* self); + +/** + * NDM::LockState service function + * Inputs: + * 0 : Header code [0x00040002] + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void LockState(Service::Interface* self); + +/** + * NDM::UnlockState service function + * Inputs: + * 0 : Header code [0x00050002] + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void UnlockState(Service::Interface* self); + /** - * SuspendDaemons + * NDM::SuspendDaemons service function * Inputs: - * 0 : Command header (0x00020082) + * 0 : Header code [0x00060040] * 1 : Daemon bit mask * Outputs: * 1 : Result, 0 on success, otherwise error code @@ -23,9 +104,9 @@ namespace NDM { void SuspendDaemons(Service::Interface* self); /** - * ResumeDaemons + * NDM::ResumeDaemons service function * Inputs: - * 0 : Command header (0x00020082) + * 0 : Header code [0x00070040] * 1 : Daemon bit mask * Outputs: * 1 : Result, 0 on success, otherwise error code @@ -33,15 +114,138 @@ void SuspendDaemons(Service::Interface* self); void ResumeDaemons(Service::Interface* self); /** - * OverrideDefaultDaemons + * NDM::SuspendScheduler service function * Inputs: - * 0 : Command header (0x00020082) + * 0 : Header code [0x00080040] + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void SuspendScheduler(Service::Interface* self); + +/** + * NDM::ResumeScheduler service function + * Inputs: + * 0 : Header code [0x00090000] + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void ResumeScheduler(Service::Interface* self); + +/** + * NDM::QueryStatus service function + * Inputs: + * 0 : Header code [0x000D0040] + * 1 : Daemon + * Outputs: + * 1 : Result, 0 on success, otherwise error code + * 2 : Daemon status + */ +void QueryStatus(Service::Interface* self); + +/** + * NDM::GetDaemonDisableCount service function + * Inputs: + * 0 : Header code [0x000E0040] + * 1 : Daemon + * Outputs: + * 1 : Result, 0 on success, otherwise error code + * 2 : Current process disable count + * 3 : Total disable count + */ +void GetDaemonDisableCount(Service::Interface* self); + +/** + * NDM::GetSchedulerDisableCount service function + * Inputs: + * 0 : Header code [0x000F0000] + * Outputs: + * 1 : Result, 0 on success, otherwise error code + * 2 : Current process disable count + * 3 : Total disable count + */ +void GetSchedulerDisableCount(Service::Interface* self); + +/** + * NDM::SetScanInterval service function + * Inputs: + * 0 : Header code [0x00100040] + * 1 : Interval (default = 30) + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void SetScanInterval(Service::Interface* self); + +/** + * NDM::GetScanInterval service function + * Inputs: + * 0 : Header code [0x00110000] + * Outputs: + * 1 : Result, 0 on success, otherwise error code + * 2 : Interval (default = 30) + */ +void GetScanInterval(Service::Interface* self); + +/** + * NDM::SetRetryInterval service function + * Inputs: + * 0 : Header code [0x00120040] + * 1 : Interval (default = 10) + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void SetRetryInterval(Service::Interface* self); + +/** + * NDM::GetRetryInterval service function + * Inputs: + * 0 : Header code [0x00130000] + * Outputs: + * 1 : Result, 0 on success, otherwise error code + * 2 : Interval (default = 10) + */ +void GetRetryInterval(Service::Interface* self); + + +/** + * NDM::OverrideDefaultDaemons service function + * Inputs: + * 0 : Header code [0x00140040] * 1 : Daemon bit mask * Outputs: * 1 : Result, 0 on success, otherwise error code */ void OverrideDefaultDaemons(Service::Interface* self); +/** + * NDM::ResetDefaultDaemons service function + * Inputs: + * 0 : Header code [0x00150000] + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void ResetDefaultDaemons(Service::Interface* self); + +/** + * NDM::GetDefaultDaemons service function + * Inputs: + * 0 : Header code [0x00160000] + * Outputs: + * 1 : Result, 0 on success, otherwise error code + * 2 : Daemon bit mask + * Note: + * Gets the current default daemon bit mask. The default value is (DAEMONMASK_CEC | DAEMONMASK_FRIENDS) + */ +void GetDefaultDaemons(Service::Interface* self); + +/** + * NDM::ClearHalfAwakeMacFilter service function + * Inputs: + * 0 : Header code [0x00170000] + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ +void ClearHalfAwakeMacFilter(Service::Interface* self); + /// Initialize NDM service void Init(); diff --git a/src/core/hle/service/ndm/ndm_u.cpp b/src/core/hle/service/ndm/ndm_u.cpp index bf95cc7aa..3ff0744ee 100644 --- a/src/core/hle/service/ndm/ndm_u.cpp +++ b/src/core/hle/service/ndm/ndm_u.cpp @@ -9,29 +9,29 @@ namespace Service { namespace NDM { const Interface::FunctionInfo FunctionTable[] = { - {0x00010042, nullptr, "EnterExclusiveState"}, - {0x00020002, nullptr, "LeaveExclusiveState"}, - {0x00030000, nullptr, "QueryExclusiveMode"}, - {0x00040002, nullptr, "LockState"}, - {0x00050002, nullptr, "UnlockState"}, + {0x00010042, EnterExclusiveState, "EnterExclusiveState"}, + {0x00020002, LeaveExclusiveState, "LeaveExclusiveState"}, + {0x00030000, QueryExclusiveMode, "QueryExclusiveMode"}, + {0x00040002, LockState, "LockState"}, + {0x00050002, UnlockState, "UnlockState"}, {0x00060040, SuspendDaemons, "SuspendDaemons"}, {0x00070040, ResumeDaemons, "ResumeDaemons"}, - {0x00080040, nullptr, "DisableWifiUsage"}, - {0x00090000, nullptr, "EnableWifiUsage"}, + {0x00080040, SuspendScheduler, "SuspendScheduler"}, + {0x00090000, ResumeScheduler, "ResumeScheduler"}, {0x000A0000, nullptr, "GetCurrentState"}, {0x000B0000, nullptr, "GetTargetState"}, {0x000C0000, nullptr, "<Stubbed>"}, - {0x000D0040, nullptr, "QueryStatus"}, - {0x000E0040, nullptr, "GetDaemonDisableCount"}, - {0x000F0000, nullptr, "GetSchedulerDisableCount"}, - {0x00100040, nullptr, "SetScanInterval"}, - {0x00110000, nullptr, "GetScanInterval"}, - {0x00120040, nullptr, "SetRetryInterval"}, - {0x00130000, nullptr, "GetRetryInterval"}, + {0x000D0040, QueryStatus, "QueryStatus"}, + {0x000E0040, GetDaemonDisableCount, "GetDaemonDisableCount"}, + {0x000F0000, GetSchedulerDisableCount,"GetSchedulerDisableCount"}, + {0x00100040, SetScanInterval, "SetScanInterval"}, + {0x00110000, GetScanInterval, "GetScanInterval"}, + {0x00120040, SetRetryInterval, "SetRetryInterval"}, + {0x00130000, GetRetryInterval, "GetRetryInterval"}, {0x00140040, OverrideDefaultDaemons, "OverrideDefaultDaemons"}, - {0x00150000, nullptr, "ResetDefaultDaemons"}, - {0x00160000, nullptr, "GetDefaultDaemons"}, - {0x00170000, nullptr, "ClearHalfAwakeMacFilter"}, + {0x00150000, ResetDefaultDaemons, "ResetDefaultDaemons"}, + {0x00160000, GetDefaultDaemons, "GetDefaultDaemons"}, + {0x00170000, ClearHalfAwakeMacFilter, "ClearHalfAwakeMacFilter"}, }; NDM_U_Interface::NDM_U_Interface() { diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 35b648409..0fe3a4d7a 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -9,6 +9,7 @@ #include "core/hle/service/ac_u.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" @@ -70,9 +71,8 @@ ResultVal<bool> Interface::SyncRequest() { // TODO(bunnei): Hack - ignore error cmd_buff[1] = 0; return MakeResult<bool>(false); - } else { - LOG_TRACE(Service, "%s", MakeFunctionString(itr->second.name, GetPortName().c_str(), cmd_buff).c_str()); } + LOG_TRACE(Service, "%s", MakeFunctionString(itr->second.name, GetPortName().c_str(), cmd_buff).c_str()); itr->second.func(this); @@ -121,6 +121,7 @@ void Init() { AddService(new AC_U::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); diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp index b52e52d4a..d3e5d4bca 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -5,6 +5,7 @@ #include <algorithm> #include <cstring> #include <unordered_map> +#include <vector> #include "common/assert.h" #include "common/bit_field.h" @@ -150,6 +151,34 @@ static int TranslateError(int error) { return error; } +/// Holds the translation from system network socket options to 3DS network socket options +/// Note: -1 = No effect/unavailable +static const std::unordered_map<int, int> sockopt_map = { { + { 0x0004, SO_REUSEADDR }, + { 0x0080, -1 }, + { 0x0100, -1 }, + { 0x1001, SO_SNDBUF }, + { 0x1002, SO_RCVBUF }, + { 0x1003, -1 }, +#ifdef _WIN32 + /// Unsupported in WinSock2 + { 0x1004, -1 }, +#else + { 0x1004, SO_RCVLOWAT }, +#endif + { 0x1008, SO_TYPE }, + { 0x1009, SO_ERROR }, +}}; + +/// Converts a socket option from 3ds-specific to platform-specific +static int TranslateSockOpt(int console_opt_name) { + auto found = sockopt_map.find(console_opt_name); + if (found != sockopt_map.end()) { + return found->second; + } + return console_opt_name; +} + /// Holds information about a particular socket struct SocketHolder { u32 socket_fd; ///< The socket descriptor @@ -567,7 +596,7 @@ static void RecvFrom(Service::Interface* self) { socklen_t src_addr_len = sizeof(src_addr); int ret = ::recvfrom(socket_handle, (char*)output_buff, len, flags, &src_addr, &src_addr_len); - if (buffer_parameters.output_src_address_buffer != 0) { + 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); } @@ -593,17 +622,13 @@ static void Poll(Service::Interface* self) { // The 3ds_pollfd and the pollfd structures may be different (Windows/Linux have different sizes) // so we have to copy the data - pollfd* platform_pollfd = new pollfd[nfds]; - for (unsigned current_fds = 0; current_fds < nfds; ++current_fds) - platform_pollfd[current_fds] = CTRPollFD::ToPlatform(input_fds[current_fds]); + std::vector<pollfd> platform_pollfd(nfds); + std::transform(input_fds, input_fds + nfds, platform_pollfd.begin(), CTRPollFD::ToPlatform); - int ret = ::poll(platform_pollfd, nfds, timeout); + const int ret = ::poll(platform_pollfd.data(), nfds, timeout); // Now update the output pollfd structure - for (unsigned current_fds = 0; current_fds < nfds; ++current_fds) - output_fds[current_fds] = CTRPollFD::FromPlatform(platform_pollfd[current_fds]); - - delete[] platform_pollfd; + std::transform(platform_pollfd.begin(), platform_pollfd.end(), output_fds, CTRPollFD::FromPlatform); int result = 0; if (ret == SOCKET_ERROR_VALUE) @@ -727,6 +752,72 @@ static void ShutdownSockets(Service::Interface* self) { cmd_buffer[1] = 0; } +static void GetSockOpt(Service::Interface* self) { + u32* cmd_buffer = Kernel::GetCommandBuffer(); + u32 socket_handle = cmd_buffer[1]; + u32 level = cmd_buffer[2]; + int optname = TranslateSockOpt(cmd_buffer[3]); + socklen_t optlen = (socklen_t)cmd_buffer[4]; + + int ret = -1; + int err = 0; + + if(optname < 0) { +#ifdef _WIN32 + err = WSAEINVAL; +#else + err = EINVAL; +#endif + } else { + // 0x100 = static buffer offset (bytes) + // + 0x4 = 2nd pointer (u32) position + // >> 2 = convert to u32 offset instead of byte offset (cmd_buffer = u32*) + char* optval = reinterpret_cast<char *>(Memory::GetPointer(cmd_buffer[0x104 >> 2])); + + ret = ::getsockopt(socket_handle, level, optname, optval, &optlen); + err = 0; + if (ret == SOCKET_ERROR_VALUE) { + err = TranslateError(GET_ERRNO); + } + } + + cmd_buffer[0] = IPC::MakeHeader(0x11, 4, 2); + cmd_buffer[1] = ret; + cmd_buffer[2] = err; + cmd_buffer[3] = optlen; +} + +static void SetSockOpt(Service::Interface* self) { + u32* cmd_buffer = Kernel::GetCommandBuffer(); + u32 socket_handle = cmd_buffer[1]; + u32 level = cmd_buffer[2]; + int optname = TranslateSockOpt(cmd_buffer[3]); + + int ret = -1; + int err = 0; + + if(optname < 0) { +#ifdef _WIN32 + err = WSAEINVAL; +#else + err = EINVAL; +#endif + } else { + socklen_t optlen = static_cast<socklen_t>(cmd_buffer[4]); + const char* optval = reinterpret_cast<const char *>(Memory::GetPointer(cmd_buffer[8])); + + ret = static_cast<u32>(::setsockopt(socket_handle, level, optname, optval, optlen)); + err = 0; + if (ret == SOCKET_ERROR_VALUE) { + err = TranslateError(GET_ERRNO); + } + } + + cmd_buffer[0] = IPC::MakeHeader(0x12, 4, 4); + cmd_buffer[1] = ret; + cmd_buffer[2] = err; +} + const Interface::FunctionInfo FunctionTable[] = { {0x00010044, InitializeSockets, "InitializeSockets"}, {0x000200C2, Socket, "Socket"}, @@ -744,8 +835,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x000E00C2, nullptr, "GetHostByAddr"}, {0x000F0106, nullptr, "GetAddrInfo"}, {0x00100102, nullptr, "GetNameInfo"}, - {0x00110102, nullptr, "GetSockOpt"}, - {0x00120104, nullptr, "SetSockOpt"}, + {0x00110102, GetSockOpt, "GetSockOpt"}, + {0x00120104, SetSockOpt, "SetSockOpt"}, {0x001300C2, Fcntl, "Fcntl"}, {0x00140084, Poll, "Poll"}, {0x00150042, nullptr, "SockAtMark"}, diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp index 22f373adf..d16578f87 100644 --- a/src/core/hle/service/y2r_u.cpp +++ b/src/core/hle/service/y2r_u.cpp @@ -4,6 +4,7 @@ #include <cstring> +#include "common/common_funcs.h" #include "common/common_types.h" #include "common/logging/log.h" @@ -12,9 +13,6 @@ #include "core/hle/service/y2r_u.h" #include "core/hw/y2r.h" -#include "video_core/renderer_base.h" -#include "video_core/video_core.h" - //////////////////////////////////////////////////////////////////////////////////////////////////// // Namespace Y2R_U @@ -28,13 +26,17 @@ struct ConversionParameters { u16 input_line_width; u16 input_lines; StandardCoefficient standard_coefficient; - u8 reserved; + u8 padding; u16 alpha; }; static_assert(sizeof(ConversionParameters) == 12, "ConversionParameters struct has incorrect size"); static Kernel::SharedPtr<Kernel::Event> completion_event; static ConversionConfiguration conversion; +static DitheringWeightParams dithering_weight_params; +static u32 temporal_dithering_enabled = 0; +static u32 transfer_end_interrupt_enabled = 0; +static u32 spacial_dithering_enabled = 0; static const CoefficientSet standard_coefficients[4] = { {{ 0x100, 0x166, 0xB6, 0x58, 0x1C5, -0x166F, 0x10EE, -0x1C5B }}, // ITU_Rec601 @@ -73,7 +75,7 @@ ResultCode ConversionConfiguration::SetInputLines(u16 lines) { ResultCode ConversionConfiguration::SetStandardCoefficient(StandardCoefficient standard_coefficient) { size_t index = static_cast<size_t>(standard_coefficient); - if (index >= 4) { + if (index >= ARRAY_SIZE(standard_coefficients)) { return ResultCode(ErrorDescription::InvalidEnumValue, ErrorModule::CAM, ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E053ED } @@ -86,44 +88,183 @@ static void SetInputFormat(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.input_format = static_cast<InputFormat>(cmd_buff[1]); + + cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_DEBUG(Service_Y2R, "called input_format=%hhu", conversion.input_format); +} + +static void GetInputFormat(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[0] = IPC::MakeHeader(0x2, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = static_cast<u32>(conversion.input_format); + + LOG_DEBUG(Service_Y2R, "called input_format=%hhu", conversion.input_format); } static void SetOutputFormat(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.output_format = static_cast<OutputFormat>(cmd_buff[1]); + + cmd_buff[0] = IPC::MakeHeader(0x3, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_DEBUG(Service_Y2R, "called output_format=%hhu", conversion.output_format); +} + +static void GetOutputFormat(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[0] = IPC::MakeHeader(0x4, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = static_cast<u32>(conversion.output_format); + + LOG_DEBUG(Service_Y2R, "called output_format=%hhu", conversion.output_format); } static void SetRotation(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.rotation = static_cast<Rotation>(cmd_buff[1]); + + cmd_buff[0] = IPC::MakeHeader(0x5, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_DEBUG(Service_Y2R, "called rotation=%hhu", conversion.rotation); +} + +static void GetRotation(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[0] = IPC::MakeHeader(0x6, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = static_cast<u32>(conversion.rotation); + + LOG_DEBUG(Service_Y2R, "called rotation=%hhu", conversion.rotation); } static void SetBlockAlignment(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.block_alignment = static_cast<BlockAlignment>(cmd_buff[1]); - LOG_DEBUG(Service_Y2R, "called alignment=%hhu", conversion.block_alignment); + cmd_buff[0] = IPC::MakeHeader(0x7, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_DEBUG(Service_Y2R, "called block_alignment=%hhu", conversion.block_alignment); +} + +static void GetBlockAlignment(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x8, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = static_cast<u32>(conversion.block_alignment); + + LOG_DEBUG(Service_Y2R, "called block_alignment=%hhu", conversion.block_alignment); +} + +/** + * Y2R_U::SetSpacialDithering service function + * Inputs: + * 1 : u8, 0 = Disabled, 1 = Enabled + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void SetSpacialDithering(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + spacial_dithering_enabled = cmd_buff[1] & 0xF; + + cmd_buff[0] = IPC::MakeHeader(0x9, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_Y2R, "(STUBBED) called"); +} + +/** + * Y2R_U::GetSpacialDithering service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : u8, 0 = Disabled, 1 = Enabled + */ +static void GetSpacialDithering(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0xA, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = spacial_dithering_enabled; + + LOG_WARNING(Service_Y2R, "(STUBBED) called"); +} + +/** + * Y2R_U::SetTemporalDithering service function + * Inputs: + * 1 : u8, 0 = Disabled, 1 = Enabled + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void SetTemporalDithering(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + temporal_dithering_enabled = cmd_buff[1] & 0xF; + + cmd_buff[0] = IPC::MakeHeader(0xB, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_Y2R, "(STUBBED) called"); } +/** + * Y2R_U::GetTemporalDithering service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : u8, 0 = Disabled, 1 = Enabled + */ +static void GetTemporalDithering(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0xC, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = temporal_dithering_enabled; + + LOG_WARNING(Service_Y2R, "(STUBBED) called"); +} + +/** + * Y2R_U::SetTransferEndInterrupt service function + * Inputs: + * 1 : u8, 0 = Disabled, 1 = Enabled + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ static void SetTransferEndInterrupt(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + transfer_end_interrupt_enabled = cmd_buff[1] & 0xf; cmd_buff[0] = IPC::MakeHeader(0xD, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_DEBUG(Service_Y2R, "(STUBBED) called"); + + LOG_WARNING(Service_Y2R, "(STUBBED) called"); +} + +/** + * Y2R_U::GetTransferEndInterrupt service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : u8, 0 = Disabled, 1 = Enabled + */ +static void GetTransferEndInterrupt(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0xE, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = transfer_end_interrupt_enabled; + + LOG_WARNING(Service_Y2R, "(STUBBED) called"); } /** @@ -135,8 +276,10 @@ static void SetTransferEndInterrupt(Service::Interface* self) { static void GetTransferEndEvent(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom(); + LOG_DEBUG(Service_Y2R, "called"); } @@ -147,12 +290,12 @@ static void SetSendingY(Service::Interface* self) { conversion.src_Y.image_size = cmd_buff[2]; conversion.src_Y.transfer_unit = cmd_buff[3]; conversion.src_Y.gap = cmd_buff[4]; - u32 src_process_handle = cmd_buff[6]; - LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " - "src_process_handle=0x%08X", conversion.src_Y.image_size, - conversion.src_Y.transfer_unit, conversion.src_Y.gap, src_process_handle); + cmd_buff[0] = IPC::MakeHeader(0x10, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, src_process_handle=0x%08X", + conversion.src_Y.image_size, conversion.src_Y.transfer_unit, conversion.src_Y.gap, cmd_buff[6]); } static void SetSendingU(Service::Interface* self) { @@ -162,12 +305,12 @@ static void SetSendingU(Service::Interface* self) { conversion.src_U.image_size = cmd_buff[2]; conversion.src_U.transfer_unit = cmd_buff[3]; conversion.src_U.gap = cmd_buff[4]; - u32 src_process_handle = cmd_buff[6]; - LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " - "src_process_handle=0x%08X", conversion.src_U.image_size, - conversion.src_U.transfer_unit, conversion.src_U.gap, src_process_handle); + cmd_buff[0] = IPC::MakeHeader(0x11, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, src_process_handle=0x%08X", + conversion.src_U.image_size, conversion.src_U.transfer_unit, conversion.src_U.gap, cmd_buff[6]); } static void SetSendingV(Service::Interface* self) { @@ -177,12 +320,12 @@ static void SetSendingV(Service::Interface* self) { conversion.src_V.image_size = cmd_buff[2]; conversion.src_V.transfer_unit = cmd_buff[3]; conversion.src_V.gap = cmd_buff[4]; - u32 src_process_handle = cmd_buff[6]; - LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " - "src_process_handle=0x%08X", conversion.src_V.image_size, - conversion.src_V.transfer_unit, conversion.src_V.gap, src_process_handle); + cmd_buff[0] = IPC::MakeHeader(0x12, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, src_process_handle=0x%08X", + conversion.src_V.image_size, conversion.src_V.transfer_unit, conversion.src_V.gap, cmd_buff[6]); } static void SetSendingYUYV(Service::Interface* self) { @@ -192,12 +335,76 @@ static void SetSendingYUYV(Service::Interface* self) { conversion.src_YUYV.image_size = cmd_buff[2]; conversion.src_YUYV.transfer_unit = cmd_buff[3]; conversion.src_YUYV.gap = cmd_buff[4]; - u32 src_process_handle = cmd_buff[6]; - LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " - "src_process_handle=0x%08X", conversion.src_YUYV.image_size, - conversion.src_YUYV.transfer_unit, conversion.src_YUYV.gap, src_process_handle); + cmd_buff[0] = IPC::MakeHeader(0x13, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, src_process_handle=0x%08X", + conversion.src_YUYV.image_size, conversion.src_YUYV.transfer_unit, conversion.src_YUYV.gap, cmd_buff[6]); +} + +/** + * Y2R::IsFinishedSendingYuv service function + * Output: + * 1 : Result of the function, 0 on success, otherwise error code + * 2 : u8, 0 = Not Finished, 1 = Finished + */ +static void IsFinishedSendingYuv(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x14, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = 1; + + LOG_WARNING(Service_Y2R, "(STUBBED) called"); +} + +/** + * Y2R::IsFinishedSendingY service function + * Output: + * 1 : Result of the function, 0 on success, otherwise error code + * 2 : u8, 0 = Not Finished, 1 = Finished + */ +static void IsFinishedSendingY(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x15, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = 1; + + LOG_WARNING(Service_Y2R, "(STUBBED) called"); +} + +/** + * Y2R::IsFinishedSendingU service function + * Output: + * 1 : Result of the function, 0 on success, otherwise error code + * 2 : u8, 0 = Not Finished, 1 = Finished + */ +static void IsFinishedSendingU(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x16, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = 1; + + LOG_WARNING(Service_Y2R, "(STUBBED) called"); +} + +/** + * Y2R::IsFinishedSendingV service function + * Output: + * 1 : Result of the function, 0 on success, otherwise error code + * 2 : u8, 0 = Not Finished, 1 = Finished + */ +static void IsFinishedSendingV(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x17, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = 1; + + LOG_WARNING(Service_Y2R, "(STUBBED) called"); } static void SetReceiving(Service::Interface* self) { @@ -207,27 +414,66 @@ static void SetReceiving(Service::Interface* self) { conversion.dst.image_size = cmd_buff[2]; conversion.dst.transfer_unit = cmd_buff[3]; conversion.dst.gap = cmd_buff[4]; - u32 dst_process_handle = cmd_buff[6]; - LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " - "dst_process_handle=0x%08X", conversion.dst.image_size, - conversion.dst.transfer_unit, conversion.dst.gap, - dst_process_handle); + cmd_buff[0] = IPC::MakeHeader(0x18, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, dst_process_handle=0x%08X", + conversion.dst.image_size, conversion.dst.transfer_unit, conversion.dst.gap, cmd_buff[6]); +} + +/** + * Y2R::IsFinishedReceiving service function + * Output: + * 1 : Result of the function, 0 on success, otherwise error code + * 2 : u8, 0 = Not Finished, 1 = Finished + */ +static void IsFinishedReceiving(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x19, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = 1; + + LOG_WARNING(Service_Y2R, "(STUBBED) called"); } static void SetInputLineWidth(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - LOG_DEBUG(Service_Y2R, "called input_line_width=%u", cmd_buff[1]); + cmd_buff[0] = IPC::MakeHeader(0x1A, 1, 0); cmd_buff[1] = conversion.SetInputLineWidth(cmd_buff[1]).raw; + + LOG_DEBUG(Service_Y2R, "called input_line_width=%u", cmd_buff[1]); +} + +static void GetInputLineWidth(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x1B, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = conversion.input_line_width; + + LOG_DEBUG(Service_Y2R, "called input_line_width=%u", conversion.input_line_width); } static void SetInputLines(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - LOG_DEBUG(Service_Y2R, "called input_line_number=%u", cmd_buff[1]); + cmd_buff[0] = IPC::MakeHeader(0x1C, 1, 0); cmd_buff[1] = conversion.SetInputLines(cmd_buff[1]).raw; + + LOG_DEBUG(Service_Y2R, "called input_lines=%u", cmd_buff[1]); +} + +static void GetInputLines(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x1D, 2, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = static_cast<u32>(conversion.input_lines); + + LOG_DEBUG(Service_Y2R, "called input_lines=%u", conversion.input_lines); } static void SetCoefficient(Service::Interface* self) { @@ -235,45 +481,111 @@ static void SetCoefficient(Service::Interface* self) { const u16* coefficients = reinterpret_cast<const u16*>(&cmd_buff[1]); std::memcpy(conversion.coefficients.data(), coefficients, sizeof(CoefficientSet)); + + cmd_buff[0] = IPC::MakeHeader(0x1E, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_DEBUG(Service_Y2R, "called coefficients=[%hX, %hX, %hX, %hX, %hX, %hX, %hX, %hX]", coefficients[0], coefficients[1], coefficients[2], coefficients[3], coefficients[4], coefficients[5], coefficients[6], coefficients[7]); +} +static void GetCoefficient(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x1F, 5, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + std::memcpy(&cmd_buff[2], conversion.coefficients.data(), sizeof(CoefficientSet)); + + LOG_DEBUG(Service_Y2R, "called"); } static void SetStandardCoefficient(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - LOG_DEBUG(Service_Y2R, "called standard_coefficient=%u", cmd_buff[1]); + u32 index = cmd_buff[1]; + + cmd_buff[0] = IPC::MakeHeader(0x20, 1, 0); + cmd_buff[1] = conversion.SetStandardCoefficient((StandardCoefficient)index).raw; + + LOG_DEBUG(Service_Y2R, "called standard_coefficient=%u", index); +} + +static void GetStandardCoefficient(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 index = cmd_buff[1]; + + if (index < ARRAY_SIZE(standard_coefficients)) { + cmd_buff[0] = IPC::MakeHeader(0x21, 5, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + std::memcpy(&cmd_buff[2], &standard_coefficients[index], sizeof(CoefficientSet)); - cmd_buff[1] = conversion.SetStandardCoefficient((StandardCoefficient)cmd_buff[1]).raw; + LOG_DEBUG(Service_Y2R, "called standard_coefficient=%u ", index); + } else { + cmd_buff[0] = IPC::MakeHeader(0x21, 1, 0); + cmd_buff[1] = -1; // TODO(bunnei): Identify the correct error code for this + + LOG_ERROR(Service_Y2R, "called standard_coefficient=%u The argument is invalid!", index); + } } static void SetAlpha(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.alpha = cmd_buff[1]; + + cmd_buff[0] = IPC::MakeHeader(0x22, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_DEBUG(Service_Y2R, "called alpha=%hu", conversion.alpha); +} + +static void GetAlpha(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[0] = IPC::MakeHeader(0x23, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = conversion.alpha; + + LOG_DEBUG(Service_Y2R, "called alpha=%hu", conversion.alpha); } -static void StartConversion(Service::Interface* self) { +static void SetDitheringWeightParams(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + std::memcpy(&dithering_weight_params, &cmd_buff[1], sizeof(DitheringWeightParams)); - HW::Y2R::PerformConversion(conversion); + cmd_buff[0] = IPC::MakeHeader(0x24, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; - // dst_image_size would seem to be perfect for this, but it doesn't include the gap :( - u32 total_output_size = conversion.input_lines * - (conversion.dst.transfer_unit + conversion.dst.gap); - VideoCore::g_renderer->Rasterizer()->InvalidateRegion( - Memory::VirtualToPhysicalAddress(conversion.dst.address), total_output_size); + LOG_DEBUG(Service_Y2R, "called"); +} + +static void GetDitheringWeightParams(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x25, 9, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + std::memcpy(&cmd_buff[2], &dithering_weight_params, sizeof(DitheringWeightParams)); LOG_DEBUG(Service_Y2R, "called"); +} + +static void StartConversion(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + // dst_image_size would seem to be perfect for this, but it doesn't include the gap :( + u32 total_output_size = conversion.input_lines * (conversion.dst.transfer_unit + conversion.dst.gap); + Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(conversion.dst.address), total_output_size); + + HW::Y2R::PerformConversion(conversion); + completion_event->Signal(); + cmd_buff[0] = IPC::MakeHeader(0x26, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_DEBUG(Service_Y2R, "called"); } static void StopConversion(Service::Interface* self) { @@ -281,6 +593,7 @@ static void StopConversion(Service::Interface* self) { cmd_buff[0] = IPC::MakeHeader(0x27, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_DEBUG(Service_Y2R, "called"); } @@ -293,50 +606,61 @@ static void StopConversion(Service::Interface* self) { static void IsBusyConversion(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[0] = IPC::MakeHeader(0x28, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 0; // StartConversion always finishes immediately + LOG_DEBUG(Service_Y2R, "called"); } /** - * Y2R_U::SetConversionParams service function + * Y2R_U::SetPackageParameter service function */ -static void SetConversionParams(Service::Interface* self) { +static void SetPackageParameter(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); auto params = reinterpret_cast<const ConversionParameters*>(&cmd_buff[1]); - LOG_DEBUG(Service_Y2R, - "called input_format=%hhu output_format=%hhu rotation=%hhu block_alignment=%hhu " - "input_line_width=%hu input_lines=%hu standard_coefficient=%hhu " - "reserved=%hhu alpha=%hX", - params->input_format, params->output_format, params->rotation, params->block_alignment, - params->input_line_width, params->input_lines, params->standard_coefficient, - params->reserved, params->alpha); - - ResultCode result = RESULT_SUCCESS; conversion.input_format = params->input_format; conversion.output_format = params->output_format; conversion.rotation = params->rotation; conversion.block_alignment = params->block_alignment; - result = conversion.SetInputLineWidth(params->input_line_width); - if (result.IsError()) goto cleanup; + + ResultCode result = conversion.SetInputLineWidth(params->input_line_width); + + if (result.IsError()) + goto cleanup; + result = conversion.SetInputLines(params->input_lines); - if (result.IsError()) goto cleanup; + + if (result.IsError()) + goto cleanup; + result = conversion.SetStandardCoefficient(params->standard_coefficient); - if (result.IsError()) goto cleanup; + + if (result.IsError()) + goto cleanup; + + conversion.padding = params->padding; conversion.alpha = params->alpha; cleanup: cmd_buff[0] = IPC::MakeHeader(0x29, 1, 0); cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_Y2R, "called input_format=%hhu output_format=%hhu rotation=%hhu block_alignment=%hhu " + "input_line_width=%hu input_lines=%hu standard_coefficient=%hhu reserved=%hhu alpha=%hX", + params->input_format, params->output_format, params->rotation, params->block_alignment, + params->input_line_width, params->input_lines, params->standard_coefficient, params->padding, params->alpha); } static void PingProcess(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[0] = IPC::MakeHeader(0x2A, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 0; + LOG_WARNING(Service_Y2R, "(STUBBED) called"); } @@ -362,6 +686,7 @@ static void DriverInitialize(Service::Interface* self) { cmd_buff[0] = IPC::MakeHeader(0x2B, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_DEBUG(Service_Y2R, "called"); } @@ -370,54 +695,67 @@ static void DriverFinalize(Service::Interface* self) { cmd_buff[0] = IPC::MakeHeader(0x2C, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_DEBUG(Service_Y2R, "called"); +} + + +static void GetPackageParameter(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0x2D, 4, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + std::memcpy(&cmd_buff[2], &conversion, sizeof(ConversionParameters)); + LOG_DEBUG(Service_Y2R, "called"); } const Interface::FunctionInfo FunctionTable[] = { {0x00010040, SetInputFormat, "SetInputFormat"}, - {0x00020000, nullptr, "GetInputFormat"}, + {0x00020000, GetInputFormat, "GetInputFormat"}, {0x00030040, SetOutputFormat, "SetOutputFormat"}, - {0x00040000, nullptr, "GetOutputFormat"}, + {0x00040000, GetOutputFormat, "GetOutputFormat"}, {0x00050040, SetRotation, "SetRotation"}, - {0x00060000, nullptr, "GetRotation"}, + {0x00060000, GetRotation, "GetRotation"}, {0x00070040, SetBlockAlignment, "SetBlockAlignment"}, - {0x00080000, nullptr, "GetBlockAlignment"}, - {0x00090040, nullptr, "SetSpacialDithering"}, - {0x000A0000, nullptr, "GetSpacialDithering"}, - {0x000B0040, nullptr, "SetTemporalDithering"}, - {0x000C0000, nullptr, "GetTemporalDithering"}, + {0x00080000, GetBlockAlignment, "GetBlockAlignment"}, + {0x00090040, SetSpacialDithering, "SetSpacialDithering"}, + {0x000A0000, GetSpacialDithering, "GetSpacialDithering"}, + {0x000B0040, SetTemporalDithering, "SetTemporalDithering"}, + {0x000C0000, GetTemporalDithering, "GetTemporalDithering"}, {0x000D0040, SetTransferEndInterrupt, "SetTransferEndInterrupt"}, + {0x000E0000, GetTransferEndInterrupt, "GetTransferEndInterrupt"}, {0x000F0000, GetTransferEndEvent, "GetTransferEndEvent"}, {0x00100102, SetSendingY, "SetSendingY"}, {0x00110102, SetSendingU, "SetSendingU"}, {0x00120102, SetSendingV, "SetSendingV"}, {0x00130102, SetSendingYUYV, "SetSendingYUYV"}, - {0x00140000, nullptr, "IsFinishedSendingYuv"}, - {0x00150000, nullptr, "IsFinishedSendingY"}, - {0x00160000, nullptr, "IsFinishedSendingU"}, - {0x00170000, nullptr, "IsFinishedSendingV"}, + {0x00140000, IsFinishedSendingYuv, "IsFinishedSendingYuv"}, + {0x00150000, IsFinishedSendingY, "IsFinishedSendingY"}, + {0x00160000, IsFinishedSendingU, "IsFinishedSendingU"}, + {0x00170000, IsFinishedSendingV, "IsFinishedSendingV"}, {0x00180102, SetReceiving, "SetReceiving"}, - {0x00190000, nullptr, "IsFinishedReceiving"}, + {0x00190000, IsFinishedReceiving, "IsFinishedReceiving"}, {0x001A0040, SetInputLineWidth, "SetInputLineWidth"}, - {0x001B0000, nullptr, "GetInputLineWidth"}, + {0x001B0000, GetInputLineWidth, "GetInputLineWidth"}, {0x001C0040, SetInputLines, "SetInputLines"}, - {0x001D0000, nullptr, "GetInputLines"}, + {0x001D0000, GetInputLines, "GetInputLines"}, {0x001E0100, SetCoefficient, "SetCoefficient"}, - {0x001F0000, nullptr, "GetCoefficient"}, + {0x001F0000, GetCoefficient, "GetCoefficient"}, {0x00200040, SetStandardCoefficient, "SetStandardCoefficient"}, - {0x00210040, nullptr, "GetStandardCoefficientParams"}, + {0x00210040, GetStandardCoefficient, "GetStandardCoefficient"}, {0x00220040, SetAlpha, "SetAlpha"}, - {0x00230000, nullptr, "GetAlpha"}, - {0x00240200, nullptr, "SetDitheringWeightParams"}, - {0x00250000, nullptr, "GetDitheringWeightParams"}, + {0x00230000, GetAlpha, "GetAlpha"}, + {0x00240200, SetDitheringWeightParams,"SetDitheringWeightParams"}, + {0x00250000, GetDitheringWeightParams,"GetDitheringWeightParams"}, {0x00260000, StartConversion, "StartConversion"}, {0x00270000, StopConversion, "StopConversion"}, {0x00280000, IsBusyConversion, "IsBusyConversion"}, - {0x002901C0, SetConversionParams, "SetConversionParams"}, + {0x002901C0, SetPackageParameter, "SetPackageParameter"}, {0x002A0000, PingProcess, "PingProcess"}, {0x002B0000, DriverInitialize, "DriverInitialize"}, {0x002C0000, DriverFinalize, "DriverFinalize"}, - {0x002D0000, nullptr, "GetPackageParameter"}, + {0x002D0000, GetPackageParameter, "GetPackageParameter"}, }; //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/hle/service/y2r_u.h b/src/core/hle/service/y2r_u.h index 3965a5545..95fa2fdb7 100644 --- a/src/core/hle/service/y2r_u.h +++ b/src/core/hle/service/y2r_u.h @@ -97,6 +97,7 @@ struct ConversionConfiguration { u16 input_line_width; u16 input_lines; CoefficientSet coefficients; + u8 padding; u16 alpha; /// Input parameters for the Y (luma) plane @@ -109,6 +110,25 @@ struct ConversionConfiguration { ResultCode SetStandardCoefficient(StandardCoefficient standard_coefficient); }; +struct DitheringWeightParams { + u16 w0_xEven_yEven; + u16 w0_xOdd_yEven; + u16 w0_xEven_yOdd; + u16 w0_xOdd_yOdd; + u16 w1_xEven_yEven; + u16 w1_xOdd_yEven; + u16 w1_xEven_yOdd; + u16 w1_xOdd_yOdd; + u16 w2_xEven_yEven; + u16 w2_xOdd_yEven; + u16 w2_xEven_yOdd; + u16 w2_xOdd_yOdd; + u16 w3_xEven_yEven; + u16 w3_xOdd_yEven; + u16 w3_xEven_yOdd; + u16 w3_xOdd_yOdd; +}; + class Interface : public Service::Interface { public: Interface(); diff --git a/src/core/hle/shared_page.cpp b/src/core/hle/shared_page.cpp index 50c5bc01b..2a1caeaac 100644 --- a/src/core/hle/shared_page.cpp +++ b/src/core/hle/shared_page.cpp @@ -16,6 +16,9 @@ void Init() { std::memset(&shared_page, 0, sizeof(shared_page)); shared_page.running_hw = 0x1; // product + + // Some games wait until this value becomes 0x1, before asking running_hw + shared_page.unknown_value = 0x1; } } // namespace diff --git a/src/core/hle/shared_page.h b/src/core/hle/shared_page.h index 379bb7b63..35a07c685 100644 --- a/src/core/hle/shared_page.h +++ b/src/core/hle/shared_page.h @@ -39,12 +39,14 @@ struct SharedPageDef { DateTime date_time_0; // 20 DateTime date_time_1; // 40 u8 wifi_macaddr[6]; // 60 - u8 wifi_unknown1; // 66 + u8 wifi_link_level; // 66 u8 wifi_unknown2; // 67 INSERT_PADDING_BYTES(0x80 - 0x68); // 68 float_le sliderstate_3d; // 80 u8 ledstate_3d; // 84 - INSERT_PADDING_BYTES(0xA0 - 0x85); // 85 + INSERT_PADDING_BYTES(1); // 85 + u8 unknown_value; // 86 + INSERT_PADDING_BYTES(0xA0 - 0x87); // 87 u64_le menu_title_id; // A0 u64_le active_menu_title_id; // A8 INSERT_PADDING_BYTES(0x1000 - 0xB0); // B0 diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index ae54afb1c..fb2aecbf2 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -6,7 +6,6 @@ #include "common/logging/log.h" #include "common/microprofile.h" -#include "common/profiler.h" #include "common/string_util.h" #include "common/symbols.h" @@ -860,6 +859,10 @@ static ResultCode GetProcessInfo(s64* out, Handle process_handle, u32 type) { // TODO(yuriks): Type 0 returns a slightly higher number than type 2, but I'm not sure // what's the difference between them. *out = process->heap_used + process->linear_heap_used + process->misc_memory_used; + if(*out % Memory::PAGE_SIZE != 0) { + LOG_ERROR(Kernel_SVC, "called, memory size not page-aligned"); + return ERR_MISALIGNED_SIZE; + } break; case 1: case 3: @@ -1031,8 +1034,6 @@ static const FunctionDef SVC_Table[] = { {0x7D, HLE::Wrap<QueryProcessMemory>, "QueryProcessMemory"}, }; -Common::Profiling::TimingCategory profiler_svc("SVC Calls"); - static const FunctionDef* GetSVCInfo(u32 func_num) { if (func_num >= ARRAY_SIZE(SVC_Table)) { LOG_ERROR(Kernel_SVC, "unknown svc=0x%02X", func_num); @@ -1044,7 +1045,6 @@ static const FunctionDef* GetSVCInfo(u32 func_num) { MICROPROFILE_DEFINE(Kernel_SVC, "Kernel", "SVC", MP_RGB(70, 200, 70)); void CallSVC(u32 immediate) { - Common::Profiling::ScopeTimer timer_svc(profiler_svc); MICROPROFILE_SCOPE(Kernel_SVC); const FunctionDef* info = GetSVCInfo(immediate); diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index 7e2f9cdfa..2fe856293 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -115,21 +115,39 @@ inline void Write(u32 addr, const T data) { u8* start = Memory::GetPhysicalPointer(config.GetStartAddress()); u8* end = Memory::GetPhysicalPointer(config.GetEndAddress()); - if (config.fill_24bit) { - // fill with 24-bit values - for (u8* ptr = start; ptr < end; ptr += 3) { - ptr[0] = config.value_24bit_r; - ptr[1] = config.value_24bit_g; - ptr[2] = config.value_24bit_b; + // TODO: Consider always accelerating and returning vector of + // regions that the accelerated fill did not cover to + // reduce/eliminate the fill that the cpu has to do. + // This would also mean that the flush below is not needed. + // Fill should first flush all surfaces that touch but are + // not completely within the fill range. + // Then fill all completely covered surfaces, and return the + // regions that were between surfaces or within the touching + // ones for cpu to manually fill here. + if (!VideoCore::g_renderer->Rasterizer()->AccelerateFill(config)) { + Memory::RasterizerFlushAndInvalidateRegion(config.GetStartAddress(), config.GetEndAddress() - config.GetStartAddress()); + + if (config.fill_24bit) { + // fill with 24-bit values + for (u8* ptr = start; ptr < end; ptr += 3) { + ptr[0] = config.value_24bit_r; + ptr[1] = config.value_24bit_g; + ptr[2] = config.value_24bit_b; + } + } else if (config.fill_32bit) { + // fill with 32-bit values + if (end > start) { + u32 value = config.value_32bit; + size_t len = (end - start) / sizeof(u32); + for (size_t i = 0; i < len; ++i) + memcpy(&start[i * sizeof(u32)], &value, sizeof(u32)); + } + } else { + // fill with 16-bit values + u16 value_16bit = config.value_16bit.Value(); + for (u8* ptr = start; ptr < end; ptr += sizeof(u16)) + memcpy(ptr, &value_16bit, sizeof(u16)); } - } else if (config.fill_32bit) { - // fill with 32-bit values - for (u32* ptr = (u32*)start; ptr < (u32*)end; ++ptr) - *ptr = config.value_32bit; - } else { - // fill with 16-bit values - for (u16* ptr = (u16*)start; ptr < (u16*)end; ++ptr) - *ptr = config.value_16bit; } LOG_TRACE(HW_GPU, "MemoryFill from 0x%08x to 0x%08x", config.GetStartAddress(), config.GetEndAddress()); @@ -139,8 +157,6 @@ inline void Write(u32 addr, const T data) { } else { GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC1); } - - VideoCore::g_renderer->Rasterizer()->InvalidateRegion(config.GetStartAddress(), config.GetEndAddress() - config.GetStartAddress()); } // Reset "trigger" flag and set the "finish" flag @@ -161,184 +177,185 @@ inline void Write(u32 addr, const T data) { if (Pica::g_debug_context) Pica::g_debug_context->OnEvent(Pica::DebugContext::Event::IncomingDisplayTransfer, nullptr); - u8* src_pointer = Memory::GetPhysicalPointer(config.GetPhysicalInputAddress()); - u8* dst_pointer = Memory::GetPhysicalPointer(config.GetPhysicalOutputAddress()); - - if (config.is_texture_copy) { - u32 input_width = config.texture_copy.input_width * 16; - u32 input_gap = config.texture_copy.input_gap * 16; - u32 output_width = config.texture_copy.output_width * 16; - u32 output_gap = config.texture_copy.output_gap * 16; - - size_t contiguous_input_size = config.texture_copy.size / input_width * (input_width + input_gap); - VideoCore::g_renderer->Rasterizer()->FlushRegion(config.GetPhysicalInputAddress(), contiguous_input_size); - - u32 remaining_size = config.texture_copy.size; - u32 remaining_input = input_width; - u32 remaining_output = output_width; - while (remaining_size > 0) { - u32 copy_size = std::min({ remaining_input, remaining_output, remaining_size }); + if (!VideoCore::g_renderer->Rasterizer()->AccelerateDisplayTransfer(config)) { + u8* src_pointer = Memory::GetPhysicalPointer(config.GetPhysicalInputAddress()); + u8* dst_pointer = Memory::GetPhysicalPointer(config.GetPhysicalOutputAddress()); - std::memcpy(dst_pointer, src_pointer, copy_size); - src_pointer += copy_size; - dst_pointer += copy_size; + if (config.is_texture_copy) { + u32 input_width = config.texture_copy.input_width * 16; + u32 input_gap = config.texture_copy.input_gap * 16; + u32 output_width = config.texture_copy.output_width * 16; + u32 output_gap = config.texture_copy.output_gap * 16; - remaining_input -= copy_size; - remaining_output -= copy_size; - remaining_size -= copy_size; + size_t contiguous_input_size = config.texture_copy.size / input_width * (input_width + input_gap); + Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), contiguous_input_size); - if (remaining_input == 0) { - remaining_input = input_width; - src_pointer += input_gap; - } - if (remaining_output == 0) { - remaining_output = output_width; - dst_pointer += output_gap; - } - } + size_t contiguous_output_size = config.texture_copy.size / output_width * (output_width + output_gap); + Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), contiguous_output_size); - LOG_TRACE(HW_GPU, "TextureCopy: 0x%X bytes from 0x%08X(%u+%u)-> 0x%08X(%u+%u), flags 0x%08X", - config.texture_copy.size, - config.GetPhysicalInputAddress(), input_width, input_gap, - config.GetPhysicalOutputAddress(), output_width, output_gap, - config.flags); + u32 remaining_size = config.texture_copy.size; + u32 remaining_input = input_width; + u32 remaining_output = output_width; + while (remaining_size > 0) { + u32 copy_size = std::min({ remaining_input, remaining_output, remaining_size }); - size_t contiguous_output_size = config.texture_copy.size / output_width * (output_width + output_gap); - VideoCore::g_renderer->Rasterizer()->InvalidateRegion(config.GetPhysicalOutputAddress(), contiguous_output_size); + std::memcpy(dst_pointer, src_pointer, copy_size); + src_pointer += copy_size; + dst_pointer += copy_size; - GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); - break; - } + remaining_input -= copy_size; + remaining_output -= copy_size; + remaining_size -= copy_size; - if (config.scaling > config.ScaleXY) { - LOG_CRITICAL(HW_GPU, "Unimplemented display transfer scaling mode %u", config.scaling.Value()); - UNIMPLEMENTED(); - break; - } + if (remaining_input == 0) { + remaining_input = input_width; + src_pointer += input_gap; + } + if (remaining_output == 0) { + remaining_output = output_width; + dst_pointer += output_gap; + } + } - if (config.input_linear && config.scaling != config.NoScale) { - LOG_CRITICAL(HW_GPU, "Scaling is only implemented on tiled input"); - UNIMPLEMENTED(); - break; - } + LOG_TRACE(HW_GPU, "TextureCopy: 0x%X bytes from 0x%08X(%u+%u)-> 0x%08X(%u+%u), flags 0x%08X", + config.texture_copy.size, + config.GetPhysicalInputAddress(), input_width, input_gap, + config.GetPhysicalOutputAddress(), output_width, output_gap, + config.flags); - bool horizontal_scale = config.scaling != config.NoScale; - bool vertical_scale = config.scaling == config.ScaleXY; + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); + break; + } - u32 output_width = config.output_width >> horizontal_scale; - u32 output_height = config.output_height >> vertical_scale; + if (config.scaling > config.ScaleXY) { + LOG_CRITICAL(HW_GPU, "Unimplemented display transfer scaling mode %u", config.scaling.Value()); + UNIMPLEMENTED(); + break; + } - u32 input_size = config.input_width * config.input_height * GPU::Regs::BytesPerPixel(config.input_format); - u32 output_size = output_width * output_height * GPU::Regs::BytesPerPixel(config.output_format); + if (config.input_linear && config.scaling != config.NoScale) { + LOG_CRITICAL(HW_GPU, "Scaling is only implemented on tiled input"); + UNIMPLEMENTED(); + break; + } - VideoCore::g_renderer->Rasterizer()->FlushRegion(config.GetPhysicalInputAddress(), input_size); + int horizontal_scale = config.scaling != config.NoScale ? 1 : 0; + int vertical_scale = config.scaling == config.ScaleXY ? 1 : 0; - for (u32 y = 0; y < output_height; ++y) { - for (u32 x = 0; x < output_width; ++x) { - Math::Vec4<u8> src_color; + u32 output_width = config.output_width >> horizontal_scale; + u32 output_height = config.output_height >> vertical_scale; - // Calculate the [x,y] position of the input image - // based on the current output position and the scale - u32 input_x = x << horizontal_scale; - u32 input_y = y << vertical_scale; + u32 input_size = config.input_width * config.input_height * GPU::Regs::BytesPerPixel(config.input_format); + u32 output_size = output_width * output_height * GPU::Regs::BytesPerPixel(config.output_format); - if (config.flip_vertically) { - // Flip the y value of the output data, - // we do this after calculating the [x,y] position of the input image - // to account for the scaling options. - y = output_height - y - 1; - } + Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), input_size); + Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), output_size); - u32 dst_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.output_format); - u32 src_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.input_format); - u32 src_offset; - u32 dst_offset; + for (u32 y = 0; y < output_height; ++y) { + for (u32 x = 0; x < output_width; ++x) { + Math::Vec4<u8> src_color; - if (config.input_linear) { - if (!config.dont_swizzle) { - // Interpret the input as linear and the output as tiled - u32 coarse_y = y & ~7; - u32 stride = output_width * dst_bytes_per_pixel; + // Calculate the [x,y] position of the input image + // based on the current output position and the scale + u32 input_x = x << horizontal_scale; + u32 input_y = y << vertical_scale; - src_offset = (input_x + input_y * config.input_width) * src_bytes_per_pixel; - dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + coarse_y * stride; - } else { - // Both input and output are linear - src_offset = (input_x + input_y * config.input_width) * src_bytes_per_pixel; - dst_offset = (x + y * output_width) * dst_bytes_per_pixel; + if (config.flip_vertically) { + // Flip the y value of the output data, + // we do this after calculating the [x,y] position of the input image + // to account for the scaling options. + y = output_height - y - 1; } - } else { - if (!config.dont_swizzle) { - // Interpret the input as tiled and the output as linear - u32 coarse_y = input_y & ~7; - u32 stride = config.input_width * src_bytes_per_pixel; - src_offset = VideoCore::GetMortonOffset(input_x, input_y, src_bytes_per_pixel) + coarse_y * stride; - dst_offset = (x + y * output_width) * dst_bytes_per_pixel; + u32 dst_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.output_format); + u32 src_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.input_format); + u32 src_offset; + u32 dst_offset; + + if (config.input_linear) { + if (!config.dont_swizzle) { + // Interpret the input as linear and the output as tiled + u32 coarse_y = y & ~7; + u32 stride = output_width * dst_bytes_per_pixel; + + src_offset = (input_x + input_y * config.input_width) * src_bytes_per_pixel; + dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + coarse_y * stride; + } else { + // Both input and output are linear + src_offset = (input_x + input_y * config.input_width) * src_bytes_per_pixel; + dst_offset = (x + y * output_width) * dst_bytes_per_pixel; + } } else { - // Both input and output are tiled - u32 out_coarse_y = y & ~7; - u32 out_stride = output_width * dst_bytes_per_pixel; - - u32 in_coarse_y = input_y & ~7; - u32 in_stride = config.input_width * src_bytes_per_pixel; - - src_offset = VideoCore::GetMortonOffset(input_x, input_y, src_bytes_per_pixel) + in_coarse_y * in_stride; - dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + out_coarse_y * out_stride; + if (!config.dont_swizzle) { + // Interpret the input as tiled and the output as linear + u32 coarse_y = input_y & ~7; + u32 stride = config.input_width * src_bytes_per_pixel; + + src_offset = VideoCore::GetMortonOffset(input_x, input_y, src_bytes_per_pixel) + coarse_y * stride; + dst_offset = (x + y * output_width) * dst_bytes_per_pixel; + } else { + // Both input and output are tiled + u32 out_coarse_y = y & ~7; + u32 out_stride = output_width * dst_bytes_per_pixel; + + u32 in_coarse_y = input_y & ~7; + u32 in_stride = config.input_width * src_bytes_per_pixel; + + src_offset = VideoCore::GetMortonOffset(input_x, input_y, src_bytes_per_pixel) + in_coarse_y * in_stride; + dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + out_coarse_y * out_stride; + } } - } - const u8* src_pixel = src_pointer + src_offset; - src_color = DecodePixel(config.input_format, src_pixel); - if (config.scaling == config.ScaleX) { - Math::Vec4<u8> pixel = DecodePixel(config.input_format, src_pixel + src_bytes_per_pixel); - src_color = ((src_color + pixel) / 2).Cast<u8>(); - } else if (config.scaling == config.ScaleXY) { - Math::Vec4<u8> pixel1 = DecodePixel(config.input_format, src_pixel + 1 * src_bytes_per_pixel); - Math::Vec4<u8> pixel2 = DecodePixel(config.input_format, src_pixel + 2 * src_bytes_per_pixel); - Math::Vec4<u8> pixel3 = DecodePixel(config.input_format, src_pixel + 3 * src_bytes_per_pixel); - src_color = (((src_color + pixel1) + (pixel2 + pixel3)) / 4).Cast<u8>(); - } + const u8* src_pixel = src_pointer + src_offset; + src_color = DecodePixel(config.input_format, src_pixel); + if (config.scaling == config.ScaleX) { + Math::Vec4<u8> pixel = DecodePixel(config.input_format, src_pixel + src_bytes_per_pixel); + src_color = ((src_color + pixel) / 2).Cast<u8>(); + } else if (config.scaling == config.ScaleXY) { + Math::Vec4<u8> pixel1 = DecodePixel(config.input_format, src_pixel + 1 * src_bytes_per_pixel); + Math::Vec4<u8> pixel2 = DecodePixel(config.input_format, src_pixel + 2 * src_bytes_per_pixel); + Math::Vec4<u8> pixel3 = DecodePixel(config.input_format, src_pixel + 3 * src_bytes_per_pixel); + src_color = (((src_color + pixel1) + (pixel2 + pixel3)) / 4).Cast<u8>(); + } - u8* dst_pixel = dst_pointer + dst_offset; - switch (config.output_format) { - case Regs::PixelFormat::RGBA8: - Color::EncodeRGBA8(src_color, dst_pixel); - break; + u8* dst_pixel = dst_pointer + dst_offset; + switch (config.output_format) { + case Regs::PixelFormat::RGBA8: + Color::EncodeRGBA8(src_color, dst_pixel); + break; - case Regs::PixelFormat::RGB8: - Color::EncodeRGB8(src_color, dst_pixel); - break; + case Regs::PixelFormat::RGB8: + Color::EncodeRGB8(src_color, dst_pixel); + break; - case Regs::PixelFormat::RGB565: - Color::EncodeRGB565(src_color, dst_pixel); - break; + case Regs::PixelFormat::RGB565: + Color::EncodeRGB565(src_color, dst_pixel); + break; - case Regs::PixelFormat::RGB5A1: - Color::EncodeRGB5A1(src_color, dst_pixel); - break; + case Regs::PixelFormat::RGB5A1: + Color::EncodeRGB5A1(src_color, dst_pixel); + break; - case Regs::PixelFormat::RGBA4: - Color::EncodeRGBA4(src_color, dst_pixel); - break; + case Regs::PixelFormat::RGBA4: + Color::EncodeRGBA4(src_color, dst_pixel); + break; - default: - LOG_ERROR(HW_GPU, "Unknown destination framebuffer format %x", config.output_format.Value()); - break; + default: + LOG_ERROR(HW_GPU, "Unknown destination framebuffer format %x", config.output_format.Value()); + break; + } } } - } - LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), dst format %x, flags 0x%08X", + LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), dst format %x, flags 0x%08X", config.output_height * output_width * GPU::Regs::BytesPerPixel(config.output_format), config.GetPhysicalInputAddress(), config.input_width.Value(), config.input_height.Value(), config.GetPhysicalOutputAddress(), output_width, output_height, config.output_format.Value(), config.flags); + } g_regs.display_transfer_config.trigger = 0; GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); - - VideoCore::g_renderer->Rasterizer()->InvalidateRegion(config.GetPhysicalOutputAddress(), output_size); } break; } diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h index a00adbf53..da4c345b4 100644 --- a/src/core/hw/gpu.h +++ b/src/core/hw/gpu.h @@ -78,7 +78,7 @@ struct Regs { INSERT_PADDING_WORDS(0x4); - struct { + struct MemoryFillConfig { u32 address_start; u32 address_end; @@ -165,7 +165,7 @@ struct Regs { INSERT_PADDING_WORDS(0x169); - struct { + struct DisplayTransferConfig { u32 input_address; u32 output_address; diff --git a/src/core/hw/lcd.h b/src/core/hw/lcd.h index 3dd877fbf..57029c5e8 100644 --- a/src/core/hw/lcd.h +++ b/src/core/hw/lcd.h @@ -52,8 +52,6 @@ struct Regs { return content[index]; } -#undef ASSERT_MEMBER_SIZE - }; static_assert(std::is_standard_layout<Regs>::value, "Structure does not use standard layout"); diff --git a/src/core/hw/y2r.cpp b/src/core/hw/y2r.cpp index 48c45564f..083391e83 100644 --- a/src/core/hw/y2r.cpp +++ b/src/core/hw/y2r.cpp @@ -261,7 +261,7 @@ void PerformConversion(ConversionConfiguration& cvt) { ASSERT(cvt.block_alignment != BlockAlignment::Block8x8 || cvt.input_lines % 8 == 0); // Tiles per row size_t num_tiles = cvt.input_line_width / 8; - ASSERT(num_tiles < MAX_TILES); + ASSERT(num_tiles <= MAX_TILES); // Buffer used as a CDMA source/target. std::unique_ptr<u8[]> data_buffer(new u8[cvt.input_line_width * 8 * 4]); diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 8eed6a50a..48a11ef81 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -10,13 +10,9 @@ #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/elf.h" -#include "core/loader/ncch.h" +#include "core/loader/3dsx.h" #include "core/memory.h" -#include "3dsx.h" - namespace Loader { /* @@ -307,4 +303,31 @@ ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& ro return ResultStatus::ErrorNotUsed; } +ResultStatus AppLoader_THREEDSX::ReadIcon(std::vector<u8>& buffer) { + if (!file.IsOpen()) + return ResultStatus::Error; + + // Reset read pointer in case this file has been read before. + file.Seek(0, SEEK_SET); + + THREEDSX_Header hdr; + if (file.ReadBytes(&hdr, sizeof(THREEDSX_Header)) != sizeof(THREEDSX_Header)) + return ResultStatus::Error; + + if (hdr.header_size != sizeof(THREEDSX_Header)) + return ResultStatus::Error; + + // Check if the 3DSX has a SMDH... + if (hdr.smdh_offset != 0) { + file.Seek(hdr.smdh_offset, SEEK_SET); + buffer.resize(hdr.smdh_size); + + if (file.ReadBytes(&buffer[0], hdr.smdh_size) != hdr.smdh_size) + return ResultStatus::Error; + + return ResultStatus::Success; + } + return ResultStatus::ErrorNotUsed; +} + } // namespace Loader diff --git a/src/core/loader/3dsx.h b/src/core/loader/3dsx.h index 365ddb7a5..3ee686703 100644 --- a/src/core/loader/3dsx.h +++ b/src/core/loader/3dsx.h @@ -17,7 +17,7 @@ namespace Loader { /// Loads an 3DSX file class AppLoader_THREEDSX final : public AppLoader { public: - AppLoader_THREEDSX(FileUtil::IOFile&& file, std::string filename, const std::string& filepath) + AppLoader_THREEDSX(FileUtil::IOFile&& file, const std::string& filename, const std::string& filepath) : AppLoader(std::move(file)), filename(std::move(filename)), filepath(filepath) {} /** @@ -34,6 +34,13 @@ public: ResultStatus Load() override; /** + * Get the icon (typically icon section) of the application + * @param buffer Reference to buffer to store data + * @return ResultStatus result of function + */ + ResultStatus ReadIcon(std::vector<u8>& buffer) override; + + /** * Get the RomFS of the application * @param romfs_file Reference to buffer to store data * @param offset Offset in the file to the RomFS diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index b1907cd55..0d4c1d351 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -6,7 +6,6 @@ #include <string> #include "common/logging/log.h" -#include "common/make_unique.h" #include "common/string_util.h" #include "core/file_sys/archive_romfs.h" @@ -91,6 +90,28 @@ const char* GetFileTypeString(FileType type) { return "unknown"; } +std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type, + const std::string& filename, const std::string& filepath) { + switch (type) { + + // 3DSX file format. + case FileType::THREEDSX: + return std::make_unique<AppLoader_THREEDSX>(std::move(file), filename, filepath); + + // Standard ELF file format. + case FileType::ELF: + return std::make_unique<AppLoader_ELF>(std::move(file), filename); + + // NCCH/NCSD container formats. + case FileType::CXI: + case FileType::CCI: + return std::make_unique<AppLoader_NCCH>(std::move(file), filepath); + + default: + return std::unique_ptr<AppLoader>(); + } +} + ResultStatus LoadFile(const std::string& filename) { FileUtil::IOFile file(filename, "rb"); if (!file.IsOpen()) { @@ -112,15 +133,19 @@ ResultStatus LoadFile(const std::string& filename) { LOG_INFO(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type)); + std::unique_ptr<AppLoader> app_loader = GetLoader(std::move(file), type, filename_filename, filename); + switch (type) { - //3DSX file format... + // 3DSX file format... + // or NCCH/NCSD container formats... case FileType::THREEDSX: + case FileType::CXI: + case FileType::CCI: { - AppLoader_THREEDSX app_loader(std::move(file), filename_filename, filename); // Load application and RomFS - if (ResultStatus::Success == app_loader.Load()) { - Service::FS::RegisterArchiveType(Common::make_unique<FileSys::ArchiveFactory_RomFS>(app_loader), Service::FS::ArchiveIdCode::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; @@ -128,21 +153,7 @@ ResultStatus LoadFile(const std::string& filename) { // Standard ELF file format... case FileType::ELF: - return AppLoader_ELF(std::move(file), filename_filename).Load(); - - // NCCH/NCSD container formats... - case FileType::CXI: - case FileType::CCI: - { - AppLoader_NCCH app_loader(std::move(file), filename); - - // Load application and RomFS - ResultStatus result = app_loader.Load(); - if (ResultStatus::Success == result) { - Service::FS::RegisterArchiveType(Common::make_unique<FileSys::ArchiveFactory_RomFS>(app_loader), Service::FS::ArchiveIdCode::RomFS); - } - return result; - } + return app_loader->Load(); // CIA file format... case FileType::CIA: diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index a7f2715ba..9d3e9ed3b 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -10,8 +10,10 @@ #include <string> #include <vector> +#include "common/common_funcs.h" #include "common/common_types.h" #include "common/file_util.h" +#include "common/swap.h" namespace Kernel { struct AddressMapping; @@ -74,10 +76,55 @@ enum class ResultStatus { ErrorEncrypted, }; -static inline u32 MakeMagic(char a, char b, char c, char d) { +constexpr u32 MakeMagic(char a, char b, char c, char d) { return a | b << 8 | c << 16 | d << 24; } +/// SMDH data structure that contains titles, icons etc. See https://www.3dbrew.org/wiki/SMDH +struct SMDH { + u32_le magic; + u16_le version; + INSERT_PADDING_BYTES(2); + + struct Title { + std::array<u16, 0x40> short_title; + std::array<u16, 0x80> long_title; + std::array<u16, 0x40> publisher; + }; + std::array<Title, 16> titles; + + std::array<u8, 16> ratings; + u32_le region_lockout; + u32_le match_maker_id; + u64_le match_maker_bit_id; + u32_le flags; + u16_le eula_version; + INSERT_PADDING_BYTES(2); + float_le banner_animation_frame; + u32_le cec_id; + INSERT_PADDING_BYTES(8); + + std::array<u8, 0x480> small_icon; + std::array<u8, 0x1200> large_icon; + + /// indicates the language used for each title entry + enum class TitleLanguage { + Japanese = 0, + English = 1, + French = 2, + German = 3, + Italian = 4, + Spanish = 5, + SimplifiedChinese = 6, + Korean= 7, + Dutch = 8, + Portuguese = 9, + Russian = 10, + TraditionalChinese = 11 + }; +}; +static_assert(sizeof(SMDH) == 0x36C0, "SMDH structure size is wrong"); + /// Interface for loading an application class AppLoader : NonCopyable { public: @@ -150,6 +197,16 @@ protected: extern const std::initializer_list<Kernel::AddressMapping> default_address_mappings; /** + * Get a loader for a file with a specific type + * @param file The file to load + * @param type The type of the file + * @param filename the file name (without path) + * @param filepath the file full path (with name) + * @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type + */ +std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type, const std::string& filename, const std::string& filepath); + +/** * Identifies and loads a bootable file * @param filename String filename of bootable file * @return ResultStatus result of function diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 93f21bec2..d362a4419 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -7,7 +7,6 @@ #include <memory> #include "common/logging/log.h" -#include "common/make_unique.h" #include "common/string_util.h" #include "common/swap.h" @@ -174,8 +173,12 @@ ResultStatus AppLoader_NCCH::LoadSectionExeFS(const char* name, std::vector<u8>& if (!file.IsOpen()) return ResultStatus::Error; + ResultStatus result = LoadExeFS(); + if (result != ResultStatus::Success) + return result; + LOG_DEBUG(Loader, "%d sections:", kMaxSections); - // Iterate through the ExeFs archive until we find the .code file... + // Iterate through the ExeFs archive until we find a section with the specified name... for (unsigned section_number = 0; section_number < kMaxSections; section_number++) { const auto& section = exefs_header.section[section_number]; @@ -187,7 +190,7 @@ ResultStatus AppLoader_NCCH::LoadSectionExeFS(const char* name, std::vector<u8>& s64 section_offset = (section.offset + exefs_offset + sizeof(ExeFs_Header) + ncch_offset); file.Seek(section_offset, SEEK_SET); - if (is_compressed) { + if (strcmp(section.name, ".code") == 0 && is_compressed) { // Section is compressed, read compressed .code section... std::unique_ptr<u8[]> temp_buffer; try { @@ -216,9 +219,9 @@ ResultStatus AppLoader_NCCH::LoadSectionExeFS(const char* name, std::vector<u8>& return ResultStatus::ErrorNotUsed; } -ResultStatus AppLoader_NCCH::Load() { - if (is_loaded) - return ResultStatus::ErrorAlreadyLoaded; +ResultStatus AppLoader_NCCH::LoadExeFS() { + if (is_exefs_loaded) + return ResultStatus::Success; if (!file.IsOpen()) return ResultStatus::Error; @@ -256,7 +259,7 @@ ResultStatus AppLoader_NCCH::Load() { resource_limit_category = exheader_header.arm11_system_local_caps.resource_limit_category; LOG_INFO(Loader, "Name: %s" , exheader_header.codeset_info.name); - LOG_INFO(Loader, "Program ID: %016X" , ncch_header.program_id); + LOG_INFO(Loader, "Program ID: %016llX" , ncch_header.program_id); LOG_DEBUG(Loader, "Code compressed: %s" , is_compressed ? "yes" : "no"); LOG_DEBUG(Loader, "Entry point: 0x%08X", entry_point); LOG_DEBUG(Loader, "Code size: 0x%08X", code_size); @@ -283,6 +286,18 @@ ResultStatus AppLoader_NCCH::Load() { if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header)) return ResultStatus::Error; + is_exefs_loaded = true; + return ResultStatus::Success; +} + +ResultStatus AppLoader_NCCH::Load() { + if (is_loaded) + return ResultStatus::ErrorAlreadyLoaded; + + ResultStatus result = LoadExeFS(); + if (result != ResultStatus::Success) + return result; + is_loaded = true; // Set state to loaded return LoadExec(); // Load the executable into memory for booting diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index ca6772a78..fd852c3de 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -232,6 +232,13 @@ private: */ ResultStatus LoadExec(); + /** + * Ensure ExeFS is loaded and ready for reading sections + * @return ResultStatus result of function + */ + ResultStatus LoadExeFS(); + + bool is_exefs_loaded = false; bool is_compressed = false; u32 entry_point = 0; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 7de5bd15d..ee9b69f81 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -15,6 +15,9 @@ #include "core/memory_setup.h" #include "core/mmio.h" +#include "video_core/renderer_base.h" +#include "video_core/video_core.h" + namespace Memory { enum class PageType { @@ -22,8 +25,12 @@ enum class PageType { Unmapped, /// Page is mapped to regular memory. This is the only type you can get pointers to. Memory, + /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and invalidation + RasterizerCachedMemory, /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions. Special, + /// Page is mapped to a I/O region, but also needs to check for rasterizer cache flushing and invalidation + RasterizerCachedSpecial, }; struct SpecialRegion { @@ -57,6 +64,12 @@ struct PageTable { * the corresponding entry in `pointers` MUST be set to null. */ std::array<PageType, NUM_ENTRIES> attributes; + + /** + * Indicates the number of externally cached resources touching a page that should be + * flushed before the memory is accessed + */ + std::array<u8, NUM_ENTRIES> cached_res_count; }; /// Singular page table used for the singleton process @@ -72,8 +85,15 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) { while (base != end) { ASSERT_MSG(base < PageTable::NUM_ENTRIES, "out of range mapping at %08X", base); + // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be null here + if (current_page_table->attributes[base] == PageType::RasterizerCachedMemory || + current_page_table->attributes[base] == PageType::RasterizerCachedSpecial) { + RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(base << PAGE_BITS), PAGE_SIZE); + } + current_page_table->attributes[base] = type; current_page_table->pointers[base] = memory; + current_page_table->cached_res_count[base] = 0; base += 1; if (memory != nullptr) @@ -84,6 +104,7 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) { void InitMemoryMap() { main_page_table.pointers.fill(nullptr); main_page_table.attributes.fill(PageType::Unmapped); + main_page_table.cached_res_count.fill(0); } void MapMemoryRegion(VAddr base, u32 size, u8* target) { @@ -107,6 +128,28 @@ void UnmapRegion(VAddr base, u32 size) { } /** + * Gets a pointer to the exact memory at the virtual address (i.e. not page aligned) + * using a VMA from the current process + */ +static u8* GetPointerFromVMA(VAddr vaddr) { + u8* direct_pointer = nullptr; + + auto& vma = Kernel::g_current_process->vm_manager.FindVMA(vaddr)->second; + switch (vma.type) { + case Kernel::VMAType::AllocatedMemoryBlock: + direct_pointer = vma.backing_block->data() + vma.offset; + break; + case Kernel::VMAType::BackingMemory: + direct_pointer = vma.backing_memory; + break; + default: + UNREACHABLE(); + } + + return direct_pointer + (vaddr - vma.base); +} + +/** * This function should only be called for virtual addreses with attribute `PageType::Special`. */ static MMIORegionPointer GetMMIOHandler(VAddr vaddr) { @@ -126,6 +169,7 @@ template <typename T> T Read(const VAddr vaddr) { const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; if (page_pointer) { + // NOTE: Avoid adding any extra logic to this fast-path block T value; std::memcpy(&value, &page_pointer[vaddr & PAGE_MASK], sizeof(T)); return value; @@ -139,8 +183,22 @@ T Read(const VAddr vaddr) { case PageType::Memory: ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr); break; + case PageType::RasterizerCachedMemory: + { + RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T)); + + T value; + std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T)); + return value; + } case PageType::Special: return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr); + case PageType::RasterizerCachedSpecial: + { + RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T)); + + return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr); + } default: UNREACHABLE(); } @@ -153,6 +211,7 @@ template <typename T> void Write(const VAddr vaddr, const T data) { u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; if (page_pointer) { + // NOTE: Avoid adding any extra logic to this fast-path block std::memcpy(&page_pointer[vaddr & PAGE_MASK], &data, sizeof(T)); return; } @@ -165,9 +224,23 @@ void Write(const VAddr vaddr, const T data) { case PageType::Memory: ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr); break; + case PageType::RasterizerCachedMemory: + { + RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T)); + + std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T)); + break; + } case PageType::Special: WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data); break; + case PageType::RasterizerCachedSpecial: + { + RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T)); + + WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data); + break; + } default: UNREACHABLE(); } @@ -179,6 +252,10 @@ u8* GetPointer(const VAddr vaddr) { return page_pointer + (vaddr & PAGE_MASK); } + if (current_page_table->attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) { + return GetPointerFromVMA(vaddr); + } + LOG_ERROR(HW_Memory, "unknown GetPointer @ 0x%08x", vaddr); return nullptr; } @@ -187,6 +264,69 @@ u8* GetPhysicalPointer(PAddr address) { return GetPointer(PhysicalToVirtualAddress(address)); } +void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) { + if (start == 0) { + return; + } + + u32 num_pages = ((start + size - 1) >> PAGE_BITS) - (start >> PAGE_BITS) + 1; + PAddr paddr = start; + + for (unsigned i = 0; i < num_pages; ++i) { + VAddr vaddr = PhysicalToVirtualAddress(paddr); + u8& res_count = current_page_table->cached_res_count[vaddr >> PAGE_BITS]; + ASSERT_MSG(count_delta <= UINT8_MAX - res_count, "Rasterizer resource cache counter overflow!"); + ASSERT_MSG(count_delta >= -res_count, "Rasterizer resource cache counter underflow!"); + + // Switch page type to cached if now cached + if (res_count == 0) { + PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS]; + switch (page_type) { + case PageType::Memory: + page_type = PageType::RasterizerCachedMemory; + current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr; + break; + case PageType::Special: + page_type = PageType::RasterizerCachedSpecial; + break; + default: + UNREACHABLE(); + } + } + + res_count += count_delta; + + // Switch page type to uncached if now uncached + if (res_count == 0) { + PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS]; + switch (page_type) { + case PageType::RasterizerCachedMemory: + page_type = PageType::Memory; + current_page_table->pointers[vaddr >> PAGE_BITS] = GetPointerFromVMA(vaddr & ~PAGE_MASK); + break; + case PageType::RasterizerCachedSpecial: + page_type = PageType::Special; + break; + default: + UNREACHABLE(); + } + } + paddr += PAGE_SIZE; + } +} + +void RasterizerFlushRegion(PAddr start, u32 size) { + if (VideoCore::g_renderer != nullptr) { + VideoCore::g_renderer->Rasterizer()->FlushRegion(start, size); + } +} + +void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size) { + if (VideoCore::g_renderer != nullptr) { + VideoCore::g_renderer->Rasterizer()->FlushAndInvalidateRegion(start, size); + } +} + u8 Read8(const VAddr addr) { return Read<u8>(addr); } diff --git a/src/core/memory.h b/src/core/memory.h index 5af72b7a7..9caa3c3f5 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -148,4 +148,20 @@ VAddr PhysicalToVirtualAddress(PAddr addr); */ u8* GetPhysicalPointer(PAddr address); +/** + * Adds the supplied value to the rasterizer resource cache counter of each + * page touching the region. + */ +void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta); + +/** + * Flushes any externally cached rasterizer resources touching the given region. + */ +void RasterizerFlushRegion(PAddr start, u32 size); + +/** + * Flushes and invalidates any externally cached rasterizer resources touching the given region. + */ +void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size); + } diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 8a14f75aa..77261eafe 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -4,8 +4,27 @@ #include "settings.h" +#include "audio_core/audio_core.h" + +#include "core/gdbstub/gdbstub.h" + +#include "video_core/video_core.h" + namespace Settings { Values values = {}; +void Apply() { + + GDBStub::SetServerPort(static_cast<u32>(values.gdbstub_port)); + GDBStub::ToggleServer(values.use_gdbstub); + + VideoCore::g_hw_renderer_enabled = values.use_hw_renderer; + VideoCore::g_shader_jit_enabled = values.use_shader_jit; + VideoCore::g_scaled_resolution_enabled = values.use_scaled_resolution; + + AudioCore::SelectSink(values.sink_id); + } + +} // namespace diff --git a/src/core/settings.h b/src/core/settings.h index 97ddcdff9..ce2a31164 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -6,7 +6,8 @@ #include <string> #include <array> -#include <common/file_util.h> + +#include "common/common_types.h" namespace Settings { @@ -55,6 +56,7 @@ struct Values { // Renderer bool use_hw_renderer; bool use_shader_jit; + bool use_scaled_resolution; float bg_red; float bg_green; @@ -62,9 +64,14 @@ struct Values { std::string log_filter; + // Audio + std::string sink_id; + // Debugging bool use_gdbstub; u16 gdbstub_port; } extern values; +void Apply(); + } diff --git a/src/core/tracer/recorder.h b/src/core/tracer/recorder.h index a42ccc45f..febf883c8 100644 --- a/src/core/tracer/recorder.h +++ b/src/core/tracer/recorder.h @@ -4,6 +4,7 @@ #pragma once +#include <string> #include <unordered_map> #include <vector> diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 76cfd4f7d..581a37897 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -15,7 +15,7 @@ set(SRCS shader/shader.cpp shader/shader_interpreter.cpp swrasterizer.cpp - utils.cpp + vertex_loader.cpp video_core.cpp ) @@ -43,6 +43,7 @@ set(HEADERS shader/shader_interpreter.h swrasterizer.h utils.h + vertex_loader.h video_core.h ) diff --git a/src/video_core/clipper.cpp b/src/video_core/clipper.cpp index 3d503486e..2bc747102 100644 --- a/src/video_core/clipper.cpp +++ b/src/video_core/clipper.cpp @@ -2,13 +2,24 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> +#include <array> +#include <cstddef> + #include <boost/container/static_vector.hpp> +#include <boost/container/vector.hpp> + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/vector_math.h" #include "video_core/clipper.h" #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/pica_types.h" #include "video_core/rasterizer.h" -#include "video_core/shader/shader_interpreter.h" +#include "video_core/shader/shader.h" namespace Pica { diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 4b59984ad..dd1379503 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -2,26 +2,32 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cmath> -#include <boost/range/algorithm/fill.hpp> +#include <array> +#include <cstddef> +#include <memory> +#include <utility> -#include "common/alignment.h" +#include "common/assert.h" +#include "common/logging/log.h" #include "common/microprofile.h" -#include "common/profiler.h" +#include "common/vector_math.h" -#include "core/settings.h" #include "core/hle/service/gsp_gpu.h" #include "core/hw/gpu.h" +#include "core/memory.h" +#include "core/tracer/recorder.h" -#include "video_core/clipper.h" #include "video_core/command_processor.h" +#include "video_core/debug_utils/debug_utils.h" #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/pica_types.h" #include "video_core/primitive_assembly.h" +#include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" +#include "video_core/shader/shader.h" +#include "video_core/vertex_loader.h" #include "video_core/video_core.h" -#include "video_core/debug_utils/debug_utils.h" -#include "video_core/shader/shader_interpreter.h" namespace Pica { @@ -35,8 +41,6 @@ static int default_attr_counter = 0; static u32 default_attr_write_buffer[3]; -Common::Profiling::TimingCategory category_drawing("Drawing"); - // Expand a 4-bit mask to 4-byte mask, e.g. 0b0101 -> 0x00FF00FF static const u32 expand_bits_to_bytes[] = { 0x00000000, 0x000000ff, 0x0000ff00, 0x0000ffff, @@ -75,12 +79,17 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::P3D); break; + case PICA_REG_INDEX_WORKAROUND(triangle_topology, 0x25E): + g_state.primitive_assembler.Reconfigure(regs.triangle_topology); + break; + + case PICA_REG_INDEX_WORKAROUND(restart_primitive, 0x25F): + g_state.primitive_assembler.Reset(); + break; + case PICA_REG_INDEX_WORKAROUND(vs_default_attributes_setup.index, 0x232): - if (regs.vs_default_attributes_setup.index == 15) { - // Reset immediate primitive state - g_state.immediate.primitive_assembler.Reconfigure(regs.triangle_topology); - g_state.immediate.attribute_id = 0; - } + g_state.immediate.current_attribute = 0; + default_attr_counter = 0; break; // Load default vertex input attributes @@ -105,7 +114,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { break; } - Math::Vec4<float24>& attribute = g_state.vs.default_attributes[setup.index]; + Math::Vec4<float24> attribute; // NOTE: The destination component order indeed is "backwards" attribute.w = float24::FromRaw(default_attr_write_buffer[0] >> 8); @@ -119,26 +128,28 @@ 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; setup.index++; } else { // Put each attribute into an immediate input buffer. // When all specified immediate attributes are present, the Vertex Shader is invoked and everything is // sent to the primitive assembler. - auto& immediate_input = g_state.immediate.input; - auto& immediate_attribute_id = g_state.immediate.attribute_id; - const auto& attribute_config = regs.vertex_attributes; + auto& immediate_input = g_state.immediate.input_vertex; + auto& immediate_attribute_id = g_state.immediate.current_attribute; immediate_input.attr[immediate_attribute_id++] = attribute; - if (immediate_attribute_id >= attribute_config.GetNumTotalAttributes()) { + if (immediate_attribute_id >= regs.vs.num_input_attributes+1) { immediate_attribute_id = 0; Shader::UnitState<false> shader_unit; - Shader::Setup(shader_unit); + Shader::Setup(); // Send to vertex shader - Shader::OutputVertex output = Shader::Run(shader_unit, immediate_input, attribute_config.GetNumTotalAttributes()); + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, static_cast<void*>(&immediate_input)); + Shader::OutputVertex output = Shader::Run(shader_unit, immediate_input, regs.vs.num_input_attributes+1); // Send to renderer using Pica::Shader::OutputVertex; @@ -146,7 +157,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2); }; - g_state.immediate.primitive_assembler.SubmitVertex(output, AddTriangle); + g_state.primitive_assembler.SubmitVertex(output, AddTriangle); } } } @@ -154,9 +165,13 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { } case PICA_REG_INDEX(gpu_mode): - if (regs.gpu_mode == Regs::GPUMode::Configuring && regs.vs_default_attributes_setup.index == 15) { + if (regs.gpu_mode == Regs::GPUMode::Configuring) { // Draw immediate mode triangles when GPU Mode is set to GPUMode::Configuring VideoCore::g_renderer->Rasterizer()->DrawTriangles(); + + if (g_debug_context) { + g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); + } } break; @@ -174,60 +189,19 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { case PICA_REG_INDEX(trigger_draw): case PICA_REG_INDEX(trigger_draw_indexed): { - Common::Profiling::ScopeTimer scope_timer(category_drawing); MICROPROFILE_SCOPE(GPU_Drawing); #if PICA_LOG_TEV DebugUtils::DumpTevStageConfig(regs.GetTevStages()); #endif - if (g_debug_context) g_debug_context->OnEvent(DebugContext::Event::IncomingPrimitiveBatch, nullptr); - const auto& attribute_config = regs.vertex_attributes; - const u32 base_address = attribute_config.GetPhysicalBaseAddress(); - - // Information about internal vertex attributes - u32 vertex_attribute_sources[16]; - boost::fill(vertex_attribute_sources, 0xdeadbeef); - u32 vertex_attribute_strides[16] = {}; - Regs::VertexAttributeFormat vertex_attribute_formats[16] = {}; - - u32 vertex_attribute_elements[16] = {}; - u32 vertex_attribute_element_size[16] = {}; - - // Setup attribute data from loaders - for (int loader = 0; loader < 12; ++loader) { - const auto& loader_config = attribute_config.attribute_loaders[loader]; - - u32 offset = 0; - - // TODO: What happens if a loader overwrites a previous one's data? - for (unsigned component = 0; component < loader_config.component_count; ++component) { - if (component >= 12) { - LOG_ERROR(HW_GPU, "Overflow in the vertex attribute loader %u trying to load component %u", loader, component); - continue; - } - - u32 attribute_index = loader_config.GetComponent(component); - if (attribute_index < 12) { - int element_size = attribute_config.GetElementSizeInBytes(attribute_index); - offset = Common::AlignUp(offset, element_size); - vertex_attribute_sources[attribute_index] = base_address + loader_config.data_offset + offset; - vertex_attribute_strides[attribute_index] = static_cast<u32>(loader_config.byte_count); - vertex_attribute_formats[attribute_index] = attribute_config.GetFormat(attribute_index); - vertex_attribute_elements[attribute_index] = attribute_config.GetNumElements(attribute_index); - vertex_attribute_element_size[attribute_index] = element_size; - offset += attribute_config.GetStride(attribute_index); - } else if (attribute_index < 16) { - // Attribute ids 12, 13, 14 and 15 signify 4, 8, 12 and 16-byte paddings, respectively - offset = Common::AlignUp(offset, 4); - offset += (attribute_index - 11) * 4; - } else { - UNREACHABLE(); // This is truly unreachable due to the number of bits for each component - } - } - } + // 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); // Load vertices bool is_indexed = (id == PICA_REG_INDEX(trigger_draw_indexed)); @@ -237,11 +211,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { const u16* index_address_16 = reinterpret_cast<const u16*>(index_address_8); bool index_u16 = index_info.format != 0; -#if PICA_DUMP_GEOMETRY - DebugUtils::GeometryDumper geometry_dumper; - PrimitiveAssembler<DebugUtils::GeometryDumper::Vertex> dumping_primitive_assembler(regs.triangle_topology.Value()); -#endif - PrimitiveAssembler<Shader::OutputVertex> primitive_assembler(regs.triangle_topology.Value()); + PrimitiveAssembler<Shader::OutputVertex>& primitive_assembler = g_state.primitive_assembler; if (g_debug_context) { for (int i = 0; i < 3; ++i) { @@ -255,32 +225,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { } } - class { - /// Combine overlapping and close ranges - void SimplifyRanges() { - for (auto it = ranges.begin(); it != ranges.end(); ++it) { - // NOTE: We add 32 to the range end address to make sure "close" ranges are combined, too - auto it2 = std::next(it); - while (it2 != ranges.end() && it->first + it->second + 32 >= it2->first) { - it->second = std::max(it->second, it2->first + it2->second - it->first); - it2 = ranges.erase(it2); - } - } - } - - public: - /// Record a particular memory access in the list - void AddAccess(u32 paddr, u32 size) { - // Create new range or extend existing one - ranges[paddr] = std::max(ranges[paddr], size); - - // Simplify ranges... - SimplifyRanges(); - } - - /// Map of accessed ranges (mapping start address to range size) - std::map<u32, u32> ranges; - } memory_accesses; + DebugUtils::MemoryAccessTracker memory_accesses; // Simple circular-replacement vertex cache // The size has been tuned for optimal balance between hit-rate and the cost of lookup @@ -292,7 +237,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { vertex_cache_ids.fill(-1); Shader::UnitState<false> shader_unit; - Shader::Setup(shader_unit); + Shader::Setup(); for (unsigned int index = 0; index < regs.num_vertices; ++index) { @@ -324,71 +269,12 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { if (!vertex_cache_hit) { // Initialize data for the current vertex Shader::InputVertex input; + loader.LoadVertex(base_address, index, vertex, input, memory_accesses); - for (int i = 0; i < attribute_config.GetNumTotalAttributes(); ++i) { - if (vertex_attribute_elements[i] != 0) { - // Default attribute values set if array elements have < 4 components. This - // is *not* carried over from the default attribute settings even if they're - // enabled for this attribute. - static const float24 zero = float24::FromFloat32(0.0f); - static const float24 one = float24::FromFloat32(1.0f); - input.attr[i] = Math::Vec4<float24>(zero, zero, zero, one); - - // Load per-vertex data from the loader arrays - for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { - u32 source_addr = vertex_attribute_sources[i] + vertex_attribute_strides[i] * vertex + comp * vertex_attribute_element_size[i]; - const u8* srcdata = Memory::GetPhysicalPointer(source_addr); - - if (g_debug_context && Pica::g_debug_context->recorder) { - memory_accesses.AddAccess(source_addr, - (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::FLOAT) ? 4 - : (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::SHORT) ? 2 : 1); - } - - const float srcval = - (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::BYTE) ? *reinterpret_cast<const s8*>(srcdata) : - (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::UBYTE) ? *reinterpret_cast<const u8*>(srcdata) : - (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::SHORT) ? *reinterpret_cast<const s16*>(srcdata) : - *reinterpret_cast<const float*>(srcdata); - - input.attr[i][comp] = float24::FromFloat32(srcval); - LOG_TRACE(HW_GPU, "Loaded component %x of attribute %x for vertex %x (index %x) from 0x%08x + 0x%08x + 0x%04x: %f", - comp, i, vertex, index, - attribute_config.GetPhysicalBaseAddress(), - vertex_attribute_sources[i] - base_address, - vertex_attribute_strides[i] * vertex + comp * vertex_attribute_element_size[i], - input.attr[i][comp].ToFloat32()); - } - } else if (attribute_config.IsDefaultAttribute(i)) { - // Load the default attribute if we're configured to do so - 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(), - input.attr[i][2].ToFloat32(), input.attr[i][3].ToFloat32()); - } else { - // TODO(yuriks): In this case, no data gets loaded and the vertex - // remains with the last value it had. This isn't currently maintained - // as global state, however, and so won't work in Citra yet. - } - } - - if (g_debug_context) - g_debug_context->OnEvent(DebugContext::Event::VertexLoaded, (void*)&input); - -#if PICA_DUMP_GEOMETRY - // NOTE: When dumping geometry, we simply assume that the first input attribute - // corresponds to the position for now. - DebugUtils::GeometryDumper::Vertex dumped_vertex = { - input.attr[0][0].ToFloat32(), input.attr[0][1].ToFloat32(), input.attr[0][2].ToFloat32() - }; - using namespace std::placeholders; - dumping_primitive_assembler.SubmitVertex(dumped_vertex, - std::bind(&DebugUtils::GeometryDumper::AddTriangle, - &geometry_dumper, _1, _2, _3)); -#endif // Send to vertex shader - output = Shader::Run(shader_unit, input, attribute_config.GetNumTotalAttributes()); + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, (void*)&input); + output = Shader::Run(shader_unit, input, loader.GetNumTotalAttributes()); if (is_indexed) { vertex_cache[vertex_cache_pos] = output; @@ -412,16 +298,6 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { range.second, range.first); } - VideoCore::g_renderer->Rasterizer()->DrawTriangles(); - -#if PICA_DUMP_GEOMETRY - geometry_dumper.Dump(); -#endif - - if (g_debug_context) { - g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); - } - break; } diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index bac6d69c7..fb20f81dd 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -4,35 +4,41 @@ #include <algorithm> #include <condition_variable> +#include <cstdint> #include <cstring> #include <fstream> -#include <list> #include <map> #include <mutex> +#include <stdexcept> #include <string> #ifdef HAVE_PNG #include <png.h> +#include <setjmp.h> #endif +#include <nihstro/bit_field.h> #include <nihstro/float24.h> #include <nihstro/shader_binary.h> #include "common/assert.h" +#include "common/bit_field.h" #include "common/color.h" #include "common/common_types.h" #include "common/file_util.h" +#include "common/logging/log.h" #include "common/math_util.h" #include "common/vector_math.h" -#include "core/settings.h" - +#include "video_core/debug_utils/debug_utils.h" #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/pica_types.h" +#include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" +#include "video_core/shader/shader.h" #include "video_core/utils.h" #include "video_core/video_core.h" -#include "video_core/debug_utils/debug_utils.h" using nihstro::DVLBHeader; using nihstro::DVLEHeader; @@ -40,15 +46,12 @@ using nihstro::DVLPHeader; namespace Pica { -void DebugContext::OnEvent(Event event, void* data) { - if (!breakpoints[event].enabled) - return; - +void DebugContext::DoOnEvent(Event event, void* data) { { std::unique_lock<std::mutex> lock(breakpoint_mutex); - // Commit the hardware renderer's framebuffer so it will show on debug widgets - VideoCore::g_renderer->Rasterizer()->FlushFramebuffer(); + // Commit the rasterizer's caches so framebuffers, render targets, etc. will show on debug widgets + VideoCore::g_renderer->Rasterizer()->FlushAll(); // TODO: Should stop the CPU thread here once we multithread emulation. @@ -85,35 +88,6 @@ std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global namespace DebugUtils { -void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) { - vertices.push_back(v0); - vertices.push_back(v1); - vertices.push_back(v2); - - int num_vertices = (int)vertices.size(); - faces.push_back({{ num_vertices-3, num_vertices-2, num_vertices-1 }}); -} - -void GeometryDumper::Dump() { - static int index = 0; - std::string filename = std::string("geometry_dump") + std::to_string(++index) + ".obj"; - - std::ofstream file(filename); - - for (const auto& vertex : vertices) { - file << "v " << vertex.pos[0] - << " " << vertex.pos[1] - << " " << vertex.pos[2] << std::endl; - } - - for (const Face& face : faces) { - file << "f " << 1+face.index[0] - << " " << 1+face.index[1] - << " " << 1+face.index[2] << std::endl; - } -} - - void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, const Shader::ShaderSetup& setup, const Regs::VSOutputAttributes* output_attributes) { struct StuffToWrite { @@ -315,7 +289,7 @@ void StartPicaTracing() } std::lock_guard<std::mutex> lock(pica_trace_mutex); - pica_trace = std::unique_ptr<PicaTrace>(new PicaTrace); + pica_trace = std::make_unique<PicaTrace>(); is_pica_tracing = true; } @@ -615,6 +589,21 @@ TextureInfo TextureInfo::FromPicaRegister(const Regs::TextureConfig& config, return info; } +#ifdef HAVE_PNG +// Adapter functions to libpng to write/flush to File::IOFile instances. +static void WriteIOFile(png_structp png_ptr, png_bytep data, png_size_t length) { + auto* fp = static_cast<FileUtil::IOFile*>(png_get_io_ptr(png_ptr)); + if (!fp->WriteBytes(data, length)) + png_error(png_ptr, "Failed to write to output PNG file."); +} + +static void FlushIOFile(png_structp png_ptr) { + auto* fp = static_cast<FileUtil::IOFile*>(png_get_io_ptr(png_ptr)); + if (!fp->Flush()) + png_error(png_ptr, "Failed to flush to output PNG file."); +} +#endif + void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { #ifndef HAVE_PNG return; @@ -658,7 +647,7 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { goto finalise; } - png_init_io(png_ptr, fp.GetHandle()); + png_set_write_fn(png_ptr, static_cast<void*>(&fp), WriteIOFile, FlushIOFile); // Write header (8 bit color depth) png_set_IHDR(png_ptr, info_ptr, texture_config.width, texture_config.height, diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index 795160a32..f628292a4 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -4,23 +4,33 @@ #pragma once +#include <algorithm> #include <array> #include <condition_variable> +#include <iterator> #include <list> #include <map> #include <memory> #include <mutex> +#include <string> +#include <utility> #include <vector> +#include "common/common_types.h" #include "common/vector_math.h" -#include "core/tracer/recorder.h" - #include "video_core/pica.h" -#include "video_core/shader/shader.h" + +namespace CiTrace { +class Recorder; +} namespace Pica { +namespace Shader { +struct ShaderSetup; +} + class DebugContext { public: enum class Event { @@ -30,7 +40,7 @@ public: PicaCommandProcessed, IncomingPrimitiveBatch, FinishedPrimitiveBatch, - VertexLoaded, + VertexShaderInvocation, IncomingDisplayTransfer, GSPCommandProcessed, BufferSwapped, @@ -114,7 +124,15 @@ public: * @param event Event which has happened * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until Resume() is called. */ - void OnEvent(Event event, void* data); + void OnEvent(Event event, void* data) { + // This check is left in the header to allow the compiler to inline it. + if (!breakpoints[(int)event].enabled) + return; + // For the rest of event handling, call a separate function. + DoOnEvent(event, data); + } + + void DoOnEvent(Event event, void *data); /** * Resume from the current breakpoint. @@ -126,12 +144,14 @@ public: * Delete all set breakpoints and resume emulation. */ void ClearBreakpoints() { - breakpoints.clear(); + for (auto &bp : breakpoints) { + bp.enabled = false; + } Resume(); } // TODO: Evaluate if access to these members should be hidden behind a public interface. - std::map<Event, BreakPoint> breakpoints; + std::array<BreakPoint, (int)Event::NumEvents> breakpoints; Event active_breakpoint; bool at_breakpoint = false; @@ -158,30 +178,9 @@ extern std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this g namespace DebugUtils { -#define PICA_DUMP_GEOMETRY 0 #define PICA_DUMP_TEXTURES 0 #define PICA_LOG_TEV 0 -// Simple utility class for dumping geometry data to an OBJ file -class GeometryDumper { -public: - struct Vertex { - std::array<float,3> pos; - }; - - void AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2); - - void Dump(); - -private: - struct Face { - int index[3]; - }; - - std::vector<Vertex> vertices; - std::vector<Face> faces; -}; - void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, const Shader::ShaderSetup& setup, const Regs::VSOutputAttributes* output_attributes); @@ -227,6 +226,36 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data); void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages); +/** + * Used in the vertex loader to merge access records. TODO: Investigate if actually useful. + */ +class MemoryAccessTracker { + /// Combine overlapping and close ranges + void SimplifyRanges() { + for (auto it = ranges.begin(); it != ranges.end(); ++it) { + // NOTE: We add 32 to the range end address to make sure "close" ranges are combined, too + auto it2 = std::next(it); + while (it2 != ranges.end() && it->first + it->second + 32 >= it2->first) { + it->second = std::max(it->second, it2->first + it2->second - it->first); + it2 = ranges.erase(it2); + } + } + } + +public: + /// Record a particular memory access in the list + void AddAccess(u32 paddr, u32 size) { + // Create new range or extend existing one + ranges[paddr] = std::max(ranges[paddr], size); + + // Simplify ranges... + SimplifyRanges(); + } + + /// Map of accessed ranges (mapping start address to range size) + std::map<u32, u32> ranges; +}; + } // namespace } // namespace diff --git a/src/video_core/pica.cpp b/src/video_core/pica.cpp index 32ad72674..be82cf4b5 100644 --- a/src/video_core/pica.cpp +++ b/src/video_core/pica.cpp @@ -3,10 +3,13 @@ // Refer to the license.txt file included. #include <cstring> +#include <iterator> #include <unordered_map> +#include <utility> #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/primitive_assembly.h" #include "video_core/shader/shader.h" namespace Pica { @@ -480,7 +483,7 @@ std::string Regs::GetCommandName(int index) { static std::unordered_map<u32, const char*> map; if (map.empty()) { - map.insert(begin(register_names), end(register_names)); + map.insert(std::begin(register_names), std::end(register_names)); } // Return empty string if no match is found @@ -493,12 +496,25 @@ std::string Regs::GetCommandName(int index) { } void Init() { + g_state.Reset(); } void Shutdown() { Shader::Shutdown(); +} + +template <typename T> +void Zero(T& o) { + memset(&o, 0, sizeof(o)); +} - memset(&g_state, 0, sizeof(State)); +void State::Reset() { + Zero(regs); + Zero(vs); + Zero(gs); + Zero(cmd_list); + Zero(immediate); + primitive_assembler.Reconfigure(Regs::TriangleTopology::List); } } diff --git a/src/video_core/pica.h b/src/video_core/pica.h index 337cff8ce..5891fb72a 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -5,10 +5,13 @@ #pragma once #include <array> -#include <cmath> #include <cstddef> #include <string> +#ifndef _MSC_VER +#include <type_traits> // for std::enable_if +#endif + #include "common/assert.h" #include "common/bit_field.h" #include "common/common_funcs.h" @@ -16,8 +19,6 @@ #include "common/vector_math.h" #include "common/logging/log.h" -#include "pica_types.h" - namespace Pica { // Returns index corresponding to the Regs member labeled by field_name @@ -71,7 +72,7 @@ struct Regs { BitField<0, 24, u32> viewport_depth_range; // float24 BitField<0, 24, u32> viewport_depth_far_plane; // float24 - INSERT_PADDING_WORDS(0x1); + BitField<0, 3, u32> vs_output_total; union VSOutputAttributes { // Maps components of output vertex attributes to semantics @@ -577,8 +578,18 @@ struct Regs { } } - struct { - INSERT_PADDING_WORDS(0x6); + struct FramebufferConfig { + INSERT_PADDING_WORDS(0x3); + + union { + BitField<0, 4, u32> allow_color_write; // 0 = disable, else enable + }; + + INSERT_PADDING_WORDS(0x1); + + union { + BitField<0, 2, u32> allow_depth_stencil_write; // 0 = disable, else enable + }; DepthFormat depth_format; // TODO: Should be a BitField! BitField<16, 3, ColorFormat> color_format; @@ -737,8 +748,13 @@ struct Regs { case LightingSampler::ReflectGreen: case LightingSampler::ReflectBlue: return (config == LightingConfig::Config4) || (config == LightingConfig::Config5) || (config == LightingConfig::Config7); + default: + UNREACHABLE_MSG("Regs::IsLightingSamplerSupported: Reached " + "unreachable section, sampler should be one " + "of Distribution0, Distribution1, Fresnel, " + "ReflectRed, ReflectGreen or ReflectBlue, instead " + "got %i", static_cast<int>(config)); } - return false; } struct { @@ -1123,7 +1139,12 @@ struct Regs { BitField<24, 8, u32> w; } int_uniforms[4]; - INSERT_PADDING_WORDS(0x5); + INSERT_PADDING_WORDS(0x4); + + union { + // Number of input attributes to shader unit - 1 + BitField<0, 4, u32> num_input_attributes; + }; // Offset to shader program entry point (in words) BitField<0, 16, u32> main_offset; @@ -1157,8 +1178,10 @@ struct Regs { } } input_register_map; - // OUTMAP_MASK, 0x28E, CODETRANSFER_END - INSERT_PADDING_WORDS(0x3); + BitField<0, 16, u32> output_mask; + + // 0x28E, CODETRANSFER_END + INSERT_PADDING_WORDS(0x2); struct { enum Format : u32 diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h index c7616bc55..bbecad850 100644 --- a/src/video_core/pica_state.h +++ b/src/video_core/pica_state.h @@ -4,6 +4,11 @@ #pragma once +#include <array> + +#include "common/bit_field.h" +#include "common/common_types.h" + #include "video_core/pica.h" #include "video_core/primitive_assembly.h" #include "video_core/shader/shader.h" @@ -12,6 +17,8 @@ namespace Pica { /// Struct used to describe current Pica state struct State { + void Reset(); + /// Pica registers Regs regs; @@ -46,13 +53,14 @@ struct State { /// Struct used to describe immediate mode rendering state struct ImmediateModeState { - Shader::InputVertex input; - // This is constructed with a dummy triangle topology - PrimitiveAssembler<Shader::OutputVertex> primitive_assembler; - int attribute_id = 0; - - ImmediateModeState() : primitive_assembler(Regs::TriangleTopology::List) {} + // 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; } immediate; + + // This is constructed with a dummy triangle topology + PrimitiveAssembler<Shader::OutputVertex> primitive_assembler; }; extern State g_state; ///< Current Pica state diff --git a/src/video_core/pica_types.h b/src/video_core/pica_types.h index ecf45654b..3b7bfbdca 100644 --- a/src/video_core/pica_types.h +++ b/src/video_core/pica_types.h @@ -4,6 +4,7 @@ #pragma once +#include <cmath> #include <cstring> #include "common/common_types.h" diff --git a/src/video_core/primitive_assembly.cpp b/src/video_core/primitive_assembly.cpp index 0061690f1..68ea3c08a 100644 --- a/src/video_core/primitive_assembly.cpp +++ b/src/video_core/primitive_assembly.cpp @@ -6,8 +6,7 @@ #include "video_core/pica.h" #include "video_core/primitive_assembly.h" -#include "video_core/debug_utils/debug_utils.h" -#include "video_core/shader/shader_interpreter.h" +#include "video_core/shader/shader.h" namespace Pica { @@ -68,7 +67,5 @@ void PrimitiveAssembler<VertexType>::Reconfigure(Regs::TriangleTopology topology // explicitly instantiate use cases template struct PrimitiveAssembler<Shader::OutputVertex>; -template -struct PrimitiveAssembler<DebugUtils::GeometryDumper::Vertex>; } // namespace diff --git a/src/video_core/primitive_assembly.h b/src/video_core/primitive_assembly.h index cc6e5fde5..9396b4c85 100644 --- a/src/video_core/primitive_assembly.h +++ b/src/video_core/primitive_assembly.h @@ -20,7 +20,7 @@ struct PrimitiveAssembler { VertexType& v1, VertexType& v2)>; - PrimitiveAssembler(Regs::TriangleTopology topology); + PrimitiveAssembler(Regs::TriangleTopology topology = Regs::TriangleTopology::List); /* * Queues a vertex, builds primitives from the vertex queue according to the given diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp index fd02aa652..df67b9081 100644 --- a/src/video_core/rasterizer.cpp +++ b/src/video_core/rasterizer.cpp @@ -3,23 +3,28 @@ // Refer to the license.txt file included. #include <algorithm> +#include <array> #include <cmath> +#include "common/assert.h" +#include "common/bit_field.h" #include "common/color.h" #include "common/common_types.h" +#include "common/logging/log.h" #include "common/math_util.h" #include "common/microprofile.h" -#include "common/profiler.h" +#include "common/vector_math.h" #include "core/memory.h" #include "core/hw/gpu.h" +#include "video_core/debug_utils/debug_utils.h" #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/pica_types.h" #include "video_core/rasterizer.h" #include "video_core/utils.h" -#include "video_core/debug_utils/debug_utils.h" -#include "video_core/shader/shader_interpreter.h" +#include "video_core/shader/shader.h" namespace Pica { @@ -287,7 +292,6 @@ static int SignedArea (const Math::Vec2<Fix12P4>& vtx1, return Math::Cross(vec1, vec2).z; }; -static Common::Profiling::TimingCategory rasterization_category("Rasterization"); MICROPROFILE_DEFINE(GPU_Rasterization, "GPU", "Rasterization", MP_RGB(50, 50, 240)); /** @@ -300,7 +304,6 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, bool reversed = false) { const auto& regs = g_state.regs; - Common::Profiling::ScopeTimer timer(rasterization_category); MICROPROFILE_SCOPE(GPU_Rasterization); // vertex positions in rasterizer coordinates @@ -809,7 +812,8 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, auto UpdateStencil = [stencil_test, x, y, &old_stencil](Pica::Regs::StencilAction action) { u8 new_stencil = PerformStencilAction(action, old_stencil, stencil_test.reference_value); - SetStencil(x >> 4, y >> 4, (new_stencil & stencil_test.write_mask) | (old_stencil & ~stencil_test.write_mask)); + if (g_state.regs.framebuffer.allow_depth_stencil_write != 0) + SetStencil(x >> 4, y >> 4, (new_stencil & stencil_test.write_mask) | (old_stencil & ~stencil_test.write_mask)); }; if (stencil_action_enable) { @@ -909,7 +913,7 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, } } - if (output_merger.depth_write_enable) + if (regs.framebuffer.allow_depth_stencil_write != 0 && output_merger.depth_write_enable) SetDepth(x >> 4, y >> 4, z); // The stencil depth_pass action is executed even if depth testing is disabled @@ -922,92 +926,72 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, if (output_merger.alphablend_enable) { auto params = output_merger.alpha_blending; - auto LookupFactorRGB = [&](Regs::BlendFactor factor) -> Math::Vec3<u8> { + auto LookupFactor = [&](unsigned channel, Regs::BlendFactor factor) -> u8 { + DEBUG_ASSERT(channel < 4); + + const Math::Vec4<u8> blend_const = { + static_cast<u8>(output_merger.blend_const.r), + static_cast<u8>(output_merger.blend_const.g), + static_cast<u8>(output_merger.blend_const.b), + static_cast<u8>(output_merger.blend_const.a) + }; + switch (factor) { - case Regs::BlendFactor::Zero : - return Math::Vec3<u8>(0, 0, 0); + case Regs::BlendFactor::Zero: + return 0; - case Regs::BlendFactor::One : - return Math::Vec3<u8>(255, 255, 255); + case Regs::BlendFactor::One: + return 255; case Regs::BlendFactor::SourceColor: - return combiner_output.rgb(); + return combiner_output[channel]; case Regs::BlendFactor::OneMinusSourceColor: - return Math::Vec3<u8>(255 - combiner_output.r(), 255 - combiner_output.g(), 255 - combiner_output.b()); + return 255 - combiner_output[channel]; case Regs::BlendFactor::DestColor: - return dest.rgb(); + return dest[channel]; case Regs::BlendFactor::OneMinusDestColor: - return Math::Vec3<u8>(255 - dest.r(), 255 - dest.g(), 255 - dest.b()); + return 255 - dest[channel]; case Regs::BlendFactor::SourceAlpha: - return Math::Vec3<u8>(combiner_output.a(), combiner_output.a(), combiner_output.a()); + return combiner_output.a(); case Regs::BlendFactor::OneMinusSourceAlpha: - return Math::Vec3<u8>(255 - combiner_output.a(), 255 - combiner_output.a(), 255 - combiner_output.a()); + return 255 - combiner_output.a(); case Regs::BlendFactor::DestAlpha: - return Math::Vec3<u8>(dest.a(), dest.a(), dest.a()); + return dest.a(); case Regs::BlendFactor::OneMinusDestAlpha: - return Math::Vec3<u8>(255 - dest.a(), 255 - dest.a(), 255 - dest.a()); + return 255 - dest.a(); case Regs::BlendFactor::ConstantColor: - return Math::Vec3<u8>(output_merger.blend_const.r, output_merger.blend_const.g, output_merger.blend_const.b); + return blend_const[channel]; case Regs::BlendFactor::OneMinusConstantColor: - return Math::Vec3<u8>(255 - output_merger.blend_const.r, 255 - output_merger.blend_const.g, 255 - output_merger.blend_const.b); + return 255 - blend_const[channel]; case Regs::BlendFactor::ConstantAlpha: - return Math::Vec3<u8>(output_merger.blend_const.a, output_merger.blend_const.a, output_merger.blend_const.a); + return blend_const.a(); case Regs::BlendFactor::OneMinusConstantAlpha: - return Math::Vec3<u8>(255 - output_merger.blend_const.a, 255 - output_merger.blend_const.a, 255 - output_merger.blend_const.a); - - default: - LOG_CRITICAL(HW_GPU, "Unknown color blend factor %x", factor); - UNIMPLEMENTED(); - break; - } - - return {}; - }; - - auto LookupFactorA = [&](Regs::BlendFactor factor) -> u8 { - switch (factor) { - case Regs::BlendFactor::Zero: - return 0; + return 255 - blend_const.a(); - case Regs::BlendFactor::One: - return 255; - - case Regs::BlendFactor::SourceAlpha: - return combiner_output.a(); - - case Regs::BlendFactor::OneMinusSourceAlpha: - return 255 - combiner_output.a(); - - case Regs::BlendFactor::DestAlpha: - return dest.a(); - - case Regs::BlendFactor::OneMinusDestAlpha: - return 255 - dest.a(); - - case Regs::BlendFactor::ConstantAlpha: - return output_merger.blend_const.a; - - case Regs::BlendFactor::OneMinusConstantAlpha: - return 255 - output_merger.blend_const.a; + case Regs::BlendFactor::SourceAlphaSaturate: + // Returns 1.0 for the alpha channel + if (channel == 3) + return 255; + return std::min(combiner_output.a(), static_cast<u8>(255 - dest.a())); default: - LOG_CRITICAL(HW_GPU, "Unknown alpha blend factor %x", factor); + LOG_CRITICAL(HW_GPU, "Unknown blend factor %x", factor); UNIMPLEMENTED(); break; } - return {}; + return combiner_output[channel]; }; static auto EvaluateBlendEquation = [](const Math::Vec4<u8>& src, const Math::Vec4<u8>& srcfactor, @@ -1059,10 +1043,15 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, MathUtil::Clamp(result.a(), 0, 255)); }; - auto srcfactor = Math::MakeVec(LookupFactorRGB(params.factor_source_rgb), - LookupFactorA(params.factor_source_a)); - auto dstfactor = Math::MakeVec(LookupFactorRGB(params.factor_dest_rgb), - LookupFactorA(params.factor_dest_a)); + auto srcfactor = Math::MakeVec(LookupFactor(0, params.factor_source_rgb), + LookupFactor(1, params.factor_source_rgb), + LookupFactor(2, params.factor_source_rgb), + LookupFactor(3, params.factor_source_a)); + + auto dstfactor = Math::MakeVec(LookupFactor(0, params.factor_dest_rgb), + LookupFactor(1, params.factor_dest_rgb), + LookupFactor(2, params.factor_dest_rgb), + LookupFactor(3, params.factor_dest_a)); blend_output = EvaluateBlendEquation(combiner_output, srcfactor, dest, dstfactor, params.blend_equation_rgb); blend_output.a() = EvaluateBlendEquation(combiner_output, srcfactor, dest, dstfactor, params.blend_equation_a).a(); @@ -1133,7 +1122,8 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, output_merger.alpha_enable ? blend_output.a() : dest.a() }; - DrawPixel(x >> 4, y >> 4, result); + if (regs.framebuffer.allow_color_write != 0) + DrawPixel(x >> 4, y >> 4, result); } } } diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 008c5827b..bf7101665 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -6,6 +6,10 @@ #include "common/common_types.h" +#include "core/hw/gpu.h" + +struct ScreenInfo; + namespace Pica { namespace Shader { struct OutputVertex; @@ -18,12 +22,6 @@ class RasterizerInterface { public: virtual ~RasterizerInterface() {} - /// Initialize API-specific GPU objects - virtual void InitObjects() = 0; - - /// Reset the rasterizer, such as flushing all caches and updating all state - virtual void Reset() = 0; - /// Queues the primitive formed by the given vertices for rendering virtual void AddTriangle(const Pica::Shader::OutputVertex& v0, const Pica::Shader::OutputVertex& v1, @@ -32,17 +30,26 @@ public: /// Draw the current batch of triangles virtual void DrawTriangles() = 0; - /// Commit the rasterizer's framebuffer contents immediately to the current 3DS memory framebuffer - virtual void FlushFramebuffer() = 0; - /// Notify rasterizer that the specified PICA register has been changed virtual void NotifyPicaRegisterChanged(u32 id) = 0; - /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory. + /// Notify rasterizer that all caches should be flushed to 3DS memory + virtual void FlushAll() = 0; + + /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory virtual void FlushRegion(PAddr addr, u32 size) = 0; - /// Notify rasterizer that any caches of the specified region should be discraded and reloaded from 3DS memory. - virtual void InvalidateRegion(PAddr addr, u32 size) = 0; + /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory and invalidated + virtual void FlushAndInvalidateRegion(PAddr addr, u32 size) = 0; + + /// Attempt to use a faster method to perform a display transfer + virtual bool AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) { return false; } + + /// Attempt to use a faster method to fill a region + virtual bool AccelerateFill(const GPU::Regs::MemoryFillConfig& config) { return false; } + + /// Attempt to use a faster method to display the framebuffer to screen + virtual bool AccelerateDisplay(const GPU::Regs::FramebufferConfig& config, PAddr framebuffer_addr, u32 pixel_stride, ScreenInfo& screen_info) { return false; } }; } diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index 6467ff723..3f451e062 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -2,12 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <atomic> #include <memory> -#include "common/make_unique.h" - -#include "core/settings.h" - #include "video_core/renderer_base.h" #include "video_core/video_core.h" #include "video_core/swrasterizer.h" @@ -19,11 +16,9 @@ void RendererBase::RefreshRasterizerSetting() { opengl_rasterizer_active = hw_renderer_enabled; if (hw_renderer_enabled) { - rasterizer = Common::make_unique<RasterizerOpenGL>(); + rasterizer = std::make_unique<RasterizerOpenGL>(); } else { - rasterizer = Common::make_unique<VideoCore::SWRasterizer>(); + rasterizer = std::make_unique<VideoCore::SWRasterizer>(); } - rasterizer->InitObjects(); - rasterizer->Reset(); } } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index b3dc6aa19..519d81aeb 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -2,29 +2,28 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cstring> #include <memory> +#include <string> +#include <tuple> +#include <utility> #include <glad/glad.h> +#include "common/assert.h" #include "common/color.h" -#include "common/file_util.h" -#include "common/make_unique.h" +#include "common/logging/log.h" #include "common/math_util.h" -#include "common/microprofile.h" -#include "common/profiler.h" +#include "common/vector_math.h" -#include "core/memory.h" -#include "core/settings.h" #include "core/hw/gpu.h" #include "video_core/pica.h" #include "video_core/pica_state.h" -#include "video_core/utils.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_gen.h" #include "video_core/renderer_opengl/gl_shader_util.h" #include "video_core/renderer_opengl/pica_to_gl.h" +#include "video_core/renderer_opengl/renderer_opengl.h" static bool IsPassThroughTevStage(const Pica::Regs::TevStageConfig& stage) { return (stage.color_op == Pica::Regs::TevStageConfig::Operation::Replace && @@ -37,10 +36,7 @@ static bool IsPassThroughTevStage(const Pica::Regs::TevStageConfig& stage) { stage.GetAlphaMultiplier() == 1); } -RasterizerOpenGL::RasterizerOpenGL() : cached_fb_color_addr(0), cached_fb_depth_addr(0) { } -RasterizerOpenGL::~RasterizerOpenGL() { } - -void RasterizerOpenGL::InitObjects() { +RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { // Create sampler objects for (size_t i = 0; i < texture_samplers.size(); ++i) { texture_samplers[i].Create(); @@ -62,6 +58,10 @@ void RasterizerOpenGL::InitObjects() { uniform_block_data.dirty = true; + for (unsigned index = 0; index < lighting_luts.size(); index++) { + uniform_block_data.lut_dirty[index] = true; + } + // Set vertex attributes glVertexAttribPointer(GLShader::ATTRIBUTE_POSITION, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, position)); glEnableVertexAttribArray(GLShader::ATTRIBUTE_POSITION); @@ -82,69 +82,24 @@ void RasterizerOpenGL::InitObjects() { glVertexAttribPointer(GLShader::ATTRIBUTE_VIEW, 3, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, view)); glEnableVertexAttribArray(GLShader::ATTRIBUTE_VIEW); - SetShader(); - - // Create textures for OGL framebuffer that will be rendered to, initially 1x1 to succeed in framebuffer creation - fb_color_texture.texture.Create(); - ReconfigureColorTexture(fb_color_texture, Pica::Regs::ColorFormat::RGBA8, 1, 1); - - state.texture_units[0].texture_2d = fb_color_texture.texture.handle; - state.Apply(); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - state.texture_units[0].texture_2d = 0; - state.Apply(); - - fb_depth_texture.texture.Create(); - ReconfigureDepthTexture(fb_depth_texture, Pica::Regs::DepthFormat::D16, 1, 1); - - state.texture_units[0].texture_2d = fb_depth_texture.texture.handle; - state.Apply(); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); - - state.texture_units[0].texture_2d = 0; - state.Apply(); - - // Configure OpenGL framebuffer + // Create render framebuffer framebuffer.Create(); - state.draw.framebuffer = framebuffer.handle; + // Allocate and bind lighting lut textures + for (size_t i = 0; i < lighting_luts.size(); ++i) { + lighting_luts[i].Create(); + state.lighting_luts[i].texture_1d = lighting_luts[i].handle; + } state.Apply(); - glActiveTexture(GL_TEXTURE0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb_color_texture.texture.handle, 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, fb_depth_texture.texture.handle, 0); - - for (size_t i = 0; i < lighting_lut.size(); ++i) { - lighting_lut[i].Create(); - state.lighting_lut[i].texture_1d = lighting_lut[i].handle; - + for (size_t i = 0; i < lighting_luts.size(); ++i) { glActiveTexture(GL_TEXTURE3 + i); - glBindTexture(GL_TEXTURE_1D, state.lighting_lut[i].texture_1d); - 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); } - state.Apply(); - ASSERT_MSG(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, - "OpenGL rasterizer framebuffer setup failed, status %X", glCheckFramebufferStatus(GL_FRAMEBUFFER)); -} - -void RasterizerOpenGL::Reset() { + // Sync fixed function OpenGL state SyncCullMode(); SyncDepthModifiers(); SyncBlendEnabled(); @@ -153,10 +108,13 @@ void RasterizerOpenGL::Reset() { SyncLogicOp(); SyncStencilTest(); SyncDepthTest(); + SyncColorWriteMask(); + SyncStencilWriteMask(); + SyncDepthWriteMask(); +} - SetShader(); +RasterizerOpenGL::~RasterizerOpenGL() { - res_cache.InvalidateAll(); } /** @@ -190,47 +148,101 @@ void RasterizerOpenGL::AddTriangle(const Pica::Shader::OutputVertex& v0, } void RasterizerOpenGL::DrawTriangles() { - SyncFramebuffer(); - SyncDrawState(); + if (vertex_batch.empty()) + return; + + const auto& regs = Pica::g_state.regs; + + // Sync and bind the framebuffer surfaces + CachedSurface* color_surface; + CachedSurface* depth_surface; + MathUtil::Rectangle<int> rect; + std::tie(color_surface, depth_surface, rect) = res_cache.GetFramebufferSurfaces(regs.framebuffer); + + state.draw.draw_framebuffer = framebuffer.handle; + state.Apply(); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_surface != nullptr ? color_surface->texture.handle : 0, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_surface != nullptr ? depth_surface->texture.handle : 0, 0); + bool has_stencil = regs.framebuffer.depth_format == Pica::Regs::DepthFormat::D24S8; + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, (has_stencil && depth_surface != nullptr) ? depth_surface->texture.handle : 0, 0); + + if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + return; + } + + // Sync the viewport + // These registers hold half-width and half-height, so must be multiplied by 2 + GLsizei viewport_width = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_x).ToFloat32() * 2; + GLsizei viewport_height = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_y).ToFloat32() * 2; + + glViewport((GLint)(rect.left + regs.viewport_corner.x * color_surface->res_scale_width), + (GLint)(rect.bottom + regs.viewport_corner.y * color_surface->res_scale_height), + (GLsizei)(viewport_width * color_surface->res_scale_width), (GLsizei)(viewport_height * color_surface->res_scale_height)); + + // Sync and bind the texture surfaces + const auto pica_textures = regs.GetTextures(); + for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) { + const auto& texture = pica_textures[texture_index]; - if (state.draw.shader_dirty) { + if (texture.enabled) { + texture_samplers[texture_index].SyncWithConfig(texture.config); + CachedSurface* surface = res_cache.GetTextureSurface(texture); + if (surface != nullptr) { + state.texture_units[texture_index].texture_2d = surface->texture.handle; + } else { + // Can occur when texture addr is null or its memory is unmapped/invalid + state.texture_units[texture_index].texture_2d = 0; + } + } else { + state.texture_units[texture_index].texture_2d = 0; + } + } + + // Sync and bind the shader + if (shader_dirty) { SetShader(); - state.draw.shader_dirty = false; + shader_dirty = false; } - for (unsigned index = 0; index < lighting_lut.size(); index++) { + // Sync the lighting luts + for (unsigned index = 0; index < lighting_luts.size(); index++) { if (uniform_block_data.lut_dirty[index]) { SyncLightingLUT(index); uniform_block_data.lut_dirty[index] = false; } } + // Sync the uniform data if (uniform_block_data.dirty) { glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformData), &uniform_block_data.data, GL_STATIC_DRAW); uniform_block_data.dirty = false; } + state.Apply(); + + // Draw the vertex batch glBufferData(GL_ARRAY_BUFFER, vertex_batch.size() * sizeof(HardwareVertex), vertex_batch.data(), GL_STREAM_DRAW); glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertex_batch.size()); - vertex_batch.clear(); - - // Flush the resource cache at the current depth and color framebuffer addresses for render-to-texture - const auto& regs = Pica::g_state.regs; - - u32 cached_fb_color_size = Pica::Regs::BytesPerColorPixel(fb_color_texture.format) - * fb_color_texture.width * fb_color_texture.height; - - u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format) - * fb_depth_texture.width * fb_depth_texture.height; + // Mark framebuffer surfaces as dirty + // TODO: Restrict invalidation area to the viewport + if (color_surface != nullptr) { + color_surface->dirty = true; + res_cache.FlushRegion(color_surface->addr, color_surface->size, color_surface, true); + } + if (depth_surface != nullptr) { + depth_surface->dirty = true; + res_cache.FlushRegion(depth_surface->addr, depth_surface->size, depth_surface, true); + } - res_cache.InvalidateInRange(cached_fb_color_addr, cached_fb_color_size, true); - res_cache.InvalidateInRange(cached_fb_depth_addr, cached_fb_depth_size, true); -} + vertex_batch.clear(); -void RasterizerOpenGL::FlushFramebuffer() { - CommitColorBuffer(); - CommitDepthBuffer(); + // Unbind textures for potential future use as framebuffer attachments + for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) { + state.texture_units[texture_index].texture_2d = 0; + } + state.Apply(); } void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { @@ -262,18 +274,39 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { // Alpha test case PICA_REG_INDEX(output_merger.alpha_test): SyncAlphaTest(); - state.draw.shader_dirty = true; + shader_dirty = true; break; - // Stencil test + // Sync GL stencil test + stencil write mask + // (Pica stencil test function register also contains a stencil write mask) case PICA_REG_INDEX(output_merger.stencil_test.raw_func): + SyncStencilTest(); + SyncStencilWriteMask(); + break; case PICA_REG_INDEX(output_merger.stencil_test.raw_op): + case PICA_REG_INDEX(framebuffer.depth_format): SyncStencilTest(); break; - // Depth test + // Sync GL depth test + depth and color write mask + // (Pica depth test function register also contains a depth and color write mask) case PICA_REG_INDEX(output_merger.depth_test_enable): SyncDepthTest(); + SyncDepthWriteMask(); + SyncColorWriteMask(); + break; + + // Sync GL depth and stencil write mask + // (This is a dedicated combined depth / stencil write-enable register) + case PICA_REG_INDEX(framebuffer.allow_depth_stencil_write): + SyncDepthWriteMask(); + SyncStencilWriteMask(); + break; + + // Sync GL color write mask + // (This is a dedicated color write-enable register) + case PICA_REG_INDEX(framebuffer.allow_color_write): + SyncColorWriteMask(); break; // Logic op @@ -307,7 +340,7 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { case PICA_REG_INDEX(tev_stage5.color_op): case PICA_REG_INDEX(tev_stage5.color_scale): case PICA_REG_INDEX(tev_combiner_buffer_input): - state.draw.shader_dirty = true; + shader_dirty = true; break; case PICA_REG_INDEX(tev_stage0.const_r): SyncTevConstColor(0, regs.tev_stage0); @@ -494,41 +527,257 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { } } +void RasterizerOpenGL::FlushAll() { + res_cache.FlushAll(); +} + void RasterizerOpenGL::FlushRegion(PAddr addr, u32 size) { - const auto& regs = Pica::g_state.regs; + res_cache.FlushRegion(addr, size, nullptr, false); +} + +void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) { + res_cache.FlushRegion(addr, size, nullptr, true); +} + +bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) { + using PixelFormat = CachedSurface::PixelFormat; + using SurfaceType = CachedSurface::SurfaceType; - u32 cached_fb_color_size = Pica::Regs::BytesPerColorPixel(fb_color_texture.format) - * fb_color_texture.width * fb_color_texture.height; + if (config.is_texture_copy) { + // TODO(tfarley): Try to hardware accelerate this + return false; + } + + CachedSurface src_params; + src_params.addr = config.GetPhysicalInputAddress(); + src_params.width = config.output_width; + src_params.height = config.output_height; + src_params.is_tiled = !config.input_linear; + src_params.pixel_format = CachedSurface::PixelFormatFromGPUPixelFormat(config.input_format); + + CachedSurface dst_params; + dst_params.addr = config.GetPhysicalOutputAddress(); + dst_params.width = config.scaling != config.NoScale ? config.output_width / 2 : config.output_width.Value(); + dst_params.height = config.scaling == config.ScaleXY ? config.output_height / 2 : config.output_height.Value(); + dst_params.is_tiled = config.input_linear != config.dont_swizzle; + dst_params.pixel_format = CachedSurface::PixelFormatFromGPUPixelFormat(config.output_format); + + MathUtil::Rectangle<int> src_rect; + CachedSurface* src_surface = res_cache.GetSurfaceRect(src_params, false, true, src_rect); + + if (src_surface == nullptr) { + return false; + } - u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format) - * fb_depth_texture.width * fb_depth_texture.height; + // Require destination surface to have same resolution scale as source to preserve scaling + dst_params.res_scale_width = src_surface->res_scale_width; + dst_params.res_scale_height = src_surface->res_scale_height; - // If source memory region overlaps 3DS framebuffers, commit them before the copy happens - if (MathUtil::IntervalsIntersect(addr, size, cached_fb_color_addr, cached_fb_color_size)) - CommitColorBuffer(); + MathUtil::Rectangle<int> dst_rect; + CachedSurface* dst_surface = res_cache.GetSurfaceRect(dst_params, true, false, dst_rect); + + if (dst_surface == nullptr) { + return false; + } - if (MathUtil::IntervalsIntersect(addr, size, cached_fb_depth_addr, cached_fb_depth_size)) - CommitDepthBuffer(); + // Don't accelerate if the src and dst surfaces are the same + if (src_surface == dst_surface) { + return false; + } + + if (config.flip_vertically) { + std::swap(dst_rect.top, dst_rect.bottom); + } + + if (!res_cache.TryBlitSurfaces(src_surface, src_rect, dst_surface, dst_rect)) { + return false; + } + + u32 dst_size = dst_params.width * dst_params.height * CachedSurface::GetFormatBpp(dst_params.pixel_format) / 8; + dst_surface->dirty = true; + res_cache.FlushRegion(config.GetPhysicalOutputAddress(), dst_size, dst_surface, true); + return true; } -void RasterizerOpenGL::InvalidateRegion(PAddr addr, u32 size) { - const auto& regs = Pica::g_state.regs; +bool RasterizerOpenGL::AccelerateFill(const GPU::Regs::MemoryFillConfig& config) { + using PixelFormat = CachedSurface::PixelFormat; + using SurfaceType = CachedSurface::SurfaceType; + + CachedSurface* dst_surface = res_cache.TryGetFillSurface(config); + + if (dst_surface == nullptr) { + return false; + } + + OpenGLState cur_state = OpenGLState::GetCurState(); + + SurfaceType dst_type = CachedSurface::GetFormatType(dst_surface->pixel_format); + + GLuint old_fb = cur_state.draw.draw_framebuffer; + cur_state.draw.draw_framebuffer = framebuffer.handle; + // TODO: When scissor test is implemented, need to disable scissor test in cur_state here so Clear call isn't affected + cur_state.Apply(); + + if (dst_type == SurfaceType::Color || dst_type == SurfaceType::Texture) { + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_surface->texture.handle, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + return false; + } + + GLfloat color_values[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + + // TODO: Handle additional pixel format and fill value size combinations to accelerate more cases + // For instance, checking if fill value's bytes/bits repeat to allow filling I8/A8/I4/A4/... + // Currently only handles formats that are multiples of the fill value size + + if (config.fill_24bit) { + switch (dst_surface->pixel_format) { + case PixelFormat::RGB8: + color_values[0] = config.value_24bit_r / 255.0f; + color_values[1] = config.value_24bit_g / 255.0f; + color_values[2] = config.value_24bit_b / 255.0f; + break; + default: + return false; + } + } else if (config.fill_32bit) { + u32 value = config.value_32bit; + + switch (dst_surface->pixel_format) { + case PixelFormat::RGBA8: + color_values[0] = (value >> 24) / 255.0f; + color_values[1] = ((value >> 16) & 0xFF) / 255.0f; + color_values[2] = ((value >> 8) & 0xFF) / 255.0f; + color_values[3] = (value & 0xFF) / 255.0f; + break; + default: + return false; + } + } else { + u16 value_16bit = config.value_16bit.Value(); + Math::Vec4<u8> color; + + switch (dst_surface->pixel_format) { + case PixelFormat::RGBA8: + color_values[0] = (value_16bit >> 8) / 255.0f; + color_values[1] = (value_16bit & 0xFF) / 255.0f; + color_values[2] = color_values[0]; + color_values[3] = color_values[1]; + break; + case PixelFormat::RGB5A1: + color = Color::DecodeRGB5A1((const u8*)&value_16bit); + color_values[0] = color[0] / 31.0f; + color_values[1] = color[1] / 31.0f; + color_values[2] = color[2] / 31.0f; + color_values[3] = color[3]; + break; + case PixelFormat::RGB565: + color = Color::DecodeRGB565((const u8*)&value_16bit); + color_values[0] = color[0] / 31.0f; + color_values[1] = color[1] / 63.0f; + color_values[2] = color[2] / 31.0f; + break; + case PixelFormat::RGBA4: + color = Color::DecodeRGBA4((const u8*)&value_16bit); + color_values[0] = color[0] / 15.0f; + color_values[1] = color[1] / 15.0f; + color_values[2] = color[2] / 15.0f; + color_values[3] = color[3] / 15.0f; + break; + case PixelFormat::IA8: + case PixelFormat::RG8: + color_values[0] = (value_16bit >> 8) / 255.0f; + color_values[1] = (value_16bit & 0xFF) / 255.0f; + break; + default: + return false; + } + } + + cur_state.color_mask.red_enabled = true; + cur_state.color_mask.green_enabled = true; + cur_state.color_mask.blue_enabled = true; + cur_state.color_mask.alpha_enabled = true; + cur_state.Apply(); + glClearBufferfv(GL_COLOR, 0, color_values); + } else if (dst_type == SurfaceType::Depth) { + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dst_surface->texture.handle, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + return false; + } + + GLfloat value_float; + if (dst_surface->pixel_format == CachedSurface::PixelFormat::D16) { + value_float = config.value_32bit / 65535.0f; // 2^16 - 1 + } else if (dst_surface->pixel_format == CachedSurface::PixelFormat::D24) { + value_float = config.value_32bit / 16777215.0f; // 2^24 - 1 + } + + cur_state.depth.write_mask = true; + cur_state.Apply(); + glClearBufferfv(GL_DEPTH, 0, &value_float); + } else if (dst_type == SurfaceType::DepthStencil) { + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, dst_surface->texture.handle, 0); - u32 cached_fb_color_size = Pica::Regs::BytesPerColorPixel(fb_color_texture.format) - * fb_color_texture.width * fb_color_texture.height; + if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + return false; + } - u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format) - * fb_depth_texture.width * fb_depth_texture.height; + GLfloat value_float = (config.value_32bit & 0xFFFFFF) / 16777215.0f; // 2^24 - 1 + GLint value_int = (config.value_32bit >> 24); - // If modified memory region overlaps 3DS framebuffers, reload their contents into OpenGL - if (MathUtil::IntervalsIntersect(addr, size, cached_fb_color_addr, cached_fb_color_size)) - ReloadColorBuffer(); + cur_state.depth.write_mask = true; + cur_state.stencil.write_mask = true; + cur_state.Apply(); + glClearBufferfi(GL_DEPTH_STENCIL, 0, value_float, value_int); + } - if (MathUtil::IntervalsIntersect(addr, size, cached_fb_depth_addr, cached_fb_depth_size)) - ReloadDepthBuffer(); + cur_state.draw.draw_framebuffer = old_fb; + // TODO: Return scissor test to previous value when scissor test is implemented + cur_state.Apply(); - // Notify cache of flush in case the region touches a cached resource - res_cache.InvalidateInRange(addr, size); + dst_surface->dirty = true; + res_cache.FlushRegion(dst_surface->addr, dst_surface->size, dst_surface, true); + return true; +} + +bool RasterizerOpenGL::AccelerateDisplay(const GPU::Regs::FramebufferConfig& config, PAddr framebuffer_addr, u32 pixel_stride, ScreenInfo& screen_info) { + if (framebuffer_addr == 0) { + return false; + } + + CachedSurface src_params; + src_params.addr = framebuffer_addr; + src_params.width = config.width; + src_params.height = config.height; + src_params.stride = pixel_stride; + src_params.is_tiled = false; + src_params.pixel_format = CachedSurface::PixelFormatFromGPUPixelFormat(config.color_format); + + MathUtil::Rectangle<int> src_rect; + CachedSurface* src_surface = res_cache.GetSurfaceRect(src_params, false, true, src_rect); + + if (src_surface == nullptr) { + return false; + } + + u32 scaled_width = src_surface->GetScaledWidth(); + u32 scaled_height = src_surface->GetScaledHeight(); + + screen_info.display_texcoords = MathUtil::Rectangle<float>((float)src_rect.top / (float)scaled_height, + (float)src_rect.left / (float)scaled_width, + (float)src_rect.bottom / (float)scaled_height, + (float)src_rect.right / (float)scaled_width); + + screen_info.display_texture = src_surface->texture.handle; + + return true; } void RasterizerOpenGL::SamplerInfo::Create() { @@ -564,117 +813,16 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Pica::Regs::TextureConf if (wrap_s == TextureConfig::ClampToBorder || wrap_t == TextureConfig::ClampToBorder) { if (border_color != config.border_color.raw) { + border_color = config.border_color.raw; auto gl_color = PicaToGL::ColorRGBA8(border_color); glSamplerParameterfv(s, GL_TEXTURE_BORDER_COLOR, gl_color.data()); } } } -void RasterizerOpenGL::ReconfigureColorTexture(TextureInfo& texture, Pica::Regs::ColorFormat format, u32 width, u32 height) { - GLint internal_format; - - texture.format = format; - texture.width = width; - texture.height = height; - - switch (format) { - case Pica::Regs::ColorFormat::RGBA8: - internal_format = GL_RGBA; - texture.gl_format = GL_RGBA; - texture.gl_type = GL_UNSIGNED_INT_8_8_8_8; - break; - - case Pica::Regs::ColorFormat::RGB8: - // This pixel format uses BGR since GL_UNSIGNED_BYTE specifies byte-order, unlike every - // specific OpenGL type used in this function using native-endian (that is, little-endian - // mostly everywhere) for words or half-words. - // TODO: check how those behave on big-endian processors. - internal_format = GL_RGB; - texture.gl_format = GL_BGR; - texture.gl_type = GL_UNSIGNED_BYTE; - break; - - case Pica::Regs::ColorFormat::RGB5A1: - internal_format = GL_RGBA; - texture.gl_format = GL_RGBA; - texture.gl_type = GL_UNSIGNED_SHORT_5_5_5_1; - break; - - case Pica::Regs::ColorFormat::RGB565: - internal_format = GL_RGB; - texture.gl_format = GL_RGB; - texture.gl_type = GL_UNSIGNED_SHORT_5_6_5; - break; - - case Pica::Regs::ColorFormat::RGBA4: - internal_format = GL_RGBA; - texture.gl_format = GL_RGBA; - texture.gl_type = GL_UNSIGNED_SHORT_4_4_4_4; - break; - - default: - LOG_CRITICAL(Render_OpenGL, "Unknown framebuffer texture color format %x", format); - UNIMPLEMENTED(); - break; - } - - state.texture_units[0].texture_2d = texture.texture.handle; - state.Apply(); - - glActiveTexture(GL_TEXTURE0); - glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0, - texture.gl_format, texture.gl_type, nullptr); - - state.texture_units[0].texture_2d = 0; - state.Apply(); -} - -void RasterizerOpenGL::ReconfigureDepthTexture(DepthTextureInfo& texture, Pica::Regs::DepthFormat format, u32 width, u32 height) { - GLint internal_format; - - texture.format = format; - texture.width = width; - texture.height = height; - - switch (format) { - case Pica::Regs::DepthFormat::D16: - internal_format = GL_DEPTH_COMPONENT16; - texture.gl_format = GL_DEPTH_COMPONENT; - texture.gl_type = GL_UNSIGNED_SHORT; - break; - - case Pica::Regs::DepthFormat::D24: - internal_format = GL_DEPTH_COMPONENT24; - texture.gl_format = GL_DEPTH_COMPONENT; - texture.gl_type = GL_UNSIGNED_INT; - break; - - case Pica::Regs::DepthFormat::D24S8: - internal_format = GL_DEPTH24_STENCIL8; - texture.gl_format = GL_DEPTH_STENCIL; - texture.gl_type = GL_UNSIGNED_INT_24_8; - break; - - default: - LOG_CRITICAL(Render_OpenGL, "Unknown framebuffer texture depth format %x", format); - UNIMPLEMENTED(); - break; - } - - state.texture_units[0].texture_2d = texture.texture.handle; - state.Apply(); - - glActiveTexture(GL_TEXTURE0); - glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0, - texture.gl_format, texture.gl_type, nullptr); - - state.texture_units[0].texture_2d = 0; - state.Apply(); -} - void RasterizerOpenGL::SetShader() { PicaShaderConfig config = PicaShaderConfig::CurrentConfig(); - std::unique_ptr<PicaShader> shader = Common::make_unique<PicaShader>(); + std::unique_ptr<PicaShader> shader = std::make_unique<PicaShader>(); // Find (or generate) the GLSL shader for the current TEV state auto cached_shader = shader_cache.find(config); @@ -727,6 +875,8 @@ void RasterizerOpenGL::SetShader() { SyncGlobalAmbient(); for (int light_index = 0; light_index < 8; light_index++) { + SyncLightSpecular0(light_index); + SyncLightSpecular1(light_index); SyncLightDiffuse(light_index); SyncLightAmbient(light_index); SyncLightPosition(light_index); @@ -734,79 +884,6 @@ void RasterizerOpenGL::SetShader() { } } -void RasterizerOpenGL::SyncFramebuffer() { - const auto& regs = Pica::g_state.regs; - - PAddr new_fb_color_addr = regs.framebuffer.GetColorBufferPhysicalAddress(); - Pica::Regs::ColorFormat new_fb_color_format = regs.framebuffer.color_format; - - PAddr new_fb_depth_addr = regs.framebuffer.GetDepthBufferPhysicalAddress(); - Pica::Regs::DepthFormat new_fb_depth_format = regs.framebuffer.depth_format; - - bool fb_size_changed = fb_color_texture.width != static_cast<GLsizei>(regs.framebuffer.GetWidth()) || - fb_color_texture.height != static_cast<GLsizei>(regs.framebuffer.GetHeight()); - - bool color_fb_prop_changed = fb_color_texture.format != new_fb_color_format || - fb_size_changed; - - bool depth_fb_prop_changed = fb_depth_texture.format != new_fb_depth_format || - fb_size_changed; - - bool color_fb_modified = cached_fb_color_addr != new_fb_color_addr || - color_fb_prop_changed; - - bool depth_fb_modified = cached_fb_depth_addr != new_fb_depth_addr || - depth_fb_prop_changed; - - // Commit if framebuffer modified in any way - if (color_fb_modified) - CommitColorBuffer(); - - if (depth_fb_modified) - CommitDepthBuffer(); - - // Reconfigure framebuffer textures if any property has changed - if (color_fb_prop_changed) { - ReconfigureColorTexture(fb_color_texture, new_fb_color_format, - regs.framebuffer.GetWidth(), regs.framebuffer.GetHeight()); - } - - if (depth_fb_prop_changed) { - ReconfigureDepthTexture(fb_depth_texture, new_fb_depth_format, - regs.framebuffer.GetWidth(), regs.framebuffer.GetHeight()); - - // Only attach depth buffer as stencil if it supports stencil - switch (new_fb_depth_format) { - case Pica::Regs::DepthFormat::D16: - case Pica::Regs::DepthFormat::D24: - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - break; - - case Pica::Regs::DepthFormat::D24S8: - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, fb_depth_texture.texture.handle, 0); - break; - - default: - LOG_CRITICAL(Render_OpenGL, "Unknown framebuffer depth format %x", new_fb_depth_format); - UNIMPLEMENTED(); - break; - } - } - - // Load buffer data again if fb modified in any way - if (color_fb_modified) { - cached_fb_color_addr = new_fb_color_addr; - - ReloadColorBuffer(); - } - - if (depth_fb_modified) { - cached_fb_depth_addr = new_fb_depth_addr; - - ReloadDepthBuffer(); - } -} - void RasterizerOpenGL::SyncCullMode() { const auto& regs = Pica::g_state.regs; @@ -873,13 +950,39 @@ void RasterizerOpenGL::SyncLogicOp() { state.logic_op = PicaToGL::LogicOp(Pica::g_state.regs.output_merger.logic_op); } +void RasterizerOpenGL::SyncColorWriteMask() { + const auto& regs = Pica::g_state.regs; + + auto IsColorWriteEnabled = [&](u32 value) { + return (regs.framebuffer.allow_color_write != 0 && value != 0) ? GL_TRUE : GL_FALSE; + }; + + state.color_mask.red_enabled = IsColorWriteEnabled(regs.output_merger.red_enable); + state.color_mask.green_enabled = IsColorWriteEnabled(regs.output_merger.green_enable); + state.color_mask.blue_enabled = IsColorWriteEnabled(regs.output_merger.blue_enable); + state.color_mask.alpha_enabled = IsColorWriteEnabled(regs.output_merger.alpha_enable); +} + +void RasterizerOpenGL::SyncStencilWriteMask() { + const auto& regs = Pica::g_state.regs; + state.stencil.write_mask = (regs.framebuffer.allow_depth_stencil_write != 0) + ? static_cast<GLuint>(regs.output_merger.stencil_test.write_mask) + : 0; +} + +void RasterizerOpenGL::SyncDepthWriteMask() { + const auto& regs = Pica::g_state.regs; + state.depth.write_mask = (regs.framebuffer.allow_depth_stencil_write != 0 && regs.output_merger.depth_write_enable) + ? GL_TRUE + : GL_FALSE; +} + void RasterizerOpenGL::SyncStencilTest() { const auto& regs = Pica::g_state.regs; state.stencil.test_enabled = regs.output_merger.stencil_test.enable && regs.framebuffer.depth_format == Pica::Regs::DepthFormat::D24S8; state.stencil.test_func = PicaToGL::CompareFunc(regs.output_merger.stencil_test.func); state.stencil.test_ref = regs.output_merger.stencil_test.reference_value; state.stencil.test_mask = regs.output_merger.stencil_test.input_mask; - state.stencil.write_mask = regs.output_merger.stencil_test.write_mask; state.stencil.action_stencil_fail = PicaToGL::StencilOp(regs.output_merger.stencil_test.action_stencil_fail); state.stencil.action_depth_fail = PicaToGL::StencilOp(regs.output_merger.stencil_test.action_depth_fail); state.stencil.action_depth_pass = PicaToGL::StencilOp(regs.output_merger.stencil_test.action_depth_pass); @@ -891,11 +994,6 @@ void RasterizerOpenGL::SyncDepthTest() { regs.output_merger.depth_write_enable == 1; state.depth.test_func = regs.output_merger.depth_test_enable == 1 ? PicaToGL::CompareFunc(regs.output_merger.depth_test_func) : GL_ALWAYS; - state.color_mask.red_enabled = regs.output_merger.red_enable; - state.color_mask.green_enabled = regs.output_merger.green_enable; - state.color_mask.blue_enabled = regs.output_merger.blue_enable; - state.color_mask.alpha_enabled = regs.output_merger.alpha_enable; - state.depth.write_mask = regs.output_merger.depth_write_enable ? GL_TRUE : GL_FALSE; } void RasterizerOpenGL::SyncCombinerColor() { @@ -982,229 +1080,3 @@ void RasterizerOpenGL::SyncLightPosition(int light_index) { uniform_block_data.dirty = true; } } - -void RasterizerOpenGL::SyncDrawState() { - const auto& regs = Pica::g_state.regs; - - // Sync the viewport - GLsizei viewport_width = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_x).ToFloat32() * 2; - GLsizei viewport_height = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_y).ToFloat32() * 2; - - // OpenGL uses different y coordinates, so negate corner offset and flip origin - // TODO: Ensure viewport_corner.x should not be negated or origin flipped - // TODO: Use floating-point viewports for accuracy if supported - glViewport((GLsizei)regs.viewport_corner.x, - (GLsizei)regs.viewport_corner.y, - viewport_width, viewport_height); - - // Sync bound texture(s), upload if not cached - const auto pica_textures = regs.GetTextures(); - for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) { - const auto& texture = pica_textures[texture_index]; - - if (texture.enabled) { - texture_samplers[texture_index].SyncWithConfig(texture.config); - res_cache.LoadAndBindTexture(state, texture_index, texture); - } else { - state.texture_units[texture_index].texture_2d = 0; - } - } - - state.draw.uniform_buffer = uniform_buffer.handle; - state.Apply(); -} - -MICROPROFILE_DEFINE(OpenGL_FramebufferReload, "OpenGL", "FB Reload", MP_RGB(70, 70, 200)); - -void RasterizerOpenGL::ReloadColorBuffer() { - u8* color_buffer = Memory::GetPhysicalPointer(cached_fb_color_addr); - - if (color_buffer == nullptr) - return; - - MICROPROFILE_SCOPE(OpenGL_FramebufferReload); - - u32 bytes_per_pixel = Pica::Regs::BytesPerColorPixel(fb_color_texture.format); - - std::unique_ptr<u8[]> temp_fb_color_buffer(new u8[fb_color_texture.width * fb_color_texture.height * bytes_per_pixel]); - - // Directly copy pixels. Internal OpenGL color formats are consistent so no conversion is necessary. - for (int y = 0; y < fb_color_texture.height; ++y) { - for (int x = 0; x < fb_color_texture.width; ++x) { - const u32 coarse_y = y & ~7; - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_color_texture.width * bytes_per_pixel; - u32 gl_pixel_index = (x + (fb_color_texture.height - 1 - y) * fb_color_texture.width) * bytes_per_pixel; - - u8* pixel = color_buffer + dst_offset; - memcpy(&temp_fb_color_buffer[gl_pixel_index], pixel, bytes_per_pixel); - } - } - - state.texture_units[0].texture_2d = fb_color_texture.texture.handle; - state.Apply(); - - glActiveTexture(GL_TEXTURE0); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, fb_color_texture.width, fb_color_texture.height, - fb_color_texture.gl_format, fb_color_texture.gl_type, temp_fb_color_buffer.get()); - - state.texture_units[0].texture_2d = 0; - state.Apply(); -} - -void RasterizerOpenGL::ReloadDepthBuffer() { - if (cached_fb_depth_addr == 0) - return; - - // TODO: Appears to work, but double-check endianness of depth values and order of depth-stencil - u8* depth_buffer = Memory::GetPhysicalPointer(cached_fb_depth_addr); - - if (depth_buffer == nullptr) - return; - - MICROPROFILE_SCOPE(OpenGL_FramebufferReload); - - u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format); - - // OpenGL needs 4 bpp alignment for D24 - u32 gl_bpp = bytes_per_pixel == 3 ? 4 : bytes_per_pixel; - - std::unique_ptr<u8[]> temp_fb_depth_buffer(new u8[fb_depth_texture.width * fb_depth_texture.height * gl_bpp]); - - u8* temp_fb_depth_data = bytes_per_pixel == 3 ? (temp_fb_depth_buffer.get() + 1) : temp_fb_depth_buffer.get(); - - if (fb_depth_texture.format == Pica::Regs::DepthFormat::D24S8) { - for (int y = 0; y < fb_depth_texture.height; ++y) { - for (int x = 0; x < fb_depth_texture.width; ++x) { - const u32 coarse_y = y & ~7; - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_depth_texture.width * bytes_per_pixel; - u32 gl_pixel_index = (x + (fb_depth_texture.height - 1 - y) * fb_depth_texture.width); - - u8* pixel = depth_buffer + dst_offset; - u32 depth_stencil = *(u32*)pixel; - ((u32*)temp_fb_depth_data)[gl_pixel_index] = (depth_stencil << 8) | (depth_stencil >> 24); - } - } - } else { - for (int y = 0; y < fb_depth_texture.height; ++y) { - for (int x = 0; x < fb_depth_texture.width; ++x) { - const u32 coarse_y = y & ~7; - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_depth_texture.width * bytes_per_pixel; - u32 gl_pixel_index = (x + (fb_depth_texture.height - 1 - y) * fb_depth_texture.width) * gl_bpp; - - u8* pixel = depth_buffer + dst_offset; - memcpy(&temp_fb_depth_data[gl_pixel_index], pixel, bytes_per_pixel); - } - } - } - - state.texture_units[0].texture_2d = fb_depth_texture.texture.handle; - state.Apply(); - - glActiveTexture(GL_TEXTURE0); - if (fb_depth_texture.format == Pica::Regs::DepthFormat::D24S8) { - // TODO(Subv): There is a bug with Intel Windows drivers that makes glTexSubImage2D not change the stencil buffer. - // The bug has been reported to Intel (https://communities.intel.com/message/324464) - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, fb_depth_texture.width, fb_depth_texture.height, 0, - GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, temp_fb_depth_buffer.get()); - } else { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, fb_depth_texture.width, fb_depth_texture.height, - fb_depth_texture.gl_format, fb_depth_texture.gl_type, temp_fb_depth_buffer.get()); - } - - state.texture_units[0].texture_2d = 0; - state.Apply(); -} - -Common::Profiling::TimingCategory buffer_commit_category("Framebuffer Commit"); -MICROPROFILE_DEFINE(OpenGL_FramebufferCommit, "OpenGL", "FB Commit", MP_RGB(70, 70, 200)); - -void RasterizerOpenGL::CommitColorBuffer() { - if (cached_fb_color_addr != 0) { - u8* color_buffer = Memory::GetPhysicalPointer(cached_fb_color_addr); - - if (color_buffer != nullptr) { - Common::Profiling::ScopeTimer timer(buffer_commit_category); - MICROPROFILE_SCOPE(OpenGL_FramebufferCommit); - - u32 bytes_per_pixel = Pica::Regs::BytesPerColorPixel(fb_color_texture.format); - - std::unique_ptr<u8[]> temp_gl_color_buffer(new u8[fb_color_texture.width * fb_color_texture.height * bytes_per_pixel]); - - state.texture_units[0].texture_2d = fb_color_texture.texture.handle; - state.Apply(); - - glActiveTexture(GL_TEXTURE0); - glGetTexImage(GL_TEXTURE_2D, 0, fb_color_texture.gl_format, fb_color_texture.gl_type, temp_gl_color_buffer.get()); - - state.texture_units[0].texture_2d = 0; - state.Apply(); - - // Directly copy pixels. Internal OpenGL color formats are consistent so no conversion is necessary. - for (int y = 0; y < fb_color_texture.height; ++y) { - for (int x = 0; x < fb_color_texture.width; ++x) { - const u32 coarse_y = y & ~7; - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_color_texture.width * bytes_per_pixel; - u32 gl_pixel_index = x * bytes_per_pixel + (fb_color_texture.height - 1 - y) * fb_color_texture.width * bytes_per_pixel; - - u8* pixel = color_buffer + dst_offset; - memcpy(pixel, &temp_gl_color_buffer[gl_pixel_index], bytes_per_pixel); - } - } - } - } -} - -void RasterizerOpenGL::CommitDepthBuffer() { - if (cached_fb_depth_addr != 0) { - // TODO: Output seems correct visually, but doesn't quite match sw renderer output. One of them is wrong. - u8* depth_buffer = Memory::GetPhysicalPointer(cached_fb_depth_addr); - - if (depth_buffer != nullptr) { - Common::Profiling::ScopeTimer timer(buffer_commit_category); - MICROPROFILE_SCOPE(OpenGL_FramebufferCommit); - - u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format); - - // OpenGL needs 4 bpp alignment for D24 - u32 gl_bpp = bytes_per_pixel == 3 ? 4 : bytes_per_pixel; - - std::unique_ptr<u8[]> temp_gl_depth_buffer(new u8[fb_depth_texture.width * fb_depth_texture.height * gl_bpp]); - - state.texture_units[0].texture_2d = fb_depth_texture.texture.handle; - state.Apply(); - - glActiveTexture(GL_TEXTURE0); - glGetTexImage(GL_TEXTURE_2D, 0, fb_depth_texture.gl_format, fb_depth_texture.gl_type, temp_gl_depth_buffer.get()); - - state.texture_units[0].texture_2d = 0; - state.Apply(); - - u8* temp_gl_depth_data = bytes_per_pixel == 3 ? (temp_gl_depth_buffer.get() + 1) : temp_gl_depth_buffer.get(); - - if (fb_depth_texture.format == Pica::Regs::DepthFormat::D24S8) { - for (int y = 0; y < fb_depth_texture.height; ++y) { - for (int x = 0; x < fb_depth_texture.width; ++x) { - const u32 coarse_y = y & ~7; - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_depth_texture.width * bytes_per_pixel; - u32 gl_pixel_index = (x + (fb_depth_texture.height - 1 - y) * fb_depth_texture.width); - - u8* pixel = depth_buffer + dst_offset; - u32 depth_stencil = ((u32*)temp_gl_depth_data)[gl_pixel_index]; - *(u32*)pixel = (depth_stencil >> 8) | (depth_stencil << 24); - } - } - } else { - for (int y = 0; y < fb_depth_texture.height; ++y) { - for (int x = 0; x < fb_depth_texture.width; ++x) { - const u32 coarse_y = y & ~7; - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_depth_texture.width * bytes_per_pixel; - u32 gl_pixel_index = (x + (fb_depth_texture.height - 1 - y) * fb_depth_texture.width) * gl_bpp; - - u8* pixel = depth_buffer + dst_offset; - memcpy(pixel, &temp_gl_depth_data[gl_pixel_index], bytes_per_pixel); - } - } - } - } - } -} diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index fc85aa3ff..82fa61742 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -4,22 +4,33 @@ #pragma once +#include <array> #include <cstddef> #include <cstring> #include <memory> #include <vector> #include <unordered_map> +#include <glad/glad.h> + +#include "common/bit_field.h" #include "common/common_types.h" #include "common/hash.h" +#include "common/vector_math.h" + +#include "core/hw/gpu.h" #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/pica_types.h" #include "video_core/rasterizer_interface.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_state.h" #include "video_core/renderer_opengl/pica_to_gl.h" -#include "video_core/shader/shader_interpreter.h" +#include "video_core/shader/shader.h" + +struct ScreenInfo; /** * This struct contains all state used to generate the GLSL shader program that emulates the current @@ -38,36 +49,18 @@ struct PicaShaderConfig { res.alpha_test_func = regs.output_merger.alpha_test.enable ? regs.output_merger.alpha_test.func.Value() : Pica::Regs::CompareFunc::Always; - // Copy relevant TevStageConfig fields only. We're doing this manually (instead of calling - // the GetTevStages() function) because BitField explicitly disables copies. - - res.tev_stages[0].sources_raw = regs.tev_stage0.sources_raw; - res.tev_stages[1].sources_raw = regs.tev_stage1.sources_raw; - res.tev_stages[2].sources_raw = regs.tev_stage2.sources_raw; - res.tev_stages[3].sources_raw = regs.tev_stage3.sources_raw; - res.tev_stages[4].sources_raw = regs.tev_stage4.sources_raw; - res.tev_stages[5].sources_raw = regs.tev_stage5.sources_raw; - - res.tev_stages[0].modifiers_raw = regs.tev_stage0.modifiers_raw; - res.tev_stages[1].modifiers_raw = regs.tev_stage1.modifiers_raw; - res.tev_stages[2].modifiers_raw = regs.tev_stage2.modifiers_raw; - res.tev_stages[3].modifiers_raw = regs.tev_stage3.modifiers_raw; - res.tev_stages[4].modifiers_raw = regs.tev_stage4.modifiers_raw; - res.tev_stages[5].modifiers_raw = regs.tev_stage5.modifiers_raw; - - res.tev_stages[0].ops_raw = regs.tev_stage0.ops_raw; - res.tev_stages[1].ops_raw = regs.tev_stage1.ops_raw; - res.tev_stages[2].ops_raw = regs.tev_stage2.ops_raw; - res.tev_stages[3].ops_raw = regs.tev_stage3.ops_raw; - res.tev_stages[4].ops_raw = regs.tev_stage4.ops_raw; - res.tev_stages[5].ops_raw = regs.tev_stage5.ops_raw; - - res.tev_stages[0].scales_raw = regs.tev_stage0.scales_raw; - res.tev_stages[1].scales_raw = regs.tev_stage1.scales_raw; - res.tev_stages[2].scales_raw = regs.tev_stage2.scales_raw; - res.tev_stages[3].scales_raw = regs.tev_stage3.scales_raw; - res.tev_stages[4].scales_raw = regs.tev_stage4.scales_raw; - res.tev_stages[5].scales_raw = regs.tev_stage5.scales_raw; + // 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()); + 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; + } res.combiner_buffer_input = regs.tev_combiner_buffer_input.update_mask_rgb.Value() | @@ -191,16 +184,17 @@ public: RasterizerOpenGL(); ~RasterizerOpenGL() override; - void InitObjects() override; - void Reset() override; void AddTriangle(const Pica::Shader::OutputVertex& v0, const Pica::Shader::OutputVertex& v1, const Pica::Shader::OutputVertex& v2) override; void DrawTriangles() override; - void FlushFramebuffer() override; void NotifyPicaRegisterChanged(u32 id) override; + void FlushAll() override; void FlushRegion(PAddr addr, u32 size) override; - void InvalidateRegion(PAddr addr, u32 size) override; + void FlushAndInvalidateRegion(PAddr addr, u32 size) override; + bool AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) override; + bool AccelerateFill(const GPU::Regs::MemoryFillConfig& config) override; + bool AccelerateDisplay(const GPU::Regs::FramebufferConfig& config, PAddr framebuffer_addr, u32 pixel_stride, ScreenInfo& screen_info) override; /// OpenGL shader generated for a given Pica register state struct PicaShader { @@ -210,26 +204,6 @@ public: private: - /// Structure used for storing information about color textures - struct TextureInfo { - OGLTexture texture; - GLsizei width; - GLsizei height; - Pica::Regs::ColorFormat format; - GLenum gl_format; - GLenum gl_type; - }; - - /// Structure used for storing information about depth textures - struct DepthTextureInfo { - OGLTexture texture; - GLsizei width; - GLsizei height; - Pica::Regs::DepthFormat format; - GLenum gl_format; - GLenum gl_type; - }; - struct SamplerInfo { using TextureConfig = Pica::Regs::TextureConfig; @@ -311,18 +285,9 @@ private: static_assert(sizeof(UniformData) == 0x310, "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"); - /// Reconfigure the OpenGL color texture to use the given format and dimensions - void ReconfigureColorTexture(TextureInfo& texture, Pica::Regs::ColorFormat format, u32 width, u32 height); - - /// Reconfigure the OpenGL depth texture to use the given format and dimensions - void ReconfigureDepthTexture(DepthTextureInfo& texture, Pica::Regs::DepthFormat format, u32 width, u32 height); - /// Sets the OpenGL shader in accordance with the current PICA register state void SetShader(); - /// Syncs the state and contents of the OpenGL framebuffer to match the current PICA framebuffer - void SyncFramebuffer(); - /// Syncs the cull mode to match the PICA register void SyncCullMode(); @@ -344,78 +309,57 @@ private: /// Syncs the logic op states to match the PICA register void SyncLogicOp(); + /// Syncs the color write mask to match the PICA register state + void SyncColorWriteMask(); + + /// Syncs the stencil write mask to match the PICA register state + void SyncStencilWriteMask(); + + /// Syncs the depth write mask to match the PICA register state + void SyncDepthWriteMask(); + /// Syncs the stencil test states to match the PICA register void SyncStencilTest(); /// Syncs the depth test states to match the PICA register void SyncDepthTest(); - /// Syncs the TEV constant color to match the PICA register - void SyncTevConstColor(int tev_index, const Pica::Regs::TevStageConfig& tev_stage); - /// Syncs the TEV combiner color buffer to match the PICA register void SyncCombinerColor(); + /// Syncs the TEV constant color to match the PICA register + void SyncTevConstColor(int tev_index, const Pica::Regs::TevStageConfig& tev_stage); + /// Syncs the lighting global ambient color to match the PICA register void SyncGlobalAmbient(); /// Syncs the lighting lookup tables void SyncLightingLUT(unsigned index); - /// Syncs the specified light's diffuse color to match the PICA register - void SyncLightDiffuse(int light_index); - - /// Syncs the specified light's ambient color to match the PICA register - void SyncLightAmbient(int light_index); - - /// Syncs the specified light's position to match the PICA register - void SyncLightPosition(int light_index); - /// Syncs the specified light's specular 0 color to match the PICA register void SyncLightSpecular0(int light_index); /// Syncs the specified light's specular 1 color to match the PICA register void SyncLightSpecular1(int light_index); - /// Syncs the remaining OpenGL drawing state to match the current PICA state - void SyncDrawState(); - - /// Copies the 3DS color framebuffer into the OpenGL color framebuffer texture - void ReloadColorBuffer(); + /// Syncs the specified light's diffuse color to match the PICA register + void SyncLightDiffuse(int light_index); - /// Copies the 3DS depth framebuffer into the OpenGL depth framebuffer texture - void ReloadDepthBuffer(); + /// Syncs the specified light's ambient color to match the PICA register + void SyncLightAmbient(int light_index); - /** - * Save the current OpenGL color framebuffer to the current PICA framebuffer in 3DS memory - * Loads the OpenGL framebuffer textures into temporary buffers - * Then copies into the 3DS framebuffer using proper Morton order - */ - void CommitColorBuffer(); + /// Syncs the specified light's position to match the PICA register + void SyncLightPosition(int light_index); - /** - * Save the current OpenGL depth framebuffer to the current PICA framebuffer in 3DS memory - * Loads the OpenGL framebuffer textures into temporary buffers - * Then copies into the 3DS framebuffer using proper Morton order - */ - void CommitDepthBuffer(); + OpenGLState state; RasterizerCacheOpenGL res_cache; std::vector<HardwareVertex> vertex_batch; - OpenGLState state; - - PAddr cached_fb_color_addr; - PAddr cached_fb_depth_addr; - - // Hardware rasterizer - std::array<SamplerInfo, 3> texture_samplers; - TextureInfo fb_color_texture; - DepthTextureInfo fb_depth_texture; - std::unordered_map<PicaShaderConfig, std::unique_ptr<PicaShader>> shader_cache; const PicaShader* current_shader = nullptr; + bool shader_dirty; struct { UniformData data; @@ -423,11 +367,12 @@ private: bool dirty; } uniform_block_data; + std::array<SamplerInfo, 3> texture_samplers; OGLVertexArray vertex_array; OGLBuffer vertex_buffer; OGLBuffer uniform_buffer; OGLFramebuffer framebuffer; - std::array<OGLTexture, 6> lighting_lut; + std::array<OGLTexture, 6> lighting_luts; std::array<std::array<GLvec4, 256>, 6> lighting_lut_data; }; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index a9ad46fe0..7efd0038a 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -2,8 +2,19 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/hash.h" -#include "common/make_unique.h" +#include <algorithm> +#include <atomic> +#include <cstring> +#include <iterator> +#include <unordered_set> +#include <utility> +#include <vector> + +#include <glad/glad.h> + +#include "common/bit_field.h" +#include "common/emu_window.h" +#include "common/logging/log.h" #include "common/math_util.h" #include "common/microprofile.h" #include "common/vector_math.h" @@ -11,71 +22,693 @@ #include "core/memory.h" #include "video_core/debug_utils/debug_utils.h" +#include "video_core/pica_state.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" -#include "video_core/renderer_opengl/pica_to_gl.h" +#include "video_core/renderer_opengl/gl_state.h" +#include "video_core/utils.h" +#include "video_core/video_core.h" + +struct FormatTuple { + GLint internal_format; + GLenum format; + GLenum type; +}; + +static const std::array<FormatTuple, 5> fb_format_tuples = {{ + { GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8 }, // RGBA8 + { GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE }, // RGB8 + { GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 }, // RGB5A1 + { GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5 }, // RGB565 + { GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4 }, // RGBA4 +}}; + +static const std::array<FormatTuple, 4> depth_format_tuples = {{ + { GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT }, // D16 + {}, + { GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT }, // D24 + { GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8 }, // D24S8 +}}; + +RasterizerCacheOpenGL::RasterizerCacheOpenGL() { + transfer_framebuffers[0].Create(); + transfer_framebuffers[1].Create(); +} RasterizerCacheOpenGL::~RasterizerCacheOpenGL() { - InvalidateAll(); + FlushAll(); +} + +static void MortonCopyPixels(CachedSurface::PixelFormat pixel_format, u32 width, u32 height, u32 bytes_per_pixel, u32 gl_bytes_per_pixel, u8* morton_data, u8* gl_data, bool morton_to_gl) { + using PixelFormat = CachedSurface::PixelFormat; + + u8* data_ptrs[2]; + u32 depth_stencil_shifts[2] = {24, 8}; + + if (morton_to_gl) { + std::swap(depth_stencil_shifts[0], depth_stencil_shifts[1]); + } + + if (pixel_format == PixelFormat::D24S8) { + for (unsigned y = 0; y < height; ++y) { + for (unsigned x = 0; x < width; ++x) { + const u32 coarse_y = y & ~7; + u32 morton_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel; + u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel; + + data_ptrs[morton_to_gl] = morton_data + morton_offset; + data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index]; + + // Swap depth and stencil value ordering since 3DS does not match OpenGL + u32 depth_stencil; + memcpy(&depth_stencil, data_ptrs[1], sizeof(u32)); + depth_stencil = (depth_stencil << depth_stencil_shifts[0]) | (depth_stencil >> depth_stencil_shifts[1]); + + memcpy(data_ptrs[0], &depth_stencil, sizeof(u32)); + } + } + } else { + for (unsigned y = 0; y < height; ++y) { + for (unsigned x = 0; x < width; ++x) { + const u32 coarse_y = y & ~7; + u32 morton_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel; + u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel; + + data_ptrs[morton_to_gl] = morton_data + morton_offset; + data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index]; + + memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); + } + } + } +} + +bool RasterizerCacheOpenGL::BlitTextures(GLuint src_tex, GLuint dst_tex, CachedSurface::SurfaceType type, const MathUtil::Rectangle<int>& src_rect, const MathUtil::Rectangle<int>& dst_rect) { + using SurfaceType = CachedSurface::SurfaceType; + + OpenGLState cur_state = OpenGLState::GetCurState(); + + // Make sure textures aren't bound to texture units, since going to bind them to framebuffer components + OpenGLState::ResetTexture(src_tex); + OpenGLState::ResetTexture(dst_tex); + + // Keep track of previous framebuffer bindings + GLuint old_fbs[2] = { cur_state.draw.read_framebuffer, cur_state.draw.draw_framebuffer }; + cur_state.draw.read_framebuffer = transfer_framebuffers[0].handle; + cur_state.draw.draw_framebuffer = transfer_framebuffers[1].handle; + cur_state.Apply(); + + u32 buffers = 0; + + if (type == SurfaceType::Color || type == SurfaceType::Texture) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, src_tex, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + buffers = GL_COLOR_BUFFER_BIT; + } else if (type == SurfaceType::Depth) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, src_tex, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dst_tex, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + buffers = GL_DEPTH_BUFFER_BIT; + } else if (type == SurfaceType::DepthStencil) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, src_tex, 0); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, dst_tex, 0); + + buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + } + + if (OpenGLState::CheckFBStatus(GL_READ_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + return false; + } + + if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + return false; + } + + glBlitFramebuffer(src_rect.left, src_rect.top, src_rect.right, src_rect.bottom, + dst_rect.left, dst_rect.top, dst_rect.right, dst_rect.bottom, + buffers, buffers == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST); + + // Restore previous framebuffer bindings + cur_state.draw.read_framebuffer = old_fbs[0]; + cur_state.draw.draw_framebuffer = old_fbs[1]; + cur_state.Apply(); + + return true; +} + +bool RasterizerCacheOpenGL::TryBlitSurfaces(CachedSurface* src_surface, const MathUtil::Rectangle<int>& src_rect, CachedSurface* dst_surface, const MathUtil::Rectangle<int>& dst_rect) { + using SurfaceType = CachedSurface::SurfaceType; + + if (!CachedSurface::CheckFormatsBlittable(src_surface->pixel_format, dst_surface->pixel_format)) { + return false; + } + + return BlitTextures(src_surface->texture.handle, dst_surface->texture.handle, CachedSurface::GetFormatType(src_surface->pixel_format), src_rect, dst_rect); +} + +static void AllocateSurfaceTexture(GLuint texture, CachedSurface::PixelFormat pixel_format, u32 width, u32 height) { + // Allocate an uninitialized texture of appropriate size and format for the surface + using SurfaceType = CachedSurface::SurfaceType; + + OpenGLState cur_state = OpenGLState::GetCurState(); + + // Keep track of previous texture bindings + GLuint old_tex = cur_state.texture_units[0].texture_2d; + cur_state.texture_units[0].texture_2d = texture; + cur_state.Apply(); + glActiveTexture(GL_TEXTURE0); + + SurfaceType type = CachedSurface::GetFormatType(pixel_format); + + FormatTuple tuple; + if (type == SurfaceType::Color) { + ASSERT((size_t)pixel_format < fb_format_tuples.size()); + tuple = fb_format_tuples[(unsigned int)pixel_format]; + } else if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) { + size_t tuple_idx = (size_t)pixel_format - 14; + ASSERT(tuple_idx < depth_format_tuples.size()); + tuple = depth_format_tuples[tuple_idx]; + } else { + tuple = { GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE }; + } + + glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, width, height, 0, + tuple.format, tuple.type, nullptr); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Restore previous texture bindings + cur_state.texture_units[0].texture_2d = old_tex; + cur_state.Apply(); } -MICROPROFILE_DEFINE(OpenGL_TextureUpload, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192)); +MICROPROFILE_DEFINE(OpenGL_SurfaceUpload, "OpenGL", "Surface Upload", MP_RGB(128, 64, 192)); +CachedSurface* RasterizerCacheOpenGL::GetSurface(const CachedSurface& params, bool match_res_scale, bool load_if_create) { + using PixelFormat = CachedSurface::PixelFormat; + using SurfaceType = CachedSurface::SurfaceType; + + if (params.addr == 0) { + return nullptr; + } + + u32 params_size = params.width * params.height * CachedSurface::GetFormatBpp(params.pixel_format) / 8; + + // Check for an exact match in existing surfaces + CachedSurface* best_exact_surface = nullptr; + float exact_surface_goodness = -1.f; + + auto surface_interval = boost::icl::interval<PAddr>::right_open(params.addr, params.addr + params_size); + auto range = surface_cache.equal_range(surface_interval); + for (auto it = range.first; it != range.second; ++it) { + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + CachedSurface* surface = it2->get(); + + // Check if the request matches the surface exactly + if (params.addr == surface->addr && + params.width == surface->width && params.height == surface->height && + params.pixel_format == surface->pixel_format) + { + // Make sure optional param-matching criteria are fulfilled + bool tiling_match = (params.is_tiled == surface->is_tiled); + bool res_scale_match = (params.res_scale_width == surface->res_scale_width && params.res_scale_height == surface->res_scale_height); + if (!match_res_scale || res_scale_match) { + // Prioritize same-tiling and highest resolution surfaces + float match_goodness = (float)tiling_match + surface->res_scale_width * surface->res_scale_height; + if (match_goodness > exact_surface_goodness || surface->dirty) { + exact_surface_goodness = match_goodness; + best_exact_surface = surface; + } + } + } + } + } + + // Return the best exact surface if found + if (best_exact_surface != nullptr) { + return best_exact_surface; + } + + // No matching surfaces found, so create a new one + u8* texture_src_data = Memory::GetPhysicalPointer(params.addr); + if (texture_src_data == nullptr) { + return nullptr; + } + + MICROPROFILE_SCOPE(OpenGL_SurfaceUpload); + + std::shared_ptr<CachedSurface> new_surface = std::make_shared<CachedSurface>(); + + new_surface->addr = params.addr; + new_surface->size = params_size; + + new_surface->texture.Create(); + new_surface->width = params.width; + new_surface->height = params.height; + new_surface->stride = params.stride; + new_surface->res_scale_width = params.res_scale_width; + new_surface->res_scale_height = params.res_scale_height; -void RasterizerCacheOpenGL::LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::DebugUtils::TextureInfo& info) { - const auto cached_texture = texture_cache.find(info.physical_address); + new_surface->is_tiled = params.is_tiled; + new_surface->pixel_format = params.pixel_format; + new_surface->dirty = false; - if (cached_texture != texture_cache.end()) { - state.texture_units[texture_unit].texture_2d = cached_texture->second->texture.handle; - state.Apply(); + if (!load_if_create) { + // Don't load any data; just allocate the surface's texture + AllocateSurfaceTexture(new_surface->texture.handle, new_surface->pixel_format, new_surface->GetScaledWidth(), new_surface->GetScaledHeight()); } else { - MICROPROFILE_SCOPE(OpenGL_TextureUpload); + // TODO: Consider attempting subrect match in existing surfaces and direct blit here instead of memory upload below if that's a common scenario in some game + + Memory::RasterizerFlushRegion(params.addr, params_size); + + // Load data from memory to the new surface + OpenGLState cur_state = OpenGLState::GetCurState(); + + GLuint old_tex = cur_state.texture_units[0].texture_2d; + cur_state.texture_units[0].texture_2d = new_surface->texture.handle; + cur_state.Apply(); + glActiveTexture(GL_TEXTURE0); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)new_surface->stride); + if (!new_surface->is_tiled) { + // TODO: Ensure this will always be a color format, not a depth or other format + ASSERT((size_t)new_surface->pixel_format < fb_format_tuples.size()); + const FormatTuple& tuple = fb_format_tuples[(unsigned int)params.pixel_format]; + + glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, params.width, params.height, 0, + tuple.format, tuple.type, texture_src_data); + } else { + SurfaceType type = CachedSurface::GetFormatType(new_surface->pixel_format); + if (type != SurfaceType::Depth && type != SurfaceType::DepthStencil) { + FormatTuple tuple; + if ((size_t)params.pixel_format < fb_format_tuples.size()) { + tuple = fb_format_tuples[(unsigned int)params.pixel_format]; + } else { + // Texture + tuple = { GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE }; + } + + std::vector<Math::Vec4<u8>> tex_buffer(params.width * params.height); + + Pica::DebugUtils::TextureInfo tex_info; + tex_info.width = params.width; + tex_info.height = params.height; + tex_info.stride = params.width * CachedSurface::GetFormatBpp(params.pixel_format) / 8; + tex_info.format = (Pica::Regs::TextureFormat)params.pixel_format; + tex_info.physical_address = params.addr; + + for (unsigned y = 0; y < params.height; ++y) { + for (unsigned x = 0; x < params.width; ++x) { + tex_buffer[x + params.width * y] = Pica::DebugUtils::LookupTexture(texture_src_data, x, params.height - 1 - y, tex_info); + } + } + + glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, params.width, params.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_buffer.data()); + } else { + // Depth/Stencil formats need special treatment since they aren't sampleable using LookupTexture and can't use RGBA format + size_t tuple_idx = (size_t)params.pixel_format - 14; + ASSERT(tuple_idx < depth_format_tuples.size()); + const FormatTuple& tuple = depth_format_tuples[tuple_idx]; + + u32 bytes_per_pixel = CachedSurface::GetFormatBpp(params.pixel_format) / 8; + + // OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type + bool use_4bpp = (params.pixel_format == PixelFormat::D24); + + u32 gl_bytes_per_pixel = use_4bpp ? 4 : bytes_per_pixel; + + std::vector<u8> temp_fb_depth_buffer(params.width * params.height * gl_bytes_per_pixel); + + u8* temp_fb_depth_buffer_ptr = use_4bpp ? temp_fb_depth_buffer.data() + 1 : temp_fb_depth_buffer.data(); + + MortonCopyPixels(params.pixel_format, params.width, params.height, bytes_per_pixel, gl_bytes_per_pixel, texture_src_data, temp_fb_depth_buffer_ptr, true); + + glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, params.width, params.height, 0, + tuple.format, tuple.type, temp_fb_depth_buffer.data()); + } + } + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + // If not 1x scale, blit 1x texture to a new scaled texture and replace texture in surface + if (new_surface->res_scale_width != 1.f || new_surface->res_scale_height != 1.f) { + OGLTexture scaled_texture; + scaled_texture.Create(); + + AllocateSurfaceTexture(scaled_texture.handle, new_surface->pixel_format, new_surface->GetScaledWidth(), new_surface->GetScaledHeight()); + BlitTextures(new_surface->texture.handle, scaled_texture.handle, CachedSurface::GetFormatType(new_surface->pixel_format), + MathUtil::Rectangle<int>(0, 0, new_surface->width, new_surface->height), + MathUtil::Rectangle<int>(0, 0, new_surface->GetScaledWidth(), new_surface->GetScaledHeight())); + + new_surface->texture.Release(); + new_surface->texture.handle = scaled_texture.handle; + scaled_texture.handle = 0; + cur_state.texture_units[0].texture_2d = new_surface->texture.handle; + cur_state.Apply(); + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - std::unique_ptr<CachedTexture> new_texture = Common::make_unique<CachedTexture>(); + cur_state.texture_units[0].texture_2d = old_tex; + cur_state.Apply(); + } + + Memory::RasterizerMarkRegionCached(new_surface->addr, new_surface->size, 1); + surface_cache.add(std::make_pair(boost::icl::interval<PAddr>::right_open(new_surface->addr, new_surface->addr + new_surface->size), std::set<std::shared_ptr<CachedSurface>>({ new_surface }))); + return new_surface.get(); +} - new_texture->texture.Create(); - state.texture_units[texture_unit].texture_2d = new_texture->texture.handle; - state.Apply(); - glActiveTexture(GL_TEXTURE0 + texture_unit); +CachedSurface* RasterizerCacheOpenGL::GetSurfaceRect(const CachedSurface& params, bool match_res_scale, bool load_if_create, MathUtil::Rectangle<int>& out_rect) { + if (params.addr == 0) { + return nullptr; + } - u8* texture_src_data = Memory::GetPhysicalPointer(info.physical_address); + u32 total_pixels = params.width * params.height; + u32 params_size = total_pixels * CachedSurface::GetFormatBpp(params.pixel_format) / 8; - new_texture->width = info.width; - new_texture->height = info.height; - new_texture->size = info.stride * info.height; - new_texture->addr = info.physical_address; - new_texture->hash = Common::ComputeHash64(texture_src_data, new_texture->size); + // Attempt to find encompassing surfaces + CachedSurface* best_subrect_surface = nullptr; + float subrect_surface_goodness = -1.f; - std::unique_ptr<Math::Vec4<u8>[]> temp_texture_buffer_rgba(new Math::Vec4<u8>[info.width * info.height]); + auto surface_interval = boost::icl::interval<PAddr>::right_open(params.addr, params.addr + params_size); + auto cache_upper_bound = surface_cache.upper_bound(surface_interval); + for (auto it = surface_cache.lower_bound(surface_interval); it != cache_upper_bound; ++it) { + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + CachedSurface* surface = it2->get(); - for (int y = 0; y < info.height; ++y) { - for (int x = 0; x < info.width; ++x) { - temp_texture_buffer_rgba[x + info.width * y] = Pica::DebugUtils::LookupTexture(texture_src_data, x, info.height - 1 - y, info); + // Check if the request is contained in the surface + if (params.addr >= surface->addr && + params.addr + params_size - 1 <= surface->addr + surface->size - 1 && + params.pixel_format == surface->pixel_format) + { + // Make sure optional param-matching criteria are fulfilled + bool tiling_match = (params.is_tiled == surface->is_tiled); + bool res_scale_match = (params.res_scale_width == surface->res_scale_width && params.res_scale_height == surface->res_scale_height); + if (!match_res_scale || res_scale_match) { + // Prioritize same-tiling and highest resolution surfaces + float match_goodness = (float)tiling_match + surface->res_scale_width * surface->res_scale_height; + if (match_goodness > subrect_surface_goodness || surface->dirty) { + subrect_surface_goodness = match_goodness; + best_subrect_surface = surface; + } + } } } + } + + // Return the best subrect surface if found + if (best_subrect_surface != nullptr) { + unsigned int bytes_per_pixel = (CachedSurface::GetFormatBpp(best_subrect_surface->pixel_format) / 8); + + int x0, y0; + + if (!params.is_tiled) { + u32 begin_pixel_index = (params.addr - best_subrect_surface->addr) / bytes_per_pixel; + x0 = begin_pixel_index % best_subrect_surface->width; + y0 = begin_pixel_index / best_subrect_surface->width; + + out_rect = MathUtil::Rectangle<int>(x0, y0, x0 + params.width, y0 + params.height); + } else { + u32 bytes_per_tile = 8 * 8 * bytes_per_pixel; + u32 tiles_per_row = best_subrect_surface->width / 8; + + u32 begin_tile_index = (params.addr - best_subrect_surface->addr) / bytes_per_tile; + x0 = begin_tile_index % tiles_per_row * 8; + y0 = begin_tile_index / tiles_per_row * 8; + + // Tiled surfaces are flipped vertically in the rasterizer vs. 3DS memory. + out_rect = MathUtil::Rectangle<int>(x0, best_subrect_surface->height - y0, x0 + params.width, best_subrect_surface->height - (y0 + params.height)); + } + + out_rect.left = (int)(out_rect.left * best_subrect_surface->res_scale_width); + out_rect.right = (int)(out_rect.right * best_subrect_surface->res_scale_width); + out_rect.top = (int)(out_rect.top * best_subrect_surface->res_scale_height); + out_rect.bottom = (int)(out_rect.bottom * best_subrect_surface->res_scale_height); + + return best_subrect_surface; + } + + // No subrect found - create and return a new surface + if (!params.is_tiled) { + out_rect = MathUtil::Rectangle<int>(0, 0, (int)(params.width * params.res_scale_width), (int)(params.height * params.res_scale_height)); + } else { + out_rect = MathUtil::Rectangle<int>(0, (int)(params.height * params.res_scale_height), (int)(params.width * params.res_scale_width), 0); + } + + return GetSurface(params, match_res_scale, load_if_create); +} + +CachedSurface* RasterizerCacheOpenGL::GetTextureSurface(const Pica::Regs::FullTextureConfig& config) { + Pica::DebugUtils::TextureInfo info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config.config, config.format); + + CachedSurface params; + params.addr = info.physical_address; + params.width = info.width; + params.height = info.height; + params.is_tiled = true; + params.pixel_format = CachedSurface::PixelFormatFromTextureFormat(info.format); + return GetSurface(params, false, true); +} + +std::tuple<CachedSurface*, CachedSurface*, MathUtil::Rectangle<int>> RasterizerCacheOpenGL::GetFramebufferSurfaces(const Pica::Regs::FramebufferConfig& config) { + const auto& regs = Pica::g_state.regs; + + // Make sur that framebuffers don't overlap if both color and depth are being used + u32 fb_area = config.GetWidth() * config.GetHeight(); + bool framebuffers_overlap = config.GetColorBufferPhysicalAddress() != 0 && + config.GetDepthBufferPhysicalAddress() != 0 && + MathUtil::IntervalsIntersect(config.GetColorBufferPhysicalAddress(), fb_area * GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(config.color_format.Value())), + config.GetDepthBufferPhysicalAddress(), fb_area * Pica::Regs::BytesPerDepthPixel(config.depth_format)); + bool using_color_fb = config.GetColorBufferPhysicalAddress() != 0; + bool using_depth_fb = config.GetDepthBufferPhysicalAddress() != 0 && (regs.output_merger.depth_test_enable || regs.output_merger.depth_write_enable || !framebuffers_overlap); + + if (framebuffers_overlap && using_color_fb && using_depth_fb) { + LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; overlapping framebuffers not supported!"); + using_depth_fb = false; + } + + // get color and depth surfaces + CachedSurface color_params; + CachedSurface depth_params; + color_params.width = depth_params.width = config.GetWidth(); + color_params.height = depth_params.height = config.GetHeight(); + color_params.is_tiled = depth_params.is_tiled = true; + if (VideoCore::g_scaled_resolution_enabled) { + auto layout = VideoCore::g_emu_window->GetFramebufferLayout(); + + // Assume same scaling factor for top and bottom screens + color_params.res_scale_width = depth_params.res_scale_width = (float)layout.top_screen.GetWidth() / VideoCore::kScreenTopWidth; + color_params.res_scale_height = depth_params.res_scale_height = (float)layout.top_screen.GetHeight() / VideoCore::kScreenTopHeight; + } + + color_params.addr = config.GetColorBufferPhysicalAddress(); + color_params.pixel_format = CachedSurface::PixelFormatFromColorFormat(config.color_format); + + depth_params.addr = config.GetDepthBufferPhysicalAddress(); + depth_params.pixel_format = CachedSurface::PixelFormatFromDepthFormat(config.depth_format); + + MathUtil::Rectangle<int> color_rect; + CachedSurface* color_surface = using_color_fb ? GetSurfaceRect(color_params, true, true, color_rect) : nullptr; + + MathUtil::Rectangle<int> depth_rect; + CachedSurface* depth_surface = using_depth_fb ? GetSurfaceRect(depth_params, true, true, depth_rect) : nullptr; + + // Sanity check to make sure found surfaces aren't the same + if (using_depth_fb && using_color_fb && color_surface == depth_surface) { + LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer surfaces overlap; overlapping surfaces not supported!"); + using_depth_fb = false; + depth_surface = nullptr; + } + + MathUtil::Rectangle<int> rect; + + if (color_surface != nullptr && depth_surface != nullptr && (depth_rect.left != color_rect.left || depth_rect.top != color_rect.top)) { + // Can't specify separate color and depth viewport offsets in OpenGL, so re-zero both if they don't match + if (color_rect.left != 0 || color_rect.top != 0) { + color_surface = GetSurface(color_params, true, true); + } + + if (depth_rect.left != 0 || depth_rect.top != 0) { + depth_surface = GetSurface(depth_params, true, true); + } - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, info.width, info.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, temp_texture_buffer_rgba.get()); + if (!color_surface->is_tiled) { + rect = MathUtil::Rectangle<int>(0, 0, (int)(color_params.width * color_params.res_scale_width), (int)(color_params.height * color_params.res_scale_height)); + } else { + rect = MathUtil::Rectangle<int>(0, (int)(color_params.height * color_params.res_scale_height), (int)(color_params.width * color_params.res_scale_width), 0); + } + } else if (color_surface != nullptr) { + rect = color_rect; + } else if (depth_surface != nullptr) { + rect = depth_rect; + } else { + rect = MathUtil::Rectangle<int>(0, 0, 0, 0); + } + + return std::make_tuple(color_surface, depth_surface, rect); +} - texture_cache.emplace(info.physical_address, std::move(new_texture)); +CachedSurface* RasterizerCacheOpenGL::TryGetFillSurface(const GPU::Regs::MemoryFillConfig& config) { + auto surface_interval = boost::icl::interval<PAddr>::right_open(config.GetStartAddress(), config.GetEndAddress()); + auto range = surface_cache.equal_range(surface_interval); + for (auto it = range.first; it != range.second; ++it) { + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + int bits_per_value = 0; + if (config.fill_24bit) { + bits_per_value = 24; + } else if (config.fill_32bit) { + bits_per_value = 32; + } else { + bits_per_value = 16; + } + + CachedSurface* surface = it2->get(); + + if (surface->addr == config.GetStartAddress() && + CachedSurface::GetFormatBpp(surface->pixel_format) == bits_per_value && + (surface->width * surface->height * CachedSurface::GetFormatBpp(surface->pixel_format) / 8) == (config.GetEndAddress() - config.GetStartAddress())) + { + return surface; + } + } } + + return nullptr; } -void RasterizerCacheOpenGL::InvalidateInRange(PAddr addr, u32 size, bool ignore_hash) { - // TODO: Optimize by also inserting upper bound (addr + size) of each texture into the same map and also narrow using lower_bound - auto cache_upper_bound = texture_cache.upper_bound(addr + size); +MICROPROFILE_DEFINE(OpenGL_SurfaceDownload, "OpenGL", "Surface Download", MP_RGB(128, 192, 64)); +void RasterizerCacheOpenGL::FlushSurface(CachedSurface* surface) { + using PixelFormat = CachedSurface::PixelFormat; + using SurfaceType = CachedSurface::SurfaceType; + + if (!surface->dirty) { + return; + } + + MICROPROFILE_SCOPE(OpenGL_SurfaceDownload); + + u8* dst_buffer = Memory::GetPhysicalPointer(surface->addr); + if (dst_buffer == nullptr) { + return; + } + + OpenGLState cur_state = OpenGLState::GetCurState(); + GLuint old_tex = cur_state.texture_units[0].texture_2d; + + OGLTexture unscaled_tex; + GLuint texture_to_flush = surface->texture.handle; + + // If not 1x scale, blit scaled texture to a new 1x texture and use that to flush + if (surface->res_scale_width != 1.f || surface->res_scale_height != 1.f) { + unscaled_tex.Create(); + + AllocateSurfaceTexture(unscaled_tex.handle, surface->pixel_format, surface->width, surface->height); + BlitTextures(surface->texture.handle, unscaled_tex.handle, CachedSurface::GetFormatType(surface->pixel_format), + MathUtil::Rectangle<int>(0, 0, surface->GetScaledWidth(), surface->GetScaledHeight()), + MathUtil::Rectangle<int>(0, 0, surface->width, surface->height)); + + texture_to_flush = unscaled_tex.handle; + } + + cur_state.texture_units[0].texture_2d = texture_to_flush; + cur_state.Apply(); + glActiveTexture(GL_TEXTURE0); - for (auto it = texture_cache.begin(); it != cache_upper_bound;) { - const auto& info = *it->second; + glPixelStorei(GL_PACK_ROW_LENGTH, (GLint)surface->stride); + if (!surface->is_tiled) { + // TODO: Ensure this will always be a color format, not a depth or other format + ASSERT((size_t)surface->pixel_format < fb_format_tuples.size()); + const FormatTuple& tuple = fb_format_tuples[(unsigned int)surface->pixel_format]; - // Flush the texture only if the memory region intersects and a change is detected - if (MathUtil::IntervalsIntersect(addr, size, info.addr, info.size) && - (ignore_hash || info.hash != Common::ComputeHash64(Memory::GetPhysicalPointer(info.addr), info.size))) { + glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, dst_buffer); + } else { + SurfaceType type = CachedSurface::GetFormatType(surface->pixel_format); + if (type != SurfaceType::Depth && type != SurfaceType::DepthStencil) { + ASSERT((size_t)surface->pixel_format < fb_format_tuples.size()); + const FormatTuple& tuple = fb_format_tuples[(unsigned int)surface->pixel_format]; + + u32 bytes_per_pixel = CachedSurface::GetFormatBpp(surface->pixel_format) / 8; + + std::vector<u8> temp_gl_buffer(surface->width * surface->height * bytes_per_pixel); - it = texture_cache.erase(it); + glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, temp_gl_buffer.data()); + + // Directly copy pixels. Internal OpenGL color formats are consistent so no conversion is necessary. + MortonCopyPixels(surface->pixel_format, surface->width, surface->height, bytes_per_pixel, bytes_per_pixel, dst_buffer, temp_gl_buffer.data(), false); } else { - ++it; + // Depth/Stencil formats need special treatment since they aren't sampleable using LookupTexture and can't use RGBA format + size_t tuple_idx = (size_t)surface->pixel_format - 14; + ASSERT(tuple_idx < depth_format_tuples.size()); + const FormatTuple& tuple = depth_format_tuples[tuple_idx]; + + u32 bytes_per_pixel = CachedSurface::GetFormatBpp(surface->pixel_format) / 8; + + // OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type + bool use_4bpp = (surface->pixel_format == PixelFormat::D24); + + u32 gl_bytes_per_pixel = use_4bpp ? 4 : bytes_per_pixel; + + std::vector<u8> temp_gl_buffer(surface->width * surface->height * gl_bytes_per_pixel); + + glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, temp_gl_buffer.data()); + + u8* temp_gl_buffer_ptr = use_4bpp ? temp_gl_buffer.data() + 1 : temp_gl_buffer.data(); + + MortonCopyPixels(surface->pixel_format, surface->width, surface->height, bytes_per_pixel, gl_bytes_per_pixel, dst_buffer, temp_gl_buffer_ptr, false); + } + } + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + + surface->dirty = false; + + cur_state.texture_units[0].texture_2d = old_tex; + cur_state.Apply(); +} + +void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u32 size, const CachedSurface* skip_surface, bool invalidate) { + if (size == 0) { + return; + } + + // Gather up unique surfaces that touch the region + std::unordered_set<std::shared_ptr<CachedSurface>> touching_surfaces; + + auto surface_interval = boost::icl::interval<PAddr>::right_open(addr, addr + size); + auto cache_upper_bound = surface_cache.upper_bound(surface_interval); + for (auto it = surface_cache.lower_bound(surface_interval); it != cache_upper_bound; ++it) { + std::copy_if(it->second.begin(), it->second.end(), std::inserter(touching_surfaces, touching_surfaces.end()), + [skip_surface](std::shared_ptr<CachedSurface> surface) { return (surface.get() != skip_surface); }); + } + + // Flush and invalidate surfaces + for (auto surface : touching_surfaces) { + FlushSurface(surface.get()); + if (invalidate) { + Memory::RasterizerMarkRegionCached(surface->addr, surface->size, -1); + surface_cache.subtract(std::make_pair(boost::icl::interval<PAddr>::right_open(surface->addr, surface->addr + surface->size), std::set<std::shared_ptr<CachedSurface>>({ surface }))); } } } -void RasterizerCacheOpenGL::InvalidateAll() { - texture_cache.clear(); +void RasterizerCacheOpenGL::FlushAll() { + for (auto& surfaces : surface_cache) { + for (auto& surface : surfaces.second) { + FlushSurface(surface.get()); + } + } } diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index b69651427..225596415 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -4,40 +4,219 @@ #pragma once -#include <map> +#include <array> #include <memory> +#include <set> +#include <tuple> + +#include <boost/icl/interval_map.hpp> +#include <glad/glad.h> + +#include "common/assert.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +#include "core/hw/gpu.h" #include "video_core/pica.h" -#include "video_core/debug_utils/debug_utils.h" #include "video_core/renderer_opengl/gl_resource_manager.h" -#include "video_core/renderer_opengl/gl_state.h" + +namespace MathUtil { +template <class T> struct Rectangle; +} + +struct CachedSurface; + +using SurfaceCache = boost::icl::interval_map<PAddr, std::set<std::shared_ptr<CachedSurface>>>; + +struct CachedSurface { + enum class PixelFormat { + // First 5 formats are shared between textures and color buffers + RGBA8 = 0, + RGB8 = 1, + RGB5A1 = 2, + RGB565 = 3, + RGBA4 = 4, + + // Texture-only formats + IA8 = 5, + RG8 = 6, + I8 = 7, + A8 = 8, + IA4 = 9, + I4 = 10, + A4 = 11, + ETC1 = 12, + ETC1A4 = 13, + + // Depth buffer-only formats + D16 = 14, + // gap + D24 = 16, + D24S8 = 17, + + Invalid = 255, + }; + + enum class SurfaceType { + Color = 0, + Texture = 1, + Depth = 2, + DepthStencil = 3, + Invalid = 4, + }; + + static unsigned int GetFormatBpp(CachedSurface::PixelFormat format) { + static const std::array<unsigned int, 18> bpp_table = { + 32, // RGBA8 + 24, // RGB8 + 16, // RGB5A1 + 16, // RGB565 + 16, // RGBA4 + 16, // IA8 + 16, // RG8 + 8, // I8 + 8, // A8 + 8, // IA4 + 4, // I4 + 4, // A4 + 4, // ETC1 + 8, // ETC1A4 + 16, // D16 + 0, + 24, // D24 + 32, // D24S8 + }; + + ASSERT((unsigned int)format < ARRAY_SIZE(bpp_table)); + return bpp_table[(unsigned int)format]; + } + + static PixelFormat PixelFormatFromTextureFormat(Pica::Regs::TextureFormat format) { + return ((unsigned int)format < 14) ? (PixelFormat)format : PixelFormat::Invalid; + } + + static PixelFormat PixelFormatFromColorFormat(Pica::Regs::ColorFormat format) { + return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid; + } + + static PixelFormat PixelFormatFromDepthFormat(Pica::Regs::DepthFormat format) { + return ((unsigned int)format < 4) ? (PixelFormat)((unsigned int)format + 14) : PixelFormat::Invalid; + } + + static PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) { + switch (format) { + // RGB565 and RGB5A1 are switched in PixelFormat compared to ColorFormat + case GPU::Regs::PixelFormat::RGB565: + return PixelFormat::RGB565; + case GPU::Regs::PixelFormat::RGB5A1: + return PixelFormat::RGB5A1; + default: + return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid; + } + } + + static bool CheckFormatsBlittable(PixelFormat pixel_format_a, PixelFormat pixel_format_b) { + SurfaceType a_type = GetFormatType(pixel_format_a); + SurfaceType b_type = GetFormatType(pixel_format_b); + + if ((a_type == SurfaceType::Color || a_type == SurfaceType::Texture) && (b_type == SurfaceType::Color || b_type == SurfaceType::Texture)) { + return true; + } + + if (a_type == SurfaceType::Depth && b_type == SurfaceType::Depth) { + return true; + } + + if (a_type == SurfaceType::DepthStencil && b_type == SurfaceType::DepthStencil) { + return true; + } + + return false; + } + + static SurfaceType GetFormatType(PixelFormat pixel_format) { + if ((unsigned int)pixel_format < 5) { + return SurfaceType::Color; + } + + if ((unsigned int)pixel_format < 14) { + return SurfaceType::Texture; + } + + if (pixel_format == PixelFormat::D16 || pixel_format == PixelFormat::D24) { + return SurfaceType::Depth; + } + + if (pixel_format == PixelFormat::D24S8) { + return SurfaceType::DepthStencil; + } + + return SurfaceType::Invalid; + } + + u32 GetScaledWidth() const { + return (u32)(width * res_scale_width); + } + + u32 GetScaledHeight() const { + return (u32)(height * res_scale_height); + } + + PAddr addr; + u32 size; + + PAddr min_valid; + PAddr max_valid; + + OGLTexture texture; + u32 width; + u32 height; + u32 stride = 0; + float res_scale_width = 1.f; + float res_scale_height = 1.f; + + bool is_tiled; + PixelFormat pixel_format; + bool dirty; +}; class RasterizerCacheOpenGL : NonCopyable { public: + RasterizerCacheOpenGL(); ~RasterizerCacheOpenGL(); + /// Blits one texture to another + bool BlitTextures(GLuint src_tex, GLuint dst_tex, CachedSurface::SurfaceType type, const MathUtil::Rectangle<int>& src_rect, const MathUtil::Rectangle<int>& dst_rect); + + /// Attempt to blit one surface's texture to another + bool TryBlitSurfaces(CachedSurface* src_surface, const MathUtil::Rectangle<int>& src_rect, CachedSurface* dst_surface, const MathUtil::Rectangle<int>& dst_rect); + /// Loads a texture from 3DS memory to OpenGL and caches it (if not already cached) - void LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::DebugUtils::TextureInfo& info); + CachedSurface* GetSurface(const CachedSurface& params, bool match_res_scale, bool load_if_create); - void LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::Regs::FullTextureConfig& config) { - LoadAndBindTexture(state, texture_unit, Pica::DebugUtils::TextureInfo::FromPicaRegister(config.config, config.format)); - } + /// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from 3DS memory to OpenGL and caches it (if not already cached) + CachedSurface* GetSurfaceRect(const CachedSurface& params, bool match_res_scale, bool load_if_create, MathUtil::Rectangle<int>& out_rect); - /// Invalidate any cached resource intersecting the specified region. - void InvalidateInRange(PAddr addr, u32 size, bool ignore_hash = false); + /// Gets a surface based on the texture configuration + CachedSurface* GetTextureSurface(const Pica::Regs::FullTextureConfig& config); - /// Invalidate all cached OpenGL resources tracked by this cache manager - void InvalidateAll(); + /// Gets the color and depth surfaces and rect (resolution scaled) based on the framebuffer configuration + std::tuple<CachedSurface*, CachedSurface*, MathUtil::Rectangle<int>> GetFramebufferSurfaces(const Pica::Regs::FramebufferConfig& config); -private: - struct CachedTexture { - OGLTexture texture; - GLuint width; - GLuint height; - u32 size; - u64 hash; - PAddr addr; - }; + /// Attempt to get a surface that exactly matches the fill region and format + CachedSurface* TryGetFillSurface(const GPU::Regs::MemoryFillConfig& config); + + /// Write the surface back to memory + void FlushSurface(CachedSurface* surface); - std::map<PAddr, std::unique_ptr<CachedTexture>> texture_cache; + /// Write any cached resources overlapping the region back to memory (if dirty) and optionally invalidate them in the cache + void FlushRegion(PAddr addr, u32 size, const CachedSurface* skip_surface, bool invalidate); + + /// Flush all cached resources tracked by this cache manager + void FlushAll(); + +private: + SurfaceCache surface_cache; + OGLFramebuffer transfer_framebuffers[2]; }; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index ee4b54ab9..9011caa39 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -2,9 +2,17 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> +#include <cstddef> + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/logging/log.h" + #include "video_core/pica.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_gen.h" +#include "video_core/renderer_opengl/gl_shader_util.h" using Pica::Regs; using TevStageConfig = Regs::TevStageConfig; @@ -198,6 +206,9 @@ static void AppendColorCombiner(std::string& out, TevStageConfig::Operation oper case Operation::AddThenMultiply: out += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0)) * " + variable_name + "[2]"; break; + case Operation::Dot3_RGB: + out += "vec3(dot(" + variable_name + "[0] - vec3(0.5), " + variable_name + "[1] - vec3(0.5)) * 4.0)"; + break; default: out += "vec3(0.0)"; LOG_CRITICAL(Render_OpenGL, "Unknown color combiner operation: %u", operation); diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index 0ca9d2879..3eb07d57a 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> -#include "video_core/renderer_opengl/gl_rasterizer.h" +struct PicaShaderConfig; namespace GLShader { diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp index e3f7a5868..dded3db46 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.cpp +++ b/src/video_core/renderer_opengl/gl_shader_util.cpp @@ -2,9 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <algorithm> #include <vector> +#include <glad/glad.h> + #include "common/logging/log.h" #include "video_core/renderer_opengl/gl_shader_util.h" diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 08e4d0b54..02cd9f417 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -2,7 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "video_core/pica.h" +#include <glad/glad.h> + +#include "common/common_funcs.h" +#include "common/logging/log.h" + #include "video_core/renderer_opengl/gl_state.h" OpenGLState OpenGLState::cur_state; @@ -48,17 +52,19 @@ OpenGLState::OpenGLState() { texture_unit.sampler = 0; } - for (auto& lut : lighting_lut) { + for (auto& lut : lighting_luts) { lut.texture_1d = 0; } - draw.framebuffer = 0; + draw.read_framebuffer = 0; + draw.draw_framebuffer = 0; draw.vertex_array = 0; draw.vertex_buffer = 0; + draw.uniform_buffer = 0; draw.shader_program = 0; } -void OpenGLState::Apply() { +void OpenGLState::Apply() const { // Culling if (cull.enabled != cur_state.cull.enabled) { if (cull.enabled) { @@ -175,16 +181,19 @@ void OpenGLState::Apply() { } // Lighting LUTs - for (unsigned i = 0; i < ARRAY_SIZE(lighting_lut); ++i) { - if (lighting_lut[i].texture_1d != cur_state.lighting_lut[i].texture_1d) { + for (unsigned i = 0; i < ARRAY_SIZE(lighting_luts); ++i) { + if (lighting_luts[i].texture_1d != cur_state.lighting_luts[i].texture_1d) { glActiveTexture(GL_TEXTURE3 + i); - glBindTexture(GL_TEXTURE_1D, lighting_lut[i].texture_1d); + glBindTexture(GL_TEXTURE_1D, lighting_luts[i].texture_1d); } } // Framebuffer - if (draw.framebuffer != cur_state.draw.framebuffer) { - glBindFramebuffer(GL_FRAMEBUFFER, draw.framebuffer); + if (draw.read_framebuffer != cur_state.draw.read_framebuffer) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer); + } + if (draw.draw_framebuffer != cur_state.draw.draw_framebuffer) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw.draw_framebuffer); } // Vertex array @@ -210,45 +219,58 @@ void OpenGLState::Apply() { cur_state = *this; } -void OpenGLState::ResetTexture(GLuint id) { +GLenum OpenGLState::CheckFBStatus(GLenum target) { + GLenum fb_status = glCheckFramebufferStatus(target); + if (fb_status != GL_FRAMEBUFFER_COMPLETE) { + const char* fb_description = (target == GL_READ_FRAMEBUFFER ? "READ" : (target == GL_DRAW_FRAMEBUFFER ? "DRAW" : "UNK")); + LOG_CRITICAL(Render_OpenGL, "OpenGL %s framebuffer check failed, status %X", fb_description, fb_status); + } + + return fb_status; +} + +void OpenGLState::ResetTexture(GLuint handle) { for (auto& unit : cur_state.texture_units) { - if (unit.texture_2d == id) { + if (unit.texture_2d == handle) { unit.texture_2d = 0; } } } -void OpenGLState::ResetSampler(GLuint id) { +void OpenGLState::ResetSampler(GLuint handle) { for (auto& unit : cur_state.texture_units) { - if (unit.sampler == id) { + if (unit.sampler == handle) { unit.sampler = 0; } } } -void OpenGLState::ResetProgram(GLuint id) { - if (cur_state.draw.shader_program == id) { +void OpenGLState::ResetProgram(GLuint handle) { + if (cur_state.draw.shader_program == handle) { cur_state.draw.shader_program = 0; } } -void OpenGLState::ResetBuffer(GLuint id) { - if (cur_state.draw.vertex_buffer == id) { +void OpenGLState::ResetBuffer(GLuint handle) { + if (cur_state.draw.vertex_buffer == handle) { cur_state.draw.vertex_buffer = 0; } - if (cur_state.draw.uniform_buffer == id) { + if (cur_state.draw.uniform_buffer == handle) { cur_state.draw.uniform_buffer = 0; } } -void OpenGLState::ResetVertexArray(GLuint id) { - if (cur_state.draw.vertex_array == id) { +void OpenGLState::ResetVertexArray(GLuint handle) { + if (cur_state.draw.vertex_array == handle) { cur_state.draw.vertex_array = 0; } } -void OpenGLState::ResetFramebuffer(GLuint id) { - if (cur_state.draw.framebuffer == id) { - cur_state.draw.framebuffer = 0; +void OpenGLState::ResetFramebuffer(GLuint handle) { + if (cur_state.draw.read_framebuffer == handle) { + cur_state.draw.read_framebuffer = 0; + } + if (cur_state.draw.draw_framebuffer == handle) { + cur_state.draw.draw_framebuffer = 0; } } diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index e848058d7..24f20e47c 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -63,15 +63,15 @@ public: struct { GLuint texture_1d; // GL_TEXTURE_BINDING_1D - } lighting_lut[6]; + } lighting_luts[6]; struct { - GLuint framebuffer; // GL_DRAW_FRAMEBUFFER_BINDING + GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING + GLuint draw_framebuffer; // GL_DRAW_FRAMEBUFFER_BINDING GLuint vertex_array; // GL_VERTEX_ARRAY_BINDING GLuint vertex_buffer; // GL_ARRAY_BUFFER_BINDING GLuint uniform_buffer; // GL_UNIFORM_BUFFER_BINDING GLuint shader_program; // GL_CURRENT_PROGRAM - bool shader_dirty; } draw; OpenGLState(); @@ -82,14 +82,18 @@ public: } /// Apply this state as the current OpenGL state - void Apply(); - - static void ResetTexture(GLuint id); - static void ResetSampler(GLuint id); - static void ResetProgram(GLuint id); - static void ResetBuffer(GLuint id); - static void ResetVertexArray(GLuint id); - static void ResetFramebuffer(GLuint id); + void Apply() const; + + /// Check the status of the current OpenGL read or draw framebuffer configuration + static GLenum CheckFBStatus(GLenum target); + + /// Resets and unbinds any references to the given resource in the current OpenGL state + static void ResetTexture(GLuint handle); + static void ResetSampler(GLuint handle); + static void ResetProgram(GLuint handle); + static void ResetBuffer(GLuint handle); + static void ResetVertexArray(GLuint handle); + static void ResetFramebuffer(GLuint handle); private: static OpenGLState cur_state; diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h index 3d6c4e9e5..976d1f364 100644 --- a/src/video_core/renderer_opengl/pica_to_gl.h +++ b/src/video_core/renderer_opengl/pica_to_gl.h @@ -4,9 +4,16 @@ #pragma once +#include <array> +#include <cstddef> + #include <glad/glad.h> +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" #include "common/common_types.h" +#include "common/logging/log.h" #include "video_core/pica.h" @@ -22,7 +29,7 @@ inline GLenum TextureFilterMode(Pica::Regs::TextureConfig::TextureFilter mode) { }; // Range check table for input - if (mode >= ARRAY_SIZE(filter_mode_table)) { + if (static_cast<size_t>(mode) >= ARRAY_SIZE(filter_mode_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown texture filtering mode %d", mode); UNREACHABLE(); @@ -51,7 +58,7 @@ inline GLenum WrapMode(Pica::Regs::TextureConfig::WrapMode mode) { }; // Range check table for input - if (mode >= ARRAY_SIZE(wrap_mode_table)) { + if (static_cast<size_t>(mode) >= ARRAY_SIZE(wrap_mode_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown texture wrap mode %d", mode); UNREACHABLE(); @@ -91,7 +98,7 @@ inline GLenum BlendFunc(Pica::Regs::BlendFactor factor) { }; // Range check table for input - if ((unsigned)factor >= ARRAY_SIZE(blend_func_table)) { + if (static_cast<size_t>(factor) >= ARRAY_SIZE(blend_func_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown blend factor %d", factor); UNREACHABLE(); @@ -122,7 +129,7 @@ inline GLenum LogicOp(Pica::Regs::LogicOp op) { }; // Range check table for input - if ((unsigned)op >= ARRAY_SIZE(logic_op_table)) { + if (static_cast<size_t>(op) >= ARRAY_SIZE(logic_op_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown logic op %d", op); UNREACHABLE(); @@ -145,7 +152,7 @@ inline GLenum CompareFunc(Pica::Regs::CompareFunc func) { }; // Range check table for input - if ((unsigned)func >= ARRAY_SIZE(compare_func_table)) { + if (static_cast<size_t>(func) >= ARRAY_SIZE(compare_func_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown compare function %d", func); UNREACHABLE(); @@ -168,7 +175,7 @@ inline GLenum StencilOp(Pica::Regs::StencilAction action) { }; // Range check table for input - if ((unsigned)action >= ARRAY_SIZE(stencil_op_table)) { + if (static_cast<size_t>(action) >= ARRAY_SIZE(stencil_op_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown stencil op %d", action); UNREACHABLE(); diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 11c4d0daf..0e9a0be8b 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -5,23 +5,28 @@ #include <algorithm> #include <cstddef> #include <cstdlib> +#include <memory> + +#include <glad/glad.h> #include "common/assert.h" +#include "common/bit_field.h" #include "common/emu_window.h" #include "common/logging/log.h" #include "common/profiler_reporting.h" +#include "common/synchronized_wrapper.h" -#include "core/memory.h" -#include "core/settings.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" #include "core/hw/lcd.h" +#include "core/memory.h" +#include "core/settings.h" +#include "core/tracer/recorder.h" -#include "video_core/video_core.h" #include "video_core/debug_utils/debug_utils.h" -#include "video_core/renderer_opengl/gl_rasterizer.h" -#include "video_core/renderer_opengl/gl_shader_util.h" +#include "video_core/rasterizer_interface.h" #include "video_core/renderer_opengl/renderer_opengl.h" +#include "video_core/video_core.h" static const char vertex_shader[] = R"( #version 150 core @@ -107,7 +112,7 @@ void RendererOpenGL::SwapBuffers() { OpenGLState prev_state = OpenGLState::GetCurState(); state.Apply(); - for(int i : {0, 1}) { + for (int i : {0, 1}) { const auto& framebuffer = GPU::g_regs.framebuffer_config[i]; // Main LCD (0): 0x1ED02204, Sub LCD (1): 0x1ED02A04 @@ -117,25 +122,25 @@ void RendererOpenGL::SwapBuffers() { LCD::Read(color_fill.raw, lcd_color_addr); if (color_fill.is_enabled) { - LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b, textures[i]); + LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b, screen_infos[i].texture); // Resize the texture in case the framebuffer size has changed - textures[i].width = 1; - textures[i].height = 1; + screen_infos[i].texture.width = 1; + screen_infos[i].texture.height = 1; } else { - if (textures[i].width != (GLsizei)framebuffer.width || - textures[i].height != (GLsizei)framebuffer.height || - textures[i].format != framebuffer.color_format) { + if (screen_infos[i].texture.width != (GLsizei)framebuffer.width || + screen_infos[i].texture.height != (GLsizei)framebuffer.height || + screen_infos[i].texture.format != framebuffer.color_format) { // Reallocate texture if the framebuffer size has changed. // This is expected to not happen very often and hence should not be a // performance problem. - ConfigureFramebufferTexture(textures[i], framebuffer); + ConfigureFramebufferTexture(screen_infos[i].texture, framebuffer); } - LoadFBToActiveGLTexture(framebuffer, textures[i]); + LoadFBToScreenInfo(framebuffer, screen_infos[i]); // Resize the texture in case the framebuffer size has changed - textures[i].width = framebuffer.width; - textures[i].height = framebuffer.height; + screen_infos[i].texture.width = framebuffer.width; + screen_infos[i].texture.height = framebuffer.height; } } @@ -166,8 +171,8 @@ void RendererOpenGL::SwapBuffers() { /** * Loads framebuffer from emulated memory into the active OpenGL texture. */ -void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer, - const TextureInfo& texture) { +void RendererOpenGL::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer, + ScreenInfo& screen_info) { const PAddr framebuffer_addr = framebuffer.active_fb == 0 ? framebuffer.address_left1 : framebuffer.address_left2; @@ -177,8 +182,6 @@ void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer_addr, (int)framebuffer.width, (int)framebuffer.height, (int)framebuffer.format); - const u8* framebuffer_data = Memory::GetPhysicalPointer(framebuffer_addr); - int bpp = GPU::Regs::BytesPerPixel(framebuffer.color_format); size_t pixel_stride = framebuffer.stride / bpp; @@ -189,24 +192,34 @@ void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& // only allows rows to have a memory alignement of 4. ASSERT(pixel_stride % 4 == 0); - state.texture_units[0].texture_2d = texture.handle; - state.Apply(); + if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, 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); - glActiveTexture(GL_TEXTURE0); - glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)pixel_stride); + Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height); - // Update existing texture - // TODO: Test what happens on hardware when you change the framebuffer dimensions so that they - // differ from the LCD resolution. - // TODO: Applications could theoretically crash Citra here by specifying too large - // framebuffer sizes. We should make sure that this cannot happen. - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height, - texture.gl_format, texture.gl_type, framebuffer_data); + const u8* framebuffer_data = Memory::GetPhysicalPointer(framebuffer_addr); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + state.texture_units[0].texture_2d = screen_info.texture.resource.handle; + state.Apply(); - state.texture_units[0].texture_2d = 0; - state.Apply(); + glActiveTexture(GL_TEXTURE0); + glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)pixel_stride); + + // Update existing texture + // TODO: Test what happens on hardware when you change the framebuffer dimensions so that they + // differ from the LCD resolution. + // TODO: Applications could theoretically crash Citra here by specifying too large + // framebuffer sizes. We should make sure that this cannot happen. + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height, + screen_info.texture.gl_format, screen_info.texture.gl_type, framebuffer_data); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + state.texture_units[0].texture_2d = 0; + state.Apply(); + } } /** @@ -216,7 +229,7 @@ void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& */ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture) { - state.texture_units[0].texture_2d = texture.handle; + state.texture_units[0].texture_2d = texture.resource.handle; state.Apply(); glActiveTexture(GL_TEXTURE0); @@ -224,6 +237,9 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color // Update existing texture glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, framebuffer_data); + + state.texture_units[0].texture_2d = 0; + state.Apply(); } /** @@ -233,20 +249,22 @@ void RendererOpenGL::InitOpenGLObjects() { glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, 0.0f); // Link shaders and get variable locations - program_id = GLShader::LoadProgram(vertex_shader, fragment_shader); - uniform_modelview_matrix = glGetUniformLocation(program_id, "modelview_matrix"); - uniform_color_texture = glGetUniformLocation(program_id, "color_texture"); - attrib_position = glGetAttribLocation(program_id, "vert_position"); - attrib_tex_coord = glGetAttribLocation(program_id, "vert_tex_coord"); + shader.Create(vertex_shader, fragment_shader); + state.draw.shader_program = shader.handle; + state.Apply(); + uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix"); + uniform_color_texture = glGetUniformLocation(shader.handle, "color_texture"); + attrib_position = glGetAttribLocation(shader.handle, "vert_position"); + attrib_tex_coord = glGetAttribLocation(shader.handle, "vert_tex_coord"); // Generate VBO handle for drawing - glGenBuffers(1, &vertex_buffer_handle); + vertex_buffer.Create(); // Generate VAO - glGenVertexArrays(1, &vertex_array_handle); + vertex_array.Create(); - state.draw.vertex_array = vertex_array_handle; - state.draw.vertex_buffer = vertex_buffer_handle; + state.draw.vertex_array = vertex_array.handle; + state.draw.vertex_buffer = vertex_buffer.handle; state.draw.uniform_buffer = 0; state.Apply(); @@ -258,13 +276,13 @@ void RendererOpenGL::InitOpenGLObjects() { glEnableVertexAttribArray(attrib_tex_coord); // Allocate textures for each screen - for (auto& texture : textures) { - glGenTextures(1, &texture.handle); + for (auto& screen_info : screen_infos) { + screen_info.texture.resource.Create(); // Allocation of storage is deferred until the first frame, when we // know the framebuffer size. - state.texture_units[0].texture_2d = texture.handle; + state.texture_units[0].texture_2d = screen_info.texture.resource.handle; state.Apply(); glActiveTexture(GL_TEXTURE0); @@ -273,6 +291,8 @@ void RendererOpenGL::InitOpenGLObjects() { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + screen_info.display_texture = screen_info.texture.resource.handle; } state.texture_units[0].texture_2d = 0; @@ -327,30 +347,38 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, UNIMPLEMENTED(); } - state.texture_units[0].texture_2d = texture.handle; + state.texture_units[0].texture_2d = texture.resource.handle; state.Apply(); glActiveTexture(GL_TEXTURE0); glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0, texture.gl_format, texture.gl_type, nullptr); + + state.texture_units[0].texture_2d = 0; + state.Apply(); } /** * Draws a single texture to the emulator window, rotating the texture to correct for the 3DS's LCD rotation. */ -void RendererOpenGL::DrawSingleScreenRotated(const TextureInfo& texture, float x, float y, float w, float h) { +void RendererOpenGL::DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h) { + auto& texcoords = screen_info.display_texcoords; + std::array<ScreenRectVertex, 4> vertices = {{ - ScreenRectVertex(x, y, 1.f, 0.f), - ScreenRectVertex(x+w, y, 1.f, 1.f), - ScreenRectVertex(x, y+h, 0.f, 0.f), - ScreenRectVertex(x+w, y+h, 0.f, 1.f), + ScreenRectVertex(x, y, texcoords.bottom, texcoords.left), + ScreenRectVertex(x+w, y, texcoords.bottom, texcoords.right), + ScreenRectVertex(x, y+h, texcoords.top, texcoords.left), + ScreenRectVertex(x+w, y+h, texcoords.top, texcoords.right), }}; - state.texture_units[0].texture_2d = texture.handle; + state.texture_units[0].texture_2d = screen_info.display_texture; state.Apply(); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + state.texture_units[0].texture_2d = 0; + state.Apply(); } /** @@ -362,9 +390,6 @@ void RendererOpenGL::DrawScreens() { glViewport(0, 0, layout.width, layout.height); glClear(GL_COLOR_BUFFER_BIT); - state.draw.shader_program = program_id; - state.Apply(); - // Set projection matrix std::array<GLfloat, 3 * 2> ortho_matrix = MakeOrthographicMatrix((float)layout.width, (float)layout.height); @@ -374,9 +399,9 @@ void RendererOpenGL::DrawScreens() { glActiveTexture(GL_TEXTURE0); glUniform1i(uniform_color_texture, 0); - DrawSingleScreenRotated(textures[0], (float)layout.top_screen.left, (float)layout.top_screen.top, + DrawSingleScreenRotated(screen_infos[0], (float)layout.top_screen.left, (float)layout.top_screen.top, (float)layout.top_screen.GetWidth(), (float)layout.top_screen.GetHeight()); - DrawSingleScreenRotated(textures[1], (float)layout.bottom_screen.left,(float)layout.bottom_screen.top, + DrawSingleScreenRotated(screen_infos[1], (float)layout.bottom_screen.left,(float)layout.bottom_screen.top, (float)layout.bottom_screen.GetWidth(), (float)layout.bottom_screen.GetHeight()); m_current_frame++; diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index fe4d142a5..00e1044ab 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -8,13 +8,34 @@ #include <glad/glad.h> +#include "common/common_types.h" +#include "common/math_util.h" + #include "core/hw/gpu.h" #include "video_core/renderer_base.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_state.h" class EmuWindow; +/// Structure used for storing information about the textures for each 3DS screen +struct TextureInfo { + OGLTexture resource; + GLsizei width; + GLsizei height; + GPU::Regs::PixelFormat format; + GLenum gl_format; + GLenum gl_type; +}; + +/// Structure used for storing information about the display target for each 3DS screen +struct ScreenInfo { + GLuint display_texture; + MathUtil::Rectangle<float> display_texcoords; + TextureInfo texture; +}; + class RendererOpenGL : public RendererBase { public: @@ -37,26 +58,16 @@ public: void ShutDown() override; private: - /// Structure used for storing information about the textures for each 3DS screen - struct TextureInfo { - GLuint handle; - GLsizei width; - GLsizei height; - GPU::Regs::PixelFormat format; - GLenum gl_format; - GLenum gl_type; - }; - void InitOpenGLObjects(); void ConfigureFramebufferTexture(TextureInfo& texture, const GPU::Regs::FramebufferConfig& framebuffer); void DrawScreens(); - void DrawSingleScreenRotated(const TextureInfo& texture, float x, float y, float w, float h); + void DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h); void UpdateFramerate(); - // Loads framebuffer from emulated memory into the active OpenGL texture. - void LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer, - const TextureInfo& texture); + // Loads framebuffer from emulated memory into the display information structure + void LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer, + ScreenInfo& screen_info); // Fills active OpenGL texture with the given RGB color. void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture); @@ -69,10 +80,10 @@ private: OpenGLState state; // OpenGL object IDs - GLuint vertex_array_handle; - GLuint vertex_buffer_handle; - GLuint program_id; - std::array<TextureInfo, 2> textures; ///< Textures for top and bottom screens respectively + OGLVertexArray vertex_array; + OGLBuffer vertex_buffer; + OGLShader shader; + std::array<ScreenInfo, 2> screen_infos; ///< Display information for top and bottom screens respectively // Shader uniform location indices GLuint uniform_modelview_matrix; GLuint uniform_color_texture; diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp index 509558fc0..65dcc9156 100644 --- a/src/video_core/shader/shader.cpp +++ b/src/video_core/shader/shader.cpp @@ -2,63 +2,53 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <memory> +#include <atomic> +#include <cmath> +#include <cstring> #include <unordered_map> +#include <utility> #include <boost/range/algorithm/fill.hpp> +#include "common/bit_field.h" #include "common/hash.h" -#include "common/make_unique.h" +#include "common/logging/log.h" #include "common/microprofile.h" -#include "common/profiler.h" -#include "video_core/debug_utils/debug_utils.h" #include "video_core/pica.h" #include "video_core/pica_state.h" -#include "video_core/video_core.h" - -#include "shader.h" -#include "shader_interpreter.h" +#include "video_core/shader/shader.h" +#include "video_core/shader/shader_interpreter.h" #ifdef ARCHITECTURE_x86_64 -#include "shader_jit_x64.h" +#include "video_core/shader/shader_jit_x64.h" #endif // ARCHITECTURE_x86_64 +#include "video_core/video_core.h" + namespace Pica { namespace Shader { #ifdef ARCHITECTURE_x86_64 -static std::unordered_map<u64, CompiledShader*> shader_map; -static JitCompiler jit; -static CompiledShader* jit_shader; - -static void ClearCache() { - shader_map.clear(); - jit.Clear(); - LOG_INFO(HW_GPU, "Shader JIT cache cleared"); -} +static std::unordered_map<u64, std::unique_ptr<JitShader>> shader_map; +static const JitShader* jit_shader; #endif // ARCHITECTURE_x86_64 -void Setup(UnitState<false>& state) { +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)) ^ - g_state.regs.vs.main_offset); + 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; + jit_shader = iter->second.get(); } else { - // Check if remaining JIT code space is enough for at least one more (massive) shader - if (jit.GetSpaceLeft() < jit_shader_size) { - // If not, clear the cache of all previously compiled shaders - ClearCache(); - } - - jit_shader = jit.Compile(); - shader_map.emplace(cache_key, jit_shader); + auto shader = std::make_unique<JitShader>(); + shader->Compile(); + jit_shader = shader.get(); + shader_map[cache_key] = std::move(shader); } } #endif // ARCHITECTURE_x86_64 @@ -66,17 +56,15 @@ void Setup(UnitState<false>& state) { void Shutdown() { #ifdef ARCHITECTURE_x86_64 - ClearCache(); + shader_map.clear(); #endif // ARCHITECTURE_x86_64 } -static Common::Profiling::TimingCategory shader_category("Vertex Shader"); 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; - Common::Profiling::ScopeTimer timer(shader_category); MICROPROFILE_SCOPE(GPU_VertexShader); state.program_counter = config.main_offset; @@ -86,31 +74,15 @@ OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attr // Setup input register table const auto& attribute_register_map = config.input_register_map; - // TODO: Instead of this cumbersome logic, just load the input data directly like - // for (int attr = 0; attr < num_attributes; ++attr) { input_attr[0] = state.registers.input[attribute_register_map.attribute0_register]; } - if (num_attributes > 0) state.registers.input[attribute_register_map.attribute0_register] = input.attr[0]; - if (num_attributes > 1) state.registers.input[attribute_register_map.attribute1_register] = input.attr[1]; - if (num_attributes > 2) state.registers.input[attribute_register_map.attribute2_register] = input.attr[2]; - if (num_attributes > 3) state.registers.input[attribute_register_map.attribute3_register] = input.attr[3]; - if (num_attributes > 4) state.registers.input[attribute_register_map.attribute4_register] = input.attr[4]; - if (num_attributes > 5) state.registers.input[attribute_register_map.attribute5_register] = input.attr[5]; - if (num_attributes > 6) state.registers.input[attribute_register_map.attribute6_register] = input.attr[6]; - if (num_attributes > 7) state.registers.input[attribute_register_map.attribute7_register] = input.attr[7]; - if (num_attributes > 8) state.registers.input[attribute_register_map.attribute8_register] = input.attr[8]; - if (num_attributes > 9) state.registers.input[attribute_register_map.attribute9_register] = input.attr[9]; - if (num_attributes > 10) state.registers.input[attribute_register_map.attribute10_register] = input.attr[10]; - if (num_attributes > 11) state.registers.input[attribute_register_map.attribute11_register] = input.attr[11]; - if (num_attributes > 12) state.registers.input[attribute_register_map.attribute12_register] = input.attr[12]; - if (num_attributes > 13) state.registers.input[attribute_register_map.attribute13_register] = input.attr[13]; - if (num_attributes > 14) state.registers.input[attribute_register_map.attribute14_register] = input.attr[14]; - if (num_attributes > 15) state.registers.input[attribute_register_map.attribute15_register] = input.attr[15]; + 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(&state.registers); + jit_shader->Run(&state.registers, g_state.regs.vs.main_offset); else RunInterpreter(state); #else @@ -121,15 +93,23 @@ OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attr OutputVertex ret; // TODO(neobrain): Under some circumstances, up to 16 attributes may be output. We need to // figure out what those circumstances are and enable the remaining outputs then. - for (int i = 0; i < 7; ++i) { - const auto& output_register_map = g_state.regs.vs_output_attributes[i]; // TODO: Don't hardcode VS here + unsigned index = 0; + for (unsigned i = 0; i < 7; ++i) { + + if (index >= g_state.regs.vs_output_total) + break; + + if ((g_state.regs.vs.output_mask & (1 << i)) == 0) + continue; + + const auto& output_register_map = g_state.regs.vs_output_attributes[index]; // TODO: Don't hardcode VS here u32 semantics[4] = { output_register_map.map_x, output_register_map.map_y, output_register_map.map_z, output_register_map.map_w }; - for (int comp = 0; comp < 4; ++comp) { + 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]; @@ -139,10 +119,12 @@ OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attr memset(out, 0, sizeof(*out)); } } + + index++; } // The hardware takes the absolute and saturates vertex colors like this, *before* doing interpolation - for (int i = 0; i < 4; ++i) { + for (unsigned i = 0; i < 4; ++i) { ret.color[i] = float24::FromFloat32( std::fmin(std::fabs(ret.color[i].ToFloat32()), 1.0f)); } @@ -170,22 +152,8 @@ DebugData<true> ProduceDebugInfo(const InputVertex& input, int num_attributes, c float24 dummy_register; boost::fill(state.registers.input, &dummy_register); - if (num_attributes > 0) state.registers.input[attribute_register_map.attribute0_register] = &input.attr[0].x; - if (num_attributes > 1) state.registers.input[attribute_register_map.attribute1_register] = &input.attr[1].x; - if (num_attributes > 2) state.registers.input[attribute_register_map.attribute2_register] = &input.attr[2].x; - if (num_attributes > 3) state.registers.input[attribute_register_map.attribute3_register] = &input.attr[3].x; - if (num_attributes > 4) state.registers.input[attribute_register_map.attribute4_register] = &input.attr[4].x; - if (num_attributes > 5) state.registers.input[attribute_register_map.attribute5_register] = &input.attr[5].x; - if (num_attributes > 6) state.registers.input[attribute_register_map.attribute6_register] = &input.attr[6].x; - if (num_attributes > 7) state.registers.input[attribute_register_map.attribute7_register] = &input.attr[7].x; - if (num_attributes > 8) state.registers.input[attribute_register_map.attribute8_register] = &input.attr[8].x; - if (num_attributes > 9) state.registers.input[attribute_register_map.attribute9_register] = &input.attr[9].x; - if (num_attributes > 10) state.registers.input[attribute_register_map.attribute10_register] = &input.attr[10].x; - if (num_attributes > 11) state.registers.input[attribute_register_map.attribute11_register] = &input.attr[11].x; - if (num_attributes > 12) state.registers.input[attribute_register_map.attribute12_register] = &input.attr[12].x; - if (num_attributes > 13) state.registers.input[attribute_register_map.attribute13_register] = &input.attr[13].x; - if (num_attributes > 14) state.registers.input[attribute_register_map.attribute14_register] = &input.attr[14].x; - if (num_attributes > 15) state.registers.input[attribute_register_map.attribute15_register] = &input.attr[15].x; + 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; diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h index 7af8f1fa1..56b83bfeb 100644 --- a/src/video_core/shader/shader.h +++ b/src/video_core/shader/shader.h @@ -4,17 +4,23 @@ #pragma once +#include <array> +#include <cstddef> +#include <memory> +#include <type_traits> #include <vector> #include <boost/container/static_vector.hpp> -#include <nihstro/shader_binary.h> +#include <nihstro/shader_bytecode.h> +#include "common/assert.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/vector_math.h" #include "video_core/pica.h" +#include "video_core/pica_types.h" using nihstro::RegisterType; using nihstro::SourceRegister; @@ -25,7 +31,7 @@ namespace Pica { namespace Shader { struct InputVertex { - Math::Vec4<float24> attr[16]; + alignas(16) Math::Vec4<float24> attr[16]; }; struct OutputVertex { @@ -339,9 +345,8 @@ 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). - * @param state Shader unit state, must be setup per shader and per shader unit */ -void Setup(UnitState<false>& state); +void Setup(); /// Performs any cleanup when the emulator is shutdown void Shutdown(); diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp index 9b978583e..7710f7fbc 100644 --- a/src/video_core/shader/shader_interpreter.cpp +++ b/src/video_core/shader/shader_interpreter.cpp @@ -2,12 +2,20 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> +#include <array> +#include <cmath> #include <numeric> + #include <nihstro/shader_bytecode.h> -#include "common/file_util.h" -#include "video_core/pica.h" +#include "common/assert.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/vector_math.h" + #include "video_core/pica_state.h" +#include "video_core/pica_types.h" #include "video_core/shader/shader.h" #include "video_core/shader/shader_interpreter.h" diff --git a/src/video_core/shader/shader_interpreter.h b/src/video_core/shader/shader_interpreter.h index 294bca50e..6048cdf3a 100644 --- a/src/video_core/shader/shader_interpreter.h +++ b/src/video_core/shader/shader_interpreter.h @@ -4,12 +4,12 @@ #pragma once -#include "video_core/shader/shader.h" - namespace Pica { namespace Shader { +template <bool Debug> struct UnitState; + template<bool Debug> void RunInterpreter(UnitState<Debug>& state); diff --git a/src/video_core/shader/shader_jit_x64.cpp b/src/video_core/shader/shader_jit_x64.cpp index dffe051ef..99f6c51eb 100644 --- a/src/video_core/shader/shader_jit_x64.cpp +++ b/src/video_core/shader/shader_jit_x64.cpp @@ -2,8 +2,16 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <smmintrin.h> +#include <algorithm> +#include <cmath> +#include <cstdint> +#include <xmmintrin.h> +#include <nihstro/shader_bytecode.h> + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/vector_math.h" #include "common/x64/abi.h" #include "common/x64/cpu_detect.h" #include "common/x64/emitter.h" @@ -12,6 +20,7 @@ #include "shader_jit_x64.h" #include "video_core/pica_state.h" +#include "video_core/pica_types.h" namespace Pica { @@ -19,73 +28,73 @@ namespace Shader { using namespace Gen; -typedef void (JitCompiler::*JitFunction)(Instruction instr); +typedef void (JitShader::*JitFunction)(Instruction instr); const JitFunction instr_table[64] = { - &JitCompiler::Compile_ADD, // add - &JitCompiler::Compile_DP3, // dp3 - &JitCompiler::Compile_DP4, // dp4 - &JitCompiler::Compile_DPH, // dph + &JitShader::Compile_ADD, // add + &JitShader::Compile_DP3, // dp3 + &JitShader::Compile_DP4, // dp4 + &JitShader::Compile_DPH, // dph nullptr, // unknown - &JitCompiler::Compile_EX2, // ex2 - &JitCompiler::Compile_LG2, // lg2 + &JitShader::Compile_EX2, // ex2 + &JitShader::Compile_LG2, // lg2 nullptr, // unknown - &JitCompiler::Compile_MUL, // mul - &JitCompiler::Compile_SGE, // sge - &JitCompiler::Compile_SLT, // slt - &JitCompiler::Compile_FLR, // flr - &JitCompiler::Compile_MAX, // max - &JitCompiler::Compile_MIN, // min - &JitCompiler::Compile_RCP, // rcp - &JitCompiler::Compile_RSQ, // rsq + &JitShader::Compile_MUL, // mul + &JitShader::Compile_SGE, // sge + &JitShader::Compile_SLT, // slt + &JitShader::Compile_FLR, // flr + &JitShader::Compile_MAX, // max + &JitShader::Compile_MIN, // min + &JitShader::Compile_RCP, // rcp + &JitShader::Compile_RSQ, // rsq nullptr, // unknown nullptr, // unknown - &JitCompiler::Compile_MOVA, // mova - &JitCompiler::Compile_MOV, // mov + &JitShader::Compile_MOVA, // mova + &JitShader::Compile_MOV, // mov nullptr, // unknown nullptr, // unknown nullptr, // unknown nullptr, // unknown - &JitCompiler::Compile_DPH, // dphi + &JitShader::Compile_DPH, // dphi nullptr, // unknown - &JitCompiler::Compile_SGE, // sgei - &JitCompiler::Compile_SLT, // slti + &JitShader::Compile_SGE, // sgei + &JitShader::Compile_SLT, // slti nullptr, // unknown nullptr, // unknown nullptr, // unknown nullptr, // unknown nullptr, // unknown - &JitCompiler::Compile_NOP, // nop - &JitCompiler::Compile_END, // end + &JitShader::Compile_NOP, // nop + &JitShader::Compile_END, // end nullptr, // break - &JitCompiler::Compile_CALL, // call - &JitCompiler::Compile_CALLC, // callc - &JitCompiler::Compile_CALLU, // callu - &JitCompiler::Compile_IF, // ifu - &JitCompiler::Compile_IF, // ifc - &JitCompiler::Compile_LOOP, // loop + &JitShader::Compile_CALL, // call + &JitShader::Compile_CALLC, // callc + &JitShader::Compile_CALLU, // callu + &JitShader::Compile_IF, // ifu + &JitShader::Compile_IF, // ifc + &JitShader::Compile_LOOP, // loop nullptr, // emit nullptr, // sete - &JitCompiler::Compile_JMP, // jmpc - &JitCompiler::Compile_JMP, // jmpu - &JitCompiler::Compile_CMP, // cmp - &JitCompiler::Compile_CMP, // cmp - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad + &JitShader::Compile_JMP, // jmpc + &JitShader::Compile_JMP, // jmpu + &JitShader::Compile_CMP, // cmp + &JitShader::Compile_CMP, // cmp + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad }; // The following is used to alias some commonly used registers. Generally, RAX-RDX and XMM0-XMM3 can @@ -138,13 +147,32 @@ static const u8 NO_SRC_REG_SWIZZLE = 0x1b; static const u8 NO_DEST_REG_MASK = 0xf; /** + * Get the vertex shader instruction for a given offset in the current shader program + * @param offset Offset in the current shader program of the instruction + * @return Instruction at the specified offset + */ +static Instruction GetVertexShaderInstruction(size_t offset) { + return { g_state.vs.program_code[offset] }; +} + +static void LogCritical(const char* msg) { + LOG_CRITICAL(HW_GPU, "%s", msg); +} + +void JitShader::Compile_Assert(bool condition, const char* msg) { + if (!condition) { + ABI_CallFunctionP(reinterpret_cast<const void*>(LogCritical), const_cast<char*>(msg)); + } +} + +/** * Loads and swizzles a source register into the specified XMM register. * @param instr VS instruction, used for determining how to load the source register * @param src_num Number indicating which source register to load (1 = src1, 2 = src2, 3 = src3) * @param src_reg SourceRegister object corresponding to the source register to load * @param dest Destination XMM register to store the loaded, swizzled source register */ -void JitCompiler::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, X64Reg dest) { +void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, X64Reg dest) { X64Reg src_ptr; size_t src_offset; @@ -216,7 +244,7 @@ void JitCompiler::Compile_SwizzleSrc(Instruction instr, unsigned src_num, Source } } -void JitCompiler::Compile_DestEnable(Instruction instr,X64Reg src) { +void JitShader::Compile_DestEnable(Instruction instr,X64Reg src) { DestRegister dest; unsigned operand_desc_id; if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD || @@ -263,7 +291,7 @@ void JitCompiler::Compile_DestEnable(Instruction instr,X64Reg src) { } } -void JitCompiler::Compile_SanitizedMul(Gen::X64Reg src1, Gen::X64Reg src2, Gen::X64Reg scratch) { +void JitShader::Compile_SanitizedMul(Gen::X64Reg src1, Gen::X64Reg src2, Gen::X64Reg scratch) { MOVAPS(scratch, R(src1)); CMPPS(scratch, R(src2), CMP_ORD); @@ -276,7 +304,7 @@ void JitCompiler::Compile_SanitizedMul(Gen::X64Reg src1, Gen::X64Reg src2, Gen:: ANDPS(src1, R(scratch)); } -void JitCompiler::Compile_EvaluateCondition(Instruction instr) { +void JitShader::Compile_EvaluateCondition(Instruction instr) { // Note: NXOR is used below to check for equality switch (instr.flow_control.op) { case Instruction::FlowControlType::Or: @@ -307,23 +335,23 @@ void JitCompiler::Compile_EvaluateCondition(Instruction instr) { } } -void JitCompiler::Compile_UniformCondition(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)); } -BitSet32 JitCompiler::PersistentCallerSavedRegs() { +BitSet32 JitShader::PersistentCallerSavedRegs() { return persistent_regs & ABI_ALL_CALLER_SAVED; } -void JitCompiler::Compile_ADD(Instruction instr) { +void JitShader::Compile_ADD(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); ADDPS(SRC1, R(SRC2)); Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_DP3(Instruction instr) { +void JitShader::Compile_DP3(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); @@ -342,7 +370,7 @@ void JitCompiler::Compile_DP3(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_DP4(Instruction instr) { +void JitShader::Compile_DP4(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); @@ -359,7 +387,7 @@ void JitCompiler::Compile_DP4(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_DPH(Instruction instr) { +void JitShader::Compile_DPH(Instruction instr) { if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::DPHI) { Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); @@ -391,7 +419,7 @@ void JitCompiler::Compile_DPH(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_EX2(Instruction instr) { +void JitShader::Compile_EX2(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); MOVSS(XMM0, R(SRC1)); @@ -404,7 +432,7 @@ void JitCompiler::Compile_EX2(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_LG2(Instruction instr) { +void JitShader::Compile_LG2(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); MOVSS(XMM0, R(SRC1)); @@ -417,14 +445,14 @@ void JitCompiler::Compile_LG2(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_MUL(Instruction instr) { +void JitShader::Compile_MUL(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); Compile_SanitizedMul(SRC1, SRC2, SCRATCH); Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_SGE(Instruction instr) { +void JitShader::Compile_SGE(Instruction instr) { if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::SGEI) { Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); @@ -439,7 +467,7 @@ void JitCompiler::Compile_SGE(Instruction instr) { Compile_DestEnable(instr, SRC2); } -void JitCompiler::Compile_SLT(Instruction instr) { +void JitShader::Compile_SLT(Instruction instr) { if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::SLTI) { Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); @@ -454,7 +482,7 @@ void JitCompiler::Compile_SLT(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_FLR(Instruction instr) { +void JitShader::Compile_FLR(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); if (Common::GetCPUCaps().sse4_1) { @@ -467,7 +495,7 @@ void JitCompiler::Compile_FLR(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_MAX(Instruction instr) { +void JitShader::Compile_MAX(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned. @@ -475,7 +503,7 @@ void JitCompiler::Compile_MAX(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_MIN(Instruction instr) { +void JitShader::Compile_MIN(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned. @@ -483,7 +511,7 @@ void JitCompiler::Compile_MIN(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_MOVA(Instruction instr) { +void JitShader::Compile_MOVA(Instruction instr) { SwizzlePattern swiz = { g_state.vs.swizzle_data[instr.common.operand_desc_id] }; if (!swiz.DestComponentEnabled(0) && !swiz.DestComponentEnabled(1)) { @@ -528,12 +556,12 @@ void JitCompiler::Compile_MOVA(Instruction instr) { } } -void JitCompiler::Compile_MOV(Instruction instr) { +void JitShader::Compile_MOV(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_RCP(Instruction instr) { +void JitShader::Compile_RCP(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); // TODO(bunnei): RCPSS is a pretty rough approximation, this might cause problems if Pica @@ -544,7 +572,7 @@ void JitCompiler::Compile_RCP(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_RSQ(Instruction instr) { +void JitShader::Compile_RSQ(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); // TODO(bunnei): RSQRTSS is a pretty rough approximation, this might cause problems if Pica @@ -555,36 +583,41 @@ void JitCompiler::Compile_RSQ(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_NOP(Instruction instr) { +void JitShader::Compile_NOP(Instruction instr) { } -void JitCompiler::Compile_END(Instruction instr) { +void JitShader::Compile_END(Instruction instr) { ABI_PopRegistersAndAdjustStack(ABI_ALL_CALLEE_SAVED, 8); RET(); } -void JitCompiler::Compile_CALL(Instruction instr) { - unsigned offset = instr.flow_control.dest_offset; - while (offset < (instr.flow_control.dest_offset + instr.flow_control.num_instructions)) { - Compile_NextInstr(&offset); - } +void JitShader::Compile_CALL(Instruction instr) { + // Push offset of the return + PUSH(64, Imm32(instr.flow_control.dest_offset + instr.flow_control.num_instructions)); + + // Call the subroutine + FixupBranch b = CALL(); + fixup_branches.push_back({ b, instr.flow_control.dest_offset }); + + // Skip over the return offset that's on the stack + ADD(64, R(RSP), Imm32(8)); } -void JitCompiler::Compile_CALLC(Instruction instr) { +void JitShader::Compile_CALLC(Instruction instr) { Compile_EvaluateCondition(instr); FixupBranch b = J_CC(CC_Z, true); Compile_CALL(instr); SetJumpTarget(b); } -void JitCompiler::Compile_CALLU(Instruction instr) { +void JitShader::Compile_CALLU(Instruction instr) { Compile_UniformCondition(instr); FixupBranch b = J_CC(CC_Z, true); Compile_CALL(instr); SetJumpTarget(b); } -void JitCompiler::Compile_CMP(Instruction instr) { +void JitShader::Compile_CMP(Instruction instr) { using Op = Instruction::Common::CompareOpType::Op; Op op_x = instr.common.compare_op.x; Op op_y = instr.common.compare_op.y; @@ -627,7 +660,7 @@ void JitCompiler::Compile_CMP(Instruction instr) { SHR(64, R(COND1), Imm8(63)); } -void JitCompiler::Compile_MAD(Instruction instr) { +void JitShader::Compile_MAD(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.mad.src1, SRC1); if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) { @@ -644,9 +677,8 @@ void JitCompiler::Compile_MAD(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_IF(Instruction instr) { - ASSERT_MSG(instr.flow_control.dest_offset > *offset_ptr, "Backwards if-statements (%d -> %d) not supported", - *offset_ptr, instr.flow_control.dest_offset.Value()); +void JitShader::Compile_IF(Instruction instr) { + Compile_Assert(instr.flow_control.dest_offset >= program_counter, "Backwards if-statements not supported"); // Evaluate the "IF" condition if (instr.opcode.Value() == OpCode::Id::IFU) { @@ -676,10 +708,9 @@ void JitCompiler::Compile_IF(Instruction instr) { SetJumpTarget(b2); } -void JitCompiler::Compile_LOOP(Instruction instr) { - ASSERT_MSG(instr.flow_control.dest_offset > *offset_ptr, "Backwards loops (%d -> %d) not supported", - *offset_ptr, instr.flow_control.dest_offset.Value()); - ASSERT_MSG(!looping, "Nested loops not supported"); +void JitShader::Compile_LOOP(Instruction instr) { + Compile_Assert(instr.flow_control.dest_offset >= program_counter, "Backwards loops not supported"); + Compile_Assert(!looping, "Nested loops not supported"); looping = true; @@ -705,10 +736,7 @@ void JitCompiler::Compile_LOOP(Instruction instr) { looping = false; } -void JitCompiler::Compile_JMP(Instruction instr) { - ASSERT_MSG(instr.flow_control.dest_offset > *offset_ptr, "Backwards jumps (%d -> %d) not supported", - *offset_ptr, instr.flow_control.dest_offset.Value()); - +void JitShader::Compile_JMP(Instruction instr) { if (instr.opcode.Value() == OpCode::Id::JMPC) Compile_EvaluateCondition(instr); else if (instr.opcode.Value() == OpCode::Id::JMPU) @@ -718,30 +746,38 @@ void JitCompiler::Compile_JMP(Instruction instr) { bool inverted_condition = (instr.opcode.Value() == OpCode::Id::JMPU) && (instr.flow_control.num_instructions & 1); + FixupBranch b = J_CC(inverted_condition ? CC_Z : CC_NZ, true); + fixup_branches.push_back({ b, instr.flow_control.dest_offset }); +} - Compile_Block(instr.flow_control.dest_offset); +void JitShader::Compile_Block(unsigned end) { + while (program_counter < end) { + Compile_NextInstr(); + } +} + +void JitShader::Compile_Return() { + // Peek return offset on the stack and check if we're at that offset + MOV(64, R(RAX), MDisp(RSP, 8)); + CMP(32, R(RAX), Imm32(program_counter)); + // If so, jump back to before CALL + FixupBranch b = J_CC(CC_NZ, true); + RET(); SetJumpTarget(b); } -void JitCompiler::Compile_Block(unsigned end) { - // Save current offset pointer - unsigned* prev_offset_ptr = offset_ptr; - unsigned offset = *prev_offset_ptr; +void JitShader::Compile_NextInstr() { + if (std::binary_search(return_offsets.begin(), return_offsets.end(), program_counter)) { + Compile_Return(); + } - while (offset < end) - Compile_NextInstr(&offset); + ASSERT_MSG(code_ptr[program_counter] == nullptr, "Tried to compile already compiled shader location!"); + code_ptr[program_counter] = GetCodePtr(); - // Restore current offset pointer - offset_ptr = prev_offset_ptr; - *offset_ptr = offset; -} + Instruction instr = GetVertexShaderInstruction(program_counter++); -void JitCompiler::Compile_NextInstr(unsigned* offset) { - offset_ptr = offset; - - Instruction instr = *(Instruction*)&g_state.vs.program_code[(*offset_ptr)++]; OpCode::Id opcode = instr.opcode.Value(); auto instr_func = instr_table[static_cast<unsigned>(opcode)]; @@ -755,9 +791,37 @@ void JitCompiler::Compile_NextInstr(unsigned* offset) { } } -CompiledShader* JitCompiler::Compile() { - const u8* start = GetCodePtr(); - unsigned offset = g_state.regs.vs.main_offset; +void JitShader::FindReturnOffsets() { + return_offsets.clear(); + + for (size_t offset = 0; offset < g_state.vs.program_code.size(); ++offset) { + Instruction instr = GetVertexShaderInstruction(offset); + + switch (instr.opcode.Value()) { + case OpCode::Id::CALL: + case OpCode::Id::CALLC: + case OpCode::Id::CALLU: + return_offsets.push_back(instr.flow_control.dest_offset + instr.flow_control.num_instructions); + break; + default: + break; + } + } + + // Sort for efficient binary search later + std::sort(return_offsets.begin(), return_offsets.end()); +} + +void JitShader::Compile() { + // Reset flow control state + program = (CompiledShader*)GetCodePtr(); + program_counter = 0; + looping = false; + code_ptr.fill(nullptr); + fixup_branches.clear(); + + // Find all `CALL` instructions and identify return locations + FindReturnOffsets(); // The stack pointer is 8 modulo 16 at the entry of a procedure ABI_PushRegistersAndAdjustStack(ABI_ALL_CALLEE_SAVED, 8); @@ -780,21 +844,31 @@ CompiledShader* JitCompiler::Compile() { MOV(PTRBITS, R(RAX), ImmPtr(&neg)); MOVAPS(NEGBIT, MatR(RAX)); - looping = false; + // Jump to start of the shader program + JMPptr(R(ABI_PARAM2)); + + // Compile entire program + Compile_Block(static_cast<unsigned>(g_state.vs.program_code.size())); - while (offset < g_state.vs.program_code.size()) { - Compile_NextInstr(&offset); + // Set the target for any incomplete branches now that the entire shader program has been emitted + for (const auto& branch : fixup_branches) { + SetJumpTarget(branch.first, code_ptr[branch.second]); } - return (CompiledShader*)start; -} + // Free memory that's no longer needed + return_offsets.clear(); + return_offsets.shrink_to_fit(); + fixup_branches.clear(); + fixup_branches.shrink_to_fit(); + + uintptr_t size = reinterpret_cast<uintptr_t>(GetCodePtr()) - reinterpret_cast<uintptr_t>(program); + ASSERT_MSG(size <= MAX_SHADER_SIZE, "Compiled a shader that exceeds the allocated size!"); -JitCompiler::JitCompiler() { - AllocCodeSpace(jit_cache_size); + LOG_DEBUG(HW_GPU, "Compiled shader size=%lu", size); } -void JitCompiler::Clear() { - ClearCodeSpace(); +JitShader::JitShader() { + AllocCodeSpace(MAX_SHADER_SIZE); } } // namespace Shader diff --git a/src/video_core/shader/shader_jit_x64.h b/src/video_core/shader/shader_jit_x64.h index 5357c964b..30aa7ff30 100644 --- a/src/video_core/shader/shader_jit_x64.h +++ b/src/video_core/shader/shader_jit_x64.h @@ -4,11 +4,17 @@ #pragma once +#include <array> +#include <cstddef> +#include <utility> +#include <vector> + #include <nihstro/shader_bytecode.h> +#include "common/bit_set.h" +#include "common/common_types.h" #include "common/x64/emitter.h" -#include "video_core/pica.h" #include "video_core/shader/shader.h" using nihstro::Instruction; @@ -19,24 +25,22 @@ namespace Pica { namespace Shader { -/// Memory needed to be available to compile the next shader (otherwise, clear the cache) -constexpr size_t jit_shader_size = 1024 * 512; -/// Memory allocated for the JIT code space cache -constexpr size_t jit_cache_size = 1024 * 1024 * 8; - -using CompiledShader = void(void* registers); +/// Memory allocated for each compiled shader (64Kb) +constexpr size_t MAX_SHADER_SIZE = 1024 * 64; /** * This class implements the shader JIT compiler. It recompiles a Pica shader program into x86_64 * code that can be executed on the host machine directly. */ -class JitCompiler : public Gen::XCodeBlock { +class JitShader : public Gen::XCodeBlock { public: - JitCompiler(); + JitShader(); - CompiledShader* Compile(); + void Run(void* registers, unsigned offset) const { + program(registers, code_ptr[offset]); + } - void Clear(); + void Compile(); void Compile_ADD(Instruction instr); void Compile_DP3(Instruction instr); @@ -66,8 +70,9 @@ public: void Compile_MAD(Instruction instr); private: + void Compile_Block(unsigned end); - void Compile_NextInstr(unsigned* offset); + void Compile_NextInstr(); void Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, Gen::X64Reg dest); void Compile_DestEnable(Instruction instr, Gen::X64Reg dest); @@ -81,13 +86,39 @@ private: void Compile_EvaluateCondition(Instruction instr); void Compile_UniformCondition(Instruction instr); + /** + * Emits the code to conditionally return from a subroutine envoked by the `CALL` instruction. + */ + void Compile_Return(); + BitSet32 PersistentCallerSavedRegs(); - /// Pointer to the variable that stores the current Pica code offset. Used to handle nested code blocks. - unsigned* offset_ptr = nullptr; + /** + * Assertion evaluated at compile-time, but only triggered if executed at runtime. + * @param msg Message to be logged if the assertion fails. + */ + void Compile_Assert(bool condition, const char* msg); + + /** + * Analyzes the entire shader program for `CALL` instructions before emitting any code, + * identifying the locations where a return needs to be inserted. + */ + void FindReturnOffsets(); + + /// Mapping of Pica VS instructions to pointers in the emitted code + std::array<const u8*, 1024> code_ptr; + + /// Offsets in code where a return needs to be inserted + std::vector<unsigned> return_offsets; + + unsigned program_counter = 0; ///< Offset of the next instruction to decode + bool looping = false; ///< True if compiling a loop, used to check for nested loops + + /// Branches that need to be fixed up once the entire shader program is compiled + std::vector<std::pair<Gen::FixupBranch, unsigned>> fixup_branches; - /// Set to true if currently in a loop, used to check for the existence of nested loops - bool looping = false; + using CompiledShader = void(void* registers, const u8* start_addr); + CompiledShader* program = nullptr; }; } // Shader diff --git a/src/video_core/swrasterizer.h b/src/video_core/swrasterizer.h index 9a9a76d7a..0a028b774 100644 --- a/src/video_core/swrasterizer.h +++ b/src/video_core/swrasterizer.h @@ -8,19 +8,23 @@ #include "video_core/rasterizer_interface.h" +namespace Pica { +namespace Shader { +struct OutputVertex; +} +} + namespace VideoCore { class SWRasterizer : public RasterizerInterface { - void InitObjects() override {} - void Reset() override {} void AddTriangle(const Pica::Shader::OutputVertex& v0, const Pica::Shader::OutputVertex& v1, const Pica::Shader::OutputVertex& v2) override; void DrawTriangles() override {} - void FlushFramebuffer() override {} void NotifyPicaRegisterChanged(u32 id) override {} + void FlushAll() override {} void FlushRegion(PAddr addr, u32 size) override {} - void InvalidateRegion(PAddr addr, u32 size) override {} + void FlushAndInvalidateRegion(PAddr addr, u32 size) override {} }; } diff --git a/src/video_core/utils.cpp b/src/video_core/utils.cpp deleted file mode 100644 index 6e1ff5cf4..000000000 --- a/src/video_core/utils.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <cstdio> -#include <cstring> - -#include "video_core/utils.h" - -namespace VideoCore { - -/** - * Dumps a texture to TGA - * @param filename String filename to dump texture to - * @param width Width of texture in pixels - * @param height Height of texture in pixels - * @param raw_data Raw RGBA8 texture data to dump - * @todo This should be moved to some general purpose/common code - */ -void DumpTGA(std::string filename, short width, short height, u8* raw_data) { - TGAHeader hdr = {0, 0, 2, 0, 0, 0, 0, width, height, 24, 0}; - FILE* fout = fopen(filename.c_str(), "wb"); - - fwrite(&hdr, sizeof(TGAHeader), 1, fout); - - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - putc(raw_data[(3 * (y * width)) + (3 * x) + 0], fout); // b - putc(raw_data[(3 * (y * width)) + (3 * x) + 1], fout); // g - putc(raw_data[(3 * (y * width)) + (3 * x) + 2], fout); // r - } - } - - fclose(fout); -} -} // namespace diff --git a/src/video_core/utils.h b/src/video_core/utils.h index 4fa60a10e..7ce83a055 100644 --- a/src/video_core/utils.h +++ b/src/video_core/utils.h @@ -4,37 +4,10 @@ #pragma once -#include <string> - #include "common/common_types.h" namespace VideoCore { -/// Structure for the TGA texture format (for dumping) -struct TGAHeader { - char idlength; - char colormaptype; - char datatypecode; - short int colormaporigin; - short int colormaplength; - short int x_origin; - short int y_origin; - short width; - short height; - char bitsperpixel; - char imagedescriptor; -}; - -/** - * Dumps a texture to TGA - * @param filename String filename to dump texture to - * @param width Width of texture in pixels - * @param height Height of texture in pixels - * @param raw_data Raw RGBA8 texture data to dump - * @todo This should be moved to some general purpose/common code - */ -void DumpTGA(std::string filename, short width, short height, u8* raw_data); - /** * Interleave the lower 3 bits of each coordinate to get the intra-block offsets, which are * arranged in a Z-order curve. More details on the bit manipulation at: diff --git a/src/video_core/vertex_loader.cpp b/src/video_core/vertex_loader.cpp new file mode 100644 index 000000000..21ae52949 --- /dev/null +++ b/src/video_core/vertex_loader.cpp @@ -0,0 +1,140 @@ +#include <memory> + +#include <boost/range/algorithm/fill.hpp> + +#include "common/assert.h" +#include "common/alignment.h" +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/vector_math.h" + +#include "core/memory.h" + +#include "video_core/debug_utils/debug_utils.h" +#include "video_core/pica.h" +#include "video_core/pica_state.h" +#include "video_core/pica_types.h" +#include "video_core/shader/shader.h" +#include "video_core/vertex_loader.h" + +namespace Pica { + +void VertexLoader::Setup(const Pica::Regs& regs) { + const auto& attribute_config = regs.vertex_attributes; + num_total_attributes = attribute_config.GetNumTotalAttributes(); + + boost::fill(vertex_attribute_sources, 0xdeadbeef); + + for (int i = 0; i < 16; i++) { + vertex_attribute_is_default[i] = attribute_config.IsDefaultAttribute(i); + } + + // Setup attribute data from loaders + for (int loader = 0; loader < 12; ++loader) { + const auto& loader_config = attribute_config.attribute_loaders[loader]; + + u32 offset = 0; + + // TODO: What happens if a loader overwrites a previous one's data? + for (unsigned component = 0; component < loader_config.component_count; ++component) { + if (component >= 12) { + LOG_ERROR(HW_GPU, "Overflow in the vertex attribute loader %u trying to load component %u", loader, component); + continue; + } + + u32 attribute_index = loader_config.GetComponent(component); + if (attribute_index < 12) { + offset = Common::AlignUp(offset, attribute_config.GetElementSizeInBytes(attribute_index)); + vertex_attribute_sources[attribute_index] = loader_config.data_offset + offset; + vertex_attribute_strides[attribute_index] = static_cast<u32>(loader_config.byte_count); + vertex_attribute_formats[attribute_index] = attribute_config.GetFormat(attribute_index); + vertex_attribute_elements[attribute_index] = attribute_config.GetNumElements(attribute_index); + offset += attribute_config.GetStride(attribute_index); + } else if (attribute_index < 16) { + // Attribute ids 12, 13, 14 and 15 signify 4, 8, 12 and 16-byte paddings, respectively + offset = Common::AlignUp(offset, 4); + offset += (attribute_index - 11) * 4; + } else { + UNREACHABLE(); // This is truly unreachable due to the number of bits for each component + } + } + } +} + +void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, DebugUtils::MemoryAccessTracker& memory_accesses) { + for (int i = 0; i < num_total_attributes; ++i) { + if (vertex_attribute_elements[i] != 0) { + // Load per-vertex data from the loader arrays + u32 source_addr = base_address + vertex_attribute_sources[i] + vertex_attribute_strides[i] * vertex; + + if (g_debug_context && Pica::g_debug_context->recorder) { + memory_accesses.AddAccess(source_addr, vertex_attribute_elements[i] * ( + (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::FLOAT) ? 4 + : (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::SHORT) ? 2 : 1)); + } + + switch (vertex_attribute_formats[i]) { + case Regs::VertexAttributeFormat::BYTE: + { + const s8* srcdata = reinterpret_cast<const s8*>(Memory::GetPhysicalPointer(source_addr)); + for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { + input.attr[i][comp] = float24::FromFloat32(srcdata[comp]); + } + break; + } + case Regs::VertexAttributeFormat::UBYTE: + { + const u8* srcdata = reinterpret_cast<const u8*>(Memory::GetPhysicalPointer(source_addr)); + for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { + input.attr[i][comp] = float24::FromFloat32(srcdata[comp]); + } + break; + } + case Regs::VertexAttributeFormat::SHORT: + { + const s16* srcdata = reinterpret_cast<const s16*>(Memory::GetPhysicalPointer(source_addr)); + for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { + input.attr[i][comp] = float24::FromFloat32(srcdata[comp]); + } + break; + } + case Regs::VertexAttributeFormat::FLOAT: + { + const float* srcdata = reinterpret_cast<const float*>(Memory::GetPhysicalPointer(source_addr)); + for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { + input.attr[i][comp] = float24::FromFloat32(srcdata[comp]); + } + break; + } + } + + // Default attribute values set if array elements have < 4 components. This + // is *not* carried over from the default attribute settings even if they're + // enabled for this attribute. + for (unsigned int comp = vertex_attribute_elements[i]; comp < 4; ++comp) { + input.attr[i][comp] = comp == 3 ? float24::FromFloat32(1.0f) : float24::FromFloat32(0.0f); + } + + LOG_TRACE(HW_GPU, "Loaded %d components of attribute %x for vertex %x (index %x) from 0x%08x + 0x%08x + 0x%04x: %f %f %f %f", + vertex_attribute_elements[i], i, vertex, index, + base_address, + vertex_attribute_sources[i], + vertex_attribute_strides[i] * vertex, + 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]; + 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(), + input.attr[i][2].ToFloat32(), input.attr[i][3].ToFloat32()); + } else { + // TODO(yuriks): In this case, no data gets loaded and the vertex + // remains with the last value it had. This isn't currently maintained + // as global state, however, and so won't work in Citra yet. + } + } +} + +} // namespace Pica diff --git a/src/video_core/vertex_loader.h b/src/video_core/vertex_loader.h new file mode 100644 index 000000000..becf5a403 --- /dev/null +++ b/src/video_core/vertex_loader.h @@ -0,0 +1,33 @@ +#pragma once + +#include "common/common_types.h" + +#include "video_core/pica.h" + +namespace Pica { + +namespace DebugUtils { +class MemoryAccessTracker; +} + +namespace Shader { +class InputVertex; +} + +class VertexLoader { +public: + 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; +}; + +} // namespace Pica diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index ee5e50df1..c9975876d 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -4,13 +4,8 @@ #include <memory> -#include "common/emu_window.h" -#include "common/make_unique.h" #include "common/logging/log.h" -#include "core/core.h" -#include "core/settings.h" - #include "video_core/pica.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" @@ -26,13 +21,14 @@ std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin std::atomic<bool> g_hw_renderer_enabled; std::atomic<bool> g_shader_jit_enabled; +std::atomic<bool> g_scaled_resolution_enabled; /// Initialize the video core bool Init(EmuWindow* emu_window) { Pica::Init(); g_emu_window = emu_window; - g_renderer = Common::make_unique<RendererOpenGL>(); + g_renderer = std::make_unique<RendererOpenGL>(); g_renderer->SetWindow(g_emu_window); if (g_renderer->Init()) { LOG_DEBUG(Render, "initialized OK"); diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index bca67fb8c..30267489e 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -36,6 +36,7 @@ extern EmuWindow* g_emu_window; ///< Emu window // TODO: Wrap these in a user settings struct along with any other graphics settings (often set from qt ui) extern std::atomic<bool> g_hw_renderer_enabled; extern std::atomic<bool> g_shader_jit_enabled; +extern std::atomic<bool> g_scaled_resolution_enabled; /// Start the video core void Start(); |