Merge remote-tracking branch 'upstream/master' into feature/savestates-2

This commit is contained in:
Hamish Milne 2020-03-28 12:33:21 +00:00
commit 7049af744f
77 changed files with 18323 additions and 11473 deletions

View File

@ -5,6 +5,7 @@ Citra
[![Travis CI Build Status](https://travis-ci.com/citra-emu/citra.svg?branch=master)](https://travis-ci.com/citra-emu/citra) [![Travis CI Build Status](https://travis-ci.com/citra-emu/citra.svg?branch=master)](https://travis-ci.com/citra-emu/citra)
[![AppVeyor CI Build Status](https://ci.appveyor.com/api/projects/status/sdf1o4kh3g1e68m9?svg=true)](https://ci.appveyor.com/project/bunnei/citra) [![AppVeyor CI Build Status](https://ci.appveyor.com/api/projects/status/sdf1o4kh3g1e68m9?svg=true)](https://ci.appveyor.com/project/bunnei/citra)
[![Bitrise CI Build Status](https://app.bitrise.io/app/4ccd8e5720f0d13b/status.svg?token=H32TmbCwxb3OQ-M66KbAyw&branch=master)](https://app.bitrise.io/app/4ccd8e5720f0d13b) [![Bitrise CI Build Status](https://app.bitrise.io/app/4ccd8e5720f0d13b/status.svg?token=H32TmbCwxb3OQ-M66KbAyw&branch=master)](https://app.bitrise.io/app/4ccd8e5720f0d13b)
[![Discord](https://img.shields.io/discord/220740965957107713?color=%237289DA&label=Citra&logo=discord&logoColor=white)](https://discord.gg/FAXfZV9)
Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and macOS. Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and macOS.

File diff suppressed because it is too large Load Diff

1025
dist/languages/de.ts vendored

File diff suppressed because it is too large Load Diff

1039
dist/languages/es_ES.ts vendored

File diff suppressed because it is too large Load Diff

995
dist/languages/fi.ts vendored

File diff suppressed because it is too large Load Diff

5256
dist/languages/fi_FI.ts vendored Normal file

File diff suppressed because it is too large Load Diff

1044
dist/languages/fr.ts vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1179
dist/languages/id.ts vendored

File diff suppressed because it is too large Load Diff

1231
dist/languages/it.ts vendored

File diff suppressed because it is too large Load Diff

1075
dist/languages/ja_JP.ts vendored

File diff suppressed because it is too large Load Diff

1048
dist/languages/ko_KR.ts vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

965
dist/languages/nb.ts vendored

File diff suppressed because it is too large Load Diff

1087
dist/languages/nl.ts vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1110
dist/languages/pt_BR.ts vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1235
dist/languages/vi_VN.ts vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

6
dist/license.md vendored
View File

@ -2,11 +2,11 @@ The icons in this folder and its subfolders have the following licenses:
Icon Name | License | Origin/Author Icon Name | License | Origin/Author
--- | --- | --- --- | --- | ---
qt_themes/default/icons/16x16/checked.png | Free for non-commercial use qt_themes/default/icons/16x16/checked.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/16x16/failed.png | Free for non-commercial use qt_themes/default/icons/16x16/failed.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
@ -15,11 +15,9 @@ qt_themes/default/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/48x48/no_avatar.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/48x48/no_avatar.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/16x16/checked.png | Free for non-commercial use
qt_themes/qdarkstyle/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/16x16/failed.png | Free for non-commercial use
qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 B

After

Width:  |  Height:  |  Size: 657 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 B

After

Width:  |  Height:  |  Size: 524 B

View File

@ -343,11 +343,11 @@ The icons used in this project have the following licenses:
Icon Name | License | Origin/Author Icon Name | License | Origin/Author
--- | --- | --- --- | --- | ---
checked.png | Free for non-commercial use checked.png | CC BY-ND 3.0 | https://icons8.com
connected.png | CC BY-ND 3.0 | https://icons8.com connected.png | CC BY-ND 3.0 | https://icons8.com
connected_notification.png | CC BY-ND 3.0 | https://icons8.com connected_notification.png | CC BY-ND 3.0 | https://icons8.com
disconnected.png | CC BY-ND 3.0 | https://icons8.com disconnected.png | CC BY-ND 3.0 | https://icons8.com
failed.png | Free for non-commercial use failed.png | CC BY-ND 3.0 | https://icons8.com
lock.png | CC BY-ND 3.0 | https://icons8.com lock.png | CC BY-ND 3.0 | https://icons8.com
plus_folder.png | CC BY-ND 3.0 | https://icons8.com plus_folder.png | CC BY-ND 3.0 | https://icons8.com
bad_folder.png | CC BY-ND 3.0 | https://icons8.com bad_folder.png | CC BY-ND 3.0 | https://icons8.com

View File

@ -71,6 +71,14 @@ elseif(ENABLE_FDK)
target_compile_definitions(audio_core PUBLIC HAVE_FDK) target_compile_definitions(audio_core PUBLIC HAVE_FDK)
endif() endif()
if(ANDROID)
target_sources(audio_core PRIVATE
hle/mediandk_decoder.cpp
hle/mediandk_decoder.h
)
target_link_libraries(audio_core PRIVATE mediandk)
endif()
if(SDL2_FOUND) if(SDL2_FOUND)
target_link_libraries(audio_core PRIVATE SDL2) target_link_libraries(audio_core PRIVATE SDL2)
target_compile_definitions(audio_core PRIVATE HAVE_SDL2) target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
@ -80,4 +88,3 @@ if(ENABLE_CUBEB)
target_link_libraries(audio_core PRIVATE cubeb) target_link_libraries(audio_core PRIVATE cubeb)
target_compile_definitions(audio_core PUBLIC HAVE_CUBEB) target_compile_definitions(audio_core PUBLIC HAVE_CUBEB)
endif() endif()

View File

@ -12,6 +12,8 @@
#include "audio_core/hle/wmf_decoder.h" #include "audio_core/hle/wmf_decoder.h"
#elif HAVE_FFMPEG #elif HAVE_FFMPEG
#include "audio_core/hle/ffmpeg_decoder.h" #include "audio_core/hle/ffmpeg_decoder.h"
#elif ANDROID
#include "audio_core/hle/mediandk_decoder.h"
#elif HAVE_FDK #elif HAVE_FDK
#include "audio_core/hle/fdk_decoder.h" #include "audio_core/hle/fdk_decoder.h"
#endif #endif
@ -126,6 +128,8 @@ DspHle::Impl::Impl(DspHle& parent_, Memory::MemorySystem& memory) : parent(paren
decoder = std::make_unique<HLE::WMFDecoder>(memory); decoder = std::make_unique<HLE::WMFDecoder>(memory);
#elif defined(HAVE_FFMPEG) #elif defined(HAVE_FFMPEG)
decoder = std::make_unique<HLE::FFMPEGDecoder>(memory); decoder = std::make_unique<HLE::FFMPEGDecoder>(memory);
#elif ANDROID
decoder = std::make_unique<HLE::MediaNDKDecoder>(memory);
#elif defined(HAVE_FDK) #elif defined(HAVE_FDK)
decoder = std::make_unique<HLE::FDKDecoder>(memory); decoder = std::make_unique<HLE::FDKDecoder>(memory);
#else #else

View File

@ -0,0 +1,239 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <media/NdkMediaCodec.h>
#include <media/NdkMediaError.h>
#include <media/NdkMediaFormat.h>
#include <memory>
#include <vector>
#include "audio_core/hle/adts.h"
#include "audio_core/hle/mediandk_decoder.h"
namespace AudioCore::HLE {
struct AMediaCodecRelease {
void operator()(AMediaCodec* codec) const {
AMediaCodec_stop(codec);
AMediaCodec_delete(codec);
};
};
class MediaNDKDecoder::Impl {
public:
explicit Impl(Memory::MemorySystem& memory);
~Impl();
std::optional<BinaryResponse> ProcessRequest(const BinaryRequest& request);
bool SetMediaType(const ADTSData& adts_data);
private:
std::optional<BinaryResponse> Initalize(const BinaryRequest& request);
std::optional<BinaryResponse> Decode(const BinaryRequest& request);
Memory::MemorySystem& mMemory;
std::unique_ptr<AMediaCodec, AMediaCodecRelease> mDecoder;
// default: 2 channles, 48000 samplerate
ADTSData mADTSData{/* MPEG2 */ false, /*profile*/ 2, /*channels*/ 2,
/*channel_idx*/ 2, /*framecount*/ 0, /*samplerate_idx*/ 3,
/*length*/ 0, /*samplerate*/ 48000};
};
MediaNDKDecoder::Impl::Impl(Memory::MemorySystem& memory) : mMemory(memory) {
SetMediaType(mADTSData);
}
MediaNDKDecoder::Impl::~Impl() = default;
std::optional<BinaryResponse> MediaNDKDecoder::Impl::Initalize(const BinaryRequest& request) {
BinaryResponse response;
std::memcpy(&response, &request, sizeof(response));
response.unknown1 = 0x0;
return response;
}
bool MediaNDKDecoder::Impl::SetMediaType(const ADTSData& adts_data) {
const char* mime = "audio/mp4a-latm";
if (mDecoder && mADTSData.profile == adts_data.profile &&
mADTSData.channel_idx == adts_data.channel_idx &&
mADTSData.samplerate_idx == adts_data.samplerate_idx) {
return true;
}
mDecoder.reset(AMediaCodec_createDecoderByType(mime));
if (mDecoder == nullptr) {
return false;
}
u8 csd_0[2];
csd_0[0] = static_cast<u8>((adts_data.profile << 3) | (adts_data.samplerate_idx >> 1));
csd_0[1] =
static_cast<u8>(((adts_data.samplerate_idx << 7) & 0x80) | (adts_data.channel_idx << 3));
AMediaFormat* format = AMediaFormat_new();
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, mime);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, adts_data.samplerate);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, adts_data.channels);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_IS_ADTS, 1);
AMediaFormat_setBuffer(format, "csd-0", csd_0, sizeof(csd_0));
media_status_t status = AMediaCodec_configure(mDecoder.get(), format, NULL, NULL, 0);
if (status != AMEDIA_OK) {
AMediaFormat_delete(format);
mDecoder.reset();
return false;
}
status = AMediaCodec_start(mDecoder.get());
if (status != AMEDIA_OK) {
AMediaFormat_delete(format);
mDecoder.reset();
return false;
}
AMediaFormat_delete(format);
mADTSData = adts_data;
return true;
}
std::optional<BinaryResponse> MediaNDKDecoder::Impl::ProcessRequest(const BinaryRequest& request) {
if (request.codec != DecoderCodec::AAC) {
LOG_ERROR(Audio_DSP, "AAC Decoder cannot handle such codec: {}",
static_cast<u16>(request.codec));
return {};
}
switch (request.cmd) {
case DecoderCommand::Init: {
return Initalize(request);
}
case DecoderCommand::Decode: {
return Decode(request);
}
case DecoderCommand::Unknown: {
BinaryResponse response;
std::memcpy(&response, &request, sizeof(response));
response.unknown1 = 0x0;
return response;
}
default:
LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", static_cast<u16>(request.cmd));
return {};
}
}
std::optional<BinaryResponse> MediaNDKDecoder::Impl::Decode(const BinaryRequest& request) {
BinaryResponse response;
response.codec = request.codec;
response.cmd = request.cmd;
response.size = request.size;
response.num_samples = 1024;
if (request.src_addr < Memory::FCRAM_PADDR ||
request.src_addr + request.size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}", request.src_addr);
return response;
}
u8* data = mMemory.GetFCRAMPointer(request.src_addr - Memory::FCRAM_PADDR);
ADTSData adts_data = ParseADTS(reinterpret_cast<const char*>(data));
SetMediaType(adts_data);
response.num_channels = adts_data.channels;
if (!mDecoder) {
LOG_ERROR(Audio_DSP, "Missing decoder for profile: {}, channels: {}, samplerate: {}",
adts_data.profile, adts_data.channels, adts_data.samplerate);
return {};
}
// input
constexpr int timeout = 160;
std::size_t buffer_size = 0;
u8* buffer = nullptr;
ssize_t buffer_index = AMediaCodec_dequeueInputBuffer(mDecoder.get(), timeout);
if (buffer_index < 0) {
LOG_ERROR(Audio_DSP, "Failed to enqueue the input samples: {}", buffer_index);
return response;
}
buffer = AMediaCodec_getInputBuffer(mDecoder.get(), buffer_index, &buffer_size);
if (buffer_size < request.size) {
return response;
}
std::memcpy(buffer, data, request.size);
media_status_t status =
AMediaCodec_queueInputBuffer(mDecoder.get(), buffer_index, 0, request.size, 0, 0);
if (status != AMEDIA_OK) {
LOG_WARNING(Audio_DSP, "Try queue input buffer again later!");
return response;
}
// output
AMediaCodecBufferInfo info;
std::array<std::vector<u16>, 2> out_streams;
buffer_index = AMediaCodec_dequeueOutputBuffer(mDecoder.get(), &info, timeout);
switch (buffer_index) {
case AMEDIACODEC_INFO_TRY_AGAIN_LATER:
LOG_WARNING(Audio_DSP, "Failed to dequeue output buffer: timeout!");
break;
case AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED:
LOG_WARNING(Audio_DSP, "Failed to dequeue output buffer: buffers changed!");
break;
case AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED: {
AMediaFormat* format = AMediaCodec_getOutputFormat(mDecoder.get());
LOG_WARNING(Audio_DSP, "output format: {}", AMediaFormat_toString(format));
AMediaFormat_delete(format);
buffer_index = AMediaCodec_dequeueOutputBuffer(mDecoder.get(), &info, timeout);
}
default: {
int offset = info.offset;
buffer = AMediaCodec_getOutputBuffer(mDecoder.get(), buffer_index, &buffer_size);
while (offset < info.size) {
for (int channel = 0; channel < response.num_channels; channel++) {
u16 pcm_data;
std::memcpy(&pcm_data, buffer + offset, sizeof(pcm_data));
out_streams[channel].push_back(pcm_data);
offset += sizeof(pcm_data);
}
}
AMediaCodec_releaseOutputBuffer(mDecoder.get(), buffer_index, info.size != 0);
}
}
// transfer the decoded buffer from vector to the FCRAM
size_t stream0_size = out_streams[0].size() * sizeof(u16);
if (stream0_size != 0) {
if (request.dst_addr_ch0 < Memory::FCRAM_PADDR ||
request.dst_addr_ch0 + stream0_size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch0 {:08x}", request.dst_addr_ch0);
return response;
}
std::memcpy(mMemory.GetFCRAMPointer(request.dst_addr_ch0 - Memory::FCRAM_PADDR),
out_streams[0].data(), stream0_size);
}
size_t stream1_size = out_streams[1].size() * sizeof(u16);
if (stream1_size != 0) {
if (request.dst_addr_ch1 < Memory::FCRAM_PADDR ||
request.dst_addr_ch1 + stream1_size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch1 {:08x}", request.dst_addr_ch1);
return response;
}
std::memcpy(mMemory.GetFCRAMPointer(request.dst_addr_ch1 - Memory::FCRAM_PADDR),
out_streams[1].data(), stream1_size);
}
return response;
}
MediaNDKDecoder::MediaNDKDecoder(Memory::MemorySystem& memory)
: impl(std::make_unique<Impl>(memory)) {}
MediaNDKDecoder::~MediaNDKDecoder() = default;
std::optional<BinaryResponse> MediaNDKDecoder::ProcessRequest(const BinaryRequest& request) {
return impl->ProcessRequest(request);
}
bool MediaNDKDecoder::IsValid() const {
return true;
}
} // namespace AudioCore::HLE

View File

@ -0,0 +1,22 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "audio_core/hle/decoder.h"
namespace AudioCore::HLE {
class MediaNDKDecoder final : public DecoderBase {
public:
explicit MediaNDKDecoder(Memory::MemorySystem& memory);
~MediaNDKDecoder() override;
std::optional<BinaryResponse> ProcessRequest(const BinaryRequest& request) override;
bool IsValid() const override;
private:
class Impl;
std::unique_ptr<Impl> impl;
};
} // namespace AudioCore::HLE

View File

@ -128,6 +128,10 @@ void Config::ReadValues() {
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
Settings::values.use_vsync_new = Settings::values.use_vsync_new =
static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync_new", 1)); static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync_new", 1));
Settings::values.texture_filter_name =
sdl2_config->GetString("Renderer", "texture_filter_name", "none");
Settings::values.texture_filter_factor =
sdl2_config->GetInteger("Renderer", "texture_filter_factor", 1);
Settings::values.render_3d = static_cast<Settings::StereoRenderOption>( Settings::values.render_3d = static_cast<Settings::StereoRenderOption>(
sdl2_config->GetInteger("Renderer", "render_3d", 0)); sdl2_config->GetInteger("Renderer", "render_3d", 0));

View File

@ -126,6 +126,10 @@ use_disk_shader_cache =
# factor for the 3DS resolution # factor for the 3DS resolution
resolution_factor = resolution_factor =
# Texture filter name and scale factor
texture_filter_name =
texture_filter_factor =
# Turns on the frame limiter, which will limit frames output to the target game speed # Turns on the frame limiter, which will limit frames output to the target game speed
# 0: Off, 1: On (default) # 0: Off, 1: On (default)
use_frame_limit = use_frame_limit =

View File

@ -448,6 +448,13 @@ void Config::ReadRendererValues() {
Settings::values.bg_green = ReadSetting(QStringLiteral("bg_green"), 0.0).toFloat(); Settings::values.bg_green = ReadSetting(QStringLiteral("bg_green"), 0.0).toFloat();
Settings::values.bg_blue = ReadSetting(QStringLiteral("bg_blue"), 0.0).toFloat(); Settings::values.bg_blue = ReadSetting(QStringLiteral("bg_blue"), 0.0).toFloat();
Settings::values.texture_filter_name =
ReadSetting(QStringLiteral("texture_filter_name"), QStringLiteral("none"))
.toString()
.toStdString();
Settings::values.texture_filter_factor =
ReadSetting(QStringLiteral("texture_filter_factor"), 1).toInt();
qt_config->endGroup(); qt_config->endGroup();
} }
@ -879,6 +886,12 @@ void Config::SaveRendererValues() {
WriteSetting(QStringLiteral("bg_green"), (double)Settings::values.bg_green, 0.0); WriteSetting(QStringLiteral("bg_green"), (double)Settings::values.bg_green, 0.0);
WriteSetting(QStringLiteral("bg_blue"), (double)Settings::values.bg_blue, 0.0); WriteSetting(QStringLiteral("bg_blue"), (double)Settings::values.bg_blue, 0.0);
WriteSetting(QStringLiteral("texture_filter_name"),
QString::fromStdString(Settings::values.texture_filter_name),
QStringLiteral("none"));
WriteSetting(QStringLiteral("texture_filter_factor"), Settings::values.texture_filter_factor,
1);
qt_config->endGroup(); qt_config->endGroup();
} }

View File

@ -8,10 +8,18 @@
#include "core/settings.h" #include "core/settings.h"
#include "ui_configure_enhancements.h" #include "ui_configure_enhancements.h"
#include "video_core/renderer_opengl/post_processing_opengl.h" #include "video_core/renderer_opengl/post_processing_opengl.h"
#include "video_core/renderer_opengl/texture_filters/texture_filter_manager.h"
ConfigureEnhancements::ConfigureEnhancements(QWidget* parent) ConfigureEnhancements::ConfigureEnhancements(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureEnhancements) { : QWidget(parent), ui(new Ui::ConfigureEnhancements) {
ui->setupUi(this); ui->setupUi(this);
for (const auto& filter : OpenGL::TextureFilterManager::TextureFilterMap())
ui->texture_filter_combobox->addItem(QString::fromStdString(filter.first.data()));
connect(ui->texture_filter_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigureEnhancements::updateTextureFilter);
SetConfiguration(); SetConfiguration();
ui->layoutBox->setEnabled(!Settings::values.custom_layout); ui->layoutBox->setEnabled(!Settings::values.custom_layout);
@ -52,6 +60,15 @@ void ConfigureEnhancements::SetConfiguration() {
ui->factor_3d->setValue(Settings::values.factor_3d); ui->factor_3d->setValue(Settings::values.factor_3d);
updateShaders(Settings::values.render_3d); updateShaders(Settings::values.render_3d);
ui->toggle_linear_filter->setChecked(Settings::values.filter_mode); ui->toggle_linear_filter->setChecked(Settings::values.filter_mode);
ui->texture_scale_spinbox->setValue(Settings::values.texture_filter_factor);
int tex_filter_idx = ui->texture_filter_combobox->findText(
QString::fromStdString(Settings::values.texture_filter_name));
if (tex_filter_idx == -1) {
ui->texture_filter_combobox->setCurrentIndex(0);
} else {
ui->texture_filter_combobox->setCurrentIndex(tex_filter_idx);
}
updateTextureFilter(tex_filter_idx);
ui->layout_combobox->setCurrentIndex(static_cast<int>(Settings::values.layout_option)); ui->layout_combobox->setCurrentIndex(static_cast<int>(Settings::values.layout_option));
ui->swap_screen->setChecked(Settings::values.swap_screen); ui->swap_screen->setChecked(Settings::values.swap_screen);
ui->toggle_disk_shader_cache->setChecked(Settings::values.use_hw_shader && ui->toggle_disk_shader_cache->setChecked(Settings::values.use_hw_shader &&
@ -88,6 +105,17 @@ void ConfigureEnhancements::updateShaders(Settings::StereoRenderOption stereo_op
} }
} }
void ConfigureEnhancements::updateTextureFilter(int index) {
if (index == -1)
return;
ui->texture_filter_group->setEnabled(index != 0);
const auto& clamp = OpenGL::TextureFilterManager::TextureFilterMap()
.at(ui->texture_filter_combobox->currentText().toStdString())
.clamp_scale;
ui->texture_scale_spinbox->setMinimum(clamp.min);
ui->texture_scale_spinbox->setMaximum(clamp.max);
}
void ConfigureEnhancements::RetranslateUI() { void ConfigureEnhancements::RetranslateUI() {
ui->retranslateUi(this); ui->retranslateUi(this);
} }
@ -101,6 +129,8 @@ void ConfigureEnhancements::ApplyConfiguration() {
Settings::values.pp_shader_name = Settings::values.pp_shader_name =
ui->shader_combobox->itemText(ui->shader_combobox->currentIndex()).toStdString(); ui->shader_combobox->itemText(ui->shader_combobox->currentIndex()).toStdString();
Settings::values.filter_mode = ui->toggle_linear_filter->isChecked(); Settings::values.filter_mode = ui->toggle_linear_filter->isChecked();
Settings::values.texture_filter_name = ui->texture_filter_combobox->currentText().toStdString();
Settings::values.texture_filter_factor = ui->texture_scale_spinbox->value();
Settings::values.layout_option = Settings::values.layout_option =
static_cast<Settings::LayoutOption>(ui->layout_combobox->currentIndex()); static_cast<Settings::LayoutOption>(ui->layout_combobox->currentIndex());
Settings::values.swap_screen = ui->swap_screen->isChecked(); Settings::values.swap_screen = ui->swap_screen->isChecked();

View File

@ -27,6 +27,7 @@ public:
private: private:
void updateShaders(Settings::StereoRenderOption stereo_option); void updateShaders(Settings::StereoRenderOption stereo_option);
void updateTextureFilter(int index);
Ui::ConfigureEnhancements* ui; Ui::ConfigureEnhancements* ui;
QColor bg_color; QColor bg_color;

View File

@ -117,6 +117,56 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Texture Filter</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="texture_filter_combobox"/>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="texture_filter_group" native="true">
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="leftMargin">
<number>16</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Texture Scale Factor</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="texture_scale_spinbox">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -1556,7 +1556,7 @@ void GMainWindow::ToggleWindowMode() {
// Render in the main window... // Render in the main window...
render_window->BackupGeometry(); render_window->BackupGeometry();
ui.horizontalLayout->addWidget(render_window); ui.horizontalLayout->addWidget(render_window);
render_window->setFocusPolicy(Qt::ClickFocus); render_window->setFocusPolicy(Qt::StrongFocus);
if (emulation_running) { if (emulation_running) {
render_window->setVisible(true); render_window->setVisible(true);
render_window->setFocus(); render_window->setFocus();

View File

@ -3,6 +3,7 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <array> #include <array>
#include <limits>
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <unordered_map> #include <unordered_map>
@ -541,11 +542,11 @@ void CopyDir(const std::string& source_path, const std::string& dest_path) {
std::optional<std::string> GetCurrentDir() { std::optional<std::string> GetCurrentDir() {
// Get the current working directory (getcwd uses malloc) // Get the current working directory (getcwd uses malloc)
#ifdef _WIN32 #ifdef _WIN32
wchar_t* dir; wchar_t* dir = _wgetcwd(nullptr, 0);
if (!(dir = _wgetcwd(nullptr, 0))) { if (!dir) {
#else #else
char* dir; char* dir = getcwd(nullptr, 0);
if (!(dir = getcwd(nullptr, 0))) { if (!dir) {
#endif #endif
LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg()); LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
return {}; return {};
@ -891,16 +892,16 @@ IOFile::~IOFile() {
Close(); Close();
} }
IOFile::IOFile(IOFile&& other) { IOFile::IOFile(IOFile&& other) noexcept {
Swap(other); Swap(other);
} }
IOFile& IOFile::operator=(IOFile&& other) { IOFile& IOFile::operator=(IOFile&& other) noexcept {
Swap(other); Swap(other);
return *this; return *this;
} }
void IOFile::Swap(IOFile& other) { void IOFile::Swap(IOFile& other) noexcept {
std::swap(m_file, other.m_file); std::swap(m_file, other.m_file);
std::swap(m_good, other.m_good); std::swap(m_good, other.m_good);
std::swap(filename, other.filename); std::swap(filename, other.filename);
@ -915,15 +916,16 @@ bool IOFile::Open() {
if (flags != 0) { if (flags != 0) {
m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(), m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(),
Common::UTF8ToUTF16W(openmode).c_str(), flags); Common::UTF8ToUTF16W(openmode).c_str(), flags);
m_good = m_file != nullptr;
} else { } else {
_wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(), m_good = _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(),
Common::UTF8ToUTF16W(openmode).c_str()); Common::UTF8ToUTF16W(openmode).c_str()) == 0;
} }
#else #else
m_file = fopen(filename.c_str(), openmode.c_str()); m_file = std::fopen(filename.c_str(), openmode.c_str());
m_good = m_file != nullptr;
#endif #endif
m_good = IsOpen();
return m_good; return m_good;
} }
@ -953,7 +955,7 @@ u64 IOFile::Tell() const {
if (IsOpen()) if (IsOpen())
return ftello(m_file); return ftello(m_file);
return -1; return std::numeric_limits<u64>::max();
} }
bool IOFile::Flush() { bool IOFile::Flush() {

View File

@ -219,10 +219,10 @@ public:
~IOFile(); ~IOFile();
IOFile(IOFile&& other); IOFile(IOFile&& other) noexcept;
IOFile& operator=(IOFile&& other); IOFile& operator=(IOFile&& other) noexcept;
void Swap(IOFile& other); void Swap(IOFile& other) noexcept;
bool Close(); bool Close();

View File

@ -28,11 +28,8 @@ namespace Common {
#ifdef _MSC_VER #ifdef _MSC_VER
// Sets the debugger-visible name of the current thread. // Sets the debugger-visible name of the current thread.
// Uses undocumented (actually, it is now documented) trick. // Uses trick documented in:
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsdebug/html/vxtsksettingthreadname.asp // https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-set-a-thread-name-in-native-code
// This is implemented much nicer in upcoming msvc++, see:
// http://msdn.microsoft.com/en-us/library/xcb2z8hs(VS.100).aspx
void SetCurrentThreadName(const char* name) { void SetCurrentThreadName(const char* name) {
static const DWORD MS_VC_EXCEPTION = 0x406D1388; static const DWORD MS_VC_EXCEPTION = 0x406D1388;
@ -47,7 +44,7 @@ void SetCurrentThreadName(const char* name) {
info.dwType = 0x1000; info.dwType = 0x1000;
info.szName = name; info.szName = name;
info.dwThreadID = -1; // dwThreadID; info.dwThreadID = static_cast<DWORD>(-1);
info.dwFlags = 0; info.dwFlags = 0;
__try { __try {

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include <algorithm>
#include <array> #include <array>
#include <deque> #include <deque>
#include <boost/serialization/deque.hpp> #include <boost/serialization/deque.hpp>

View File

@ -70,6 +70,7 @@ void Module::PortConfig::Clear() {
completion_event->Clear(); completion_event->Clear();
buffer_error_interrupt_event->Clear(); buffer_error_interrupt_event->Clear();
vsync_interrupt_event->Clear(); vsync_interrupt_event->Clear();
vsync_timings.clear();
is_receiving = false; is_receiving = false;
is_active = false; is_active = false;
is_pending_receiving = false; is_pending_receiving = false;
@ -143,6 +144,27 @@ void Module::CompletionEventCallBack(u64 port_id, s64) {
port.completion_event->Signal(); port.completion_event->Signal();
} }
static constexpr std::size_t MaxVsyncTimings = 5;
void Module::VsyncInterruptEventCallBack(u64 port_id, s64 cycles_late) {
PortConfig& port = ports[port_id];
const CameraConfig& camera = cameras[port.camera_id];
if (!port.is_active) {
return;
}
port.vsync_timings.emplace_front(system.CoreTiming().GetGlobalTimeUs().count());
if (port.vsync_timings.size() > MaxVsyncTimings) {
port.vsync_timings.pop_back();
}
port.vsync_interrupt_event->Signal();
system.CoreTiming().ScheduleEvent(
msToCycles(LATENCY_BY_FRAME_RATE[static_cast<int>(camera.frame_rate)]) - cycles_late,
vsync_interrupt_event_callback, port_id);
}
void Module::StartReceiving(int port_id) { void Module::StartReceiving(int port_id) {
PortConfig& port = ports[port_id]; PortConfig& port = ports[port_id];
port.is_receiving = true; port.is_receiving = true;
@ -183,6 +205,9 @@ void Module::ActivatePort(int port_id, int camera_id) {
} }
ports[port_id].is_active = true; ports[port_id].is_active = true;
ports[port_id].camera_id = camera_id; ports[port_id].camera_id = camera_id;
system.CoreTiming().ScheduleEvent(
msToCycles(LATENCY_BY_FRAME_RATE[static_cast<int>(cameras[camera_id].frame_rate)]),
vsync_interrupt_event_callback, port_id);
} }
template <int max_index> template <int max_index>
@ -641,6 +666,7 @@ void Module::Interface::Activate(Kernel::HLERequestContext& ctx) {
cam->ports[i].is_busy = false; cam->ports[i].is_busy = false;
} }
cam->ports[i].is_active = false; cam->ports[i].is_active = false;
cam->system.CoreTiming().UnscheduleEvent(cam->vsync_interrupt_event_callback, i);
} }
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
} else if (camera_select[0] && camera_select[1]) { } else if (camera_select[0] && camera_select[1]) {
@ -870,6 +896,34 @@ void Module::Interface::SynchronizeVsyncTiming(Kernel::HLERequestContext& ctx) {
camera_select1, camera_select2); camera_select1, camera_select2);
} }
void Module::Interface::GetLatestVsyncTiming(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x2A, 2, 0);
const PortSet port_select(rp.Pop<u8>());
const u32 count = rp.Pop<u32>();
if (!port_select.IsSingle() || count > MaxVsyncTimings) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(ERROR_OUT_OF_RANGE);
rb.PushStaticBuffer({}, 0);
return;
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
const std::size_t port_id = port_select.m_val == 1 ? 0 : 1;
std::vector<u8> out(count * sizeof(s64_le));
std::size_t offset = 0;
for (const s64_le timing : cam->ports[port_id].vsync_timings) {
std::memcpy(out.data() + offset * sizeof(timing), &timing, sizeof(timing));
offset++;
if (offset >= count) {
break;
}
}
rb.PushStaticBuffer(out, 0);
}
void Module::Interface::GetStereoCameraCalibrationData(Kernel::HLERequestContext& ctx) { void Module::Interface::GetStereoCameraCalibrationData(Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb = IPC::RequestParser(ctx, 0x2B, 0, 0).MakeBuilder(17, 0); IPC::RequestBuilder rb = IPC::RequestParser(ctx, 0x2B, 0, 0).MakeBuilder(17, 0);
@ -1042,6 +1096,10 @@ Module::Module(Core::System& system) : system(system) {
completion_event_callback = system.CoreTiming().RegisterEvent( completion_event_callback = system.CoreTiming().RegisterEvent(
"CAM::CompletionEventCallBack", "CAM::CompletionEventCallBack",
[this](u64 userdata, s64 cycles_late) { CompletionEventCallBack(userdata, cycles_late); }); [this](u64 userdata, s64 cycles_late) { CompletionEventCallBack(userdata, cycles_late); });
vsync_interrupt_event_callback = system.CoreTiming().RegisterEvent(
"CAM::VsyncInterruptEventCallBack", [this](u64 userdata, s64 cycles_late) {
VsyncInterruptEventCallBack(userdata, cycles_late);
});
} }
Module::~Module() { Module::~Module() {

View File

@ -5,6 +5,7 @@
#pragma once #pragma once
#include <array> #include <array>
#include <deque>
#include <future> #include <future>
#include <memory> #include <memory>
#include <vector> #include <vector>
@ -629,6 +630,21 @@ public:
*/ */
void SynchronizeVsyncTiming(Kernel::HLERequestContext& ctx); void SynchronizeVsyncTiming(Kernel::HLERequestContext& ctx);
/**
* Gets the vsync timing record of the specified camera for the specified number of signals.
* Inputs:
* 0: 0x002A0080
* 1: Port
* 2: Number of timings to get
* 64: ((PastTimings * 8) << 14) | 2
* 65: s64* TimingsOutput
* Outputs:
* 0: 0x002A0042
* 1: ResultCode
* 2-3: Output static buffer
*/
void GetLatestVsyncTiming(Kernel::HLERequestContext& ctx);
/** /**
* Returns calibration data relating the outside cameras to each other, for use in AR * Returns calibration data relating the outside cameras to each other, for use in AR
* applications. * applications.
@ -729,6 +745,7 @@ public:
private: private:
void CompletionEventCallBack(u64 port_id, s64); void CompletionEventCallBack(u64 port_id, s64);
void VsyncInterruptEventCallBack(u64 port_id, s64 cycles_late);
// Starts a receiving process on the specified port. This can only be called when is_busy = true // Starts a receiving process on the specified port. This can only be called when is_busy = true
// and is_receiving = false. // and is_receiving = false.
@ -767,7 +784,7 @@ private:
std::unique_ptr<Camera::CameraInterface> impl; std::unique_ptr<Camera::CameraInterface> impl;
std::array<ContextConfig, 2> contexts; std::array<ContextConfig, 2> contexts;
int current_context{0}; int current_context{0};
FrameRate frame_rate{FrameRate::Rate_5}; FrameRate frame_rate{FrameRate::Rate_15};
private: private:
template <class Archive> template <class Archive>
@ -806,6 +823,8 @@ private:
std::shared_ptr<Kernel::Event> buffer_error_interrupt_event; std::shared_ptr<Kernel::Event> buffer_error_interrupt_event;
std::shared_ptr<Kernel::Event> vsync_interrupt_event; std::shared_ptr<Kernel::Event> vsync_interrupt_event;
std::deque<s64> vsync_timings;
std::future<std::vector<u16>> capture_result; // will hold the received frame. std::future<std::vector<u16>> capture_result; // will hold the received frame.
Kernel::Process* dest_process{nullptr}; Kernel::Process* dest_process{nullptr};
VAddr dest{0}; // the destination address of the receiving process VAddr dest{0}; // the destination address of the receiving process
@ -843,8 +862,8 @@ private:
Core::System& system; Core::System& system;
std::array<CameraConfig, NumCameras> cameras; std::array<CameraConfig, NumCameras> cameras;
std::array<PortConfig, 2> ports; std::array<PortConfig, 2> ports;
// TODO: Make this *const Core::TimingEventType* completion_event_callback;
const Core::TimingEventType* completion_event_callback; Core::TimingEventType* vsync_interrupt_event_callback;
std::atomic<bool> is_camera_reload_pending{false}; std::atomic<bool> is_camera_reload_pending{false};
template <class Archive> template <class Archive>

View File

@ -51,7 +51,7 @@ CAM_C::CAM_C(std::shared_ptr<Module> cam) : Module::Interface(std::move(cam), "c
{0x00270140, nullptr, "SetAutoWhiteBalanceWindow"}, {0x00270140, nullptr, "SetAutoWhiteBalanceWindow"},
{0x00280080, nullptr, "SetNoiseFilter"}, {0x00280080, nullptr, "SetNoiseFilter"},
{0x00290080, &CAM_C::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"}, {0x00290080, &CAM_C::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"},
{0x002A0080, nullptr, "GetLatestVsyncTiming"}, {0x002A0080, &CAM_C::GetLatestVsyncTiming, "GetLatestVsyncTiming"},
{0x002B0000, &CAM_C::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"}, {0x002B0000, &CAM_C::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"},
{0x002C0400, nullptr, "SetStereoCameraCalibrationData"}, {0x002C0400, nullptr, "SetStereoCameraCalibrationData"},
{0x002D00C0, nullptr, "WriteRegisterI2c"}, {0x002D00C0, nullptr, "WriteRegisterI2c"},

View File

@ -51,7 +51,7 @@ CAM_S::CAM_S(std::shared_ptr<Module> cam) : Module::Interface(std::move(cam), "c
{0x00270140, nullptr, "SetAutoWhiteBalanceWindow"}, {0x00270140, nullptr, "SetAutoWhiteBalanceWindow"},
{0x00280080, nullptr, "SetNoiseFilter"}, {0x00280080, nullptr, "SetNoiseFilter"},
{0x00290080, &CAM_S::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"}, {0x00290080, &CAM_S::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"},
{0x002A0080, nullptr, "GetLatestVsyncTiming"}, {0x002A0080, &CAM_S::GetLatestVsyncTiming, "GetLatestVsyncTiming"},
{0x002B0000, &CAM_S::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"}, {0x002B0000, &CAM_S::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"},
{0x002C0400, nullptr, "SetStereoCameraCalibrationData"}, {0x002C0400, nullptr, "SetStereoCameraCalibrationData"},
{0x002D00C0, nullptr, "WriteRegisterI2c"}, {0x002D00C0, nullptr, "WriteRegisterI2c"},

View File

@ -51,7 +51,7 @@ CAM_U::CAM_U(std::shared_ptr<Module> cam) : Module::Interface(std::move(cam), "c
{0x00270140, nullptr, "SetAutoWhiteBalanceWindow"}, {0x00270140, nullptr, "SetAutoWhiteBalanceWindow"},
{0x00280080, nullptr, "SetNoiseFilter"}, {0x00280080, nullptr, "SetNoiseFilter"},
{0x00290080, &CAM_U::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"}, {0x00290080, &CAM_U::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"},
{0x002A0080, nullptr, "GetLatestVsyncTiming"}, {0x002A0080, &CAM_U::GetLatestVsyncTiming, "GetLatestVsyncTiming"},
{0x002B0000, &CAM_U::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"}, {0x002B0000, &CAM_U::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"},
{0x002C0400, nullptr, "SetStereoCameraCalibrationData"}, {0x002C0400, nullptr, "SetStereoCameraCalibrationData"},
{0x002D00C0, nullptr, "WriteRegisterI2c"}, {0x002D00C0, nullptr, "WriteRegisterI2c"},

View File

@ -101,7 +101,8 @@ static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exact
} // namespace } // namespace
static const EULAVersion MAX_EULA_VERSION = {0x7F, 0x7F}; static const EULAVersion MAX_EULA_VERSION = {0x7F, 0x7F};
static const ConsoleModelInfo CONSOLE_MODEL = {NINTENDO_3DS_XL, {0, 0, 0}}; static const ConsoleModelInfo CONSOLE_MODEL_OLD = {NINTENDO_3DS_XL, {0, 0, 0}};
static const ConsoleModelInfo CONSOLE_MODEL_NEW = {NEW_NINTENDO_3DS_XL, {0, 0, 0}};
static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN; static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN;
static const UsernameBlock CONSOLE_USERNAME_BLOCK = {u"CITRA", 0, 0}; static const UsernameBlock CONSOLE_USERNAME_BLOCK = {u"CITRA", 0, 0};
static const BirthdayBlock PROFILE_BIRTHDAY = {3, 25}; // March 25th, 2014 static const BirthdayBlock PROFILE_BIRTHDAY = {3, 25}; // March 25th, 2014
@ -244,6 +245,18 @@ void Module::Interface::GetSystemModel(Kernel::HLERequestContext& ctx) {
// TODO(Subv): Find out the correct error codes // TODO(Subv): Find out the correct error codes
rb.Push(cfg->GetConfigInfoBlock(ConsoleModelBlockID, 4, 0x8, reinterpret_cast<u8*>(&data))); rb.Push(cfg->GetConfigInfoBlock(ConsoleModelBlockID, 4, 0x8, reinterpret_cast<u8*>(&data)));
ConsoleModelInfo model;
std::memcpy(&model, &data, 4);
if ((model.model == NINTENDO_3DS || model.model == NINTENDO_3DS_XL ||
model.model == NINTENDO_2DS) &&
Settings::values.is_new_3ds) {
model.model = NEW_NINTENDO_3DS_XL;
} else if ((model.model == NEW_NINTENDO_3DS || model.model == NEW_NINTENDO_3DS_XL ||
model.model == NEW_NINTENDO_2DS_XL) &&
!Settings::values.is_new_3ds) {
model.model = NINTENDO_3DS_XL;
}
std::memcpy(&data, &model, 4);
rb.Push<u8>(data & 0xFF); rb.Push<u8>(data & 0xFF);
} }
@ -522,7 +535,8 @@ ResultCode Module::FormatConfig() {
if (!res.IsSuccess()) if (!res.IsSuccess())
return res; return res;
res = CreateConfigInfoBlk(ConsoleModelBlockID, sizeof(CONSOLE_MODEL), 0xC, &CONSOLE_MODEL); res = CreateConfigInfoBlk(ConsoleModelBlockID, sizeof(CONSOLE_MODEL_OLD), 0xC,
&CONSOLE_MODEL_OLD);
if (!res.IsSuccess()) if (!res.IsSuccess())
return res; return res;
@ -536,7 +550,7 @@ ResultCode Module::FormatConfig() {
if (!res.IsSuccess()) if (!res.IsSuccess())
return res; return res;
return RESULT_SUCCESS; return RESULT_SUCCESS;
} } // namespace Service::CFG
ResultCode Module::LoadConfigNANDSaveFile() { ResultCode Module::LoadConfigNANDSaveFile() {
std::string nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); std::string nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);

View File

@ -104,7 +104,7 @@ void File::Write(Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
const FileSessionSlot* file = GetSessionData(ctx.Session()); FileSessionSlot* file = GetSessionData(ctx.Session());
// Subfiles can not be written to // Subfiles can not be written to
if (file->subfile) { if (file->subfile) {
@ -117,6 +117,10 @@ void File::Write(Kernel::HLERequestContext& ctx) {
std::vector<u8> data(length); std::vector<u8> data(length);
buffer.Read(data.data(), 0, data.size()); buffer.Read(data.data(), 0, data.size());
ResultVal<std::size_t> written = backend->Write(offset, data.size(), flush != 0, data.data()); ResultVal<std::size_t> written = backend->Write(offset, data.size(), flush != 0, data.data());
// Update file size
file->size = backend->GetSize();
if (written.Failed()) { if (written.Failed()) {
rb.Push(written.Code()); rb.Push(written.Code());
rb.Push<u32>(0); rb.Push<u32>(0);

View File

@ -13,6 +13,7 @@
#include "core/hle/service/mic_u.h" #include "core/hle/service/mic_u.h"
#include "core/settings.h" #include "core/settings.h"
#include "video_core/renderer_base.h" #include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/texture_filters/texture_filter_manager.h"
#include "video_core/video_core.h" #include "video_core/video_core.h"
namespace Settings { namespace Settings {
@ -38,6 +39,9 @@ void Apply() {
VideoCore::g_renderer_sampler_update_requested = true; VideoCore::g_renderer_sampler_update_requested = true;
VideoCore::g_renderer_shader_update_requested = true; VideoCore::g_renderer_shader_update_requested = true;
OpenGL::TextureFilterManager::GetInstance().SetTextureFilter(values.texture_filter_name,
values.texture_filter_factor);
auto& system = Core::System::GetInstance(); auto& system = Core::System::GetInstance();
if (system.IsPoweredOn()) { if (system.IsPoweredOn()) {
Core::DSP().SetSink(values.sink_id, values.audio_device_id); Core::DSP().SetSink(values.sink_id, values.audio_device_id);
@ -83,6 +87,8 @@ void LogSettings() {
LogSetting("Renderer_FrameLimit", Settings::values.frame_limit); LogSetting("Renderer_FrameLimit", Settings::values.frame_limit);
LogSetting("Renderer_PostProcessingShader", Settings::values.pp_shader_name); LogSetting("Renderer_PostProcessingShader", Settings::values.pp_shader_name);
LogSetting("Renderer_FilterMode", Settings::values.filter_mode); LogSetting("Renderer_FilterMode", Settings::values.filter_mode);
LogSetting("Renderer_TextureFilterFactor", Settings::values.texture_filter_factor);
LogSetting("Renderer_TextureFilterName", Settings::values.texture_filter_name);
LogSetting("Stereoscopy_Render3d", static_cast<int>(Settings::values.render_3d)); LogSetting("Stereoscopy_Render3d", static_cast<int>(Settings::values.render_3d));
LogSetting("Stereoscopy_Factor3d", Settings::values.factor_3d); LogSetting("Stereoscopy_Factor3d", Settings::values.factor_3d);
LogSetting("Layout_LayoutOption", static_cast<int>(Settings::values.layout_option)); LogSetting("Layout_LayoutOption", static_cast<int>(Settings::values.layout_option));

View File

@ -147,6 +147,8 @@ struct Values {
u16 resolution_factor; u16 resolution_factor;
bool use_frame_limit; bool use_frame_limit;
u16 frame_limit; u16 frame_limit;
u16 texture_filter_factor;
std::string texture_filter_name;
LayoutOption layout_option; LayoutOption layout_option;
bool swap_screen; bool swap_screen;

View File

@ -50,6 +50,15 @@ add_library(video_core STATIC
renderer_opengl/post_processing_opengl.h renderer_opengl/post_processing_opengl.h
renderer_opengl/renderer_opengl.cpp renderer_opengl/renderer_opengl.cpp
renderer_opengl/renderer_opengl.h renderer_opengl/renderer_opengl.h
renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.cpp
renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h
renderer_opengl/texture_filters/bicubic/bicubic.cpp
renderer_opengl/texture_filters/bicubic/bicubic.h
renderer_opengl/texture_filters/texture_filter_interface.h
renderer_opengl/texture_filters/texture_filter_manager.cpp
renderer_opengl/texture_filters/texture_filter_manager.h
renderer_opengl/texture_filters/xbrz/xbrz_freescale.cpp
renderer_opengl/texture_filters/xbrz/xbrz_freescale.h
shader/debug_data.h shader/debug_data.h
shader/shader.cpp shader/shader.cpp
shader/shader.h shader/shader.h
@ -80,6 +89,35 @@ add_library(video_core STATIC
video_core.h video_core.h
) )
set(SHADER_FILES
renderer_opengl/texture_filters/anime4k/refine.frag
renderer_opengl/texture_filters/anime4k/refine.vert
renderer_opengl/texture_filters/anime4k/x_gradient.frag
renderer_opengl/texture_filters/anime4k/y_gradient.frag
renderer_opengl/texture_filters/anime4k/y_gradient.vert
renderer_opengl/texture_filters/bicubic/bicubic.frag
renderer_opengl/texture_filters/tex_coord.vert
renderer_opengl/texture_filters/xbrz/xbrz_freescale.frag
renderer_opengl/texture_filters/xbrz/xbrz_freescale.vert
)
include(${CMAKE_CURRENT_SOURCE_DIR}/generate_shaders.cmake)
foreach(shader_file ${SHADER_FILES})
get_filename_component(shader_file_name ${shader_file} NAME)
GetShaderHeaderFile(${shader_file_name})
list(APPEND SHADER_HEADERS ${shader_header_file})
endforeach()
add_custom_target(shaders
BYPRODUCTS ${SHADER_HEADERS}
COMMAND cmake -P ${CMAKE_CURRENT_SOURCE_DIR}/generate_shaders.cmake
SOURCES ${SHADER_FILES}
)
add_dependencies(video_core shaders)
target_include_directories(video_core PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
if(ARCHITECTURE_x86_64) if(ARCHITECTURE_x86_64)
target_sources(video_core target_sources(video_core
PRIVATE PRIVATE

View File

@ -0,0 +1,16 @@
function(GetShaderHeaderFile shader_file_name)
set(shader_header_file ${CMAKE_CURRENT_BINARY_DIR}/shaders/${shader_file_name} PARENT_SCOPE)
endfunction()
foreach(shader_file ${SHADER_FILES})
file(READ ${shader_file} shader)
get_filename_component(shader_file_name ${shader_file} NAME)
string(REPLACE . _ shader_name ${shader_file_name})
GetShaderHeaderFile(${shader_file_name})
file(WRITE ${shader_header_file}
"#pragma once\n"
"constexpr std::string_view ${shader_name} = R\"(\n"
"${shader}"
")\";\n"
)
endforeach()

View File

@ -34,6 +34,7 @@
#include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_state.h" #include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/gl_vars.h" #include "video_core/renderer_opengl/gl_vars.h"
#include "video_core/renderer_opengl/texture_filters/texture_filter_manager.h"
#include "video_core/utils.h" #include "video_core/utils.h"
#include "video_core/video_core.h" #include "video_core/video_core.h"
@ -42,12 +43,6 @@ namespace OpenGL {
using SurfaceType = SurfaceParams::SurfaceType; using SurfaceType = SurfaceParams::SurfaceType;
using PixelFormat = SurfaceParams::PixelFormat; using PixelFormat = SurfaceParams::PixelFormat;
struct FormatTuple {
GLint internal_format;
GLenum format;
GLenum type;
};
static constexpr std::array<FormatTuple, 5> fb_format_tuples = {{ static constexpr std::array<FormatTuple, 5> fb_format_tuples = {{
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8}, // RGBA8 {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8}, // RGBA8
{GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE}, // RGB8 {GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE}, // RGB8
@ -74,9 +69,7 @@ static constexpr std::array<FormatTuple, 4> depth_format_tuples = {{
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24S8 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24S8
}}; }};
static constexpr FormatTuple tex_tuple = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}; const FormatTuple& GetFormatTuple(PixelFormat pixel_format) {
static const FormatTuple& GetFormatTuple(PixelFormat pixel_format) {
const SurfaceType type = SurfaceParams::GetFormatType(pixel_format); const SurfaceType type = SurfaceParams::GetFormatType(pixel_format);
if (type == SurfaceType::Color) { if (type == SurfaceType::Color) {
ASSERT(static_cast<std::size_t>(pixel_format) < fb_format_tuples.size()); ASSERT(static_cast<std::size_t>(pixel_format) < fb_format_tuples.size());
@ -745,9 +738,8 @@ void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) {
if (texture_src_data == nullptr) if (texture_src_data == nullptr)
return; return;
if (gl_buffer == nullptr) { if (gl_buffer.empty()) {
gl_buffer_size = width * height * GetGLBytesPerPixel(pixel_format); gl_buffer.resize(width * height * GetGLBytesPerPixel(pixel_format));
gl_buffer.reset(new u8[gl_buffer_size]);
} }
// TODO: Should probably be done in ::Memory:: and check for other regions too // TODO: Should probably be done in ::Memory:: and check for other regions too
@ -819,7 +811,7 @@ void CachedSurface::FlushGLBuffer(PAddr flush_start, PAddr flush_end) {
if (dst_buffer == nullptr) if (dst_buffer == nullptr)
return; return;
ASSERT(gl_buffer_size == width * height * GetGLBytesPerPixel(pixel_format)); ASSERT(gl_buffer.size() == width * height * GetGLBytesPerPixel(pixel_format));
// TODO: Should probably be done in ::Memory:: and check for other regions too // TODO: Should probably be done in ::Memory:: and check for other regions too
// same as loadglbuffer() // same as loadglbuffer()
@ -858,8 +850,7 @@ void CachedSurface::FlushGLBuffer(PAddr flush_start, PAddr flush_end) {
} }
} }
bool CachedSurface::LoadCustomTexture(u64 tex_hash, Core::CustomTexInfo& tex_info, bool CachedSurface::LoadCustomTexture(u64 tex_hash, Core::CustomTexInfo& tex_info) {
Common::Rectangle<u32>& custom_rect) {
bool result = false; bool result = false;
auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache(); auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache();
const auto& image_interface = Core::System::GetInstance().GetImageInterface(); const auto& image_interface = Core::System::GetInstance().GetImageInterface();
@ -889,13 +880,6 @@ bool CachedSurface::LoadCustomTexture(u64 tex_hash, Core::CustomTexInfo& tex_inf
} }
} }
if (result) {
custom_rect.left = (custom_rect.left / width) * tex_info.width;
custom_rect.top = (custom_rect.top / height) * tex_info.height;
custom_rect.right = (custom_rect.right / width) * tex_info.width;
custom_rect.bottom = (custom_rect.bottom / height) * tex_info.height;
}
return result; return result;
} }
@ -943,31 +927,31 @@ void CachedSurface::DumpTexture(GLuint target_tex, u64 tex_hash) {
} }
MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 192, 64)); MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 192, 64));
void CachedSurface::UploadGLTexture(const Common::Rectangle<u32>& rect, GLuint read_fb_handle, void CachedSurface::UploadGLTexture(Common::Rectangle<u32> rect, GLuint read_fb_handle,
GLuint draw_fb_handle) { GLuint draw_fb_handle) {
if (type == SurfaceType::Fill) if (type == SurfaceType::Fill)
return; return;
MICROPROFILE_SCOPE(OpenGL_TextureUL); MICROPROFILE_SCOPE(OpenGL_TextureUL);
ASSERT(gl_buffer_size == width * height * GetGLBytesPerPixel(pixel_format)); ASSERT(gl_buffer.size() == width * height * GetGLBytesPerPixel(pixel_format));
// Read custom texture
auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache();
std::string dump_path; // Has to be declared here for logging later std::string dump_path; // Has to be declared here for logging later
u64 tex_hash = 0; u64 tex_hash = 0;
// Required for rect to function properly with custom textures
Common::Rectangle custom_rect = rect;
if (Settings::values.dump_textures || Settings::values.custom_textures) if (Settings::values.dump_textures || Settings::values.custom_textures)
tex_hash = Common::ComputeHash64(gl_buffer.get(), gl_buffer_size); tex_hash = Common::ComputeHash64(gl_buffer.data(), gl_buffer.size());
if (Settings::values.custom_textures) if (Settings::values.custom_textures)
is_custom = LoadCustomTexture(tex_hash, custom_tex_info, custom_rect); is_custom = LoadCustomTexture(tex_hash, custom_tex_info);
TextureFilterInterface* const texture_filter =
is_custom ? nullptr : TextureFilterManager::GetInstance().GetTextureFilter();
const u16 default_scale = texture_filter ? texture_filter->scale_factor : 1;
// Load data from memory to the surface // Load data from memory to the surface
GLint x0 = static_cast<GLint>(custom_rect.left); GLint x0 = static_cast<GLint>(rect.left);
GLint y0 = static_cast<GLint>(custom_rect.bottom); GLint y0 = static_cast<GLint>(rect.bottom);
std::size_t buffer_offset = (y0 * stride + x0) * GetGLBytesPerPixel(pixel_format); std::size_t buffer_offset = (y0 * stride + x0) * GetGLBytesPerPixel(pixel_format);
const FormatTuple& tuple = GetFormatTuple(pixel_format); const FormatTuple& tuple = GetFormatTuple(pixel_format);
@ -976,7 +960,7 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle<u32>& rect, GLuint r
// If not 1x scale, create 1x texture that we will blit from to replace texture subrect in // If not 1x scale, create 1x texture that we will blit from to replace texture subrect in
// surface // surface
OGLTexture unscaled_tex; OGLTexture unscaled_tex;
if (res_scale != 1) { if (res_scale != default_scale) {
x0 = 0; x0 = 0;
y0 = 0; y0 = 0;
@ -985,8 +969,8 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle<u32>& rect, GLuint r
AllocateSurfaceTexture(unscaled_tex.handle, GetFormatTuple(PixelFormat::RGBA8), AllocateSurfaceTexture(unscaled_tex.handle, GetFormatTuple(PixelFormat::RGBA8),
custom_tex_info.width, custom_tex_info.height); custom_tex_info.width, custom_tex_info.height);
} else { } else {
AllocateSurfaceTexture(unscaled_tex.handle, tuple, custom_rect.GetWidth(), AllocateSurfaceTexture(unscaled_tex.handle, tuple, rect.GetWidth() * default_scale,
custom_rect.GetHeight()); rect.GetHeight() * default_scale);
} }
target_tex = unscaled_tex.handle; target_tex = unscaled_tex.handle;
} }
@ -1012,6 +996,16 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle<u32>& rect, GLuint r
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, custom_tex_info.width, custom_tex_info.height, glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, custom_tex_info.width, custom_tex_info.height,
GL_RGBA, GL_UNSIGNED_BYTE, custom_tex_info.tex.data()); GL_RGBA, GL_UNSIGNED_BYTE, custom_tex_info.tex.data());
} else if (texture_filter) {
if (res_scale == default_scale) {
AllocateSurfaceTexture(texture.handle, GetFormatTuple(pixel_format),
rect.GetWidth() * default_scale,
rect.GetHeight() * default_scale);
cur_state.texture_units[0].texture_2d = texture.handle;
cur_state.Apply();
}
texture_filter->scale(*this, {(u32)x0, (u32)y0, rect.GetWidth(), rect.GetHeight()},
buffer_offset);
} else { } else {
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride)); glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride));
@ -1022,21 +1016,23 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle<u32>& rect, GLuint r
} }
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
if (Settings::values.dump_textures && !is_custom) if (Settings::values.dump_textures && !is_custom && !texture_filter)
DumpTexture(target_tex, tex_hash); DumpTexture(target_tex, tex_hash);
cur_state.texture_units[0].texture_2d = old_tex; cur_state.texture_units[0].texture_2d = old_tex;
cur_state.Apply(); cur_state.Apply();
if (res_scale != 1) { if (res_scale != default_scale) {
auto scaled_rect = custom_rect; auto scaled_rect = rect;
scaled_rect.left *= res_scale; scaled_rect.left *= res_scale;
scaled_rect.top *= res_scale; scaled_rect.top *= res_scale;
scaled_rect.right *= res_scale; scaled_rect.right *= res_scale;
scaled_rect.bottom *= res_scale; scaled_rect.bottom *= res_scale;
auto from_rect =
BlitTextures(unscaled_tex.handle, {0, custom_rect.GetHeight(), custom_rect.GetWidth(), 0}, is_custom ? Common::Rectangle<u32>{0, custom_tex_info.height, custom_tex_info.width, 0}
texture.handle, scaled_rect, type, read_fb_handle, draw_fb_handle); : Common::Rectangle<u32>{0, rect.GetHeight(), rect.GetWidth(), 0};
BlitTextures(unscaled_tex.handle, from_rect, texture.handle, scaled_rect, type,
read_fb_handle, draw_fb_handle);
} }
InvalidateAllWatcher(); InvalidateAllWatcher();
@ -1050,9 +1046,8 @@ void CachedSurface::DownloadGLTexture(const Common::Rectangle<u32>& rect, GLuint
MICROPROFILE_SCOPE(OpenGL_TextureDL); MICROPROFILE_SCOPE(OpenGL_TextureDL);
if (gl_buffer == nullptr) { if (gl_buffer.empty()) {
gl_buffer_size = width * height * GetGLBytesPerPixel(pixel_format); gl_buffer.resize(width * height * GetGLBytesPerPixel(pixel_format));
gl_buffer.reset(new u8[gl_buffer_size]);
} }
OpenGLState state = OpenGLState::GetCurState(); OpenGLState state = OpenGLState::GetCurState();
@ -1090,7 +1085,7 @@ void CachedSurface::DownloadGLTexture(const Common::Rectangle<u32>& rect, GLuint
if (GLES) { if (GLES) {
GetTexImageOES(GL_TEXTURE_2D, 0, tuple.format, tuple.type, rect.GetHeight(), GetTexImageOES(GL_TEXTURE_2D, 0, tuple.format, tuple.type, rect.GetHeight(),
rect.GetWidth(), 0, &gl_buffer[buffer_offset], rect.GetWidth(), 0, &gl_buffer[buffer_offset],
gl_buffer_size - buffer_offset); gl_buffer.size() - buffer_offset);
} else { } else {
glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]); glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]);
} }
@ -1371,8 +1366,8 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatc
if (surface == nullptr) { if (surface == nullptr) {
u16 target_res_scale = params.res_scale; u16 target_res_scale = params.res_scale;
if (match_res_scale != ScaleMatch::Exact) { if (match_res_scale != ScaleMatch::Exact) {
// This surface may have a subrect of another surface with a higher res_scale, find it // This surface may have a subrect of another surface with a higher res_scale, find
// to adjust our params // it to adjust our params
SurfaceParams find_params = params; SurfaceParams find_params = params;
Surface expandable = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>( Surface expandable = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>(
surface_cache, find_params, match_res_scale); surface_cache, find_params, match_res_scale);
@ -1421,7 +1416,6 @@ SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams&
surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(surface_cache, params, surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(surface_cache, params,
ScaleMatch::Ignore); ScaleMatch::Ignore);
if (surface != nullptr) { if (surface != nullptr) {
ASSERT(surface->res_scale < params.res_scale);
SurfaceParams new_params = *surface; SurfaceParams new_params = *surface;
new_params.res_scale = params.res_scale; new_params.res_scale = params.res_scale;
@ -1501,6 +1495,11 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInf
params.height = info.height; params.height = info.height;
params.is_tiled = true; params.is_tiled = true;
params.pixel_format = SurfaceParams::PixelFormatFromTextureFormat(info.format); params.pixel_format = SurfaceParams::PixelFormatFromTextureFormat(info.format);
TextureFilterInterface* filter{};
params.res_scale = (filter = TextureFilterManager::GetInstance().GetTextureFilter())
? filter->scale_factor
: 1;
params.UpdateParams(); params.UpdateParams();
u32 min_width = info.width >> max_level; u32 min_width = info.width >> max_level;
@ -1518,6 +1517,8 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInf
} }
auto surface = GetSurface(params, ScaleMatch::Ignore, true); auto surface = GetSurface(params, ScaleMatch::Ignore, true);
if (!surface)
return nullptr;
// Update mipmap if necessary // Update mipmap if necessary
if (max_level != 0) { if (max_level != 0) {
@ -1544,8 +1545,8 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInf
width = surface->custom_tex_info.width; width = surface->custom_tex_info.width;
height = surface->custom_tex_info.height; height = surface->custom_tex_info.height;
} else { } else {
width = surface->width * surface->res_scale; width = surface->GetScaledWidth();
height = surface->height * surface->res_scale; height = surface->GetScaledHeight();
} }
for (u32 level = surface->max_level + 1; level <= max_level; ++level) { for (u32 level = surface->max_level + 1; level <= max_level; ++level) {
glTexImage2D(GL_TEXTURE_2D, level, format_tuple.internal_format, width >> level, glTexImage2D(GL_TEXTURE_2D, level, format_tuple.internal_format, width >> level,
@ -1643,9 +1644,10 @@ const CachedTextureCube& RasterizerCacheOpenGL::GetTextureCube(const TextureCube
if (surface) { if (surface) {
face.watcher = surface->CreateWatcher(); face.watcher = surface->CreateWatcher();
} else { } else {
// Can occur when texture address is invalid. We mark the watcher with nullptr in // Can occur when texture address is invalid. We mark the watcher with nullptr
// this case and the content of the face wouldn't get updated. These are usually // in this case and the content of the face wouldn't get updated. These are
// leftover setup in the texture unit and games are not supposed to draw using them. // usually leftover setup in the texture unit and games are not supposed to draw
// using them.
face.watcher = nullptr; face.watcher = nullptr;
} }
} }
@ -1711,7 +1713,9 @@ SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(
// update resolution_scale_factor and reset cache if changed // update resolution_scale_factor and reset cache if changed
static u16 resolution_scale_factor = VideoCore::GetResolutionScaleFactor(); static u16 resolution_scale_factor = VideoCore::GetResolutionScaleFactor();
if (resolution_scale_factor != VideoCore::GetResolutionScaleFactor()) { if (resolution_scale_factor != VideoCore::GetResolutionScaleFactor() ||
TextureFilterManager::GetInstance().IsUpdated()) {
TextureFilterManager::GetInstance().Reset();
resolution_scale_factor = VideoCore::GetResolutionScaleFactor(); resolution_scale_factor = VideoCore::GetResolutionScaleFactor();
FlushAll(); FlushAll();
while (!surface_cache.empty()) while (!surface_cache.empty())
@ -1959,8 +1963,8 @@ void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u32 size, Surface flush_surf
for (auto& pair : RangeFromInterval(dirty_regions, flush_interval)) { for (auto& pair : RangeFromInterval(dirty_regions, flush_interval)) {
// small sizes imply that this most likely comes from the cpu, flush the entire region // small sizes imply that this most likely comes from the cpu, flush the entire region
// the point is to avoid thousands of small writes every frame if the cpu decides to access // the point is to avoid thousands of small writes every frame if the cpu decides to
// that region, anything higher than 8 you're guaranteed it comes from a service // access that region, anything higher than 8 you're guaranteed it comes from a service
const auto interval = size <= 8 ? pair.first : pair.first & flush_interval; const auto interval = size <= 8 ? pair.first : pair.first & flush_interval;
auto& surface = pair.second; auto& surface = pair.second;
@ -2054,7 +2058,7 @@ Surface RasterizerCacheOpenGL::CreateSurface(const SurfaceParams& params) {
surface->texture.Create(); surface->texture.Create();
surface->gl_buffer_size = 0; surface->gl_buffer.resize(0);
surface->invalid_regions.insert(surface->GetInterval()); surface->invalid_regions.insert(surface->GetInterval());
AllocateSurfaceTexture(surface->texture.handle, GetFormatTuple(surface->pixel_format), AllocateSurfaceTexture(surface->texture.handle, GetFormatTuple(surface->pixel_format),
surface->GetScaledWidth(), surface->GetScaledHeight()); surface->GetScaledWidth(), surface->GetScaledHeight());

View File

@ -382,21 +382,18 @@ struct CachedSurface : SurfaceParams, std::enable_shared_from_this<CachedSurface
: SurfaceParams::GetFormatBpp(format) / 8; : SurfaceParams::GetFormatBpp(format) / 8;
} }
std::unique_ptr<u8[]> gl_buffer; std::vector<u8> gl_buffer;
std::size_t gl_buffer_size = 0;
// Read/Write data in 3DS memory to/from gl_buffer // Read/Write data in 3DS memory to/from gl_buffer
void LoadGLBuffer(PAddr load_start, PAddr load_end); void LoadGLBuffer(PAddr load_start, PAddr load_end);
void FlushGLBuffer(PAddr flush_start, PAddr flush_end); void FlushGLBuffer(PAddr flush_start, PAddr flush_end);
// Custom texture loading and dumping // Custom texture loading and dumping
bool LoadCustomTexture(u64 tex_hash, Core::CustomTexInfo& tex_info, bool LoadCustomTexture(u64 tex_hash, Core::CustomTexInfo& tex_info);
Common::Rectangle<u32>& custom_rect);
void DumpTexture(GLuint target_tex, u64 tex_hash); void DumpTexture(GLuint target_tex, u64 tex_hash);
// Upload/Download data in gl_buffer in/to this surface's texture // Upload/Download data in gl_buffer in/to this surface's texture
void UploadGLTexture(const Common::Rectangle<u32>& rect, GLuint read_fb_handle, void UploadGLTexture(Common::Rectangle<u32> rect, GLuint read_fb_handle, GLuint draw_fb_handle);
GLuint draw_fb_handle);
void DownloadGLTexture(const Common::Rectangle<u32>& rect, GLuint read_fb_handle, void DownloadGLTexture(const Common::Rectangle<u32>& rect, GLuint read_fb_handle,
GLuint draw_fb_handle); GLuint draw_fb_handle);
@ -528,4 +525,14 @@ private:
std::unordered_map<TextureCubeConfig, CachedTextureCube> texture_cube_cache; std::unordered_map<TextureCubeConfig, CachedTextureCube> texture_cube_cache;
}; };
struct FormatTuple {
GLint internal_format;
GLenum format;
GLenum type;
};
constexpr FormatTuple tex_tuple = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
const FormatTuple& GetFormatTuple(SurfaceParams::PixelFormat pixel_format);
} // namespace OpenGL } // namespace OpenGL

View File

@ -38,6 +38,13 @@ constexpr GLuint ShadowTexturePZ = 5;
constexpr GLuint ShadowTextureNZ = 6; constexpr GLuint ShadowTextureNZ = 6;
} // namespace ImageUnits } // namespace ImageUnits
struct Viewport {
GLint x;
GLint y;
GLsizei width;
GLsizei height;
};
class OpenGLState { class OpenGLState {
public: public:
struct { struct {
@ -135,12 +142,7 @@ public:
GLsizei height; GLsizei height;
} scissor; } scissor;
struct { Viewport viewport;
GLint x;
GLint y;
GLsizei width;
GLsizei height;
} viewport;
std::array<bool, 2> clip_distance; // GL_CLIP_DISTANCE std::array<bool, 2> clip_distance; // GL_CLIP_DISTANCE

View File

@ -32,6 +32,7 @@
#include "video_core/renderer_opengl/gl_vars.h" #include "video_core/renderer_opengl/gl_vars.h"
#include "video_core/renderer_opengl/post_processing_opengl.h" #include "video_core/renderer_opengl/post_processing_opengl.h"
#include "video_core/renderer_opengl/renderer_opengl.h" #include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/renderer_opengl/texture_filters/texture_filter_manager.h"
#include "video_core/video_core.h" #include "video_core/video_core.h"
namespace Frontend { namespace Frontend {
@ -1178,10 +1179,14 @@ VideoCore::ResultStatus RendererOpenGL::Init() {
RefreshRasterizerSetting(); RefreshRasterizerSetting();
TextureFilterManager::GetInstance().Reset();
return VideoCore::ResultStatus::Success; return VideoCore::ResultStatus::Success;
} }
/// Shutdown the renderer /// Shutdown the renderer
void RendererOpenGL::ShutDown() {} void RendererOpenGL::ShutDown() {
TextureFilterManager::GetInstance().Destroy();
}
} // namespace OpenGL } // namespace OpenGL

View File

@ -0,0 +1,138 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// modified from
// https://github.com/bloc97/Anime4K/blob/533cee5f7018d0e57ad2a26d76d43f13b9d8782a/glsl/Anime4K_Adaptive_v1.0RC2_UltraFast.glsl
// MIT License
//
// Copyright(c) 2019 bloc97
//
// Permission is hereby granted,
// free of charge,
// to any person obtaining a copy of this software and associated documentation
// files(the "Software"),
// to deal in the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and / or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in all copies
// or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS",
// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h"
#include "shaders/refine.frag"
#include "shaders/refine.vert"
#include "shaders/tex_coord.vert"
#include "shaders/x_gradient.frag"
#include "shaders/y_gradient.frag"
#include "shaders/y_gradient.vert"
namespace OpenGL {
Anime4kUltrafast::Anime4kUltrafast(u16 scale_factor) : TextureFilterInterface(scale_factor) {
const OpenGLState cur_state = OpenGLState::GetCurState();
const auto setup_temp_tex = [this, scale_factor](TempTex& texture, GLint internal_format,
GLint format) {
texture.fbo.Create();
texture.tex.Create();
state.draw.draw_framebuffer = texture.fbo.handle;
state.Apply();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_RECTANGLE, texture.tex.handle);
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, internal_format, 1024 * scale_factor,
1024 * scale_factor, 0, format, GL_HALF_FLOAT, nullptr);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE,
texture.tex.handle, 0);
};
setup_temp_tex(LUMAD, GL_R16F, GL_RED);
setup_temp_tex(XY, GL_RG16F, GL_RG);
vao.Create();
out_fbo.Create();
for (std::size_t idx = 0; idx < samplers.size(); ++idx) {
samplers[idx].Create();
state.texture_units[idx].sampler = samplers[idx].handle;
glSamplerParameteri(samplers[idx].handle, GL_TEXTURE_MIN_FILTER,
idx == 0 ? GL_LINEAR : GL_NEAREST);
glSamplerParameteri(samplers[idx].handle, GL_TEXTURE_MAG_FILTER,
idx == 0 ? GL_LINEAR : GL_NEAREST);
glSamplerParameteri(samplers[idx].handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(samplers[idx].handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
state.draw.vertex_array = vao.handle;
gradient_x_program.Create(tex_coord_vert.data(), x_gradient_frag.data());
gradient_y_program.Create(y_gradient_vert.data(), y_gradient_frag.data());
refine_program.Create(refine_vert.data(), refine_frag.data());
state.draw.shader_program = gradient_y_program.handle;
state.Apply();
glUniform1i(glGetUniformLocation(gradient_y_program.handle, "tex_input"), 2);
state.draw.shader_program = refine_program.handle;
state.Apply();
glUniform1i(glGetUniformLocation(refine_program.handle, "LUMAD"), 1);
cur_state.Apply();
}
void Anime4kUltrafast::scale(CachedSurface& surface, const Common::Rectangle<u32>& rect,
std::size_t buffer_offset) {
const OpenGLState cur_state = OpenGLState::GetCurState();
OGLTexture src_tex;
src_tex.Create();
state.viewport = RectToViewport(rect);
state.texture_units[0].texture_2d = src_tex.handle;
state.draw.draw_framebuffer = XY.fbo.handle;
state.draw.shader_program = gradient_x_program.handle;
state.Apply();
const FormatTuple tuple = GetFormatTuple(surface.pixel_format);
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(surface.stride));
glActiveTexture(GL_TEXTURE0);
glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, rect.GetWidth(), rect.GetHeight(), 0,
tuple.format, tuple.type, &surface.gl_buffer[buffer_offset]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE, LUMAD.tex.handle);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_RECTANGLE, XY.tex.handle);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// gradient y pass
state.draw.draw_framebuffer = LUMAD.fbo.handle;
state.draw.shader_program = gradient_y_program.handle;
state.Apply();
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// refine pass
state.draw.draw_framebuffer = out_fbo.handle;
state.draw.shader_program = refine_program.handle;
state.Apply();
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
cur_state.texture_units[0].texture_2d, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
cur_state.Apply();
}
} // namespace OpenGL

View File

@ -0,0 +1,45 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/texture_filters/texture_filter_interface.h"
namespace OpenGL {
class Anime4kUltrafast : public TextureFilterInterface {
public:
static TextureFilterInfo GetInfo() {
TextureFilterInfo info;
info.name = "Anime4K Ultrafast";
info.clamp_scale = {2, 2};
info.constructor = std::make_unique<Anime4kUltrafast, u16>;
return info;
}
Anime4kUltrafast(u16 scale_factor);
void scale(CachedSurface& surface, const Common::Rectangle<u32>& rect,
std::size_t buffer_offset) override;
private:
OpenGLState state{};
OGLVertexArray vao;
OGLFramebuffer out_fbo;
struct TempTex {
OGLTexture tex;
OGLFramebuffer fbo;
};
TempTex LUMAD;
TempTex XY;
std::array<OGLSampler, 3> samplers;
OGLProgram gradient_x_program, gradient_y_program, refine_program;
};
} // namespace OpenGL

View File

@ -0,0 +1,117 @@
//? #version 330
in vec2 tex_coord;
in vec2 input_max;
out vec4 frag_color;
uniform sampler2D HOOKED;
uniform sampler2DRect LUMAD;
uniform sampler2DRect LUMAG;
const float LINE_DETECT_THRESHOLD = 0.4;
const float STRENGTH = 0.6;
// the original shader used the alpha channel for luminance,
// which doesn't work for our use case
struct RGBAL {
vec4 c;
float l;
};
vec4 getAverage(vec4 cc, vec4 a, vec4 b, vec4 c) {
return cc * (1 - STRENGTH) + ((a + b + c) / 3) * STRENGTH;
}
#define GetRGBAL(offset) \
RGBAL(textureOffset(HOOKED, tex_coord, offset), \
texture(LUMAD, clamp(gl_FragCoord.xy + offset, vec2(0.0), input_max)).x)
float min3v(float a, float b, float c) {
return min(min(a, b), c);
}
float max3v(float a, float b, float c) {
return max(max(a, b), c);
}
vec4 Compute() {
RGBAL cc = GetRGBAL(ivec2(0));
if (cc.l > LINE_DETECT_THRESHOLD) {
return cc.c;
}
RGBAL tl = GetRGBAL(ivec2(-1, -1));
RGBAL t = GetRGBAL(ivec2(0, -1));
RGBAL tr = GetRGBAL(ivec2(1, -1));
RGBAL l = GetRGBAL(ivec2(-1, 0));
RGBAL r = GetRGBAL(ivec2(1, 0));
RGBAL bl = GetRGBAL(ivec2(-1, 1));
RGBAL b = GetRGBAL(ivec2(0, 1));
RGBAL br = GetRGBAL(ivec2(1, 1));
// Kernel 0 and 4
float maxDark = max3v(br.l, b.l, bl.l);
float minLight = min3v(tl.l, t.l, tr.l);
if (minLight > cc.l && minLight > maxDark) {
return getAverage(cc.c, tl.c, t.c, tr.c);
} else {
maxDark = max3v(tl.l, t.l, tr.l);
minLight = min3v(br.l, b.l, bl.l);
if (minLight > cc.l && minLight > maxDark) {
return getAverage(cc.c, br.c, b.c, bl.c);
}
}
// Kernel 1 and 5
maxDark = max3v(cc.l, l.l, b.l);
minLight = min3v(r.l, t.l, tr.l);
if (minLight > maxDark) {
return getAverage(cc.c, r.c, t.c, tr.c);
} else {
maxDark = max3v(cc.l, r.l, t.l);
minLight = min3v(bl.l, l.l, b.l);
if (minLight > maxDark) {
return getAverage(cc.c, bl.c, l.c, b.c);
}
}
// Kernel 2 and 6
maxDark = max3v(l.l, tl.l, bl.l);
minLight = min3v(r.l, br.l, tr.l);
if (minLight > cc.l && minLight > maxDark) {
return getAverage(cc.c, r.c, br.c, tr.c);
} else {
maxDark = max3v(r.l, br.l, tr.l);
minLight = min3v(l.l, tl.l, bl.l);
if (minLight > cc.l && minLight > maxDark) {
return getAverage(cc.c, l.c, tl.c, bl.c);
}
}
// Kernel 3 and 7
maxDark = max3v(cc.l, l.l, t.l);
minLight = min3v(r.l, br.l, b.l);
if (minLight > maxDark) {
return getAverage(cc.c, r.c, br.c, b.c);
} else {
maxDark = max3v(cc.l, r.l, b.l);
minLight = min3v(t.l, l.l, tl.l);
if (minLight > maxDark) {
return getAverage(cc.c, t.c, l.c, tl.c);
}
}
return cc.c;
}
void main() {
frag_color = Compute();
}

View File

@ -0,0 +1,14 @@
//? #version 330
out vec2 tex_coord;
out vec2 input_max;
uniform sampler2D HOOKED;
const vec2 vertices[4] =
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
void main() {
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
tex_coord = (vertices[gl_VertexID] + 1.0) / 2.0;
input_max = textureSize(HOOKED, 0) * 2.0 - 1.0;
}

View File

@ -0,0 +1,18 @@
//? #version 330
in vec2 tex_coord;
out vec2 frag_color;
uniform sampler2D tex_input;
const vec3 K = vec3(0.2627, 0.6780, 0.0593);
// TODO: improve handling of alpha channel
#define GetLum(xoffset) dot(K, textureOffset(tex_input, tex_coord, ivec2(xoffset, 0)).rgb)
void main() {
float l = GetLum(-1);
float c = GetLum(0);
float r = GetLum(1);
frag_color = vec2(r - l, l + 2.0 * c + r);
}

View File

@ -0,0 +1,16 @@
//? #version 330
in vec2 input_max;
out float frag_color;
uniform sampler2DRect tex_input;
void main() {
vec2 t = texture(tex_input, min(gl_FragCoord.xy + vec2(0.0, 1.0), input_max)).xy;
vec2 c = texture(tex_input, gl_FragCoord.xy).xy;
vec2 b = texture(tex_input, max(gl_FragCoord.xy - vec2(0.0, 1.0), vec2(0.0))).xy;
vec2 grad = vec2(t.x + 2 * c.x + b.x, b.y - t.y);
frag_color = 1 - length(grad);
}

View File

@ -0,0 +1,12 @@
//? #version 330
out vec2 input_max;
uniform sampler2D tex_size;
const vec2 vertices[4] =
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
void main() {
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
input_max = textureSize(tex_size, 0) * 2 - 1;
}

View File

@ -0,0 +1,56 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/texture_filters/bicubic/bicubic.h"
#include "shaders/bicubic.frag"
#include "shaders/tex_coord.vert"
namespace OpenGL {
Bicubic::Bicubic(u16 scale_factor) : TextureFilterInterface(scale_factor) {
program.Create(tex_coord_vert.data(), bicubic_frag.data());
vao.Create();
draw_fbo.Create();
src_sampler.Create();
state.draw.shader_program = program.handle;
state.draw.vertex_array = vao.handle;
state.draw.shader_program = program.handle;
state.draw.draw_framebuffer = draw_fbo.handle;
state.texture_units[0].sampler = src_sampler.handle;
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
void Bicubic::scale(CachedSurface& surface, const Common::Rectangle<u32>& rect,
std::size_t buffer_offset) {
const OpenGLState cur_state = OpenGLState::GetCurState();
OGLTexture src_tex;
src_tex.Create();
state.texture_units[0].texture_2d = src_tex.handle;
state.viewport = RectToViewport(rect);
state.Apply();
const FormatTuple tuple = GetFormatTuple(surface.pixel_format);
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(surface.stride));
glActiveTexture(GL_TEXTURE0);
glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, rect.GetWidth(), rect.GetHeight(), 0,
tuple.format, tuple.type, &surface.gl_buffer[buffer_offset]);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
cur_state.texture_units[0].texture_2d, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
cur_state.Apply();
}
} // namespace OpenGL

View File

@ -0,0 +1,52 @@
//? #version 330
in vec2 tex_coord;
out vec4 frag_color;
uniform sampler2D input_texture;
// from http://www.java-gaming.org/index.php?topic=35123.0
vec4 cubic(float v) {
vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v;
vec4 s = n * n * n;
float x = s.x;
float y = s.y - 4.0 * s.x;
float z = s.z - 4.0 * s.y + 6.0 * s.x;
float w = 6.0 - x - y - z;
return vec4(x, y, z, w) * (1.0 / 6.0);
}
vec4 textureBicubic(sampler2D sampler, vec2 texCoords) {
vec2 texSize = textureSize(sampler, 0);
vec2 invTexSize = 1.0 / texSize;
texCoords = texCoords * texSize - 0.5;
vec2 fxy = fract(texCoords);
texCoords -= fxy;
vec4 xcubic = cubic(fxy.x);
vec4 ycubic = cubic(fxy.y);
vec4 c = texCoords.xxyy + vec2(-0.5, +1.5).xyxy;
vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
vec4 offset = c + vec4(xcubic.yw, ycubic.yw) / s;
offset *= invTexSize.xxyy;
vec4 sample0 = texture(sampler, offset.xz);
vec4 sample1 = texture(sampler, offset.yz);
vec4 sample2 = texture(sampler, offset.xw);
vec4 sample3 = texture(sampler, offset.yw);
float sx = s.x / (s.x + s.y);
float sy = s.z / (s.z + s.w);
return mix(mix(sample3, sample2, sx), mix(sample1, sample0, sx), sy);
}
void main() {
frag_color = textureBicubic(input_texture, tex_coord);
}

View File

@ -0,0 +1,32 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/texture_filters/texture_filter_interface.h"
namespace OpenGL {
class Bicubic : public TextureFilterInterface {
public:
static TextureFilterInfo GetInfo() {
TextureFilterInfo info;
info.name = "Bicubic";
info.constructor = std::make_unique<Bicubic, u16>;
return info;
}
Bicubic(u16 scale_factor);
void scale(CachedSurface& surface, const Common::Rectangle<u32>& rect,
std::size_t buffer_offset) override;
private:
OpenGLState state{};
OGLProgram program{};
OGLVertexArray vao{};
OGLFramebuffer draw_fbo{};
OGLSampler src_sampler{};
};
} // namespace OpenGL

View File

@ -0,0 +1,10 @@
//? #version 330
out vec2 tex_coord;
const vec2 vertices[4] =
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
void main() {
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
tex_coord = (vertices[gl_VertexID] + 1.0) / 2.0;
}

View File

@ -0,0 +1,38 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <string_view>
#include "common/common_types.h"
#include "common/math_util.h"
namespace OpenGL {
struct CachedSurface;
struct Viewport;
class TextureFilterInterface {
public:
const u16 scale_factor{};
TextureFilterInterface(u16 scale_factor) : scale_factor{scale_factor} {}
virtual void scale(CachedSurface& surface, const Common::Rectangle<u32>& rect,
std::size_t buffer_offset) = 0;
virtual ~TextureFilterInterface() = default;
protected:
Viewport RectToViewport(const Common::Rectangle<u32>& rect);
};
// every texture filter should have a static GetInfo function
struct TextureFilterInfo {
std::string_view name;
struct {
u16 min, max;
} clamp_scale{1, 10};
std::function<std::unique_ptr<TextureFilterInterface>(u16 scale_factor)> constructor;
};
} // namespace OpenGL

View File

@ -0,0 +1,89 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h"
#include "video_core/renderer_opengl/texture_filters/bicubic/bicubic.h"
#include "video_core/renderer_opengl/texture_filters/texture_filter_manager.h"
#include "video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.h"
namespace OpenGL {
Viewport TextureFilterInterface::RectToViewport(const Common::Rectangle<u32>& rect) {
return {
static_cast<GLint>(rect.left) * scale_factor,
static_cast<GLint>(rect.top) * scale_factor,
static_cast<GLsizei>(rect.GetWidth()) * scale_factor,
static_cast<GLsizei>(rect.GetHeight()) * scale_factor,
};
}
namespace {
template <typename T>
std::pair<std::string_view, TextureFilterInfo> FilterMapPair() {
return {T::GetInfo().name, T::GetInfo()};
};
struct NoFilter {
static TextureFilterInfo GetInfo() {
TextureFilterInfo info;
info.name = TextureFilterManager::NONE;
info.clamp_scale = {1, 1};
info.constructor = [](u16) { return nullptr; };
return info;
}
};
} // namespace
const std::map<std::string_view, TextureFilterInfo, TextureFilterManager::FilterNameComp>&
TextureFilterManager::TextureFilterMap() {
static const std::map<std::string_view, TextureFilterInfo, FilterNameComp> filter_map{
FilterMapPair<NoFilter>(),
FilterMapPair<Anime4kUltrafast>(),
FilterMapPair<Bicubic>(),
FilterMapPair<XbrzFreescale>(),
};
return filter_map;
}
void TextureFilterManager::SetTextureFilter(std::string filter_name, u16 new_scale_factor) {
if (name == filter_name && scale_factor == new_scale_factor)
return;
std::lock_guard<std::mutex> lock{mutex};
name = std::move(filter_name);
scale_factor = new_scale_factor;
updated = true;
}
TextureFilterInterface* TextureFilterManager::GetTextureFilter() const {
return filter.get();
}
bool TextureFilterManager::IsUpdated() const {
return updated;
}
void TextureFilterManager::Reset() {
std::lock_guard<std::mutex> lock{mutex};
updated = false;
auto iter = TextureFilterMap().find(name);
if (iter == TextureFilterMap().end()) {
LOG_ERROR(Render_OpenGL, "Invalid texture filter: {}", name);
filter = nullptr;
return;
}
const auto& filter_info = iter->second;
u16 clamped_scale =
std::clamp(scale_factor, filter_info.clamp_scale.min, filter_info.clamp_scale.max);
if (clamped_scale != scale_factor)
LOG_ERROR(Render_OpenGL, "Invalid scale factor {} for texture filter {}, clamped to {}",
scale_factor, filter_info.name, clamped_scale);
filter = filter_info.constructor(clamped_scale);
}
} // namespace OpenGL

View File

@ -0,0 +1,55 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <map>
#include <memory>
#include <mutex>
#include <string_view>
#include <tuple>
#include "video_core/renderer_opengl/texture_filters/texture_filter_interface.h"
namespace OpenGL {
class TextureFilterManager {
public:
static constexpr std::string_view NONE = "none";
struct FilterNameComp {
bool operator()(const std::string_view a, const std::string_view b) const {
bool na = a == NONE;
bool nb = b == NONE;
if (na | nb)
return na & !nb;
return a < b;
}
};
// function ensures map is initialized before use
static const std::map<std::string_view, TextureFilterInfo, FilterNameComp>& TextureFilterMap();
static TextureFilterManager& GetInstance() {
static TextureFilterManager singleton;
return singleton;
}
void Destroy() {
filter.reset();
}
void SetTextureFilter(std::string filter_name, u16 new_scale_factor);
TextureFilterInterface* GetTextureFilter() const;
// returns true if filter has been changed and a cache reset is needed
bool IsUpdated() const;
void Reset();
private:
std::atomic<bool> updated{false};
std::mutex mutex;
std::string name{"none"};
u16 scale_factor{1};
std::unique_ptr<TextureFilterInterface> filter;
};
} // namespace OpenGL

View File

@ -0,0 +1,100 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// adapted from
// https://github.com/libretro/glsl-shaders/blob/d7a8b8eb2a61a5732da4cbe2e0f9ad30600c3f17/xbrz/shaders/xbrz-freescale.glsl
// xBRZ freescale
// based on :
// 4xBRZ shader - Copyright (C) 2014-2016 DeSmuME team
//
// This file is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the this software. If not, see <http://www.gnu.org/licenses/>.
// Hyllian's xBR-vertex code and texel mapping
// Copyright (C) 2011/2016 Hyllian - sergiogdb@gmail.com
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.h"
#include "shaders/xbrz_freescale.frag"
#include "shaders/xbrz_freescale.vert"
namespace OpenGL {
XbrzFreescale::XbrzFreescale(u16 scale_factor) : TextureFilterInterface(scale_factor) {
const OpenGLState cur_state = OpenGLState::GetCurState();
program.Create(xbrz_freescale_vert.data(), xbrz_freescale_frag.data());
vao.Create();
draw_fbo.Create();
src_sampler.Create();
state.draw.shader_program = program.handle;
state.Apply();
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glUniform1f(glGetUniformLocation(program.handle, "scale"), static_cast<GLfloat>(scale_factor));
cur_state.Apply();
state.draw.vertex_array = vao.handle;
state.draw.shader_program = program.handle;
state.draw.draw_framebuffer = draw_fbo.handle;
state.texture_units[0].sampler = src_sampler.handle;
}
void XbrzFreescale::scale(CachedSurface& surface, const Common::Rectangle<u32>& rect,
std::size_t buffer_offset) {
const OpenGLState cur_state = OpenGLState::GetCurState();
OGLTexture src_tex;
src_tex.Create();
state.texture_units[0].texture_2d = src_tex.handle;
state.viewport = RectToViewport(rect);
state.Apply();
const FormatTuple tuple = GetFormatTuple(surface.pixel_format);
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(surface.stride));
glActiveTexture(GL_TEXTURE0);
glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, rect.GetWidth(), rect.GetHeight(), 0,
tuple.format, tuple.type, &surface.gl_buffer[buffer_offset]);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
cur_state.texture_units[0].texture_2d, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
cur_state.Apply();
}
} // namespace OpenGL

View File

@ -0,0 +1,242 @@
//? #version 330
in vec2 tex_coord;
in vec2 source_size;
in vec2 output_size;
out vec4 frag_color;
uniform sampler2D tex;
uniform float scale;
const int BLEND_NONE = 0;
const int BLEND_NORMAL = 1;
const int BLEND_DOMINANT = 2;
const float LUMINANCE_WEIGHT = 1.0;
const float EQUAL_COLOR_TOLERANCE = 30.0 / 255.0;
const float STEEP_DIRECTION_THRESHOLD = 2.2;
const float DOMINANT_DIRECTION_THRESHOLD = 3.6;
float ColorDist(vec4 a, vec4 b) {
// https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.2020_conversion
const vec3 K = vec3(0.2627, 0.6780, 0.0593);
const mat3 MATRIX = mat3(K, -.5 * K.r / (1.0 - K.b), -.5 * K.g / (1.0 - K.b), .5, .5,
-.5 * K.g / (1.0 - K.r), -.5 * K.b / (1.0 - K.r));
vec4 diff = a - b;
vec3 YCbCr = diff.rgb * MATRIX;
// LUMINANCE_WEIGHT is currently 1, otherwise y would be multiplied by it
float d = length(YCbCr);
return sqrt(a.a * b.a * d * d + diff.a * diff.a);
}
bool IsPixEqual(const vec4 pixA, const vec4 pixB) {
return ColorDist(pixA, pixB) < EQUAL_COLOR_TOLERANCE;
}
float GetLeftRatio(vec2 center, vec2 origin, vec2 direction) {
vec2 P0 = center - origin;
vec2 proj = direction * (dot(P0, direction) / dot(direction, direction));
vec2 distv = P0 - proj;
vec2 orth = vec2(-direction.y, direction.x);
float side = sign(dot(P0, orth));
float v = side * length(distv * scale);
return smoothstep(-sqrt(2.0) / 2.0, sqrt(2.0) / 2.0, v);
}
vec2 pos = fract(tex_coord * source_size) - vec2(0.5, 0.5);
vec2 coord = tex_coord - pos / source_size;
#define P(x, y) textureOffset(tex, coord, ivec2(x, y))
void main() {
//---------------------------------------
// Input Pixel Mapping: -|x|x|x|-
// x|A|B|C|x
// x|D|E|F|x
// x|G|H|I|x
// -|x|x|x|-
vec4 A = P(-1, -1);
vec4 B = P(0, -1);
vec4 C = P(1, -1);
vec4 D = P(-1, 0);
vec4 E = P(0, 0);
vec4 F = P(1, 0);
vec4 G = P(-1, 1);
vec4 H = P(0, 1);
vec4 I = P(1, 1);
// blendResult Mapping: x|y|
// w|z|
ivec4 blendResult = ivec4(BLEND_NONE, BLEND_NONE, BLEND_NONE, BLEND_NONE);
// Preprocess corners
// Pixel Tap Mapping: -|-|-|-|-
// -|-|B|C|-
// -|D|E|F|x
// -|G|H|I|x
// -|-|x|x|-
if (!((E == F && H == I) || (E == H && F == I))) {
float dist_H_F = ColorDist(G, E) + ColorDist(E, C) + ColorDist(P(0, 2), I) +
ColorDist(I, P(2, 0)) + (4.0 * ColorDist(H, F));
float dist_E_I = ColorDist(D, H) + ColorDist(H, P(1, 2)) + ColorDist(B, F) +
ColorDist(F, P(2, 1)) + (4.0 * ColorDist(E, I));
bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_H_F) < dist_E_I;
blendResult.z = ((dist_H_F < dist_E_I) && E != F && E != H)
? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL)
: BLEND_NONE;
}
// Pixel Tap Mapping: -|-|-|-|-
// -|A|B|-|-
// x|D|E|F|-
// x|G|H|I|-
// -|x|x|-|-
if (!((D == E && G == H) || (D == G && E == H))) {
float dist_G_E = ColorDist(P(-2, 1), D) + ColorDist(D, B) + ColorDist(P(-1, 2), H) +
ColorDist(H, F) + (4.0 * ColorDist(G, E));
float dist_D_H = ColorDist(P(-2, 0), G) + ColorDist(G, P(0, 2)) + ColorDist(A, E) +
ColorDist(E, I) + (4.0 * ColorDist(D, H));
bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_D_H) < dist_G_E;
blendResult.w = ((dist_G_E > dist_D_H) && E != D && E != H)
? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL)
: BLEND_NONE;
}
// Pixel Tap Mapping: -|-|x|x|-
// -|A|B|C|x
// -|D|E|F|x
// -|-|H|I|-
// -|-|-|-|-
if (!((B == C && E == F) || (B == E && C == F))) {
float dist_E_C = ColorDist(D, B) + ColorDist(B, P(1, -2)) + ColorDist(H, F) +
ColorDist(F, P(2, -1)) + (4.0 * ColorDist(E, C));
float dist_B_F = ColorDist(A, E) + ColorDist(E, I) + ColorDist(P(0, -2), C) +
ColorDist(C, P(2, 0)) + (4.0 * ColorDist(B, F));
bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_B_F) < dist_E_C;
blendResult.y = ((dist_E_C > dist_B_F) && E != B && E != F)
? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL)
: BLEND_NONE;
}
// Pixel Tap Mapping: -|x|x|-|-
// x|A|B|C|-
// x|D|E|F|-
// -|G|H|-|-
// -|-|-|-|-
if (!((A == B && D == E) || (A == D && B == E))) {
float dist_D_B = ColorDist(P(-2, 0), A) + ColorDist(A, P(0, -2)) + ColorDist(G, E) +
ColorDist(E, C) + (4.0 * ColorDist(D, B));
float dist_A_E = ColorDist(P(-2, -1), D) + ColorDist(D, H) + ColorDist(P(-1, -2), B) +
ColorDist(B, F) + (4.0 * ColorDist(A, E));
bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_D_B) < dist_A_E;
blendResult.x = ((dist_D_B < dist_A_E) && E != D && E != B)
? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL)
: BLEND_NONE;
}
vec4 res = E;
// Pixel Tap Mapping: -|-|-|-|-
// -|-|B|C|-
// -|D|E|F|x
// -|G|H|I|x
// -|-|x|x|-
if (blendResult.z != BLEND_NONE) {
float dist_F_G = ColorDist(F, G);
float dist_H_C = ColorDist(H, C);
bool doLineBlend = (blendResult.z == BLEND_DOMINANT ||
!((blendResult.y != BLEND_NONE && !IsPixEqual(E, G)) ||
(blendResult.w != BLEND_NONE && !IsPixEqual(E, C)) ||
(IsPixEqual(G, H) && IsPixEqual(H, I) && IsPixEqual(I, F) &&
IsPixEqual(F, C) && !IsPixEqual(E, I))));
vec2 origin = vec2(0.0, 1.0 / sqrt(2.0));
ivec2 direction = ivec2(1, -1);
if (doLineBlend) {
bool haveShallowLine =
(STEEP_DIRECTION_THRESHOLD * dist_F_G <= dist_H_C) && E != G && D != G;
bool haveSteepLine =
(STEEP_DIRECTION_THRESHOLD * dist_H_C <= dist_F_G) && E != C && B != C;
origin = haveShallowLine ? vec2(0.0, 0.25) : vec2(0.0, 0.5);
direction.x += haveShallowLine ? 1 : 0;
direction.y -= haveSteepLine ? 1 : 0;
}
vec4 blendPix = mix(H, F, step(ColorDist(E, F), ColorDist(E, H)));
res = mix(res, blendPix, GetLeftRatio(pos, origin, direction));
}
// Pixel Tap Mapping: -|-|-|-|-
// -|A|B|-|-
// x|D|E|F|-
// x|G|H|I|-
// -|x|x|-|-
if (blendResult.w != BLEND_NONE) {
float dist_H_A = ColorDist(H, A);
float dist_D_I = ColorDist(D, I);
bool doLineBlend = (blendResult.w == BLEND_DOMINANT ||
!((blendResult.z != BLEND_NONE && !IsPixEqual(E, A)) ||
(blendResult.x != BLEND_NONE && !IsPixEqual(E, I)) ||
(IsPixEqual(A, D) && IsPixEqual(D, G) && IsPixEqual(G, H) &&
IsPixEqual(H, I) && !IsPixEqual(E, G))));
vec2 origin = vec2(-1.0 / sqrt(2.0), 0.0);
ivec2 direction = ivec2(1, 1);
if (doLineBlend) {
bool haveShallowLine =
(STEEP_DIRECTION_THRESHOLD * dist_H_A <= dist_D_I) && E != A && B != A;
bool haveSteepLine =
(STEEP_DIRECTION_THRESHOLD * dist_D_I <= dist_H_A) && E != I && F != I;
origin = haveShallowLine ? vec2(-0.25, 0.0) : vec2(-0.5, 0.0);
direction.y += haveShallowLine ? 1 : 0;
direction.x += haveSteepLine ? 1 : 0;
}
origin = origin;
direction = direction;
vec4 blendPix = mix(H, D, step(ColorDist(E, D), ColorDist(E, H)));
res = mix(res, blendPix, GetLeftRatio(pos, origin, direction));
}
// Pixel Tap Mapping: -|-|x|x|-
// -|A|B|C|x
// -|D|E|F|x
// -|-|H|I|-
// -|-|-|-|-
if (blendResult.y != BLEND_NONE) {
float dist_B_I = ColorDist(B, I);
float dist_F_A = ColorDist(F, A);
bool doLineBlend = (blendResult.y == BLEND_DOMINANT ||
!((blendResult.x != BLEND_NONE && !IsPixEqual(E, I)) ||
(blendResult.z != BLEND_NONE && !IsPixEqual(E, A)) ||
(IsPixEqual(I, F) && IsPixEqual(F, C) && IsPixEqual(C, B) &&
IsPixEqual(B, A) && !IsPixEqual(E, C))));
vec2 origin = vec2(1.0 / sqrt(2.0), 0.0);
ivec2 direction = ivec2(-1, -1);
if (doLineBlend) {
bool haveShallowLine =
(STEEP_DIRECTION_THRESHOLD * dist_B_I <= dist_F_A) && E != I && H != I;
bool haveSteepLine =
(STEEP_DIRECTION_THRESHOLD * dist_F_A <= dist_B_I) && E != A && D != A;
origin = haveShallowLine ? vec2(0.25, 0.0) : vec2(0.5, 0.0);
direction.y -= haveShallowLine ? 1 : 0;
direction.x -= haveSteepLine ? 1 : 0;
}
vec4 blendPix = mix(F, B, step(ColorDist(E, B), ColorDist(E, F)));
res = mix(res, blendPix, GetLeftRatio(pos, origin, direction));
}
// Pixel Tap Mapping: -|x|x|-|-
// x|A|B|C|-
// x|D|E|F|-
// -|G|H|-|-
// -|-|-|-|-
if (blendResult.x != BLEND_NONE) {
float dist_D_C = ColorDist(D, C);
float dist_B_G = ColorDist(B, G);
bool doLineBlend = (blendResult.x == BLEND_DOMINANT ||
!((blendResult.w != BLEND_NONE && !IsPixEqual(E, C)) ||
(blendResult.y != BLEND_NONE && !IsPixEqual(E, G)) ||
(IsPixEqual(C, B) && IsPixEqual(B, A) && IsPixEqual(A, D) &&
IsPixEqual(D, G) && !IsPixEqual(E, A))));
vec2 origin = vec2(0.0, -1.0 / sqrt(2.0));
ivec2 direction = ivec2(-1, 1);
if (doLineBlend) {
bool haveShallowLine =
(STEEP_DIRECTION_THRESHOLD * dist_D_C <= dist_B_G) && E != C && F != C;
bool haveSteepLine =
(STEEP_DIRECTION_THRESHOLD * dist_B_G <= dist_D_C) && E != G && H != G;
origin = haveShallowLine ? vec2(0.0, -0.25) : vec2(0.0, -0.5);
direction.x -= haveShallowLine ? 1 : 0;
direction.y += haveSteepLine ? 1 : 0;
}
vec4 blendPix = mix(D, B, step(ColorDist(E, B), ColorDist(E, D)));
res = mix(res, blendPix, GetLeftRatio(pos, origin, direction));
}
frag_color = res;
}

View File

@ -0,0 +1,33 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/texture_filters/texture_filter_interface.h"
namespace OpenGL {
class XbrzFreescale : public TextureFilterInterface {
public:
static TextureFilterInfo GetInfo() {
TextureFilterInfo info;
info.name = "xBRZ freescale";
info.constructor = std::make_unique<XbrzFreescale, u16>;
return info;
}
XbrzFreescale(u16 scale_factor);
void scale(CachedSurface& surface, const Common::Rectangle<u32>& rect,
std::size_t buffer_offset) override;
private:
OpenGLState state{};
OGLProgram program{};
OGLVertexArray vao{};
OGLFramebuffer draw_fbo{};
OGLSampler src_sampler{};
};
} // namespace OpenGL

View File

@ -0,0 +1,17 @@
//? #version 330
out vec2 tex_coord;
out vec2 source_size;
out vec2 output_size;
uniform sampler2D tex;
uniform float scale;
const vec2 vertices[4] =
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
void main() {
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
tex_coord = (vertices[gl_VertexID] + 1.0) / 2.0;
source_size = textureSize(tex, 0);
output_size = source_size * scale;
}

View File

@ -59,6 +59,7 @@ ResultStatus Init(Frontend::EmuWindow& emu_window, Memory::MemorySystem& memory)
void Shutdown() { void Shutdown() {
Pica::Shutdown(); Pica::Shutdown();
g_renderer->ShutDown();
g_renderer.reset(); g_renderer.reset();
LOG_DEBUG(Render, "shutdown OK"); LOG_DEBUG(Render, "shutdown OK");