Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
5a38c5f190 |
8
.github/workflows/ci-merge.js
vendored
8
.github/workflows/ci-merge.js
vendored
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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>());
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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, ¶ms, &minimum_latency) != CUBEB_OK) {
|
if (cubeb_get_min_latency(impl->ctx, ¶ms, &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, ¶ms, std::max(512u, minimum_latency),
|
output_device, ¶ms, 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);
|
||||||
|
@ -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;
|
||||||
|
@ -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)};
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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 =
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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());
|
||||||
|
@ -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;
|
|
||||||
};
|
};
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
@ -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())));
|
||||||
|
@ -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;
|
||||||
|
@ -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) \
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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>();
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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>();
|
||||||
|
@ -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:
|
||||||
|
@ -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"},
|
||||||
|
@ -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"},
|
||||||
|
@ -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"},
|
||||||
|
@ -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;
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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"},
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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 {}
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
Reference in New Issue
Block a user