Compare commits

..

1 Commits

Author SHA1 Message Date
5a38c5f190 cheats: Use global cheat engine 2023-07-31 23:39:21 +03:00
67 changed files with 505 additions and 1387 deletions

View File

@ -11,7 +11,7 @@ async function checkBaseChanges(github, context) {
repository(name:$name, owner:$owner) { repository(name:$name, owner:$owner) {
ref(qualifiedName:$ref) { ref(qualifiedName:$ref) {
target { target {
... on Commit { id committedDate oid } ... on Commit { id pushedDate oid }
} }
} }
} }
@ -22,9 +22,9 @@ async function checkBaseChanges(github, context) {
ref: 'refs/heads/master', ref: 'refs/heads/master',
}; };
const result = await github.graphql(query, variables); const result = await github.graphql(query, variables);
const committedAt = result.repository.ref.target.committedDate; const pushedAt = result.repository.ref.target.pushedDate;
console.log(`Last commit committed at ${committedAt}.`); console.log(`Last commit pushed at ${pushedAt}.`);
const delta = new Date() - new Date(committedAt); const delta = new Date() - new Date(pushedAt);
if (delta <= DETECTION_TIME_FRAME) { if (delta <= DETECTION_TIME_FRAME) {
console.info('New changes detected, triggering a new build.'); console.info('New changes detected, triggering a new build.');
return true; return true;

View File

@ -13,8 +13,8 @@ public class EmulationMenuSettings {
public static final int LayoutOption_SingleScreen = 1; public static final int LayoutOption_SingleScreen = 1;
public static final int LayoutOption_LargeScreen = 2; public static final int LayoutOption_LargeScreen = 2;
public static final int LayoutOption_SideScreen = 3; public static final int LayoutOption_SideScreen = 3;
public static final int LayoutOption_MobilePortrait = 5; public static final int LayoutOption_MobilePortrait = 4;
public static final int LayoutOption_MobileLandscape = 6; public static final int LayoutOption_MobileLandscape = 5;
public static boolean getJoystickRelCenter() { public static boolean getJoystickRelCenter() {
return mPreferences.getBoolean("EmulationMenuSettings_JoystickRelCenter", true); return mPreferences.getBoolean("EmulationMenuSettings_JoystickRelCenter", true);

View File

@ -151,7 +151,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
Camera::RegisterFactory("ndk", std::move(ndk_factory)); Camera::RegisterFactory("ndk", std::move(ndk_factory));
// Register frontend applets // Register frontend applets
Frontend::RegisterDefaultApplets(system); Frontend::RegisterDefaultApplets();
system.RegisterMiiSelector(std::make_shared<MiiSelector::AndroidMiiSelector>()); system.RegisterMiiSelector(std::make_shared<MiiSelector::AndroidMiiSelector>());
system.RegisterSoftwareKeyboard(std::make_shared<SoftwareKeyboard::AndroidKeyboard>()); system.RegisterSoftwareKeyboard(std::make_shared<SoftwareKeyboard::AndroidKeyboard>());

View File

@ -37,33 +37,39 @@ CubebInput::CubebInput(std::string device_id)
} }
CubebInput::~CubebInput() { CubebInput::~CubebInput() {
if (!impl->ctx)
return;
if (impl->stream) { if (impl->stream) {
if (cubeb_stream_stop(impl->stream) != CUBEB_OK) { if (cubeb_stream_stop(impl->stream) != CUBEB_OK) {
LOG_ERROR(Audio, "Error stopping cubeb input stream."); LOG_ERROR(Audio, "Error stopping cubeb input stream.");
} }
cubeb_stream_destroy(impl->stream); cubeb_stream_destroy(impl->stream);
} }
if (impl->ctx) { cubeb_destroy(impl->ctx);
cubeb_destroy(impl->ctx);
}
} }
void CubebInput::StartSampling(const InputParameters& params) { void CubebInput::StartSampling(const InputParameters& params) {
// Cubeb apparently only supports signed 16 bit PCM (and float32 which the 3ds doesn't support) // Cubeb apparently only supports signed 16 bit PCM (and float32 which the 3ds doesn't support)
// TODO: Resample the input stream. // TODO resample the input stream
if (params.sign == Signedness::Unsigned) { if (params.sign == Signedness::Unsigned) {
LOG_ERROR(Audio, LOG_ERROR(Audio,
"Application requested unsupported unsigned pcm format. Falling back to signed."); "Application requested unsupported unsigned pcm format. Falling back to signed");
} }
parameters = params;
impl->sample_size_in_bytes = params.sample_size / 8; impl->sample_size_in_bytes = params.sample_size / 8;
parameters = params;
is_sampling = true;
cubeb_devid input_device = nullptr; cubeb_devid input_device = nullptr;
if (device_id != auto_device_name && !device_id.empty()) { if (device_id != auto_device_name && !device_id.empty()) {
cubeb_device_collection collection; cubeb_device_collection collection;
if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) == CUBEB_OK) { if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) != CUBEB_OK) {
LOG_WARNING(Audio, "Audio input device enumeration not supported");
} else {
const auto collection_end = collection.device + collection.count; const auto collection_end = collection.device + collection.count;
const auto device = std::find_if( const auto device = std::find_if(
collection.device, collection_end, [this](const cubeb_device_info& info) { collection.device, collection_end, [this](const cubeb_device_info& info) {
@ -73,42 +79,39 @@ void CubebInput::StartSampling(const InputParameters& params) {
input_device = device->devid; input_device = device->devid;
} }
cubeb_device_collection_destroy(impl->ctx, &collection); cubeb_device_collection_destroy(impl->ctx, &collection);
} else {
LOG_WARNING(Audio_Sink,
"Audio input device enumeration not supported, using default device.");
} }
} }
cubeb_stream_params input_params = { cubeb_stream_params input_params;
.format = CUBEB_SAMPLE_S16LE, input_params.channels = 1;
.rate = params.sample_rate, input_params.layout = CUBEB_LAYOUT_UNDEFINED;
.channels = 1, input_params.prefs = CUBEB_STREAM_PREF_NONE;
.layout = CUBEB_LAYOUT_UNDEFINED, input_params.format = CUBEB_SAMPLE_S16LE;
}; input_params.rate = params.sample_rate;
u32 latency_frames = 512; // Firefox default u32 latency_frames = 512; // Firefox default
if (cubeb_get_min_latency(impl->ctx, &input_params, &latency_frames) != CUBEB_OK) { if (cubeb_get_min_latency(impl->ctx, &input_params, &latency_frames) != CUBEB_OK) {
LOG_WARNING(Audio, "Error getting minimum input latency, falling back to default latency."); LOG_ERROR(Audio, "Could not get minimum latency");
} }
if (cubeb_stream_init(impl->ctx, &impl->stream, "Citra Microphone", input_device, &input_params, if (cubeb_stream_init(impl->ctx, &impl->stream, "Citra Microphone", input_device, &input_params,
nullptr, nullptr, latency_frames, Impl::DataCallback, Impl::StateCallback, nullptr, nullptr, latency_frames, Impl::DataCallback, Impl::StateCallback,
impl.get()) != CUBEB_OK) { impl.get()) != CUBEB_OK) {
LOG_CRITICAL(Audio, "Error creating cubeb input stream."); LOG_CRITICAL(Audio, "Error creating cubeb input stream");
is_sampling = false;
return; return;
} }
if (cubeb_stream_start(impl->stream) != CUBEB_OK) { if (cubeb_stream_start(impl->stream) != CUBEB_OK) {
LOG_CRITICAL(Audio, "Error starting cubeb input stream."); LOG_CRITICAL(Audio, "Error starting cubeb input stream");
cubeb_stream_destroy(impl->stream); is_sampling = false;
impl->stream = nullptr;
return; return;
} }
is_sampling = true;
} }
void CubebInput::StopSampling() { void CubebInput::StopSampling() {
// TODO(xperia64): Destroy the stream for now to avoid a leak because StartSampling
// reinitializes the stream every time
if (impl->stream) { if (impl->stream) {
cubeb_stream_stop(impl->stream); cubeb_stream_stop(impl->stream);
cubeb_stream_destroy(impl->stream); cubeb_stream_destroy(impl->stream);
@ -118,14 +121,8 @@ void CubebInput::StopSampling() {
} }
void CubebInput::AdjustSampleRate(u32 sample_rate) { void CubebInput::AdjustSampleRate(u32 sample_rate) {
if (!is_sampling) { // TODO This should restart the stream with the new sample rate
return; LOG_ERROR(Audio, "AdjustSampleRate unimplemented!");
}
auto new_parameters = parameters;
new_parameters.sample_rate = sample_rate;
StopSampling();
StartSampling(new_parameters);
} }
Samples CubebInput::Read() { Samples CubebInput::Read() {
@ -139,7 +136,7 @@ Samples CubebInput::Read() {
long CubebInput::Impl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, long CubebInput::Impl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
void* output_buffer, long num_frames) { void* output_buffer, long num_frames) {
auto impl = static_cast<Impl*>(user_data); Impl* impl = static_cast<Impl*>(user_data);
if (!impl) { if (!impl) {
return 0; return 0;
} }
@ -180,7 +177,9 @@ std::vector<std::string> ListCubebInputDevices() {
} }
cubeb_device_collection collection; cubeb_device_collection collection;
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) == CUBEB_OK) { if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) != CUBEB_OK) {
LOG_WARNING(Audio_Sink, "Audio input device enumeration not supported");
} else {
for (std::size_t i = 0; i < collection.count; i++) { for (std::size_t i = 0; i < collection.count; i++) {
const cubeb_device_info& device = collection.device[i]; const cubeb_device_info& device = collection.device[i];
if (device.state == CUBEB_DEVICE_STATE_ENABLED && device.friendly_name) { if (device.state == CUBEB_DEVICE_STATE_ENABLED && device.friendly_name) {
@ -188,8 +187,6 @@ std::vector<std::string> ListCubebInputDevices() {
} }
} }
cubeb_device_collection_destroy(ctx, &collection); cubeb_device_collection_destroy(ctx, &collection);
} else {
LOG_WARNING(Audio_Sink, "Audio input device enumeration not supported.");
} }
cubeb_destroy(ctx); cubeb_destroy(ctx);

View File

@ -13,6 +13,8 @@
namespace AudioCore { namespace AudioCore {
struct CubebSink::Impl { struct CubebSink::Impl {
unsigned int sample_rate = 0;
cubeb* ctx = nullptr; cubeb* ctx = nullptr;
cubeb_stream* stream = nullptr; cubeb_stream* stream = nullptr;
@ -29,29 +31,28 @@ CubebSink::CubebSink(std::string_view target_device_name) : impl(std::make_uniqu
LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
return; return;
} }
cubeb_set_log_callback(CUBEB_LOG_NORMAL, &Impl::LogCallback);
if (cubeb_set_log_callback(CUBEB_LOG_NORMAL, &Impl::LogCallback) != CUBEB_OK) { impl->sample_rate = native_sample_rate;
LOG_CRITICAL(Audio_Sink, "cubeb_set_log_callback failed");
return;
}
cubeb_stream_params params = { cubeb_stream_params params;
.format = CUBEB_SAMPLE_S16LE, params.rate = impl->sample_rate;
.rate = native_sample_rate, params.channels = 2;
.channels = 2, params.layout = CUBEB_LAYOUT_STEREO;
.layout = CUBEB_LAYOUT_STEREO, params.format = CUBEB_SAMPLE_S16NE;
}; params.prefs = CUBEB_STREAM_PREF_PERSIST;
u32 minimum_latency = 100 * native_sample_rate / 1000; // Firefox default u32 minimum_latency = 100 * impl->sample_rate / 1000; // Firefox default
if (cubeb_get_min_latency(impl->ctx, &params, &minimum_latency) != CUBEB_OK) { if (cubeb_get_min_latency(impl->ctx, &params, &minimum_latency) != CUBEB_OK) {
LOG_WARNING(Audio_Sink, LOG_CRITICAL(Audio_Sink, "Error getting minimum latency");
"Error getting minimum output latency, falling back to default latency.");
} }
cubeb_devid output_device = nullptr; cubeb_devid output_device = nullptr;
if (target_device_name != auto_device_name && !target_device_name.empty()) { if (target_device_name != auto_device_name && !target_device_name.empty()) {
cubeb_device_collection collection; cubeb_device_collection collection;
if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) == CUBEB_OK) { if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
} else {
const auto collection_end{collection.device + collection.count}; const auto collection_end{collection.device + collection.count};
const auto device{ const auto device{
std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) { std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
@ -62,15 +63,12 @@ CubebSink::CubebSink(std::string_view target_device_name) : impl(std::make_uniqu
output_device = device->devid; output_device = device->devid;
} }
cubeb_device_collection_destroy(impl->ctx, &collection); cubeb_device_collection_destroy(impl->ctx, &collection);
} else {
LOG_WARNING(Audio_Sink,
"Audio output device enumeration not supported, using default device.");
} }
} }
auto stream_err = cubeb_stream_init(impl->ctx, &impl->stream, "CitraAudio", nullptr, nullptr, int stream_err = cubeb_stream_init(impl->ctx, &impl->stream, "CitraAudio", nullptr, nullptr,
output_device, &params, std::max(512u, minimum_latency), output_device, &params, std::max(512u, minimum_latency),
&Impl::DataCallback, &Impl::StateCallback, impl.get()); &Impl::DataCallback, &Impl::StateCallback, impl.get());
if (stream_err != CUBEB_OK) { if (stream_err != CUBEB_OK) {
switch (stream_err) { switch (stream_err) {
case CUBEB_ERROR: case CUBEB_ERROR:
@ -94,20 +92,23 @@ CubebSink::CubebSink(std::string_view target_device_name) : impl(std::make_uniqu
} }
CubebSink::~CubebSink() { CubebSink::~CubebSink() {
if (impl->stream) { if (!impl->ctx) {
if (cubeb_stream_stop(impl->stream) != CUBEB_OK) { return;
LOG_ERROR(Audio_Sink, "Error stopping cubeb stream.");
}
cubeb_stream_destroy(impl->stream);
} }
if (impl->ctx) { if (cubeb_stream_stop(impl->stream) != CUBEB_OK) {
cubeb_destroy(impl->ctx); LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
} }
cubeb_stream_destroy(impl->stream);
cubeb_destroy(impl->ctx);
} }
unsigned int CubebSink::GetNativeSampleRate() const { unsigned int CubebSink::GetNativeSampleRate() const {
return native_sample_rate; if (!impl->ctx)
return native_sample_rate;
return impl->sample_rate;
} }
void CubebSink::SetCallback(std::function<void(s16*, std::size_t)> cb) { void CubebSink::SetCallback(std::function<void(s16*, std::size_t)> cb) {
@ -120,12 +121,13 @@ long CubebSink::Impl::DataCallback(cubeb_stream* stream, void* user_data, const
auto* buffer = static_cast<s16*>(output_buffer); auto* buffer = static_cast<s16*>(output_buffer);
if (!impl || !impl->cb) { if (!impl || !impl->cb) {
LOG_DEBUG(Audio_Sink, "Missing internal data and/or audio callback, emitting zeroes."); LOG_DEBUG(Audio_Sink, "Emitting zeros");
std::memset(output_buffer, 0, num_frames * 2 * sizeof(s16)); std::memset(output_buffer, 0, num_frames * 2 * sizeof(s16));
} else { return num_frames;
impl->cb(buffer, num_frames);
} }
impl->cb(buffer, num_frames);
return num_frames; return num_frames;
} }
@ -147,7 +149,7 @@ void CubebSink::Impl::StateCallback(cubeb_stream* stream, void* user_data, cubeb
} }
void CubebSink::Impl::LogCallback(char const* format, ...) { void CubebSink::Impl::LogCallback(char const* format, ...) {
std::array<char, 512> buffer{}; std::array<char, 512> buffer;
std::va_list args; std::va_list args;
va_start(args, format); va_start(args, format);
#ifdef _MSC_VER #ifdef _MSC_VER
@ -164,13 +166,15 @@ std::vector<std::string> ListCubebSinkDevices() {
std::vector<std::string> device_list; std::vector<std::string> device_list;
cubeb* ctx; cubeb* ctx;
if (cubeb_init(&ctx, "Citra Output Device Enumerator", nullptr) != CUBEB_OK) { if (cubeb_init(&ctx, "CitraEnumerator", nullptr) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
return {}; return {};
} }
cubeb_device_collection collection; cubeb_device_collection collection;
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) == CUBEB_OK) { if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
} else {
for (std::size_t i = 0; i < collection.count; i++) { for (std::size_t i = 0; i < collection.count; i++) {
const cubeb_device_info& device = collection.device[i]; const cubeb_device_info& device = collection.device[i];
if (device.state == CUBEB_DEVICE_STATE_ENABLED && device.friendly_name) { if (device.state == CUBEB_DEVICE_STATE_ENABLED && device.friendly_name) {
@ -178,8 +182,6 @@ std::vector<std::string> ListCubebSinkDevices() {
} }
} }
cubeb_device_collection_destroy(ctx, &collection); cubeb_device_collection_destroy(ctx, &collection);
} else {
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported.");
} }
cubeb_destroy(ctx); cubeb_destroy(ctx);

View File

@ -66,7 +66,7 @@ public:
private: private:
const std::size_t source_id; const std::size_t source_id;
const Memory::MemorySystem* memory_system{}; Memory::MemorySystem* memory_system;
StereoFrame16 current_frame; StereoFrame16 current_frame;
using Format = SourceConfiguration::Configuration::Format; using Format = SourceConfiguration::Configuration::Format;

View File

@ -346,7 +346,7 @@ int main(int argc, char** argv) {
system.ApplySettings(); system.ApplySettings();
// Register frontend applets // Register frontend applets
Frontend::RegisterDefaultApplets(system); Frontend::RegisterDefaultApplets();
EmuWindow_SDL2::InitializeSDL2(); EmuWindow_SDL2::InitializeSDL2();
@ -354,12 +354,12 @@ int main(int argc, char** argv) {
bool is_secondary) -> std::unique_ptr<EmuWindow_SDL2> { bool is_secondary) -> std::unique_ptr<EmuWindow_SDL2> {
switch (Settings::values.graphics_api.GetValue()) { switch (Settings::values.graphics_api.GetValue()) {
case Settings::GraphicsAPI::OpenGL: case Settings::GraphicsAPI::OpenGL:
return std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen, is_secondary); return std::make_unique<EmuWindow_SDL2_GL>(fullscreen, is_secondary);
case Settings::GraphicsAPI::Software: case Settings::GraphicsAPI::Software:
return std::make_unique<EmuWindow_SDL2_SW>(system, fullscreen, is_secondary); return std::make_unique<EmuWindow_SDL2_SW>(system, fullscreen, is_secondary);
} }
LOG_ERROR(Frontend, "Invalid Graphics API, using OpenGL"); LOG_ERROR(Frontend, "Invalid Graphics API, using OpenGL");
return std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen, is_secondary); return std::make_unique<EmuWindow_SDL2_GL>(fullscreen, is_secondary);
}; };
const auto emu_window{create_emu_window(fullscreen, false)}; const auto emu_window{create_emu_window(fullscreen, false)};

View File

@ -109,8 +109,7 @@ void EmuWindow_SDL2::Fullscreen() {
SDL_MaximizeWindow(render_window); SDL_MaximizeWindow(render_window);
} }
EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system_, bool is_secondary) EmuWindow_SDL2::EmuWindow_SDL2(bool is_secondary) : EmuWindow(is_secondary) {}
: EmuWindow(is_secondary), system(system_) {}
EmuWindow_SDL2::~EmuWindow_SDL2() { EmuWindow_SDL2::~EmuWindow_SDL2() {
SDL_Quit(); SDL_Quit();
@ -203,7 +202,7 @@ void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minima
void EmuWindow_SDL2::UpdateFramerateCounter() { void EmuWindow_SDL2::UpdateFramerateCounter() {
const u32 current_time = SDL_GetTicks(); const u32 current_time = SDL_GetTicks();
if (current_time > last_time + 2000) { if (current_time > last_time + 2000) {
const auto results = system.GetAndResetPerfStats(); const auto results = Core::System::GetInstance().GetAndResetPerfStats();
const auto title = const auto title =
fmt::format("Citra {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname, fmt::format("Citra {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc, results.game_fps, Common::g_scm_branch, Common::g_scm_desc, results.game_fps,

View File

@ -10,13 +10,9 @@
struct SDL_Window; struct SDL_Window;
namespace Core {
class System;
}
class EmuWindow_SDL2 : public Frontend::EmuWindow { class EmuWindow_SDL2 : public Frontend::EmuWindow {
public: public:
explicit EmuWindow_SDL2(Core::System& system_, bool is_secondary); explicit EmuWindow_SDL2(bool is_secondary);
~EmuWindow_SDL2(); ~EmuWindow_SDL2();
/// Initializes SDL2 /// Initializes SDL2
@ -82,6 +78,4 @@ protected:
/// Keeps track of how often to update the title bar during gameplay /// Keeps track of how often to update the title bar during gameplay
u32 last_time = 0; u32 last_time = 0;
Core::System& system;
}; };

View File

@ -42,8 +42,8 @@ private:
SDL_GLContext context; SDL_GLContext context;
}; };
EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system_, bool fullscreen, bool is_secondary) EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen, bool is_secondary)
: EmuWindow_SDL2{system_, is_secondary} { : EmuWindow_SDL2{is_secondary} {
// Initialize the window // Initialize the window
if (Settings::values.use_gles) { if (Settings::values.use_gles) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);

View File

@ -9,13 +9,9 @@
struct SDL_Window; struct SDL_Window;
namespace Core {
class System;
}
class EmuWindow_SDL2_GL : public EmuWindow_SDL2 { class EmuWindow_SDL2_GL : public EmuWindow_SDL2 {
public: public:
explicit EmuWindow_SDL2_GL(Core::System& system_, bool fullscreen, bool is_secondary); explicit EmuWindow_SDL2_GL(bool fullscreen, bool is_secondary);
~EmuWindow_SDL2_GL(); ~EmuWindow_SDL2_GL();
void Present() override; void Present() override;

View File

@ -18,7 +18,7 @@
class DummyContext : public Frontend::GraphicsContext {}; class DummyContext : public Frontend::GraphicsContext {};
EmuWindow_SDL2_SW::EmuWindow_SDL2_SW(Core::System& system_, bool fullscreen, bool is_secondary) EmuWindow_SDL2_SW::EmuWindow_SDL2_SW(Core::System& system_, bool fullscreen, bool is_secondary)
: EmuWindow_SDL2{system_, is_secondary}, system{system_} { : EmuWindow_SDL2{is_secondary}, system{system_} {
std::string window_title = fmt::format("Citra {} | {}-{}", Common::g_build_fullname, std::string window_title = fmt::format("Citra {} | {}-{}", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc); Common::g_scm_branch, Common::g_scm_desc);
render_window = render_window =

View File

@ -44,8 +44,7 @@
static Frontend::WindowSystemType GetWindowSystemType(); static Frontend::WindowSystemType GetWindowSystemType();
EmuThread::EmuThread(Core::System& system_, Frontend::GraphicsContext& core_context) EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(core_context) {}
: system{system_}, core_context(core_context) {}
EmuThread::~EmuThread() = default; EmuThread::~EmuThread() = default;
@ -63,6 +62,7 @@ static GMainWindow* GetMainWindow() {
void EmuThread::run() { void EmuThread::run() {
MicroProfileOnThreadCreate("EmuThread"); MicroProfileOnThreadCreate("EmuThread");
const auto scope = core_context.Acquire(); const auto scope = core_context.Acquire();
Core::System& system = Core::System::GetInstance();
if (Settings::values.preload_textures) { if (Settings::values.preload_textures) {
emit LoadProgress(VideoCore::LoadCallbackStage::Preload, 0, 0); emit LoadProgress(VideoCore::LoadCallbackStage::Preload, 0, 0);
@ -107,7 +107,7 @@ void EmuThread::run() {
} }
if (result != Core::System::ResultStatus::Success) { if (result != Core::System::ResultStatus::Success) {
this->SetRunning(false); this->SetRunning(false);
emit ErrorThrown(result, system.GetStatusDetails()); emit ErrorThrown(result, Core::System::GetInstance().GetStatusDetails());
} }
was_active = running || exec_step; was_active = running || exec_step;
@ -248,8 +248,8 @@ public:
#ifdef HAS_OPENGL #ifdef HAS_OPENGL
class OpenGLRenderWidget : public RenderWidget { class OpenGLRenderWidget : public RenderWidget {
public: public:
explicit OpenGLRenderWidget(GRenderWindow* parent, Core::System& system_, bool is_secondary) explicit OpenGLRenderWidget(GRenderWindow* parent, bool is_secondary)
: RenderWidget(parent), system(system_), is_secondary(is_secondary) { : RenderWidget(parent), is_secondary(is_secondary) {
setAttribute(Qt::WA_NativeWindow); setAttribute(Qt::WA_NativeWindow);
setAttribute(Qt::WA_PaintOnScreen); setAttribute(Qt::WA_PaintOnScreen);
if (GetWindowSystemType() == Frontend::WindowSystemType::Wayland) { if (GetWindowSystemType() == Frontend::WindowSystemType::Wayland) {
@ -266,7 +266,7 @@ public:
if (!isVisible()) { if (!isVisible()) {
return; return;
} }
if (!system.IsPoweredOn()) { if (!Core::System::GetInstance().IsPoweredOn()) {
return; return;
} }
context->MakeCurrent(); context->MakeCurrent();
@ -284,7 +284,6 @@ public:
private: private:
std::unique_ptr<Frontend::GraphicsContext> context{}; std::unique_ptr<Frontend::GraphicsContext> context{};
Core::System& system;
bool is_secondary; bool is_secondary;
}; };
#endif #endif
@ -297,7 +296,7 @@ struct SoftwareRenderWidget : public RenderWidget {
if (!isVisible()) { if (!isVisible()) {
return; return;
} }
if (!system.IsPoweredOn()) { if (!Core::System::GetInstance().IsPoweredOn()) {
return; return;
} }
@ -667,7 +666,7 @@ bool GRenderWindow::InitializeOpenGL() {
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose // WA_DontShowOnScreen, WA_DeleteOnClose
auto child = new OpenGLRenderWidget(this, system, is_secondary); auto child = new OpenGLRenderWidget(this, is_secondary);
child_widget = child; child_widget = child;
child_widget->windowHandle()->create(); child_widget->windowHandle()->create();

View File

@ -18,10 +18,6 @@ class QTouchEvent;
class GRenderWindow; class GRenderWindow;
namespace Core {
class System;
}
namespace VideoCore { namespace VideoCore {
enum class LoadCallbackStage; enum class LoadCallbackStage;
} }
@ -30,7 +26,7 @@ class EmuThread final : public QThread {
Q_OBJECT Q_OBJECT
public: public:
explicit EmuThread(Core::System& system_, Frontend::GraphicsContext& context); explicit EmuThread(Frontend::GraphicsContext& context);
~EmuThread() override; ~EmuThread() override;
/** /**
@ -84,7 +80,6 @@ private:
std::mutex running_mutex; std::mutex running_mutex;
std::condition_variable running_cv; std::condition_variable running_cv;
Core::System& system;
Frontend::GraphicsContext& core_context; Frontend::GraphicsContext& core_context;
signals: signals:

View File

@ -11,9 +11,9 @@
#include "core/core.h" #include "core/core.h"
#include "ui_compatdb.h" #include "ui_compatdb.h"
CompatDB::CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent) CompatDB::CompatDB(QWidget* parent)
: QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), : QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
ui{std::make_unique<Ui::CompatDB>()}, telemetry_session{telemetry_session_} { ui{std::make_unique<Ui::CompatDB>()} {
ui->setupUi(this); ui->setupUi(this);
connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext); connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext); connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext);
@ -51,15 +51,16 @@ void CompatDB::Submit() {
case CompatDBPage::Final: case CompatDBPage::Final:
back(); back();
LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId()); LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId());
telemetry_session.AddField(Common::Telemetry::FieldType::UserFeedback, "Compatibility", Core::System::GetInstance().TelemetrySession().AddField(
compatibility->checkedId()); Common::Telemetry::FieldType::UserFeedback, "Compatibility",
compatibility->checkedId());
button(NextButton)->setEnabled(false); button(NextButton)->setEnabled(false);
button(NextButton)->setText(tr("Submitting")); button(NextButton)->setText(tr("Submitting"));
button(CancelButton)->setVisible(false); button(CancelButton)->setVisible(false);
testcase_watcher.setFuture( testcase_watcher.setFuture(QtConcurrent::run(
QtConcurrent::run([this] { return telemetry_session.SubmitTestcase(); })); [] { return Core::System::GetInstance().TelemetrySession().SubmitTestcase(); }));
break; break;
default: default:
LOG_ERROR(Frontend, "Unexpected page: {}", currentId()); LOG_ERROR(Frontend, "Unexpected page: {}", currentId());

View File

@ -8,10 +8,6 @@
#include <QFutureWatcher> #include <QFutureWatcher>
#include <QWizard> #include <QWizard>
namespace Core {
class TelemetrySession;
}
namespace Ui { namespace Ui {
class CompatDB; class CompatDB;
} }
@ -20,7 +16,7 @@ class CompatDB : public QWizard {
Q_OBJECT Q_OBJECT
public: public:
explicit CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent = nullptr); explicit CompatDB(QWidget* parent = nullptr);
~CompatDB(); ~CompatDB();
private: private:
@ -31,6 +27,4 @@ private:
void Submit(); void Submit();
void OnTestcaseSubmitted(); void OnTestcaseSubmitted();
void EnableNext(); void EnableNext();
Core::TelemetrySession& telemetry_session;
}; };

View File

@ -9,12 +9,12 @@
#include "core/cheats/cheat_base.h" #include "core/cheats/cheat_base.h"
#include "core/cheats/cheats.h" #include "core/cheats/cheats.h"
#include "core/cheats/gateway_cheat.h" #include "core/cheats/gateway_cheat.h"
#include "core/core.h"
#include "core/hle/kernel/process.h"
#include "ui_configure_cheats.h" #include "ui_configure_cheats.h"
ConfigureCheats::ConfigureCheats(u64 title_id_, QWidget* parent) ConfigureCheats::ConfigureCheats(Cheats::CheatEngine& cheat_engine_, u64 title_id_, QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureCheats>()), title_id{title_id_} { : QWidget(parent),
ui(std::make_unique<Ui::ConfigureCheats>()), cheat_engine{cheat_engine_}, title_id{
title_id_} {
// Setup gui control settings // Setup gui control settings
ui->setupUi(this); ui->setupUi(this);
ui->tableCheats->setColumnWidth(0, 30); ui->tableCheats->setColumnWidth(0, 30);
@ -36,15 +36,14 @@ ConfigureCheats::ConfigureCheats(u64 title_id_, QWidget* parent)
[this] { SaveCheat(ui->tableCheats->currentRow()); }); [this] { SaveCheat(ui->tableCheats->currentRow()); });
connect(ui->buttonDelete, &QPushButton::clicked, this, &ConfigureCheats::OnDeleteCheat); connect(ui->buttonDelete, &QPushButton::clicked, this, &ConfigureCheats::OnDeleteCheat);
cheat_engine = std::make_unique<Cheats::CheatEngine>(title_id, Core::System::GetInstance()); cheat_engine.LoadCheatFile(title_id);
LoadCheats(); LoadCheats();
} }
ConfigureCheats::~ConfigureCheats() = default; ConfigureCheats::~ConfigureCheats() = default;
void ConfigureCheats::LoadCheats() { void ConfigureCheats::LoadCheats() {
cheats = cheat_engine->GetCheats(); cheats = cheat_engine.GetCheats();
const int cheats_count = static_cast<int>(cheats.size()); const int cheats_count = static_cast<int>(cheats.size());
ui->tableCheats->setRowCount(cheats_count); ui->tableCheats->setRowCount(cheats_count);
@ -108,12 +107,12 @@ bool ConfigureCheats::SaveCheat(int row) {
ui->textNotes->toPlainText().toStdString()); ui->textNotes->toPlainText().toStdString());
if (newly_created) { if (newly_created) {
cheat_engine->AddCheat(cheat); cheat_engine.AddCheat(std::move(cheat));
newly_created = false; newly_created = false;
} else { } else {
cheat_engine->UpdateCheat(row, cheat); cheat_engine.UpdateCheat(row, std::move(cheat));
} }
cheat_engine->SaveCheatFile(); cheat_engine.SaveCheatFile(title_id);
int previous_row = ui->tableCheats->currentRow(); int previous_row = ui->tableCheats->currentRow();
int previous_col = ui->tableCheats->currentColumn(); int previous_col = ui->tableCheats->currentColumn();
@ -163,7 +162,7 @@ void ConfigureCheats::OnCheckChanged(int state) {
const QCheckBox* checkbox = qobject_cast<QCheckBox*>(sender()); const QCheckBox* checkbox = qobject_cast<QCheckBox*>(sender());
int row = static_cast<int>(checkbox->property("row").toInt()); int row = static_cast<int>(checkbox->property("row").toInt());
cheats[row]->SetEnabled(state); cheats[row]->SetEnabled(state);
cheat_engine->SaveCheatFile(); cheat_engine.SaveCheatFile(title_id);
} }
void ConfigureCheats::OnTextEdited() { void ConfigureCheats::OnTextEdited() {
@ -175,8 +174,8 @@ void ConfigureCheats::OnDeleteCheat() {
if (newly_created) { if (newly_created) {
newly_created = false; newly_created = false;
} else { } else {
cheat_engine->RemoveCheat(ui->tableCheats->currentRow()); cheat_engine.RemoveCheat(ui->tableCheats->currentRow());
cheat_engine->SaveCheatFile(); cheat_engine.SaveCheatFile(title_id);
} }
LoadCheats(); LoadCheats();

View File

@ -5,6 +5,8 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <span>
#include <QWidget>
#include "common/common_types.h" #include "common/common_types.h"
namespace Cheats { namespace Cheats {
@ -20,7 +22,8 @@ class ConfigureCheats : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
explicit ConfigureCheats(u64 title_id_, QWidget* parent = nullptr); explicit ConfigureCheats(Cheats::CheatEngine& cheat_engine, u64 title_id_,
QWidget* parent = nullptr);
~ConfigureCheats(); ~ConfigureCheats();
bool ApplyConfiguration(); bool ApplyConfiguration();
@ -53,9 +56,9 @@ private slots:
private: private:
std::unique_ptr<Ui::ConfigureCheats> ui; std::unique_ptr<Ui::ConfigureCheats> ui;
std::vector<std::shared_ptr<Cheats::CheatBase>> cheats; Cheats::CheatEngine& cheat_engine;
std::span<const std::shared_ptr<Cheats::CheatBase>> cheats;
bool edited = false, newly_created = false; bool edited = false, newly_created = false;
int last_row = -1, last_col = -1; int last_row = -1, last_col = -1;
u64 title_id; u64 title_id;
std::unique_ptr<Cheats::CheatEngine> cheat_engine;
}; };

View File

@ -36,7 +36,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const QString
graphics_tab = std::make_unique<ConfigureGraphics>(this); graphics_tab = std::make_unique<ConfigureGraphics>(this);
system_tab = std::make_unique<ConfigureSystem>(this); system_tab = std::make_unique<ConfigureSystem>(this);
debug_tab = std::make_unique<ConfigureDebug>(this); debug_tab = std::make_unique<ConfigureDebug>(this);
cheat_tab = std::make_unique<ConfigureCheats>(title_id, this); cheat_tab = std::make_unique<ConfigureCheats>(system.CheatEngine(), title_id, this);
ui->setupUi(this); ui->setupUi(this);

View File

@ -1223,7 +1223,7 @@ void GMainWindow::BootGame(const QString& filename) {
} }
// Create and start the emulation thread // Create and start the emulation thread
emu_thread = std::make_unique<EmuThread>(system, *render_window); emu_thread = std::make_unique<EmuThread>(*render_window);
emit EmulationStarting(emu_thread.get()); emit EmulationStarting(emu_thread.get());
emu_thread->start(); emu_thread->start();
@ -1814,7 +1814,7 @@ void GMainWindow::OnLoadComplete() {
void GMainWindow::OnMenuReportCompatibility() { void GMainWindow::OnMenuReportCompatibility() {
if (!NetSettings::values.citra_token.empty() && !NetSettings::values.citra_username.empty()) { if (!NetSettings::values.citra_token.empty() && !NetSettings::values.citra_username.empty()) {
CompatDB compatdb{system.TelemetrySession(), this}; CompatDB compatdb{this};
compatdb.exec(); compatdb.exec();
} else { } else {
QMessageBox::critical(this, tr("Missing Citra Account"), QMessageBox::critical(this, tr("Missing Citra Account"),
@ -2931,7 +2931,7 @@ int main(int argc, char* argv[]) {
GMainWindow main_window(system); GMainWindow main_window(system);
// Register frontend applets // Register frontend applets
Frontend::RegisterDefaultApplets(system); Frontend::RegisterDefaultApplets();
system.RegisterMiiSelector(std::make_shared<QtMiiSelector>(main_window)); system.RegisterMiiSelector(std::make_shared<QtMiiSelector>(main_window));
system.RegisterSoftwareKeyboard(std::make_shared<QtKeyboard>(main_window)); system.RegisterSoftwareKeyboard(std::make_shared<QtKeyboard>(main_window));

View File

@ -3,15 +3,10 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <QImage> #include <QImage>
#include <QImageReader>
#include <QString> #include <QString>
#include "citra_qt/qt_image_interface.h" #include "citra_qt/qt_image_interface.h"
#include "common/logging/log.h" #include "common/logging/log.h"
QtImageInterface::QtImageInterface() {
QImageReader::setAllocationLimit(0);
}
bool QtImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height, bool QtImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height,
std::span<const u8> src) { std::span<const u8> src) {
QImage image(QImage::fromData(src.data(), static_cast<int>(src.size()))); QImage image(QImage::fromData(src.data(), static_cast<int>(src.size())));

View File

@ -8,7 +8,6 @@
class QtImageInterface final : public Frontend::ImageInterface { class QtImageInterface final : public Frontend::ImageInterface {
public: public:
QtImageInterface();
bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, std::span<const u8> src) override; bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, std::span<const u8> src) override;
bool EncodePNG(const std::string& path, u32 width, u32 height, bool EncodePNG(const std::string& path, u32 width, u32 height,
std::span<const u8> src) override; std::span<const u8> src) override;

View File

@ -88,7 +88,6 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Service, FRD) \ SUB(Service, FRD) \
SUB(Service, FS) \ SUB(Service, FS) \
SUB(Service, ERR) \ SUB(Service, ERR) \
SUB(Service, ACT) \
SUB(Service, APT) \ SUB(Service, APT) \
SUB(Service, BOSS) \ SUB(Service, BOSS) \
SUB(Service, GSP) \ SUB(Service, GSP) \

View File

@ -55,7 +55,6 @@ enum class Class : u8 {
Service_FRD, ///< The FRD (Friends) service Service_FRD, ///< The FRD (Friends) service
Service_FS, ///< The FS (Filesystem) service implementation Service_FS, ///< The FS (Filesystem) service implementation
Service_ERR, ///< The ERR (Error) port implementation Service_ERR, ///< The ERR (Error) port implementation
Service_ACT, ///< The ACT (Account) service
Service_APT, ///< The APT (Applets) service Service_APT, ///< The APT (Applets) service
Service_BOSS, ///< The BOSS (SpotPass) service Service_BOSS, ///< The BOSS (SpotPass) service
Service_GSP, ///< The GSP (GPU control) service Service_GSP, ///< The GSP (GPU control) service

View File

@ -10,8 +10,6 @@
#include "core/cheats/gateway_cheat.h" #include "core/cheats/gateway_cheat.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/hle/kernel/process.h"
#include "core/hw/gpu.h"
namespace Cheats { namespace Cheats {
@ -19,11 +17,11 @@ namespace Cheats {
// we use the same value // we use the same value
constexpr u64 run_interval_ticks = 50'000'000; constexpr u64 run_interval_ticks = 50'000'000;
CheatEngine::CheatEngine(u64 title_id_, Core::System& system_) CheatEngine::CheatEngine(Core::System& system_) : system{system_} {}
: system(system_), title_id{title_id_} {
LoadCheatFile(); CheatEngine::~CheatEngine() {
if (system.IsPoweredOn()) { if (system.IsPoweredOn()) {
Connect(); system.CoreTiming().UnscheduleEvent(event, 0);
} }
} }
@ -34,24 +32,18 @@ void CheatEngine::Connect() {
system.CoreTiming().ScheduleEvent(run_interval_ticks, event); system.CoreTiming().ScheduleEvent(run_interval_ticks, event);
} }
CheatEngine::~CheatEngine() { std::span<const std::shared_ptr<CheatBase>> CheatEngine::GetCheats() const {
if (system.IsPoweredOn()) { std::shared_lock lock{cheats_list_mutex};
system.CoreTiming().UnscheduleEvent(event, 0);
}
}
std::vector<std::shared_ptr<CheatBase>> CheatEngine::GetCheats() const {
std::shared_lock<std::shared_mutex> lock(cheats_list_mutex);
return cheats_list; return cheats_list;
} }
void CheatEngine::AddCheat(const std::shared_ptr<CheatBase>& cheat) { void CheatEngine::AddCheat(std::shared_ptr<CheatBase>&& cheat) {
std::unique_lock<std::shared_mutex> lock(cheats_list_mutex); std::unique_lock lock{cheats_list_mutex};
cheats_list.push_back(cheat); cheats_list.push_back(std::move(cheat));
} }
void CheatEngine::RemoveCheat(std::size_t index) { void CheatEngine::RemoveCheat(std::size_t index) {
std::unique_lock<std::shared_mutex> lock(cheats_list_mutex); std::unique_lock lock{cheats_list_mutex};
if (index < 0 || index >= cheats_list.size()) { if (index < 0 || index >= cheats_list.size()) {
LOG_ERROR(Core_Cheats, "Invalid index {}", index); LOG_ERROR(Core_Cheats, "Invalid index {}", index);
return; return;
@ -59,16 +51,16 @@ void CheatEngine::RemoveCheat(std::size_t index) {
cheats_list.erase(cheats_list.begin() + index); cheats_list.erase(cheats_list.begin() + index);
} }
void CheatEngine::UpdateCheat(std::size_t index, const std::shared_ptr<CheatBase>& new_cheat) { void CheatEngine::UpdateCheat(std::size_t index, std::shared_ptr<CheatBase>&& new_cheat) {
std::unique_lock<std::shared_mutex> lock(cheats_list_mutex); std::unique_lock lock{cheats_list_mutex};
if (index < 0 || index >= cheats_list.size()) { if (index < 0 || index >= cheats_list.size()) {
LOG_ERROR(Core_Cheats, "Invalid index {}", index); LOG_ERROR(Core_Cheats, "Invalid index {}", index);
return; return;
} }
cheats_list[index] = new_cheat; cheats_list[index] = std::move(new_cheat);
} }
void CheatEngine::SaveCheatFile() const { void CheatEngine::SaveCheatFile(u64 title_id) const {
const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir); const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir);
const std::string filepath = fmt::format("{}{:016X}.txt", cheat_dir, title_id); const std::string filepath = fmt::format("{}{:016X}.txt", cheat_dir, title_id);
@ -83,7 +75,7 @@ void CheatEngine::SaveCheatFile() const {
} }
} }
void CheatEngine::LoadCheatFile() { void CheatEngine::LoadCheatFile(u64 title_id) {
const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir); const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir);
const std::string filepath = fmt::format("{}{:016X}.txt", cheat_dir, title_id); const std::string filepath = fmt::format("{}{:016X}.txt", cheat_dir, title_id);
@ -96,15 +88,15 @@ void CheatEngine::LoadCheatFile() {
auto gateway_cheats = GatewayCheat::LoadFile(filepath); auto gateway_cheats = GatewayCheat::LoadFile(filepath);
{ {
std::unique_lock<std::shared_mutex> lock(cheats_list_mutex); std::unique_lock lock{cheats_list_mutex};
std::move(gateway_cheats.begin(), gateway_cheats.end(), std::back_inserter(cheats_list)); cheats_list = std::move(gateway_cheats);
} }
} }
void CheatEngine::RunCallback([[maybe_unused]] std::uintptr_t user_data, s64 cycles_late) { void CheatEngine::RunCallback([[maybe_unused]] std::uintptr_t user_data, s64 cycles_late) {
{ {
std::shared_lock<std::shared_mutex> lock(cheats_list_mutex); std::shared_lock lock{cheats_list_mutex};
for (auto& cheat : cheats_list) { for (const auto& cheat : cheats_list) {
if (cheat->IsEnabled()) { if (cheat->IsEnabled()) {
cheat->Execute(system); cheat->Execute(system);
} }

View File

@ -6,6 +6,7 @@
#include <memory> #include <memory>
#include <shared_mutex> #include <shared_mutex>
#include <span>
#include <vector> #include <vector>
#include "common/common_types.h" #include "common/common_types.h"
@ -24,22 +25,38 @@ class CheatBase;
class CheatEngine { class CheatEngine {
public: public:
explicit CheatEngine(u64 title_id_, Core::System& system); explicit CheatEngine(Core::System& system);
~CheatEngine(); ~CheatEngine();
/// Registers the cheat execution callback.
void Connect(); void Connect();
std::vector<std::shared_ptr<CheatBase>> GetCheats() const;
void AddCheat(const std::shared_ptr<CheatBase>& cheat); /// Returns a span of the currently active cheats.
std::span<const std::shared_ptr<CheatBase>> GetCheats() const;
/// Adds a cheat to the cheat engine.
void AddCheat(std::shared_ptr<CheatBase>&& cheat);
/// Removes a cheat at the specified index in the cheats list.
void RemoveCheat(std::size_t index); void RemoveCheat(std::size_t index);
void UpdateCheat(std::size_t index, const std::shared_ptr<CheatBase>& new_cheat);
void SaveCheatFile() const; /// Updates a cheat at the specified index in the cheats list.
void UpdateCheat(std::size_t index, std::shared_ptr<CheatBase>&& new_cheat);
/// Loads the cheat file from disk for the specified title id.
void LoadCheatFile(u64 title_id);
/// Saves currently active cheats to file for the specified title id.
void SaveCheatFile(u64 title_id) const;
private: private:
void LoadCheatFile(); /// The cheat execution callback.
void RunCallback(std::uintptr_t user_data, s64 cycles_late); void RunCallback(std::uintptr_t user_data, s64 cycles_late);
private:
Core::System& system;
Core::TimingEventType* event;
std::vector<std::shared_ptr<CheatBase>> cheats_list; std::vector<std::shared_ptr<CheatBase>> cheats_list;
mutable std::shared_mutex cheats_list_mutex; mutable std::shared_mutex cheats_list_mutex;
Core::TimingEventType* event;
Core::System& system;
u64 title_id;
}; };
} // namespace Cheats } // namespace Cheats

View File

@ -472,8 +472,8 @@ std::string GatewayCheat::ToString() const {
return result; return result;
} }
std::vector<std::unique_ptr<CheatBase>> GatewayCheat::LoadFile(const std::string& filepath) { std::vector<std::shared_ptr<CheatBase>> GatewayCheat::LoadFile(const std::string& filepath) {
std::vector<std::unique_ptr<CheatBase>> cheats; std::vector<std::shared_ptr<CheatBase>> cheats;
boost::iostreams::stream<boost::iostreams::file_descriptor_source> file; boost::iostreams::stream<boost::iostreams::file_descriptor_source> file;
FileUtil::OpenFStream<std::ios_base::in>(file, filepath); FileUtil::OpenFStream<std::ios_base::in>(file, filepath);
@ -493,7 +493,7 @@ std::vector<std::unique_ptr<CheatBase>> GatewayCheat::LoadFile(const std::string
line = Common::StripSpaces(line); // remove spaces at front and end line = Common::StripSpaces(line); // remove spaces at front and end
if (line.length() >= 2 && line.front() == '[') { if (line.length() >= 2 && line.front() == '[') {
if (!cheat_lines.empty()) { if (!cheat_lines.empty()) {
cheats.push_back(std::make_unique<GatewayCheat>(name, cheat_lines, comments)); cheats.push_back(std::make_shared<GatewayCheat>(name, cheat_lines, comments));
cheats.back()->SetEnabled(enabled); cheats.back()->SetEnabled(enabled);
enabled = false; enabled = false;
} }
@ -511,7 +511,7 @@ std::vector<std::unique_ptr<CheatBase>> GatewayCheat::LoadFile(const std::string
} }
} }
if (!cheat_lines.empty()) { if (!cheat_lines.empty()) {
cheats.push_back(std::make_unique<GatewayCheat>(name, cheat_lines, comments)); cheats.push_back(std::make_shared<GatewayCheat>(name, cheat_lines, comments));
cheats.back()->SetEnabled(enabled); cheats.back()->SetEnabled(enabled);
} }
return cheats; return cheats;

View File

@ -77,7 +77,7 @@ public:
/// (there might be multiple lines of those hex numbers) /// (there might be multiple lines of those hex numbers)
/// Comment lines start with a '*' /// Comment lines start with a '*'
/// This function will pares the file for such structures /// This function will pares the file for such structures
static std::vector<std::unique_ptr<CheatBase>> LoadFile(const std::string& filepath); static std::vector<std::shared_ptr<CheatBase>> LoadFile(const std::string& filepath);
private: private:
std::atomic<bool> enabled = false; std::atomic<bool> enabled = false;

View File

@ -72,6 +72,8 @@ Core::Timing& Global() {
return System::GetInstance().CoreTiming(); return System::GetInstance().CoreTiming();
} }
System::System() : cheat_engine{*this} {}
System::~System() = default; System::~System() = default;
System::ResultStatus System::RunLoop(bool tight_loop) { System::ResultStatus System::RunLoop(bool tight_loop) {
@ -318,7 +320,10 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
LOG_ERROR(Core, "Failed to find title id for ROM (Error {})", LOG_ERROR(Core, "Failed to find title id for ROM (Error {})",
static_cast<u32>(load_result)); static_cast<u32>(load_result));
} }
cheat_engine = std::make_unique<Cheats::CheatEngine>(title_id, *this);
cheat_engine.LoadCheatFile(title_id);
cheat_engine.Connect();
perf_stats = std::make_unique<PerfStats>(title_id); perf_stats = std::make_unique<PerfStats>(title_id);
if (Settings::values.custom_textures) { if (Settings::values.custom_textures) {
@ -489,11 +494,11 @@ const Memory::MemorySystem& System::Memory() const {
} }
Cheats::CheatEngine& System::CheatEngine() { Cheats::CheatEngine& System::CheatEngine() {
return *cheat_engine; return cheat_engine;
} }
const Cheats::CheatEngine& System::CheatEngine() const { const Cheats::CheatEngine& System::CheatEngine() const {
return *cheat_engine; return cheat_engine;
} }
void System::RegisterVideoDumper(std::shared_ptr<VideoDumper::Backend> dumper) { void System::RegisterVideoDumper(std::shared_ptr<VideoDumper::Backend> dumper) {
@ -540,7 +545,6 @@ void System::Shutdown(bool is_deserializing) {
if (!is_deserializing) { if (!is_deserializing) {
GDBStub::Shutdown(); GDBStub::Shutdown();
perf_stats.reset(); perf_stats.reset();
cheat_engine.reset();
app_loader.reset(); app_loader.reset();
} }
custom_tex_manager.reset(); custom_tex_manager.reset();
@ -712,7 +716,7 @@ void System::serialize(Archive& ar, const unsigned int file_version) {
timing->UnlockEventQueue(); timing->UnlockEventQueue();
Service::GSP::SetGlobalModule(*this); Service::GSP::SetGlobalModule(*this);
memory->SetDSP(*dsp_core); memory->SetDSP(*dsp_core);
cheat_engine->Connect(); cheat_engine.Connect();
VideoCore::g_renderer->Sync(); VideoCore::g_renderer->Sync();
} }
} }

View File

@ -10,6 +10,7 @@
#include <string> #include <string>
#include <boost/serialization/version.hpp> #include <boost/serialization/version.hpp>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/cheats/cheats.h"
#include "core/frontend/applets/mii_selector.h" #include "core/frontend/applets/mii_selector.h"
#include "core/frontend/applets/swkbd.h" #include "core/frontend/applets/swkbd.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
@ -49,10 +50,6 @@ namespace Kernel {
class KernelSystem; class KernelSystem;
} }
namespace Cheats {
class CheatEngine;
}
namespace VideoDumper { namespace VideoDumper {
class Backend; class Backend;
} }
@ -95,6 +92,7 @@ public:
ErrorUnknown ///< Any other error ErrorUnknown ///< Any other error
}; };
System();
~System(); ~System();
/** /**
@ -372,7 +370,7 @@ private:
std::shared_ptr<Frontend::SoftwareKeyboard> registered_swkbd; std::shared_ptr<Frontend::SoftwareKeyboard> registered_swkbd;
/// Cheats manager /// Cheats manager
std::unique_ptr<Cheats::CheatEngine> cheat_engine; Cheats::CheatEngine cheat_engine;
/// Video dumper backend /// Video dumper backend
std::shared_ptr<VideoDumper::Backend> video_dumper; std::shared_ptr<VideoDumper::Backend> video_dumper;

View File

@ -8,8 +8,8 @@
#include "core/frontend/applets/swkbd.h" #include "core/frontend/applets/swkbd.h"
namespace Frontend { namespace Frontend {
void RegisterDefaultApplets(Core::System& system) { void RegisterDefaultApplets() {
system.RegisterSoftwareKeyboard(std::make_shared<DefaultKeyboard>()); Core::System::GetInstance().RegisterSoftwareKeyboard(std::make_shared<DefaultKeyboard>());
system.RegisterMiiSelector(std::make_shared<DefaultMiiSelector>()); Core::System::GetInstance().RegisterMiiSelector(std::make_shared<DefaultMiiSelector>());
} }
} // namespace Frontend } // namespace Frontend

View File

@ -9,5 +9,5 @@ namespace Frontend {
* Registers default, frontend-independent applet implementations. * Registers default, frontend-independent applet implementations.
* Will be replaced later if any frontend-specific implementation is available. * Will be replaced later if any frontend-specific implementation is available.
*/ */
void RegisterDefaultApplets(Core::System& system); void RegisterDefaultApplets();
} // namespace Frontend } // namespace Frontend

View File

@ -74,7 +74,7 @@ void Recorder::SetRequestInfo(const std::shared_ptr<Kernel::Thread>& client_thre
const u32 thread_id = client_thread->GetThreadId(); const u32 thread_id = client_thread->GetThreadId();
if (!record_map.count(thread_id)) { if (!record_map.count(thread_id)) {
// This is possible when the recorder is enabled after application started // This is possible when the recorder is enabled after application started
LOG_ERROR(Kernel, "No request is associated with the thread"); LOG_ERROR(Kernel, "No request is assoicated with the thread");
return; return;
} }
@ -113,7 +113,7 @@ void Recorder::SetReplyInfo(const std::shared_ptr<Kernel::Thread>& client_thread
const u32 thread_id = client_thread->GetThreadId(); const u32 thread_id = client_thread->GetThreadId();
if (!record_map.count(thread_id)) { if (!record_map.count(thread_id)) {
// This is possible when the recorder is enabled after application started // This is possible when the recorder is enabled after application started
LOG_ERROR(Kernel, "No request is associated with the thread"); LOG_ERROR(Kernel, "No request is assoicated with the thread");
return; return;
} }
@ -133,7 +133,7 @@ void Recorder::SetHLEUnimplemented(const std::shared_ptr<Kernel::Thread>& client
const u32 thread_id = client_thread->GetThreadId(); const u32 thread_id = client_thread->GetThreadId();
if (!record_map.count(thread_id)) { if (!record_map.count(thread_id)) {
// This is possible when the recorder is enabled after application started // This is possible when the recorder is enabled after application started
LOG_ERROR(Kernel, "No request is associated with the thread"); LOG_ERROR(Kernel, "No request is assoicated with the thread");
return; return;
} }

View File

@ -87,7 +87,8 @@ Handler::Handler(Core::Timing& timing) : timing(timing) {
shared_page.sliderstate_3d = static_cast<float_le>(slidestate); shared_page.sliderstate_3d = static_cast<float_le>(slidestate);
} }
u64 Handler::GetSystemTimeSince2000() const { /// Gets system time in 3DS format. The epoch is Jan 1900, and the unit is millisecond.
u64 Handler::GetSystemTime() const {
std::chrono::milliseconds now = std::chrono::milliseconds now =
init_time + std::chrono::duration_cast<std::chrono::milliseconds>(timing.GetGlobalTimeUs()); init_time + std::chrono::duration_cast<std::chrono::milliseconds>(timing.GetGlobalTimeUs());
@ -103,25 +104,23 @@ u64 Handler::GetSystemTimeSince2000() const {
epoch_tm.tm_isdst = 0; epoch_tm.tm_isdst = 0;
s64 epoch = std::mktime(&epoch_tm) * 1000; s64 epoch = std::mktime(&epoch_tm) * 1000;
// Only when system time is after 2000, we set it as 3DS system time
if (now.count() > epoch) {
return now.count() - epoch;
} else {
return 0;
}
}
u64 Handler::GetSystemTimeSince1900() const {
// 3DS console time uses Jan 1 1900 as internal epoch, // 3DS console time uses Jan 1 1900 as internal epoch,
// so we use the milliseconds between 1900 and 2000 as base console time // so we use the milliseconds between 1900 and 2000 as base console time
return 3155673600000ULL + GetSystemTimeSince2000(); u64 console_time = 3155673600000ULL;
// Only when system time is after 2000, we set it as 3DS system time
if (now.count() > epoch) {
console_time += (now.count() - epoch);
}
return console_time;
} }
void Handler::UpdateTimeCallback(std::uintptr_t user_data, int cycles_late) { void Handler::UpdateTimeCallback(std::uintptr_t user_data, int cycles_late) {
DateTime& date_time = DateTime& date_time =
shared_page.date_time_counter % 2 ? shared_page.date_time_0 : shared_page.date_time_1; shared_page.date_time_counter % 2 ? shared_page.date_time_0 : shared_page.date_time_1;
date_time.date_time = GetSystemTimeSince1900(); date_time.date_time = GetSystemTime();
date_time.update_tick = timing.GetTicks(); date_time.update_tick = timing.GetTicks();
date_time.tick_to_second_coefficient = BASE_CLOCK_RATE_ARM11; date_time.tick_to_second_coefficient = BASE_CLOCK_RATE_ARM11;
date_time.tick_offset = 0; date_time.tick_offset = 0;

View File

@ -110,13 +110,8 @@ public:
return sizeof(shared_page); return sizeof(shared_page);
} }
/// Gets the system time in milliseconds since the year 2000.
u64 GetSystemTimeSince2000() const;
/// Gets the system time in milliseconds since the year 1900.
u64 GetSystemTimeSince1900() const;
private: private:
u64 GetSystemTime() const;
void UpdateTimeCallback(std::uintptr_t user_data, int cycles_late); void UpdateTimeCallback(std::uintptr_t user_data, int cycles_late);
Core::Timing& timing; Core::Timing& timing;
Core::TimingEventType* update_time_event; Core::TimingEventType* update_time_event;

View File

@ -3,7 +3,6 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "core/core.h" #include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/act/act.h" #include "core/hle/service/act/act.h"
#include "core/hle/service/act/act_a.h" #include "core/hle/service/act/act_a.h"
#include "core/hle/service/act/act_u.h" #include "core/hle/service/act/act_u.h"
@ -15,35 +14,6 @@ Module::Interface::Interface(std::shared_ptr<Module> act, const char* name)
Module::Interface::~Interface() = default; Module::Interface::~Interface() = default;
void Module::Interface::Initialize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x1, 2, 4); // 0x10084
const auto sdk_version = rp.Pop<u32>();
const auto shared_memory_size = rp.Pop<u32>();
const auto caller_pid = rp.PopPID();
[[maybe_unused]] const auto shared_memory = rp.PopObject<Kernel::SharedMemory>();
LOG_DEBUG(Service_ACT,
"(STUBBED) called sdk_version={:08X}, shared_memory_size={:08X}, caller_pid={}",
sdk_version, shared_memory_size, caller_pid);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
}
void Module::Interface::GetAccountDataBlock(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x6, 3, 2); // 0x600C2
const auto unknown = rp.Pop<u8>();
const auto size = rp.Pop<u32>();
const auto block_id = rp.Pop<u32>();
[[maybe_unused]] auto output_buffer = rp.PopMappedBuffer();
LOG_DEBUG(Service_ACT, "(STUBBED) called unknown={:02X}, size={:08X}, block_id={:08X}", unknown,
size, block_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
}
void InstallInterfaces(Core::System& system) { void InstallInterfaces(Core::System& system) {
auto& service_manager = system.ServiceManager(); auto& service_manager = system.ServiceManager();
auto act = std::make_shared<Module>(); auto act = std::make_shared<Module>();

View File

@ -22,33 +22,6 @@ public:
protected: protected:
std::shared_ptr<Module> act; std::shared_ptr<Module> act;
/**
* ACT::Initialize service function.
* Inputs:
* 1 : SDK version
* 2 : Shared Memory Size
* 3 : PID Translation Header (0x20)
* 4 : Caller PID
* 5 : Handle Translation Header (0x0)
* 6 : Shared Memory Handle
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void Initialize(Kernel::HLERequestContext& ctx);
/**
* ACT::GetAccountDataBlock service function.
* Inputs:
* 1 : u8 Unknown
* 2 : Size
* 3 : Block ID
* 4 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
* 5 : Output Buffer Pointer
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void GetAccountDataBlock(Kernel::HLERequestContext& ctx);
}; };
private: private:

View File

@ -11,9 +11,9 @@ ACT_A::ACT_A(std::shared_ptr<Module> act) : Module::Interface(std::move(act), "a
const FunctionInfo functions[] = { const FunctionInfo functions[] = {
// act:u shared commands // act:u shared commands
// clang-format off // clang-format off
{IPC::MakeHeader(0x0001, 2, 4), &ACT_A::Initialize, "Initialize"}, {IPC::MakeHeader(0x0001, 2, 4), nullptr, "Initialize"},
{IPC::MakeHeader(0x0002, 1, 0), nullptr, "GetErrorCode"}, {IPC::MakeHeader(0x0002, 1, 0), nullptr, "GetErrorCode"},
{IPC::MakeHeader(0x0006, 3, 2), &ACT_A::GetAccountDataBlock, "GetAccountDataBlock"}, {IPC::MakeHeader(0x0006, 3, 2), nullptr, "GetAccountDataBlock"},
{IPC::MakeHeader(0x000B, 1, 2), nullptr, "AcquireEulaList"}, {IPC::MakeHeader(0x000B, 1, 2), nullptr, "AcquireEulaList"},
{IPC::MakeHeader(0x000D, 1, 0), nullptr, "GenerateUuid"}, {IPC::MakeHeader(0x000D, 1, 0), nullptr, "GenerateUuid"},
// act:a // act:a

View File

@ -10,9 +10,9 @@ namespace Service::ACT {
ACT_U::ACT_U(std::shared_ptr<Module> act) : Module::Interface(std::move(act), "act:u") { ACT_U::ACT_U(std::shared_ptr<Module> act) : Module::Interface(std::move(act), "act:u") {
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
// clang-format off // clang-format off
{IPC::MakeHeader(0x0001, 2, 4), &ACT_U::Initialize, "Initialize"}, {IPC::MakeHeader(0x0001, 2, 4), nullptr, "Initialize"},
{IPC::MakeHeader(0x0002, 1, 0), nullptr, "GetErrorCode"}, {IPC::MakeHeader(0x0002, 1, 0), nullptr, "GetErrorCode"},
{IPC::MakeHeader(0x0006, 3, 2), &ACT_U::GetAccountDataBlock, "GetAccountDataBlock"}, {IPC::MakeHeader(0x0006, 3, 2), nullptr, "GetAccountDataBlock"},
{IPC::MakeHeader(0x000B, 1, 2), nullptr, "AcquireEulaList"}, {IPC::MakeHeader(0x000B, 1, 2), nullptr, "AcquireEulaList"},
{IPC::MakeHeader(0x000D, 1, 0), nullptr, "GenerateUuid"}, {IPC::MakeHeader(0x000D, 1, 0), nullptr, "GenerateUuid"},
// clang-format on // clang-format on

View File

@ -1121,17 +1121,6 @@ void Module::Interface::GetTicketList(Kernel::HLERequestContext& ctx) {
ticket_list_count, ticket_index); ticket_list_count, ticket_index);
} }
void Module::Interface::NeedsCleanup(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x0013, 1, 0); // 0x00130040
const auto media_type = rp.Pop<u8>();
LOG_DEBUG(Service_AM, "(STUBBED) media_type=0x{:02x}", media_type);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS);
rb.Push<bool>(false);
}
void Module::Interface::QueryAvailableTitleDatabase(Kernel::HLERequestContext& ctx) { void Module::Interface::QueryAvailableTitleDatabase(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x0019, 1, 0); // 0x190040 IPC::RequestParser rp(ctx, 0x0019, 1, 0); // 0x190040
u8 media_type = rp.Pop<u8>(); u8 media_type = rp.Pop<u8>();

View File

@ -357,16 +357,6 @@ public:
*/ */
void GetTicketList(Kernel::HLERequestContext& ctx); void GetTicketList(Kernel::HLERequestContext& ctx);
/**
* AM::NeedsCleanup service function
* Inputs:
* 1 : Media Type
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : bool, Needs Cleanup
*/
void NeedsCleanup(Kernel::HLERequestContext& ctx);
/** /**
* AM::QueryAvailableTitleDatabase service function * AM::QueryAvailableTitleDatabase service function
* Inputs: * Inputs:

View File

@ -28,7 +28,7 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
{IPC::MakeHeader(0x0010, 4, 2), nullptr, "GetImportContentContextList"}, {IPC::MakeHeader(0x0010, 4, 2), nullptr, "GetImportContentContextList"},
{IPC::MakeHeader(0x0011, 4, 4), nullptr, "GetImportContentContexts"}, {IPC::MakeHeader(0x0011, 4, 4), nullptr, "GetImportContentContexts"},
{IPC::MakeHeader(0x0012, 4, 2), nullptr, "DeleteImportContentContexts"}, {IPC::MakeHeader(0x0012, 4, 2), nullptr, "DeleteImportContentContexts"},
{IPC::MakeHeader(0x0013, 1, 0), &AM_NET::NeedsCleanup, "NeedsCleanup"}, {IPC::MakeHeader(0x0013, 1, 0), nullptr, "NeedsCleanup"},
{IPC::MakeHeader(0x0014, 1, 0), nullptr, "DoCleanup"}, {IPC::MakeHeader(0x0014, 1, 0), nullptr, "DoCleanup"},
{IPC::MakeHeader(0x0015, 1, 0), nullptr, "DeleteAllImportContexts"}, {IPC::MakeHeader(0x0015, 1, 0), nullptr, "DeleteAllImportContexts"},
{IPC::MakeHeader(0x0016, 0, 0), nullptr, "DeleteAllTemporaryPrograms"}, {IPC::MakeHeader(0x0016, 0, 0), nullptr, "DeleteAllTemporaryPrograms"},

View File

@ -28,7 +28,7 @@ AM_SYS::AM_SYS(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
{IPC::MakeHeader(0x0010, 4, 2), nullptr, "GetImportContentContextList"}, {IPC::MakeHeader(0x0010, 4, 2), nullptr, "GetImportContentContextList"},
{IPC::MakeHeader(0x0011, 4, 4), nullptr, "GetImportContentContexts"}, {IPC::MakeHeader(0x0011, 4, 4), nullptr, "GetImportContentContexts"},
{IPC::MakeHeader(0x0012, 4, 2), nullptr, "DeleteImportContentContexts"}, {IPC::MakeHeader(0x0012, 4, 2), nullptr, "DeleteImportContentContexts"},
{IPC::MakeHeader(0x0013, 1, 0), &AM_SYS::NeedsCleanup, "NeedsCleanup"}, {IPC::MakeHeader(0x0013, 1, 0), nullptr, "NeedsCleanup"},
{IPC::MakeHeader(0x0014, 1, 0), nullptr, "DoCleanup"}, {IPC::MakeHeader(0x0014, 1, 0), nullptr, "DoCleanup"},
{IPC::MakeHeader(0x0015, 1, 0), nullptr, "DeleteAllImportContexts"}, {IPC::MakeHeader(0x0015, 1, 0), nullptr, "DeleteAllImportContexts"},
{IPC::MakeHeader(0x0016, 0, 0), nullptr, "DeleteAllTemporaryPrograms"}, {IPC::MakeHeader(0x0016, 0, 0), nullptr, "DeleteAllTemporaryPrograms"},

View File

@ -28,7 +28,7 @@ AM_U::AM_U(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:u"
{IPC::MakeHeader(0x0010, 4, 2), nullptr, "GetImportContentContextList"}, {IPC::MakeHeader(0x0010, 4, 2), nullptr, "GetImportContentContextList"},
{IPC::MakeHeader(0x0011, 4, 4), nullptr, "GetImportContentContexts"}, {IPC::MakeHeader(0x0011, 4, 4), nullptr, "GetImportContentContexts"},
{IPC::MakeHeader(0x0012, 4, 2), nullptr, "DeleteImportContentContexts"}, {IPC::MakeHeader(0x0012, 4, 2), nullptr, "DeleteImportContentContexts"},
{IPC::MakeHeader(0x0013, 1, 0), &AM_U::NeedsCleanup, "NeedsCleanup"}, {IPC::MakeHeader(0x0013, 1, 0), nullptr, "NeedsCleanup"},
{IPC::MakeHeader(0x0014, 1, 0), nullptr, "DoCleanup"}, {IPC::MakeHeader(0x0014, 1, 0), nullptr, "DoCleanup"},
{IPC::MakeHeader(0x0015, 1, 0), nullptr, "DeleteAllImportContexts"}, {IPC::MakeHeader(0x0015, 1, 0), nullptr, "DeleteAllImportContexts"},
{IPC::MakeHeader(0x0016, 0, 0), nullptr, "DeleteAllTemporaryPrograms"}, {IPC::MakeHeader(0x0016, 0, 0), nullptr, "DeleteAllTemporaryPrograms"},

View File

@ -271,17 +271,6 @@ void Module::Interface::SecureInfoGetRegion(Kernel::HLERequestContext& ctx, u16
rb.Push<u8>(static_cast<u8>(cfg->GetRegionValue())); rb.Push<u8>(static_cast<u8>(cfg->GetRegionValue()));
} }
void Module::Interface::SecureInfoGetByte101(Kernel::HLERequestContext& ctx, u16 id) {
IPC::RequestParser rp(ctx, id, 0, 0);
LOG_DEBUG(Service_CFG, "(STUBBED) called");
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS);
// According to 3dbrew this is normally 0.
rb.Push<u8>(0);
}
void Module::Interface::GenHashConsoleUnique(Kernel::HLERequestContext& ctx) { void Module::Interface::GenHashConsoleUnique(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x03, 1, 0); IPC::RequestParser rp(ctx, 0x03, 1, 0);
const u32 app_id_salt = rp.Pop<u32>() & 0x000FFFFF; const u32 app_id_salt = rp.Pop<u32>() & 0x000FFFFF;

View File

@ -165,17 +165,6 @@ public:
*/ */
void SecureInfoGetRegion(Kernel::HLERequestContext& ctx, u16 id); void SecureInfoGetRegion(Kernel::HLERequestContext& ctx, u16 id);
/**
* CFG::SecureInfoGetByte101 service function
* Inputs:
* 1 : None
* Outputs:
* 0 : Result Header code
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Value loaded from SecureInfo offset 0x101
*/
void SecureInfoGetByte101(Kernel::HLERequestContext& ctx, u16 id);
/** /**
* CFG::GenHashConsoleUnique service function * CFG::GenHashConsoleUnique service function
* Inputs: * Inputs:

View File

@ -31,7 +31,7 @@ CFG_I::CFG_I(std::shared_ptr<Module> cfg) : Module::Interface(std::move(cfg), "c
{IPC::MakeHeader(0x0404, 1, 2), nullptr, "GetLocalFriendCodeSeedData"}, {IPC::MakeHeader(0x0404, 1, 2), nullptr, "GetLocalFriendCodeSeedData"},
{IPC::MakeHeader(0x0405, 0, 0), nullptr, "GetLocalFriendCodeSeed"}, {IPC::MakeHeader(0x0405, 0, 0), nullptr, "GetLocalFriendCodeSeed"},
{IPC::MakeHeader(0x0406, 0, 0), &CFG_I::D<&CFG_I::SecureInfoGetRegion, 0x0406>, "SecureInfoGetRegion"}, {IPC::MakeHeader(0x0406, 0, 0), &CFG_I::D<&CFG_I::SecureInfoGetRegion, 0x0406>, "SecureInfoGetRegion"},
{IPC::MakeHeader(0x0407, 0, 0), &CFG_I::D<&CFG_I::SecureInfoGetByte101, 0x0407>, "SecureInfoGetByte101"}, {IPC::MakeHeader(0x0407, 0, 0), nullptr, "SecureInfoGetByte101"},
{IPC::MakeHeader(0x0408, 1, 2), nullptr, "SecureInfoGetSerialNo"}, {IPC::MakeHeader(0x0408, 1, 2), nullptr, "SecureInfoGetSerialNo"},
{IPC::MakeHeader(0x0409, 0, 0), nullptr, "UpdateConfigBlk00040003"}, {IPC::MakeHeader(0x0409, 0, 0), nullptr, "UpdateConfigBlk00040003"},
{IPC::MakeHeader(0x0801, 2, 2), &CFG_I::D<&CFG_I::GetConfigInfoBlk8, 0x0801>, "GetConfigInfoBlk8"}, {IPC::MakeHeader(0x0801, 2, 2), &CFG_I::D<&CFG_I::GetConfigInfoBlk8, 0x0801>, "GetConfigInfoBlk8"},
@ -55,7 +55,7 @@ CFG_I::CFG_I(std::shared_ptr<Module> cfg) : Module::Interface(std::move(cfg), "c
{IPC::MakeHeader(0x0814, 1, 2), nullptr, "SecureInfoGetData"}, {IPC::MakeHeader(0x0814, 1, 2), nullptr, "SecureInfoGetData"},
{IPC::MakeHeader(0x0815, 1, 2), nullptr, "SecureInfoGetSignature"}, {IPC::MakeHeader(0x0815, 1, 2), nullptr, "SecureInfoGetSignature"},
{IPC::MakeHeader(0x0816, 0, 0), &CFG_I::D<&CFG_I::SecureInfoGetRegion, 0x0816>, "SecureInfoGetRegion"}, {IPC::MakeHeader(0x0816, 0, 0), &CFG_I::D<&CFG_I::SecureInfoGetRegion, 0x0816>, "SecureInfoGetRegion"},
{IPC::MakeHeader(0x0817, 0, 0), &CFG_I::D<&CFG_I::SecureInfoGetByte101, 0x0817>, "SecureInfoGetByte101"}, {IPC::MakeHeader(0x0817, 0, 0), nullptr, "SecureInfoGetByte101"},
{IPC::MakeHeader(0x0818, 1, 2), nullptr, "SecureInfoGetSerialNo"}, {IPC::MakeHeader(0x0818, 1, 2), nullptr, "SecureInfoGetSerialNo"},
// clang-format on // clang-format on
}; };

View File

@ -31,7 +31,7 @@ CFG_S::CFG_S(std::shared_ptr<Module> cfg) : Module::Interface(std::move(cfg), "c
{IPC::MakeHeader(0x0404, 1, 2), nullptr, "GetLocalFriendCodeSeedData"}, {IPC::MakeHeader(0x0404, 1, 2), nullptr, "GetLocalFriendCodeSeedData"},
{IPC::MakeHeader(0x0405, 0, 0), nullptr, "GetLocalFriendCodeSeed"}, {IPC::MakeHeader(0x0405, 0, 0), nullptr, "GetLocalFriendCodeSeed"},
{IPC::MakeHeader(0x0406, 0, 0), &CFG_S::D<&CFG_S::SecureInfoGetRegion, 0x0406>, "SecureInfoGetRegion"}, {IPC::MakeHeader(0x0406, 0, 0), &CFG_S::D<&CFG_S::SecureInfoGetRegion, 0x0406>, "SecureInfoGetRegion"},
{IPC::MakeHeader(0x0407, 0, 0), &CFG_S::D<&CFG_S::SecureInfoGetByte101, 0x0407>, "SecureInfoGetByte101"}, {IPC::MakeHeader(0x0407, 0, 0), nullptr, "SecureInfoGetByte101"},
{IPC::MakeHeader(0x0408, 1, 2), nullptr, "SecureInfoGetSerialNo"}, {IPC::MakeHeader(0x0408, 1, 2), nullptr, "SecureInfoGetSerialNo"},
{IPC::MakeHeader(0x0409, 0, 0), nullptr, "UpdateConfigBlk00040003"}, {IPC::MakeHeader(0x0409, 0, 0), nullptr, "UpdateConfigBlk00040003"},
// clang-format on // clang-format on

View File

@ -80,29 +80,23 @@ struct State {
void WriteSamples(std::span<const u8> samples) { void WriteSamples(std::span<const u8> samples) {
u32 bytes_total_written = 0; u32 bytes_total_written = 0;
auto sample_buffer = sharedmem_buffer + initial_offset; const std::size_t remaining_space = size - offset;
// Do not let sampling buffer overrun shared memory space. std::size_t bytes_to_write = std::min(samples.size(), remaining_space);
const auto sample_buffer_size =
std::min(size, sharedmem_size - initial_offset - sizeof(u32));
// Write samples in a loop until the input runs out // Write as many samples as we can to the buffer.
while (samples.size() > bytes_total_written) { // TODO if the sample size is 16bit, this could theoretically cut a sample in the case where
// TODO: If the sample size is 16-bit, this could theoretically cut a sample in the case // the application configures an odd size
// where the application configures an odd size. std::memcpy(sharedmem_buffer + offset, samples.data(), bytes_to_write);
std::size_t bytes_to_write = offset += static_cast<u32>(bytes_to_write);
std::min(samples.size() - bytes_total_written, sample_buffer_size - offset); bytes_total_written += static_cast<u32>(bytes_to_write);
std::memcpy(sample_buffer + offset, samples.data() + bytes_total_written,
// If theres any samples left to write after we looped, go ahead and write them now
if (looped_buffer && samples.size() > bytes_total_written) {
offset = initial_offset;
bytes_to_write = std::min(samples.size() - bytes_total_written, size);
std::memcpy(sharedmem_buffer + offset, samples.data() + bytes_total_written,
bytes_to_write); bytes_to_write);
offset += static_cast<u32>(bytes_to_write); offset += static_cast<u32>(bytes_to_write);
bytes_total_written += static_cast<u32>(bytes_to_write);
if (offset >= sample_buffer_size && looped_buffer) {
offset = 0;
}
if (!looped_buffer) {
break;
}
} }
// The last 4 bytes of the shared memory contains the latest offset // The last 4 bytes of the shared memory contains the latest offset
@ -211,8 +205,7 @@ struct MIC_U::Impl {
} }
u8 sample_size = encoding == Encoding::PCM8Signed || encoding == Encoding::PCM8 ? 8 : 16; u8 sample_size = encoding == Encoding::PCM8Signed || encoding == Encoding::PCM8 ? 8 : 16;
state.offset = 0; state.offset = state.initial_offset = audio_buffer_offset;
state.initial_offset = audio_buffer_offset;
state.sample_rate = sample_rate; state.sample_rate = sample_rate;
state.sample_size = sample_size; state.sample_size = sample_size;
state.looped_buffer = audio_buffer_loop; state.looped_buffer = audio_buffer_loop;

View File

@ -10,13 +10,6 @@ SERIALIZE_EXPORT_IMPL(Service::NEWS::NEWS_S)
namespace Service::NEWS { namespace Service::NEWS {
struct NewsDbHeader {
u8 unknown_one;
u8 flags;
INSERT_PADDING_BYTES(0xE);
};
static_assert(sizeof(NewsDbHeader) == 0x10, "News DB Header structure size is wrong");
void NEWS_S::GetTotalNotifications(Kernel::HLERequestContext& ctx) { void NEWS_S::GetTotalNotifications(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x5, 0, 0); IPC::RequestParser rp(ctx, 0x5, 0, 0);
@ -28,22 +21,6 @@ void NEWS_S::GetTotalNotifications(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(0); rb.Push<u32>(0);
} }
void NEWS_S::GetNewsDBHeader(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0xA, 1, 2);
const auto size = rp.Pop<u32>();
auto output_buffer = rp.PopMappedBuffer();
LOG_WARNING(Service, "(STUBBED) called size={}", size);
NewsDbHeader dummy = {.unknown_one = 1, .flags = 0};
output_buffer.Write(&dummy, 0, std::min(sizeof(NewsDbHeader), static_cast<std::size_t>(size)));
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(size);
}
NEWS_S::NEWS_S() : ServiceFramework("news:s", 2) { NEWS_S::NEWS_S() : ServiceFramework("news:s", 2) {
const FunctionInfo functions[] = { const FunctionInfo functions[] = {
// clang-format off // clang-format off
@ -53,7 +30,7 @@ NEWS_S::NEWS_S() : ServiceFramework("news:s", 2) {
{IPC::MakeHeader(0x0007, 2, 2), nullptr, "SetNotificationHeader"}, {IPC::MakeHeader(0x0007, 2, 2), nullptr, "SetNotificationHeader"},
{IPC::MakeHeader(0x0008, 2, 2), nullptr, "SetNotificationMessage"}, {IPC::MakeHeader(0x0008, 2, 2), nullptr, "SetNotificationMessage"},
{IPC::MakeHeader(0x0009, 2, 2), nullptr, "SetNotificationImage"}, {IPC::MakeHeader(0x0009, 2, 2), nullptr, "SetNotificationImage"},
{IPC::MakeHeader(0x000A, 1, 2), &NEWS_S::GetNewsDBHeader, "GetNewsDBHeader"}, {IPC::MakeHeader(0x000A, 1, 2), nullptr, "GetNewsDBHeader"},
{IPC::MakeHeader(0x000B, 2, 2), nullptr, "GetNotificationHeader"}, {IPC::MakeHeader(0x000B, 2, 2), nullptr, "GetNotificationHeader"},
{IPC::MakeHeader(0x000C, 2, 2), nullptr, "GetNotificationMessage"}, {IPC::MakeHeader(0x000C, 2, 2), nullptr, "GetNotificationMessage"},
{IPC::MakeHeader(0x000D, 2, 2), nullptr, "GetNotificationImage"}, {IPC::MakeHeader(0x000D, 2, 2), nullptr, "GetNotificationImage"},

View File

@ -25,20 +25,6 @@ private:
*/ */
void GetTotalNotifications(Kernel::HLERequestContext& ctx); void GetTotalNotifications(Kernel::HLERequestContext& ctx);
/**
* GetNewsDBHeader service function.
* Inputs:
* 0 : 0x000A0042
* 1 : Size
* 2 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
* 3 : Output Buffer Pointer
* Outputs:
* 0 : 0x000A0080
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Actual Size
*/
void GetNewsDBHeader(Kernel::HLERequestContext& ctx);
SERVICE_SERIALIZATION_SIMPLE SERVICE_SERIALIZATION_SIMPLE
}; };

View File

@ -218,10 +218,7 @@ ResultCode NfcDevice::StartDetection(TagProtocol allowed_protocol) {
return ResultInvalidOperation; return ResultInvalidOperation;
} }
// Ensure external device is active // TODO: Set console in search mode here
if (communication_state == CommunicationState::Idle) {
StartCommunication();
}
device_state = DeviceState::SearchingForTag; device_state = DeviceState::SearchingForTag;
allowed_protocols = allowed_protocol; allowed_protocols = allowed_protocol;

View File

@ -11,7 +11,6 @@
#include "core/file_sys/archive_extsavedata.h" #include "core/file_sys/archive_extsavedata.h"
#include "core/file_sys/errors.h" #include "core/file_sys/errors.h"
#include "core/file_sys/file_backend.h" #include "core/file_sys/file_backend.h"
#include "core/hle/kernel/shared_page.h"
#include "core/hle/service/ptm/ptm.h" #include "core/hle/service/ptm/ptm.h"
#include "core/hle/service/ptm/ptm_gets.h" #include "core/hle/service/ptm/ptm_gets.h"
#include "core/hle/service/ptm/ptm_play.h" #include "core/hle/service/ptm/ptm_play.h"
@ -133,17 +132,6 @@ void Module::Interface::CheckNew3DS(Kernel::HLERequestContext& ctx) {
Service::PTM::CheckNew3DS(rb); Service::PTM::CheckNew3DS(rb);
} }
void Module::Interface::GetSystemTime(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x401, 0, 0);
auto& share_page = Core::System::GetInstance().Kernel().GetSharedPageHandler();
const u64 console_time = share_page.GetSystemTimeSince2000();
IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
rb.Push(RESULT_SUCCESS);
rb.Push(console_time);
}
static void WriteGameCoinData(GameCoin gamecoin_data) { static void WriteGameCoinData(GameCoin gamecoin_data) {
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
FileSys::ArchiveFactory_ExtSaveData extdata_archive_factory(nand_directory, true); FileSys::ArchiveFactory_ExtSaveData extdata_archive_factory(nand_directory, true);

View File

@ -137,14 +137,6 @@ public:
*/ */
void CheckNew3DS(Kernel::HLERequestContext& ctx); void CheckNew3DS(Kernel::HLERequestContext& ctx);
/**
* PTM::GetSystemTime service function
* Outputs:
* 1: Result code, 0 on success, otherwise error code
* 2-3: Time since 01/01/2020.
*/
void GetSystemTime(Kernel::HLERequestContext& ctx);
protected: protected:
std::shared_ptr<Module> ptm; std::shared_ptr<Module> ptm;
}; };

View File

@ -30,7 +30,7 @@ PTM_Gets::PTM_Gets(std::shared_ptr<Module> ptm)
{IPC::MakeHeader(0x000E, 0, 0), nullptr, "GetPedometerRecordingMode"}, {IPC::MakeHeader(0x000E, 0, 0), nullptr, "GetPedometerRecordingMode"},
{IPC::MakeHeader(0x000F, 2, 4), nullptr, "GetStepHistoryAll"}, {IPC::MakeHeader(0x000F, 2, 4), nullptr, "GetStepHistoryAll"},
// ptm:gets // ptm:gets
{IPC::MakeHeader(0x0401, 0, 0), &PTM_Gets::GetSystemTime, "GetSystemTime"}, {IPC::MakeHeader(0x0401, 0, 0), nullptr, "GetSystemTime"},
// clang-format on // clang-format on
}; };
RegisterHandlers(functions); RegisterHandlers(functions);

View File

@ -643,7 +643,7 @@ std::string MemorySystem::ReadCString(VAddr vaddr, std::size_t max_length) {
return string; return string;
} }
u8* MemorySystem::GetPhysicalPointer(PAddr address) const { u8* MemorySystem::GetPhysicalPointer(PAddr address) {
return GetPhysicalRef(address); return GetPhysicalRef(address);
} }

View File

@ -576,7 +576,7 @@ public:
void RasterizerMarkRegionCached(PAddr start, u32 size, bool cached); void RasterizerMarkRegionCached(PAddr start, u32 size, bool cached);
/// Gets a pointer to the memory region beginning at the specified physical address. /// Gets a pointer to the memory region beginning at the specified physical address.
u8* GetPhysicalPointer(PAddr address) const; u8* GetPhysicalPointer(PAddr address);
/// Returns a reference to the memory region beginning at the specified physical address /// Returns a reference to the memory region beginning at the specified physical address
MemoryRef GetPhysicalRef(PAddr address) const; MemoryRef GetPhysicalRef(PAddr address) const;

View File

@ -95,8 +95,6 @@ add_library(video_core STATIC
renderer_software/sw_proctex.h renderer_software/sw_proctex.h
renderer_software/sw_rasterizer.cpp renderer_software/sw_rasterizer.cpp
renderer_software/sw_rasterizer.h renderer_software/sw_rasterizer.h
renderer_software/sw_tev_jit.cpp
renderer_software/sw_tev_jit.h
renderer_software/sw_texturing.cpp renderer_software/sw_texturing.cpp
renderer_software/sw_texturing.h renderer_software/sw_texturing.h
renderer_vulkan/pica_to_vk.h renderer_vulkan/pica_to_vk.h

View File

@ -8,7 +8,6 @@
#include "core/hw/hw.h" #include "core/hw/hw.h"
#include "core/hw/lcd.h" #include "core/hw/lcd.h"
#include "video_core/renderer_software/renderer_software.h" #include "video_core/renderer_software/renderer_software.h"
#include "video_core/renderer_software/sw_rasterizer.h"
namespace SwRenderer { namespace SwRenderer {
@ -18,10 +17,6 @@ RendererSoftware::RendererSoftware(Core::System& system, Frontend::EmuWindow& wi
RendererSoftware::~RendererSoftware() = default; RendererSoftware::~RendererSoftware() = default;
VideoCore::RasterizerInterface* RendererSoftware::Rasterizer() const {
return rasterizer.get();
}
void RendererSoftware::SwapBuffers() { void RendererSoftware::SwapBuffers() {
PrepareRenderTarget(); PrepareRenderTarget();
EndFrame(); EndFrame();

View File

@ -5,6 +5,7 @@
#pragma once #pragma once
#include "video_core/renderer_base.h" #include "video_core/renderer_base.h"
#include "video_core/renderer_software/sw_rasterizer.h"
namespace Core { namespace Core {
class System; class System;
@ -18,18 +19,19 @@ struct ScreenInfo {
std::vector<u8> pixels; std::vector<u8> pixels;
}; };
class RasterizerSoftware;
class RendererSoftware : public VideoCore::RendererBase { class RendererSoftware : public VideoCore::RendererBase {
public: public:
explicit RendererSoftware(Core::System& system, Frontend::EmuWindow& window); explicit RendererSoftware(Core::System& system, Frontend::EmuWindow& window);
~RendererSoftware() override; ~RendererSoftware() override;
[[nodiscard]] VideoCore::RasterizerInterface* Rasterizer() const override {
return rasterizer.get();
}
[[nodiscard]] const ScreenInfo& Screen(VideoCore::ScreenId id) const noexcept { [[nodiscard]] const ScreenInfo& Screen(VideoCore::ScreenId id) const noexcept {
return screen_infos[static_cast<u32>(id)]; return screen_infos[static_cast<u32>(id)];
} }
VideoCore::RasterizerInterface* Rasterizer() const override;
void SwapBuffers() override; void SwapBuffers() override;
void TryPresent(int timeout_ms, bool is_secondary) override {} void TryPresent(int timeout_ms, bool is_secondary) override {}
void Sync() override {} void Sync() override {}

View File

@ -41,22 +41,10 @@ Framebuffer::Framebuffer(Memory::MemorySystem& memory_, const Pica::FramebufferR
Framebuffer::~Framebuffer() = default; Framebuffer::~Framebuffer() = default;
void Framebuffer::Bind() { void Framebuffer::DrawPixel(int x, int y, const Common::Vec4<u8>& color) const {
PAddr addr = regs.framebuffer.GetColorBufferPhysicalAddress();
if (color_addr != addr) [[unlikely]] {
color_addr = addr;
color_buffer = memory.GetPhysicalPointer(color_addr);
}
addr = regs.framebuffer.GetDepthBufferPhysicalAddress();
if (depth_addr != addr) [[unlikely]] {
depth_addr = addr;
depth_buffer = memory.GetPhysicalPointer(depth_addr);
}
}
void Framebuffer::DrawPixel(u32 x, u32 y, const Common::Vec4<u8>& color) const {
const auto& framebuffer = regs.framebuffer; const auto& framebuffer = regs.framebuffer;
const PAddr addr = framebuffer.GetColorBufferPhysicalAddress();
// Similarly to textures, the render framebuffer is laid out from bottom to top, too. // Similarly to textures, the render framebuffer is laid out from bottom to top, too.
// NOTE: The framebuffer height register contains the actual FB height minus one. // NOTE: The framebuffer height register contains the actual FB height minus one.
y = framebuffer.height - y; y = framebuffer.height - y;
@ -66,7 +54,8 @@ void Framebuffer::DrawPixel(u32 x, u32 y, const Common::Vec4<u8>& color) const {
GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(framebuffer.color_format.Value())); GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(framebuffer.color_format.Value()));
const u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + const u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) +
coarse_y * framebuffer.width * bytes_per_pixel; coarse_y * framebuffer.width * bytes_per_pixel;
u8* dst_pixel = color_buffer + dst_offset; u8* depth_buffer = memory.GetPhysicalPointer(addr);
u8* dst_pixel = depth_buffer + dst_offset;
switch (framebuffer.color_format) { switch (framebuffer.color_format) {
case FramebufferRegs::ColorFormat::RGBA8: case FramebufferRegs::ColorFormat::RGBA8:
@ -91,8 +80,10 @@ void Framebuffer::DrawPixel(u32 x, u32 y, const Common::Vec4<u8>& color) const {
} }
} }
const Common::Vec4<u8> Framebuffer::GetPixel(u32 x, u32 y) const { const Common::Vec4<u8> Framebuffer::GetPixel(int x, int y) const {
const auto& framebuffer = regs.framebuffer; const auto& framebuffer = regs.framebuffer;
const PAddr addr = framebuffer.GetColorBufferPhysicalAddress();
y = framebuffer.height - y; y = framebuffer.height - y;
const u32 coarse_y = y & ~7; const u32 coarse_y = y & ~7;
@ -100,6 +91,7 @@ const Common::Vec4<u8> Framebuffer::GetPixel(u32 x, u32 y) const {
GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(framebuffer.color_format.Value())); GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(framebuffer.color_format.Value()));
const u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + const u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) +
coarse_y * framebuffer.width * bytes_per_pixel; coarse_y * framebuffer.width * bytes_per_pixel;
const u8* color_buffer = memory.GetPhysicalPointer(addr);
const u8* src_pixel = color_buffer + src_offset; const u8* src_pixel = color_buffer + src_offset;
switch (framebuffer.color_format) { switch (framebuffer.color_format) {
@ -122,8 +114,10 @@ const Common::Vec4<u8> Framebuffer::GetPixel(u32 x, u32 y) const {
return {0, 0, 0, 0}; return {0, 0, 0, 0};
} }
u32 Framebuffer::GetDepth(u32 x, u32 y) const { u32 Framebuffer::GetDepth(int x, int y) const {
const auto& framebuffer = regs.framebuffer; const auto& framebuffer = regs.framebuffer;
const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress();
y = framebuffer.height - y; y = framebuffer.height - y;
const u32 coarse_y = y & ~7; const u32 coarse_y = y & ~7;
@ -131,6 +125,7 @@ u32 Framebuffer::GetDepth(u32 x, u32 y) const {
const u32 stride = framebuffer.width * bytes_per_pixel; const u32 stride = framebuffer.width * bytes_per_pixel;
const u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; const u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
const u8* depth_buffer = memory.GetPhysicalPointer(addr);
const u8* src_pixel = depth_buffer + src_offset; const u8* src_pixel = depth_buffer + src_offset;
switch (framebuffer.depth_format) { switch (framebuffer.depth_format) {
@ -148,8 +143,10 @@ u32 Framebuffer::GetDepth(u32 x, u32 y) const {
} }
} }
u8 Framebuffer::GetStencil(u32 x, u32 y) const { u8 Framebuffer::GetStencil(int x, int y) const {
const auto& framebuffer = regs.framebuffer; const auto& framebuffer = regs.framebuffer;
const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress();
y = framebuffer.height - y; y = framebuffer.height - y;
const u32 coarse_y = y & ~7; const u32 coarse_y = y & ~7;
@ -157,6 +154,7 @@ u8 Framebuffer::GetStencil(u32 x, u32 y) const {
const u32 stride = framebuffer.width * bytes_per_pixel; const u32 stride = framebuffer.width * bytes_per_pixel;
const u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; const u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
const u8* depth_buffer = memory.GetPhysicalPointer(addr);
const u8* src_pixel = depth_buffer + src_offset; const u8* src_pixel = depth_buffer + src_offset;
switch (framebuffer.depth_format) { switch (framebuffer.depth_format) {
@ -171,8 +169,10 @@ u8 Framebuffer::GetStencil(u32 x, u32 y) const {
} }
} }
void Framebuffer::SetDepth(u32 x, u32 y, u32 value) const { void Framebuffer::SetDepth(int x, int y, u32 value) const {
const auto& framebuffer = regs.framebuffer; const auto& framebuffer = regs.framebuffer;
const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress();
y = framebuffer.height - y; y = framebuffer.height - y;
const u32 coarse_y = y & ~7; const u32 coarse_y = y & ~7;
@ -180,6 +180,7 @@ void Framebuffer::SetDepth(u32 x, u32 y, u32 value) const {
const u32 stride = framebuffer.width * bytes_per_pixel; const u32 stride = framebuffer.width * bytes_per_pixel;
const u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; const u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
u8* depth_buffer = memory.GetPhysicalPointer(addr);
u8* dst_pixel = depth_buffer + dst_offset; u8* dst_pixel = depth_buffer + dst_offset;
switch (framebuffer.depth_format) { switch (framebuffer.depth_format) {
@ -200,8 +201,10 @@ void Framebuffer::SetDepth(u32 x, u32 y, u32 value) const {
} }
} }
void Framebuffer::SetStencil(u32 x, u32 y, u8 value) const { void Framebuffer::SetStencil(int x, int y, u8 value) const {
const auto& framebuffer = regs.framebuffer; const auto& framebuffer = regs.framebuffer;
const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress();
y = framebuffer.height - y; y = framebuffer.height - y;
const u32 coarse_y = y & ~7; const u32 coarse_y = y & ~7;
@ -209,6 +212,7 @@ void Framebuffer::SetStencil(u32 x, u32 y, u8 value) const {
const u32 stride = framebuffer.width * bytes_per_pixel; const u32 stride = framebuffer.width * bytes_per_pixel;
const u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; const u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
u8* depth_buffer = memory.GetPhysicalPointer(addr);
u8* dst_pixel = depth_buffer + dst_offset; u8* dst_pixel = depth_buffer + dst_offset;
switch (framebuffer.depth_format) { switch (framebuffer.depth_format) {
@ -227,7 +231,7 @@ void Framebuffer::SetStencil(u32 x, u32 y, u8 value) const {
} }
} }
void Framebuffer::DrawShadowMapPixel(u32 x, u32 y, u32 depth, u8 stencil) const { void Framebuffer::DrawShadowMapPixel(int x, int y, u32 depth, u8 stencil) const {
const auto& framebuffer = regs.framebuffer; const auto& framebuffer = regs.framebuffer;
const auto& shadow = regs.shadow; const auto& shadow = regs.shadow;
const PAddr addr = framebuffer.GetColorBufferPhysicalAddress(); const PAddr addr = framebuffer.GetColorBufferPhysicalAddress();

View File

@ -23,37 +23,30 @@ public:
explicit Framebuffer(Memory::MemorySystem& memory, const Pica::FramebufferRegs& framebuffer); explicit Framebuffer(Memory::MemorySystem& memory, const Pica::FramebufferRegs& framebuffer);
~Framebuffer(); ~Framebuffer();
/// Updates the framebuffer addresses from the PICA registers.
void Bind();
/// Draws a pixel at the specified coordinates. /// Draws a pixel at the specified coordinates.
void DrawPixel(u32 x, u32 y, const Common::Vec4<u8>& color) const; void DrawPixel(int x, int y, const Common::Vec4<u8>& color) const;
/// Returns the current color at the specified coordinates. /// Returns the current color at the specified coordinates.
[[nodiscard]] const Common::Vec4<u8> GetPixel(u32 x, u32 y) const; [[nodiscard]] const Common::Vec4<u8> GetPixel(int x, int y) const;
/// Returns the depth value at the specified coordinates. /// Returns the depth value at the specified coordinates.
[[nodiscard]] u32 GetDepth(u32 x, u32 y) const; [[nodiscard]] u32 GetDepth(int x, int y) const;
/// Returns the stencil value at the specified coordinates. /// Returns the stencil value at the specified coordinates.
[[nodiscard]] u8 GetStencil(u32 x, u32 y) const; [[nodiscard]] u8 GetStencil(int x, int y) const;
/// Stores the provided depth value at the specified coordinates. /// Stores the provided depth value at the specified coordinates.
void SetDepth(u32 x, u32 y, u32 value) const; void SetDepth(int x, int y, u32 value) const;
/// Stores the provided stencil value at the specified coordinates. /// Stores the provided stencil value at the specified coordinates.
void SetStencil(u32 x, u32 y, u8 value) const; void SetStencil(int x, int y, u8 value) const;
/// Draws a pixel to the shadow buffer. /// Draws a pixel to the shadow buffer.
void DrawShadowMapPixel(u32 x, u32 y, u32 depth, u8 stencil) const; void DrawShadowMapPixel(int x, int y, u32 depth, u8 stencil) const;
private: private:
Memory::MemorySystem& memory; Memory::MemorySystem& memory;
const Pica::FramebufferRegs& regs; const Pica::FramebufferRegs& regs;
PAddr color_addr;
u8* color_buffer{};
PAddr depth_addr;
u8* depth_buffer{};
}; };
u8 PerformStencilAction(Pica::FramebufferRegs::StencilAction action, u8 old_stencil, u8 ref); u8 PerformStencilAction(Pica::FramebufferRegs::StencilAction action, u8 old_stencil, u8 ref);

View File

@ -95,14 +95,8 @@ private:
} // Anonymous namespace } // Anonymous namespace
// Kirby Blowout Blast relies on the combiner output of a previous draw
// in order to render the sky correctly.
static thread_local Common::Vec4<u8> combiner_output{};
RasterizerSoftware::RasterizerSoftware(Memory::MemorySystem& memory_) RasterizerSoftware::RasterizerSoftware(Memory::MemorySystem& memory_)
: memory{memory_}, state{Pica::g_state}, regs{state.regs}, : memory{memory_}, state{Pica::g_state}, regs{state.regs}, fb{memory, regs.framebuffer} {}
num_sw_threads{std::max(std::thread::hardware_concurrency(), 2U)},
sw_workers{num_sw_threads, "SwRenderer workers"}, fb{memory, regs.framebuffer} {}
void RasterizerSoftware::AddTriangle(const Pica::Shader::OutputVertex& v0, void RasterizerSoftware::AddTriangle(const Pica::Shader::OutputVertex& v0,
const Pica::Shader::OutputVertex& v1, const Pica::Shader::OutputVertex& v1,
@ -295,194 +289,167 @@ void RasterizerSoftware::ProcessTriangle(const Vertex& v0, const Vertex& v1, con
const auto w_inverse = Common::MakeVec(v0.pos.w, v1.pos.w, v2.pos.w); const auto w_inverse = Common::MakeVec(v0.pos.w, v1.pos.w, v2.pos.w);
const auto textures = regs.texturing.GetTextures(); auto textures = regs.texturing.GetTextures();
const auto tev_stages = regs.texturing.GetTevStages(); const auto tev_stages = regs.texturing.GetTevStages();
for (u32 i = 0; i < texture_data.size(); i++) {
const PAddr addr = textures[i].config.GetPhysicalAddress();
if (addr) {
texture_data[i] = memory.GetPhysicalPointer(addr);
}
}
fb.Bind();
if (use_jit) {
const TevConfigKey key{regs.texturing};
auto [it, new_fun] = tev_cache.try_emplace(key.Hash());
if (new_fun) {
it->second = std::make_unique<TevConfig>(regs, key);
}
tev_config = it->second.get();
}
// Enter rasterization loop, starting at the center of the topleft bounding box corner. // Enter rasterization loop, starting at the center of the topleft bounding box corner.
// TODO: Not sure if looping through x first might be faster // TODO: Not sure if looping through x first might be faster
for (u16 y = min_y + 8; y < max_y; y += 0x10) { for (u16 y = min_y + 8; y < max_y; y += 0x10) {
const auto process_scanline = [&, y] { for (u16 x = min_x + 8; x < max_x; x += 0x10) {
for (u16 x = min_x + 8; x < max_x; x += 0x10) { // Do not process the pixel if it's inside the scissor box and the scissor mode is set
// Do not process the pixel if it's inside the scissor box and the scissor mode is // to Exclude.
// set to Exclude. if (regs.rasterizer.scissor_test.mode == RasterizerRegs::ScissorMode::Exclude) {
if (regs.rasterizer.scissor_test.mode == RasterizerRegs::ScissorMode::Exclude) { if (x >= scissor_x1 && x < scissor_x2 && y >= scissor_y1 && y < scissor_y2) {
if (x >= scissor_x1 && x < scissor_x2 && y >= scissor_y1 && y < scissor_y2) {
continue;
}
}
// Calculate the barycentric coordinates w0, w1 and w2
const s32 w0 = bias0 + SignedArea(vtxpos[1].xy(), vtxpos[2].xy(), {x, y});
const s32 w1 = bias1 + SignedArea(vtxpos[2].xy(), vtxpos[0].xy(), {x, y});
const s32 w2 = bias2 + SignedArea(vtxpos[0].xy(), vtxpos[1].xy(), {x, y});
const s32 wsum = w0 + w1 + w2;
// If current pixel is not covered by the current primitive
if (w0 < 0 || w1 < 0 || w2 < 0) {
continue; continue;
} }
const auto baricentric_coordinates = Common::MakeVec(
f24::FromFloat32(static_cast<f32>(w0)), f24::FromFloat32(static_cast<f32>(w1)),
f24::FromFloat32(static_cast<f32>(w2)));
const f24 interpolated_w_inverse =
f24::One() / Common::Dot(w_inverse, baricentric_coordinates);
// interpolated_z = z / w
const float interpolated_z_over_w =
(v0.screenpos[2].ToFloat32() * w0 + v1.screenpos[2].ToFloat32() * w1 +
v2.screenpos[2].ToFloat32() * w2) /
wsum;
// Not fully accurate. About 3 bits in precision are missing.
// Z-Buffer (z / w * scale + offset)
const float depth_scale =
f24::FromRaw(regs.rasterizer.viewport_depth_range).ToFloat32();
const float depth_offset =
f24::FromRaw(regs.rasterizer.viewport_depth_near_plane).ToFloat32();
float depth = interpolated_z_over_w * depth_scale + depth_offset;
// Potentially switch to W-Buffer
if (regs.rasterizer.depthmap_enable ==
Pica::RasterizerRegs::DepthBuffering::WBuffering) {
// W-Buffer (z * scale + w * offset = (z / w * scale + offset) * w)
depth *= interpolated_w_inverse.ToFloat32() * wsum;
}
// Clamp the result
depth = std::clamp(depth, 0.0f, 1.0f);
/**
* Perspective correct attribute interpolation:
* Attribute values cannot be calculated by simple linear interpolation since
* they are not linear in screen space. For example, when interpolating a
* texture coordinate across two vertices, something simple like
* u = (u0*w0 + u1*w1)/(w0+w1)
* will not work. However, the attribute value divided by the
* clipspace w-coordinate (u/w) and and the inverse w-coordinate (1/w) are linear
* in screenspace. Hence, we can linearly interpolate these two independently and
* calculate the interpolated attribute by dividing the results.
* I.e.
* u_over_w = ((u0/v0.pos.w)*w0 + (u1/v1.pos.w)*w1)/(w0+w1)
* one_over_w = (( 1/v0.pos.w)*w0 + ( 1/v1.pos.w)*w1)/(w0+w1)
* u = u_over_w / one_over_w
*
* The generalization to three vertices is straightforward in baricentric
*coordinates.
**/
const auto get_interpolated_attribute = [&](f24 attr0, f24 attr1, f24 attr2) {
auto attr_over_w = Common::MakeVec(attr0, attr1, attr2);
f24 interpolated_attr_over_w =
Common::Dot(attr_over_w, baricentric_coordinates);
return interpolated_attr_over_w * interpolated_w_inverse;
};
const Common::Vec4<u8> primary_color{
static_cast<u8>(
round(get_interpolated_attribute(v0.color.r(), v1.color.r(), v2.color.r())
.ToFloat32() *
255)),
static_cast<u8>(
round(get_interpolated_attribute(v0.color.g(), v1.color.g(), v2.color.g())
.ToFloat32() *
255)),
static_cast<u8>(
round(get_interpolated_attribute(v0.color.b(), v1.color.b(), v2.color.b())
.ToFloat32() *
255)),
static_cast<u8>(
round(get_interpolated_attribute(v0.color.a(), v1.color.a(), v2.color.a())
.ToFloat32() *
255)),
};
std::array<Common::Vec2<f24>, 3> uv;
uv[0].u() = get_interpolated_attribute(v0.tc0.u(), v1.tc0.u(), v2.tc0.u());
uv[0].v() = get_interpolated_attribute(v0.tc0.v(), v1.tc0.v(), v2.tc0.v());
uv[1].u() = get_interpolated_attribute(v0.tc1.u(), v1.tc1.u(), v2.tc1.u());
uv[1].v() = get_interpolated_attribute(v0.tc1.v(), v1.tc1.v(), v2.tc1.v());
uv[2].u() = get_interpolated_attribute(v0.tc2.u(), v1.tc2.u(), v2.tc2.u());
uv[2].v() = get_interpolated_attribute(v0.tc2.v(), v1.tc2.v(), v2.tc2.v());
// Sample bound texture units.
const f24 tc0_w = get_interpolated_attribute(v0.tc0_w, v1.tc0_w, v2.tc0_w);
auto texture_color = TextureColor(uv, textures, tc0_w);
Common::Vec4<u8> primary_fragment_color = {0, 0, 0, 0};
Common::Vec4<u8> secondary_fragment_color = {0, 0, 0, 0};
if (!regs.lighting.disable) {
const auto normquat =
Common::Quaternion<f32>{
{get_interpolated_attribute(v0.quat.x, v1.quat.x, v2.quat.x)
.ToFloat32(),
get_interpolated_attribute(v0.quat.y, v1.quat.y, v2.quat.y)
.ToFloat32(),
get_interpolated_attribute(v0.quat.z, v1.quat.z, v2.quat.z)
.ToFloat32()},
get_interpolated_attribute(v0.quat.w, v1.quat.w, v2.quat.w).ToFloat32(),
}
.Normalized();
const Common::Vec3f view{
get_interpolated_attribute(v0.view.x, v1.view.x, v2.view.x).ToFloat32(),
get_interpolated_attribute(v0.view.y, v1.view.y, v2.view.y).ToFloat32(),
get_interpolated_attribute(v0.view.z, v1.view.z, v2.view.z).ToFloat32(),
};
std::tie(primary_fragment_color, secondary_fragment_color) =
ComputeFragmentsColors(regs.lighting, state.lighting, normquat, view,
texture_color);
}
// Write the TEV stages.
WriteTevConfig(texture_color, tev_stages, primary_color, primary_fragment_color,
secondary_fragment_color);
const auto& output_merger = regs.framebuffer.output_merger;
if (output_merger.fragment_operation_mode ==
FramebufferRegs::FragmentOperationMode::Shadow) {
u32 depth_int = static_cast<u32>(depth * 0xFFFFFF);
// Use green color as the shadow intensity
u8 stencil = combiner_output.y;
fb.DrawShadowMapPixel(x >> 4, y >> 4, depth_int, stencil);
// Skip the normal output merger pipeline if it is in shadow mode
continue;
}
// Does alpha testing happen before or after stencil?
if (!DoAlphaTest(combiner_output.a())) {
continue;
}
WriteFog(depth);
if (!DoDepthStencilTest(x, y, depth)) {
continue;
}
const auto result = PixelColor(x, y);
if (regs.framebuffer.framebuffer.allow_color_write != 0) {
fb.DrawPixel(x >> 4, y >> 4, result);
}
} }
};
sw_workers.QueueWork(std::move(process_scanline)); // Calculate the barycentric coordinates w0, w1 and w2
const s32 w0 = bias0 + SignedArea(vtxpos[1].xy(), vtxpos[2].xy(), {x, y});
const s32 w1 = bias1 + SignedArea(vtxpos[2].xy(), vtxpos[0].xy(), {x, y});
const s32 w2 = bias2 + SignedArea(vtxpos[0].xy(), vtxpos[1].xy(), {x, y});
const s32 wsum = w0 + w1 + w2;
// If current pixel is not covered by the current primitive
if (w0 < 0 || w1 < 0 || w2 < 0) {
continue;
}
const auto baricentric_coordinates = Common::MakeVec(
f24::FromFloat32(static_cast<f32>(w0)), f24::FromFloat32(static_cast<f32>(w1)),
f24::FromFloat32(static_cast<f32>(w2)));
const f24 interpolated_w_inverse =
f24::One() / Common::Dot(w_inverse, baricentric_coordinates);
// interpolated_z = z / w
const float interpolated_z_over_w =
(v0.screenpos[2].ToFloat32() * w0 + v1.screenpos[2].ToFloat32() * w1 +
v2.screenpos[2].ToFloat32() * w2) /
wsum;
// Not fully accurate. About 3 bits in precision are missing.
// Z-Buffer (z / w * scale + offset)
const float depth_scale =
f24::FromRaw(regs.rasterizer.viewport_depth_range).ToFloat32();
const float depth_offset =
f24::FromRaw(regs.rasterizer.viewport_depth_near_plane).ToFloat32();
float depth = interpolated_z_over_w * depth_scale + depth_offset;
// Potentially switch to W-Buffer
if (regs.rasterizer.depthmap_enable ==
Pica::RasterizerRegs::DepthBuffering::WBuffering) {
// W-Buffer (z * scale + w * offset = (z / w * scale + offset) * w)
depth *= interpolated_w_inverse.ToFloat32() * wsum;
}
// Clamp the result
depth = std::clamp(depth, 0.0f, 1.0f);
/**
* Perspective correct attribute interpolation:
* Attribute values cannot be calculated by simple linear interpolation since
* they are not linear in screen space. For example, when interpolating a
* texture coordinate across two vertices, something simple like
* u = (u0*w0 + u1*w1)/(w0+w1)
* will not work. However, the attribute value divided by the
* clipspace w-coordinate (u/w) and and the inverse w-coordinate (1/w) are linear
* in screenspace. Hence, we can linearly interpolate these two independently and
* calculate the interpolated attribute by dividing the results.
* I.e.
* u_over_w = ((u0/v0.pos.w)*w0 + (u1/v1.pos.w)*w1)/(w0+w1)
* one_over_w = (( 1/v0.pos.w)*w0 + ( 1/v1.pos.w)*w1)/(w0+w1)
* u = u_over_w / one_over_w
*
* The generalization to three vertices is straightforward in baricentric coordinates.
**/
const auto get_interpolated_attribute = [&](f24 attr0, f24 attr1, f24 attr2) {
auto attr_over_w = Common::MakeVec(attr0, attr1, attr2);
f24 interpolated_attr_over_w = Common::Dot(attr_over_w, baricentric_coordinates);
return interpolated_attr_over_w * interpolated_w_inverse;
};
const Common::Vec4<u8> primary_color{
static_cast<u8>(
round(get_interpolated_attribute(v0.color.r(), v1.color.r(), v2.color.r())
.ToFloat32() *
255)),
static_cast<u8>(
round(get_interpolated_attribute(v0.color.g(), v1.color.g(), v2.color.g())
.ToFloat32() *
255)),
static_cast<u8>(
round(get_interpolated_attribute(v0.color.b(), v1.color.b(), v2.color.b())
.ToFloat32() *
255)),
static_cast<u8>(
round(get_interpolated_attribute(v0.color.a(), v1.color.a(), v2.color.a())
.ToFloat32() *
255)),
};
std::array<Common::Vec2<f24>, 3> uv;
uv[0].u() = get_interpolated_attribute(v0.tc0.u(), v1.tc0.u(), v2.tc0.u());
uv[0].v() = get_interpolated_attribute(v0.tc0.v(), v1.tc0.v(), v2.tc0.v());
uv[1].u() = get_interpolated_attribute(v0.tc1.u(), v1.tc1.u(), v2.tc1.u());
uv[1].v() = get_interpolated_attribute(v0.tc1.v(), v1.tc1.v(), v2.tc1.v());
uv[2].u() = get_interpolated_attribute(v0.tc2.u(), v1.tc2.u(), v2.tc2.u());
uv[2].v() = get_interpolated_attribute(v0.tc2.v(), v1.tc2.v(), v2.tc2.v());
// Sample bound texture units.
const f24 tc0_w = get_interpolated_attribute(v0.tc0_w, v1.tc0_w, v2.tc0_w);
const auto texture_color = TextureColor(uv, textures, tc0_w);
Common::Vec4<u8> primary_fragment_color = {0, 0, 0, 0};
Common::Vec4<u8> secondary_fragment_color = {0, 0, 0, 0};
if (!regs.lighting.disable) {
const auto normquat =
Common::Quaternion<f32>{
{get_interpolated_attribute(v0.quat.x, v1.quat.x, v2.quat.x).ToFloat32(),
get_interpolated_attribute(v0.quat.y, v1.quat.y, v2.quat.y).ToFloat32(),
get_interpolated_attribute(v0.quat.z, v1.quat.z, v2.quat.z).ToFloat32()},
get_interpolated_attribute(v0.quat.w, v1.quat.w, v2.quat.w).ToFloat32(),
}
.Normalized();
const Common::Vec3f view{
get_interpolated_attribute(v0.view.x, v1.view.x, v2.view.x).ToFloat32(),
get_interpolated_attribute(v0.view.y, v1.view.y, v2.view.y).ToFloat32(),
get_interpolated_attribute(v0.view.z, v1.view.z, v2.view.z).ToFloat32(),
};
std::tie(primary_fragment_color, secondary_fragment_color) = ComputeFragmentsColors(
regs.lighting, state.lighting, normquat, view, texture_color);
}
// Write the TEV stages.
WriteTevConfig(texture_color, tev_stages, primary_color, primary_fragment_color,
secondary_fragment_color);
const auto& output_merger = regs.framebuffer.output_merger;
if (output_merger.fragment_operation_mode ==
FramebufferRegs::FragmentOperationMode::Shadow) {
u32 depth_int = static_cast<u32>(depth * 0xFFFFFF);
// Use green color as the shadow intensity
u8 stencil = combiner_output.y;
fb.DrawShadowMapPixel(x >> 4, y >> 4, depth_int, stencil);
// Skip the normal output merger pipeline if it is in shadow mode
continue;
}
// Does alpha testing happen before or after stencil?
if (!DoAlphaTest(combiner_output.a())) {
continue;
}
WriteFog(combiner_output, depth);
if (!DoDepthStencilTest(x, y, depth)) {
continue;
}
const auto result = PixelColor(x, y, combiner_output);
if (regs.framebuffer.framebuffer.allow_color_write != 0) {
fb.DrawPixel(x >> 4, y >> 4, result);
}
}
} }
sw_workers.WaitForRequests();
} }
std::array<Common::Vec4<u8>, 4> RasterizerSoftware::TextureColor( std::array<Common::Vec4<u8>, 4> RasterizerSoftware::TextureColor(
@ -571,10 +538,11 @@ std::array<Common::Vec4<u8>, 4> RasterizerSoftware::TextureColor(
t = texture.config.height - 1 - t = texture.config.height - 1 -
GetWrappedTexCoord(texture.config.wrap_t, t, texture.config.height); GetWrappedTexCoord(texture.config.wrap_t, t, texture.config.height);
const u8* texture_data = memory.GetPhysicalPointer(texture_address);
const auto info = TextureInfo::FromPicaRegister(texture.config, texture.format); const auto info = TextureInfo::FromPicaRegister(texture.config, texture.format);
// TODO: Apply the min and mag filters to the texture // TODO: Apply the min and mag filters to the texture
texture_color[i] = LookupTexture(texture_data[i], s, t, info); texture_color[i] = LookupTexture(texture_data, s, t, info);
} }
if (i == 0 && (texture.config.type == TexturingRegs::TextureConfig::Shadow2D || if (i == 0 && (texture.config.type == TexturingRegs::TextureConfig::Shadow2D ||
@ -604,7 +572,8 @@ std::array<Common::Vec4<u8>, 4> RasterizerSoftware::TextureColor(
return texture_color; return texture_color;
} }
Common::Vec4<u8> RasterizerSoftware::PixelColor(u16 x, u16 y) const { Common::Vec4<u8> RasterizerSoftware::PixelColor(u16 x, u16 y,
Common::Vec4<u8>& combiner_output) const {
const auto dest = fb.GetPixel(x >> 4, y >> 4); const auto dest = fb.GetPixel(x >> 4, y >> 4);
Common::Vec4<u8> blend_output = combiner_output; Common::Vec4<u8> blend_output = combiner_output;
@ -695,20 +664,10 @@ Common::Vec4<u8> RasterizerSoftware::PixelColor(u16 x, u16 y) const {
} }
void RasterizerSoftware::WriteTevConfig( void RasterizerSoftware::WriteTevConfig(
std::span<Common::Vec4<u8>, 4> texture_color, std::span<const Common::Vec4<u8>, 4> texture_color,
std::span<const Pica::TexturingRegs::TevStageConfig, 6> tev_stages, std::span<const Pica::TexturingRegs::TevStageConfig, 6> tev_stages,
Common::Vec4<u8> primary_color, Common::Vec4<u8> primary_fragment_color, Common::Vec4<u8> primary_color, Common::Vec4<u8> primary_fragment_color,
Common::Vec4<u8> secondary_fragment_color) { Common::Vec4<u8> secondary_fragment_color) {
#if CITRA_ARCH(x86_64)
if (use_jit) {
const u32 tev_combiner_buffer_color = regs.texturing.tev_combiner_buffer_color.raw;
combiner_output = tev_config->Run(texture_color, primary_color, primary_fragment_color,
secondary_fragment_color, tev_combiner_buffer_color);
return;
}
#endif
/** /**
* Texture environment - consists of 6 stages of color and alpha combining. * Texture environment - consists of 6 stages of color and alpha combining.
* Color combiners take three input color values from some source (e.g. interpolated * Color combiners take three input color values from some source (e.g. interpolated
@ -772,7 +731,6 @@ void RasterizerSoftware::WriteTevConfig(
GetColorModifier(tev_stage.color_modifier2, get_source(tev_stage.color_source2)), GetColorModifier(tev_stage.color_modifier2, get_source(tev_stage.color_source2)),
GetColorModifier(tev_stage.color_modifier3, get_source(tev_stage.color_source3)), GetColorModifier(tev_stage.color_modifier3, get_source(tev_stage.color_source3)),
}; };
const Common::Vec3<u8> color_output = ColorCombine(tev_stage.color_op, color_result); const Common::Vec3<u8> color_output = ColorCombine(tev_stage.color_op, color_result);
u8 alpha_output; u8 alpha_output;
@ -810,7 +768,7 @@ void RasterizerSoftware::WriteTevConfig(
} }
} }
void RasterizerSoftware::WriteFog(float depth) const { void RasterizerSoftware::WriteFog(Common::Vec4<u8>& combiner_output, float depth) const {
/** /**
* Apply fog combiner. Not fully accurate. We'd have to know what data type is used to * Apply fog combiner. Not fully accurate. We'd have to know what data type is used to
* store the depth etc. Using float for now until we know more about Pica datatypes. * store the depth etc. Using float for now until we know more about Pica datatypes.

View File

@ -4,20 +4,13 @@
#pragma once #pragma once
#include <memory>
#include <span> #include <span>
#include <unordered_map>
#include "common/arch.h"
#include "common/thread_worker.h"
#include "video_core/rasterizer_interface.h" #include "video_core/rasterizer_interface.h"
#include "video_core/regs_texturing.h"
#include "video_core/renderer_software/sw_clipper.h" #include "video_core/renderer_software/sw_clipper.h"
#include "video_core/renderer_software/sw_framebuffer.h" #include "video_core/renderer_software/sw_framebuffer.h"
#if CITRA_ARCH(x86_64)
#include "video_core/renderer_software/sw_tev_jit.h"
#endif
namespace Pica::Shader { namespace Pica::Shader {
struct OutputVertex; struct OutputVertex;
} }
@ -59,16 +52,16 @@ private:
std::span<const Pica::TexturingRegs::FullTextureConfig, 3> textures, f24 tc0_w) const; std::span<const Pica::TexturingRegs::FullTextureConfig, 3> textures, f24 tc0_w) const;
/// Returns the final pixel color with blending or logic ops applied. /// Returns the final pixel color with blending or logic ops applied.
Common::Vec4<u8> PixelColor(u16 x, u16 y) const; Common::Vec4<u8> PixelColor(u16 x, u16 y, Common::Vec4<u8>& combiner_output) const;
/// Emulates the TEV configuration and returns the combiner output. /// Emulates the TEV configuration and returns the combiner output.
void WriteTevConfig(std::span<Common::Vec4<u8>, 4> texture_color, void WriteTevConfig(std::span<const Common::Vec4<u8>, 4> texture_color,
std::span<const Pica::TexturingRegs::TevStageConfig, 6> tev_stages, std::span<const Pica::TexturingRegs::TevStageConfig, 6> tev_stages,
Common::Vec4<u8> primary_color, Common::Vec4<u8> primary_fragment_color, Common::Vec4<u8> primary_color, Common::Vec4<u8> primary_fragment_color,
Common::Vec4<u8> secondary_fragment_color); Common::Vec4<u8> secondary_fragment_color);
/// Blends fog to the combiner output if enabled. /// Blends fog to the combiner output if enabled.
void WriteFog(float depth) const; void WriteFog(Common::Vec4<u8>& combiner_output, float depth) const;
/// Performs the alpha test. Returns false if the test failed. /// Performs the alpha test. Returns false if the test failed.
bool DoAlphaTest(u8 alpha) const; bool DoAlphaTest(u8 alpha) const;
@ -80,13 +73,10 @@ private:
Memory::MemorySystem& memory; Memory::MemorySystem& memory;
Pica::State& state; Pica::State& state;
const Pica::Regs& regs; const Pica::Regs& regs;
bool use_jit{true};
size_t num_sw_threads;
Common::ThreadWorker sw_workers;
Framebuffer fb; Framebuffer fb;
TevCache tev_cache; // Kirby Blowout Blast relies on the combiner output of a previous draw
TevConfig* tev_config{}; // in order to render the sky correctly.
std::array<const u8*, 3> texture_data{}; Common::Vec4<u8> combiner_output{};
}; };
} // namespace SwRenderer } // namespace SwRenderer

View File

@ -1,473 +0,0 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <bit>
#include <emmintrin.h>
#include "common/x64/xbyak_abi.h"
#include "video_core/regs.h"
#include "video_core/renderer_software/sw_tev_jit.h"
namespace SwRenderer {
namespace {
using namespace Common::X64;
using namespace Xbyak::util;
using Pica::TexturingRegs;
using Xbyak::Reg32;
using Xbyak::Reg64;
using Xbyak::Xmm;
using TevStageConfig = Pica::TexturingRegs::TevStageConfig;
constexpr Reg32 A0 = r11d;
constexpr Reg32 A1 = r12d;
constexpr Reg32 A2 = r13d;
constexpr Reg32 ALPHA_OUTPUT = r14d;
constexpr Xmm COMBINER_OUTPUT = xmm0;
constexpr Xmm COMBINER_BUFFER = xmm1;
constexpr Xmm NEXT_COMBINER_BUFFER = xmm2;
constexpr Xmm VEC0 = xmm3;
constexpr Xmm VEC1 = xmm4;
constexpr Xmm VEC2 = xmm5;
constexpr Xmm COLOR_OUTPUT = xmm6;
constexpr Xmm ZERO = xmm13;
constexpr Xmm MID_COLOR = xmm14;
constexpr Xmm MAX_COLOR = xmm15;
bool IsPassThroughTevStage(const TevStageConfig& stage) {
return (stage.color_op == TevStageConfig::Operation::Replace &&
stage.alpha_op == TevStageConfig::Operation::Replace &&
stage.color_source1 == TevStageConfig::Source::Previous &&
stage.alpha_source1 == TevStageConfig::Source::Previous &&
stage.color_modifier1 == TevStageConfig::ColorModifier::SourceColor &&
stage.alpha_modifier1 == TevStageConfig::AlphaModifier::SourceAlpha &&
stage.GetColorMultiplier() == 1 && stage.GetAlphaMultiplier() == 1);
}
} // Anonymous namespace
TevConfigKey::TevConfigKey(const Pica::TexturingRegs& regs) {
const auto& tev_stages = regs.GetTevStages();
for (size_t i = 0; i < tev_stages.size(); i++) {
const auto& tev_stage = tev_stages[i];
stages[i].sources_raw = tev_stage.sources_raw;
stages[i].modifiers_raw = tev_stage.modifiers_raw;
stages[i].ops_raw = tev_stage.ops_raw;
stages[i].const_color = tev_stage.const_color;
stages[i].scales_raw = tev_stage.scales_raw;
}
}
TevConfig::TevConfig(const Pica::Regs& regs_, const TevConfigKey& key) : regs{regs_} {
WriteTevConfig(key);
}
TevConfig::~TevConfig() = default;
Common::Vec4<u8> TevConfig::Run(std::span<Common::Vec4<u8>, 4> texture_color_,
Common::Vec4<u8> primary_color_,
Common::Vec4<u8> primary_fragment_color_,
Common::Vec4<u8> secondary_fragment_color_,
u64 tev_combiner_buffer_color) {
u32* texture_color = reinterpret_cast<u32*>(texture_color_.data());
const u32 primary_color = std::bit_cast<u32>(primary_color_);
const u32 primary_fragment_color = std::bit_cast<u32>(primary_fragment_color_);
const u32 secondary_fragment_color = std::bit_cast<u32>(secondary_fragment_color_);
const u64 secondary_fragment_color_and_tev_combiner_buffer_color =
secondary_fragment_color | (tev_combiner_buffer_color << 32);
const u32 result = program(texture_color, primary_color, primary_fragment_color,
secondary_fragment_color_and_tev_combiner_buffer_color);
return std::bit_cast<Common::Vec4<u8>>(result);
}
void TevConfig::WriteTevConfig(const TevConfigKey& key) {
program = (CompiledTevFun*)getCurr();
constexpr Xbyak::Reg TEXTURE_COLOR = ABI_PARAM1;
constexpr Xbyak::Reg PRIMARY_COLOR = ABI_PARAM2;
constexpr Xbyak::Reg PRIMARY_FRAGMENT_COLOR = ABI_PARAM3;
constexpr Xbyak::Reg SECONDARY_FRAGMENT_COLOR = ABI_PARAM4;
// Save calle state
ABI_PushRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8, 16);
// Clear the combiner registers and zero constant
pxor(COMBINER_OUTPUT, COMBINER_OUTPUT);
pxor(COMBINER_BUFFER, COMBINER_BUFFER);
pxor(ZERO, ZERO);
// Used to set an xmm register to the max color
static const __m128i max = _mm_set1_epi32(255);
mov(rax, reinterpret_cast<size_t>(&max));
movdqu(MAX_COLOR, xword[rax]);
// Used to set an xmm register to the mid color
static const __m128i mid = _mm_set1_epi32(128);
mov(rax, reinterpret_cast<size_t>(&mid));
movdqu(MID_COLOR, xword[rax]);
// Load next_combiner_buffer
mov(rax, ABI_PARAM4);
shr(rax, 32);
vmovd(NEXT_COMBINER_BUFFER, eax);
pmovzxbd(NEXT_COMBINER_BUFFER, NEXT_COMBINER_BUFFER);
for (u32 tev_stage_index = 0; tev_stage_index < key.stages.size(); ++tev_stage_index) {
const auto& tev_stage = key.stages[tev_stage_index];
if (!IsPassThroughTevStage(tev_stage)) {
using Source = TexturingRegs::TevStageConfig::Source;
const auto get_source = [&](const Xbyak::Xmm& dest, Source source) {
switch (source) {
case Source::PrimaryColor:
vmovd(dest, PRIMARY_COLOR.cvt32());
pmovzxbd(dest, dest);
break;
case Source::PrimaryFragmentColor:
vmovd(dest, PRIMARY_FRAGMENT_COLOR.cvt32());
pmovzxbd(dest, dest);
break;
case Source::SecondaryFragmentColor:
vmovd(dest, SECONDARY_FRAGMENT_COLOR.cvt32());
pmovzxbd(dest, dest);
break;
case Source::Texture0:
case Source::Texture1:
case Source::Texture2:
case Source::Texture3: {
const u32 index = static_cast<u32>(source) - static_cast<u32>(Source::Texture0);
vmovd(dest, dword[TEXTURE_COLOR + index * sizeof(u32)]);
pmovzxbd(dest, dest);
break;
}
case Source::PreviousBuffer:
vmovdqa(dest, COMBINER_BUFFER);
break;
case Source::Constant:
mov(eax, tev_stage.const_color);
vmovd(dest, eax);
pmovzxbd(dest, dest);
break;
case Source::Previous:
vmovdqa(dest, COMBINER_OUTPUT);
break;
default:
LOG_ERROR(HW_GPU, "Unknown color combiner source {}", source);
UNIMPLEMENTED();
vmovdqa(dest, ZERO);
}
return dest;
};
// Load the color modifiers to VEC0/1/2.
GetColorModifier(get_source(VEC0, tev_stage.color_source1), tev_stage.color_modifier1);
GetColorModifier(get_source(VEC1, tev_stage.color_source2), tev_stage.color_modifier2);
GetColorModifier(get_source(VEC2, tev_stage.color_source3), tev_stage.color_modifier3);
// Combine the texture colors to COLOR_OUTPUT.
ColorCombine(COLOR_OUTPUT, tev_stage.color_op);
if (tev_stage.color_op == TexturingRegs::TevStageConfig::Operation::Dot3_RGBA) {
// Result of Dot3_RGBA operation is also placed to the alpha component
vmovd(ALPHA_OUTPUT.cvt32(), COLOR_OUTPUT);
} else {
// Load the alpha modifers to VEC0/1/2.
GetAlphaModifier(get_source(VEC0, tev_stage.alpha_source1), A0,
tev_stage.alpha_modifier1);
GetAlphaModifier(get_source(VEC1, tev_stage.alpha_source2), A1,
tev_stage.alpha_modifier2);
GetAlphaModifier(get_source(VEC2, tev_stage.alpha_source3), A2,
tev_stage.alpha_modifier3);
// Combine the alpha values to ALPHA_OUTPUT.
AlphaCombine(ALPHA_OUTPUT, tev_stage.alpha_op);
}
// Load the color multipler to an SSE vector.
mov(eax, tev_stage.GetColorMultiplier());
movd(VEC0, eax);
pshufd(VEC0, VEC0, 0);
// Multiply color output with the multiplier and take the minimum.
pmulld(COLOR_OUTPUT, VEC0);
pminsd(COLOR_OUTPUT, MAX_COLOR);
// Load the alpha multiplier, multiply it with the alpha output.
mov(eax, tev_stage.GetAlphaMultiplier());
imul(ALPHA_OUTPUT, eax);
// Load result to a vector and take the minimum
movd(VEC0, ALPHA_OUTPUT);
pshufd(VEC0, VEC0, 0);
pminsd(VEC0, MAX_COLOR);
// Blend vectors to get the combiner output
vpblendd(COMBINER_OUTPUT, COLOR_OUTPUT, VEC0, 0b1000);
}
// Set combiner buffer to the next buffer
movq(COMBINER_BUFFER, NEXT_COMBINER_BUFFER);
if (regs.texturing.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferColor(
tev_stage_index)) {
vpblendd(NEXT_COMBINER_BUFFER, COMBINER_OUTPUT, NEXT_COMBINER_BUFFER, 0b1000);
}
if (regs.texturing.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferAlpha(
tev_stage_index)) {
vpblendd(NEXT_COMBINER_BUFFER, COMBINER_OUTPUT, NEXT_COMBINER_BUFFER, 0b0111);
}
}
// Pack combiner output to a u32 to be returned.
vpextrd(edx, COMBINER_OUTPUT, 3);
vpextrd(eax, COMBINER_OUTPUT, 2);
sal(edx, 8);
or_(eax, edx);
vpextrd(edx, COMBINER_OUTPUT, 1);
sal(eax, 8);
or_(edx, eax);
vmovd(eax, COMBINER_OUTPUT);
sal(edx, 8);
or_(eax, edx);
ABI_PopRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8, 16);
ret();
ready();
}
void TevConfig::GetColorModifier(const Xbyak::Xmm& dest, TevStageConfig::ColorModifier factor) {
using ColorModifier = TevStageConfig::ColorModifier;
const auto broadcast = [&](u32 comp) {
const u8 mask = comp | (comp << 2) | (comp << 4);
vpshufd(dest, dest, mask);
};
switch (factor) {
case ColorModifier::SourceColor:
vpblendd(dest, dest, ZERO, 0b1000);
break;
case ColorModifier::OneMinusSourceColor:
vpsubd(dest, MAX_COLOR, dest);
break;
case ColorModifier::SourceAlpha:
broadcast(3);
break;
case ColorModifier::OneMinusSourceAlpha:
broadcast(3);
vpsubd(dest, MAX_COLOR, dest);
break;
case ColorModifier::SourceRed:
broadcast(0);
break;
case ColorModifier::OneMinusSourceRed:
broadcast(0);
vpsubd(dest, MAX_COLOR, dest);
break;
case ColorModifier::SourceGreen:
broadcast(1);
break;
case ColorModifier::OneMinusSourceGreen:
broadcast(1);
vpsubd(dest, MAX_COLOR, dest);
break;
case ColorModifier::SourceBlue:
broadcast(2);
break;
case ColorModifier::OneMinusSourceBlue:
broadcast(2);
vpsubd(dest, MAX_COLOR, dest);
break;
default:
UNREACHABLE();
}
pand(dest, MAX_COLOR);
};
void TevConfig::ColorCombine(const Xbyak::Xmm& dest, TevStageConfig::Operation op) {
using Operation = TevStageConfig::Operation;
switch (op) {
case Operation::Replace:
vmovdqa(dest, VEC0);
break;
case Operation::Modulate:
pmulld(VEC0, VEC1);
vpsrlq(dest, VEC0, 8); // TODO: This is a very crude approximation of division by 255
break;
case Operation::Add:
vpaddd(VEC0, VEC0, VEC1);
vpminsd(dest, MAX_COLOR, VEC0);
break;
case Operation::AddSigned:
vpaddd(VEC0, VEC0, VEC1);
vpsubd(VEC0, VEC0, MID_COLOR);
vpminsd(VEC0, VEC0, MAX_COLOR);
vpmaxsd(dest, VEC0, ZERO);
break;
case Operation::Lerp:
pmulld(VEC0, VEC2);
psubd(VEC2, MAX_COLOR);
pmulld(VEC1, VEC2);
vpaddd(dest, VEC0, VEC1);
vpsrlq(dest, VEC0, 8); // TODO: This is a very crude approximation of division by 255
break;
case Operation::Subtract:
psubd(VEC0, VEC1);
vpmaxsd(dest, VEC0, ZERO);
break;
case Operation::MultiplyThenAdd:
pmulld(VEC0, VEC1);
pmulld(VEC2, MAX_COLOR);
paddd(VEC0, VEC2);
pminsd(VEC0, MAX_COLOR);
vpsrlq(dest, VEC0, 8); // TODO: This is a very crude approximation of division by 255
break;
case Operation::AddThenMultiply:
paddd(VEC0, VEC1);
pminsd(VEC0, MAX_COLOR);
pmulld(VEC0, VEC2);
vpsrlq(dest, VEC0, 8); // TODO: This is a very crude approximation of division by 255
break;
case Operation::Dot3_RGB:
case Operation::Dot3_RGBA:
pslld(VEC0, 1);
psubd(VEC0, MAX_COLOR);
pslld(VEC1, 1);
psubd(VEC1, MAX_COLOR);
pmulld(VEC0, VEC1);
paddd(VEC0, MID_COLOR);
psrld(VEC0, 8);
vpblendd(VEC0, VEC0, ZERO, 0b1000);
phaddd(VEC0, VEC0);
phaddd(VEC0, VEC0);
pminsd(VEC0, MAX_COLOR);
pmaxsd(VEC0, ZERO);
pshufd(dest, VEC0, 0);
break;
default:
LOG_ERROR(HW_GPU, "Unknown color combiner operation {}", (int)op);
UNIMPLEMENTED();
}
pand(dest, MAX_COLOR);
};
void TevConfig::GetAlphaModifier(const Xbyak::Xmm& src, const Xbyak::Reg32& dest,
TevStageConfig::AlphaModifier factor) {
using AlphaModifier = TevStageConfig::AlphaModifier;
const auto get_comp = [&](u32 comp, bool minus = false) {
const auto& reg = minus ? eax : dest;
vpextrd(reg, src, comp);
if (minus) {
mov(dest, 255);
sub(dest, reg);
}
};
switch (factor) {
case AlphaModifier::SourceAlpha:
get_comp(3);
break;
case AlphaModifier::OneMinusSourceAlpha:
get_comp(3, true);
break;
case AlphaModifier::SourceRed:
get_comp(0);
break;
case AlphaModifier::OneMinusSourceRed:
get_comp(0, true);
break;
case AlphaModifier::SourceGreen:
get_comp(1);
break;
case AlphaModifier::OneMinusSourceGreen:
get_comp(1, true);
break;
case AlphaModifier::SourceBlue:
get_comp(2);
break;
case AlphaModifier::OneMinusSourceBlue:
get_comp(2, true);
break;
default:
UNREACHABLE();
}
};
void TevConfig::AlphaCombine(const Xbyak::Reg32& dest, TevStageConfig::Operation op) {
using Operation = TevStageConfig::Operation;
const auto div_255 = [&](const Reg32& dst, const Reg32& src) {
mov(dst, 0x80808081);
imul(dst.cvt64(), src.cvt64());
shr(dst.cvt64(), 39);
};
switch (op) {
case Operation::Replace:
mov(dest, A0);
break;
case Operation::Modulate:
imul(A0, A1);
div_255(dest, A0);
break;
case Operation::Add:
add(A0, A1);
cmp(A0, 255);
mov(eax, 255);
cmovb(A0, eax);
break;
case Operation::AddSigned:
xor_(eax, eax);
add(A0, A1);
sub(A0, 128);
test(A0, A0);
cmovg(eax, A0);
cmp(eax, 255);
mov(A0, 255);
cmovb(A0, eax);
break;
case Operation::Lerp:
imul(A0, A2);
mov(eax, 255);
sub(eax, A2);
imul(A1, eax);
add(A0, A1);
div_255(dest, A0);
break;
case Operation::Subtract:
sub(A0, A1);
xor_(eax, eax);
test(A0, A0);
cmovl(A0, eax);
mov(dest, A0);
break;
case Operation::MultiplyThenAdd:
imul(A0, A1);
mov(dest, A2);
shl(dest, 8);
sub(dest, A2);
add(dest, A0);
div_255(eax, dest);
cmp(eax, 255);
mov(dest, 255);
cmovb(dest, eax);
break;
case Operation::AddThenMultiply:
add(A0, A1);
cmp(A0, 255);
mov(eax, 255);
cmovg(A0, eax);
imul(A0, A2);
div_255(dest, A0);
break;
default:
LOG_ERROR(HW_GPU, "Unknown alpha combiner operation {}", (int)op);
UNIMPLEMENTED();
}
};
} // namespace SwRenderer

View File

@ -1,64 +0,0 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <span>
#include <xbyak/xbyak.h>
#include "common/hash.h"
#include "common/vector_math.h"
#include "video_core/regs_texturing.h"
namespace Pica {
struct State;
struct Regs;
} // namespace Pica
namespace SwRenderer {
struct TevConfigKey {
explicit TevConfigKey(const Pica::TexturingRegs& regs);
u64 Hash() const noexcept {
return Common::ComputeHash64(this, sizeof(TevConfigKey));
}
std::array<Pica::TexturingRegs::TevStageConfig, 6> stages;
};
class TevConfig : public Xbyak::CodeGenerator {
public:
explicit TevConfig(const Pica::Regs& regs, const TevConfigKey& key);
~TevConfig();
Common::Vec4<u8> Run(std::span<Common::Vec4<u8>, 4> texture_color_,
Common::Vec4<u8> primary_color_, Common::Vec4<u8> primary_fragment_color_,
Common::Vec4<u8> secondary_fragment_color_, u64 tev_combiner_buffer_color);
private:
void WriteTevConfig(const TevConfigKey& key);
void GetColorModifier(const Xbyak::Xmm& dest,
Pica::TexturingRegs::TevStageConfig::ColorModifier factor);
void GetAlphaModifier(const Xbyak::Xmm& src, const Xbyak::Reg32& dest,
Pica::TexturingRegs::TevStageConfig::AlphaModifier factor);
void ColorCombine(const Xbyak::Xmm& dest, Pica::TexturingRegs::TevStageConfig::Operation op);
void AlphaCombine(const Xbyak::Reg32& dest, Pica::TexturingRegs::TevStageConfig::Operation op);
private:
const Pica::Regs& regs;
using CompiledTevFun = u32(u32* texture_color, u32 primary_color, u32 primary_fragment_color,
u64 secondary_fragment_color_and_tev_combiner_buffer_color);
CompiledTevFun* program = nullptr;
};
using TevCache = std::unordered_map<u64, std::unique_ptr<TevConfig>, Common::IdentityHash<u64>>;
} // namespace SwRenderer

View File

@ -338,39 +338,15 @@ void JitShader::Compile_SanitizedMul(Xmm src1, Xmm src2, Xmm scratch) {
// where neither source was, this NaN was generated by a 0 * inf multiplication, and so the // where neither source was, this NaN was generated by a 0 * inf multiplication, and so the
// result should be transformed to 0 to match PICA fp rules. // result should be transformed to 0 to match PICA fp rules.
if (host_caps.has(Cpu::tAVX512F | Cpu::tAVX512VL | Cpu::tAVX512DQ)) {
vmulps(scratch, src1, src2);
// Mask of any NaN values found in the result
const Xbyak::Opmask zero_mask = k1;
vcmpunordps(zero_mask, scratch, scratch);
// Mask of any non-NaN inputs producing NaN results
vcmpordps(zero_mask | zero_mask, src1, src2);
knotb(zero_mask, zero_mask);
vmovaps(src1 | zero_mask | T_z, scratch);
return;
}
// Set scratch to mask of (src1 != NaN and src2 != NaN) // Set scratch to mask of (src1 != NaN and src2 != NaN)
if (host_caps.has(Cpu::tAVX)) { movaps(scratch, src1);
vcmpordps(scratch, src1, src2); cmpordps(scratch, src2);
} else {
movaps(scratch, src1);
cmpordps(scratch, src2);
}
mulps(src1, src2); mulps(src1, src2);
// Set src2 to mask of (result == NaN) // Set src2 to mask of (result == NaN)
if (host_caps.has(Cpu::tAVX)) { movaps(src2, src1);
vcmpunordps(src2, src2, src1); cmpunordps(src2, src2);
} else {
movaps(src2, src1);
cmpunordps(src2, src2);
}
// Clear components where scratch != src2 (i.e. if result is NaN where neither source was NaN) // Clear components where scratch != src2 (i.e. if result is NaN where neither source was NaN)
xorps(scratch, src2); xorps(scratch, src2);
@ -430,20 +406,13 @@ void JitShader::Compile_DP3(Instruction instr) {
Compile_SanitizedMul(SRC1, SRC2, SCRATCH); Compile_SanitizedMul(SRC1, SRC2, SCRATCH);
if (host_caps.has(Cpu::tAVX)) { movaps(SRC2, SRC1);
vshufps(SRC3, SRC1, SRC1, _MM_SHUFFLE(2, 2, 2, 2)); shufps(SRC2, SRC2, _MM_SHUFFLE(1, 1, 1, 1));
vshufps(SRC2, SRC1, SRC1, _MM_SHUFFLE(1, 1, 1, 1));
vshufps(SRC1, SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0));
} else {
movaps(SRC2, SRC1);
shufps(SRC2, SRC2, _MM_SHUFFLE(1, 1, 1, 1));
movaps(SRC3, SRC1); movaps(SRC3, SRC1);
shufps(SRC3, SRC3, _MM_SHUFFLE(2, 2, 2, 2)); shufps(SRC3, SRC3, _MM_SHUFFLE(2, 2, 2, 2));
shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0));
}
shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0));
addps(SRC1, SRC2); addps(SRC1, SRC2);
addps(SRC1, SRC3); addps(SRC1, SRC3);
@ -620,15 +589,9 @@ void JitShader::Compile_MOV(Instruction instr) {
void JitShader::Compile_RCP(Instruction instr) { void JitShader::Compile_RCP(Instruction instr) {
Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
if (host_caps.has(Cpu::tAVX512F | Cpu::tAVX512VL)) { // TODO(bunnei): RCPSS is a pretty rough approximation, this might cause problems if Pica
// Accurate to 14 bits of precisions rather than 12 bits of rcpss // performs this operation more accurately. This should be checked on hardware.
vrcp14ss(SRC1, SRC1, SRC1); rcpss(SRC1, SRC1);
} else {
// TODO(bunnei): RCPSS is a pretty rough approximation, this might cause problems if Pica
// performs this operation more accurately. This should be checked on hardware.
rcpss(SRC1, SRC1);
}
shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX
Compile_DestEnable(instr, SRC1); Compile_DestEnable(instr, SRC1);
@ -637,15 +600,9 @@ void JitShader::Compile_RCP(Instruction instr) {
void JitShader::Compile_RSQ(Instruction instr) { void JitShader::Compile_RSQ(Instruction instr) {
Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
if (host_caps.has(Cpu::tAVX512F | Cpu::tAVX512VL)) { // TODO(bunnei): RSQRTSS is a pretty rough approximation, this might cause problems if Pica
// Accurate to 14 bits of precisions rather than 12 bits of rsqrtss // performs this operation more accurately. This should be checked on hardware.
vrsqrt14ss(SRC1, SRC1, SRC1); rsqrtss(SRC1, SRC1);
} else {
// TODO(bunnei): RSQRTSS is a pretty rough approximation, this might cause problems if Pica
// performs this operation more accurately. This should be checked on hardware.
rsqrtss(SRC1, SRC1);
}
shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX
Compile_DestEnable(instr, SRC1); Compile_DestEnable(instr, SRC1);
@ -1093,47 +1050,32 @@ Xbyak::Label JitShader::CompilePrelude_Log2() {
jp(input_is_nan); jp(input_is_nan);
jae(input_out_of_range); jae(input_out_of_range);
// Split input: SRC1=MANT[1,2) SCRATCH2=Exponent // Split input
if (host_caps.has(Cpu::tAVX512F | Cpu::tAVX512VL)) { movd(eax, SRC1);
vgetexpss(SCRATCH2, SRC1, SRC1); mov(edx, eax);
vgetmantss(SRC1, SRC1, SRC1, 0x0'0); and_(eax, 0x7f800000);
} else { and_(edx, 0x007fffff);
movd(eax, SRC1); movss(SCRATCH, xword[rip + c0]); // Preload c0.
mov(edx, eax); or_(edx, 0x3f800000);
and_(eax, 0x7f800000); movd(SRC1, edx);
and_(edx, 0x007fffff); // SRC1 now contains the mantissa of the input.
or_(edx, 0x3f800000); mulss(SCRATCH, SRC1);
movd(SRC1, edx); shr(eax, 23);
// SRC1 now contains the mantissa of the input. sub(eax, 0x7f);
shr(eax, 23); cvtsi2ss(SCRATCH2, eax);
sub(eax, 0x7f); // SCRATCH2 now contains the exponent of the input.
cvtsi2ss(SCRATCH2, eax);
// SCRATCH2 now contains the exponent of the input.
}
movss(SCRATCH, xword[rip + c0]);
// Complete computation of polynomial // Complete computation of polynomial
if (host_caps.has(Cpu::tFMA)) { addss(SCRATCH, xword[rip + c1]);
vfmadd213ss(SCRATCH, SRC1, xword[rip + c1]); mulss(SCRATCH, SRC1);
vfmadd213ss(SCRATCH, SRC1, xword[rip + c2]); addss(SCRATCH, xword[rip + c2]);
vfmadd213ss(SCRATCH, SRC1, xword[rip + c3]); mulss(SCRATCH, SRC1);
vfmadd213ss(SCRATCH, SRC1, xword[rip + c4]); addss(SCRATCH, xword[rip + c3]);
subss(SRC1, ONE); mulss(SCRATCH, SRC1);
vfmadd231ss(SCRATCH2, SCRATCH, SRC1); subss(SRC1, ONE);
} else { addss(SCRATCH, xword[rip + c4]);
mulss(SCRATCH, SRC1); mulss(SCRATCH, SRC1);
addss(SCRATCH, xword[rip + c1]); addss(SCRATCH2, SCRATCH);
mulss(SCRATCH, SRC1);
addss(SCRATCH, xword[rip + c2]);
mulss(SCRATCH, SRC1);
addss(SCRATCH, xword[rip + c3]);
mulss(SCRATCH, SRC1);
subss(SRC1, ONE);
addss(SCRATCH, xword[rip + c4]);
mulss(SCRATCH, SRC1);
addss(SCRATCH2, SCRATCH);
}
// Duplicate result across vector // Duplicate result across vector
xorps(SRC1, SRC1); // break dependency chain xorps(SRC1, SRC1); // break dependency chain
@ -1180,69 +1122,33 @@ Xbyak::Label JitShader::CompilePrelude_Exp2() {
// Handle edge cases // Handle edge cases
ucomiss(SRC1, SRC1); ucomiss(SRC1, SRC1);
jp(ret_label); jp(ret_label);
// Clamp to maximum range since we shift the value directly into the exponent.
minss(SRC1, xword[rip + input_max]);
maxss(SRC1, xword[rip + input_min]);
// Decompose input: // Decompose input
// SCRATCH=2^round(input) movss(SCRATCH, SRC1);
// SRC1=input-round(input) [-0.5, 0.5) movss(SCRATCH2, xword[rip + c0]); // Preload c0.
if (host_caps.has(Cpu::tAVX512F | Cpu::tAVX512VL)) { subss(SCRATCH, xword[rip + half]);
// input - 0.5 cvtss2si(eax, SCRATCH);
vsubss(SCRATCH, SRC1, xword[rip + half]); cvtsi2ss(SCRATCH, eax);
// SCRATCH now contains input rounded to the nearest integer.
// trunc(input - 0.5) add(eax, 0x7f);
vrndscaless(SCRATCH2, SCRATCH, SCRATCH, _MM_FROUND_TRUNC); subss(SRC1, SCRATCH);
// SRC1 contains input - round(input), which is in [-0.5, 0.5).
// SCRATCH = 1 * 2^(trunc(input - 0.5)) mulss(SCRATCH2, SRC1);
vscalefss(SCRATCH, ONE, SCRATCH2); shl(eax, 23);
movd(SCRATCH, eax);
// SRC1 = input-trunc(input - 0.5) // SCRATCH contains 2^(round(input)).
vsubss(SRC1, SRC1, SCRATCH2);
} else {
// Clamp to maximum range since we shift the value directly into the exponent.
minss(SRC1, xword[rip + input_max]);
maxss(SRC1, xword[rip + input_min]);
if (host_caps.has(Cpu::tAVX)) {
vsubss(SCRATCH, SRC1, xword[rip + half]);
} else {
movss(SCRATCH, SRC1);
subss(SCRATCH, xword[rip + half]);
}
if (host_caps.has(Cpu::tSSE41)) {
roundss(SCRATCH, SCRATCH, _MM_FROUND_TRUNC);
cvtss2si(eax, SCRATCH);
} else {
cvtss2si(eax, SCRATCH);
cvtsi2ss(SCRATCH, eax);
}
// SCRATCH now contains input rounded to the nearest integer.
add(eax, 0x7f);
subss(SRC1, SCRATCH);
// SRC1 contains input - round(input), which is in [-0.5, 0.5).
shl(eax, 23);
movd(SCRATCH, eax);
// SCRATCH contains 2^(round(input)).
}
// Complete computation of polynomial. // Complete computation of polynomial.
movss(SCRATCH2, xword[rip + c0]); addss(SCRATCH2, xword[rip + c1]);
mulss(SCRATCH2, SRC1);
if (host_caps.has(Cpu::tFMA)) { addss(SCRATCH2, xword[rip + c2]);
vfmadd213ss(SCRATCH2, SRC1, xword[rip + c1]); mulss(SCRATCH2, SRC1);
vfmadd213ss(SCRATCH2, SRC1, xword[rip + c2]); addss(SCRATCH2, xword[rip + c3]);
vfmadd213ss(SCRATCH2, SRC1, xword[rip + c3]); mulss(SRC1, SCRATCH2);
vfmadd213ss(SRC1, SCRATCH2, xword[rip + c4]); addss(SRC1, xword[rip + c4]);
} else {
mulss(SCRATCH2, SRC1);
addss(SCRATCH2, xword[rip + c1]);
mulss(SCRATCH2, SRC1);
addss(SCRATCH2, xword[rip + c2]);
mulss(SCRATCH2, SRC1);
addss(SCRATCH2, xword[rip + c3]);
mulss(SRC1, SCRATCH2);
addss(SRC1, xword[rip + c4]);
}
mulss(SRC1, SCRATCH); mulss(SRC1, SCRATCH);
// Duplicate result across vector // Duplicate result across vector