mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-25 13:40:14 +00:00
Merge remote-tracking branch 'origin/master' into soc-getaddrinfo
This commit is contained in:
commit
15e673bc90
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -7,3 +7,6 @@
|
||||
[submodule "nihstro"]
|
||||
path = externals/nihstro
|
||||
url = https://github.com/neobrain/nihstro.git
|
||||
[submodule "soundtouch"]
|
||||
path = externals/soundtouch
|
||||
url = https://github.com/citra-emu/soundtouch.git
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
@ -245,6 +249,9 @@ if(ENABLE_QT)
|
||||
include_directories(externals/qhexedit)
|
||||
add_subdirectory(externals/qhexedit)
|
||||
endif()
|
||||
|
||||
add_subdirectory(externals/soundtouch)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
# Install freedesktop.org metadata files, following those specifications:
|
||||
|
2
externals/boost
vendored
2
externals/boost
vendored
@ -1 +1 @@
|
||||
Subproject commit d81b9269900ae183d0dc98403eea4c971590a807
|
||||
Subproject commit 2dcb9d979665b6aabb1635c617973e02914e60ec
|
7
externals/microprofile/microprofileui.h
vendored
7
externals/microprofile/microprofileui.h
vendored
@ -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;
|
||||
|
1
externals/soundtouch
vendored
Submodule
1
externals/soundtouch
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 5274ec4dec498bd88ccbcd28862a0f78a3b95eff
|
@ -16,6 +16,9 @@ set(HEADERS
|
||||
sink.h
|
||||
)
|
||||
|
||||
include_directories(../../externals/soundtouch/include)
|
||||
|
||||
create_directory_groups(${SRCS} ${HEADERS})
|
||||
|
||||
add_library(audio_core STATIC ${SRCS} ${HEADERS})
|
||||
target_link_libraries(audio_core SoundTouch)
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "audio_core/hle/dsp.h"
|
||||
#include "audio_core/hle/pipe.h"
|
||||
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
@ -17,10 +18,10 @@ 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
|
||||
|
@ -10,8 +10,6 @@ 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
|
||||
|
@ -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.
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include <cstddef>
|
||||
#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"
|
||||
@ -305,7 +305,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);
|
||||
@ -320,7 +320,7 @@ struct SourceStatus {
|
||||
INSERT_PADDING_DSPWORDS(1);
|
||||
};
|
||||
|
||||
Status status[AudioCore::num_sources];
|
||||
Status status[num_sources];
|
||||
};
|
||||
ASSERT_DSP_STRUCT(SourceStatus::Status, 12);
|
||||
|
||||
@ -413,7 +413,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 +427,7 @@ ASSERT_DSP_STRUCT(DspStatus, 32);
|
||||
/// Final mixed output in PCM16 stereo format, what you hear out of the speakers.
|
||||
/// When the application writes to this region it has no effect.
|
||||
struct FinalMixSamples {
|
||||
s16_le pcm16[2 * AudioCore::samples_per_frame];
|
||||
s16_le pcm16[2 * samples_per_frame];
|
||||
};
|
||||
ASSERT_DSP_STRUCT(FinalMixSamples, 640);
|
||||
|
||||
@ -437,7 +437,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;
|
||||
|
@ -12,12 +12,14 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include "core/hle/service/dsp_dsp.h"
|
||||
|
||||
namespace DSP {
|
||||
namespace HLE {
|
||||
|
||||
static DspState dsp_state = DspState::Off;
|
||||
|
||||
static std::array<std::vector<u8>, static_cast<size_t>(DspPipe::DspPipe_MAX)> pipe_data;
|
||||
static std::array<std::vector<u8>, NUM_DSP_PIPE> pipe_data;
|
||||
|
||||
void ResetPipes() {
|
||||
for (auto& data : pipe_data) {
|
||||
@ -27,16 +29,18 @@ void ResetPipes() {
|
||||
}
|
||||
|
||||
std::vector<u8> PipeRead(DspPipe pipe_number, u32 length) {
|
||||
if (pipe_number >= DspPipe::DspPipe_MAX) {
|
||||
LOG_ERROR(Audio_DSP, "pipe_number = %u invalid", pipe_number);
|
||||
const size_t pipe_index = static_cast<size_t>(pipe_number);
|
||||
|
||||
if (pipe_index >= NUM_DSP_PIPE) {
|
||||
LOG_ERROR(Audio_DSP, "pipe_number = %zu invalid", pipe_index);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<u8>& data = pipe_data[static_cast<size_t>(pipe_number)];
|
||||
std::vector<u8>& data = pipe_data[pipe_index];
|
||||
|
||||
if (length > data.size()) {
|
||||
LOG_WARNING(Audio_DSP, "pipe_number = %u is out of data, application requested read of %u but %zu remain",
|
||||
pipe_number, length, data.size());
|
||||
LOG_WARNING(Audio_DSP, "pipe_number = %zu is out of data, application requested read of %u but %zu remain",
|
||||
pipe_index, length, data.size());
|
||||
length = data.size();
|
||||
}
|
||||
|
||||
@ -49,16 +53,20 @@ std::vector<u8> PipeRead(DspPipe pipe_number, u32 length) {
|
||||
}
|
||||
|
||||
size_t GetPipeReadableSize(DspPipe pipe_number) {
|
||||
if (pipe_number >= DspPipe::DspPipe_MAX) {
|
||||
LOG_ERROR(Audio_DSP, "pipe_number = %u invalid", pipe_number);
|
||||
const size_t pipe_index = static_cast<size_t>(pipe_number);
|
||||
|
||||
if (pipe_index >= NUM_DSP_PIPE) {
|
||||
LOG_ERROR(Audio_DSP, "pipe_number = %zu invalid", pipe_index);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return pipe_data[static_cast<size_t>(pipe_number)].size();
|
||||
return pipe_data[pipe_index].size();
|
||||
}
|
||||
|
||||
static void WriteU16(DspPipe pipe_number, u16 value) {
|
||||
std::vector<u8>& data = pipe_data[static_cast<size_t>(pipe_number)];
|
||||
const size_t pipe_index = static_cast<size_t>(pipe_number);
|
||||
|
||||
std::vector<u8>& data = pipe_data.at(pipe_index);
|
||||
// Little endian
|
||||
data.emplace_back(value & 0xFF);
|
||||
data.emplace_back(value >> 8);
|
||||
@ -91,6 +99,8 @@ static void AudioPipeWriteStructAddresses() {
|
||||
for (u16 addr : struct_addresses) {
|
||||
WriteU16(DspPipe::Audio, addr);
|
||||
}
|
||||
// Signal that we have data on this pipe.
|
||||
DSP_DSP::SignalPipeInterrupt(DspPipe::Audio);
|
||||
}
|
||||
|
||||
void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) {
|
||||
@ -145,7 +155,7 @@ void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) {
|
||||
return;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(Audio_DSP, "pipe_number = %u unimplemented", pipe_number);
|
||||
LOG_CRITICAL(Audio_DSP, "pipe_number = %zu unimplemented", static_cast<size_t>(pipe_number));
|
||||
UNIMPLEMENTED();
|
||||
return;
|
||||
}
|
||||
|
@ -19,9 +19,9 @@ enum class DspPipe {
|
||||
Debug = 0,
|
||||
Dma = 1,
|
||||
Audio = 2,
|
||||
Binary = 3,
|
||||
DspPipe_MAX
|
||||
Binary = 3
|
||||
};
|
||||
constexpr size_t NUM_DSP_PIPE = 8;
|
||||
|
||||
/**
|
||||
* Read a DSP pipe.
|
||||
|
@ -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")
|
||||
|
@ -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];
|
||||
|
@ -65,6 +65,7 @@ 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);
|
||||
|
@ -46,6 +46,10 @@ 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 =
|
||||
|
@ -92,7 +92,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")
|
||||
|
@ -71,7 +71,9 @@ void EmuThread::run() {
|
||||
// Shutdown the core emulation
|
||||
System::Shutdown();
|
||||
|
||||
#if MICROPROFILE_ENABLED
|
||||
MicroProfileOnThreadExit();
|
||||
#endif
|
||||
|
||||
render_window->moveContext();
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ 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();
|
||||
@ -129,6 +130,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);
|
||||
|
@ -25,6 +25,7 @@ void ConfigureGeneral::setConfiguration() {
|
||||
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() {
|
||||
@ -33,5 +34,6 @@ void ConfigureGeneral::applyConfiguration() {
|
||||
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();
|
||||
}
|
||||
|
@ -128,6 +128,13 @@
|
||||
</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>
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -9,13 +9,16 @@
|
||||
#include "citra_qt/debugger/profiler.h"
|
||||
#include "citra_qt/util/util.h"
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/profiler_reporting.h"
|
||||
|
||||
// Include the implementation of the UI in this file. This isn't in microprofile.cpp because the
|
||||
// non-Qt frontends don't need it (and don't implement the UI drawing hooks either).
|
||||
#if MICROPROFILE_ENABLED
|
||||
#define MICROPROFILEUI_IMPL 1
|
||||
#include "common/microprofileui.h"
|
||||
#endif
|
||||
|
||||
using namespace Common::Profiling;
|
||||
|
||||
@ -34,21 +37,9 @@ static QVariant GetDataForColumn(int col, const AggregatedDuration& duration)
|
||||
}
|
||||
}
|
||||
|
||||
static const TimingCategoryInfo* GetCategoryInfo(int id)
|
||||
{
|
||||
const auto& categories = GetProfilingManager().GetTimingCategoriesInfo();
|
||||
if ((size_t)id >= categories.size()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return &categories[id];
|
||||
}
|
||||
}
|
||||
|
||||
ProfilerModel::ProfilerModel(QObject* parent) : QAbstractItemModel(parent)
|
||||
{
|
||||
updateProfilingInfo();
|
||||
const auto& categories = GetProfilingManager().GetTimingCategoriesInfo();
|
||||
results.time_per_category.resize(categories.size());
|
||||
}
|
||||
|
||||
QVariant ProfilerModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
@ -85,7 +76,7 @@ int ProfilerModel::rowCount(const QModelIndex& parent) const
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
} else {
|
||||
return static_cast<int>(results.time_per_category.size() + 2);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,17 +95,6 @@ QVariant ProfilerModel::data(const QModelIndex& index, int role) const
|
||||
} else {
|
||||
return GetDataForColumn(index.column(), results.interframe_time);
|
||||
}
|
||||
} else {
|
||||
if (index.column() == 0) {
|
||||
const TimingCategoryInfo* info = GetCategoryInfo(index.row() - 2);
|
||||
return info != nullptr ? QString(info->name) : QVariant();
|
||||
} else {
|
||||
if (index.row() - 2 < (int)results.time_per_category.size()) {
|
||||
return GetDataForColumn(index.column(), results.time_per_category[index.row() - 2]);
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,6 +128,8 @@ void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable)
|
||||
}
|
||||
}
|
||||
|
||||
#if MICROPROFILE_ENABLED
|
||||
|
||||
class MicroProfileWidget : public QWidget {
|
||||
public:
|
||||
MicroProfileWidget(QWidget* parent = nullptr);
|
||||
@ -171,6 +153,8 @@ private:
|
||||
QTimer update_timer;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
MicroProfileDialog::MicroProfileDialog(QWidget* parent)
|
||||
: QWidget(parent, Qt::Dialog)
|
||||
{
|
||||
@ -180,6 +164,8 @@ MicroProfileDialog::MicroProfileDialog(QWidget* parent)
|
||||
// Remove the "?" button from the titlebar and enable the maximize button
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint | Qt::WindowMaximizeButtonHint);
|
||||
|
||||
#if MICROPROFILE_ENABLED
|
||||
|
||||
MicroProfileWidget* widget = new MicroProfileWidget(this);
|
||||
|
||||
QLayout* layout = new QVBoxLayout(this);
|
||||
@ -191,6 +177,7 @@ MicroProfileDialog::MicroProfileDialog(QWidget* parent)
|
||||
setFocusProxy(widget);
|
||||
widget->setFocusPolicy(Qt::StrongFocus);
|
||||
widget->setFocus();
|
||||
#endif
|
||||
}
|
||||
|
||||
QAction* MicroProfileDialog::toggleViewAction() {
|
||||
@ -218,6 +205,9 @@ void MicroProfileDialog::hideEvent(QHideEvent* ev) {
|
||||
QWidget::hideEvent(ev);
|
||||
}
|
||||
|
||||
|
||||
#if MICROPROFILE_ENABLED
|
||||
|
||||
/// There's no way to pass a user pointer to MicroProfile, so this variable is used to make the
|
||||
/// QPainter available inside the drawing callbacks.
|
||||
static QPainter* mp_painter = nullptr;
|
||||
@ -337,3 +327,4 @@ void MicroProfileDrawLine2D(u32 vertices_length, float* vertices, u32 hex_color)
|
||||
mp_painter->drawPolyline(point_buf.data(), vertices_length);
|
||||
point_buf.clear();
|
||||
}
|
||||
#endif
|
||||
|
@ -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
|
||||
|
||||
|
@ -69,8 +69,10 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
|
||||
addDockWidget(Qt::BottomDockWidgetArea, profilerWidget);
|
||||
profilerWidget->hide();
|
||||
|
||||
#if MICROPROFILE_ENABLED
|
||||
microProfileDialog = new MicroProfileDialog(this);
|
||||
microProfileDialog->hide();
|
||||
#endif
|
||||
|
||||
disasmWidget = new DisassemblerWidget(this, emu_thread.get());
|
||||
addDockWidget(Qt::BottomDockWidgetArea, disasmWidget);
|
||||
@ -110,7 +112,9 @@ GMainWindow::GMainWindow() : config(new Config()), 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());
|
||||
@ -136,8 +140,10 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
|
||||
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
|
||||
|
||||
game_list->LoadInterfaceLayout();
|
||||
|
||||
@ -511,9 +517,10 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
|
||||
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;
|
||||
|
@ -47,7 +47,6 @@ set(HEADERS
|
||||
microprofile.h
|
||||
microprofileui.h
|
||||
platform.h
|
||||
profiler.h
|
||||
profiler_reporting.h
|
||||
scm_rev.h
|
||||
scope_exit.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__)
|
@ -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);
|
||||
|
@ -192,7 +192,9 @@ public:
|
||||
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;
|
||||
@ -210,7 +212,9 @@ public:
|
||||
size_t WriteArray(const 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;
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
|
@ -18,6 +18,7 @@
|
||||
/// Detailed description of the error. This listing is likely incomplete.
|
||||
enum class ErrorDescription : u32 {
|
||||
Success = 0,
|
||||
OS_InvalidBufferDescriptor = 48,
|
||||
WrongAddress = 53,
|
||||
FS_NotFound = 120,
|
||||
FS_AlreadyExists = 190,
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
if (IPC::StaticBufferDesc(size, 1) != cmd_buff[3]) {
|
||||
LOG_ERROR(Service_DSP, "IPC static buffer descriptor failed validation (0x%X). pipe=%u, size=0x%X, buffer=0x%08X", cmd_buff[3], pipe_index, size, buffer);
|
||||
cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
|
||||
cmd_buff[1] = ResultCode(ErrorDescription::OS_InvalidBufferDescriptor, ErrorModule::OS, ErrorSummary::WrongArgument, ErrorLevel::Permanent).raw;
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_MSG(Memory::GetPointer(buffer) != nullptr, "Invalid Buffer: pipe=%u, size=0x%X, buffer=0x%08X", pipe_index, size, buffer);
|
||||
|
||||
std::vector<u8> message(size);
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
message[i] = Memory::Read8(buffer + i);
|
||||
}
|
||||
|
||||
DSP::HLE::PipeWrite(pipe, message);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0xD, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||
|
||||
LOG_DEBUG(Service_DSP, "pipe=%u, size=0x%X, buffer=0x%08X", pipe, size, buffer);
|
||||
LOG_DEBUG(Service_DSP, "pipe=%u, size=0x%X, buffer=0x%08X", pipe_index, size, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -243,13 +317,16 @@ static void WriteProcessPipe(Service::Interface* self) {
|
||||
static void ReadPipeIfPossible(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
|
||||
DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(cmd_buff[1]);
|
||||
u32 pipe_index = cmd_buff[1];
|
||||
u32 unknown = cmd_buff[2];
|
||||
u32 size = cmd_buff[3] & 0xFFFF; // Lower 16 bits are size
|
||||
VAddr addr = cmd_buff[0x41];
|
||||
|
||||
ASSERT_MSG(Memory::GetPointer(addr) != nullptr, "Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe, unknown, size, addr);
|
||||
DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index);
|
||||
|
||||
ASSERT_MSG(Memory::GetPointer(addr) != nullptr, "Invalid addr: pipe=%u, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe_index, unknown, size, addr);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x10, 1, 2);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||
if (DSP::HLE::GetPipeReadableSize(pipe) >= size) {
|
||||
std::vector<u8> response = DSP::HLE::PipeRead(pipe, size);
|
||||
@ -260,8 +337,10 @@ static void ReadPipeIfPossible(Service::Interface* self) {
|
||||
} else {
|
||||
cmd_buff[2] = 0; // Return no data
|
||||
}
|
||||
cmd_buff[3] = IPC::StaticBufferDesc(size, 0);
|
||||
cmd_buff[4] = addr;
|
||||
|
||||
LOG_DEBUG(Service_DSP, "pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X, return cmd_buff[2]=0x%08X", pipe, unknown, size, addr, cmd_buff[2]);
|
||||
LOG_DEBUG(Service_DSP, "pipe=%u, unknown=0x%08X, size=0x%X, buffer=0x%08X, return cmd_buff[2]=0x%08X", pipe_index, unknown, size, addr, cmd_buff[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -278,26 +357,31 @@ static void ReadPipeIfPossible(Service::Interface* self) {
|
||||
static void ReadPipe(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
|
||||
DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(cmd_buff[1]);
|
||||
u32 pipe_index = cmd_buff[1];
|
||||
u32 unknown = cmd_buff[2];
|
||||
u32 size = cmd_buff[3] & 0xFFFF; // Lower 16 bits are size
|
||||
VAddr addr = cmd_buff[0x41];
|
||||
|
||||
ASSERT_MSG(Memory::GetPointer(addr) != nullptr, "Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe, unknown, size, addr);
|
||||
DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index);
|
||||
|
||||
ASSERT_MSG(Memory::GetPointer(addr) != nullptr, "Invalid addr: pipe=%u, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe_index, unknown, size, addr);
|
||||
|
||||
if (DSP::HLE::GetPipeReadableSize(pipe) >= size) {
|
||||
std::vector<u8> response = DSP::HLE::PipeRead(pipe, size);
|
||||
|
||||
Memory::WriteBlock(addr, response.data(), response.size());
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0xE, 2, 2);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||
cmd_buff[2] = static_cast<u32>(response.size());
|
||||
cmd_buff[3] = IPC::StaticBufferDesc(size, 0);
|
||||
cmd_buff[4] = addr;
|
||||
} else {
|
||||
// No more data is in pipe. Hardware hangs in this case; this should never happen.
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_DSP, "pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X, return cmd_buff[2]=0x%08X", pipe, unknown, size, addr, cmd_buff[2]);
|
||||
LOG_DEBUG(Service_DSP, "pipe=%u, unknown=0x%08X, size=0x%X, buffer=0x%08X, return cmd_buff[2]=0x%08X", pipe_index, unknown, size, addr, cmd_buff[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -312,13 +396,16 @@ static void ReadPipe(Service::Interface* self) {
|
||||
static void GetPipeReadableSize(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
|
||||
DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(cmd_buff[1]);
|
||||
u32 pipe_index = cmd_buff[1];
|
||||
u32 unknown = cmd_buff[2];
|
||||
|
||||
DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||
cmd_buff[2] = DSP::HLE::GetPipeReadableSize(pipe);
|
||||
|
||||
LOG_DEBUG(Service_DSP, "pipe=0x%08X, unknown=0x%08X, return cmd_buff[2]=0x%08X", pipe, unknown, cmd_buff[2]);
|
||||
LOG_DEBUG(Service_DSP, "pipe=%u, unknown=0x%08X, return cmd_buff[2]=0x%08X", pipe_index, unknown, cmd_buff[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -333,6 +420,7 @@ static void SetSemaphoreMask(Service::Interface* self) {
|
||||
|
||||
u32 mask = cmd_buff[1];
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x17, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||
|
||||
LOG_WARNING(Service_DSP, "(STUBBED) called mask=0x%08X", mask);
|
||||
@ -350,6 +438,7 @@ static void SetSemaphoreMask(Service::Interface* self) {
|
||||
static void GetHeadphoneStatus(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x1F, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||
cmd_buff[2] = 0; // Not using headphones?
|
||||
|
||||
@ -376,6 +465,7 @@ static void RecvData(Service::Interface* self) {
|
||||
|
||||
// Application reads this after requesting DSP shutdown, to verify the DSP has indeed shutdown or slept.
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x1, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
switch (DSP::HLE::GetDspState()) {
|
||||
case DSP::HLE::DspState::On:
|
||||
@ -411,6 +501,7 @@ static void RecvDataIsReady(Service::Interface* self) {
|
||||
|
||||
ASSERT_MSG(register_number == 0, "Unknown register_number %u", register_number);
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x2, 2, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
cmd_buff[2] = 1; // Ready to read
|
||||
|
||||
@ -458,14 +549,14 @@ const Interface::FunctionInfo FunctionTable[] = {
|
||||
|
||||
Interface::Interface() {
|
||||
semaphore_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "DSP_DSP::semaphore_event");
|
||||
read_pipe_count = 0;
|
||||
interrupt_events = {};
|
||||
|
||||
Register(FunctionTable);
|
||||
}
|
||||
|
||||
Interface::~Interface() {
|
||||
semaphore_event = nullptr;
|
||||
interrupt_events.clear();
|
||||
interrupt_events = {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"
|
||||
|
||||
@ -291,8 +289,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
|
||||
@ -408,6 +404,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 +417,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),
|
||||
{
|
||||
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 +518,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;
|
||||
}
|
||||
|
||||
|
@ -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]);
|
||||
LOG_DEBUG(Service_Y2R, "called input_format=%hhu", conversion.input_format);
|
||||
|
||||
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]);
|
||||
LOG_DEBUG(Service_Y2R, "called output_format=%hhu", conversion.output_format);
|
||||
|
||||
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]);
|
||||
LOG_DEBUG(Service_Y2R, "called rotation=%hhu", conversion.rotation);
|
||||
|
||||
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[1] = conversion.SetStandardCoefficient((StandardCoefficient)cmd_buff[1]).raw;
|
||||
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));
|
||||
|
||||
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];
|
||||
LOG_DEBUG(Service_Y2R, "called alpha=%hu", conversion.alpha);
|
||||
|
||||
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 SetDitheringWeightParams(Service::Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
std::memcpy(&dithering_weight_params, &cmd_buff[1], sizeof(DitheringWeightParams));
|
||||
|
||||
cmd_buff[0] = IPC::MakeHeader(0x24, 1, 0);
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
|
||||
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);
|
||||
|
||||
// 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");
|
||||
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"},
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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();
|
||||
|
@ -6,7 +6,6 @@
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/symbols.h"
|
||||
|
||||
@ -1031,8 +1030,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 +1041,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);
|
||||
|
@ -115,6 +115,18 @@ inline void Write(u32 addr, const T data) {
|
||||
u8* start = Memory::GetPhysicalPointer(config.GetStartAddress());
|
||||
u8* end = Memory::GetPhysicalPointer(config.GetEndAddress());
|
||||
|
||||
// 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) {
|
||||
@ -124,12 +136,18 @@ inline void Write(u32 addr, const T data) {
|
||||
}
|
||||
} else if (config.fill_32bit) {
|
||||
// fill with 32-bit values
|
||||
for (u32* ptr = (u32*)start; ptr < (u32*)end; ++ptr)
|
||||
*ptr = config.value_32bit;
|
||||
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
|
||||
for (u16* ptr = (u16*)start; ptr < (u16*)end; ++ptr)
|
||||
*ptr = config.value_16bit;
|
||||
u16 value_16bit = config.value_16bit.Value();
|
||||
for (u8* ptr = start; ptr < end; ptr += sizeof(u16))
|
||||
memcpy(ptr, &value_16bit, sizeof(u16));
|
||||
}
|
||||
}
|
||||
|
||||
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,6 +177,7 @@ inline void Write(u32 addr, const T data) {
|
||||
if (Pica::g_debug_context)
|
||||
Pica::g_debug_context->OnEvent(Pica::DebugContext::Event::IncomingDisplayTransfer, nullptr);
|
||||
|
||||
if (!VideoCore::g_renderer->Rasterizer()->AccelerateDisplayTransfer(config)) {
|
||||
u8* src_pointer = Memory::GetPhysicalPointer(config.GetPhysicalInputAddress());
|
||||
u8* dst_pointer = Memory::GetPhysicalPointer(config.GetPhysicalOutputAddress());
|
||||
|
||||
@ -171,7 +188,10 @@ inline void Write(u32 addr, const T data) {
|
||||
u32 output_gap = config.texture_copy.output_gap * 16;
|
||||
|
||||
size_t contiguous_input_size = config.texture_copy.size / input_width * (input_width + input_gap);
|
||||
VideoCore::g_renderer->Rasterizer()->FlushRegion(config.GetPhysicalInputAddress(), contiguous_input_size);
|
||||
Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), contiguous_input_size);
|
||||
|
||||
size_t contiguous_output_size = config.texture_copy.size / output_width * (output_width + output_gap);
|
||||
Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), contiguous_output_size);
|
||||
|
||||
u32 remaining_size = config.texture_copy.size;
|
||||
u32 remaining_input = input_width;
|
||||
@ -203,9 +223,6 @@ inline void Write(u32 addr, const T data) {
|
||||
config.GetPhysicalOutputAddress(), output_width, output_gap,
|
||||
config.flags);
|
||||
|
||||
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);
|
||||
|
||||
GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF);
|
||||
break;
|
||||
}
|
||||
@ -222,8 +239,8 @@ inline void Write(u32 addr, const T data) {
|
||||
break;
|
||||
}
|
||||
|
||||
bool horizontal_scale = config.scaling != config.NoScale;
|
||||
bool vertical_scale = config.scaling == config.ScaleXY;
|
||||
int horizontal_scale = config.scaling != config.NoScale ? 1 : 0;
|
||||
int vertical_scale = config.scaling == config.ScaleXY ? 1 : 0;
|
||||
|
||||
u32 output_width = config.output_width >> horizontal_scale;
|
||||
u32 output_height = config.output_height >> vertical_scale;
|
||||
@ -231,7 +248,8 @@ inline void Write(u32 addr, const T data) {
|
||||
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);
|
||||
|
||||
VideoCore::g_renderer->Rasterizer()->FlushRegion(config.GetPhysicalInputAddress(), input_size);
|
||||
Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), input_size);
|
||||
Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), output_size);
|
||||
|
||||
for (u32 y = 0; y < output_height; ++y) {
|
||||
for (u32 x = 0; x < output_width; ++x) {
|
||||
@ -334,11 +352,10 @@ inline void Write(u32 addr, const T data) {
|
||||
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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -255,7 +255,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);
|
||||
|
@ -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) {
|
||||
@ -106,6 +127,28 @@ void UnmapRegion(VAddr base, u32 size) {
|
||||
MapPages(base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
*/
|
||||
@ -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);
|
||||
}
|
||||
|
@ -148,4 +148,20 @@ VAddr PhysicalToVirtualAddress(PAddr addr);
|
||||
*/
|
||||
u8* GetPhysicalPointer(PAddr address);
|
||||
|
||||
/**
|
||||
* Adds the supplied value to the rasterizer resource cache counter of each
|
||||
* page touching the region.
|
||||
*/
|
||||
void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta);
|
||||
|
||||
/**
|
||||
* Flushes any externally cached rasterizer resources touching the given region.
|
||||
*/
|
||||
void RasterizerFlushRegion(PAddr start, u32 size);
|
||||
|
||||
/**
|
||||
* Flushes and invalidates any externally cached rasterizer resources touching the given region.
|
||||
*/
|
||||
void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size);
|
||||
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ void Apply() {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -55,6 +55,7 @@ struct Values {
|
||||
// Renderer
|
||||
bool use_hw_renderer;
|
||||
bool use_shader_jit;
|
||||
bool use_scaled_resolution;
|
||||
|
||||
float bg_red;
|
||||
float bg_green;
|
||||
|
@ -7,7 +7,6 @@
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/profiler.h"
|
||||
|
||||
#include "core/settings.h"
|
||||
#include "core/hle/service/gsp_gpu.h"
|
||||
@ -35,8 +34,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,
|
||||
@ -186,7 +183,6 @@ 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
|
||||
|
@ -40,15 +40,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.
|
||||
|
||||
|
@ -114,7 +114,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 +134,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;
|
||||
|
||||
|
@ -577,7 +577,7 @@ struct Regs {
|
||||
}
|
||||
}
|
||||
|
||||
struct {
|
||||
struct FramebufferConfig {
|
||||
INSERT_PADDING_WORDS(0x3);
|
||||
|
||||
union {
|
||||
@ -747,8 +747,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 {
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/profiler.h"
|
||||
|
||||
#include "core/memory.h"
|
||||
#include "core/hw/gpu.h"
|
||||
@ -287,7 +286,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 +298,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
|
||||
|
@ -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; }
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,5 @@ void RendererBase::RefreshRasterizerSetting() {
|
||||
} else {
|
||||
rasterizer = std::make_unique<VideoCore::SWRasterizer>();
|
||||
}
|
||||
rasterizer->InitObjects();
|
||||
rasterizer->Reset();
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,6 @@
|
||||
#include "common/file_util.h"
|
||||
#include "common/math_util.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/profiler.h"
|
||||
|
||||
#include "core/memory.h"
|
||||
#include "core/settings.h"
|
||||
@ -36,10 +35,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 +57,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);
|
||||
@ -81,70 +81,24 @@ void RasterizerOpenGL::InitObjects() {
|
||||
glVertexAttribPointer(GLShader::ATTRIBUTE_VIEW, 3, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, view));
|
||||
glEnableVertexAttribArray(GLShader::ATTRIBUTE_VIEW);
|
||||
|
||||
SetShader();
|
||||
|
||||
// Create textures for OGL framebuffer that will be rendered to, initially 1x1 to succeed in framebuffer creation
|
||||
fb_color_texture.texture.Create();
|
||||
ReconfigureColorTexture(fb_color_texture, Pica::Regs::ColorFormat::RGBA8, 1, 1);
|
||||
|
||||
state.texture_units[0].texture_2d = fb_color_texture.texture.handle;
|
||||
state.Apply();
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.Apply();
|
||||
|
||||
fb_depth_texture.texture.Create();
|
||||
ReconfigureDepthTexture(fb_depth_texture, Pica::Regs::DepthFormat::D16, 1, 1);
|
||||
|
||||
state.texture_units[0].texture_2d = fb_depth_texture.texture.handle;
|
||||
state.Apply();
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
||||
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.Apply();
|
||||
|
||||
// Configure OpenGL framebuffer
|
||||
// Create render framebuffer
|
||||
framebuffer.Create();
|
||||
|
||||
state.draw.framebuffer = framebuffer.handle;
|
||||
// Allocate and bind lighting lut textures
|
||||
for (size_t i = 0; i < lighting_luts.size(); ++i) {
|
||||
lighting_luts[i].Create();
|
||||
state.lighting_luts[i].texture_1d = lighting_luts[i].handle;
|
||||
}
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb_color_texture.texture.handle, 0);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, fb_depth_texture.texture.handle, 0);
|
||||
|
||||
for (size_t i = 0; i < lighting_lut.size(); ++i) {
|
||||
lighting_lut[i].Create();
|
||||
state.lighting_lut[i].texture_1d = lighting_lut[i].handle;
|
||||
|
||||
for (size_t i = 0; i < lighting_luts.size(); ++i) {
|
||||
glActiveTexture(GL_TEXTURE3 + i);
|
||||
glBindTexture(GL_TEXTURE_1D, state.lighting_lut[i].texture_1d);
|
||||
|
||||
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
}
|
||||
state.Apply();
|
||||
|
||||
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();
|
||||
@ -156,10 +110,10 @@ void RasterizerOpenGL::Reset() {
|
||||
SyncColorWriteMask();
|
||||
SyncStencilWriteMask();
|
||||
SyncDepthWriteMask();
|
||||
}
|
||||
|
||||
SetShader();
|
||||
RasterizerOpenGL::~RasterizerOpenGL() {
|
||||
|
||||
res_cache.InvalidateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,47 +150,98 @@ void RasterizerOpenGL::DrawTriangles() {
|
||||
if (vertex_batch.empty())
|
||||
return;
|
||||
|
||||
SyncFramebuffer();
|
||||
SyncDrawState();
|
||||
const auto& regs = Pica::g_state.regs;
|
||||
|
||||
if (state.draw.shader_dirty) {
|
||||
SetShader();
|
||||
state.draw.shader_dirty = false;
|
||||
// Sync and bind the framebuffer surfaces
|
||||
CachedSurface* color_surface;
|
||||
CachedSurface* depth_surface;
|
||||
MathUtil::Rectangle<int> rect;
|
||||
std::tie(color_surface, depth_surface, rect) = res_cache.GetFramebufferSurfaces(regs.framebuffer);
|
||||
|
||||
state.draw.draw_framebuffer = framebuffer.handle;
|
||||
state.Apply();
|
||||
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_surface != nullptr ? color_surface->texture.handle : 0, 0);
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_surface != nullptr ? depth_surface->texture.handle : 0, 0);
|
||||
bool has_stencil = regs.framebuffer.depth_format == Pica::Regs::DepthFormat::D24S8;
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, (has_stencil && depth_surface != nullptr) ? depth_surface->texture.handle : 0, 0);
|
||||
|
||||
if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (unsigned index = 0; index < lighting_lut.size(); index++) {
|
||||
// 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();
|
||||
shader_dirty = false;
|
||||
}
|
||||
|
||||
// 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());
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
res_cache.InvalidateInRange(cached_fb_color_addr, cached_fb_color_size, true);
|
||||
res_cache.InvalidateInRange(cached_fb_depth_addr, cached_fb_depth_size, true);
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -268,7 +273,7 @@ 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;
|
||||
|
||||
// Sync GL stencil test + stencil write mask
|
||||
@ -334,7 +339,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);
|
||||
@ -521,41 +526,257 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::FlushRegion(PAddr addr, u32 size) {
|
||||
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;
|
||||
|
||||
// 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();
|
||||
|
||||
if (MathUtil::IntervalsIntersect(addr, size, cached_fb_depth_addr, cached_fb_depth_size))
|
||||
CommitDepthBuffer();
|
||||
void RasterizerOpenGL::FlushAll() {
|
||||
res_cache.FlushAll();
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::InvalidateRegion(PAddr addr, u32 size) {
|
||||
const auto& regs = Pica::g_state.regs;
|
||||
void RasterizerOpenGL::FlushRegion(PAddr addr, u32 size) {
|
||||
res_cache.FlushRegion(addr, size, nullptr, false);
|
||||
}
|
||||
|
||||
u32 cached_fb_color_size = Pica::Regs::BytesPerColorPixel(fb_color_texture.format)
|
||||
* fb_color_texture.width * fb_color_texture.height;
|
||||
void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) {
|
||||
res_cache.FlushRegion(addr, size, nullptr, true);
|
||||
}
|
||||
|
||||
u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format)
|
||||
* fb_depth_texture.width * fb_depth_texture.height;
|
||||
bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) {
|
||||
using PixelFormat = CachedSurface::PixelFormat;
|
||||
using SurfaceType = CachedSurface::SurfaceType;
|
||||
|
||||
// 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();
|
||||
if (config.is_texture_copy) {
|
||||
// TODO(tfarley): Try to hardware accelerate this
|
||||
return false;
|
||||
}
|
||||
|
||||
if (MathUtil::IntervalsIntersect(addr, size, cached_fb_depth_addr, cached_fb_depth_size))
|
||||
ReloadDepthBuffer();
|
||||
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);
|
||||
|
||||
// Notify cache of flush in case the region touches a cached resource
|
||||
res_cache.InvalidateInRange(addr, size);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GLfloat value_float = (config.value_32bit & 0xFFFFFF) / 16777215.0f; // 2^24 - 1
|
||||
GLint value_int = (config.value_32bit >> 24);
|
||||
|
||||
cur_state.depth.write_mask = true;
|
||||
cur_state.stencil.write_mask = true;
|
||||
cur_state.Apply();
|
||||
glClearBufferfi(GL_DEPTH_STENCIL, 0, value_float, value_int);
|
||||
}
|
||||
|
||||
cur_state.draw.draw_framebuffer = old_fb;
|
||||
// TODO: Return scissor test to previous value when scissor test is implemented
|
||||
cur_state.Apply();
|
||||
|
||||
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() {
|
||||
@ -597,108 +818,6 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Pica::Regs::TextureConf
|
||||
}
|
||||
}
|
||||
|
||||
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>();
|
||||
@ -754,6 +873,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);
|
||||
@ -761,83 +882,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;
|
||||
|
||||
@ -1034,229 +1078,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/pica_to_gl.h"
|
||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
#include "video_core/shader/shader_interpreter.h"
|
||||
|
||||
/**
|
||||
@ -191,16 +192,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 +212,6 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
/// Structure used for storing information about color textures
|
||||
struct TextureInfo {
|
||||
OGLTexture texture;
|
||||
GLsizei width;
|
||||
GLsizei height;
|
||||
Pica::Regs::ColorFormat format;
|
||||
GLenum gl_format;
|
||||
GLenum gl_type;
|
||||
};
|
||||
|
||||
/// Structure used for storing information about depth textures
|
||||
struct DepthTextureInfo {
|
||||
OGLTexture texture;
|
||||
GLsizei width;
|
||||
GLsizei height;
|
||||
Pica::Regs::DepthFormat format;
|
||||
GLenum gl_format;
|
||||
GLenum gl_type;
|
||||
};
|
||||
|
||||
struct SamplerInfo {
|
||||
using TextureConfig = Pica::Regs::TextureConfig;
|
||||
|
||||
@ -311,18 +293,9 @@ private:
|
||||
static_assert(sizeof(UniformData) == 0x310, "The size of the UniformData structure has changed, update the structure in the shader");
|
||||
static_assert(sizeof(UniformData) < 16384, "UniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
/// Reconfigure the OpenGL color texture to use the given format and dimensions
|
||||
void ReconfigureColorTexture(TextureInfo& texture, Pica::Regs::ColorFormat format, u32 width, u32 height);
|
||||
|
||||
/// Reconfigure the OpenGL depth texture to use the given format and dimensions
|
||||
void ReconfigureDepthTexture(DepthTextureInfo& texture, Pica::Regs::DepthFormat format, u32 width, u32 height);
|
||||
|
||||
/// Sets the OpenGL shader in accordance with the current PICA register state
|
||||
void SetShader();
|
||||
|
||||
/// Syncs the state and contents of the OpenGL framebuffer to match the current PICA framebuffer
|
||||
void SyncFramebuffer();
|
||||
|
||||
/// Syncs the cull mode to match the PICA register
|
||||
void SyncCullMode();
|
||||
|
||||
@ -359,18 +332,24 @@ private:
|
||||
/// 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 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 specified light's diffuse color to match the PICA register
|
||||
void SyncLightDiffuse(int light_index);
|
||||
|
||||
@ -380,51 +359,15 @@ private:
|
||||
/// 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();
|
||||
|
||||
/// Copies the 3DS depth framebuffer into the OpenGL depth framebuffer texture
|
||||
void ReloadDepthBuffer();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
@ -432,11 +375,12 @@ private:
|
||||
bool dirty;
|
||||
} uniform_block_data;
|
||||
|
||||
std::array<SamplerInfo, 3> texture_samplers;
|
||||
OGLVertexArray vertex_array;
|
||||
OGLBuffer vertex_buffer;
|
||||
OGLBuffer uniform_buffer;
|
||||
OGLFramebuffer framebuffer;
|
||||
|
||||
std::array<OGLTexture, 6> lighting_lut;
|
||||
std::array<OGLTexture, 6> lighting_luts;
|
||||
std::array<std::array<GLvec4, 256>, 6> lighting_lut_data;
|
||||
};
|
||||
|
@ -2,8 +2,9 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "common/emu_window.h"
|
||||
#include "common/hash.h"
|
||||
#include "common/math_util.h"
|
||||
#include "common/microprofile.h"
|
||||
@ -12,71 +13,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/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();
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_TextureUpload, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192));
|
||||
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;
|
||||
|
||||
void RasterizerCacheOpenGL::LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::DebugUtils::TextureInfo& info) {
|
||||
const auto cached_texture = texture_cache.find(info.physical_address);
|
||||
u8* data_ptrs[2];
|
||||
u32 depth_stencil_shifts[2] = {24, 8};
|
||||
|
||||
if (cached_texture != texture_cache.end()) {
|
||||
state.texture_units[texture_unit].texture_2d = cached_texture->second->texture.handle;
|
||||
state.Apply();
|
||||
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 {
|
||||
MICROPROFILE_SCOPE(OpenGL_TextureUpload);
|
||||
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;
|
||||
|
||||
std::unique_ptr<CachedTexture> new_texture = std::make_unique<CachedTexture>();
|
||||
data_ptrs[morton_to_gl] = morton_data + morton_offset;
|
||||
data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index];
|
||||
|
||||
new_texture->texture.Create();
|
||||
state.texture_units[texture_unit].texture_2d = new_texture->texture.handle;
|
||||
state.Apply();
|
||||
glActiveTexture(GL_TEXTURE0 + texture_unit);
|
||||
|
||||
u8* texture_src_data = Memory::GetPhysicalPointer(info.physical_address);
|
||||
|
||||
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);
|
||||
|
||||
std::unique_ptr<Math::Vec4<u8>[]> temp_texture_buffer_rgba(new Math::Vec4<u8>[info.width * info.height]);
|
||||
|
||||
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);
|
||||
memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, info.width, info.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, temp_texture_buffer_rgba.get());
|
||||
|
||||
texture_cache.emplace(info.physical_address, std::move(new_texture));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
for (auto it = texture_cache.begin(); it != cache_upper_bound;) {
|
||||
const auto& info = *it->second;
|
||||
OpenGLState cur_state = OpenGLState::GetCurState();
|
||||
|
||||
// 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))) {
|
||||
// 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);
|
||||
|
||||
it = texture_cache.erase(it);
|
||||
// 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 {
|
||||
++it;
|
||||
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_SurfaceUpload, "OpenGL", "Surface Upload", MP_RGB(128, 64, 192));
|
||||
CachedSurface* RasterizerCacheOpenGL::GetSurface(const CachedSurface& params, bool match_res_scale, bool load_if_create) {
|
||||
using PixelFormat = CachedSurface::PixelFormat;
|
||||
using SurfaceType = CachedSurface::SurfaceType;
|
||||
|
||||
if (params.addr == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u32 params_size = params.width * params.height * CachedSurface::GetFormatBpp(params.pixel_format) / 8;
|
||||
|
||||
// Check for an exact match in existing surfaces
|
||||
CachedSurface* best_exact_surface = nullptr;
|
||||
float exact_surface_goodness = -1.f;
|
||||
|
||||
auto surface_interval = boost::icl::interval<PAddr>::right_open(params.addr, params.addr + params_size);
|
||||
auto range = surface_cache.equal_range(surface_interval);
|
||||
for (auto it = range.first; it != range.second; ++it) {
|
||||
for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) {
|
||||
CachedSurface* surface = it2->get();
|
||||
|
||||
// Check if the request matches the surface exactly
|
||||
if (params.addr == surface->addr &&
|
||||
params.width == surface->width && params.height == surface->height &&
|
||||
params.pixel_format == surface->pixel_format)
|
||||
{
|
||||
// Make sure optional param-matching criteria are fulfilled
|
||||
bool tiling_match = (params.is_tiled == surface->is_tiled);
|
||||
bool res_scale_match = (params.res_scale_width == surface->res_scale_width && params.res_scale_height == surface->res_scale_height);
|
||||
if (!match_res_scale || res_scale_match) {
|
||||
// Prioritize same-tiling and highest resolution surfaces
|
||||
float match_goodness = (float)tiling_match + surface->res_scale_width * surface->res_scale_height;
|
||||
if (match_goodness > exact_surface_goodness || surface->dirty) {
|
||||
exact_surface_goodness = match_goodness;
|
||||
best_exact_surface = surface;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the best exact surface if found
|
||||
if (best_exact_surface != nullptr) {
|
||||
return best_exact_surface;
|
||||
}
|
||||
|
||||
// No matching surfaces found, so create a new one
|
||||
u8* texture_src_data = Memory::GetPhysicalPointer(params.addr);
|
||||
if (texture_src_data == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_SurfaceUpload);
|
||||
|
||||
std::shared_ptr<CachedSurface> new_surface = std::make_shared<CachedSurface>();
|
||||
|
||||
new_surface->addr = params.addr;
|
||||
new_surface->size = params_size;
|
||||
|
||||
new_surface->texture.Create();
|
||||
new_surface->width = params.width;
|
||||
new_surface->height = params.height;
|
||||
new_surface->stride = params.stride;
|
||||
new_surface->res_scale_width = params.res_scale_width;
|
||||
new_surface->res_scale_height = params.res_scale_height;
|
||||
|
||||
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 {
|
||||
// TODO: Consider attempting subrect match in existing surfaces and direct blit here instead of memory upload below if that's a common scenario in some game
|
||||
|
||||
Memory::RasterizerFlushRegion(params.addr, params_size);
|
||||
|
||||
// Load data from memory to the new surface
|
||||
OpenGLState cur_state = OpenGLState::GetCurState();
|
||||
|
||||
GLuint old_tex = cur_state.texture_units[0].texture_2d;
|
||||
cur_state.texture_units[0].texture_2d = new_surface->texture.handle;
|
||||
cur_state.Apply();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)new_surface->stride);
|
||||
if (!new_surface->is_tiled) {
|
||||
// TODO: Ensure this will always be a color format, not a depth or other format
|
||||
ASSERT((size_t)new_surface->pixel_format < fb_format_tuples.size());
|
||||
const FormatTuple& tuple = fb_format_tuples[(unsigned int)params.pixel_format];
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, params.width, params.height, 0,
|
||||
tuple.format, tuple.type, texture_src_data);
|
||||
} else {
|
||||
SurfaceType type = CachedSurface::GetFormatType(new_surface->pixel_format);
|
||||
if (type != SurfaceType::Depth && type != SurfaceType::DepthStencil) {
|
||||
FormatTuple tuple;
|
||||
if ((size_t)params.pixel_format < fb_format_tuples.size()) {
|
||||
tuple = fb_format_tuples[(unsigned int)params.pixel_format];
|
||||
} else {
|
||||
// Texture
|
||||
tuple = { GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE };
|
||||
}
|
||||
|
||||
std::vector<Math::Vec4<u8>> tex_buffer(params.width * params.height);
|
||||
|
||||
Pica::DebugUtils::TextureInfo tex_info;
|
||||
tex_info.width = params.width;
|
||||
tex_info.height = params.height;
|
||||
tex_info.stride = params.width * CachedSurface::GetFormatBpp(params.pixel_format) / 8;
|
||||
tex_info.format = (Pica::Regs::TextureFormat)params.pixel_format;
|
||||
tex_info.physical_address = params.addr;
|
||||
|
||||
for (unsigned y = 0; y < params.height; ++y) {
|
||||
for (unsigned x = 0; x < params.width; ++x) {
|
||||
tex_buffer[x + params.width * y] = Pica::DebugUtils::LookupTexture(texture_src_data, x, params.height - 1 - y, tex_info);
|
||||
}
|
||||
}
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, params.width, params.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_buffer.data());
|
||||
} else {
|
||||
// Depth/Stencil formats need special treatment since they aren't sampleable using LookupTexture and can't use RGBA format
|
||||
size_t tuple_idx = (size_t)params.pixel_format - 14;
|
||||
ASSERT(tuple_idx < depth_format_tuples.size());
|
||||
const FormatTuple& tuple = depth_format_tuples[tuple_idx];
|
||||
|
||||
u32 bytes_per_pixel = CachedSurface::GetFormatBpp(params.pixel_format) / 8;
|
||||
|
||||
// OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type
|
||||
bool use_4bpp = (params.pixel_format == PixelFormat::D24);
|
||||
|
||||
u32 gl_bytes_per_pixel = use_4bpp ? 4 : bytes_per_pixel;
|
||||
|
||||
std::vector<u8> temp_fb_depth_buffer(params.width * params.height * gl_bytes_per_pixel);
|
||||
|
||||
u8* temp_fb_depth_buffer_ptr = use_4bpp ? temp_fb_depth_buffer.data() + 1 : temp_fb_depth_buffer.data();
|
||||
|
||||
MortonCopyPixels(params.pixel_format, params.width, params.height, bytes_per_pixel, gl_bytes_per_pixel, texture_src_data, temp_fb_depth_buffer_ptr, true);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, params.width, params.height, 0,
|
||||
tuple.format, tuple.type, temp_fb_depth_buffer.data());
|
||||
}
|
||||
}
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
// If not 1x scale, blit 1x texture to a new scaled texture and replace texture in surface
|
||||
if (new_surface->res_scale_width != 1.f || new_surface->res_scale_height != 1.f) {
|
||||
OGLTexture scaled_texture;
|
||||
scaled_texture.Create();
|
||||
|
||||
AllocateSurfaceTexture(scaled_texture.handle, new_surface->pixel_format, new_surface->GetScaledWidth(), new_surface->GetScaledHeight());
|
||||
BlitTextures(new_surface->texture.handle, scaled_texture.handle, CachedSurface::GetFormatType(new_surface->pixel_format),
|
||||
MathUtil::Rectangle<int>(0, 0, new_surface->width, new_surface->height),
|
||||
MathUtil::Rectangle<int>(0, 0, new_surface->GetScaledWidth(), new_surface->GetScaledHeight()));
|
||||
|
||||
new_surface->texture.Release();
|
||||
new_surface->texture.handle = scaled_texture.handle;
|
||||
scaled_texture.handle = 0;
|
||||
cur_state.texture_units[0].texture_2d = new_surface->texture.handle;
|
||||
cur_state.Apply();
|
||||
}
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// Attempt to find encompassing surfaces
|
||||
CachedSurface* best_subrect_surface = nullptr;
|
||||
float subrect_surface_goodness = -1.f;
|
||||
|
||||
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();
|
||||
|
||||
// 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 {
|
||||
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);
|
||||
}
|
||||
|
||||
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::InvalidateAll() {
|
||||
texture_cache.clear();
|
||||
void RasterizerCacheOpenGL::FlushAll() {
|
||||
for (auto& surfaces : surface_cache) {
|
||||
for (auto& surface : surfaces.second) {
|
||||
FlushSurface(surface.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,38 +6,211 @@
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
|
||||
#include "common/math_util.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"
|
||||
|
||||
class RasterizerCacheOpenGL : NonCopyable {
|
||||
public:
|
||||
~RasterizerCacheOpenGL();
|
||||
struct CachedSurface;
|
||||
|
||||
/// 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);
|
||||
using SurfaceCache = boost::icl::interval_map<PAddr, std::set<std::shared_ptr<CachedSurface>>>;
|
||||
|
||||
void LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::Regs::FullTextureConfig& config) {
|
||||
LoadAndBindTexture(state, texture_unit, Pica::DebugUtils::TextureInfo::FromPicaRegister(config.config, config.format));
|
||||
}
|
||||
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,
|
||||
|
||||
/// Invalidate any cached resource intersecting the specified region.
|
||||
void InvalidateInRange(PAddr addr, u32 size, bool ignore_hash = false);
|
||||
// Texture-only formats
|
||||
IA8 = 5,
|
||||
RG8 = 6,
|
||||
I8 = 7,
|
||||
A8 = 8,
|
||||
IA4 = 9,
|
||||
I4 = 10,
|
||||
A4 = 11,
|
||||
ETC1 = 12,
|
||||
ETC1A4 = 13,
|
||||
|
||||
/// Invalidate all cached OpenGL resources tracked by this cache manager
|
||||
void InvalidateAll();
|
||||
// Depth buffer-only formats
|
||||
D16 = 14,
|
||||
// gap
|
||||
D24 = 16,
|
||||
D24S8 = 17,
|
||||
|
||||
private:
|
||||
struct CachedTexture {
|
||||
OGLTexture texture;
|
||||
GLuint width;
|
||||
GLuint height;
|
||||
u32 size;
|
||||
u64 hash;
|
||||
PAddr addr;
|
||||
Invalid = 255,
|
||||
};
|
||||
|
||||
std::map<PAddr, std::unique_ptr<CachedTexture>> texture_cache;
|
||||
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)
|
||||
CachedSurface* GetSurface(const CachedSurface& params, bool match_res_scale, bool load_if_create);
|
||||
|
||||
/// 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);
|
||||
|
||||
/// Gets a surface based on the texture configuration
|
||||
CachedSurface* GetTextureSurface(const Pica::Regs::FullTextureConfig& config);
|
||||
|
||||
/// 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);
|
||||
|
||||
/// 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);
|
||||
|
||||
/// 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];
|
||||
};
|
||||
|
@ -198,6 +198,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);
|
||||
|
@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "video_core/pica.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
|
||||
OpenGLState OpenGLState::cur_state;
|
||||
@ -48,17 +49,19 @@ OpenGLState::OpenGLState() {
|
||||
texture_unit.sampler = 0;
|
||||
}
|
||||
|
||||
for (auto& lut : lighting_lut) {
|
||||
for (auto& lut : lighting_luts) {
|
||||
lut.texture_1d = 0;
|
||||
}
|
||||
|
||||
draw.framebuffer = 0;
|
||||
draw.read_framebuffer = 0;
|
||||
draw.draw_framebuffer = 0;
|
||||
draw.vertex_array = 0;
|
||||
draw.vertex_buffer = 0;
|
||||
draw.uniform_buffer = 0;
|
||||
draw.shader_program = 0;
|
||||
}
|
||||
|
||||
void OpenGLState::Apply() {
|
||||
void OpenGLState::Apply() const {
|
||||
// Culling
|
||||
if (cull.enabled != cur_state.cull.enabled) {
|
||||
if (cull.enabled) {
|
||||
@ -175,16 +178,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 +216,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;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include <memory>
|
||||
|
||||
class OpenGLState {
|
||||
public:
|
||||
@ -63,15 +64,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 +83,18 @@ public:
|
||||
}
|
||||
|
||||
/// Apply this state as the current OpenGL state
|
||||
void Apply();
|
||||
void Apply() const;
|
||||
|
||||
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);
|
||||
/// 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;
|
||||
|
@ -107,7 +107,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 +117,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 +166,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 +177,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,7 +187,16 @@ 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;
|
||||
if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, pixel_stride, screen_info)) {
|
||||
// Reset the screen info's display texture to its own permanent texture
|
||||
screen_info.display_texture = screen_info.texture.resource.handle;
|
||||
screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f);
|
||||
|
||||
Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height);
|
||||
|
||||
const u8* framebuffer_data = Memory::GetPhysicalPointer(framebuffer_addr);
|
||||
|
||||
state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
@ -201,12 +208,13 @@ void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig&
|
||||
// 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);
|
||||
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 +224,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 +232,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 +244,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 +271,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 +286,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 +342,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 +385,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 +394,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++;
|
||||
|
@ -11,10 +11,28 @@
|
||||
#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 +55,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 +77,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;
|
||||
|
@ -9,7 +9,6 @@
|
||||
|
||||
#include "common/hash.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/profiler.h"
|
||||
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/pica.h"
|
||||
@ -57,13 +56,11 @@ void Shutdown() {
|
||||
#endif // ARCHITECTURE_x86_64
|
||||
}
|
||||
|
||||
static Common::Profiling::TimingCategory shader_category("Vertex Shader");
|
||||
MICROPROFILE_DEFINE(GPU_VertexShader, "GPU", "Vertex Shader", MP_RGB(50, 50, 240));
|
||||
|
||||
OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attributes) {
|
||||
auto& config = g_state.regs.vs;
|
||||
|
||||
Common::Profiling::ScopeTimer timer(shader_category);
|
||||
MICROPROFILE_SCOPE(GPU_VertexShader);
|
||||
|
||||
state.program_counter = config.main_offset;
|
||||
|
@ -148,7 +148,7 @@ static Instruction GetVertexShaderInstruction(size_t offset) {
|
||||
}
|
||||
|
||||
static void LogCritical(const char* msg) {
|
||||
LOG_CRITICAL(HW_GPU, msg);
|
||||
LOG_CRITICAL(HW_GPU, "%s", msg);
|
||||
}
|
||||
|
||||
void JitShader::Compile_Assert(bool condition, const char* msg) {
|
||||
@ -795,6 +795,8 @@ void JitShader::FindReturnOffsets() {
|
||||
case OpCode::Id::CALLU:
|
||||
return_offsets.push_back(instr.flow_control.dest_offset + instr.flow_control.num_instructions);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -854,7 +856,7 @@ void JitShader::Compile() {
|
||||
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!");
|
||||
|
||||
LOG_DEBUG(HW_GPU, "Compiled shader size=%d", size);
|
||||
LOG_DEBUG(HW_GPU, "Compiled shader size=%lu", size);
|
||||
}
|
||||
|
||||
JitShader::JitShader() {
|
||||
|
@ -11,16 +11,14 @@
|
||||
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 {}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,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) {
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user