summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitmodules6
-rwxr-xr-x.travis-build.sh9
-rwxr-xr-x.travis-deps.sh5
-rw-r--r--CMakeLists.txt22
-rw-r--r--appveyor.yml9
m---------externals/boost0
m---------externals/catch0
-rw-r--r--externals/microprofile/microprofileui.h7
m---------externals/soundtouch0
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/audio_core/CMakeLists.txt27
-rw-r--r--src/audio_core/audio_core.cpp46
-rw-r--r--src/audio_core/audio_core.h7
-rw-r--r--src/audio_core/hle/common.h11
-rw-r--r--src/audio_core/hle/dsp.cpp128
-rw-r--r--src/audio_core/hle/dsp.h40
-rw-r--r--src/audio_core/hle/filter.h1
-rw-r--r--src/audio_core/hle/mixers.cpp201
-rw-r--r--src/audio_core/hle/mixers.h63
-rw-r--r--src/audio_core/hle/pipe.cpp41
-rw-r--r--src/audio_core/hle/pipe.h16
-rw-r--r--src/audio_core/hle/source.cpp320
-rw-r--r--src/audio_core/hle/source.h144
-rw-r--r--src/audio_core/interpolate.cpp85
-rw-r--r--src/audio_core/interpolate.h41
-rw-r--r--src/audio_core/null_sink.h29
-rw-r--r--src/audio_core/sdl2_sink.cpp126
-rw-r--r--src/audio_core/sdl2_sink.h30
-rw-r--r--src/audio_core/sink.h2
-rw-r--r--src/audio_core/sink_details.cpp25
-rw-r--r--src/audio_core/sink_details.h27
-rw-r--r--src/audio_core/time_stretch.cpp144
-rw-r--r--src/audio_core/time_stretch.h57
-rw-r--r--src/citra/CMakeLists.txt2
-rw-r--r--src/citra/citra.cpp38
-rw-r--r--src/citra/config.cpp6
-rw-r--r--src/citra/default_ini.h9
-rw-r--r--src/citra/emu_window/emu_window_sdl2.cpp7
-rw-r--r--src/citra_qt/CMakeLists.txt21
-rw-r--r--src/citra_qt/bootmanager.cpp2
-rw-r--r--src/citra_qt/config.cpp91
-rw-r--r--src/citra_qt/configure.ui108
-rw-r--r--src/citra_qt/configure_audio.cpp44
-rw-r--r--src/citra_qt/configure_audio.h27
-rw-r--r--src/citra_qt/configure_audio.ui48
-rw-r--r--src/citra_qt/configure_debug.cpp31
-rw-r--r--src/citra_qt/configure_debug.h29
-rw-r--r--src/citra_qt/configure_debug.ui102
-rw-r--r--src/citra_qt/configure_dialog.cpp30
-rw-r--r--src/citra_qt/configure_dialog.h29
-rw-r--r--src/citra_qt/configure_general.cpp39
-rw-r--r--src/citra_qt/configure_general.h29
-rw-r--r--src/citra_qt/configure_general.ui173
-rw-r--r--src/citra_qt/debugger/graphics_breakpoints.cpp6
-rw-r--r--src/citra_qt/debugger/graphics_framebuffer.cpp6
-rw-r--r--src/citra_qt/debugger/graphics_tracing.cpp6
-rw-r--r--src/citra_qt/debugger/graphics_vertex_shader.cpp8
-rw-r--r--src/citra_qt/debugger/profiler.cpp55
-rw-r--r--src/citra_qt/debugger/profiler.h3
-rw-r--r--src/citra_qt/game_list.cpp27
-rw-r--r--src/citra_qt/game_list.h6
-rw-r--r--src/citra_qt/game_list_p.h106
-rw-r--r--src/citra_qt/hotkeys.cpp54
-rw-r--r--src/citra_qt/hotkeys.h8
-rw-r--r--src/citra_qt/hotkeys.ui47
-rw-r--r--src/citra_qt/main.cpp184
-rw-r--r--src/citra_qt/main.h8
-rw-r--r--src/citra_qt/main.ui51
-rw-r--r--src/citra_qt/ui_settings.cpp11
-rw-r--r--src/citra_qt/ui_settings.h47
-rw-r--r--src/citra_qt/util/util.cpp2
-rw-r--r--src/common/CMakeLists.txt1
-rw-r--r--src/common/assert.h2
-rw-r--r--src/common/bit_field.h2
-rw-r--r--src/common/bit_set.h3
-rw-r--r--src/common/code_block.h6
-rw-r--r--src/common/common_funcs.h4
-rw-r--r--src/common/file_util.cpp58
-rw-r--r--src/common/file_util.h41
-rw-r--r--src/common/logging/backend.cpp1
-rw-r--r--src/common/logging/log.h3
-rw-r--r--src/common/microprofile.h4
-rw-r--r--src/common/microprofileui.h3
-rw-r--r--src/common/profiler.cpp82
-rw-r--r--src/common/profiler.h152
-rw-r--r--src/common/profiler_reporting.h27
-rw-r--r--src/common/swap.h68
-rw-r--r--src/common/thread.h46
-rw-r--r--src/common/x64/emitter.cpp28
-rw-r--r--src/common/x64/emitter.h4
-rw-r--r--src/core/CMakeLists.txt4
-rw-r--r--src/core/arm/arm_interface.h1
-rw-r--r--src/core/arm/dyncom/arm_dyncom.cpp2
-rw-r--r--src/core/arm/dyncom/arm_dyncom_interpreter.cpp24
-rw-r--r--src/core/core.cpp2
-rw-r--r--src/core/gdbstub/gdbstub.cpp30
-rw-r--r--src/core/hle/applets/applet.h1
-rw-r--r--src/core/hle/applets/mii_selector.cpp29
-rw-r--r--src/core/hle/applets/mii_selector.h50
-rw-r--r--src/core/hle/applets/swkbd.cpp24
-rw-r--r--src/core/hle/applets/swkbd.h7
-rw-r--r--src/core/hle/config_mem.cpp7
-rw-r--r--src/core/hle/function_wrappers.h3
-rw-r--r--src/core/hle/hle.cpp22
-rw-r--r--src/core/hle/hle.h4
-rw-r--r--src/core/hle/kernel/memory.cpp5
-rw-r--r--src/core/hle/kernel/process.cpp2
-rw-r--r--src/core/hle/kernel/process.h9
-rw-r--r--src/core/hle/kernel/shared_memory.cpp177
-rw-r--r--src/core/hle/kernel/shared_memory.h48
-rw-r--r--src/core/hle/kernel/thread.cpp89
-rw-r--r--src/core/hle/kernel/thread.h4
-rw-r--r--src/core/hle/result.h3
-rw-r--r--src/core/hle/service/ac_u.cpp26
-rw-r--r--src/core/hle/service/act_a.cpp26
-rw-r--r--src/core/hle/service/act_a.h23
-rw-r--r--src/core/hle/service/act_u.cpp3
-rw-r--r--src/core/hle/service/am/am.cpp2
-rw-r--r--src/core/hle/service/apt/apt.cpp58
-rw-r--r--src/core/hle/service/apt/apt.h15
-rw-r--r--src/core/hle/service/apt/bcfnt/bcfnt.cpp71
-rw-r--r--src/core/hle/service/apt/bcfnt/bcfnt.h87
-rw-r--r--src/core/hle/service/cfg/cfg.cpp4
-rw-r--r--src/core/hle/service/cfg/cfg.h13
-rw-r--r--src/core/hle/service/csnd_snd.cpp13
-rw-r--r--src/core/hle/service/dsp_dsp.cpp199
-rw-r--r--src/core/hle/service/dsp_dsp.h19
-rw-r--r--src/core/hle/service/fs/archive.cpp1
-rw-r--r--src/core/hle/service/fs/fs_user.cpp2
-rw-r--r--src/core/hle/service/gsp_gpu.cpp75
-rw-r--r--src/core/hle/service/gsp_gpu.h1
-rw-r--r--src/core/hle/service/hid/hid.cpp5
-rw-r--r--src/core/hle/service/ir/ir.cpp5
-rw-r--r--src/core/hle/service/ndm/ndm.cpp197
-rw-r--r--src/core/hle/service/ndm/ndm.h216
-rw-r--r--src/core/hle/service/ndm/ndm_u.cpp34
-rw-r--r--src/core/hle/service/service.cpp2
-rw-r--r--src/core/hle/service/soc_u.cpp100
-rw-r--r--src/core/hle/service/y2r_u.cpp490
-rw-r--r--src/core/hle/service/y2r_u.h20
-rw-r--r--src/core/hle/shared_page.cpp3
-rw-r--r--src/core/hle/shared_page.h6
-rw-r--r--src/core/hle/svc.cpp70
-rw-r--r--src/core/hw/gpu.cpp327
-rw-r--r--src/core/hw/gpu.h4
-rw-r--r--src/core/hw/lcd.h2
-rw-r--r--src/core/hw/y2r.cpp2
-rw-r--r--src/core/loader/3dsx.cpp39
-rw-r--r--src/core/loader/3dsx.h9
-rw-r--r--src/core/loader/loader.cpp53
-rw-r--r--src/core/loader/loader.h57
-rw-r--r--src/core/loader/ncch.cpp31
-rw-r--r--src/core/loader/ncch.h7
-rw-r--r--src/core/memory.cpp140
-rw-r--r--src/core/memory.h22
-rw-r--r--src/core/settings.cpp19
-rw-r--r--src/core/settings.h9
-rw-r--r--src/core/tracer/recorder.cpp24
-rw-r--r--src/core/tracer/recorder.h1
-rw-r--r--src/tests/CMakeLists.txt16
-rw-r--r--src/tests/tests.cpp9
-rw-r--r--src/video_core/CMakeLists.txt3
-rw-r--r--src/video_core/clipper.cpp17
-rw-r--r--src/video_core/command_processor.cpp189
-rw-r--r--src/video_core/debug_utils/debug_utils.cpp279
-rw-r--r--src/video_core/debug_utils/debug_utils.h91
-rw-r--r--src/video_core/pica.cpp7
-rw-r--r--src/video_core/pica.h58
-rw-r--r--src/video_core/pica_state.h9
-rw-r--r--src/video_core/pica_types.h1
-rw-r--r--src/video_core/primitive_assembly.cpp5
-rw-r--r--src/video_core/rasterizer.cpp175
-rw-r--r--src/video_core/rasterizer_interface.h31
-rw-r--r--src/video_core/renderer_base.cpp5
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp962
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h378
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp712
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.h221
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp188
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.h2
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.cpp3
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.h1
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp75
-rw-r--r--src/video_core/renderer_opengl/gl_state.h28
-rw-r--r--src/video_core/renderer_opengl/pica_to_gl.h27
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp149
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h47
-rw-r--r--src/video_core/shader/shader.cpp116
-rw-r--r--src/video_core/shader/shader.h137
-rw-r--r--src/video_core/shader/shader_interpreter.cpp86
-rw-r--r--src/video_core/shader/shader_interpreter.h6
-rw-r--r--src/video_core/shader/shader_jit_x64.cpp344
-rw-r--r--src/video_core/shader/shader_jit_x64.h63
-rw-r--r--src/video_core/swrasterizer.h12
-rw-r--r--src/video_core/utils.cpp36
-rw-r--r--src/video_core/utils.h27
-rw-r--r--src/video_core/vertex_loader.cpp146
-rw-r--r--src/video_core/vertex_loader.h40
-rw-r--r--src/video_core/video_core.cpp5
-rw-r--r--src/video_core/video_core.h1
200 files changed, 8236 insertions, 3255 deletions
diff --git a/.gitmodules b/.gitmodules
index 598e4c64d..1f0b80768 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -7,3 +7,9 @@
[submodule "nihstro"]
path = externals/nihstro
url = https://github.com/neobrain/nihstro.git
+[submodule "soundtouch"]
+ path = externals/soundtouch
+ url = https://github.com/citra-emu/ext-soundtouch.git
+[submodule "catch"]
+ path = externals/catch
+ url = https://github.com/philsquared/Catch.git
diff --git a/.travis-build.sh b/.travis-build.sh
index e06a4299b..511df04ac 100755
--- a/.travis-build.sh
+++ b/.travis-build.sh
@@ -18,9 +18,16 @@ if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
mkdir build && cd build
cmake -DCITRA_FORCE_QT4=ON ..
make -j4
+
+ ctest -VV -C Release
elif [ "$TRAVIS_OS_NAME" = "osx" ]; then
+ set -o pipefail
+
export Qt5_DIR=$(brew --prefix)/opt/qt5
+
mkdir build && cd build
cmake .. -GXcode
- xcodebuild -configuration Release | xcpretty -c && exit ${PIPESTATUS[0]}
+ xcodebuild -configuration Release | xcpretty -c
+
+ ctest -VV -C Release
fi
diff --git a/.travis-deps.sh b/.travis-deps.sh
index c7bb7e785..4a79feb70 100755
--- a/.travis-deps.sh
+++ b/.travis-deps.sh
@@ -9,7 +9,7 @@ if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
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/CMakeLists.txt b/CMakeLists.txt
index 3a0a161e7..f7b0af115 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++1y -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
@@ -148,12 +152,15 @@ if (ENABLE_SDL2)
download_bundled_external("sdl2/" ${SDL2_VER} SDL2_PREFIX)
endif()
+ set(SDL2_FOUND YES)
set(SDL2_INCLUDE_DIR "${SDL2_PREFIX}/include" CACHE PATH "Path to SDL2 headers")
set(SDL2_LIBRARY "${SDL2_PREFIX}/lib/x64/SDL2.lib" CACHE PATH "Path to SDL2 library")
set(SDL2_DLL_DIR "${SDL2_PREFIX}/lib/x64/" CACHE PATH "Path to SDL2.dll")
else()
find_package(SDL2 REQUIRED)
endif()
+else()
+ set(SDL2_FOUND NO)
endif()
IF (APPLE)
@@ -245,6 +252,11 @@ if(ENABLE_QT)
include_directories(externals/qhexedit)
add_subdirectory(externals/qhexedit)
endif()
+
+add_subdirectory(externals/soundtouch)
+
+enable_testing()
+
add_subdirectory(src)
# Install freedesktop.org metadata files, following those specifications:
diff --git a/appveyor.yml b/appveyor.yml
index 1edd7041f..fa4134384 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -22,7 +22,14 @@ before_build:
- cmake -G "Visual Studio 14 2015 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 ..
- cd ..
-after_build:
+build:
+ project: build/citra.sln
+ parallel: true
+
+test_script:
+ - cd build && ctest -VV -C Release && cd ..
+
+on_success:
# copying the needed QT Dlls is now done post build. See the CMakeLists.txt file in the citra-qt folder
- ps: >
if (!"$env:APPVEYOR_PULL_REQUEST_TITLE" -and ("$env:APPVEYOR_REPO_BRANCH" -eq "master"))
diff --git a/externals/boost b/externals/boost
-Subproject d81b9269900ae183d0dc98403eea4c971590a80
+Subproject 2dcb9d979665b6aabb1635c617973e02914e60e
diff --git a/externals/catch b/externals/catch
new file mode 160000
+Subproject c984fc3ecde60b59efa2203e82261acac8ac850
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/CMakeLists.txt b/src/CMakeLists.txt
index de4fe716a..1e1245160 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -5,6 +5,7 @@ add_subdirectory(common)
add_subdirectory(core)
add_subdirectory(video_core)
add_subdirectory(audio_core)
+add_subdirectory(tests)
if (ENABLE_SDL2)
add_subdirectory(citra)
endif()
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 869da5e83..a72a907ef 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -3,7 +3,12 @@ set(SRCS
codec.cpp
hle/dsp.cpp
hle/filter.cpp
+ hle/mixers.cpp
hle/pipe.cpp
+ hle/source.cpp
+ interpolate.cpp
+ sink_details.cpp
+ time_stretch.cpp
)
set(HEADERS
@@ -12,10 +17,30 @@ set(HEADERS
hle/common.h
hle/dsp.h
hle/filter.h
+ hle/mixers.h
hle/pipe.h
+ hle/source.h
+ interpolate.h
+ null_sink.h
sink.h
+ sink_details.h
+ time_stretch.h
)
+include_directories(../../externals/soundtouch/include)
+
+if(SDL2_FOUND)
+ set(SRCS ${SRCS} sdl2_sink.cpp)
+ set(HEADERS ${HEADERS} sdl2_sink.h)
+ include_directories(${SDL2_INCLUDE_DIR})
+endif()
+
create_directory_groups(${SRCS} ${HEADERS})
-add_library(audio_core STATIC ${SRCS} ${HEADERS}) \ No newline at end of file
+add_library(audio_core STATIC ${SRCS} ${HEADERS})
+target_link_libraries(audio_core SoundTouch)
+
+if(SDL2_FOUND)
+ target_link_libraries(audio_core ${SDL2_LIBRARY})
+ set_property(TARGET audio_core APPEND PROPERTY COMPILE_DEFINITIONS HAVE_SDL2)
+endif()
diff --git a/src/audio_core/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/hle/common.h b/src/audio_core/hle/common.h
index 37d441eb2..596b67eaf 100644
--- a/src/audio_core/hle/common.h
+++ b/src/audio_core/hle/common.h
@@ -7,18 +7,19 @@
#include <algorithm>
#include <array>
-#include "audio_core/audio_core.h"
-
#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>, AudioCore::samples_per_frame>;
+using StereoFrame16 = std::array<std::array<s16, 2>, samples_per_frame>;
/// The DSP is quadraphonic internally.
-using QuadFrame32 = std::array<std::array<s32, 4>, AudioCore::samples_per_frame>;
+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.
@@ -26,7 +27,7 @@ using QuadFrame32 = std::array<std::array<s32, 4>, AudioCore::samples_per_fram
*/
template<typename FrameT, typename FilterT>
void FilterFrame(FrameT& frame, FilterT& filter) {
- std::transform(frame.begin(), frame.end(), frame.begin(), [&filter](const typename FrameT::value_type& sample) {
+ std::transform(frame.begin(), frame.end(), frame.begin(), [&filter](const auto& sample) {
return filter.ProcessSample(sample);
});
}
diff --git a/src/audio_core/hle/dsp.cpp b/src/audio_core/hle/dsp.cpp
index c89356edc..0640e1eff 100644
--- a/src/audio_core/hle/dsp.cpp
+++ b/src/audio_core/hle/dsp.cpp
@@ -2,40 +2,138 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include <memory>
+
#include "audio_core/hle/dsp.h"
+#include "audio_core/hle/mixers.h"
#include "audio_core/hle/pipe.h"
+#include "audio_core/hle/source.h"
+#include "audio_core/sink.h"
+#include "audio_core/time_stretch.h"
namespace DSP {
namespace HLE {
-SharedMemory g_region0;
-SharedMemory g_region1;
+// Region management
+
+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()];
+}
+
+// Audio processing and mixing
+
+static std::array<Source, num_sources> sources = {
+ Source(0), Source(1), Source(2), Source(3), Source(4), Source(5),
+ Source(6), Source(7), Source(8), Source(9), Source(10), Source(11),
+ Source(12), Source(13), Source(14), Source(15), Source(16), Source(17),
+ Source(18), Source(19), Source(20), Source(21), Source(22), Source(23)
+};
+static Mixers mixers;
+
+static StereoFrame16 GenerateCurrentFrame() {
+ SharedMemory& read = ReadRegion();
+ SharedMemory& write = WriteRegion();
+
+ std::array<QuadFrame32, 3> intermediate_mixes = {};
+
+ // Generate intermediate mixes
+ for (size_t i = 0; i < num_sources; i++) {
+ write.source_statuses.status[i] = sources[i].Tick(read.source_configurations.config[i], read.adpcm_coefficients.coeff[i]);
+ for (size_t mix = 0; mix < 3; mix++) {
+ sources[i].MixInto(intermediate_mixes[mix], mix);
+ }
+ }
+
+ // Generate final mix
+ write.dsp_status = mixers.Tick(read.dsp_configuration, read.intermediate_mix_samples, write.intermediate_mix_samples, intermediate_mixes);
+
+ StereoFrame16 output_frame = mixers.GetOutput();
+
+ // Write current output frame to the shared memory region
+ for (size_t samplei = 0; samplei < output_frame.size(); samplei++) {
+ for (size_t channeli = 0; channeli < output_frame[0].size(); channeli++) {
+ write.final_samples.pcm16[samplei][channeli] = s16_le(output_frame[samplei][channeli]);
+ }
+ }
+
+ return output_frame;
+}
+
+// Audio output
+
+static std::unique_ptr<AudioCore::Sink> sink;
+static AudioCore::TimeStretcher time_stretcher;
+
+static void OutputCurrentFrame(const StereoFrame16& frame) {
+ time_stretcher.AddSamples(&frame[0][0], frame.size());
+ sink->EnqueueSamples(time_stretcher.Process(sink->SamplesInQueue()));
+}
+
+// Public Interface
void Init() {
DSP::HLE::ResetPipes();
+
+ for (auto& source : sources) {
+ source.Reset();
+ }
+
+ mixers.Reset();
+
+ time_stretcher.Reset();
+ if (sink) {
+ time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate());
+ }
}
void Shutdown() {
+ time_stretcher.Flush();
+ while (true) {
+ std::vector<s16> residual_audio = time_stretcher.Process(sink->SamplesInQueue());
+ if (residual_audio.empty())
+ break;
+ sink->EnqueueSamples(residual_audio);
+ }
}
bool Tick() {
- return true;
-}
+ StereoFrame16 current_frame = {};
-SharedMemory& CurrentRegion() {
- // The region with the higher frame counter is chosen unless there is wraparound.
+ // TODO: Check dsp::DSP semaphore (which indicates emulated application has finished writing to shared memory region)
+ current_frame = GenerateCurrentFrame();
- if (g_region0.frame_counter == 0xFFFFu && g_region1.frame_counter != 0xFFFEu) {
- // Wraparound has occured.
- return g_region1;
- }
+ OutputCurrentFrame(current_frame);
- 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_);
+ time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate());
}
} // namespace HLE
diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/dsp.h
index c15ef0b7a..9275cd7de 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 {
@@ -27,13 +33,8 @@ namespace HLE {
// double-buffer. The frame counter is located as the very last u16 of each region and is incremented
// each audio tick.
-struct SharedMemory;
-
constexpr VAddr region0_base = 0x1FF50000;
-extern SharedMemory g_region0;
-
constexpr VAddr region1_base = 0x1FF70000;
-extern SharedMemory g_region1;
/**
* The DSP is native 16-bit. The DSP also appears to be big-endian. When reading 32-bit numbers from
@@ -164,9 +165,9 @@ struct SourceConfiguration {
float_le rate_multiplier;
enum class InterpolationMode : u8 {
- None = 0,
+ Polyphase = 0,
Linear = 1,
- Polyphase = 2
+ None = 2
};
InterpolationMode interpolation_mode;
@@ -305,7 +306,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);
@@ -313,14 +314,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);
@@ -413,7 +414,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);
@@ -427,7 +428,7 @@ ASSERT_DSP_STRUCT(DspStatus, 32);
/// Final mixed output in PCM16 stereo format, what you hear out of the speakers.
/// When the application writes to this region it has no effect.
struct FinalMixSamples {
- s16_le pcm16[2 * AudioCore::samples_per_frame];
+ s16_le pcm16[samples_per_frame][2];
};
ASSERT_DSP_STRUCT(FinalMixSamples, 640);
@@ -437,7 +438,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;
@@ -502,6 +503,8 @@ struct SharedMemory {
};
ASSERT_DSP_STRUCT(SharedMemory, 0x8000);
+extern std::array<SharedMemory, 2> g_regions;
+
// Structures must have an offset that is a multiple of two.
static_assert(offsetof(SharedMemory, frame_counter) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
static_assert(offsetof(SharedMemory, source_configurations) % 2 == 0, "Structures in DSP::HLE::SharedMemory must be 2-byte aligned");
@@ -535,8 +538,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.h b/src/audio_core/hle/filter.h
index 75738f600..43d2035cd 100644
--- a/src/audio_core/hle/filter.h
+++ b/src/audio_core/hle/filter.h
@@ -16,6 +16,7 @@ namespace HLE {
/// Preprocessing filters. There is an independent set of filters for each Source.
class SourceFilters final {
+public:
SourceFilters() { Reset(); }
/// Reset internal state.
diff --git a/src/audio_core/hle/mixers.cpp b/src/audio_core/hle/mixers.cpp
new file mode 100644
index 000000000..18335f7f0
--- /dev/null
+++ b/src/audio_core/hle/mixers.cpp
@@ -0,0 +1,201 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstddef>
+
+#include "audio_core/hle/common.h"
+#include "audio_core/hle/dsp.h"
+#include "audio_core/hle/mixers.h"
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/math_util.h"
+
+namespace DSP {
+namespace HLE {
+
+void Mixers::Reset() {
+ current_frame.fill({});
+ state = {};
+}
+
+DspStatus Mixers::Tick(DspConfiguration& config,
+ const IntermediateMixSamples& read_samples,
+ IntermediateMixSamples& write_samples,
+ const std::array<QuadFrame32, 3>& input)
+{
+ ParseConfig(config);
+
+ AuxReturn(read_samples);
+ AuxSend(write_samples, input);
+
+ MixCurrentFrame();
+
+ return GetCurrentStatus();
+}
+
+void Mixers::ParseConfig(DspConfiguration& config) {
+ if (!config.dirty_raw) {
+ return;
+ }
+
+ if (config.mixer1_enabled_dirty) {
+ config.mixer1_enabled_dirty.Assign(0);
+ state.mixer1_enabled = config.mixer1_enabled != 0;
+ LOG_TRACE(Audio_DSP, "mixers mixer1_enabled = %hu", config.mixer1_enabled);
+ }
+
+ if (config.mixer2_enabled_dirty) {
+ config.mixer2_enabled_dirty.Assign(0);
+ state.mixer2_enabled = config.mixer2_enabled != 0;
+ LOG_TRACE(Audio_DSP, "mixers mixer2_enabled = %hu", config.mixer2_enabled);
+ }
+
+ if (config.volume_0_dirty) {
+ config.volume_0_dirty.Assign(0);
+ state.intermediate_mixer_volume[0] = config.volume[0];
+ LOG_TRACE(Audio_DSP, "mixers volume[0] = %f", config.volume[0]);
+ }
+
+ if (config.volume_1_dirty) {
+ config.volume_1_dirty.Assign(0);
+ state.intermediate_mixer_volume[1] = config.volume[1];
+ LOG_TRACE(Audio_DSP, "mixers volume[1] = %f", config.volume[1]);
+ }
+
+ if (config.volume_2_dirty) {
+ config.volume_2_dirty.Assign(0);
+ state.intermediate_mixer_volume[2] = config.volume[2];
+ LOG_TRACE(Audio_DSP, "mixers volume[2] = %f", config.volume[2]);
+ }
+
+ if (config.output_format_dirty) {
+ config.output_format_dirty.Assign(0);
+ state.output_format = config.output_format;
+ LOG_TRACE(Audio_DSP, "mixers output_format = %zu", static_cast<size_t>(config.output_format));
+ }
+
+ if (config.headphones_connected_dirty) {
+ config.headphones_connected_dirty.Assign(0);
+ // Do nothing.
+ // (Note: Whether headphones are connected does affect coefficients used for surround sound.)
+ LOG_TRACE(Audio_DSP, "mixers headphones_connected=%hu", config.headphones_connected);
+ }
+
+ if (config.dirty_raw) {
+ LOG_DEBUG(Audio_DSP, "mixers remaining_dirty=%x", config.dirty_raw);
+ }
+
+ config.dirty_raw = 0;
+}
+
+static s16 ClampToS16(s32 value) {
+ return static_cast<s16>(MathUtil::Clamp(value, -32768, 32767));
+}
+
+static std::array<s16, 2> AddAndClampToS16(const std::array<s16, 2>& a, const std::array<s16, 2>& b) {
+ return {
+ ClampToS16(static_cast<s32>(a[0]) + static_cast<s32>(b[0])),
+ ClampToS16(static_cast<s32>(a[1]) + static_cast<s32>(b[1]))
+ };
+}
+
+void Mixers::DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& samples) {
+ // TODO(merry): Limiter. (Currently we're performing final mixing assuming a disabled limiter.)
+
+ switch (state.output_format) {
+ case OutputFormat::Mono:
+ std::transform(current_frame.begin(), current_frame.end(), samples.begin(), current_frame.begin(),
+ [gain](const std::array<s16, 2>& accumulator, const std::array<s32, 4>& sample) -> std::array<s16, 2> {
+ // Downmix to mono
+ s16 mono = ClampToS16(static_cast<s32>((gain * sample[0] + gain * sample[1] + gain * sample[2] + gain * sample[3]) / 2));
+ // Mix into current frame
+ return AddAndClampToS16(accumulator, { mono, mono });
+ });
+ return;
+
+ case OutputFormat::Surround:
+ // TODO(merry): Implement surround sound.
+ // fallthrough
+
+ case OutputFormat::Stereo:
+ std::transform(current_frame.begin(), current_frame.end(), samples.begin(), current_frame.begin(),
+ [gain](const std::array<s16, 2>& accumulator, const std::array<s32, 4>& sample) -> std::array<s16, 2> {
+ // Downmix to stereo
+ s16 left = ClampToS16(static_cast<s32>(gain * sample[0] + gain * sample[2]));
+ s16 right = ClampToS16(static_cast<s32>(gain * sample[1] + gain * sample[3]));
+ // Mix into current frame
+ return AddAndClampToS16(accumulator, { left, right });
+ });
+ return;
+ }
+
+ UNREACHABLE_MSG("Invalid output_format %zu", static_cast<size_t>(state.output_format));
+}
+
+void Mixers::AuxReturn(const IntermediateMixSamples& read_samples) {
+ // NOTE: read_samples.mix{1,2}.pcm32 annoyingly have their dimensions in reverse order to QuadFrame32.
+
+ if (state.mixer1_enabled) {
+ for (size_t sample = 0; sample < samples_per_frame; sample++) {
+ for (size_t channel = 0; channel < 4; channel++) {
+ state.intermediate_mix_buffer[1][sample][channel] = read_samples.mix1.pcm32[channel][sample];
+ }
+ }
+ }
+
+ if (state.mixer2_enabled) {
+ for (size_t sample = 0; sample < samples_per_frame; sample++) {
+ for (size_t channel = 0; channel < 4; channel++) {
+ state.intermediate_mix_buffer[2][sample][channel] = read_samples.mix2.pcm32[channel][sample];
+ }
+ }
+ }
+}
+
+void Mixers::AuxSend(IntermediateMixSamples& write_samples, const std::array<QuadFrame32, 3>& input) {
+ // NOTE: read_samples.mix{1,2}.pcm32 annoyingly have their dimensions in reverse order to QuadFrame32.
+
+ state.intermediate_mix_buffer[0] = input[0];
+
+ if (state.mixer1_enabled) {
+ for (size_t sample = 0; sample < samples_per_frame; sample++) {
+ for (size_t channel = 0; channel < 4; channel++) {
+ write_samples.mix1.pcm32[channel][sample] = input[1][sample][channel];
+ }
+ }
+ } else {
+ state.intermediate_mix_buffer[1] = input[1];
+ }
+
+ if (state.mixer2_enabled) {
+ for (size_t sample = 0; sample < samples_per_frame; sample++) {
+ for (size_t channel = 0; channel < 4; channel++) {
+ write_samples.mix2.pcm32[channel][sample] = input[2][sample][channel];
+ }
+ }
+ } else {
+ state.intermediate_mix_buffer[2] = input[2];
+ }
+}
+
+void Mixers::MixCurrentFrame() {
+ current_frame.fill({});
+
+ for (size_t mix = 0; mix < 3; mix++) {
+ DownmixAndMixIntoCurrentFrame(state.intermediate_mixer_volume[mix], state.intermediate_mix_buffer[mix]);
+ }
+
+ // TODO(merry): Compressor. (We currently assume a disabled compressor.)
+}
+
+DspStatus Mixers::GetCurrentStatus() const {
+ DspStatus status;
+ status.unknown = 0;
+ status.dropped_frames = 0;
+ return status;
+}
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/hle/mixers.h b/src/audio_core/hle/mixers.h
new file mode 100644
index 000000000..b52952eb5
--- /dev/null
+++ b/src/audio_core/hle/mixers.h
@@ -0,0 +1,63 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/hle/common.h"
+#include "audio_core/hle/dsp.h"
+
+namespace DSP {
+namespace HLE {
+
+class Mixers final {
+public:
+ Mixers() {
+ Reset();
+ }
+
+ void Reset();
+
+ DspStatus Tick(DspConfiguration& config,
+ const IntermediateMixSamples& read_samples,
+ IntermediateMixSamples& write_samples,
+ const std::array<QuadFrame32, 3>& input);
+
+ StereoFrame16 GetOutput() const {
+ return current_frame;
+ }
+
+private:
+ StereoFrame16 current_frame = {};
+
+ using OutputFormat = DspConfiguration::OutputFormat;
+
+ struct {
+ std::array<float, 3> intermediate_mixer_volume = {};
+
+ bool mixer1_enabled = false;
+ bool mixer2_enabled = false;
+ std::array<QuadFrame32, 3> intermediate_mix_buffer = {};
+
+ OutputFormat output_format = OutputFormat::Stereo;
+
+ } state;
+
+ /// INTERNAL: Update our internal state based on the current config.
+ void ParseConfig(DspConfiguration& config);
+ /// INTERNAL: Read samples from shared memory that have been modified by the ARM11.
+ void AuxReturn(const IntermediateMixSamples& read_samples);
+ /// INTERNAL: Write samples to shared memory for the ARM11 to modify.
+ void AuxSend(IntermediateMixSamples& write_samples, const std::array<QuadFrame32, 3>& input);
+ /// INTERNAL: Mix current_frame.
+ void MixCurrentFrame();
+ /// INTERNAL: Downmix from quadraphonic to stereo based on status.output_format and accumulate into current_frame.
+ void DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& samples);
+ /// INTERNAL: Generate DspStatus based on internal state.
+ DspStatus GetCurrentStatus() const;
+};
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/hle/pipe.cpp b/src/audio_core/hle/pipe.cpp
index 9381883b4..44dff1345 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,17 +29,24 @@ 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)];
+ if (length > UINT16_MAX) { // Can only read at most UINT16_MAX from the pipe
+ LOG_ERROR(Audio_DSP, "length of %u greater than max of %u", length, UINT16_MAX);
+ return {};
+ }
+
+ std::vector<u8>& data = pipe_data[pipe_index];
if (length > data.size()) {
- LOG_WARNING(Audio_DSP, "pipe_number = %u is out of data, application requested read of %u but %zu remain",
- pipe_number, length, data.size());
- 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 = static_cast<u32>(data.size());
}
if (length == 0)
@@ -49,16 +58,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);
@@ -86,11 +99,13 @@ static void AudioPipeWriteStructAddresses() {
};
// Begin with a u16 denoting the number of structs.
- WriteU16(DspPipe::Audio, struct_addresses.size());
+ WriteU16(DspPipe::Audio, static_cast<u16>(struct_addresses.size()));
// Then write the struct addresses.
for (u16 addr : struct_addresses) {
WriteU16(DspPipe::Audio, addr);
}
+ // 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 +160,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..b714c0496 100644
--- a/src/audio_core/hle/pipe.h
+++ b/src/audio_core/hle/pipe.h
@@ -19,15 +19,19 @@ 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.
- * @param pipe_number The Pipe ID
- * @param length How much data to request.
- * @return The data read from the pipe. The size of this vector can be less than the length requested.
+ * Reads `length` bytes from the DSP pipe identified with `pipe_number`.
+ * @note Can read up to the maximum value of a u16 in bytes (65,535).
+ * @note IF an error is encoutered with either an invalid `pipe_number` or `length` value, an empty vector will be returned.
+ * @note IF `length` is set to 0, an empty vector will be returned.
+ * @note IF `length` is greater than the amount of data available, this function will only read the available amount.
+ * @param pipe_number a `DspPipe`
+ * @param length the number of bytes to read. The max is 65,535 (max of u16).
+ * @returns a vector of bytes from the specified pipe. On error, will be empty.
*/
std::vector<u8> PipeRead(DspPipe pipe_number, u32 length);
diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp
new file mode 100644
index 000000000..30552fe26
--- /dev/null
+++ b/src/audio_core/hle/source.cpp
@@ -0,0 +1,320 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <array>
+
+#include "audio_core/codec.h"
+#include "audio_core/hle/common.h"
+#include "audio_core/hle/source.h"
+#include "audio_core/interpolate.h"
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+
+#include "core/memory.h"
+
+namespace DSP {
+namespace HLE {
+
+SourceStatus::Status Source::Tick(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]) {
+ ParseConfig(config, adpcm_coeffs);
+
+ if (state.enabled) {
+ GenerateFrame();
+ }
+
+ return GetCurrentStatus();
+}
+
+void Source::MixInto(QuadFrame32& dest, size_t intermediate_mix_id) const {
+ if (!state.enabled)
+ return;
+
+ const std::array<float, 4>& gains = state.gain.at(intermediate_mix_id);
+ for (size_t samplei = 0; samplei < samples_per_frame; samplei++) {
+ // Conversion from stereo (current_frame) to quadraphonic (dest) occurs here.
+ dest[samplei][0] += static_cast<s32>(gains[0] * current_frame[samplei][0]);
+ dest[samplei][1] += static_cast<s32>(gains[1] * current_frame[samplei][1]);
+ dest[samplei][2] += static_cast<s32>(gains[2] * current_frame[samplei][0]);
+ dest[samplei][3] += static_cast<s32>(gains[3] * current_frame[samplei][1]);
+ }
+}
+
+void Source::Reset() {
+ current_frame.fill({});
+ state = {};
+}
+
+void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]) {
+ if (!config.dirty_raw) {
+ return;
+ }
+
+ if (config.reset_flag) {
+ config.reset_flag.Assign(0);
+ Reset();
+ LOG_TRACE(Audio_DSP, "source_id=%zu reset", source_id);
+ }
+
+ if (config.partial_reset_flag) {
+ config.partial_reset_flag.Assign(0);
+ state.input_queue = std::priority_queue<Buffer, std::vector<Buffer>, BufferOrder>{};
+ LOG_TRACE(Audio_DSP, "source_id=%zu partial_reset", source_id);
+ }
+
+ if (config.enable_dirty) {
+ config.enable_dirty.Assign(0);
+ state.enabled = config.enable != 0;
+ LOG_TRACE(Audio_DSP, "source_id=%zu enable=%d", source_id, state.enabled);
+ }
+
+ if (config.sync_dirty) {
+ config.sync_dirty.Assign(0);
+ state.sync = config.sync;
+ LOG_TRACE(Audio_DSP, "source_id=%zu sync=%u", source_id, state.sync);
+ }
+
+ if (config.rate_multiplier_dirty) {
+ config.rate_multiplier_dirty.Assign(0);
+ state.rate_multiplier = config.rate_multiplier;
+ LOG_TRACE(Audio_DSP, "source_id=%zu rate=%f", source_id, state.rate_multiplier);
+
+ if (state.rate_multiplier <= 0) {
+ LOG_ERROR(Audio_DSP, "Was given an invalid rate multiplier: source_id=%zu rate=%f", source_id, state.rate_multiplier);
+ state.rate_multiplier = 1.0f;
+ // Note: Actual firmware starts producing garbage if this occurs.
+ }
+ }
+
+ if (config.adpcm_coefficients_dirty) {
+ config.adpcm_coefficients_dirty.Assign(0);
+ std::transform(adpcm_coeffs, adpcm_coeffs + state.adpcm_coeffs.size(), state.adpcm_coeffs.begin(),
+ [](const auto& coeff) { return static_cast<s16>(coeff); });
+ LOG_TRACE(Audio_DSP, "source_id=%zu adpcm update", source_id);
+ }
+
+ if (config.gain_0_dirty) {
+ config.gain_0_dirty.Assign(0);
+ std::transform(config.gain[0], config.gain[0] + state.gain[0].size(), state.gain[0].begin(),
+ [](const auto& coeff) { return static_cast<float>(coeff); });
+ LOG_TRACE(Audio_DSP, "source_id=%zu gain 0 update", source_id);
+ }
+
+ if (config.gain_1_dirty) {
+ config.gain_1_dirty.Assign(0);
+ std::transform(config.gain[1], config.gain[1] + state.gain[1].size(), state.gain[1].begin(),
+ [](const auto& coeff) { return static_cast<float>(coeff); });
+ LOG_TRACE(Audio_DSP, "source_id=%zu gain 1 update", source_id);
+ }
+
+ if (config.gain_2_dirty) {
+ config.gain_2_dirty.Assign(0);
+ std::transform(config.gain[2], config.gain[2] + state.gain[2].size(), state.gain[2].begin(),
+ [](const auto& coeff) { return static_cast<float>(coeff); });
+ LOG_TRACE(Audio_DSP, "source_id=%zu gain 2 update", source_id);
+ }
+
+ if (config.filters_enabled_dirty) {
+ config.filters_enabled_dirty.Assign(0);
+ state.filters.Enable(config.simple_filter_enabled.ToBool(), config.biquad_filter_enabled.ToBool());
+ LOG_TRACE(Audio_DSP, "source_id=%zu enable_simple=%hu enable_biquad=%hu",
+ source_id, config.simple_filter_enabled.Value(), config.biquad_filter_enabled.Value());
+ }
+
+ if (config.simple_filter_dirty) {
+ config.simple_filter_dirty.Assign(0);
+ state.filters.Configure(config.simple_filter);
+ LOG_TRACE(Audio_DSP, "source_id=%zu simple filter update", source_id);
+ }
+
+ if (config.biquad_filter_dirty) {
+ config.biquad_filter_dirty.Assign(0);
+ state.filters.Configure(config.biquad_filter);
+ LOG_TRACE(Audio_DSP, "source_id=%zu biquad filter update", source_id);
+ }
+
+ if (config.interpolation_dirty) {
+ config.interpolation_dirty.Assign(0);
+ state.interpolation_mode = config.interpolation_mode;
+ LOG_TRACE(Audio_DSP, "source_id=%zu interpolation_mode=%zu", source_id, static_cast<size_t>(state.interpolation_mode));
+ }
+
+ if (config.format_dirty || config.embedded_buffer_dirty) {
+ config.format_dirty.Assign(0);
+ state.format = config.format;
+ LOG_TRACE(Audio_DSP, "source_id=%zu format=%zu", source_id, static_cast<size_t>(state.format));
+ }
+
+ if (config.mono_or_stereo_dirty || config.embedded_buffer_dirty) {
+ config.mono_or_stereo_dirty.Assign(0);
+ state.mono_or_stereo = config.mono_or_stereo;
+ LOG_TRACE(Audio_DSP, "source_id=%zu mono_or_stereo=%zu", source_id, static_cast<size_t>(state.mono_or_stereo));
+ }
+
+ if (config.embedded_buffer_dirty) {
+ config.embedded_buffer_dirty.Assign(0);
+ state.input_queue.emplace(Buffer{
+ config.physical_address,
+ config.length,
+ static_cast<u8>(config.adpcm_ps),
+ { config.adpcm_yn[0], config.adpcm_yn[1] },
+ config.adpcm_dirty.ToBool(),
+ config.is_looping.ToBool(),
+ config.buffer_id,
+ state.mono_or_stereo,
+ state.format,
+ false
+ });
+ LOG_TRACE(Audio_DSP, "enqueuing embedded addr=0x%08x len=%u id=%hu", config.physical_address, config.length, config.buffer_id);
+ }
+
+ if (config.buffer_queue_dirty) {
+ config.buffer_queue_dirty.Assign(0);
+ for (size_t i = 0; i < 4; i++) {
+ if (config.buffers_dirty & (1 << i)) {
+ const auto& b = config.buffers[i];
+ state.input_queue.emplace(Buffer{
+ b.physical_address,
+ b.length,
+ static_cast<u8>(b.adpcm_ps),
+ { b.adpcm_yn[0], b.adpcm_yn[1] },
+ b.adpcm_dirty != 0,
+ b.is_looping != 0,
+ b.buffer_id,
+ state.mono_or_stereo,
+ state.format,
+ true
+ });
+ LOG_TRACE(Audio_DSP, "enqueuing queued %zu addr=0x%08x len=%u id=%hu", i, b.physical_address, b.length, b.buffer_id);
+ }
+ }
+ config.buffers_dirty = 0;
+ }
+
+ if (config.dirty_raw) {
+ LOG_DEBUG(Audio_DSP, "source_id=%zu remaining_dirty=%x", source_id, config.dirty_raw);
+ }
+
+ config.dirty_raw = 0;
+}
+
+void Source::GenerateFrame() {
+ current_frame.fill({});
+
+ if (state.current_buffer.empty() && !DequeueBuffer()) {
+ state.enabled = false;
+ state.buffer_update = true;
+ state.current_buffer_id = 0;
+ return;
+ }
+
+ size_t frame_position = 0;
+
+ state.current_sample_number = state.next_sample_number;
+ while (frame_position < current_frame.size()) {
+ if (state.current_buffer.empty() && !DequeueBuffer()) {
+ break;
+ }
+
+ const size_t size_to_copy = std::min(state.current_buffer.size(), current_frame.size() - frame_position);
+
+ std::copy(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy, current_frame.begin() + frame_position);
+ state.current_buffer.erase(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy);
+
+ frame_position += size_to_copy;
+ state.next_sample_number += static_cast<u32>(size_to_copy);
+ }
+
+ state.filters.ProcessFrame(current_frame);
+}
+
+
+bool Source::DequeueBuffer() {
+ ASSERT_MSG(state.current_buffer.empty(), "Shouldn't dequeue; we still have data in current_buffer");
+
+ if (state.input_queue.empty())
+ return false;
+
+ const Buffer buf = state.input_queue.top();
+ state.input_queue.pop();
+
+ if (buf.adpcm_dirty) {
+ state.adpcm_state.yn1 = buf.adpcm_yn[0];
+ state.adpcm_state.yn2 = buf.adpcm_yn[1];
+ }
+
+ if (buf.is_looping) {
+ LOG_ERROR(Audio_DSP, "Looped buffers are unimplemented at the moment");
+ }
+
+ const u8* const memory = Memory::GetPhysicalPointer(buf.physical_address);
+ if (memory) {
+ const unsigned num_channels = buf.mono_or_stereo == MonoOrStereo::Stereo ? 2 : 1;
+ switch (buf.format) {
+ case Format::PCM8:
+ state.current_buffer = Codec::DecodePCM8(num_channels, memory, buf.length);
+ break;
+ case Format::PCM16:
+ state.current_buffer = Codec::DecodePCM16(num_channels, memory, buf.length);
+ break;
+ case Format::ADPCM:
+ DEBUG_ASSERT(num_channels == 1);
+ state.current_buffer = Codec::DecodeADPCM(memory, buf.length, state.adpcm_coeffs, state.adpcm_state);
+ break;
+ default:
+ UNIMPLEMENTED();
+ break;
+ }
+ } else {
+ LOG_WARNING(Audio_DSP, "source_id=%zu buffer_id=%hu length=%u: Invalid physical address 0x%08X",
+ source_id, buf.buffer_id, buf.length, buf.physical_address);
+ state.current_buffer.clear();
+ return true;
+ }
+
+ switch (state.interpolation_mode) {
+ case InterpolationMode::None:
+ state.current_buffer = AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier);
+ break;
+ case InterpolationMode::Linear:
+ state.current_buffer = AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
+ break;
+ case InterpolationMode::Polyphase:
+ // TODO(merry): Implement polyphase interpolation
+ state.current_buffer = AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
+ break;
+ default:
+ UNIMPLEMENTED();
+ break;
+ }
+
+ state.current_sample_number = 0;
+ state.next_sample_number = 0;
+ state.current_buffer_id = buf.buffer_id;
+ state.buffer_update = buf.from_queue;
+
+ LOG_TRACE(Audio_DSP, "source_id=%zu buffer_id=%hu from_queue=%s current_buffer.size()=%zu",
+ source_id, buf.buffer_id, buf.from_queue ? "true" : "false", state.current_buffer.size());
+ return true;
+}
+
+SourceStatus::Status Source::GetCurrentStatus() {
+ SourceStatus::Status ret;
+
+ // Applications depend on the correct emulation of
+ // current_buffer_id_dirty and current_buffer_id to synchronise
+ // audio with video.
+ ret.is_enabled = state.enabled;
+ ret.current_buffer_id_dirty = state.buffer_update ? 1 : 0;
+ state.buffer_update = false;
+ ret.current_buffer_id = state.current_buffer_id;
+ ret.buffer_position = state.current_sample_number;
+ ret.sync = state.sync;
+
+ return ret;
+}
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/hle/source.h b/src/audio_core/hle/source.h
new file mode 100644
index 000000000..7ee08d424
--- /dev/null
+++ b/src/audio_core/hle/source.h
@@ -0,0 +1,144 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <queue>
+#include <vector>
+
+#include "audio_core/codec.h"
+#include "audio_core/hle/common.h"
+#include "audio_core/hle/dsp.h"
+#include "audio_core/hle/filter.h"
+#include "audio_core/interpolate.h"
+
+#include "common/common_types.h"
+
+namespace DSP {
+namespace HLE {
+
+/**
+ * This module performs:
+ * - Buffer management
+ * - Decoding of buffers
+ * - Buffer resampling and interpolation
+ * - Per-source filtering (SimpleFilter, BiquadFilter)
+ * - Per-source gain
+ * - Other per-source processing
+ */
+class Source final {
+public:
+ explicit Source(size_t source_id_) : source_id(source_id_) {
+ Reset();
+ }
+
+ /// Resets internal state.
+ void Reset();
+
+ /**
+ * This is called once every audio frame. This performs per-source processing every frame.
+ * @param config The new configuration we've got for this Source from the application.
+ * @param adpcm_coeffs ADPCM coefficients to use if config tells us to use them (may contain invalid values otherwise).
+ * @return The current status of this Source. This is given back to the emulated application via SharedMemory.
+ */
+ SourceStatus::Status Tick(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]);
+
+ /**
+ * Mix this source's output into dest, using the gains for the `intermediate_mix_id`-th intermediate mixer.
+ * @param dest The QuadFrame32 to mix into.
+ * @param intermediate_mix_id The id of the intermediate mix whose gains we are using.
+ */
+ void MixInto(QuadFrame32& dest, size_t intermediate_mix_id) const;
+
+private:
+ const size_t source_id;
+ StereoFrame16 current_frame;
+
+ using Format = SourceConfiguration::Configuration::Format;
+ using InterpolationMode = SourceConfiguration::Configuration::InterpolationMode;
+ using MonoOrStereo = SourceConfiguration::Configuration::MonoOrStereo;
+
+ /// Internal representation of a buffer for our buffer queue
+ struct Buffer {
+ PAddr physical_address;
+ u32 length;
+ u8 adpcm_ps;
+ std::array<u16, 2> adpcm_yn;
+ bool adpcm_dirty;
+ bool is_looping;
+ u16 buffer_id;
+
+ MonoOrStereo mono_or_stereo;
+ Format format;
+
+ bool from_queue;
+ };
+
+ struct BufferOrder {
+ bool operator() (const Buffer& a, const Buffer& b) const {
+ // Lower buffer_id comes first.
+ return a.buffer_id > b.buffer_id;
+ }
+ };
+
+ struct {
+
+ // State variables
+
+ bool enabled = false;
+ u16 sync = 0;
+
+ // Mixing
+
+ std::array<std::array<float, 4>, 3> gain = {};
+
+ // Buffer queue
+
+ std::priority_queue<Buffer, std::vector<Buffer>, BufferOrder> input_queue;
+ MonoOrStereo mono_or_stereo = MonoOrStereo::Mono;
+ Format format = Format::ADPCM;
+
+ // Current buffer
+
+ u32 current_sample_number = 0;
+ u32 next_sample_number = 0;
+ std::vector<std::array<s16, 2>> current_buffer;
+
+ // buffer_id state
+
+ bool buffer_update = false;
+ u32 current_buffer_id = 0;
+
+ // Decoding state
+
+ std::array<s16, 16> adpcm_coeffs = {};
+ Codec::ADPCMState adpcm_state = {};
+
+ // Resampling state
+
+ float rate_multiplier = 1.0;
+ InterpolationMode interpolation_mode = InterpolationMode::Polyphase;
+ AudioInterp::State interp_state = {};
+
+ // Filter state
+
+ SourceFilters filters;
+
+ } state;
+
+ // Internal functions
+
+ /// INTERNAL: Update our internal state based on the current config.
+ void ParseConfig(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]);
+ /// INTERNAL: Generate the current audio output for this frame based on our internal state.
+ void GenerateFrame();
+ /// INTERNAL: Dequeues a buffer and does preprocessing on it (decoding, resampling). Puts it into current_buffer.
+ bool DequeueBuffer();
+ /// INTERNAL: Generates a SourceStatus::Status based on our internal state.
+ SourceStatus::Status GetCurrentStatus();
+};
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/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/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp
new file mode 100644
index 000000000..dc75c04ee
--- /dev/null
+++ b/src/audio_core/sdl2_sink.cpp
@@ -0,0 +1,126 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <list>
+#include <vector>
+
+#include <SDL.h>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/sdl2_sink.h"
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include <numeric>
+
+namespace AudioCore {
+
+struct SDL2Sink::Impl {
+ unsigned int sample_rate = 0;
+
+ SDL_AudioDeviceID audio_device_id = 0;
+
+ std::list<std::vector<s16>> queue;
+
+ static void Callback(void* impl_, u8* buffer, int buffer_size_in_bytes);
+};
+
+SDL2Sink::SDL2Sink() : impl(std::make_unique<Impl>()) {
+ if (SDL_Init(SDL_INIT_AUDIO) < 0) {
+ LOG_CRITICAL(Audio_Sink, "SDL_Init(SDL_INIT_AUDIO) failed");
+ impl->audio_device_id = 0;
+ return;
+ }
+
+ SDL_AudioSpec desired_audiospec;
+ SDL_zero(desired_audiospec);
+ desired_audiospec.format = AUDIO_S16;
+ desired_audiospec.channels = 2;
+ desired_audiospec.freq = native_sample_rate;
+ desired_audiospec.samples = 1024;
+ desired_audiospec.userdata = impl.get();
+ desired_audiospec.callback = &Impl::Callback;
+
+ SDL_AudioSpec obtained_audiospec;
+ SDL_zero(obtained_audiospec);
+
+ impl->audio_device_id = SDL_OpenAudioDevice(nullptr, false, &desired_audiospec, &obtained_audiospec, 0);
+ if (impl->audio_device_id <= 0) {
+ LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed");
+ return;
+ }
+
+ impl->sample_rate = obtained_audiospec.freq;
+
+ // SDL2 audio devices start out paused, unpause it:
+ SDL_PauseAudioDevice(impl->audio_device_id, 0);
+}
+
+SDL2Sink::~SDL2Sink() {
+ if (impl->audio_device_id <= 0)
+ return;
+
+ SDL_CloseAudioDevice(impl->audio_device_id);
+}
+
+unsigned int SDL2Sink::GetNativeSampleRate() const {
+ if (impl->audio_device_id <= 0)
+ return native_sample_rate;
+
+ return impl->sample_rate;
+}
+
+void SDL2Sink::EnqueueSamples(const std::vector<s16>& samples) {
+ if (impl->audio_device_id <= 0)
+ return;
+
+ ASSERT_MSG(samples.size() % 2 == 0, "Samples must be in interleaved stereo PCM16 format (size must be a multiple of two)");
+
+ SDL_LockAudioDevice(impl->audio_device_id);
+ impl->queue.emplace_back(samples);
+ SDL_UnlockAudioDevice(impl->audio_device_id);
+}
+
+size_t SDL2Sink::SamplesInQueue() const {
+ if (impl->audio_device_id <= 0)
+ return 0;
+
+ SDL_LockAudioDevice(impl->audio_device_id);
+
+ size_t total_size = std::accumulate(impl->queue.begin(), impl->queue.end(), static_cast<size_t>(0),
+ [](size_t sum, const auto& buffer) {
+ // Division by two because each stereo sample is made of two s16.
+ return sum + buffer.size() / 2;
+ });
+
+ SDL_UnlockAudioDevice(impl->audio_device_id);
+
+ return total_size;
+}
+
+void SDL2Sink::Impl::Callback(void* impl_, u8* buffer, int buffer_size_in_bytes) {
+ Impl* impl = reinterpret_cast<Impl*>(impl_);
+
+ size_t remaining_size = static_cast<size_t>(buffer_size_in_bytes) / sizeof(s16); // Keep track of size in 16-bit increments.
+
+ while (remaining_size > 0 && !impl->queue.empty()) {
+ if (impl->queue.front().size() <= remaining_size) {
+ memcpy(buffer, impl->queue.front().data(), impl->queue.front().size() * sizeof(s16));
+ buffer += impl->queue.front().size() * sizeof(s16);
+ remaining_size -= impl->queue.front().size();
+ impl->queue.pop_front();
+ } else {
+ memcpy(buffer, impl->queue.front().data(), remaining_size * sizeof(s16));
+ buffer += remaining_size * sizeof(s16);
+ impl->queue.front().erase(impl->queue.front().begin(), impl->queue.front().begin() + remaining_size);
+ remaining_size = 0;
+ }
+ }
+
+ if (remaining_size > 0) {
+ memset(buffer, 0, remaining_size * sizeof(s16));
+ }
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h
new file mode 100644
index 000000000..0f296b673
--- /dev/null
+++ b/src/audio_core/sdl2_sink.h
@@ -0,0 +1,30 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <memory>
+
+#include "audio_core/sink.h"
+
+namespace AudioCore {
+
+class SDL2Sink final : public Sink {
+public:
+ SDL2Sink();
+ ~SDL2Sink() override;
+
+ unsigned int GetNativeSampleRate() const override;
+
+ void EnqueueSamples(const std::vector<s16>& samples) override;
+
+ size_t SamplesInQueue() const override;
+
+private:
+ struct Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h
index cad21a85e..1c881c3d2 100644
--- a/src/audio_core/sink.h
+++ b/src/audio_core/sink.h
@@ -19,7 +19,7 @@ public:
virtual ~Sink() = default;
/// The native rate of this sink. The sink expects to be fed samples that respect this. (Units: samples/sec)
- virtual unsigned GetNativeSampleRate() const = 0;
+ virtual unsigned int GetNativeSampleRate() const = 0;
/**
* Feed stereo samples to sink.
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp
new file mode 100644
index 000000000..ba5e83d17
--- /dev/null
+++ b/src/audio_core/sink_details.cpp
@@ -0,0 +1,25 @@
+// 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"
+
+#ifdef HAVE_SDL2
+#include "audio_core/sdl2_sink.h"
+#endif
+
+namespace AudioCore {
+
+// g_sink_details is ordered in terms of desirability, with the best choice at the top.
+const std::vector<SinkDetails> g_sink_details = {
+#ifdef HAVE_SDL2
+ { "sdl2", []() { return std::make_unique<SDL2Sink>(); } },
+#endif
+ { "null", []() { return std::make_unique<NullSink>(); } },
+};
+
+} // 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/audio_core/time_stretch.cpp b/src/audio_core/time_stretch.cpp
new file mode 100644
index 000000000..ea38f40d0
--- /dev/null
+++ b/src/audio_core/time_stretch.cpp
@@ -0,0 +1,144 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <chrono>
+#include <cmath>
+#include <vector>
+
+#include <SoundTouch.h>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/time_stretch.h"
+
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/math_util.h"
+
+using steady_clock = std::chrono::steady_clock;
+
+namespace AudioCore {
+
+constexpr double MIN_RATIO = 0.1;
+constexpr double MAX_RATIO = 100.0;
+
+static double ClampRatio(double ratio) {
+ return MathUtil::Clamp(ratio, MIN_RATIO, MAX_RATIO);
+}
+
+constexpr double MIN_DELAY_TIME = 0.05; // Units: seconds
+constexpr double MAX_DELAY_TIME = 0.25; // Units: seconds
+constexpr size_t DROP_FRAMES_SAMPLE_DELAY = 16000; // Units: samples
+
+constexpr double SMOOTHING_FACTOR = 0.007;
+
+struct TimeStretcher::Impl {
+ soundtouch::SoundTouch soundtouch;
+
+ steady_clock::time_point frame_timer = steady_clock::now();
+ size_t samples_queued = 0;
+
+ double smoothed_ratio = 1.0;
+
+ double sample_rate = static_cast<double>(native_sample_rate);
+};
+
+std::vector<s16> TimeStretcher::Process(size_t samples_in_queue) {
+ // This is a very simple algorithm without any fancy control theory. It works and is stable.
+
+ double ratio = CalculateCurrentRatio();
+ ratio = CorrectForUnderAndOverflow(ratio, samples_in_queue);
+ impl->smoothed_ratio = (1.0 - SMOOTHING_FACTOR) * impl->smoothed_ratio + SMOOTHING_FACTOR * ratio;
+ impl->smoothed_ratio = ClampRatio(impl->smoothed_ratio);
+
+ // SoundTouch's tempo definition the inverse of our ratio definition.
+ impl->soundtouch.setTempo(1.0 / impl->smoothed_ratio);
+
+ std::vector<s16> samples = GetSamples();
+ if (samples_in_queue >= DROP_FRAMES_SAMPLE_DELAY) {
+ samples.clear();
+ LOG_DEBUG(Audio, "Dropping frames!");
+ }
+ return samples;
+}
+
+TimeStretcher::TimeStretcher() : impl(std::make_unique<Impl>()) {
+ impl->soundtouch.setPitch(1.0);
+ impl->soundtouch.setChannels(2);
+ impl->soundtouch.setSampleRate(native_sample_rate);
+ Reset();
+}
+
+TimeStretcher::~TimeStretcher() {
+ impl->soundtouch.clear();
+}
+
+void TimeStretcher::SetOutputSampleRate(unsigned int sample_rate) {
+ impl->sample_rate = static_cast<double>(sample_rate);
+ impl->soundtouch.setRate(static_cast<double>(native_sample_rate) / impl->sample_rate);
+}
+
+void TimeStretcher::AddSamples(const s16* buffer, size_t num_samples) {
+ impl->soundtouch.putSamples(buffer, static_cast<uint>(num_samples));
+ impl->samples_queued += num_samples;
+}
+
+void TimeStretcher::Flush() {
+ impl->soundtouch.flush();
+}
+
+void TimeStretcher::Reset() {
+ impl->soundtouch.setTempo(1.0);
+ impl->soundtouch.clear();
+ impl->smoothed_ratio = 1.0;
+ impl->frame_timer = steady_clock::now();
+ impl->samples_queued = 0;
+ SetOutputSampleRate(native_sample_rate);
+}
+
+double TimeStretcher::CalculateCurrentRatio() {
+ const steady_clock::time_point now = steady_clock::now();
+ const std::chrono::duration<double> duration = now - impl->frame_timer;
+
+ const double expected_time = static_cast<double>(impl->samples_queued) / static_cast<double>(native_sample_rate);
+ const double actual_time = duration.count();
+
+ double ratio;
+ if (expected_time != 0) {
+ ratio = ClampRatio(actual_time / expected_time);
+ } else {
+ ratio = impl->smoothed_ratio;
+ }
+
+ impl->frame_timer = now;
+ impl->samples_queued = 0;
+
+ return ratio;
+}
+
+double TimeStretcher::CorrectForUnderAndOverflow(double ratio, size_t sample_delay) const {
+ const size_t min_sample_delay = static_cast<size_t>(MIN_DELAY_TIME * impl->sample_rate);
+ const size_t max_sample_delay = static_cast<size_t>(MAX_DELAY_TIME * impl->sample_rate);
+
+ if (sample_delay < min_sample_delay) {
+ // Make the ratio bigger.
+ ratio = ratio > 1.0 ? ratio * ratio : sqrt(ratio);
+ } else if (sample_delay > max_sample_delay) {
+ // Make the ratio smaller.
+ ratio = ratio > 1.0 ? sqrt(ratio) : ratio * ratio;
+ }
+
+ return ClampRatio(ratio);
+}
+
+std::vector<s16> TimeStretcher::GetSamples() {
+ uint available = impl->soundtouch.numSamples();
+
+ std::vector<s16> output(static_cast<size_t>(available) * 2);
+
+ impl->soundtouch.receiveSamples(output.data(), available);
+
+ return output;
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/time_stretch.h b/src/audio_core/time_stretch.h
new file mode 100644
index 000000000..1fde3f72a
--- /dev/null
+++ b/src/audio_core/time_stretch.h
@@ -0,0 +1,57 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstddef>
+#include <memory>
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace AudioCore {
+
+class TimeStretcher final {
+public:
+ TimeStretcher();
+ ~TimeStretcher();
+
+ /**
+ * Set sample rate for the samples that Process returns.
+ * @param sample_rate The sample rate.
+ */
+ void SetOutputSampleRate(unsigned int sample_rate);
+
+ /**
+ * Add samples to be processed.
+ * @param sample_buffer Buffer of samples in interleaved stereo PCM16 format.
+ * @param num_sample Number of samples.
+ */
+ void AddSamples(const s16* sample_buffer, size_t num_samples);
+
+ /// Flush audio remaining in internal buffers.
+ void Flush();
+
+ /// Resets internal state and clears buffers.
+ void Reset();
+
+ /**
+ * Does audio stretching and produces the time-stretched samples.
+ * Timer calculations use sample_delay to determine how much of a margin we have.
+ * @param sample_delay How many samples are buffered downstream of this module and haven't been played yet.
+ * @return Samples to play in interleaved stereo PCM16 format.
+ */
+ std::vector<s16> Process(size_t sample_delay);
+
+private:
+ struct Impl;
+ std::unique_ptr<Impl> impl;
+
+ /// INTERNAL: ratio = wallclock time / emulated time
+ double CalculateCurrentRatio();
+ /// INTERNAL: If we have too many or too few samples downstream, nudge ratio in the appropriate direction.
+ double CorrectForUnderAndOverflow(double ratio, size_t sample_delay) const;
+ /// INTERNAL: Gets the time-stretched samples from SoundTouch.
+ std::vector<s16> GetSamples();
+};
+
+} // namespace AudioCore
diff --git a/src/citra/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 3a1fbe3f7..b4501eb2e 100644
--- a/src/citra/citra.cpp
+++ b/src/citra/citra.cpp
@@ -20,6 +20,7 @@
#include "common/logging/log.h"
#include "common/logging/backend.h"
#include "common/logging/filter.h"
+#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "core/settings.h"
@@ -34,11 +35,17 @@
#include "video_core/video_core.h"
-static void PrintHelp()
+static void PrintHelp(const char *argv0)
{
- std::cout << "Usage: citra [options] <filename>" << std::endl;
- std::cout << "--help, -h Display this information" << std::endl;
- std::cout << "--gdbport, -g number Enable gdb stub on port number" << 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
@@ -51,18 +58,16 @@ int main(int argc, char **argv) {
std::string boot_filename;
static struct option long_options[] = {
- { "help", no_argument, 0, 'h' },
{ "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, ":hg:", long_options, &option_index);
+ char arg = getopt_long(argc, argv, "g:hv", long_options, &option_index);
if (arg != -1) {
switch (arg) {
- case 'h':
- PrintHelp();
- return 0;
case 'g':
errno = 0;
gdb_port = strtoul(optarg, &endarg, 0);
@@ -73,6 +78,12 @@ int main(int argc, char **argv) {
exit(1);
}
break;
+ case 'h':
+ PrintHelp(argv[0]);
+ return 0;
+ case 'v':
+ PrintVersion();
+ return 0;
}
} else {
boot_filename = argv[optind];
@@ -93,14 +104,13 @@ int main(int argc, char **argv) {
log_filter.ParseFilterString(Settings::values.log_filter);
- GDBStub::ToggleServer(use_gdbstub);
- GDBStub::SetServerPort(gdb_port);
+ // Apply the command line arguments
+ Settings::values.gdbstub_port = gdb_port;
+ Settings::values.use_gdbstub = use_gdbstub;
+ Settings::Apply();
std::unique_ptr<EmuWindow_SDL2> emu_window = std::make_unique<EmuWindow_SDL2>();
- VideoCore::g_hw_renderer_enabled = Settings::values.use_hw_renderer;
- VideoCore::g_shader_jit_enabled = Settings::values.use_shader_jit;
-
System::Init(emu_window.get());
SCOPE_EXIT({ System::Shutdown(); });
diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index ebea5f840..4d170dec8 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -65,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);
@@ -82,7 +86,7 @@ void Config::ReadValues() {
// Debugging
Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
- Settings::values.gdbstub_port = sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689);
+ Settings::values.gdbstub_port = static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
}
void Config::Reload() {
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index c9b490a00..49126356f 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, sdl2: SDL2 (if available)
+output_engine =
+
[Data Storage]
# Whether to create a virtual SD card.
# 1 (default): Yes, 0: No
diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp
index 924189f4c..12cdd9d95 100644
--- a/src/citra/emu_window/emu_window_sdl2.cpp
+++ b/src/citra/emu_window/emu_window_sdl2.cpp
@@ -9,6 +9,8 @@
#define SDL_MAIN_HANDLED
#include <SDL.h>
+#include <glad/glad.h>
+
#include "common/key_map.h"
#include "common/logging/log.h"
#include "common/scm_rev.h"
@@ -98,6 +100,11 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
exit(1);
}
+ if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
+ LOG_CRITICAL(Frontend, "Failed to initialize GL functions! Exiting...");
+ exit(1);
+ }
+
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 9b3eb2cd6..0a5d4624b 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -17,12 +17,17 @@ set(SRCS
debugger/profiler.cpp
debugger/ramview.cpp
debugger/registers.cpp
- game_list.cpp
util/spinbox.cpp
util/util.cpp
bootmanager.cpp
+ configure_audio.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 +49,18 @@ set(HEADERS
debugger/profiler.h
debugger/ramview.h
debugger/registers.h
- game_list.h
util/spinbox.h
util/util.h
bootmanager.h
+ configure_audio.h
+ configure_debug.h
+ configure_dialog.h
+ configure_general.h
+ game_list.h
+ game_list_p.h
hotkeys.h
main.h
+ ui_settings.h
version.h
)
@@ -59,6 +70,10 @@ set(UIS
debugger/disassembler.ui
debugger/profiler.ui
debugger/registers.ui
+ configure.ui
+ configure_audio.ui
+ configure_debug.ui
+ configure_general.ui
hotkeys.ui
main.ui
)
@@ -81,7 +96,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 66271aa7b..f6e498128 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();
@@ -68,6 +73,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() {
@@ -85,6 +135,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);
@@ -92,6 +143,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();
@@ -109,10 +164,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..e1624bbef
--- /dev/null
+++ b/src/citra_qt/configure.ui
@@ -0,0 +1,108 @@
+<?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="ConfigureAudio" name="audioTab">
+ <attribute name="title">
+ <string>Audio</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>ConfigureAudio</class>
+ <extends>QWidget</extends>
+ <header>configure_audio.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>ConfigureDebug</class>
+ <extends>QWidget</extends>
+ <header>configure_debug.h</header>
+ <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_audio.cpp b/src/citra_qt/configure_audio.cpp
new file mode 100644
index 000000000..cedfa2f2a
--- /dev/null
+++ b/src/citra_qt/configure_audio.cpp
@@ -0,0 +1,44 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/sink_details.h"
+
+#include "citra_qt/configure_audio.h"
+#include "ui_configure_audio.h"
+
+#include "core/settings.h"
+
+ConfigureAudio::ConfigureAudio(QWidget* parent) :
+ QWidget(parent),
+ ui(std::make_unique<Ui::ConfigureAudio>())
+{
+ ui->setupUi(this);
+
+ ui->output_sink_combo_box->clear();
+ ui->output_sink_combo_box->addItem("auto");
+ for (const auto& sink_detail : AudioCore::g_sink_details) {
+ ui->output_sink_combo_box->addItem(sink_detail.id);
+ }
+
+ this->setConfiguration();
+}
+
+ConfigureAudio::~ConfigureAudio() {
+}
+
+void ConfigureAudio::setConfiguration() {
+ int new_sink_index = 0;
+ for (int index = 0; index < ui->output_sink_combo_box->count(); index++) {
+ if (ui->output_sink_combo_box->itemText(index).toStdString() == Settings::values.sink_id) {
+ new_sink_index = index;
+ break;
+ }
+ }
+ ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
+}
+
+void ConfigureAudio::applyConfiguration() {
+ Settings::values.sink_id = ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()).toStdString();
+ Settings::Apply();
+}
diff --git a/src/citra_qt/configure_audio.h b/src/citra_qt/configure_audio.h
new file mode 100644
index 000000000..51df2e27b
--- /dev/null
+++ b/src/citra_qt/configure_audio.h
@@ -0,0 +1,27 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QWidget>
+
+namespace Ui {
+class ConfigureAudio;
+}
+
+class ConfigureAudio : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureAudio(QWidget* parent = nullptr);
+ ~ConfigureAudio();
+
+ void applyConfiguration();
+
+private:
+ void setConfiguration();
+
+ std::unique_ptr<Ui::ConfigureAudio> ui;
+};
diff --git a/src/citra_qt/configure_audio.ui b/src/citra_qt/configure_audio.ui
new file mode 100644
index 000000000..d7f6946ca
--- /dev/null
+++ b/src/citra_qt/configure_audio.ui
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<ui version="4.0">
+ <class>ConfigureAudio</class>
+ <widget class="QWidget" name="ConfigureAudio">
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QGroupBox">
+ <property name="title">
+ <string>Audio</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel">
+ <property name="text">
+ <string>Output Engine:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="output_sink_combo_box">
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources />
+ <connections />
+</ui>
diff --git a/src/citra_qt/configure_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..2f0317fe0
--- /dev/null
+++ b/src/citra_qt/configure_dialog.cpp
@@ -0,0 +1,30 @@
+// 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->audioTab->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..9c80f7ec9 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"
@@ -70,7 +74,7 @@ void GraphicsTracingWidget::StartRecording() {
std::array<u32, 4 * 16> default_attributes;
for (unsigned i = 0; i < 16; ++i) {
for (unsigned comp = 0; comp < 3; ++comp) {
- default_attributes[4 * i + comp] = nihstro::to_float24(Pica::g_state.vs.default_attributes[i][comp].ToFloat32());
+ default_attributes[4 * i + comp] = nihstro::to_float24(Pica::g_state.vs_default_attributes[i][comp].ToFloat32());
}
}
diff --git a/src/citra_qt/debugger/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics_vertex_shader.cpp
index d648d4640..391666d35 100644
--- a/src/citra_qt/debugger/graphics_vertex_shader.cpp
+++ b/src/citra_qt/debugger/graphics_vertex_shader.cpp
@@ -365,7 +365,7 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De
input_data[i]->setValidator(new QDoubleValidator(input_data[i]));
}
- breakpoint_warning = new QLabel(tr("(data only available at VertexLoaded breakpoints)"));
+ breakpoint_warning = new QLabel(tr("(data only available at vertex shader invocation breakpoints)"));
// TODO: Add some button for jumping to the shader entry point
@@ -454,7 +454,7 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De
void GraphicsVertexShaderWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) {
auto input = static_cast<Pica::Shader::InputVertex*>(data);
- if (event == Pica::DebugContext::Event::VertexLoaded) {
+ if (event == Pica::DebugContext::Event::VertexShaderInvocation) {
Reload(true, data);
} else {
// No vertex data is retrievable => invalidate currently stored vertex data
@@ -501,7 +501,7 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d
info.labels.insert({ entry_point, "main" });
// Generate debug information
- debug_data = Pica::Shader::ProduceDebugInfo(input_vertex, num_attributes, shader_config, shader_setup);
+ debug_data = Pica::g_state.vs.ProduceDebugInfo(input_vertex, num_attributes, shader_config, shader_setup);
// Reload widget state
for (int attr = 0; attr < num_attributes; ++attr) {
@@ -515,7 +515,7 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d
}
// Initialize debug info text for current cycle count
- cycle_index->setMaximum(debug_data.records.size() - 1);
+ cycle_index->setMaximum(static_cast<int>(debug_data.records.size() - 1));
OnCycleIndexChanged(cycle_index->value());
model->endResetModel();
diff --git a/src/citra_qt/debugger/profiler.cpp b/src/citra_qt/debugger/profiler.cpp
index 4f6ba0e1f..585ac049a 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);
@@ -169,8 +151,12 @@ private:
/// This timer is used to redraw the widget's contents continuously. To save resources, it only
/// runs while the widget is visible.
QTimer update_timer;
+ /// Scale the coordinate system appropriately when physical DPI != logical DPI.
+ qreal x_scale, y_scale;
};
+#endif
+
MicroProfileDialog::MicroProfileDialog(QWidget* parent)
: QWidget(parent, Qt::Dialog)
{
@@ -180,6 +166,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 +179,7 @@ MicroProfileDialog::MicroProfileDialog(QWidget* parent)
setFocusProxy(widget);
widget->setFocusPolicy(Qt::StrongFocus);
widget->setFocus();
+#endif
}
QAction* MicroProfileDialog::toggleViewAction() {
@@ -218,6 +207,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;
@@ -230,11 +222,17 @@ MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) {
MicroProfileInitUI();
connect(&update_timer, SIGNAL(timeout()), SLOT(update()));
+
+ QPainter painter(this);
+ x_scale = qreal(painter.device()->physicalDpiX()) / qreal(painter.device()->logicalDpiX());
+ y_scale = qreal(painter.device()->physicalDpiY()) / qreal(painter.device()->logicalDpiY());
}
void MicroProfileWidget::paintEvent(QPaintEvent* ev) {
QPainter painter(this);
+ painter.scale(x_scale, y_scale);
+
painter.setBackground(Qt::black);
painter.eraseRect(rect());
@@ -258,24 +256,24 @@ void MicroProfileWidget::hideEvent(QHideEvent* ev) {
}
void MicroProfileWidget::mouseMoveEvent(QMouseEvent* ev) {
- MicroProfileMousePosition(ev->x(), ev->y(), 0);
+ MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0);
ev->accept();
}
void MicroProfileWidget::mousePressEvent(QMouseEvent* ev) {
- MicroProfileMousePosition(ev->x(), ev->y(), 0);
+ MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0);
MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton);
ev->accept();
}
void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* ev) {
- MicroProfileMousePosition(ev->x(), ev->y(), 0);
+ MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0);
MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton);
ev->accept();
}
void MicroProfileWidget::wheelEvent(QWheelEvent* ev) {
- MicroProfileMousePosition(ev->x(), ev->y(), ev->delta() / 120);
+ MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, ev->delta() / 120);
ev->accept();
}
@@ -337,3 +335,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 ffcab1f03..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&)));
@@ -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 ca0ae6f7b..a85c94a4b 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -6,6 +6,9 @@
#include <memory>
#include <thread>
+#include <glad/glad.h>
+
+#define QT_NO_OPENGL
#include <QDesktopWidget>
#include <QtGui>
#include <QFileDialog>
@@ -14,9 +17,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"
@@ -50,12 +55,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 +72,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 +115,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 +140,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 +166,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 +190,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 +200,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) {
@@ -254,6 +243,14 @@ bool GMainWindow::InitializeSystem() {
if (emu_thread != nullptr)
ShutdownGame();
+ render_window->MakeCurrent();
+ if (!gladLoadGL()) {
+ QMessageBox::critical(this, tr("Error while starting Citra!"),
+ tr("Failed to initialize the video core!\n\n"
+ "Please ensure that your GPU supports OpenGL 3.3 and that you have the latest graphics driver."));
+ return false;
+ }
+
// Initialize the core emulation
System::Result system_result = System::Init(render_window);
if (System::Result::Success != system_result) {
@@ -375,32 +372,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);
}
@@ -421,36 +410,28 @@ void GMainWindow::OnGameListLoadFile(QString game_path) {
}
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.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.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);
}
}
@@ -466,10 +447,7 @@ void GMainWindow::OnMenuRecentFile() {
// 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 +474,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 +500,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 +525,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 +562,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 &amp;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/citra_qt/util/util.cpp b/src/citra_qt/util/util.cpp
index 8734a8efd..2f9beb5cc 100644
--- a/src/citra_qt/util/util.cpp
+++ b/src/citra_qt/util/util.cpp
@@ -19,7 +19,7 @@ QString ReadableByteSize(qulonglong size) {
static const std::array<const char*, 6> units = { "B", "KiB", "MiB", "GiB", "TiB", "PiB" };
if (size == 0)
return "0";
- int digit_groups = std::min<int>((int)(std::log10(size) / std::log10(1024)), units.size());
+ int digit_groups = std::min<int>(static_cast<int>(std::log10(size) / std::log10(1024)), static_cast<int>(units.size()));
return QString("%L1 %2").arg(size / std::pow(1024, digit_groups), 0, 'f', 1)
.arg(units[digit_groups]);
}
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index c839ce173..aa6eee2a3 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -47,7 +47,6 @@ set(HEADERS
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/file_util.cpp b/src/common/file_util.cpp
index 9ada09f8a..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,6 +86,10 @@ bool Exists(const std::string &filename)
StripTailDirSlashes(copy);
#ifdef _WIN32
+ // 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);
@@ -102,6 +107,10 @@ bool IsDirectory(const std::string &filename)
StripTailDirSlashes(copy);
#ifdef _WIN32
+ // 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);
@@ -824,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());
}
@@ -877,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);
}
@@ -896,7 +899,6 @@ IOFile::~IOFile()
}
IOFile::IOFile(IOFile&& other)
- : m_file(nullptr), m_good(true)
{
Swap(other);
}
@@ -935,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)
@@ -965,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 3d39f94d5..d7008fc66 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -65,6 +65,7 @@ namespace Log {
SUB(Render, OpenGL) \
CLS(Audio) \
SUB(Audio, DSP) \
+ SUB(Audio, Sink) \
CLS(Loader)
// GetClassName is a macro defined by Windows.h, grrr...
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 521362317..c6910b1c7 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -78,8 +78,9 @@ enum class Class : ClassType {
Render, ///< Emulator video output and hardware acceleration
Render_Software, ///< Software renderer backend
Render_OpenGL, ///< OpenGL backend
- Audio, ///< Emulator audio output
+ Audio, ///< Audio emulation
Audio_DSP, ///< The HLE implementation of the DSP
+ Audio_Sink, ///< Emulator audio output backend
Loader, ///< ROM loader
Count ///< Total number of logging classes
diff --git a/src/common/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/swap.h b/src/common/swap.h
index a7c37bc44..1749bd7a4 100644
--- a/src/common/swap.h
+++ b/src/common/swap.h
@@ -25,6 +25,8 @@
#include <sys/endian.h>
#endif
+#include <cstring>
+
#include "common/common_types.h"
// GCC 4.6+
@@ -58,9 +60,6 @@
namespace Common {
-inline u8 swap8(u8 _data) {return _data;}
-inline u32 swap24(const u8* _data) {return (_data[0] << 16) | (_data[1] << 8) | _data[2];}
-
#ifdef _MSC_VER
inline u16 swap16(u16 _data) {return _byteswap_ushort(_data);}
inline u32 swap32(u32 _data) {return _byteswap_ulong (_data);}
@@ -92,52 +91,29 @@ inline u64 swap64(u64 data) {return ((u64)swap32(data) << 32) | swap32(data >> 3
#endif
inline float swapf(float f) {
- union {
- float f;
- unsigned int u32;
- } dat1, dat2;
-
- dat1.f = f;
- dat2.u32 = swap32(dat1.u32);
+ static_assert(sizeof(u32) == sizeof(float),
+ "float must be the same size as uint32_t.");
- return dat2.f;
-}
-
-inline double swapd(double f) {
- union {
- double f;
- unsigned long long u64;
- } dat1, dat2;
+ u32 value;
+ std::memcpy(&value, &f, sizeof(u32));
- dat1.f = f;
- dat2.u64 = swap64(dat1.u64);
+ value = swap32(value);
+ std::memcpy(&f, &value, sizeof(u32));
- return dat2.f;
+ return f;
}
-inline u16 swap16(const u8* _pData) {return swap16(*(const u16*)_pData);}
-inline u32 swap32(const u8* _pData) {return swap32(*(const u32*)_pData);}
-inline u64 swap64(const u8* _pData) {return swap64(*(const u64*)_pData);}
-
-template <int count>
-void swap(u8*);
+inline double swapd(double f) {
+ static_assert(sizeof(u64) == sizeof(double),
+ "double must be the same size as uint64_t.");
-template <>
-inline void swap<1>(u8* data) { }
+ u64 value;
+ std::memcpy(&value, &f, sizeof(u64));
-template <>
-inline void swap<2>(u8* data) {
- *reinterpret_cast<u16*>(data) = swap16(data);
-}
-
-template <>
-inline void swap<4>(u8* data) {
- *reinterpret_cast<u32*>(data) = swap32(data);
-}
+ value = swap64(value);
+ std::memcpy(&f, &value, sizeof(u64));
-template <>
-inline void swap<8>(u8* data) {
- *reinterpret_cast<u64*>(data) = swap64(data);
+ return f;
}
} // Namespace Common
@@ -534,35 +510,35 @@ bool operator==(const S &p, const swap_struct_t<T, F> v) {
template <typename T>
struct swap_64_t {
static T swap(T x) {
- return (T)Common::swap64(*(u64 *)&x);
+ return static_cast<T>(Common::swap64(x));
}
};
template <typename T>
struct swap_32_t {
static T swap(T x) {
- return (T)Common::swap32(*(u32 *)&x);
+ return static_cast<T>(Common::swap32(x));
}
};
template <typename T>
struct swap_16_t {
static T swap(T x) {
- return (T)Common::swap16(*(u16 *)&x);
+ return static_cast<T>(Common::swap16(x));
}
};
template <typename T>
struct swap_float_t {
static T swap(T x) {
- return (T)Common::swapf(*(float *)&x);
+ return static_cast<T>(Common::swapf(x));
}
};
template <typename T>
struct swap_double_t {
static T swap(T x) {
- return (T)Common::swapd(*(double *)&x);
+ return static_cast<T>(Common::swapd(x));
}
};
diff --git a/src/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 a8d891689..12080a802 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -42,6 +42,7 @@ set(SRCS
hle/kernel/timer.cpp
hle/kernel/vm_manager.cpp
hle/service/ac_u.cpp
+ hle/service/act_a.cpp
hle/service/act_u.cpp
hle/service/am/am.cpp
hle/service/am/am_app.cpp
@@ -52,6 +53,7 @@ set(SRCS
hle/service/apt/apt_a.cpp
hle/service/apt/apt_s.cpp
hle/service/apt/apt_u.cpp
+ hle/service/apt/bcfnt/bcfnt.cpp
hle/service/boss/boss.cpp
hle/service/boss/boss_p.cpp
hle/service/boss/boss_u.cpp
@@ -175,6 +177,7 @@ set(HEADERS
hle/kernel/vm_manager.h
hle/result.h
hle/service/ac_u.h
+ hle/service/act_a.h
hle/service/act_u.h
hle/service/am/am.h
hle/service/am/am_app.h
@@ -185,6 +188,7 @@ set(HEADERS
hle/service/apt/apt_a.h
hle/service/apt/apt_s.h
hle/service/apt/apt_u.h
+ hle/service/apt/bcfnt/bcfnt.h
hle/service/boss/boss.h
hle/service/boss/boss_p.h
hle/service/boss/boss_u.h
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 533067d4f..d8abe5aeb 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -6,6 +6,7 @@
#include "common/common_types.h"
#include "core/arm/skyeye_common/arm_regformat.h"
+#include "core/arm/skyeye_common/vfp/asm_vfp.h"
namespace Core {
struct ThreadContext;
diff --git a/src/core/arm/dyncom/arm_dyncom.cpp b/src/core/arm/dyncom/arm_dyncom.cpp
index a3581132c..13492a08b 100644
--- a/src/core/arm/dyncom/arm_dyncom.cpp
+++ b/src/core/arm/dyncom/arm_dyncom.cpp
@@ -93,7 +93,7 @@ void ARM_DynCom::ResetContext(Core::ThreadContext& context, u32 stack_top, u32 e
context.cpu_registers[0] = arg;
context.pc = entry_point;
context.sp = stack_top;
- context.cpsr = 0x1F | ((entry_point & 1) << 5); // Usermode and THUMB mode
+ context.cpsr = USER32MODE | ((entry_point & 1) << 5); // Usermode and THUMB mode
}
void ARM_DynCom::SaveContext(Core::ThreadContext& ctx) {
diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp
index a6faf42b9..cfc67287f 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),
@@ -3496,7 +3492,6 @@ static unsigned int InterpreterTranslateInstruction(const ARMul_State* cpu, cons
}
static int InterpreterTranslateBlock(ARMul_State* cpu, int& bb_start, u32 addr) {
- Common::Profiling::ScopeTimer timer_decode(profile_decode);
MICROPROFILE_SCOPE(DynCom_Decode);
// Decode instruction, get index
@@ -3530,7 +3525,6 @@ static int InterpreterTranslateBlock(ARMul_State* cpu, int& bb_start, u32 addr)
}
static int InterpreterTranslateSingle(ARMul_State* cpu, int& bb_start, u32 addr) {
- Common::Profiling::ScopeTimer timer_decode(profile_decode);
MICROPROFILE_SCOPE(DynCom_Decode);
ARM_INST_PTR inst_base = nullptr;
@@ -3565,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;
@@ -4080,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;
@@ -5533,28 +5527,32 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) {
// SMUAD and SMLAD
if (BIT(op2, 1) == 0) {
- RD = (product1 + product2);
+ u32 rd_val = (product1 + product2);
if (inst_cream->Ra != 15) {
- RD += cpu->Reg[inst_cream->Ra];
+ rd_val += cpu->Reg[inst_cream->Ra];
if (ARMul_AddOverflowQ(product1 + product2, cpu->Reg[inst_cream->Ra]))
cpu->Cpsr |= (1 << 27);
}
+ RD = rd_val;
+
if (ARMul_AddOverflowQ(product1, product2))
cpu->Cpsr |= (1 << 27);
}
// SMUSD and SMLSD
else {
- RD = (product1 - product2);
+ u32 rd_val = (product1 - product2);
if (inst_cream->Ra != 15) {
- RD += cpu->Reg[inst_cream->Ra];
+ rd_val += cpu->Reg[inst_cream->Ra];
if (ARMul_AddOverflowQ(product1 - product2, cpu->Reg[inst_cream->Ra]))
cpu->Cpsr |= (1 << 27);
}
+
+ RD = rd_val;
}
}
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 3bb843aab..cabab744a 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -51,7 +51,7 @@ void RunLoop(int tight_loop) {
}
HW::Update();
- if (HLE::g_reschedule) {
+ if (HLE::IsReschedulePending()) {
Kernel::Reschedule();
}
}
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index c1a7ec5bf..820b19e1a 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -374,7 +374,7 @@ static void SendReply(const char* reply) {
memset(command_buffer, 0, sizeof(command_buffer));
- command_length = strlen(reply);
+ command_length = static_cast<u32>(strlen(reply));
if (command_length + 4 > sizeof(command_buffer)) {
LOG_ERROR(Debug_GDBStub, "command_buffer overflow in SendReply");
return;
@@ -437,7 +437,7 @@ static void HandleSetThread() {
*
* @param signal Signal to be sent to client.
*/
-void SendSignal(u32 signal) {
+static void SendSignal(u32 signal) {
if (gdbserver_socket == -1) {
return;
}
@@ -515,7 +515,7 @@ static bool IsDataAvailable() {
return false;
}
- return FD_ISSET(gdbserver_socket, &fd_socket);
+ return FD_ISSET(gdbserver_socket, &fd_socket) != 0;
}
/// Send requested register to gdb client.
@@ -529,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());
@@ -584,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));
@@ -633,10 +633,10 @@ static void ReadMemory() {
auto start_offset = command_buffer+1;
auto addr_pos = std::find(start_offset, command_buffer+command_length, ',');
- PAddr addr = HexToInt(start_offset, addr_pos - start_offset);
+ PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset));
start_offset = addr_pos+1;
- u32 len = HexToInt(start_offset, (command_buffer + command_length) - start_offset);
+ u32 len = HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset));
LOG_DEBUG(Debug_GDBStub, "gdb: addr: %08x len: %08x\n", addr, len);
@@ -658,11 +658,11 @@ static void ReadMemory() {
static void WriteMemory() {
auto start_offset = command_buffer+1;
auto addr_pos = std::find(start_offset, command_buffer+command_length, ',');
- PAddr addr = HexToInt(start_offset, addr_pos - start_offset);
+ PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset));
start_offset = addr_pos+1;
auto len_pos = std::find(start_offset, command_buffer+command_length, ':');
- u32 len = HexToInt(start_offset, len_pos - start_offset);
+ u32 len = HexToInt(start_offset, static_cast<u32>(len_pos - start_offset));
u8* dst = Memory::GetPointer(addr);
if (!dst) {
@@ -713,7 +713,7 @@ static void Continue() {
* @param addr Address of breakpoint.
* @param len Length of breakpoint.
*/
-bool CommitBreakpoint(BreakpointType type, PAddr addr, u32 len) {
+static bool CommitBreakpoint(BreakpointType type, PAddr addr, u32 len) {
std::map<u32, Breakpoint>& p = GetBreakpointList(type);
Breakpoint breakpoint;
@@ -752,10 +752,10 @@ static void AddBreakpoint() {
auto start_offset = command_buffer+3;
auto addr_pos = std::find(start_offset, command_buffer+command_length, ',');
- PAddr addr = HexToInt(start_offset, addr_pos - start_offset);
+ PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset));
start_offset = addr_pos+1;
- u32 len = HexToInt(start_offset, (command_buffer + command_length) - start_offset);
+ u32 len = HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset));
if (type == BreakpointType::Access) {
// Access is made up of Read and Write types, so add both breakpoints
@@ -800,10 +800,10 @@ static void RemoveBreakpoint() {
auto start_offset = command_buffer+3;
auto addr_pos = std::find(start_offset, command_buffer+command_length, ',');
- PAddr addr = HexToInt(start_offset, addr_pos - start_offset);
+ PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset));
start_offset = addr_pos+1;
- u32 len = HexToInt(start_offset, (command_buffer + command_length) - start_offset);
+ u32 len = HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset));
if (type == BreakpointType::Access) {
// Access is made up of Read and Write types, so add both breakpoints
@@ -907,7 +907,7 @@ void ToggleServer(bool status) {
}
}
-void Init(u16 port) {
+static void Init(u16 port) {
if (!g_server_enabled) {
// Set the halt loop to false in case the user enabled the gdbstub mid-execution.
// This way the CPU can still execute normally.
diff --git a/src/core/hle/applets/applet.h b/src/core/hle/applets/applet.h
index af442f81d..754c6f7db 100644
--- a/src/core/hle/applets/applet.h
+++ b/src/core/hle/applets/applet.h
@@ -65,6 +65,7 @@ protected:
virtual ResultCode StartImpl(const Service::APT::AppletStartupParameter& parameter) = 0;
Service::APT::AppletId id; ///< Id of this Applet
+ std::shared_ptr<std::vector<u8>> heap_memory; ///< Heap memory for this Applet
};
/// Returns whether a library applet is currently running
diff --git a/src/core/hle/applets/mii_selector.cpp b/src/core/hle/applets/mii_selector.cpp
index 708d2f630..bf39eca22 100644
--- a/src/core/hle/applets/mii_selector.cpp
+++ b/src/core/hle/applets/mii_selector.cpp
@@ -21,13 +21,6 @@
namespace HLE {
namespace Applets {
-MiiSelector::MiiSelector(Service::APT::AppletId id) : Applet(id), started(false) {
- // Create the SharedMemory that will hold the framebuffer data
- // TODO(Subv): What size should we use here?
- using Kernel::MemoryPermission;
- framebuffer_memory = Kernel::SharedMemory::Create(0x1000, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, "MiiSelector Memory");
-}
-
ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& parameter) {
if (parameter.signal != static_cast<u32>(Service::APT::SignalType::LibAppJustStarted)) {
LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal);
@@ -36,8 +29,23 @@ ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& p
return ResultCode(-1);
}
+ // The LibAppJustStarted message contains a buffer with the size of the framebuffer shared memory.
+ // Create the SharedMemory that will hold the framebuffer data
+ Service::APT::CaptureBufferInfo capture_info;
+ ASSERT(sizeof(capture_info) == parameter.buffer_size);
+
+ memcpy(&capture_info, parameter.data, sizeof(capture_info));
+
+ using Kernel::MemoryPermission;
+ // Allocate a heap block of the required size for this applet.
+ heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
+ // Create a SharedMemory that directly points to this heap block.
+ framebuffer_memory = Kernel::SharedMemory::CreateForApplet(heap_memory, 0, heap_memory->size(),
+ MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
+ "MiiSelector Memory");
+
+ // Send the response message with the newly created SharedMemory
Service::APT::MessageParameter result;
- // The buffer passed in parameter contains the data returned by GSPGPU::ImportDisplayCaptureInfo
result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
result.data = nullptr;
result.buffer_size = 0;
@@ -55,6 +63,11 @@ ResultCode MiiSelector::StartImpl(const Service::APT::AppletStartupParameter& pa
// TODO(Subv): Set the expected fields in the response buffer before resending it to the application.
// TODO(Subv): Reverse the parameter format for the Mii Selector
+ if(parameter.buffer_size >= sizeof(u32)) {
+ // TODO: defaults return no error, but garbage in other unknown fields
+ memset(parameter.data, 0, sizeof(u32));
+ }
+
// Let the application know that we're closing
Service::APT::MessageParameter message;
message.buffer_size = parameter.buffer_size;
diff --git a/src/core/hle/applets/mii_selector.h b/src/core/hle/applets/mii_selector.h
index 6a3e7c8eb..be6b04642 100644
--- a/src/core/hle/applets/mii_selector.h
+++ b/src/core/hle/applets/mii_selector.h
@@ -16,17 +16,61 @@
namespace HLE {
namespace Applets {
+struct MiiConfig {
+ u8 unk_000;
+ u8 unk_001;
+ u8 unk_002;
+ u8 unk_003;
+ u8 unk_004;
+ INSERT_PADDING_BYTES(3);
+ u16 unk_008;
+ INSERT_PADDING_BYTES(0x8C - 0xA);
+ u8 unk_08C;
+ INSERT_PADDING_BYTES(3);
+ u16 unk_090;
+ INSERT_PADDING_BYTES(2);
+ u32 unk_094;
+ u16 unk_098;
+ u8 unk_09A[0x64];
+ u8 unk_0FE;
+ u8 unk_0FF;
+ u32 unk_100;
+};
+
+static_assert(sizeof(MiiConfig) == 0x104, "MiiConfig structure has incorrect size");
+#define ASSERT_REG_POSITION(field_name, position) static_assert(offsetof(MiiConfig, field_name) == position, "Field "#field_name" has invalid position")
+ASSERT_REG_POSITION(unk_008, 0x08);
+ASSERT_REG_POSITION(unk_08C, 0x8C);
+ASSERT_REG_POSITION(unk_090, 0x90);
+ASSERT_REG_POSITION(unk_094, 0x94);
+ASSERT_REG_POSITION(unk_0FE, 0xFE);
+#undef ASSERT_REG_POSITION
+
+struct MiiResult {
+ u32 result_code;
+ u8 unk_04;
+ INSERT_PADDING_BYTES(7);
+ u8 unk_0C[0x60];
+ u8 unk_6C[0x16];
+ INSERT_PADDING_BYTES(2);
+};
+static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size");
+#define ASSERT_REG_POSITION(field_name, position) static_assert(offsetof(MiiResult, field_name) == position, "Field "#field_name" has invalid position")
+ASSERT_REG_POSITION(unk_0C, 0x0C);
+ASSERT_REG_POSITION(unk_6C, 0x6C);
+#undef ASSERT_REG_POSITION
+
class MiiSelector final : public Applet {
public:
- MiiSelector(Service::APT::AppletId id);
+ MiiSelector(Service::APT::AppletId id) : Applet(id), started(false) { }
ResultCode ReceiveParameter(const Service::APT::MessageParameter& parameter) override;
ResultCode StartImpl(const Service::APT::AppletStartupParameter& parameter) override;
void Update() override;
bool IsRunning() const override { return started; }
- /// TODO(Subv): Find out what this is actually used for.
- /// It is believed that the application stores the current screen image here.
+ /// This SharedMemory will be created when we receive the LibAppJustStarted message.
+ /// It holds the framebuffer info retrieved by the application with GSPGPU::ImportDisplayCaptureInfo
Kernel::SharedPtr<Kernel::SharedMemory> framebuffer_memory;
/// Whether this applet is currently running instead of the host application or not.
diff --git a/src/core/hle/applets/swkbd.cpp b/src/core/hle/applets/swkbd.cpp
index 1db6b5a17..90c6adc65 100644
--- a/src/core/hle/applets/swkbd.cpp
+++ b/src/core/hle/applets/swkbd.cpp
@@ -24,13 +24,6 @@
namespace HLE {
namespace Applets {
-SoftwareKeyboard::SoftwareKeyboard(Service::APT::AppletId id) : Applet(id), started(false) {
- // Create the SharedMemory that will hold the framebuffer data
- // TODO(Subv): What size should we use here?
- using Kernel::MemoryPermission;
- framebuffer_memory = Kernel::SharedMemory::Create(0x1000, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, "SoftwareKeyboard Memory");
-}
-
ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter const& parameter) {
if (parameter.signal != static_cast<u32>(Service::APT::SignalType::LibAppJustStarted)) {
LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal);
@@ -39,8 +32,23 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con
return ResultCode(-1);
}
+ // The LibAppJustStarted message contains a buffer with the size of the framebuffer shared memory.
+ // Create the SharedMemory that will hold the framebuffer data
+ Service::APT::CaptureBufferInfo capture_info;
+ ASSERT(sizeof(capture_info) == parameter.buffer_size);
+
+ memcpy(&capture_info, parameter.data, sizeof(capture_info));
+
+ using Kernel::MemoryPermission;
+ // Allocate a heap block of the required size for this applet.
+ heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
+ // Create a SharedMemory that directly points to this heap block.
+ framebuffer_memory = Kernel::SharedMemory::CreateForApplet(heap_memory, 0, heap_memory->size(),
+ MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
+ "SoftwareKeyboard Memory");
+
+ // Send the response message with the newly created SharedMemory
Service::APT::MessageParameter result;
- // The buffer passed in parameter contains the data returned by GSPGPU::ImportDisplayCaptureInfo
result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
result.data = nullptr;
result.buffer_size = 0;
diff --git a/src/core/hle/applets/swkbd.h b/src/core/hle/applets/swkbd.h
index cb95b8d90..cf26a8fb7 100644
--- a/src/core/hle/applets/swkbd.h
+++ b/src/core/hle/applets/swkbd.h
@@ -53,8 +53,7 @@ static_assert(sizeof(SoftwareKeyboardConfig) == 0x400, "Software Keyboard Config
class SoftwareKeyboard final : public Applet {
public:
- SoftwareKeyboard(Service::APT::AppletId id);
- ~SoftwareKeyboard() {}
+ SoftwareKeyboard(Service::APT::AppletId id) : Applet(id), started(false) { }
ResultCode ReceiveParameter(const Service::APT::MessageParameter& parameter) override;
ResultCode StartImpl(const Service::APT::AppletStartupParameter& parameter) override;
@@ -72,8 +71,8 @@ public:
*/
void Finalize();
- /// TODO(Subv): Find out what this is actually used for.
- /// It is believed that the application stores the current screen image here.
+ /// This SharedMemory will be created when we receive the LibAppJustStarted message.
+ /// It holds the framebuffer info retrieved by the application with GSPGPU::ImportDisplayCaptureInfo
Kernel::SharedPtr<Kernel::SharedMemory> framebuffer_memory;
/// SharedMemory where the output text will be stored
diff --git a/src/core/hle/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/function_wrappers.h b/src/core/hle/function_wrappers.h
index 4d718b681..bf7f875b6 100644
--- a/src/core/hle/function_wrappers.h
+++ b/src/core/hle/function_wrappers.h
@@ -170,7 +170,8 @@ template<ResultCode func(s64*, u32, s32)> void Wrap() {
template<ResultCode func(u32*, u32, u32, u32, u32)> void Wrap() {
u32 param_1 = 0;
- u32 retval = func(&param_1, PARAM(1), PARAM(2), PARAM(3), PARAM(4)).raw;
+ // The last parameter is passed in R0 instead of R4
+ u32 retval = func(&param_1, PARAM(1), PARAM(2), PARAM(3), PARAM(0)).raw;
Core::g_app_core->SetReg(1, param_1);
FuncReturn(retval);
}
diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp
index 331b1b22a..5c5373517 100644
--- a/src/core/hle/hle.cpp
+++ b/src/core/hle/hle.cpp
@@ -8,15 +8,17 @@
#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"
////////////////////////////////////////////////////////////////////////////////////////////////////
-namespace HLE {
+namespace {
+
+bool reschedule; ///< If true, immediately reschedules the CPU to a new thread
-bool g_reschedule; ///< If true, immediately reschedules the CPU to a new thread
+}
+
+namespace HLE {
void Reschedule(const char *reason) {
DEBUG_ASSERT_MSG(reason != nullptr && strlen(reason) < 256, "Reschedule: Invalid or too long reason.");
@@ -29,13 +31,21 @@ void Reschedule(const char *reason) {
Core::g_app_core->PrepareReschedule();
- g_reschedule = true;
+ reschedule = true;
+}
+
+bool IsReschedulePending() {
+ return reschedule;
+}
+
+void DoneRescheduling() {
+ reschedule = false;
}
void Init() {
Service::Init();
- g_reschedule = false;
+ reschedule = false;
LOG_DEBUG(Kernel, "initialized OK");
}
diff --git a/src/core/hle/hle.h b/src/core/hle/hle.h
index e0b97797c..69ac0ade6 100644
--- a/src/core/hle/hle.h
+++ b/src/core/hle/hle.h
@@ -13,9 +13,9 @@ const Handle INVALID_HANDLE = 0;
namespace HLE {
-extern bool g_reschedule; ///< If true, immediately reschedules the CPU to a new thread
-
void Reschedule(const char *reason);
+bool IsReschedulePending();
+void DoneRescheduling();
void Init();
void Shutdown();
diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp
index 862643448..17ae87aef 100644
--- a/src/core/hle/kernel/memory.cpp
+++ b/src/core/hle/kernel/memory.cpp
@@ -55,6 +55,9 @@ void MemoryInit(u32 mem_type) {
memory_regions[i].size = memory_region_sizes[mem_type][i];
memory_regions[i].used = 0;
memory_regions[i].linear_heap_memory = std::make_shared<std::vector<u8>>();
+ // Reserve enough space for this region of FCRAM.
+ // We do not want this block of memory to be relocated when allocating from it.
+ memory_regions[i].linear_heap_memory->reserve(memory_regions[i].size);
base += memory_regions[i].size;
}
@@ -107,9 +110,7 @@ struct MemoryArea {
// We don't declare the IO regions in here since its handled by other means.
static MemoryArea memory_areas[] = {
- {SHARED_MEMORY_VADDR, SHARED_MEMORY_SIZE, "Shared Memory"}, // Shared memory
{VRAM_VADDR, VRAM_SIZE, "VRAM"}, // Video memory (VRAM)
- {TLS_AREA_VADDR, TLS_AREA_SIZE, "TLS Area"}, // TLS memory
};
}
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 0546f6e16..69302cc82 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -209,7 +209,7 @@ ResultVal<VAddr> Process::LinearAllocate(VAddr target, u32 size, VMAPermission p
return ERR_INVALID_ADDRESS;
}
- // Expansion of the linear heap is only allowed if you do an allocation immediatelly at its
+ // Expansion of the linear heap is only allowed if you do an allocation immediately at its
// end. It's possible to free gaps in the middle of the heap and then reallocate them later,
// but expansions are only allowed at the end.
if (target == heap_end) {
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index 6d2ca96a2..d781ef32c 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -107,6 +107,8 @@ public:
ProcessFlags flags;
/// Kernel compatibility version for this process
u16 kernel_version = 0;
+ /// The default CPU for this process, threads are scheduled on this cpu by default.
+ u8 ideal_processor = 0;
/// The id of this process
u32 process_id = next_process_id++;
@@ -140,8 +142,11 @@ public:
MemoryRegionInfo* memory_region = nullptr;
- /// Bitmask of the used TLS slots
- std::bitset<300> used_tls_slots;
+ /// The Thread Local Storage area is allocated as processes create threads,
+ /// each TLS area is 0x200 bytes, so one page (0x1000) is split up in 8 parts, and each part
+ /// holds the TLS for a specific thread. This vector contains which parts are in use for each page as a bitmask.
+ /// This vector will grow as more pages are allocated for new threads.
+ std::vector<std::bitset<8>> tls_slots;
VAddr GetLinearHeapAreaAddress() const;
VAddr GetLinearHeapBase() const;
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp
index d90f0f00f..6a22c8986 100644
--- a/src/core/hle/kernel/shared_memory.cpp
+++ b/src/core/hle/kernel/shared_memory.cpp
@@ -7,6 +7,7 @@
#include "common/logging/log.h"
#include "core/memory.h"
+#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/shared_memory.h"
namespace Kernel {
@@ -14,93 +15,157 @@ namespace Kernel {
SharedMemory::SharedMemory() {}
SharedMemory::~SharedMemory() {}
-SharedPtr<SharedMemory> SharedMemory::Create(u32 size, MemoryPermission permissions,
- MemoryPermission other_permissions, std::string name) {
+SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u32 size, MemoryPermission permissions,
+ MemoryPermission other_permissions, VAddr address, MemoryRegion region, std::string name) {
SharedPtr<SharedMemory> shared_memory(new SharedMemory);
+ shared_memory->owner_process = owner_process;
shared_memory->name = std::move(name);
- shared_memory->base_address = 0x0;
- shared_memory->fixed_address = 0x0;
shared_memory->size = size;
shared_memory->permissions = permissions;
shared_memory->other_permissions = other_permissions;
+ if (address == 0) {
+ // We need to allocate a block from the Linear Heap ourselves.
+ // We'll manually allocate some memory from the linear heap in the specified region.
+ MemoryRegionInfo* memory_region = GetMemoryRegion(region);
+ auto& linheap_memory = memory_region->linear_heap_memory;
+
+ ASSERT_MSG(linheap_memory->size() + size <= memory_region->size, "Not enough space in region to allocate shared memory!");
+
+ shared_memory->backing_block = linheap_memory;
+ shared_memory->backing_block_offset = linheap_memory->size();
+ // Allocate some memory from the end of the linear heap for this region.
+ linheap_memory->insert(linheap_memory->end(), size, 0);
+ memory_region->used += size;
+
+ shared_memory->linear_heap_phys_address = Memory::FCRAM_PADDR + memory_region->base + shared_memory->backing_block_offset;
+
+ // Increase the amount of used linear heap memory for the owner process.
+ if (shared_memory->owner_process != nullptr) {
+ shared_memory->owner_process->linear_heap_used += size;
+ }
+
+ // Refresh the address mappings for the current process.
+ if (Kernel::g_current_process != nullptr) {
+ Kernel::g_current_process->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get());
+ }
+ } else {
+ // TODO(Subv): What happens if an application tries to create multiple memory blocks pointing to the same address?
+ auto& vm_manager = shared_memory->owner_process->vm_manager;
+ // The memory is already available and mapped in the owner process.
+ auto vma = vm_manager.FindVMA(address)->second;
+ // Copy it over to our own storage
+ shared_memory->backing_block = std::make_shared<std::vector<u8>>(vma.backing_block->data() + vma.offset,
+ vma.backing_block->data() + vma.offset + size);
+ shared_memory->backing_block_offset = 0;
+ // Unmap the existing pages
+ vm_manager.UnmapRange(address, size);
+ // Map our own block into the address space
+ vm_manager.MapMemoryBlock(address, shared_memory->backing_block, 0, size, MemoryState::Shared);
+ // Reprotect the block with the new permissions
+ vm_manager.ReprotectRange(address, size, ConvertPermissions(permissions));
+ }
+
+ shared_memory->base_address = address;
return shared_memory;
}
-ResultCode SharedMemory::Map(VAddr address, MemoryPermission permissions,
- MemoryPermission other_permissions) {
+SharedPtr<SharedMemory> SharedMemory::CreateForApplet(std::shared_ptr<std::vector<u8>> heap_block, u32 offset, u32 size,
+ MemoryPermission permissions, MemoryPermission other_permissions, std::string name) {
+ SharedPtr<SharedMemory> shared_memory(new SharedMemory);
- if (base_address != 0) {
- LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s: already mapped at 0x%08X!",
- GetObjectId(), address, name.c_str(), base_address);
- // TODO: Verify error code with hardware
- return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel,
- ErrorSummary::InvalidArgument, ErrorLevel::Permanent);
- }
+ shared_memory->owner_process = nullptr;
+ shared_memory->name = std::move(name);
+ shared_memory->size = size;
+ shared_memory->permissions = permissions;
+ shared_memory->other_permissions = other_permissions;
+ shared_memory->backing_block = heap_block;
+ shared_memory->backing_block_offset = offset;
+ shared_memory->base_address = Memory::HEAP_VADDR + offset;
- // TODO(Subv): Return E0E01BEE when permissions and other_permissions don't
- // match what was specified when the memory block was created.
+ return shared_memory;
+}
- // TODO(Subv): Return E0E01BEE when address should be 0.
- // Note: Find out when that's the case.
+ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermission permissions,
+ MemoryPermission other_permissions) {
- if (fixed_address != 0) {
- if (address != 0 && address != fixed_address) {
- LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s: fixed_addres is 0x%08X!",
- GetObjectId(), address, name.c_str(), fixed_address);
- // TODO: Verify error code with hardware
- return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel,
- ErrorSummary::InvalidArgument, ErrorLevel::Permanent);
- }
+ MemoryPermission own_other_permissions = target_process == owner_process ? this->permissions : this->other_permissions;
- // HACK(yuriks): This is only here to support the APT shared font mapping right now.
- // Later, this should actually map the memory block onto the address space.
- return RESULT_SUCCESS;
+ // Automatically allocated memory blocks can only be mapped with other_permissions = DontCare
+ if (base_address == 0 && other_permissions != MemoryPermission::DontCare) {
+ return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage);
}
- if (address < Memory::SHARED_MEMORY_VADDR || address + size >= Memory::SHARED_MEMORY_VADDR_END) {
- LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s outside of shared mem bounds!",
- GetObjectId(), address, name.c_str());
- // TODO: Verify error code with hardware
- return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel,
- ErrorSummary::InvalidArgument, ErrorLevel::Permanent);
+ // Error out if the requested permissions don't match what the creator process allows.
+ if (static_cast<u32>(permissions) & ~static_cast<u32>(own_other_permissions)) {
+ LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, permissions don't match",
+ GetObjectId(), address, name.c_str());
+ return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage);
}
- // TODO: Test permissions
+ // Heap-backed memory blocks can not be mapped with other_permissions = DontCare
+ if (base_address != 0 && other_permissions == MemoryPermission::DontCare) {
+ LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, permissions don't match",
+ GetObjectId(), address, name.c_str());
+ return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+ }
- // HACK: Since there's no way to write to the memory block without mapping it onto the game
- // process yet, at least initialize memory the first time it's mapped.
- if (address != this->base_address) {
- std::memset(Memory::GetPointer(address), 0, size);
+ // Error out if the provided permissions are not compatible with what the creator process needs.
+ if (other_permissions != MemoryPermission::DontCare &&
+ static_cast<u32>(this->permissions) & ~static_cast<u32>(other_permissions)) {
+ LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, permissions don't match",
+ GetObjectId(), address, name.c_str());
+ return ResultCode(ErrorDescription::WrongPermission, ErrorModule::OS, ErrorSummary::WrongArgument, ErrorLevel::Permanent);
}
- this->base_address = address;
+ // TODO(Subv): Check for the Shared Device Mem flag in the creator process.
+ /*if (was_created_with_shared_device_mem && address != 0) {
+ return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+ }*/
- return RESULT_SUCCESS;
-}
+ // TODO(Subv): The same process that created a SharedMemory object
+ // can not map it in its own address space unless it was created with addr=0, result 0xD900182C.
-ResultCode SharedMemory::Unmap(VAddr address) {
- if (base_address == 0) {
- // TODO(Subv): Verify what actually happens when you want to unmap a memory block that
- // was originally mapped with address = 0
- return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+ if (address != 0) {
+ if (address < Memory::HEAP_VADDR || address + size >= Memory::SHARED_MEMORY_VADDR_END) {
+ LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, invalid address",
+ GetObjectId(), address, name.c_str());
+ return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS,
+ ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+ }
}
- if (base_address != address)
- return ResultCode(ErrorDescription::WrongAddress, ErrorModule::OS, ErrorSummary::InvalidState, ErrorLevel::Usage);
+ VAddr target_address = address;
- base_address = 0;
+ if (base_address == 0 && target_address == 0) {
+ // Calculate the address at which to map the memory block.
+ target_address = Memory::PhysicalToVirtualAddress(linear_heap_phys_address);
+ }
+
+ // Map the memory block into the target process
+ auto result = target_process->vm_manager.MapMemoryBlock(target_address, backing_block, backing_block_offset, size, MemoryState::Shared);
+ if (result.Failed()) {
+ LOG_ERROR(Kernel, "cannot map id=%u, target_address=0x%08X name=%s, error mapping to virtual memory",
+ GetObjectId(), target_address, name.c_str());
+ return result.Code();
+ }
- return RESULT_SUCCESS;
+ return target_process->vm_manager.ReprotectRange(target_address, size, ConvertPermissions(permissions));
}
-u8* SharedMemory::GetPointer(u32 offset) {
- if (base_address != 0)
- return Memory::GetPointer(base_address + offset);
+ResultCode SharedMemory::Unmap(Process* target_process, VAddr address) {
+ // TODO(Subv): Verify what happens if the application tries to unmap an address that is not mapped to a SharedMemory.
+ return target_process->vm_manager.UnmapRange(address, size);
+}
+
+VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) {
+ u32 masked_permissions = static_cast<u32>(permission) & static_cast<u32>(MemoryPermission::ReadWriteExecute);
+ return static_cast<VMAPermission>(masked_permissions);
+};
- LOG_ERROR(Kernel_SVC, "memory block id=%u not mapped!", GetObjectId());
- return nullptr;
+u8* SharedMemory::GetPointer(u32 offset) {
+ return backing_block->data() + backing_block_offset + offset;
}
} // namespace
diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h
index b51049ad0..0c404a9f8 100644
--- a/src/core/hle/kernel/shared_memory.h
+++ b/src/core/hle/kernel/shared_memory.h
@@ -9,6 +9,7 @@
#include "common/common_types.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/result.h"
namespace Kernel {
@@ -29,14 +30,29 @@ enum class MemoryPermission : u32 {
class SharedMemory final : public Object {
public:
/**
- * Creates a shared memory object
+ * Creates a shared memory object.
+ * @param owner_process Process that created this shared memory object.
* @param size Size of the memory block. Must be page-aligned.
* @param permissions Permission restrictions applied to the process which created the block.
* @param other_permissions Permission restrictions applied to other processes mapping the block.
+ * @param address The address from which to map the Shared Memory.
+ * @param region If the address is 0, the shared memory will be allocated in this region of the linear heap.
* @param name Optional object name, used for debugging purposes.
*/
- static SharedPtr<SharedMemory> Create(u32 size, MemoryPermission permissions,
- MemoryPermission other_permissions, std::string name = "Unknown");
+ static SharedPtr<SharedMemory> Create(SharedPtr<Process> owner_process, u32 size, MemoryPermission permissions,
+ MemoryPermission other_permissions, VAddr address = 0, MemoryRegion region = MemoryRegion::BASE, std::string name = "Unknown");
+
+ /**
+ * Creates a shared memory object from a block of memory managed by an HLE applet.
+ * @param heap_block Heap block of the HLE applet.
+ * @param offset The offset into the heap block that the SharedMemory will map.
+ * @param size Size of the memory block. Must be page-aligned.
+ * @param permissions Permission restrictions applied to the process which created the block.
+ * @param other_permissions Permission restrictions applied to other processes mapping the block.
+ * @param name Optional object name, used for debugging purposes.
+ */
+ static SharedPtr<SharedMemory> CreateForApplet(std::shared_ptr<std::vector<u8>> heap_block, u32 offset, u32 size,
+ MemoryPermission permissions, MemoryPermission other_permissions, std::string name = "Unknown Applet");
std::string GetTypeName() const override { return "SharedMemory"; }
std::string GetName() const override { return name; }
@@ -45,19 +61,27 @@ public:
HandleType GetHandleType() const override { return HANDLE_TYPE; }
/**
- * Maps a shared memory block to an address in system memory
+ * Converts the specified MemoryPermission into the equivalent VMAPermission.
+ * @param permission The MemoryPermission to convert.
+ */
+ static VMAPermission ConvertPermissions(MemoryPermission permission);
+
+ /**
+ * Maps a shared memory block to an address in the target process' address space
+ * @param target_process Process on which to map the memory block.
* @param address Address in system memory to map shared memory block to
* @param permissions Memory block map permissions (specified by SVC field)
* @param other_permissions Memory block map other permissions (specified by SVC field)
*/
- ResultCode Map(VAddr address, MemoryPermission permissions, MemoryPermission other_permissions);
+ ResultCode Map(Process* target_process, VAddr address, MemoryPermission permissions, MemoryPermission other_permissions);
/**
* Unmaps a shared memory block from the specified address in system memory
+ * @param target_process Process from which to umap the memory block.
* @param address Address in system memory where the shared memory block is mapped
* @return Result code of the unmap operation
*/
- ResultCode Unmap(VAddr address);
+ ResultCode Unmap(Process* target_process, VAddr address);
/**
* Gets a pointer to the shared memory block
@@ -66,10 +90,16 @@ public:
*/
u8* GetPointer(u32 offset = 0);
- /// Address of shared memory block in the process.
+ /// Process that created this shared memory block.
+ SharedPtr<Process> owner_process;
+ /// Address of shared memory block in the owner process if specified.
VAddr base_address;
- /// Fixed address to allow mapping to. Used for blocks created from the linear heap.
- VAddr fixed_address;
+ /// Physical address of the shared memory block in the linear heap if no address was specified during creation.
+ PAddr linear_heap_phys_address;
+ /// Backing memory for this shared memory block.
+ std::shared_ptr<std::vector<u8>> backing_block;
+ /// Offset into the backing block for this shared memory.
+ u32 backing_block_offset;
/// Size of the memory block. Page-aligned.
u32 size;
/// Permission restrictions applied to the process which created the block.
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index bf32f653d..43def6146 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -117,9 +117,10 @@ void Thread::Stop() {
}
wait_objects.clear();
- Kernel::g_current_process->used_tls_slots[tls_index] = false;
- g_current_process->misc_memory_used -= Memory::TLS_ENTRY_SIZE;
- g_current_process->memory_region->used -= Memory::TLS_ENTRY_SIZE;
+ // Mark the TLS slot in the thread's page as free.
+ u32 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE;
+ u32 tls_slot = ((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE;
+ Kernel::g_current_process->tls_slots[tls_page].reset(tls_slot);
HLE::Reschedule(__func__);
}
@@ -366,6 +367,31 @@ static void DebugThreadQueue() {
}
}
+/**
+ * Finds a free location for the TLS section of a thread.
+ * @param tls_slots The TLS page array of the thread's owner process.
+ * Returns a tuple of (page, slot, alloc_needed) where:
+ * page: The index of the first allocated TLS page that has free slots.
+ * slot: The index of the first free slot in the indicated page.
+ * alloc_needed: Whether there's a need to allocate a new TLS page (All pages are full).
+ */
+std::tuple<u32, u32, bool> GetFreeThreadLocalSlot(std::vector<std::bitset<8>>& tls_slots) {
+ // Iterate over all the allocated pages, and try to find one where not all slots are used.
+ for (unsigned page = 0; page < tls_slots.size(); ++page) {
+ const auto& page_tls_slots = tls_slots[page];
+ if (!page_tls_slots.all()) {
+ // We found a page with at least one free slot, find which slot it is
+ for (unsigned slot = 0; slot < page_tls_slots.size(); ++slot) {
+ if (!page_tls_slots.test(slot)) {
+ return std::make_tuple(page, slot, false);
+ }
+ }
+ }
+ }
+
+ return std::make_tuple(0, 0, true);
+}
+
ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, s32 priority,
u32 arg, s32 processor_id, VAddr stack_top) {
if (priority < THREADPRIO_HIGHEST || priority > THREADPRIO_LOWEST) {
@@ -403,22 +429,50 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
thread->name = std::move(name);
thread->callback_handle = wakeup_callback_handle_table.Create(thread).MoveFrom();
thread->owner_process = g_current_process;
- thread->tls_index = -1;
thread->waitsynch_waited = false;
// Find the next available TLS index, and mark it as used
- auto& used_tls_slots = Kernel::g_current_process->used_tls_slots;
- for (unsigned int i = 0; i < used_tls_slots.size(); ++i) {
- if (used_tls_slots[i] == false) {
- thread->tls_index = i;
- used_tls_slots[i] = true;
- break;
+ auto& tls_slots = Kernel::g_current_process->tls_slots;
+ bool needs_allocation = true;
+ u32 available_page; // Which allocated page has free space
+ u32 available_slot; // Which slot within the page is free
+
+ std::tie(available_page, available_slot, needs_allocation) = GetFreeThreadLocalSlot(tls_slots);
+
+ if (needs_allocation) {
+ // There are no already-allocated pages with free slots, lets allocate a new one.
+ // TLS pages are allocated from the BASE region in the linear heap.
+ MemoryRegionInfo* memory_region = GetMemoryRegion(MemoryRegion::BASE);
+ auto& linheap_memory = memory_region->linear_heap_memory;
+
+ if (linheap_memory->size() + Memory::PAGE_SIZE > memory_region->size) {
+ LOG_ERROR(Kernel_SVC, "Not enough space in region to allocate a new TLS page for thread");
+ return ResultCode(ErrorDescription::OutOfMemory, ErrorModule::Kernel, ErrorSummary::OutOfResource, ErrorLevel::Permanent);
}
+
+ u32 offset = linheap_memory->size();
+
+ // Allocate some memory from the end of the linear heap for this region.
+ linheap_memory->insert(linheap_memory->end(), Memory::PAGE_SIZE, 0);
+ memory_region->used += Memory::PAGE_SIZE;
+ Kernel::g_current_process->linear_heap_used += Memory::PAGE_SIZE;
+
+ tls_slots.emplace_back(0); // The page is completely available at the start
+ available_page = tls_slots.size() - 1;
+ available_slot = 0; // Use the first slot in the new page
+
+ auto& vm_manager = Kernel::g_current_process->vm_manager;
+ vm_manager.RefreshMemoryBlockMappings(linheap_memory.get());
+
+ // Map the page to the current process' address space.
+ // TODO(Subv): Find the correct MemoryState for this region.
+ vm_manager.MapMemoryBlock(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE,
+ linheap_memory, offset, Memory::PAGE_SIZE, MemoryState::Private);
}
- ASSERT_MSG(thread->tls_index != -1, "Out of TLS space");
- g_current_process->misc_memory_used += Memory::TLS_ENTRY_SIZE;
- g_current_process->memory_region->used += Memory::TLS_ENTRY_SIZE;
+ // Mark the slot as used
+ tls_slots[available_page].set(available_slot);
+ thread->tls_address = Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE + available_slot * Memory::TLS_ENTRY_SIZE;
// TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used
// to initialize the context
@@ -472,6 +526,8 @@ SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) {
SharedPtr<Thread> thread = thread_res.MoveFrom();
+ thread->context.fpscr = FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO | FPSCR_IXC; // 0x03C00010
+
// Run new "main" thread
SwitchContext(thread.get());
@@ -483,7 +539,8 @@ void Reschedule() {
Thread* cur = GetCurrentThread();
Thread* next = PopNextReadyThread();
- HLE::g_reschedule = false;
+
+ HLE::DoneRescheduling();
// Don't bother switching to the same thread
if (next == cur)
@@ -508,10 +565,6 @@ void Thread::SetWaitSynchronizationOutput(s32 output) {
context.cpu_registers[1] = output;
}
-VAddr Thread::GetTLSAddress() const {
- return Memory::TLS_AREA_VADDR + tls_index * Memory::TLS_ENTRY_SIZE;
-}
-
////////////////////////////////////////////////////////////////////////////////////////////////////
void ThreadingInit() {
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 97ba57fc5..deab5d5a6 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -127,7 +127,7 @@ public:
* Returns the Thread Local Storage address of the current thread
* @returns VAddr of the thread's TLS
*/
- VAddr GetTLSAddress() const;
+ VAddr GetTLSAddress() const { return tls_address; }
Core::ThreadContext context;
@@ -144,7 +144,7 @@ public:
s32 processor_id;
- s32 tls_index; ///< Index of the Thread Local Storage of the thread
+ VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread
bool waitsynch_waited; ///< Set to true if the last svcWaitSynch call caused the thread to wait
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 2d22652d9..bfb3327ce 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,6 +17,8 @@
/// Detailed description of the error. This listing is likely incomplete.
enum class ErrorDescription : u32 {
Success = 0,
+ WrongPermission = 46,
+ OS_InvalidBufferDescriptor = 48,
WrongAddress = 53,
FS_NotFound = 120,
FS_AlreadyExists = 190,
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/act_a.cpp b/src/core/hle/service/act_a.cpp
new file mode 100644
index 000000000..3a775fa90
--- /dev/null
+++ b/src/core/hle/service/act_a.cpp
@@ -0,0 +1,26 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/act_a.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Namespace ACT_A
+
+namespace ACT_A {
+
+const Interface::FunctionInfo FunctionTable[] = {
+ {0x041300C2, nullptr, "UpdateMiiImage"},
+ {0x041B0142, nullptr, "AgreeEula"},
+ {0x04210042, nullptr, "UploadMii"},
+ {0x04230082, nullptr, "ValidateMailAddress"},
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Interface class
+
+Interface::Interface() {
+ Register(FunctionTable);
+}
+
+} // namespace
diff --git a/src/core/hle/service/act_a.h b/src/core/hle/service/act_a.h
new file mode 100644
index 000000000..765cae644
--- /dev/null
+++ b/src/core/hle/service/act_a.h
@@ -0,0 +1,23 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Namespace ACT_A
+
+namespace ACT_A {
+
+class Interface : public Service::Interface {
+public:
+ Interface();
+
+ std::string GetPortName() const override {
+ return "act:a";
+ }
+};
+
+} // namespace
diff --git a/src/core/hle/service/act_u.cpp b/src/core/hle/service/act_u.cpp
index b23d17fba..05de4d002 100644
--- a/src/core/hle/service/act_u.cpp
+++ b/src/core/hle/service/act_u.cpp
@@ -10,7 +10,10 @@
namespace ACT_U {
const Interface::FunctionInfo FunctionTable[] = {
+ {0x00010084, nullptr, "Initialize"},
+ {0x00020040, nullptr, "GetErrorCode"},
{0x000600C2, nullptr, "GetAccountDataBlock"},
+ {0x000D0040, nullptr, "GenerateUuid"},
};
////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 9591522e5..3f71e7f2b 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -43,7 +43,7 @@ void FindContentInfos(Service::Interface* self) {
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%016lx, content_cound=%u, content_ids_pointer=0x%08x, content_info_pointer=0x%08x",
+ 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);
}
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index e6fcbc714..bbf170b71 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -12,6 +12,7 @@
#include "core/hle/service/apt/apt_a.h"
#include "core/hle/service/apt/apt_s.h"
#include "core/hle/service/apt/apt_u.h"
+#include "core/hle/service/apt/bcfnt/bcfnt.h"
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/ptm/ptm.h"
@@ -23,23 +24,14 @@
namespace Service {
namespace APT {
-// Address used for shared font (as observed on HW)
-// TODO(bunnei): This is the hard-coded address where we currently dump the shared font from via
-// https://github.com/citra-emu/3dsutils. This is technically a hack, and will not work at any
-// address other than 0x18000000 due to internal pointers in the shared font dump that would need to
-// be relocated. This might be fixed by dumping the shared font @ address 0x00000000 and then
-// correctly mapping it in Citra, however we still do not understand how the mapping is determined.
-static const VAddr SHARED_FONT_VADDR = 0x18000000;
-
/// Handle to shared memory region designated to for shared system font
static Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem;
+static bool shared_font_relocated = false;
static Kernel::SharedPtr<Kernel::Mutex> lock;
static Kernel::SharedPtr<Kernel::Event> notification_event; ///< APT notification event
static Kernel::SharedPtr<Kernel::Event> parameter_event; ///< APT parameter event
-static std::shared_ptr<std::vector<u8>> shared_font;
-
static u32 cpu_percent; ///< CPU time available to the running application
// APT::CheckNew3DSApp will check this unknown_ns_state_field to determine processing mode
@@ -78,23 +70,25 @@ void Initialize(Service::Interface* self) {
void GetSharedFont(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- if (shared_font != nullptr) {
- // TODO(yuriks): This is a hack to keep this working right now even with our completely
- // broken shared memory system.
- shared_font_mem->fixed_address = SHARED_FONT_VADDR;
- Kernel::g_current_process->vm_manager.MapMemoryBlock(shared_font_mem->fixed_address,
- shared_font, 0, shared_font_mem->size, Kernel::MemoryState::Shared);
-
- cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2);
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[2] = SHARED_FONT_VADDR;
- cmd_buff[3] = IPC::MoveHandleDesc();
- cmd_buff[4] = Kernel::g_handle_table.Create(shared_font_mem).MoveFrom();
- } else {
- cmd_buff[0] = IPC::MakeHeader(0x44, 1, 0);
- cmd_buff[1] = -1; // Generic error (not really possible to verify this on hardware)
- LOG_ERROR(Kernel_SVC, "called, but %s has not been loaded!", SHARED_FONT);
+ // The shared font has to be relocated to the new address before being passed to the application.
+ VAddr target_address = Memory::PhysicalToVirtualAddress(shared_font_mem->linear_heap_phys_address);
+ // The shared font dumped by 3dsutils (https://github.com/citra-emu/3dsutils) uses this address as base,
+ // so we relocate it from there to our real address.
+ // TODO(Subv): This address is wrong if the shared font is dumped from a n3DS,
+ // we need a way to automatically calculate the original address of the font from the file.
+ static const VAddr SHARED_FONT_VADDR = 0x18000000;
+ if (!shared_font_relocated) {
+ BCFNT::RelocateSharedFont(shared_font_mem, SHARED_FONT_VADDR, target_address);
+ shared_font_relocated = true;
}
+ cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2);
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ // Since the SharedMemory interface doesn't provide the address at which the memory was allocated,
+ // the real APT service calculates this address by scanning the entire address space (using svcQueryMemory)
+ // and searches for an allocation of the same size as the Shared Font.
+ cmd_buff[2] = target_address;
+ cmd_buff[3] = IPC::MoveHandleDesc();
+ cmd_buff[4] = Kernel::g_handle_table.Create(shared_font_mem).MoveFrom();
}
void NotifyToWait(Service::Interface* self) {
@@ -483,14 +477,12 @@ void Init() {
FileUtil::IOFile file(filepath, "rb");
if (file.IsOpen()) {
- // Read shared font data
- shared_font = std::make_shared<std::vector<u8>>((size_t)file.GetSize());
- file.ReadBytes(shared_font->data(), shared_font->size());
-
// Create shared font memory object
using Kernel::MemoryPermission;
- shared_font_mem = Kernel::SharedMemory::Create(3 * 1024 * 1024, // 3MB
- MemoryPermission::ReadWrite, MemoryPermission::Read, "APT_U:shared_font_mem");
+ shared_font_mem = Kernel::SharedMemory::Create(nullptr, 0x332000, // 3272 KB
+ MemoryPermission::ReadWrite, MemoryPermission::Read, 0, Kernel::MemoryRegion::SYSTEM, "APT:SharedFont");
+ // Read shared font data
+ file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize());
} else {
LOG_WARNING(Service_APT, "Unable to load shared font: %s", filepath.c_str());
shared_font_mem = nullptr;
@@ -510,8 +502,8 @@ void Init() {
}
void Shutdown() {
- shared_font = nullptr;
shared_font_mem = nullptr;
+ shared_font_relocated = false;
lock = nullptr;
notification_event = nullptr;
parameter_event = nullptr;
diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h
index fd3c2bd37..ed7c47cca 100644
--- a/src/core/hle/service/apt/apt.h
+++ b/src/core/hle/service/apt/apt.h
@@ -5,6 +5,7 @@
#pragma once
#include "common/common_types.h"
+#include "common/swap.h"
#include "core/hle/kernel/kernel.h"
@@ -31,6 +32,20 @@ struct AppletStartupParameter {
u8* data = nullptr;
};
+/// Used by the application to pass information about the current framebuffer to applets.
+struct CaptureBufferInfo {
+ u32_le size;
+ u8 is_3d;
+ INSERT_PADDING_BYTES(0x3); // Padding for alignment
+ u32_le top_screen_left_offset;
+ u32_le top_screen_right_offset;
+ u32_le top_screen_format;
+ u32_le bottom_screen_left_offset;
+ u32_le bottom_screen_right_offset;
+ u32_le bottom_screen_format;
+};
+static_assert(sizeof(CaptureBufferInfo) == 0x20, "CaptureBufferInfo struct has incorrect size");
+
/// Signals used by APT functions
enum class SignalType : u32 {
None = 0x0,
diff --git a/src/core/hle/service/apt/bcfnt/bcfnt.cpp b/src/core/hle/service/apt/bcfnt/bcfnt.cpp
new file mode 100644
index 000000000..b0d39d4a5
--- /dev/null
+++ b/src/core/hle/service/apt/bcfnt/bcfnt.cpp
@@ -0,0 +1,71 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/apt/bcfnt/bcfnt.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace APT {
+namespace BCFNT {
+
+void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr previous_address, VAddr new_address) {
+ static const u32 SharedFontStartOffset = 0x80;
+ u8* data = shared_font->GetPointer(SharedFontStartOffset);
+
+ CFNT cfnt;
+ memcpy(&cfnt, data, sizeof(cfnt));
+
+ // Advance past the header
+ data = shared_font->GetPointer(SharedFontStartOffset + cfnt.header_size);
+
+ for (unsigned block = 0; block < cfnt.num_blocks; ++block) {
+
+ u32 section_size = 0;
+ if (memcmp(data, "FINF", 4) == 0) {
+ BCFNT::FINF finf;
+ memcpy(&finf, data, sizeof(finf));
+ section_size = finf.section_size;
+
+ // Relocate the offsets in the FINF section
+ finf.cmap_offset += new_address - previous_address;
+ finf.cwdh_offset += new_address - previous_address;
+ finf.tglp_offset += new_address - previous_address;
+
+ memcpy(data, &finf, sizeof(finf));
+ } else if (memcmp(data, "CMAP", 4) == 0) {
+ BCFNT::CMAP cmap;
+ memcpy(&cmap, data, sizeof(cmap));
+ section_size = cmap.section_size;
+
+ // Relocate the offsets in the CMAP section
+ cmap.next_cmap_offset += new_address - previous_address;
+
+ memcpy(data, &cmap, sizeof(cmap));
+ } else if (memcmp(data, "CWDH", 4) == 0) {
+ BCFNT::CWDH cwdh;
+ memcpy(&cwdh, data, sizeof(cwdh));
+ section_size = cwdh.section_size;
+
+ // Relocate the offsets in the CWDH section
+ cwdh.next_cwdh_offset += new_address - previous_address;
+
+ memcpy(data, &cwdh, sizeof(cwdh));
+ } else if (memcmp(data, "TGLP", 4) == 0) {
+ BCFNT::TGLP tglp;
+ memcpy(&tglp, data, sizeof(tglp));
+ section_size = tglp.section_size;
+
+ // Relocate the offsets in the TGLP section
+ tglp.sheet_data_offset += new_address - previous_address;
+
+ memcpy(data, &tglp, sizeof(tglp));
+ }
+
+ data += section_size;
+ }
+}
+
+} // namespace BCFNT
+} // namespace APT
+} // namespace Service \ No newline at end of file
diff --git a/src/core/hle/service/apt/bcfnt/bcfnt.h b/src/core/hle/service/apt/bcfnt/bcfnt.h
new file mode 100644
index 000000000..388c6bea0
--- /dev/null
+++ b/src/core/hle/service/apt/bcfnt/bcfnt.h
@@ -0,0 +1,87 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/swap.h"
+
+#include "core/hle/kernel/shared_memory.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace APT {
+namespace BCFNT { ///< BCFNT Shared Font file structures
+
+struct CFNT {
+ u8 magic[4];
+ u16_le endianness;
+ u16_le header_size;
+ u32_le version;
+ u32_le file_size;
+ u32_le num_blocks;
+};
+
+struct FINF {
+ u8 magic[4];
+ u32_le section_size;
+ u8 font_type;
+ u8 line_feed;
+ u16_le alter_char_index;
+ u8 default_width[3];
+ u8 encoding;
+ u32_le tglp_offset;
+ u32_le cwdh_offset;
+ u32_le cmap_offset;
+ u8 height;
+ u8 width;
+ u8 ascent;
+ u8 reserved;
+};
+
+struct TGLP {
+ u8 magic[4];
+ u32_le section_size;
+ u8 cell_width;
+ u8 cell_height;
+ u8 baseline_position;
+ u8 max_character_width;
+ u32_le sheet_size;
+ u16_le num_sheets;
+ u16_le sheet_image_format;
+ u16_le num_columns;
+ u16_le num_rows;
+ u16_le sheet_width;
+ u16_le sheet_height;
+ u32_le sheet_data_offset;
+};
+
+struct CMAP {
+ u8 magic[4];
+ u32_le section_size;
+ u16_le code_begin;
+ u16_le code_end;
+ u16_le mapping_method;
+ u16_le reserved;
+ u32_le next_cmap_offset;
+};
+
+struct CWDH {
+ u8 magic[4];
+ u32_le section_size;
+ u16_le start_index;
+ u16_le end_index;
+ u32_le next_cwdh_offset;
+};
+
+/**
+ * Relocates the internal addresses of the BCFNT Shared Font to the new base.
+ * @param shared_font SharedMemory object that contains the Shared Font
+ * @param previous_address Previous address at which the offsets in the structure were based.
+ * @param new_address New base for the offsets in the structure.
+ */
+void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr previous_address, VAddr new_address);
+
+} // namespace BCFNT
+} // namespace APT
+} // namespace Service
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index 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/csnd_snd.cpp b/src/core/hle/service/csnd_snd.cpp
index 6318bf2a7..d2bb8941c 100644
--- a/src/core/hle/service/csnd_snd.cpp
+++ b/src/core/hle/service/csnd_snd.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <cstring>
+#include "common/alignment.h"
#include "core/hle/hle.h"
#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/shared_memory.h"
@@ -41,14 +42,16 @@ static Kernel::SharedPtr<Kernel::Mutex> mutex = nullptr;
void Initialize(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- shared_memory = Kernel::SharedMemory::Create(cmd_buff[1],
- Kernel::MemoryPermission::ReadWrite,
- Kernel::MemoryPermission::ReadWrite, "CSNDSharedMem");
+ u32 size = Common::AlignUp(cmd_buff[1], Memory::PAGE_SIZE);
+ using Kernel::MemoryPermission;
+ shared_memory = Kernel::SharedMemory::Create(nullptr, size,
+ MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
+ 0, Kernel::MemoryRegion::BASE, "CSND:SharedMemory");
mutex = Kernel::Mutex::Create(false);
- cmd_buff[1] = 0;
- cmd_buff[2] = 0x4000000;
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = IPC::MoveHandleDesc(2);
cmd_buff[3] = Kernel::g_handle_table.Create(mutex).MoveFrom();
cmd_buff[4] = Kernel::g_handle_table.Create(shared_memory).MoveFrom();
}
diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp
index 08e437125..10730d7ac 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;
+ }
- for (size_t i = 0; i < size; i++) {
+ 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 (u32 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);
+ cmd_buff[2] = static_cast<u32>(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,10 +438,11 @@ 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?
+ cmd_buff[2] = 0; // Not using headphones
- LOG_WARNING(Service_DSP, "(STUBBED) called");
+ LOG_DEBUG(Service_DSP, "called");
}
/**
@@ -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/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp
index e9588cb72..cc51ede0c 100644
--- a/src/core/hle/service/fs/archive.cpp
+++ b/src/core/hle/service/fs/archive.cpp
@@ -114,6 +114,7 @@ ResultVal<bool> File::SyncRequest() {
return read.Code();
}
cmd_buff[2] = static_cast<u32>(*read);
+ Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(address), length);
break;
}
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 0c655395e..8ded9b09b 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"
@@ -45,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)));
@@ -291,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
@@ -337,8 +335,9 @@ static void RegisterInterruptRelayQueue(Service::Interface* self) {
g_interrupt_event->name = "GSP_GPU::interrupt_event";
using Kernel::MemoryPermission;
- g_shared_memory = Kernel::SharedMemory::Create(0x1000, MemoryPermission::ReadWrite,
- MemoryPermission::ReadWrite, "GSPSharedMem");
+ g_shared_memory = Kernel::SharedMemory::Create(nullptr, 0x1000,
+ MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
+ 0, Kernel::MemoryRegion::BASE, "GSP:SharedMemory");
Handle shmem_handle = Kernel::g_handle_table.Create(g_shared_memory).MoveFrom();
@@ -374,6 +373,9 @@ static void UnregisterInterruptRelayQueue(Service::Interface* self) {
* @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;
@@ -408,6 +410,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
@@ -419,18 +423,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:
{
@@ -517,13 +524,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;
}
@@ -628,6 +630,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"},
@@ -651,8 +682,8 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00130042, RegisterInterruptRelayQueue, "RegisterInterruptRelayQueue"},
{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"},
@@ -673,11 +704,13 @@ Interface::Interface() {
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 55a993bb8..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"
////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 1053d0f40..d216cecb4 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -280,8 +280,9 @@ void Init() {
AddService(new HID_SPVR_Interface);
using Kernel::MemoryPermission;
- shared_mem = SharedMemory::Create(0x1000, MemoryPermission::ReadWrite,
- MemoryPermission::Read, "HID:SharedMem");
+ shared_mem = SharedMemory::Create(nullptr, 0x1000,
+ MemoryPermission::ReadWrite, MemoryPermission::Read,
+ 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory");
next_pad_index = 0;
next_touch_index = 0;
diff --git a/src/core/hle/service/ir/ir.cpp b/src/core/hle/service/ir/ir.cpp
index 505c441c6..079a87e48 100644
--- a/src/core/hle/service/ir/ir.cpp
+++ b/src/core/hle/service/ir/ir.cpp
@@ -94,8 +94,9 @@ void Init() {
AddService(new IR_User_Interface);
using Kernel::MemoryPermission;
- shared_memory = SharedMemory::Create(0x1000, Kernel::MemoryPermission::ReadWrite,
- Kernel::MemoryPermission::ReadWrite, "IR:SharedMemory");
+ shared_memory = SharedMemory::Create(nullptr, 0x1000,
+ Kernel::MemoryPermission::ReadWrite, Kernel::MemoryPermission::ReadWrite,
+ 0, Kernel::MemoryRegion::BASE, "IR:SharedMemory");
transfer_shared_memory = nullptr;
// Create event handle(s)
diff --git a/src/core/hle/service/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 0fe3a4d7a..d7e7d4fe3 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -7,6 +7,7 @@
#include "core/hle/service/service.h"
#include "core/hle/service/ac_u.h"
+#include "core/hle/service/act_a.h"
#include "core/hle/service/act_u.h"
#include "core/hle/service/csnd_snd.h"
#include "core/hle/service/dlp_srvr.h"
@@ -119,6 +120,7 @@ void Init() {
Service::PTM::Init();
AddService(new AC_U::Interface);
+ AddService(new ACT_A::Interface);
AddService(new ACT_U::Interface);
AddService(new CSND_SND::Interface);
AddService(new DLP_SRVR::Interface);
diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp
index ff0af8f12..d3e5d4bca 100644
--- a/src/core/hle/service/soc_u.cpp
+++ b/src/core/hle/service/soc_u.cpp
@@ -151,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
@@ -568,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);
}
@@ -724,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"},
@@ -741,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..0ce72de87 100644
--- a/src/core/hle/svc.cpp
+++ b/src/core/hle/svc.cpp
@@ -6,7 +6,7 @@
#include "common/logging/log.h"
#include "common/microprofile.h"
-#include "common/profiler.h"
+#include "common/scope_exit.h"
#include "common/string_util.h"
#include "common/symbols.h"
@@ -100,6 +100,7 @@ static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 add
switch (operation & MEMOP_OPERATION_MASK) {
case MEMOP_FREE:
{
+ // TODO(Subv): What happens if an application tries to FREE a block of memory that has a SharedMemory pointing to it?
if (addr0 >= Memory::HEAP_VADDR && addr0 < Memory::HEAP_VADDR_END) {
ResultCode result = process.HeapFree(addr0, size);
if (result.IsError()) return result;
@@ -161,8 +162,6 @@ static ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 o
LOG_TRACE(Kernel_SVC, "called memblock=0x%08X, addr=0x%08X, mypermissions=0x%08X, otherpermission=%d",
handle, addr, permissions, other_permissions);
- // TODO(Subv): The same process that created a SharedMemory object can not map it in its own address space
-
SharedPtr<SharedMemory> shared_memory = Kernel::g_handle_table.Get<SharedMemory>(handle);
if (shared_memory == nullptr)
return ERR_INVALID_HANDLE;
@@ -177,7 +176,7 @@ static ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 o
case MemoryPermission::WriteExecute:
case MemoryPermission::ReadWriteExecute:
case MemoryPermission::DontCare:
- return shared_memory->Map(addr, permissions_type,
+ return shared_memory->Map(Kernel::g_current_process.get(), addr, permissions_type,
static_cast<MemoryPermission>(other_permissions));
default:
LOG_ERROR(Kernel_SVC, "unknown permissions=0x%08X", permissions);
@@ -197,7 +196,7 @@ static ResultCode UnmapMemoryBlock(Handle handle, u32 addr) {
if (shared_memory == nullptr)
return ERR_INVALID_HANDLE;
- return shared_memory->Unmap(addr);
+ return shared_memory->Unmap(Kernel::g_current_process.get(), addr);
}
/// Connect to an OS service given the port name, returns the handle to the port to out
@@ -328,9 +327,9 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou
}
}
- HLE::Reschedule(__func__);
+ SCOPE_EXIT({HLE::Reschedule("WaitSynchronizationN");}); // Reschedule after putting the threads to sleep.
- // If thread should wait, then set its state to waiting and then reschedule...
+ // If thread should wait, then set its state to waiting
if (wait_thread) {
// Actually wait the current thread on each object if we decided to wait...
@@ -497,8 +496,16 @@ static ResultCode CreateThread(Handle* out_handle, s32 priority, u32 entry_point
break;
}
+ if (processor_id == THREADPROCESSORID_1 || processor_id == THREADPROCESSORID_ALL ||
+ (processor_id == THREADPROCESSORID_DEFAULT && Kernel::g_current_process->ideal_processor == THREADPROCESSORID_1)) {
+ LOG_WARNING(Kernel_SVC, "Newly created thread is allowed to be run in the SysCore, unimplemented.");
+ }
+
CASCADE_RESULT(SharedPtr<Thread> thread, Kernel::Thread::Create(
name, entry_point, priority, arg, processor_id, stack_top));
+
+ thread->context.fpscr = FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO; // 0x03C00000
+
CASCADE_RESULT(*out_handle, Kernel::g_handle_table.Create(std::move(thread)));
LOG_TRACE(Kernel_SVC, "called entrypoint=0x%08X (%s), arg=0x%08X, stacktop=0x%08X, "
@@ -786,18 +793,44 @@ static ResultCode CreateMemoryBlock(Handle* out_handle, u32 addr, u32 size, u32
if (size % Memory::PAGE_SIZE != 0)
return ResultCode(ErrorDescription::MisalignedSize, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage);
- // TODO(Subv): Return E0A01BF5 if the address is not in the application's heap
-
- // TODO(Subv): Implement this function properly
+ SharedPtr<SharedMemory> shared_memory = nullptr;
using Kernel::MemoryPermission;
- SharedPtr<SharedMemory> shared_memory = SharedMemory::Create(size,
- (MemoryPermission)my_permission, (MemoryPermission)other_permission);
- // Map the SharedMemory to the specified address
- shared_memory->base_address = addr;
+ auto VerifyPermissions = [](MemoryPermission permission) {
+ // SharedMemory blocks can not be created with Execute permissions
+ switch (permission) {
+ case MemoryPermission::None:
+ case MemoryPermission::Read:
+ case MemoryPermission::Write:
+ case MemoryPermission::ReadWrite:
+ case MemoryPermission::DontCare:
+ return true;
+ default:
+ return false;
+ }
+ };
+
+ if (!VerifyPermissions(static_cast<MemoryPermission>(my_permission)) ||
+ !VerifyPermissions(static_cast<MemoryPermission>(other_permission)))
+ return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS,
+ ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+
+ if (addr < Memory::PROCESS_IMAGE_VADDR || addr + size > Memory::SHARED_MEMORY_VADDR_END) {
+ return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+ }
+
+ // When trying to create a memory block with address = 0,
+ // if the process has the Shared Device Memory flag in the exheader,
+ // then we have to allocate from the same region as the caller process instead of the BASE region.
+ Kernel::MemoryRegion region = Kernel::MemoryRegion::BASE;
+ if (addr == 0 && Kernel::g_current_process->flags.shared_device_mem)
+ region = Kernel::g_current_process->flags.memory_region;
+
+ shared_memory = SharedMemory::Create(Kernel::g_current_process, size,
+ static_cast<MemoryPermission>(my_permission), static_cast<MemoryPermission>(other_permission), addr, region);
CASCADE_RESULT(*out_handle, Kernel::g_handle_table.Create(std::move(shared_memory)));
- LOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x%08X", addr);
+ LOG_WARNING(Kernel_SVC, "called addr=0x%08X", addr);
return RESULT_SUCCESS;
}
@@ -860,6 +893,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 +1068,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 +1079,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..a4dfb7e43 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(), static_cast<u32>(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(), static_cast<u32>(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..98e7ab48f 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 {
/*
@@ -182,11 +178,11 @@ static THREEDSX_Error Load3DSXFile(FileUtil::IOFile& file, u32 base_addr, Shared
for (unsigned current_inprogress = 0; current_inprogress < remaining && pos < end_pos; current_inprogress++) {
const auto& table = reloc_table[current_inprogress];
LOG_TRACE(Loader, "(t=%d,skip=%u,patch=%u)", current_segment_reloc_table,
- (u32)table.skip, (u32)table.patch);
+ static_cast<u32>(table.skip), static_cast<u32>(table.patch));
pos += table.skip;
s32 num_patches = table.patch;
while (0 < num_patches && pos < end_pos) {
- u32 in_addr = (u8*)pos - program_image.data();
+ u32 in_addr = static_cast<u32>(reinterpret_cast<u8*>(pos) - program_image.data());
u32 addr = TranslateAddr(*pos, &loadinfo, offsets);
LOG_TRACE(Loader, "Patching %08X <-- rel(%08X,%d) (%08X)",
base_addr + in_addr, addr, current_segment_reloc_table, *pos);
@@ -288,7 +284,7 @@ ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& ro
// Check if the 3DSX has a RomFS...
if (hdr.fs_offset != 0) {
u32 romfs_offset = hdr.fs_offset;
- u32 romfs_size = file.GetSize() - hdr.fs_offset;
+ u32 romfs_size = static_cast<u32>(file.GetSize()) - hdr.fs_offset;
LOG_DEBUG(Loader, "RomFS offset: 0x%08X", romfs_offset);
LOG_DEBUG(Loader, "RomFS size: 0x%08X", romfs_size);
@@ -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 886501c41..af3f62248 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -90,6 +90,28 @@ const char* GetFileTypeString(FileType type) {
return "unknown";
}
+std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type,
+ const std::string& filename, const std::string& filepath) {
+ switch (type) {
+
+ // 3DSX file format.
+ case FileType::THREEDSX:
+ return std::make_unique<AppLoader_THREEDSX>(std::move(file), filename, filepath);
+
+ // Standard ELF file format.
+ case FileType::ELF:
+ return std::make_unique<AppLoader_ELF>(std::move(file), filename);
+
+ // NCCH/NCSD container formats.
+ case FileType::CXI:
+ case FileType::CCI:
+ return std::make_unique<AppLoader_NCCH>(std::move(file), filepath);
+
+ default:
+ return std::unique_ptr<AppLoader>();
+ }
+}
+
ResultStatus LoadFile(const std::string& filename) {
FileUtil::IOFile file(filename, "rb");
if (!file.IsOpen()) {
@@ -111,38 +133,29 @@ ResultStatus LoadFile(const std::string& filename) {
LOG_INFO(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type));
+ std::unique_ptr<AppLoader> app_loader = GetLoader(std::move(file), type, filename_filename, filename);
+
switch (type) {
- //3DSX file format...
+ // 3DSX file format...
+ // or NCCH/NCSD container formats...
case FileType::THREEDSX:
- {
- AppLoader_THREEDSX app_loader(std::move(file), filename_filename, filename);
- // Load application and RomFS
- if (ResultStatus::Success == app_loader.Load()) {
- Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(app_loader), Service::FS::ArchiveIdCode::RomFS);
- return ResultStatus::Success;
- }
- break;
- }
-
- // Standard ELF file format...
- case FileType::ELF:
- return AppLoader_ELF(std::move(file), filename_filename).Load();
-
- // NCCH/NCSD container formats...
case FileType::CXI:
case FileType::CCI:
{
- AppLoader_NCCH app_loader(std::move(file), filename);
-
// Load application and RomFS
- ResultStatus result = app_loader.Load();
+ ResultStatus result = app_loader->Load();
if (ResultStatus::Success == result) {
- Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(app_loader), Service::FS::ArchiveIdCode::RomFS);
+ Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*app_loader), Service::FS::ArchiveIdCode::RomFS);
+ return ResultStatus::Success;
}
return result;
}
+ // Standard ELF file format...
+ case FileType::ELF:
+ return app_loader->Load();
+
// CIA file format...
case FileType::CIA:
return ResultStatus::ErrorNotImplemented;
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 84a4ce5fc..9d3e9ed3b 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -10,8 +10,10 @@
#include <string>
#include <vector>
+#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/file_util.h"
+#include "common/swap.h"
namespace Kernel {
struct AddressMapping;
@@ -78,6 +80,51 @@ constexpr u32 MakeMagic(char a, char b, char c, char d) {
return a | b << 8 | c << 16 | d << 24;
}
+/// SMDH data structure that contains titles, icons etc. See https://www.3dbrew.org/wiki/SMDH
+struct SMDH {
+ u32_le magic;
+ u16_le version;
+ INSERT_PADDING_BYTES(2);
+
+ struct Title {
+ std::array<u16, 0x40> short_title;
+ std::array<u16, 0x80> long_title;
+ std::array<u16, 0x40> publisher;
+ };
+ std::array<Title, 16> titles;
+
+ std::array<u8, 16> ratings;
+ u32_le region_lockout;
+ u32_le match_maker_id;
+ u64_le match_maker_bit_id;
+ u32_le flags;
+ u16_le eula_version;
+ INSERT_PADDING_BYTES(2);
+ float_le banner_animation_frame;
+ u32_le cec_id;
+ INSERT_PADDING_BYTES(8);
+
+ std::array<u8, 0x480> small_icon;
+ std::array<u8, 0x1200> large_icon;
+
+ /// indicates the language used for each title entry
+ enum class TitleLanguage {
+ Japanese = 0,
+ English = 1,
+ French = 2,
+ German = 3,
+ Italian = 4,
+ Spanish = 5,
+ SimplifiedChinese = 6,
+ Korean= 7,
+ Dutch = 8,
+ Portuguese = 9,
+ Russian = 10,
+ TraditionalChinese = 11
+ };
+};
+static_assert(sizeof(SMDH) == 0x36C0, "SMDH structure size is wrong");
+
/// Interface for loading an application
class AppLoader : NonCopyable {
public:
@@ -150,6 +197,16 @@ protected:
extern const std::initializer_list<Kernel::AddressMapping> default_address_mappings;
/**
+ * Get a loader for a file with a specific type
+ * @param file The file to load
+ * @param type The type of the file
+ * @param filename the file name (without path)
+ * @param filepath the file full path (with name)
+ * @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type
+ */
+std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type, const std::string& filename, const std::string& filepath);
+
+/**
* Identifies and loads a bootable file
* @param filename String filename of bootable file
* @return ResultStatus result of function
diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp
index e63cab33f..7391bdb26 100644
--- a/src/core/loader/ncch.cpp
+++ b/src/core/loader/ncch.cpp
@@ -156,6 +156,9 @@ ResultStatus AppLoader_NCCH::LoadExec() {
Kernel::g_current_process->resource_limit = Kernel::ResourceLimit::GetForCategory(
static_cast<Kernel::ResourceLimitCategory>(exheader_header.arm11_system_local_caps.resource_limit_category));
+ // Set the default CPU core for this process
+ Kernel::g_current_process->ideal_processor = exheader_header.arm11_system_local_caps.ideal_processor;
+
// Copy data while converting endianess
std::array<u32, ARRAY_SIZE(exheader_header.arm11_kernel_caps.descriptors)> kernel_caps;
std::copy_n(exheader_header.arm11_kernel_caps.descriptors, kernel_caps.size(), begin(kernel_caps));
@@ -173,8 +176,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];
@@ -186,7 +193,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 {
@@ -215,9 +222,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;
@@ -255,7 +262,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);
@@ -282,6 +289,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..126d60471 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -100,15 +100,9 @@ enum : VAddr {
SHARED_PAGE_SIZE = 0x00001000,
SHARED_PAGE_VADDR_END = SHARED_PAGE_VADDR + SHARED_PAGE_SIZE,
- // TODO(yuriks): The size of this area is dynamic, the kernel grows
- // it as more and more threads are created. For now we'll just use a
- // hardcoded value.
/// Area where TLS (Thread-Local Storage) buffers are allocated.
TLS_AREA_VADDR = 0x1FF82000,
TLS_ENTRY_SIZE = 0x200,
- TLS_AREA_SIZE = 300 * TLS_ENTRY_SIZE + 0x800, // Space for up to 300 threads + round to page size
- TLS_AREA_VADDR_END = TLS_AREA_VADDR + TLS_AREA_SIZE,
-
/// Equivalent to LINEAR_HEAP_VADDR, but expanded to cover the extra memory in the New 3DS.
NEW_LINEAR_HEAP_VADDR = 0x30000000,
@@ -148,4 +142,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 4034b795a..a61f25cbe 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 {
@@ -58,6 +59,7 @@ struct Values {
// Renderer
bool use_hw_renderer;
bool use_shader_jit;
+ bool use_scaled_resolution;
float bg_red;
float bg_green;
@@ -65,9 +67,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.cpp b/src/core/tracer/recorder.cpp
index c6dc35c83..7abaacf70 100644
--- a/src/core/tracer/recorder.cpp
+++ b/src/core/tracer/recorder.cpp
@@ -26,17 +26,17 @@ void Recorder::Finish(const std::string& filename) {
// Calculate file offsets
auto& initial = header.initial_state_offsets;
- initial.gpu_registers_size = initial_state.gpu_registers.size();
- initial.lcd_registers_size = initial_state.lcd_registers.size();
- initial.pica_registers_size = initial_state.pica_registers.size();
- initial.default_attributes_size = initial_state.default_attributes.size();
- initial.vs_program_binary_size = initial_state.vs_program_binary.size();
- initial.vs_swizzle_data_size = initial_state.vs_swizzle_data.size();
- initial.vs_float_uniforms_size = initial_state.vs_float_uniforms.size();
- initial.gs_program_binary_size = initial_state.gs_program_binary.size();
- initial.gs_swizzle_data_size = initial_state.gs_swizzle_data.size();
- initial.gs_float_uniforms_size = initial_state.gs_float_uniforms.size();
- header.stream_size = stream.size();
+ initial.gpu_registers_size = static_cast<u32>(initial_state.gpu_registers.size());
+ initial.lcd_registers_size = static_cast<u32>(initial_state.lcd_registers.size());
+ initial.pica_registers_size = static_cast<u32>(initial_state.pica_registers.size());
+ initial.default_attributes_size = static_cast<u32>(initial_state.default_attributes.size());
+ initial.vs_program_binary_size = static_cast<u32>(initial_state.vs_program_binary.size());
+ initial.vs_swizzle_data_size = static_cast<u32>(initial_state.vs_swizzle_data.size());
+ initial.vs_float_uniforms_size = static_cast<u32>(initial_state.vs_float_uniforms.size());
+ initial.gs_program_binary_size = static_cast<u32>(initial_state.gs_program_binary.size());
+ initial.gs_swizzle_data_size = static_cast<u32>(initial_state.gs_swizzle_data.size());
+ initial.gs_float_uniforms_size = static_cast<u32>(initial_state.gs_float_uniforms.size());
+ header.stream_size = static_cast<u32>(stream.size());
initial.gpu_registers = sizeof(header);
initial.lcd_registers = initial.gpu_registers + initial.gpu_registers_size * sizeof(u32);
@@ -68,7 +68,7 @@ void Recorder::Finish(const std::string& filename) {
DEBUG_ASSERT(stream_element.extra_data.size() == 0);
break;
}
- header.stream_offset += stream_element.extra_data.size();
+ header.stream_offset += static_cast<u32>(stream_element.extra_data.size());
}
try {
diff --git a/src/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/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
new file mode 100644
index 000000000..457c55571
--- /dev/null
+++ b/src/tests/CMakeLists.txt
@@ -0,0 +1,16 @@
+set(SRCS
+ tests.cpp
+ )
+
+set(HEADERS
+ )
+
+create_directory_groups(${SRCS} ${HEADERS})
+
+include_directories(../../externals/catch/single_include/)
+
+add_executable(tests ${SRCS} ${HEADERS})
+target_link_libraries(tests core video_core audio_core common)
+target_link_libraries(tests ${PLATFORM_LIBRARIES})
+
+add_test(NAME tests COMMAND $<TARGET_FILE:tests>)
diff --git a/src/tests/tests.cpp b/src/tests/tests.cpp
new file mode 100644
index 000000000..73978676f
--- /dev/null
+++ b/src/tests/tests.cpp
@@ -0,0 +1,9 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#define CATCH_CONFIG_MAIN
+#include <catch.hpp>
+
+// Catch provides the main function since we've given it the
+// CATCH_CONFIG_MAIN preprocessor directive.
diff --git a/src/video_core/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..db99ce666 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 {
@@ -64,8 +75,6 @@ static void InitScreenCoordinates(OutputVertex& vtx)
viewport.halfsize_y = float24::FromRaw(regs.viewport_size_y);
viewport.offset_x = float24::FromFloat32(static_cast<float>(regs.viewport_corner.x));
viewport.offset_y = float24::FromFloat32(static_cast<float>(regs.viewport_corner.y));
- viewport.zscale = float24::FromRaw(regs.viewport_depth_range);
- viewport.offset_z = float24::FromRaw(regs.viewport_depth_far_plane);
float24 inv_w = float24::FromFloat32(1.f) / vtx.pos.w;
vtx.color *= inv_w;
@@ -78,7 +87,7 @@ static void InitScreenCoordinates(OutputVertex& vtx)
vtx.screenpos[0] = (vtx.pos.x * inv_w + float24::FromFloat32(1.0)) * viewport.halfsize_x + viewport.offset_x;
vtx.screenpos[1] = (vtx.pos.y * inv_w + float24::FromFloat32(1.0)) * viewport.halfsize_y + viewport.offset_y;
- vtx.screenpos[2] = viewport.offset_z + vtx.pos.z * inv_w * viewport.zscale;
+ vtx.screenpos[2] = vtx.pos.z * inv_w;
}
void ProcessTriangle(const OutputVertex &v0, const OutputVertex &v1, const OutputVertex &v2) {
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index 028b59348..bf4664f9e 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,
@@ -124,7 +128,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
// TODO: Verify that this actually modifies the register!
if (setup.index < 15) {
- g_state.vs.default_attributes[setup.index] = attribute;
+ g_state.vs_default_attributes[setup.index] = attribute;
setup.index++;
} else {
// Put each attribute into an immediate input buffer.
@@ -140,13 +144,12 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
immediate_attribute_id = 0;
Shader::UnitState<false> shader_unit;
- Shader::Setup(shader_unit);
-
- if (g_debug_context)
- g_debug_context->OnEvent(DebugContext::Event::VertexLoaded, static_cast<void*>(&immediate_input));
+ g_state.vs.Setup();
// Send to vertex shader
- Shader::OutputVertex output = Shader::Run(shader_unit, immediate_input, regs.vs.num_input_attributes+1);
+ if (g_debug_context)
+ g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, static_cast<void*>(&immediate_input));
+ Shader::OutputVertex output = g_state.vs.Run(shader_unit, immediate_input, regs.vs.num_input_attributes+1);
// Send to renderer
using Pica::Shader::OutputVertex;
@@ -186,60 +189,18 @@ 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.
+ const u32 base_address = regs.vertex_attributes.GetPhysicalBaseAddress();
+ VertexLoader loader(regs);
// Load vertices
bool is_indexed = (id == PICA_REG_INDEX(trigger_draw_indexed));
@@ -249,10 +210,6 @@ 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 = g_state.primitive_assembler;
if (g_debug_context) {
@@ -267,32 +224,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
@@ -304,7 +236,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
vertex_cache_ids.fill(-1);
Shader::UnitState<false> shader_unit;
- Shader::Setup(shader_unit);
+ g_state.vs.Setup();
for (unsigned int index = 0; index < regs.num_vertices; ++index)
{
@@ -336,71 +268,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 = g_state.vs.Run(shader_unit, input, loader.GetNumTotalAttributes());
if (is_indexed) {
vertex_cache[vertex_cache_pos] = output;
@@ -424,10 +297,6 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
range.second, range.first);
}
-#if PICA_DUMP_GEOMETRY
- geometry_dumper.Dump();
-#endif
-
break;
}
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp
index bac6d69c7..871368323 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 {
@@ -234,11 +208,12 @@ void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, c
// TODO: Reduce the amount of binary code written to relevant portions
dvlp.binary_offset = write_offset - dvlp_offset;
- dvlp.binary_size_words = setup.program_code.size();
- QueueForWriting(reinterpret_cast<const u8*>(setup.program_code.data()), setup.program_code.size() * sizeof(u32));
+ dvlp.binary_size_words = static_cast<uint32_t>(setup.program_code.size());
+ QueueForWriting(reinterpret_cast<const u8*>(setup.program_code.data()),
+ static_cast<u32>(setup.program_code.size()) * sizeof(u32));
dvlp.swizzle_info_offset = write_offset - dvlp_offset;
- dvlp.swizzle_info_num_entries = setup.swizzle_data.size();
+ dvlp.swizzle_info_num_entries = static_cast<uint32_t>(setup.swizzle_data.size());
u32 dummy = 0;
for (unsigned int i = 0; i < setup.swizzle_data.size(); ++i) {
QueueForWriting(reinterpret_cast<const u8*>(&setup.swizzle_data[i]), sizeof(setup.swizzle_data[i]));
@@ -290,7 +265,7 @@ void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, c
constant_table.emplace_back(constant);
}
dvle.constant_table_offset = write_offset - dvlb.dvle_offset;
- dvle.constant_table_size = constant_table.size();
+ dvle.constant_table_size = static_cast<uint32_t>(constant_table.size());
for (const auto& constant : constant_table) {
QueueForWriting(reinterpret_cast<const u8*>(&constant), sizeof(constant));
}
@@ -315,7 +290,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 +590,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 +648,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,
@@ -706,106 +696,125 @@ finalise:
#endif
}
-void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages)
-{
+static std::string ReplacePattern(const std::string& input, const std::string& pattern, const std::string& replacement) {
+ size_t start = input.find(pattern);
+ if (start == std::string::npos)
+ return input;
+
+ std::string ret = input;
+ ret.replace(start, pattern.length(), replacement);
+ return ret;
+}
+
+static std::string GetTevStageConfigSourceString(const Pica::Regs::TevStageConfig::Source& source) {
using Source = Pica::Regs::TevStageConfig::Source;
+ static const std::map<Source, std::string> source_map = {
+ { Source::PrimaryColor, "PrimaryColor" },
+ { Source::PrimaryFragmentColor, "PrimaryFragmentColor" },
+ { Source::SecondaryFragmentColor, "SecondaryFragmentColor" },
+ { Source::Texture0, "Texture0" },
+ { Source::Texture1, "Texture1" },
+ { Source::Texture2, "Texture2" },
+ { Source::Texture3, "Texture3" },
+ { Source::PreviousBuffer, "PreviousBuffer" },
+ { Source::Constant, "Constant" },
+ { Source::Previous, "Previous" },
+ };
+
+ const auto src_it = source_map.find(source);
+ if (src_it == source_map.end())
+ return "Unknown";
+
+ return src_it->second;
+}
+
+static std::string GetTevStageConfigColorSourceString(const Pica::Regs::TevStageConfig::Source& source, const Pica::Regs::TevStageConfig::ColorModifier modifier) {
using ColorModifier = Pica::Regs::TevStageConfig::ColorModifier;
+ static const std::map<ColorModifier, std::string> color_modifier_map = {
+ { ColorModifier::SourceColor, "%source.rgb" },
+ { ColorModifier::OneMinusSourceColor, "(1.0 - %source.rgb)" },
+ { ColorModifier::SourceAlpha, "%source.aaa" },
+ { ColorModifier::OneMinusSourceAlpha, "(1.0 - %source.aaa)" },
+ { ColorModifier::SourceRed, "%source.rrr" },
+ { ColorModifier::OneMinusSourceRed, "(1.0 - %source.rrr)" },
+ { ColorModifier::SourceGreen, "%source.ggg" },
+ { ColorModifier::OneMinusSourceGreen, "(1.0 - %source.ggg)" },
+ { ColorModifier::SourceBlue, "%source.bbb" },
+ { ColorModifier::OneMinusSourceBlue, "(1.0 - %source.bbb)" },
+ };
+
+ auto src_str = GetTevStageConfigSourceString(source);
+ auto modifier_it = color_modifier_map.find(modifier);
+ std::string modifier_str = "%source.????";
+ if (modifier_it != color_modifier_map.end())
+ modifier_str = modifier_it->second;
+
+ return ReplacePattern(modifier_str, "%source", src_str);
+}
+
+static std::string GetTevStageConfigAlphaSourceString(const Pica::Regs::TevStageConfig::Source& source, const Pica::Regs::TevStageConfig::AlphaModifier modifier) {
using AlphaModifier = Pica::Regs::TevStageConfig::AlphaModifier;
+ static const std::map<AlphaModifier, std::string> alpha_modifier_map = {
+ { AlphaModifier::SourceAlpha, "%source.a" },
+ { AlphaModifier::OneMinusSourceAlpha, "(1.0 - %source.a)" },
+ { AlphaModifier::SourceRed, "%source.r" },
+ { AlphaModifier::OneMinusSourceRed, "(1.0 - %source.r)" },
+ { AlphaModifier::SourceGreen, "%source.g" },
+ { AlphaModifier::OneMinusSourceGreen, "(1.0 - %source.g)" },
+ { AlphaModifier::SourceBlue, "%source.b" },
+ { AlphaModifier::OneMinusSourceBlue, "(1.0 - %source.b)" },
+ };
+
+ auto src_str = GetTevStageConfigSourceString(source);
+ auto modifier_it = alpha_modifier_map.find(modifier);
+ std::string modifier_str = "%source.????";
+ if (modifier_it != alpha_modifier_map.end())
+ modifier_str = modifier_it->second;
+
+ return ReplacePattern(modifier_str, "%source", src_str);
+}
+
+static std::string GetTevStageConfigOperationString(const Pica::Regs::TevStageConfig::Operation& operation) {
using Operation = Pica::Regs::TevStageConfig::Operation;
+ static const std::map<Operation, std::string> combiner_map = {
+ { Operation::Replace, "%source1" },
+ { Operation::Modulate, "(%source1 * %source2)" },
+ { Operation::Add, "(%source1 + %source2)" },
+ { Operation::AddSigned, "(%source1 + %source2) - 0.5" },
+ { Operation::Lerp, "lerp(%source1, %source2, %source3)" },
+ { Operation::Subtract, "(%source1 - %source2)" },
+ { Operation::Dot3_RGB, "dot(%source1, %source2)" },
+ { Operation::MultiplyThenAdd, "((%source1 * %source2) + %source3)" },
+ { Operation::AddThenMultiply, "((%source1 + %source2) * %source3)" },
+ };
- std::string stage_info = "Tev setup:\n";
- for (size_t index = 0; index < stages.size(); ++index) {
- const auto& tev_stage = stages[index];
+ const auto op_it = combiner_map.find(operation);
+ if (op_it == combiner_map.end())
+ return "Unknown op (%source1, %source2, %source3)";
- static const std::map<Source, std::string> source_map = {
- { Source::PrimaryColor, "PrimaryColor" },
- { Source::Texture0, "Texture0" },
- { Source::Texture1, "Texture1" },
- { Source::Texture2, "Texture2" },
- { Source::Constant, "Constant" },
- { Source::Previous, "Previous" },
- };
+ return op_it->second;
+}
- static const std::map<ColorModifier, std::string> color_modifier_map = {
- { ColorModifier::SourceColor, { "%source.rgb" } },
- { ColorModifier::SourceAlpha, { "%source.aaa" } },
- };
- static const std::map<AlphaModifier, std::string> alpha_modifier_map = {
- { AlphaModifier::SourceAlpha, "%source.a" },
- { AlphaModifier::OneMinusSourceAlpha, "(255 - %source.a)" },
- };
+std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfig& tev_stage) {
+ auto op_str = GetTevStageConfigOperationString(tev_stage.color_op);
+ op_str = ReplacePattern(op_str, "%source1", GetTevStageConfigColorSourceString(tev_stage.color_source1, tev_stage.color_modifier1));
+ op_str = ReplacePattern(op_str, "%source2", GetTevStageConfigColorSourceString(tev_stage.color_source2, tev_stage.color_modifier2));
+ return ReplacePattern(op_str, "%source3", GetTevStageConfigColorSourceString(tev_stage.color_source3, tev_stage.color_modifier3));
+}
- static const std::map<Operation, std::string> combiner_map = {
- { Operation::Replace, "%source1" },
- { Operation::Modulate, "(%source1 * %source2) / 255" },
- { Operation::Add, "(%source1 + %source2)" },
- { Operation::Lerp, "lerp(%source1, %source2, %source3)" },
- };
+std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfig& tev_stage) {
+ auto op_str = GetTevStageConfigOperationString(tev_stage.alpha_op);
+ op_str = ReplacePattern(op_str, "%source1", GetTevStageConfigAlphaSourceString(tev_stage.alpha_source1, tev_stage.alpha_modifier1));
+ op_str = ReplacePattern(op_str, "%source2", GetTevStageConfigAlphaSourceString(tev_stage.alpha_source2, tev_stage.alpha_modifier2));
+ return ReplacePattern(op_str, "%source3", GetTevStageConfigAlphaSourceString(tev_stage.alpha_source3, tev_stage.alpha_modifier3));
+}
- static auto ReplacePattern =
- [](const std::string& input, const std::string& pattern, const std::string& replacement) -> std::string {
- size_t start = input.find(pattern);
- if (start == std::string::npos)
- return input;
-
- std::string ret = input;
- ret.replace(start, pattern.length(), replacement);
- return ret;
- };
- static auto GetColorSourceStr =
- [](const Source& src, const ColorModifier& modifier) {
- auto src_it = source_map.find(src);
- std::string src_str = "Unknown";
- if (src_it != source_map.end())
- src_str = src_it->second;
-
- auto modifier_it = color_modifier_map.find(modifier);
- std::string modifier_str = "%source.????";
- if (modifier_it != color_modifier_map.end())
- modifier_str = modifier_it->second;
-
- return ReplacePattern(modifier_str, "%source", src_str);
- };
- static auto GetColorCombinerStr =
- [](const Regs::TevStageConfig& tev_stage) {
- auto op_it = combiner_map.find(tev_stage.color_op);
- std::string op_str = "Unknown op (%source1, %source2, %source3)";
- if (op_it != combiner_map.end())
- op_str = op_it->second;
-
- op_str = ReplacePattern(op_str, "%source1", GetColorSourceStr(tev_stage.color_source1, tev_stage.color_modifier1));
- op_str = ReplacePattern(op_str, "%source2", GetColorSourceStr(tev_stage.color_source2, tev_stage.color_modifier2));
- return ReplacePattern(op_str, "%source3", GetColorSourceStr(tev_stage.color_source3, tev_stage.color_modifier3));
- };
- static auto GetAlphaSourceStr =
- [](const Source& src, const AlphaModifier& modifier) {
- auto src_it = source_map.find(src);
- std::string src_str = "Unknown";
- if (src_it != source_map.end())
- src_str = src_it->second;
-
- auto modifier_it = alpha_modifier_map.find(modifier);
- std::string modifier_str = "%source.????";
- if (modifier_it != alpha_modifier_map.end())
- modifier_str = modifier_it->second;
-
- return ReplacePattern(modifier_str, "%source", src_str);
- };
- static auto GetAlphaCombinerStr =
- [](const Regs::TevStageConfig& tev_stage) {
- auto op_it = combiner_map.find(tev_stage.alpha_op);
- std::string op_str = "Unknown op (%source1, %source2, %source3)";
- if (op_it != combiner_map.end())
- op_str = op_it->second;
-
- op_str = ReplacePattern(op_str, "%source1", GetAlphaSourceStr(tev_stage.alpha_source1, tev_stage.alpha_modifier1));
- op_str = ReplacePattern(op_str, "%source2", GetAlphaSourceStr(tev_stage.alpha_source2, tev_stage.alpha_modifier2));
- return ReplacePattern(op_str, "%source3", GetAlphaSourceStr(tev_stage.alpha_source3, tev_stage.alpha_modifier3));
- };
-
- stage_info += "Stage " + std::to_string(index) + ": " + GetColorCombinerStr(tev_stage) + " " + GetAlphaCombinerStr(tev_stage) + "\n";
+void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig, 6>& stages) {
+ std::string stage_info = "Tev setup:\n";
+ for (size_t index = 0; index < stages.size(); ++index) {
+ const auto& tev_stage = stages[index];
+ stage_info += "Stage " + std::to_string(index) + ": " + GetTevStageConfigColorCombinerString(tev_stage) + " " + GetTevStageConfigAlphaCombinerString(tev_stage) + "\n";
}
-
LOG_TRACE(HW_GPU, "%s", stage_info.c_str());
}
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h
index 795160a32..92e9734ae 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);
@@ -225,7 +224,41 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int s, int t, const Texture
void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data);
-void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages);
+std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfig& tev_stage);
+std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfig& tev_stage);
+
+/// Dumps the Tev stage config to log at trace level
+void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig, 6>& stages);
+
+/**
+ * Used in the vertex loader to merge access records. TODO: Investigate if actually useful.
+ */
+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
diff --git a/src/video_core/pica.cpp b/src/video_core/pica.cpp
index ccbaf071b..ec78f9593 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
@@ -497,7 +500,7 @@ void Init() {
}
void Shutdown() {
- Shader::Shutdown();
+ Shader::ClearCache();
}
template <typename T>
diff --git a/src/video_core/pica.h b/src/video_core/pica.h
index 16f9e4006..86c0a0096 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
@@ -69,7 +70,7 @@ struct Regs {
INSERT_PADDING_WORDS(0x9);
BitField<0, 24, u32> viewport_depth_range; // float24
- BitField<0, 24, u32> viewport_depth_far_plane; // float24
+ BitField<0, 24, u32> viewport_depth_near_plane; // float24
BitField<0, 3, u32> vs_output_total;
@@ -121,9 +122,31 @@ struct Regs {
BitField<16, 10, s32> y;
} viewport_corner;
- INSERT_PADDING_WORDS(0x17);
+ INSERT_PADDING_WORDS(0x1);
+
+ //TODO: early depth
+ INSERT_PADDING_WORDS(0x1);
+
+ INSERT_PADDING_WORDS(0x2);
+
+ enum DepthBuffering : u32 {
+ WBuffering = 0,
+ ZBuffering = 1,
+ };
+ BitField< 0, 1, DepthBuffering> depthmap_enable;
+
+ INSERT_PADDING_WORDS(0x12);
struct TextureConfig {
+ enum TextureType : u32 {
+ Texture2D = 0,
+ TextureCube = 1,
+ Shadow2D = 2,
+ Projection2D = 3,
+ ShadowCube = 4,
+ Disabled = 5,
+ };
+
enum WrapMode : u32 {
ClampToEdge = 0,
ClampToBorder = 1,
@@ -154,6 +177,7 @@ struct Regs {
BitField< 2, 1, TextureFilter> min_filter;
BitField< 8, 2, WrapMode> wrap_t;
BitField<12, 2, WrapMode> wrap_s;
+ BitField<28, 2, TextureType> type; ///< @note Only valid for texture 0 according to 3DBrew.
};
INSERT_PADDING_WORDS(0x1);
@@ -577,8 +601,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 +771,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 {
@@ -1263,10 +1302,11 @@ ASSERT_REG_POSITION(cull_mode, 0x40);
ASSERT_REG_POSITION(viewport_size_x, 0x41);
ASSERT_REG_POSITION(viewport_size_y, 0x43);
ASSERT_REG_POSITION(viewport_depth_range, 0x4d);
-ASSERT_REG_POSITION(viewport_depth_far_plane, 0x4e);
+ASSERT_REG_POSITION(viewport_depth_near_plane, 0x4e);
ASSERT_REG_POSITION(vs_output_attributes[0], 0x50);
ASSERT_REG_POSITION(vs_output_attributes[1], 0x51);
ASSERT_REG_POSITION(viewport_corner, 0x68);
+ASSERT_REG_POSITION(depthmap_enable, 0x6D);
ASSERT_REG_POSITION(texture0_enable, 0x80);
ASSERT_REG_POSITION(texture0, 0x81);
ASSERT_REG_POSITION(texture0_format, 0x8e);
diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h
index 323290054..495174c25 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"
@@ -20,6 +25,8 @@ struct State {
Shader::ShaderSetup vs;
Shader::ShaderSetup gs;
+ std::array<Math::Vec4<float24>, 16> vs_default_attributes;
+
struct {
union LutEntry {
// Used for raw access
@@ -51,7 +58,7 @@ struct State {
// Used to buffer partial vertices for immediate-mode rendering.
Shader::InputVertex input_vertex;
// Index of the next attribute to be loaded into `input_vertex`.
- int current_attribute = 0;
+ u32 current_attribute = 0;
} immediate;
// This is constructed with a dummy triangle topology
diff --git a/src/video_core/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/rasterizer.cpp b/src/video_core/rasterizer.cpp
index fd02aa652..65168f05a 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
@@ -439,8 +442,33 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0,
DEBUG_ASSERT(0 != texture.config.address);
- int s = (int)(uv[i].u() * float24::FromFloat32(static_cast<float>(texture.config.width))).ToFloat32();
- int t = (int)(uv[i].v() * float24::FromFloat32(static_cast<float>(texture.config.height))).ToFloat32();
+ float24 u = uv[i].u();
+ float24 v = uv[i].v();
+
+ // Only unit 0 respects the texturing type (according to 3DBrew)
+ // TODO: Refactor so cubemaps and shadowmaps can be handled
+ if (i == 0) {
+ switch(texture.config.type) {
+ case Regs::TextureConfig::Texture2D:
+ break;
+ case Regs::TextureConfig::Projection2D: {
+ auto tc0_w = GetInterpolatedAttribute(v0.tc0_w, v1.tc0_w, v2.tc0_w);
+ u /= tc0_w;
+ v /= tc0_w;
+ break;
+ }
+ default:
+ // TODO: Change to LOG_ERROR when more types are handled.
+ LOG_DEBUG(HW_GPU, "Unhandled texture type %x", (int)texture.config.type);
+ UNIMPLEMENTED();
+ break;
+ }
+ }
+
+ int s = (int)(u * float24::FromFloat32(static_cast<float>(texture.config.width))).ToFloat32();
+ int t = (int)(v * float24::FromFloat32(static_cast<float>(texture.config.height))).ToFloat32();
+
+
static auto GetWrappedTexCoord = [](Regs::TextureConfig::WrapMode mode, int val, unsigned size) {
switch (mode) {
case Regs::TextureConfig::ClampToEdge:
@@ -809,7 +837,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) {
@@ -858,10 +887,30 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0,
}
}
+ // interpolated_z = z / w
+ float interpolated_z_over_w = (v0.screenpos[2].ToFloat32() * w0 +
+ v1.screenpos[2].ToFloat32() * w1 +
+ v2.screenpos[2].ToFloat32() * w2) / wsum;
+
+ // Not fully accurate. About 3 bits in precision are missing.
+ // Z-Buffer (z / w * scale + offset)
+ float depth_scale = float24::FromRaw(regs.viewport_depth_range).ToFloat32();
+ float depth_offset = float24::FromRaw(regs.viewport_depth_near_plane).ToFloat32();
+ float depth = interpolated_z_over_w * depth_scale + depth_offset;
+
+ // Potentially switch to W-Buffer
+ if (regs.depthmap_enable == Pica::Regs::DepthBuffering::WBuffering) {
+
+ // W-Buffer (z * scale + w * offset = (z / w * scale + offset) * w)
+ depth *= interpolated_w_inverse.ToFloat32() * wsum;
+ }
+
+ // Clamp the result
+ depth = MathUtil::Clamp(depth, 0.0f, 1.0f);
+
+ // Convert float to integer
unsigned num_bits = Regs::DepthBitsPerPixel(regs.framebuffer.depth_format);
- u32 z = (u32)((v0.screenpos[2].ToFloat32() * w0 +
- v1.screenpos[2].ToFloat32() * w1 +
- v2.screenpos[2].ToFloat32() * w2) * ((1 << num_bits) - 1) / wsum);
+ u32 z = (u32)(depth * ((1 << num_bits) - 1));
if (output_merger.depth_test_enable) {
u32 ref_z = GetDepth(x >> 4, y >> 4);
@@ -909,7 +958,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 +971,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);
+ return 255 - 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;
-
- 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 +1088,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 +1167,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 101f84eb9..3f451e062 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -2,10 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <atomic>
#include <memory>
-#include "core/settings.h"
-
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
#include "video_core/swrasterizer.h"
@@ -21,7 +20,5 @@ void RendererBase::RefreshRasterizerSetting() {
} else {
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 4fdf93a3e..bcd1ae78d 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -2,28 +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/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 &&
@@ -36,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();
@@ -61,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);
@@ -75,88 +76,47 @@ void RasterizerOpenGL::InitObjects() {
glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORD1);
glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORD2);
+ glVertexAttribPointer(GLShader::ATTRIBUTE_TEXCOORD0_W, 1, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord0_w));
+ glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORD0_W);
+
glVertexAttribPointer(GLShader::ATTRIBUTE_NORMQUAT, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, normquat));
glEnableVertexAttribArray(GLShader::ATTRIBUTE_NORMQUAT);
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;
-
- glActiveTexture(GL_TEXTURE3 + i);
- glBindTexture(GL_TEXTURE_1D, state.lighting_lut[i].texture_1d);
-
+ for (size_t i = 0; i < lighting_luts.size(); ++i) {
+ glActiveTexture(static_cast<GLenum>(GL_TEXTURE3 + i));
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
- state.Apply();
- GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- ASSERT_MSG(status == GL_FRAMEBUFFER_COMPLETE,
- "OpenGL rasterizer framebuffer setup failed, status %X", status);
-}
-
-void RasterizerOpenGL::Reset() {
+ // Sync fixed function OpenGL state
SyncCullMode();
- SyncDepthModifiers();
SyncBlendEnabled();
SyncBlendFuncs();
SyncBlendColor();
SyncLogicOp();
SyncStencilTest();
SyncDepthTest();
+ SyncColorWriteMask();
+ SyncStencilWriteMask();
+ SyncDepthWriteMask();
+}
- SetShader();
+RasterizerOpenGL::~RasterizerOpenGL() {
- res_cache.InvalidateAll();
}
/**
@@ -193,47 +153,98 @@ void RasterizerOpenGL::DrawTriangles() {
if (vertex_batch.empty())
return;
- SyncFramebuffer();
- SyncDrawState();
+ 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 (state.draw.shader_dirty) {
+ 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 (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) {
@@ -247,8 +258,15 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
// Depth modifiers
case PICA_REG_INDEX(viewport_depth_range):
- case PICA_REG_INDEX(viewport_depth_far_plane):
- SyncDepthModifiers();
+ SyncDepthScale();
+ break;
+ case PICA_REG_INDEX(viewport_depth_near_plane):
+ SyncDepthOffset();
+ break;
+
+ // Depth buffering
+ case PICA_REG_INDEX(depthmap_enable):
+ shader_dirty = true;
break;
// Blending
@@ -265,18 +283,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
@@ -284,6 +323,11 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
SyncLogicOp();
break;
+ // Texture 0 type
+ case PICA_REG_INDEX(texture0.type):
+ shader_dirty = true;
+ break;
+
// TEV stages
case PICA_REG_INDEX(tev_stage0.color_source1):
case PICA_REG_INDEX(tev_stage0.color_modifier1):
@@ -310,7 +354,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);
@@ -497,41 +541,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);
+}
- u32 cached_fb_color_size = Pica::Regs::BytesPerColorPixel(fb_color_texture.format)
- * fb_color_texture.width * fb_color_texture.height;
+bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) {
+ using PixelFormat = CachedSurface::PixelFormat;
+ using SurfaceType = CachedSurface::SurfaceType;
- u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format)
- * fb_depth_texture.width * fb_depth_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;
+ }
- // 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();
+ // 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 (MathUtil::IntervalsIntersect(addr, size, cached_fb_depth_addr, cached_fb_depth_size))
- CommitDepthBuffer();
+ MathUtil::Rectangle<int> dst_rect;
+ CachedSurface* dst_surface = res_cache.GetSurfaceRect(dst_params, true, false, dst_rect);
+
+ if (dst_surface == nullptr) {
+ return false;
+ }
+
+ // 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() {
@@ -567,114 +827,13 @@ 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 = std::make_unique<PicaShader>();
@@ -722,6 +881,8 @@ void RasterizerOpenGL::SetShader() {
glUniformBlockBinding(current_shader->shader.handle, block_index, 0);
// Update uniforms
+ SyncDepthScale();
+ SyncDepthOffset();
SyncAlphaTest();
SyncCombinerColor();
auto& tev_stages = Pica::g_state.regs.GetTevStages();
@@ -730,6 +891,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);
@@ -737,83 +900,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();
- }
-
- GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- ASSERT_MSG(status == GL_FRAMEBUFFER_COMPLETE,
- "OpenGL rasterizer framebuffer setup failed, status %X", status);
-}
-
void RasterizerOpenGL::SyncCullMode() {
const auto& regs = Pica::g_state.regs;
@@ -839,13 +925,20 @@ void RasterizerOpenGL::SyncCullMode() {
}
}
-void RasterizerOpenGL::SyncDepthModifiers() {
- float depth_scale = -Pica::float24::FromRaw(Pica::g_state.regs.viewport_depth_range).ToFloat32();
- float depth_offset = Pica::float24::FromRaw(Pica::g_state.regs.viewport_depth_far_plane).ToFloat32() / 2.0f;
+void RasterizerOpenGL::SyncDepthScale() {
+ float depth_scale = Pica::float24::FromRaw(Pica::g_state.regs.viewport_depth_range).ToFloat32();
+ if (depth_scale != uniform_block_data.data.depth_scale) {
+ uniform_block_data.data.depth_scale = depth_scale;
+ uniform_block_data.dirty = true;
+ }
+}
- // TODO: Implement scale modifier
- uniform_block_data.data.depth_offset = depth_offset;
- uniform_block_data.dirty = true;
+void RasterizerOpenGL::SyncDepthOffset() {
+ float depth_offset = Pica::float24::FromRaw(Pica::g_state.regs.viewport_depth_near_plane).ToFloat32();
+ if (depth_offset != uniform_block_data.data.depth_offset) {
+ uniform_block_data.data.depth_offset = depth_offset;
+ uniform_block_data.dirty = true;
+ }
}
void RasterizerOpenGL::SyncBlendEnabled() {
@@ -854,6 +947,8 @@ void RasterizerOpenGL::SyncBlendEnabled() {
void RasterizerOpenGL::SyncBlendFuncs() {
const auto& regs = Pica::g_state.regs;
+ state.blend.rgb_equation = PicaToGL::BlendEquation(regs.output_merger.alpha_blending.blend_equation_rgb);
+ state.blend.a_equation = PicaToGL::BlendEquation(regs.output_merger.alpha_blending.blend_equation_a);
state.blend.src_rgb_func = PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_source_rgb);
state.blend.dst_rgb_func = PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_dest_rgb);
state.blend.src_a_func = PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_source_a);
@@ -880,13 +975,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);
@@ -898,11 +1019,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() {
@@ -989,229 +1105,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..d70369400 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
@@ -28,158 +39,185 @@
* directly accessing Pica registers. This should reduce the risk of bugs in shader generation where
* Pica state is not being captured in the shader cache key, thereby resulting in (what should be)
* two separate shaders sharing the same key.
+ *
+ * We use a union because "implicitly-defined copy/move constructor for a union X copies the object representation of X."
+ * and "implicitly-defined copy assignment operator for a union X copies the object representation (3.9) of X."
+ * = Bytewise copy instead of memberwise copy.
+ * This is important because the padding bytes are included in the hash and comparison between objects.
*/
-struct PicaShaderConfig {
+union PicaShaderConfig {
+
/// Construct a PicaShaderConfig with the current Pica register configuration.
static PicaShaderConfig CurrentConfig() {
PicaShaderConfig res;
+
+ auto& state = res.state;
+ std::memset(&state, 0, sizeof(PicaShaderConfig::State));
+
const auto& regs = Pica::g_state.regs;
- res.alpha_test_func = regs.output_merger.alpha_test.enable ?
+ state.depthmap_enable = regs.depthmap_enable;
+
+ state.alpha_test_func = regs.output_merger.alpha_test.enable ?
regs.output_merger.alpha_test.func.Value() : Pica::Regs::CompareFunc::Always;
- // 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;
-
- res.combiner_buffer_input =
+ state.texture0_type = regs.texture0.type;
+
+ // Copy relevant tev stages fields.
+ // We don't sync const_color here because of the high variance, it is a
+ // shader uniform instead.
+ const auto& tev_stages = regs.GetTevStages();
+ DEBUG_ASSERT(state.tev_stages.size() == tev_stages.size());
+ for (size_t i = 0; i < tev_stages.size(); i++) {
+ const auto& tev_stage = tev_stages[i];
+ state.tev_stages[i].sources_raw = tev_stage.sources_raw;
+ state.tev_stages[i].modifiers_raw = tev_stage.modifiers_raw;
+ state.tev_stages[i].ops_raw = tev_stage.ops_raw;
+ state.tev_stages[i].scales_raw = tev_stage.scales_raw;
+ }
+
+ state.combiner_buffer_input =
regs.tev_combiner_buffer_input.update_mask_rgb.Value() |
regs.tev_combiner_buffer_input.update_mask_a.Value() << 4;
// Fragment lighting
- res.lighting.enable = !regs.lighting.disable;
- res.lighting.src_num = regs.lighting.num_lights + 1;
+ state.lighting.enable = !regs.lighting.disable;
+ state.lighting.src_num = regs.lighting.num_lights + 1;
- for (unsigned light_index = 0; light_index < res.lighting.src_num; ++light_index) {
+ for (unsigned light_index = 0; light_index < state.lighting.src_num; ++light_index) {
unsigned num = regs.lighting.light_enable.GetNum(light_index);
const auto& light = regs.lighting.light[num];
- res.lighting.light[light_index].num = num;
- res.lighting.light[light_index].directional = light.directional != 0;
- res.lighting.light[light_index].two_sided_diffuse = light.two_sided_diffuse != 0;
- res.lighting.light[light_index].dist_atten_enable = !regs.lighting.IsDistAttenDisabled(num);
- res.lighting.light[light_index].dist_atten_bias = Pica::float20::FromRaw(light.dist_atten_bias).ToFloat32();
- res.lighting.light[light_index].dist_atten_scale = Pica::float20::FromRaw(light.dist_atten_scale).ToFloat32();
+ state.lighting.light[light_index].num = num;
+ state.lighting.light[light_index].directional = light.directional != 0;
+ state.lighting.light[light_index].two_sided_diffuse = light.two_sided_diffuse != 0;
+ state.lighting.light[light_index].dist_atten_enable = !regs.lighting.IsDistAttenDisabled(num);
+ state.lighting.light[light_index].dist_atten_bias = Pica::float20::FromRaw(light.dist_atten_bias).ToFloat32();
+ state.lighting.light[light_index].dist_atten_scale = Pica::float20::FromRaw(light.dist_atten_scale).ToFloat32();
}
- res.lighting.lut_d0.enable = regs.lighting.disable_lut_d0 == 0;
- res.lighting.lut_d0.abs_input = regs.lighting.abs_lut_input.disable_d0 == 0;
- res.lighting.lut_d0.type = regs.lighting.lut_input.d0.Value();
- res.lighting.lut_d0.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d0);
-
- res.lighting.lut_d1.enable = regs.lighting.disable_lut_d1 == 0;
- res.lighting.lut_d1.abs_input = regs.lighting.abs_lut_input.disable_d1 == 0;
- res.lighting.lut_d1.type = regs.lighting.lut_input.d1.Value();
- res.lighting.lut_d1.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d1);
-
- res.lighting.lut_fr.enable = regs.lighting.disable_lut_fr == 0;
- res.lighting.lut_fr.abs_input = regs.lighting.abs_lut_input.disable_fr == 0;
- res.lighting.lut_fr.type = regs.lighting.lut_input.fr.Value();
- res.lighting.lut_fr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.fr);
-
- res.lighting.lut_rr.enable = regs.lighting.disable_lut_rr == 0;
- res.lighting.lut_rr.abs_input = regs.lighting.abs_lut_input.disable_rr == 0;
- res.lighting.lut_rr.type = regs.lighting.lut_input.rr.Value();
- res.lighting.lut_rr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rr);
-
- res.lighting.lut_rg.enable = regs.lighting.disable_lut_rg == 0;
- res.lighting.lut_rg.abs_input = regs.lighting.abs_lut_input.disable_rg == 0;
- res.lighting.lut_rg.type = regs.lighting.lut_input.rg.Value();
- res.lighting.lut_rg.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rg);
-
- res.lighting.lut_rb.enable = regs.lighting.disable_lut_rb == 0;
- res.lighting.lut_rb.abs_input = regs.lighting.abs_lut_input.disable_rb == 0;
- res.lighting.lut_rb.type = regs.lighting.lut_input.rb.Value();
- res.lighting.lut_rb.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rb);
-
- res.lighting.config = regs.lighting.config;
- res.lighting.fresnel_selector = regs.lighting.fresnel_selector;
- res.lighting.bump_mode = regs.lighting.bump_mode;
- res.lighting.bump_selector = regs.lighting.bump_selector;
- res.lighting.bump_renorm = regs.lighting.disable_bump_renorm == 0;
- res.lighting.clamp_highlights = regs.lighting.clamp_highlights != 0;
+ state.lighting.lut_d0.enable = regs.lighting.disable_lut_d0 == 0;
+ state.lighting.lut_d0.abs_input = regs.lighting.abs_lut_input.disable_d0 == 0;
+ state.lighting.lut_d0.type = regs.lighting.lut_input.d0.Value();
+ state.lighting.lut_d0.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d0);
+
+ state.lighting.lut_d1.enable = regs.lighting.disable_lut_d1 == 0;
+ state.lighting.lut_d1.abs_input = regs.lighting.abs_lut_input.disable_d1 == 0;
+ state.lighting.lut_d1.type = regs.lighting.lut_input.d1.Value();
+ state.lighting.lut_d1.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d1);
+
+ state.lighting.lut_fr.enable = regs.lighting.disable_lut_fr == 0;
+ state.lighting.lut_fr.abs_input = regs.lighting.abs_lut_input.disable_fr == 0;
+ state.lighting.lut_fr.type = regs.lighting.lut_input.fr.Value();
+ state.lighting.lut_fr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.fr);
+
+ state.lighting.lut_rr.enable = regs.lighting.disable_lut_rr == 0;
+ state.lighting.lut_rr.abs_input = regs.lighting.abs_lut_input.disable_rr == 0;
+ state.lighting.lut_rr.type = regs.lighting.lut_input.rr.Value();
+ state.lighting.lut_rr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rr);
+
+ state.lighting.lut_rg.enable = regs.lighting.disable_lut_rg == 0;
+ state.lighting.lut_rg.abs_input = regs.lighting.abs_lut_input.disable_rg == 0;
+ state.lighting.lut_rg.type = regs.lighting.lut_input.rg.Value();
+ state.lighting.lut_rg.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rg);
+
+ state.lighting.lut_rb.enable = regs.lighting.disable_lut_rb == 0;
+ state.lighting.lut_rb.abs_input = regs.lighting.abs_lut_input.disable_rb == 0;
+ state.lighting.lut_rb.type = regs.lighting.lut_input.rb.Value();
+ state.lighting.lut_rb.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rb);
+
+ state.lighting.config = regs.lighting.config;
+ state.lighting.fresnel_selector = regs.lighting.fresnel_selector;
+ state.lighting.bump_mode = regs.lighting.bump_mode;
+ state.lighting.bump_selector = regs.lighting.bump_selector;
+ state.lighting.bump_renorm = regs.lighting.disable_bump_renorm == 0;
+ state.lighting.clamp_highlights = regs.lighting.clamp_highlights != 0;
return res;
}
bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const {
- return (stage_index < 4) && (combiner_buffer_input & (1 << stage_index));
+ return (stage_index < 4) && (state.combiner_buffer_input & (1 << stage_index));
}
bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const {
- return (stage_index < 4) && ((combiner_buffer_input >> 4) & (1 << stage_index));
+ return (stage_index < 4) && ((state.combiner_buffer_input >> 4) & (1 << stage_index));
}
bool operator ==(const PicaShaderConfig& o) const {
- return std::memcmp(this, &o, sizeof(PicaShaderConfig)) == 0;
+ return std::memcmp(&state, &o.state, sizeof(PicaShaderConfig::State)) == 0;
};
- Pica::Regs::CompareFunc alpha_test_func = Pica::Regs::CompareFunc::Never;
- std::array<Pica::Regs::TevStageConfig, 6> tev_stages = {};
- u8 combiner_buffer_input = 0;
+ // NOTE: MSVC15 (Update 2) doesn't think `delete`'d constructors and operators are TC.
+ // This makes BitField not TC when used in a union or struct so we have to resort
+ // to this ugly hack.
+ // Once that bug is fixed we can use Pica::Regs::TevStageConfig here.
+ // Doesn't include const_color because we don't sync it, see comment in CurrentConfig()
+ struct TevStageConfigRaw {
+ u32 sources_raw;
+ u32 modifiers_raw;
+ u32 ops_raw;
+ u32 scales_raw;
+ explicit operator Pica::Regs::TevStageConfig() const noexcept {
+ Pica::Regs::TevStageConfig stage;
+ stage.sources_raw = sources_raw;
+ stage.modifiers_raw = modifiers_raw;
+ stage.ops_raw = ops_raw;
+ stage.const_color = 0;
+ stage.scales_raw = scales_raw;
+ return stage;
+ }
+ };
- struct {
- struct {
- unsigned num = 0;
- bool directional = false;
- bool two_sided_diffuse = false;
- bool dist_atten_enable = false;
- GLfloat dist_atten_scale = 0.0f;
- GLfloat dist_atten_bias = 0.0f;
- } light[8];
-
- bool enable = false;
- unsigned src_num = 0;
- Pica::Regs::LightingBumpMode bump_mode = Pica::Regs::LightingBumpMode::None;
- unsigned bump_selector = 0;
- bool bump_renorm = false;
- bool clamp_highlights = false;
-
- Pica::Regs::LightingConfig config = Pica::Regs::LightingConfig::Config0;
- Pica::Regs::LightingFresnelSelector fresnel_selector = Pica::Regs::LightingFresnelSelector::None;
+ struct State {
+
+ Pica::Regs::CompareFunc alpha_test_func;
+ Pica::Regs::TextureConfig::TextureType texture0_type;
+ std::array<TevStageConfigRaw, 6> tev_stages;
+ u8 combiner_buffer_input;
+
+ Pica::Regs::DepthBuffering depthmap_enable;
struct {
- bool enable = false;
- bool abs_input = false;
- Pica::Regs::LightingLutInput type = Pica::Regs::LightingLutInput::NH;
- float scale = 1.0f;
- } lut_d0, lut_d1, lut_fr, lut_rr, lut_rg, lut_rb;
- } lighting;
+ struct {
+ unsigned num;
+ bool directional;
+ bool two_sided_diffuse;
+ bool dist_atten_enable;
+ GLfloat dist_atten_scale;
+ GLfloat dist_atten_bias;
+ } light[8];
+
+ bool enable;
+ unsigned src_num;
+ Pica::Regs::LightingBumpMode bump_mode;
+ unsigned bump_selector;
+ bool bump_renorm;
+ bool clamp_highlights;
+
+ Pica::Regs::LightingConfig config;
+ Pica::Regs::LightingFresnelSelector fresnel_selector;
+
+ struct {
+ bool enable;
+ bool abs_input;
+ Pica::Regs::LightingLutInput type;
+ float scale;
+ } lut_d0, lut_d1, lut_fr, lut_rr, lut_rg, lut_rb;
+ } lighting;
+
+ } state;
};
+#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
+static_assert(std::is_trivially_copyable<PicaShaderConfig::State>::value, "PicaShaderConfig::State must be trivially copyable");
+#endif
namespace std {
template <>
struct hash<PicaShaderConfig> {
size_t operator()(const PicaShaderConfig& k) const {
- return Common::ComputeHash64(&k, sizeof(PicaShaderConfig));
+ return Common::ComputeHash64(&k.state, sizeof(PicaShaderConfig::State));
}
};
@@ -191,16 +229,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 +249,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;
@@ -265,6 +284,7 @@ private:
tex_coord1[1] = v.tc1.y.ToFloat32();
tex_coord2[0] = v.tc2.x.ToFloat32();
tex_coord2[1] = v.tc2.y.ToFloat32();
+ tex_coord0_w = v.tc0_w.ToFloat32();
normquat[0] = v.quat.x.ToFloat32();
normquat[1] = v.quat.y.ToFloat32();
normquat[2] = v.quat.z.ToFloat32();
@@ -285,6 +305,7 @@ private:
GLfloat tex_coord0[2];
GLfloat tex_coord1[2];
GLfloat tex_coord2[2];
+ GLfloat tex_coord0_w;
GLfloat normquat[4];
GLfloat view[3];
};
@@ -303,6 +324,7 @@ private:
GLvec4 const_color[6];
GLvec4 tev_combiner_buffer_color;
GLint alphatest_ref;
+ GLfloat depth_scale;
GLfloat depth_offset;
alignas(16) GLvec3 lighting_global_ambient;
LightSrc light_src[8];
@@ -311,23 +333,17 @@ 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();
- /// Syncs the depth scale and offset to match the PICA registers
- void SyncDepthModifiers();
+ /// Syncs the depth scale to match the PICA register
+ void SyncDepthScale();
+
+ /// Syncs the depth offset to match the PICA register
+ void SyncDepthOffset();
/// Syncs the blend enabled status to match the PICA register
void SyncBlendEnabled();
@@ -344,90 +360,70 @@ 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;
bool lut_dirty[6];
bool dirty;
- } uniform_block_data;
+ } 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<std::array<GLvec4, 256>, 6> lighting_lut_data;
+ 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 1323c12e4..7efd0038a 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -2,9 +2,19 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <memory>
+#include <algorithm>
+#include <atomic>
+#include <cstring>
+#include <iterator>
+#include <unordered_set>
+#include <utility>
+#include <vector>
-#include "common/hash.h"
+#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"
@@ -12,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>();
-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->addr = params.addr;
+ new_surface->size = params_size;
- if (cached_texture != texture_cache.end()) {
- state.texture_units[texture_unit].texture_2d = cached_texture->second->texture.handle;
- state.Apply();
+ 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;
+
+ new_surface->is_tiled = params.is_tiled;
+ new_surface->pixel_format = params.pixel_format;
+ new_surface->dirty = false;
+
+ 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);
- std::unique_ptr<CachedTexture> new_texture = std::make_unique<CachedTexture>();
+ 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;
- new_texture->texture.Create();
- state.texture_units[texture_unit].texture_2d = new_texture->texture.handle;
- state.Apply();
- glActiveTexture(GL_TEXTURE0 + texture_unit);
+ 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);
+ }
+ }
- u8* texture_src_data = Memory::GetPhysicalPointer(info.physical_address);
+ 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];
- 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);
+ u32 bytes_per_pixel = CachedSurface::GetFormatBpp(params.pixel_format) / 8;
- std::unique_ptr<Math::Vec4<u8>[]> temp_texture_buffer_rgba(new Math::Vec4<u8>[info.width * info.height]);
+ // OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type
+ bool use_4bpp = (params.pixel_format == PixelFormat::D24);
- 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);
+ 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();
+ }
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, info.width, info.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, temp_texture_buffer_rgba.get());
+ 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);
- texture_cache.emplace(info.physical_address, std::move(new_texture));
+ 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();
}
-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);
+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;
+ }
+
+ u32 total_pixels = params.width * params.height;
+ u32 params_size = total_pixels * CachedSurface::GetFormatBpp(params.pixel_format) / 8;
- for (auto it = texture_cache.begin(); it != cache_upper_bound;) {
- const auto& info = *it->second;
+ // Attempt to find encompassing surfaces
+ CachedSurface* best_subrect_surface = nullptr;
+ float subrect_surface_goodness = -1.f;
- // 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))) {
+ 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();
- it = texture_cache.erase(it);
+ // 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);
+ }
+
+ 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 {
- ++it;
+ 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);
}
-void RasterizerCacheOpenGL::InvalidateAll() {
- texture_cache.clear();
+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;
+}
+
+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);
+
+ 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];
+
+ 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);
+
+ 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 {
+ // 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::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..71d60e69c 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;
@@ -24,8 +32,9 @@ static bool IsPassThroughTevStage(const TevStageConfig& stage) {
}
/// Writes the specified TEV stage source component(s)
-static void AppendSource(std::string& out, TevStageConfig::Source source,
+static void AppendSource(std::string& out, const PicaShaderConfig& config, TevStageConfig::Source source,
const std::string& index_name) {
+ const auto& state = config.state;
using Source = TevStageConfig::Source;
switch (source) {
case Source::PrimaryColor:
@@ -38,7 +47,20 @@ static void AppendSource(std::string& out, TevStageConfig::Source source,
out += "secondary_fragment_color";
break;
case Source::Texture0:
- out += "texture(tex[0], texcoord[0])";
+ // Only unit 0 respects the texturing type (according to 3DBrew)
+ switch(state.texture0_type) {
+ case Pica::Regs::TextureConfig::Texture2D:
+ out += "texture(tex[0], texcoord[0])";
+ break;
+ case Pica::Regs::TextureConfig::Projection2D:
+ out += "textureProj(tex[0], vec3(texcoord[0], texcoord0_w))";
+ break;
+ default:
+ out += "texture(tex[0], texcoord[0])";
+ LOG_CRITICAL(HW_GPU, "Unhandled texture type %x", static_cast<int>(state.texture0_type));
+ UNIMPLEMENTED();
+ break;
+ }
break;
case Source::Texture1:
out += "texture(tex[1], texcoord[1])";
@@ -63,53 +85,53 @@ static void AppendSource(std::string& out, TevStageConfig::Source source,
}
/// Writes the color components to use for the specified TEV stage color modifier
-static void AppendColorModifier(std::string& out, TevStageConfig::ColorModifier modifier,
+static void AppendColorModifier(std::string& out, const PicaShaderConfig& config, TevStageConfig::ColorModifier modifier,
TevStageConfig::Source source, const std::string& index_name) {
using ColorModifier = TevStageConfig::ColorModifier;
switch (modifier) {
case ColorModifier::SourceColor:
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".rgb";
break;
case ColorModifier::OneMinusSourceColor:
out += "vec3(1.0) - ";
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".rgb";
break;
case ColorModifier::SourceAlpha:
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".aaa";
break;
case ColorModifier::OneMinusSourceAlpha:
out += "vec3(1.0) - ";
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".aaa";
break;
case ColorModifier::SourceRed:
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".rrr";
break;
case ColorModifier::OneMinusSourceRed:
out += "vec3(1.0) - ";
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".rrr";
break;
case ColorModifier::SourceGreen:
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".ggg";
break;
case ColorModifier::OneMinusSourceGreen:
out += "vec3(1.0) - ";
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".ggg";
break;
case ColorModifier::SourceBlue:
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".bbb";
break;
case ColorModifier::OneMinusSourceBlue:
out += "vec3(1.0) - ";
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".bbb";
break;
default:
@@ -120,44 +142,44 @@ static void AppendColorModifier(std::string& out, TevStageConfig::ColorModifier
}
/// Writes the alpha component to use for the specified TEV stage alpha modifier
-static void AppendAlphaModifier(std::string& out, TevStageConfig::AlphaModifier modifier,
+static void AppendAlphaModifier(std::string& out, const PicaShaderConfig& config, TevStageConfig::AlphaModifier modifier,
TevStageConfig::Source source, const std::string& index_name) {
using AlphaModifier = TevStageConfig::AlphaModifier;
switch (modifier) {
case AlphaModifier::SourceAlpha:
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".a";
break;
case AlphaModifier::OneMinusSourceAlpha:
out += "1.0 - ";
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".a";
break;
case AlphaModifier::SourceRed:
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".r";
break;
case AlphaModifier::OneMinusSourceRed:
out += "1.0 - ";
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".r";
break;
case AlphaModifier::SourceGreen:
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".g";
break;
case AlphaModifier::OneMinusSourceGreen:
out += "1.0 - ";
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".g";
break;
case AlphaModifier::SourceBlue:
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".b";
break;
case AlphaModifier::OneMinusSourceBlue:
out += "1.0 - ";
- AppendSource(out, source, index_name);
+ AppendSource(out, config, source, index_name);
out += ".b";
break;
default:
@@ -198,6 +220,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);
@@ -276,16 +301,16 @@ static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) {
/// Writes the code to emulate the specified TEV stage
static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsigned index) {
- auto& stage = config.tev_stages[index];
+ const auto stage = static_cast<const Pica::Regs::TevStageConfig>(config.state.tev_stages[index]);
if (!IsPassThroughTevStage(stage)) {
std::string index_name = std::to_string(index);
out += "vec3 color_results_" + index_name + "[3] = vec3[3](";
- AppendColorModifier(out, stage.color_modifier1, stage.color_source1, index_name);
+ AppendColorModifier(out, config, stage.color_modifier1, stage.color_source1, index_name);
out += ", ";
- AppendColorModifier(out, stage.color_modifier2, stage.color_source2, index_name);
+ AppendColorModifier(out, config, stage.color_modifier2, stage.color_source2, index_name);
out += ", ";
- AppendColorModifier(out, stage.color_modifier3, stage.color_source3, index_name);
+ AppendColorModifier(out, config, stage.color_modifier3, stage.color_source3, index_name);
out += ");\n";
out += "vec3 color_output_" + index_name + " = ";
@@ -293,11 +318,11 @@ static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsi
out += ";\n";
out += "float alpha_results_" + index_name + "[3] = float[3](";
- AppendAlphaModifier(out, stage.alpha_modifier1, stage.alpha_source1, index_name);
+ AppendAlphaModifier(out, config, stage.alpha_modifier1, stage.alpha_source1, index_name);
out += ", ";
- AppendAlphaModifier(out, stage.alpha_modifier2, stage.alpha_source2, index_name);
+ AppendAlphaModifier(out, config, stage.alpha_modifier2, stage.alpha_source2, index_name);
out += ", ";
- AppendAlphaModifier(out, stage.alpha_modifier3, stage.alpha_source3, index_name);
+ AppendAlphaModifier(out, config, stage.alpha_modifier3, stage.alpha_source3, index_name);
out += ");\n";
out += "float alpha_output_" + index_name + " = ";
@@ -320,6 +345,8 @@ static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsi
/// Writes the code to emulate fragment lighting
static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
+ const auto& lighting = config.state.lighting;
+
// Define lighting globals
out += "vec4 diffuse_sum = vec4(0.0, 0.0, 0.0, 1.0);\n"
"vec4 specular_sum = vec4(0.0, 0.0, 0.0, 1.0);\n"
@@ -327,17 +354,17 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
"vec3 refl_value = vec3(0.0);\n";
// Compute fragment normals
- if (config.lighting.bump_mode == Pica::Regs::LightingBumpMode::NormalMap) {
+ if (lighting.bump_mode == Pica::Regs::LightingBumpMode::NormalMap) {
// Bump mapping is enabled using a normal map, read perturbation vector from the selected texture
- std::string bump_selector = std::to_string(config.lighting.bump_selector);
+ std::string bump_selector = std::to_string(lighting.bump_selector);
out += "vec3 surface_normal = 2.0 * texture(tex[" + bump_selector + "], texcoord[" + bump_selector + "]).rgb - 1.0;\n";
// Recompute Z-component of perturbation if 'renorm' is enabled, this provides a higher precision result
- if (config.lighting.bump_renorm) {
+ if (lighting.bump_renorm) {
std::string val = "(1.0 - (surface_normal.x*surface_normal.x + surface_normal.y*surface_normal.y))";
out += "surface_normal.z = sqrt(max(" + val + ", 0.0));\n";
}
- } else if (config.lighting.bump_mode == Pica::Regs::LightingBumpMode::TangentMap) {
+ } else if (lighting.bump_mode == Pica::Regs::LightingBumpMode::TangentMap) {
// Bump mapping is enabled using a tangent map
LOG_CRITICAL(HW_GPU, "unimplemented bump mapping mode (tangent mapping)");
UNIMPLEMENTED();
@@ -350,7 +377,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
out += "vec3 normal = normalize(quaternion_rotate(normquat, surface_normal));\n";
// Gets the index into the specified lookup table for specular lighting
- auto GetLutIndex = [config](unsigned light_num, Regs::LightingLutInput input, bool abs) {
+ auto GetLutIndex = [&lighting](unsigned light_num, Regs::LightingLutInput input, bool abs) {
const std::string half_angle = "normalize(normalize(view) + light_vector)";
std::string index;
switch (input) {
@@ -378,7 +405,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
if (abs) {
// LUT index is in the range of (0.0, 1.0)
- index = config.lighting.light[light_num].two_sided_diffuse ? "abs(" + index + ")" : "max(" + index + ", 0.f)";
+ index = lighting.light[light_num].two_sided_diffuse ? "abs(" + index + ")" : "max(" + index + ", 0.f)";
return "(FLOAT_255 * clamp(" + index + ", 0.0, 1.0))";
} else {
// LUT index is in the range of (-1.0, 1.0)
@@ -396,8 +423,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
};
// Write the code to emulate each enabled light
- for (unsigned light_index = 0; light_index < config.lighting.src_num; ++light_index) {
- const auto& light_config = config.lighting.light[light_index];
+ for (unsigned light_index = 0; light_index < lighting.src_num; ++light_index) {
+ const auto& light_config = lighting.light[light_index];
std::string light_src = "light_src[" + std::to_string(light_config.num) + "]";
// Compute light vector (directional or positional)
@@ -421,39 +448,39 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
}
// If enabled, clamp specular component if lighting result is negative
- std::string clamp_highlights = config.lighting.clamp_highlights ? "(dot(light_vector, normal) <= 0.0 ? 0.0 : 1.0)" : "1.0";
+ std::string clamp_highlights = lighting.clamp_highlights ? "(dot(light_vector, normal) <= 0.0 ? 0.0 : 1.0)" : "1.0";
// Specular 0 component
std::string d0_lut_value = "1.0";
- if (config.lighting.lut_d0.enable && Pica::Regs::IsLightingSamplerSupported(config.lighting.config, Pica::Regs::LightingSampler::Distribution0)) {
+ if (lighting.lut_d0.enable && Pica::Regs::IsLightingSamplerSupported(lighting.config, Pica::Regs::LightingSampler::Distribution0)) {
// Lookup specular "distribution 0" LUT value
- std::string index = GetLutIndex(light_config.num, config.lighting.lut_d0.type, config.lighting.lut_d0.abs_input);
- d0_lut_value = "(" + std::to_string(config.lighting.lut_d0.scale) + " * " + GetLutValue(Regs::LightingSampler::Distribution0, index) + ")";
+ std::string index = GetLutIndex(light_config.num, lighting.lut_d0.type, lighting.lut_d0.abs_input);
+ d0_lut_value = "(" + std::to_string(lighting.lut_d0.scale) + " * " + GetLutValue(Regs::LightingSampler::Distribution0, index) + ")";
}
std::string specular_0 = "(" + d0_lut_value + " * " + light_src + ".specular_0)";
// If enabled, lookup ReflectRed value, otherwise, 1.0 is used
- if (config.lighting.lut_rr.enable && Pica::Regs::IsLightingSamplerSupported(config.lighting.config, Pica::Regs::LightingSampler::ReflectRed)) {
- std::string index = GetLutIndex(light_config.num, config.lighting.lut_rr.type, config.lighting.lut_rr.abs_input);
- std::string value = "(" + std::to_string(config.lighting.lut_rr.scale) + " * " + GetLutValue(Regs::LightingSampler::ReflectRed, index) + ")";
+ if (lighting.lut_rr.enable && Pica::Regs::IsLightingSamplerSupported(lighting.config, Pica::Regs::LightingSampler::ReflectRed)) {
+ std::string index = GetLutIndex(light_config.num, lighting.lut_rr.type, lighting.lut_rr.abs_input);
+ std::string value = "(" + std::to_string(lighting.lut_rr.scale) + " * " + GetLutValue(Regs::LightingSampler::ReflectRed, index) + ")";
out += "refl_value.r = " + value + ";\n";
} else {
out += "refl_value.r = 1.0;\n";
}
// If enabled, lookup ReflectGreen value, otherwise, ReflectRed value is used
- if (config.lighting.lut_rg.enable && Pica::Regs::IsLightingSamplerSupported(config.lighting.config, Pica::Regs::LightingSampler::ReflectGreen)) {
- std::string index = GetLutIndex(light_config.num, config.lighting.lut_rg.type, config.lighting.lut_rg.abs_input);
- std::string value = "(" + std::to_string(config.lighting.lut_rg.scale) + " * " + GetLutValue(Regs::LightingSampler::ReflectGreen, index) + ")";
+ if (lighting.lut_rg.enable && Pica::Regs::IsLightingSamplerSupported(lighting.config, Pica::Regs::LightingSampler::ReflectGreen)) {
+ std::string index = GetLutIndex(light_config.num, lighting.lut_rg.type, lighting.lut_rg.abs_input);
+ std::string value = "(" + std::to_string(lighting.lut_rg.scale) + " * " + GetLutValue(Regs::LightingSampler::ReflectGreen, index) + ")";
out += "refl_value.g = " + value + ";\n";
} else {
out += "refl_value.g = refl_value.r;\n";
}
// If enabled, lookup ReflectBlue value, otherwise, ReflectRed value is used
- if (config.lighting.lut_rb.enable && Pica::Regs::IsLightingSamplerSupported(config.lighting.config, Pica::Regs::LightingSampler::ReflectBlue)) {
- std::string index = GetLutIndex(light_config.num, config.lighting.lut_rb.type, config.lighting.lut_rb.abs_input);
- std::string value = "(" + std::to_string(config.lighting.lut_rb.scale) + " * " + GetLutValue(Regs::LightingSampler::ReflectBlue, index) + ")";
+ if (lighting.lut_rb.enable && Pica::Regs::IsLightingSamplerSupported(lighting.config, Pica::Regs::LightingSampler::ReflectBlue)) {
+ std::string index = GetLutIndex(light_config.num, lighting.lut_rb.type, lighting.lut_rb.abs_input);
+ std::string value = "(" + std::to_string(lighting.lut_rb.scale) + " * " + GetLutValue(Regs::LightingSampler::ReflectBlue, index) + ")";
out += "refl_value.b = " + value + ";\n";
} else {
out += "refl_value.b = refl_value.r;\n";
@@ -461,27 +488,27 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
// Specular 1 component
std::string d1_lut_value = "1.0";
- if (config.lighting.lut_d1.enable && Pica::Regs::IsLightingSamplerSupported(config.lighting.config, Pica::Regs::LightingSampler::Distribution1)) {
+ if (lighting.lut_d1.enable && Pica::Regs::IsLightingSamplerSupported(lighting.config, Pica::Regs::LightingSampler::Distribution1)) {
// Lookup specular "distribution 1" LUT value
- std::string index = GetLutIndex(light_config.num, config.lighting.lut_d1.type, config.lighting.lut_d1.abs_input);
- d1_lut_value = "(" + std::to_string(config.lighting.lut_d1.scale) + " * " + GetLutValue(Regs::LightingSampler::Distribution1, index) + ")";
+ std::string index = GetLutIndex(light_config.num, lighting.lut_d1.type, lighting.lut_d1.abs_input);
+ d1_lut_value = "(" + std::to_string(lighting.lut_d1.scale) + " * " + GetLutValue(Regs::LightingSampler::Distribution1, index) + ")";
}
std::string specular_1 = "(" + d1_lut_value + " * refl_value * " + light_src + ".specular_1)";
// Fresnel
- if (config.lighting.lut_fr.enable && Pica::Regs::IsLightingSamplerSupported(config.lighting.config, Pica::Regs::LightingSampler::Fresnel)) {
+ if (lighting.lut_fr.enable && Pica::Regs::IsLightingSamplerSupported(lighting.config, Pica::Regs::LightingSampler::Fresnel)) {
// Lookup fresnel LUT value
- std::string index = GetLutIndex(light_config.num, config.lighting.lut_fr.type, config.lighting.lut_fr.abs_input);
- std::string value = "(" + std::to_string(config.lighting.lut_fr.scale) + " * " + GetLutValue(Regs::LightingSampler::Fresnel, index) + ")";
+ std::string index = GetLutIndex(light_config.num, lighting.lut_fr.type, lighting.lut_fr.abs_input);
+ std::string value = "(" + std::to_string(lighting.lut_fr.scale) + " * " + GetLutValue(Regs::LightingSampler::Fresnel, index) + ")";
// Enabled for difffuse lighting alpha component
- if (config.lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::PrimaryAlpha ||
- config.lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::Both)
+ if (lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::PrimaryAlpha ||
+ lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::Both)
out += "diffuse_sum.a *= " + value + ";\n";
// Enabled for the specular lighting alpha component
- if (config.lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::SecondaryAlpha ||
- config.lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::Both)
+ if (lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::SecondaryAlpha ||
+ lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::Both)
out += "specular_sum.a *= " + value + ";\n";
}
@@ -499,6 +526,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
}
std::string GenerateFragmentShader(const PicaShaderConfig& config) {
+ const auto& state = config.state;
+
std::string out = R"(
#version 330 core
#define NUM_TEV_STAGES 6
@@ -508,6 +537,7 @@ std::string GenerateFragmentShader(const PicaShaderConfig& config) {
in vec4 primary_color;
in vec2 texcoord[3];
+in float texcoord0_w;
in vec4 normquat;
in vec3 view;
@@ -525,6 +555,7 @@ layout (std140) uniform shader_data {
vec4 const_color[NUM_TEV_STAGES];
vec4 tev_combiner_buffer_color;
int alphatest_ref;
+ float depth_scale;
float depth_offset;
vec3 lighting_global_ambient;
LightSrc light_src[NUM_LIGHTS];
@@ -544,29 +575,37 @@ vec4 secondary_fragment_color = vec4(0.0);
)";
// Do not do any sort of processing if it's obvious we're not going to pass the alpha test
- if (config.alpha_test_func == Regs::CompareFunc::Never) {
+ if (state.alpha_test_func == Regs::CompareFunc::Never) {
out += "discard; }";
return out;
}
- if (config.lighting.enable)
+ if (state.lighting.enable)
WriteLighting(out, config);
out += "vec4 combiner_buffer = vec4(0.0);\n";
out += "vec4 next_combiner_buffer = tev_combiner_buffer_color;\n";
out += "vec4 last_tex_env_out = vec4(0.0);\n";
- for (size_t index = 0; index < config.tev_stages.size(); ++index)
+ for (size_t index = 0; index < state.tev_stages.size(); ++index)
WriteTevStage(out, config, (unsigned)index);
- if (config.alpha_test_func != Regs::CompareFunc::Always) {
+ if (state.alpha_test_func != Regs::CompareFunc::Always) {
out += "if (";
- AppendAlphaTestCondition(out, config.alpha_test_func);
+ AppendAlphaTestCondition(out, state.alpha_test_func);
out += ") discard;\n";
}
out += "color = last_tex_env_out;\n";
- out += "gl_FragDepth = gl_FragCoord.z + depth_offset;\n}";
+
+ out += "float z_over_w = 1.0 - gl_FragCoord.z * 2.0;\n";
+ out += "float depth = z_over_w * depth_scale + depth_offset;\n";
+ if (state.depthmap_enable == Pica::Regs::DepthBuffering::WBuffering) {
+ out += "depth /= gl_FragCoord.w;\n";
+ }
+ out += "gl_FragDepth = depth;\n";
+
+ out += "}";
return out;
}
@@ -574,17 +613,19 @@ vec4 secondary_fragment_color = vec4(0.0);
std::string GenerateVertexShader() {
std::string out = "#version 330 core\n";
- out += "layout(location = " + std::to_string((int)ATTRIBUTE_POSITION) + ") in vec4 vert_position;\n";
- out += "layout(location = " + std::to_string((int)ATTRIBUTE_COLOR) + ") in vec4 vert_color;\n";
- out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD0) + ") in vec2 vert_texcoord0;\n";
- out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD1) + ") in vec2 vert_texcoord1;\n";
- out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD2) + ") in vec2 vert_texcoord2;\n";
- out += "layout(location = " + std::to_string((int)ATTRIBUTE_NORMQUAT) + ") in vec4 vert_normquat;\n";
- out += "layout(location = " + std::to_string((int)ATTRIBUTE_VIEW) + ") in vec3 vert_view;\n";
+ out += "layout(location = " + std::to_string((int)ATTRIBUTE_POSITION) + ") in vec4 vert_position;\n";
+ out += "layout(location = " + std::to_string((int)ATTRIBUTE_COLOR) + ") in vec4 vert_color;\n";
+ out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD0) + ") in vec2 vert_texcoord0;\n";
+ out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD1) + ") in vec2 vert_texcoord1;\n";
+ out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD2) + ") in vec2 vert_texcoord2;\n";
+ out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD0_W) + ") in float vert_texcoord0_w;\n";
+ out += "layout(location = " + std::to_string((int)ATTRIBUTE_NORMQUAT) + ") in vec4 vert_normquat;\n";
+ out += "layout(location = " + std::to_string((int)ATTRIBUTE_VIEW) + ") in vec3 vert_view;\n";
out += R"(
out vec4 primary_color;
out vec2 texcoord[3];
+out float texcoord0_w;
out vec4 normquat;
out vec3 view;
@@ -593,6 +634,7 @@ void main() {
texcoord[0] = vert_texcoord0;
texcoord[1] = vert_texcoord1;
texcoord[2] = vert_texcoord2;
+ texcoord0_w = vert_texcoord0_w;
normquat = vert_normquat;
view = vert_view;
gl_Position = vec4(vert_position.x, vert_position.y, -vert_position.z, vert_position.w);
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h
index 0ca9d2879..bef3249cf 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.h
+++ b/src/video_core/renderer_opengl/gl_shader_gen.h
@@ -6,7 +6,7 @@
#include <string>
-#include "video_core/renderer_opengl/gl_rasterizer.h"
+union 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_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h
index 097242f6f..f59912f79 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.h
+++ b/src/video_core/renderer_opengl/gl_shader_util.h
@@ -14,6 +14,7 @@ enum Attributes {
ATTRIBUTE_TEXCOORD0,
ATTRIBUTE_TEXCOORD1,
ATTRIBUTE_TEXCOORD2,
+ ATTRIBUTE_TEXCOORD0_W,
ATTRIBUTE_NORMQUAT,
ATTRIBUTE_VIEW,
};
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index 08e4d0b54..fa141fc9a 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;
@@ -32,6 +36,8 @@ OpenGLState::OpenGLState() {
stencil.action_stencil_fail = GL_KEEP;
blend.enabled = false;
+ blend.rgb_equation = GL_FUNC_ADD;
+ blend.a_equation = GL_FUNC_ADD;
blend.src_rgb_func = GL_ONE;
blend.dst_rgb_func = GL_ZERO;
blend.src_a_func = GL_ONE;
@@ -48,17 +54,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) {
@@ -159,6 +167,11 @@ void OpenGLState::Apply() {
blend.src_a_func, blend.dst_a_func);
}
+ if (blend.rgb_equation != cur_state.blend.rgb_equation ||
+ blend.a_equation != cur_state.blend.a_equation) {
+ glBlendEquationSeparate(blend.rgb_equation, blend.a_equation);
+ }
+
if (logic_op != cur_state.logic_op) {
glLogicOp(logic_op);
}
@@ -175,16 +188,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 +226,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..228727054 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -40,6 +40,8 @@ public:
struct {
bool enabled; // GL_BLEND
+ GLenum rgb_equation; // GL_BLEND_EQUATION_RGB
+ GLenum a_equation; // GL_BLEND_EQUATION_ALPHA
GLenum src_rgb_func; // GL_BLEND_SRC_RGB
GLenum dst_rgb_func; // GL_BLEND_DST_RGB
GLenum src_a_func; // GL_BLEND_SRC_ALPHA
@@ -63,15 +65,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 +84,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 fd3617d77..6dc2758c5 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"
@@ -71,6 +78,26 @@ inline GLenum WrapMode(Pica::Regs::TextureConfig::WrapMode mode) {
return gl_mode;
}
+inline GLenum BlendEquation(Pica::Regs::BlendEquation equation) {
+ static const GLenum blend_equation_table[] = {
+ GL_FUNC_ADD, // BlendEquation::Add
+ GL_FUNC_SUBTRACT, // BlendEquation::Subtract
+ GL_FUNC_REVERSE_SUBTRACT, // BlendEquation::ReverseSubtract
+ GL_MIN, // BlendEquation::Min
+ GL_MAX, // BlendEquation::Max
+ };
+
+ // Range check table for input
+ if (static_cast<size_t>(equation) >= ARRAY_SIZE(blend_equation_table)) {
+ LOG_CRITICAL(Render_OpenGL, "Unknown blend equation %d", equation);
+ UNREACHABLE();
+
+ return GL_FUNC_ADD;
+ }
+
+ return blend_equation_table[(unsigned)equation];
+}
+
inline GLenum BlendFunc(Pica::Regs::BlendFactor factor) {
static const GLenum blend_func_table[] = {
GL_ZERO, // BlendFactor::Zero
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 11c4d0daf..8f424a435 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, static_cast<u32>(pixel_stride), screen_info)) {
+ // Reset the screen info's display texture to its own permanent texture
+ screen_info.display_texture = screen_info.texture.resource.handle;
+ screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f);
- 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++;
@@ -448,12 +473,6 @@ static void DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity,
bool RendererOpenGL::Init() {
render_window->MakeCurrent();
- // TODO: Make frontends initialize this, so they can use gladLoadGLLoader with their own loaders
- if (!gladLoadGL()) {
- LOG_CRITICAL(Render_OpenGL, "Failed to initialize GL functions! Exiting...");
- exit(-1);
- }
-
if (GLAD_GL_KHR_debug) {
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(DebugHandler, nullptr);
diff --git a/src/video_core/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 78d295c76..161097610 100644
--- a/src/video_core/shader/shader.cpp
+++ b/src/video_core/shader/shader.cpp
@@ -2,118 +2,91 @@
// 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/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 std::unordered_map<u64, std::unique_ptr<JitShader>> shader_map;
+static const JitShader* jit_shader;
+#endif // ARCHITECTURE_x86_64
-static void ClearCache() {
+void ClearCache() {
+#ifdef ARCHITECTURE_x86_64
shader_map.clear();
- jit.Clear();
- LOG_INFO(HW_GPU, "Shader JIT cache cleared");
-}
#endif // ARCHITECTURE_x86_64
+}
-void Setup(UnitState<false>& state) {
+void ShaderSetup::Setup() {
#ifdef ARCHITECTURE_x86_64
if (VideoCore::g_shader_jit_enabled) {
u64 cache_key = (Common::ComputeHash64(&g_state.vs.program_code, sizeof(g_state.vs.program_code)) ^
- Common::ComputeHash64(&g_state.vs.swizzle_data, sizeof(g_state.vs.swizzle_data)) ^
- 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
}
-void Shutdown() {
-#ifdef ARCHITECTURE_x86_64
- ClearCache();
-#endif // ARCHITECTURE_x86_64
-}
-
-static Common::Profiling::TimingCategory shader_category("Vertex Shader");
-MICROPROFILE_DEFINE(GPU_VertexShader, "GPU", "Vertex Shader", MP_RGB(50, 50, 240));
+MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240));
-OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attributes) {
+OutputVertex ShaderSetup::Run(UnitState<false>& state, const InputVertex& input, int num_attributes) {
auto& config = g_state.regs.vs;
+ auto& setup = g_state.vs;
- Common::Profiling::ScopeTimer timer(shader_category);
- MICROPROFILE_SCOPE(GPU_VertexShader);
+ MICROPROFILE_SCOPE(GPU_Shader);
- state.program_counter = config.main_offset;
state.debug.max_offset = 0;
state.debug.max_opdesc_id = 0;
// 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(setup, state, config.main_offset);
else
- RunInterpreter(state);
+ RunInterpreter(setup, state, config.main_offset);
#else
- RunInterpreter(state);
+ RunInterpreter(setup, state, config.main_offset);
#endif // ARCHITECTURE_x86_64
// Setup output data
@@ -167,10 +140,9 @@ OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attr
return ret;
}
-DebugData<true> ProduceDebugInfo(const InputVertex& input, int num_attributes, const Regs::ShaderConfig& config, const ShaderSetup& setup) {
+DebugData<true> ShaderSetup::ProduceDebugInfo(const InputVertex& input, int num_attributes, const Regs::ShaderConfig& config, const ShaderSetup& setup) {
UnitState<true> state;
- state.program_counter = config.main_offset;
state.debug.max_offset = 0;
state.debug.max_opdesc_id = 0;
@@ -179,27 +151,13 @@ 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;
- RunInterpreter(state);
+ RunInterpreter(setup, state, config.main_offset);
return state.debug;
}
diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h
index 7af8f1fa1..84898f21c 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 {
@@ -37,7 +43,8 @@ struct OutputVertex {
Math::Vec4<float24> color;
Math::Vec2<float24> tc0;
Math::Vec2<float24> tc1;
- INSERT_PADDING_WORDS(2);
+ float24 tc0_w;
+ INSERT_PADDING_WORDS(1);
Math::Vec3<float24> view;
INSERT_PADDING_WORDS(1);
Math::Vec2<float24> tc2;
@@ -77,23 +84,6 @@ struct OutputVertex {
static_assert(std::is_pod<OutputVertex>::value, "Structure is not POD");
static_assert(sizeof(OutputVertex) == 32 * sizeof(float), "OutputVertex has invalid size");
-/// Vertex shader memory
-struct ShaderSetup {
- struct {
- // The float uniforms are accessed by the shader JIT using SSE instructions, and are
- // therefore required to be 16-byte aligned.
- alignas(16) Math::Vec4<float24> f[96];
-
- std::array<bool, 16> b;
- std::array<Math::Vec4<u8>, 4> i;
- } uniforms;
-
- Math::Vec4<float24> default_attributes[16];
-
- std::array<u32, 1024> program_code;
- std::array<u32, 1024> swizzle_data;
-};
-
// Helper structure used to keep track of data useful for inspection of shader emulation
template<bool full_debugging>
struct DebugData;
@@ -282,38 +272,21 @@ struct UnitState {
} registers;
static_assert(std::is_pod<Registers>::value, "Structure is not POD");
- u32 program_counter;
bool conditional_code[2];
// Two Address registers and one loop counter
// TODO: How many bits do these actually have?
s32 address_registers[3];
- enum {
- INVALID_ADDRESS = 0xFFFFFFFF
- };
-
- struct CallStackElement {
- u32 final_address; // Address upon which we jump to return_address
- u32 return_address; // Where to jump when leaving scope
- u8 repeat_counter; // How often to repeat until this call stack element is removed
- u8 loop_increment; // Which value to add to the loop counter after an iteration
- // TODO: Should this be a signed value? Does it even matter?
- u32 loop_address; // The address where we'll return to after each loop iteration
- };
-
- // TODO: Is there a maximal size for this?
- boost::container::static_vector<CallStackElement, 16> call_stack;
-
DebugData<Debug> debug;
static size_t InputOffset(const SourceRegister& reg) {
switch (reg.GetRegisterType()) {
case RegisterType::Input:
- return offsetof(UnitState::Registers, input) + reg.GetIndex()*sizeof(Math::Vec4<float24>);
+ return offsetof(UnitState, registers.input) + reg.GetIndex()*sizeof(Math::Vec4<float24>);
case RegisterType::Temporary:
- return offsetof(UnitState::Registers, temporary) + reg.GetIndex()*sizeof(Math::Vec4<float24>);
+ return offsetof(UnitState, registers.temporary) + reg.GetIndex()*sizeof(Math::Vec4<float24>);
default:
UNREACHABLE();
@@ -324,10 +297,10 @@ struct UnitState {
static size_t OutputOffset(const DestRegister& reg) {
switch (reg.GetRegisterType()) {
case RegisterType::Output:
- return offsetof(UnitState::Registers, output) + reg.GetIndex()*sizeof(Math::Vec4<float24>);
+ return offsetof(UnitState, registers.output) + reg.GetIndex()*sizeof(Math::Vec4<float24>);
case RegisterType::Temporary:
- return offsetof(UnitState::Registers, temporary) + reg.GetIndex()*sizeof(Math::Vec4<float24>);
+ return offsetof(UnitState, registers.temporary) + reg.GetIndex()*sizeof(Math::Vec4<float24>);
default:
UNREACHABLE();
@@ -336,34 +309,66 @@ struct UnitState {
}
};
-/**
- * Performs any shader unit setup that only needs to happen once per shader (as opposed to once per
- * vertex, which would happen within the `Run` function).
- * @param state Shader unit state, must be setup per shader and per shader unit
- */
-void Setup(UnitState<false>& state);
+/// Clears the shader cache
+void ClearCache();
-/// Performs any cleanup when the emulator is shutdown
-void Shutdown();
+struct ShaderSetup {
-/**
- * Runs the currently setup shader
- * @param state Shader unit state, must be setup per shader and per shader unit
- * @param input Input vertex into the shader
- * @param num_attributes The number of vertex shader attributes
- * @return The output vertex, after having been processed by the vertex shader
- */
-OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attributes);
+ struct {
+ // The float uniforms are accessed by the shader JIT using SSE instructions, and are
+ // therefore required to be 16-byte aligned.
+ alignas(16) Math::Vec4<float24> f[96];
-/**
- * Produce debug information based on the given shader and input vertex
- * @param input Input vertex into the shader
- * @param num_attributes The number of vertex shader attributes
- * @param config Configuration object for the shader pipeline
- * @param setup Setup object for the shader pipeline
- * @return Debug information for this shader with regards to the given vertex
- */
-DebugData<true> ProduceDebugInfo(const InputVertex& input, int num_attributes, const Regs::ShaderConfig& config, const ShaderSetup& setup);
+ std::array<bool, 16> b;
+ std::array<Math::Vec4<u8>, 4> i;
+ } uniforms;
+
+ static size_t UniformOffset(RegisterType type, unsigned index) {
+ switch (type) {
+ case RegisterType::FloatUniform:
+ return offsetof(ShaderSetup, uniforms.f) + index*sizeof(Math::Vec4<float24>);
+
+ case RegisterType::BoolUniform:
+ return offsetof(ShaderSetup, uniforms.b) + index*sizeof(bool);
+
+ case RegisterType::IntUniform:
+ return offsetof(ShaderSetup, uniforms.i) + index*sizeof(Math::Vec4<u8>);
+
+ default:
+ UNREACHABLE();
+ return 0;
+ }
+ }
+
+ std::array<u32, 1024> program_code;
+ std::array<u32, 1024> swizzle_data;
+
+ /**
+ * Performs any shader unit setup that only needs to happen once per shader (as opposed to once per
+ * vertex, which would happen within the `Run` function).
+ */
+ void Setup();
+
+ /**
+ * Runs the currently setup shader
+ * @param state Shader unit state, must be setup per shader and per shader unit
+ * @param input Input vertex into the shader
+ * @param num_attributes The number of vertex shader attributes
+ * @return The output vertex, after having been processed by the vertex shader
+ */
+ OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attributes);
+
+ /**
+ * Produce debug information based on the given shader and input vertex
+ * @param input Input vertex into the shader
+ * @param num_attributes The number of vertex shader attributes
+ * @param config Configuration object for the shader pipeline
+ * @param setup Setup object for the shader pipeline
+ * @return Debug information for this shader with regards to the given vertex
+ */
+ DebugData<true> ProduceDebugInfo(const InputVertex& input, int num_attributes, const Regs::ShaderConfig& config, const ShaderSetup& setup);
+
+};
} // namespace Shader
diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp
index 9b978583e..714e8bfd5 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"
@@ -21,8 +29,24 @@ namespace Pica {
namespace Shader {
+constexpr u32 INVALID_ADDRESS = 0xFFFFFFFF;
+
+struct CallStackElement {
+ u32 final_address; // Address upon which we jump to return_address
+ u32 return_address; // Where to jump when leaving scope
+ u8 repeat_counter; // How often to repeat until this call stack element is removed
+ u8 loop_increment; // Which value to add to the loop counter after an iteration
+ // TODO: Should this be a signed value? Does it even matter?
+ u32 loop_address; // The address where we'll return to after each loop iteration
+};
+
template<bool Debug>
-void RunInterpreter(UnitState<Debug>& state) {
+void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned offset) {
+ // TODO: Is there a maximal size for this?
+ boost::container::static_vector<CallStackElement, 16> call_stack;
+
+ u32 program_counter = offset;
+
const auto& uniforms = g_state.vs.uniforms;
const auto& swizzle_data = g_state.vs.swizzle_data;
const auto& program_code = g_state.vs.program_code;
@@ -33,16 +57,16 @@ void RunInterpreter(UnitState<Debug>& state) {
unsigned iteration = 0;
bool exit_loop = false;
while (!exit_loop) {
- if (!state.call_stack.empty()) {
- auto& top = state.call_stack.back();
- if (state.program_counter == top.final_address) {
+ if (!call_stack.empty()) {
+ auto& top = call_stack.back();
+ if (program_counter == top.final_address) {
state.address_registers[2] += top.loop_increment;
if (top.repeat_counter-- == 0) {
- state.program_counter = top.return_address;
- state.call_stack.pop_back();
+ program_counter = top.return_address;
+ call_stack.pop_back();
} else {
- state.program_counter = top.loop_address;
+ program_counter = top.loop_address;
}
// TODO: Is "trying again" accurate to hardware?
@@ -50,20 +74,20 @@ void RunInterpreter(UnitState<Debug>& state) {
}
}
- const Instruction instr = { program_code[state.program_counter] };
+ const Instruction instr = { program_code[program_counter] };
const SwizzlePattern swizzle = { swizzle_data[instr.common.operand_desc_id] };
- static auto call = [](UnitState<Debug>& state, u32 offset, u32 num_instructions,
+ static auto call = [&program_counter, &call_stack](UnitState<Debug>& state, u32 offset, u32 num_instructions,
u32 return_offset, u8 repeat_count, u8 loop_increment) {
- state.program_counter = offset - 1; // -1 to make sure when incrementing the PC we end up at the correct offset
- ASSERT(state.call_stack.size() < state.call_stack.capacity());
- state.call_stack.push_back({ offset + num_instructions, return_offset, repeat_count, loop_increment, offset });
+ program_counter = offset - 1; // -1 to make sure when incrementing the PC we end up at the correct offset
+ ASSERT(call_stack.size() < call_stack.capacity());
+ call_stack.push_back({ offset + num_instructions, return_offset, repeat_count, loop_increment, offset });
};
- Record<DebugDataRecord::CUR_INSTR>(state.debug, iteration, state.program_counter);
+ Record<DebugDataRecord::CUR_INSTR>(state.debug, iteration, program_counter);
if (iteration > 0)
- Record<DebugDataRecord::NEXT_INSTR>(state.debug, iteration - 1, state.program_counter);
+ Record<DebugDataRecord::NEXT_INSTR>(state.debug, iteration - 1, program_counter);
- state.debug.max_offset = std::max<u32>(state.debug.max_offset, 1 + state.program_counter);
+ state.debug.max_offset = std::max<u32>(state.debug.max_offset, 1 + program_counter);
auto LookupSourceRegister = [&](const SourceRegister& source_reg) -> const float24* {
switch (source_reg.GetRegisterType()) {
@@ -511,7 +535,7 @@ void RunInterpreter(UnitState<Debug>& state) {
case OpCode::Id::JMPC:
Record<DebugDataRecord::COND_CMP_IN>(state.debug, iteration, state.conditional_code);
if (evaluate_condition(state, instr.flow_control.refx, instr.flow_control.refy, instr.flow_control)) {
- state.program_counter = instr.flow_control.dest_offset - 1;
+ program_counter = instr.flow_control.dest_offset - 1;
}
break;
@@ -519,7 +543,7 @@ void RunInterpreter(UnitState<Debug>& state) {
Record<DebugDataRecord::COND_BOOL_IN>(state.debug, iteration, uniforms.b[instr.flow_control.bool_uniform_id]);
if (uniforms.b[instr.flow_control.bool_uniform_id] == !(instr.flow_control.num_instructions & 1)) {
- state.program_counter = instr.flow_control.dest_offset - 1;
+ program_counter = instr.flow_control.dest_offset - 1;
}
break;
@@ -527,7 +551,7 @@ void RunInterpreter(UnitState<Debug>& state) {
call(state,
instr.flow_control.dest_offset,
instr.flow_control.num_instructions,
- state.program_counter + 1, 0, 0);
+ program_counter + 1, 0, 0);
break;
case OpCode::Id::CALLU:
@@ -536,7 +560,7 @@ void RunInterpreter(UnitState<Debug>& state) {
call(state,
instr.flow_control.dest_offset,
instr.flow_control.num_instructions,
- state.program_counter + 1, 0, 0);
+ program_counter + 1, 0, 0);
}
break;
@@ -546,7 +570,7 @@ void RunInterpreter(UnitState<Debug>& state) {
call(state,
instr.flow_control.dest_offset,
instr.flow_control.num_instructions,
- state.program_counter + 1, 0, 0);
+ program_counter + 1, 0, 0);
}
break;
@@ -557,8 +581,8 @@ void RunInterpreter(UnitState<Debug>& state) {
Record<DebugDataRecord::COND_BOOL_IN>(state.debug, iteration, uniforms.b[instr.flow_control.bool_uniform_id]);
if (uniforms.b[instr.flow_control.bool_uniform_id]) {
call(state,
- state.program_counter + 1,
- instr.flow_control.dest_offset - state.program_counter - 1,
+ program_counter + 1,
+ instr.flow_control.dest_offset - program_counter - 1,
instr.flow_control.dest_offset + instr.flow_control.num_instructions, 0, 0);
} else {
call(state,
@@ -576,8 +600,8 @@ void RunInterpreter(UnitState<Debug>& state) {
Record<DebugDataRecord::COND_CMP_IN>(state.debug, iteration, state.conditional_code);
if (evaluate_condition(state, instr.flow_control.refx, instr.flow_control.refy, instr.flow_control)) {
call(state,
- state.program_counter + 1,
- instr.flow_control.dest_offset - state.program_counter - 1,
+ program_counter + 1,
+ instr.flow_control.dest_offset - program_counter - 1,
instr.flow_control.dest_offset + instr.flow_control.num_instructions, 0, 0);
} else {
call(state,
@@ -599,8 +623,8 @@ void RunInterpreter(UnitState<Debug>& state) {
Record<DebugDataRecord::LOOP_INT_IN>(state.debug, iteration, loop_param);
call(state,
- state.program_counter + 1,
- instr.flow_control.dest_offset - state.program_counter + 1,
+ program_counter + 1,
+ instr.flow_control.dest_offset - program_counter + 1,
instr.flow_control.dest_offset + 1,
loop_param.x,
loop_param.z);
@@ -617,14 +641,14 @@ void RunInterpreter(UnitState<Debug>& state) {
}
}
- ++state.program_counter;
+ ++program_counter;
++iteration;
}
}
// Explicit instantiation
-template void RunInterpreter(UnitState<false>& state);
-template void RunInterpreter(UnitState<true>& state);
+template void RunInterpreter(const ShaderSetup& setup, UnitState<false>& state, unsigned offset);
+template void RunInterpreter(const ShaderSetup& setup, UnitState<true>& state, unsigned offset);
} // namespace
diff --git a/src/video_core/shader/shader_interpreter.h b/src/video_core/shader/shader_interpreter.h
index 294bca50e..bb3ce1c6e 100644
--- a/src/video_core/shader/shader_interpreter.h
+++ b/src/video_core/shader/shader_interpreter.h
@@ -4,14 +4,14 @@
#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);
+void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned offset);
} // namespace
diff --git a/src/video_core/shader/shader_jit_x64.cpp b/src/video_core/shader/shader_jit_x64.cpp
index dffe051ef..43e7e6b4c 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
@@ -93,7 +102,7 @@ const JitFunction instr_table[64] = {
// purposes, as documented below:
/// Pointer to the uniform memory
-static const X64Reg UNIFORMS = R9;
+static const X64Reg SETUP = R9;
/// The two 32-bit VS address offset registers set by the MOVA instruction
static const X64Reg ADDROFFS_REG_0 = R10;
static const X64Reg ADDROFFS_REG_1 = R11;
@@ -108,7 +117,7 @@ static const X64Reg COND0 = R13;
/// Result of the previous CMP instruction for the Y-component comparison
static const X64Reg COND1 = R14;
/// Pointer to the UnitState instance for the current VS unit
-static const X64Reg REGISTERS = R15;
+static const X64Reg STATE = R15;
/// SIMD scratch register
static const X64Reg SCRATCH = XMM0;
/// Loaded with the first swizzled source register, otherwise can be used as a scratch register
@@ -127,7 +136,7 @@ static const X64Reg NEGBIT = XMM15;
// State registers that must not be modified by external functions calls
// Scratch registers, e.g., SRC1 and SCRATCH, have to be saved on the side if needed
static const BitSet32 persistent_regs = {
- UNIFORMS, REGISTERS, // Pointers to register blocks
+ SETUP, STATE, // Pointers to register blocks
ADDROFFS_REG_0, ADDROFFS_REG_1, LOOPCOUNT_REG, COND0, COND1, // Cached registers
ONE+16, NEGBIT+16, // Constants
};
@@ -138,21 +147,40 @@ 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;
if (src_reg.GetRegisterType() == RegisterType::FloatUniform) {
- src_ptr = UNIFORMS;
- src_offset = src_reg.GetIndex() * sizeof(float24) * 4;
+ src_ptr = SETUP;
+ src_offset = ShaderSetup::UniformOffset(RegisterType::FloatUniform, src_reg.GetIndex());
} else {
- src_ptr = REGISTERS;
+ src_ptr = STATE;
src_offset = UnitState<false>::InputOffset(src_reg);
}
@@ -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 ||
@@ -236,11 +264,11 @@ void JitCompiler::Compile_DestEnable(Instruction instr,X64Reg src) {
// If all components are enabled, write the result to the destination register
if (swiz.dest_mask == NO_DEST_REG_MASK) {
// Store dest back to memory
- MOVAPS(MDisp(REGISTERS, dest_offset_disp), src);
+ MOVAPS(MDisp(STATE, dest_offset_disp), src);
} else {
// Not all components are enabled, so mask the result when storing to the destination register...
- MOVAPS(SCRATCH, MDisp(REGISTERS, dest_offset_disp));
+ MOVAPS(SCRATCH, MDisp(STATE, dest_offset_disp));
if (Common::GetCPUCaps().sse4_1) {
u8 mask = ((swiz.dest_mask & 1) << 3) | ((swiz.dest_mask & 8) >> 3) | ((swiz.dest_mask & 2) << 1) | ((swiz.dest_mask & 4) >> 1);
@@ -259,11 +287,11 @@ void JitCompiler::Compile_DestEnable(Instruction instr,X64Reg src) {
}
// Store dest back to memory
- MOVAPS(MDisp(REGISTERS, dest_offset_disp), SCRATCH);
+ MOVAPS(MDisp(STATE, dest_offset_disp), SCRATCH);
}
}
-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) {
- 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));
+void JitShader::Compile_UniformCondition(Instruction instr) {
+ int offset = ShaderSetup::UniformOffset(RegisterType::BoolUniform, instr.flow_control.bool_uniform_id);
+ CMP(sizeof(bool) * 8, MDisp(SETUP, 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,15 +708,14 @@ 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;
- int offset = offsetof(decltype(g_state.vs.uniforms), i) + (instr.flow_control.int_uniform_id * sizeof(Math::Vec4<u8>));
- MOV(32, R(LOOPCOUNT), MDisp(UNIFORMS, offset));
+ int offset = ShaderSetup::UniformOffset(RegisterType::IntUniform, instr.flow_control.int_uniform_id);
+ MOV(32, R(LOOPCOUNT), MDisp(SETUP, offset));
MOV(32, R(LOOPCOUNT_REG), R(LOOPCOUNT));
SHR(32, R(LOOPCOUNT_REG), Imm8(8));
AND(32, R(LOOPCOUNT_REG), Imm32(0xff)); // Y-component is the start
@@ -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,15 +791,43 @@ 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);
- MOV(PTRBITS, R(REGISTERS), R(ABI_PARAM1));
- MOV(PTRBITS, R(UNIFORMS), ImmPtr(&g_state.vs.uniforms));
+ MOV(PTRBITS, R(SETUP), R(ABI_PARAM1));
+ MOV(PTRBITS, R(STATE), R(ABI_PARAM2));
// Zero address/loop registers
XOR(64, R(ADDROFFS_REG_0), R(ADDROFFS_REG_0));
@@ -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_PARAM3));
+
+ // 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..5468459d4 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(const ShaderSetup& setup, UnitState<false>& state, unsigned offset) const {
+ program(&setup, &state, 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(const void* setup, void* state, 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..e40f0f1ee
--- /dev/null
+++ b/src/video_core/vertex_loader.cpp
@@ -0,0 +1,146 @@
+#include <memory>
+
+#include <boost/range/algorithm/fill.hpp>
+
+#include "common/alignment.h"
+#include "common/assert.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) {
+ ASSERT_MSG(!is_setup, "VertexLoader is not intended to be setup more than once.");
+
+ const auto& attribute_config = regs.vertex_attributes;
+ num_total_attributes = attribute_config.GetNumTotalAttributes();
+
+ 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
+ }
+ }
+ }
+
+ is_setup = true;
+}
+
+void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, DebugUtils::MemoryAccessTracker& memory_accesses) {
+ ASSERT_MSG(is_setup, "A VertexLoader needs to be setup before loading vertices.");
+
+ for (int i = 0; i < num_total_attributes; ++i) {
+ if (vertex_attribute_elements[i] != 0) {
+ // Load per-vertex data from the loader arrays
+ 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..ac162c254
--- /dev/null
+++ b/src/video_core/vertex_loader.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+#include "video_core/pica.h"
+
+namespace Pica {
+
+namespace DebugUtils {
+class MemoryAccessTracker;
+}
+
+namespace Shader {
+struct InputVertex;
+}
+
+class VertexLoader {
+public:
+ VertexLoader() = default;
+ explicit VertexLoader(const Pica::Regs& regs) {
+ Setup(regs);
+ }
+
+ void Setup(const Pica::Regs& regs);
+ void LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, DebugUtils::MemoryAccessTracker& memory_accesses);
+
+ int GetNumTotalAttributes() const { return num_total_attributes; }
+
+private:
+ std::array<u32, 16> vertex_attribute_sources;
+ std::array<u32, 16> vertex_attribute_strides{};
+ std::array<Regs::VertexAttributeFormat, 16> vertex_attribute_formats;
+ std::array<u32, 16> vertex_attribute_elements{};
+ std::array<bool, 16> vertex_attribute_is_default;
+ int num_total_attributes = 0;
+ bool is_setup = false;
+};
+
+} // namespace Pica
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 256899c89..c9975876d 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -4,12 +4,8 @@
#include <memory>
-#include "common/emu_window.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"
@@ -25,6 +21,7 @@ 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) {
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();