Merge remote-tracking branch 'upstream/master' into feature/savestates-2
This commit is contained in:
commit
7049af744f
|
@ -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)
|
||||
[![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)
|
||||
[![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.
|
||||
|
||||
|
|
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
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
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
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
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
|
@ -2,11 +2,11 @@ The icons in this folder and its subfolders have the following licenses:
|
|||
|
||||
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_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/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/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
|
||||
|
@ -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/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/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_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/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/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
|
||||
|
|
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 |
|
@ -343,11 +343,11 @@ The icons used in this project have the following licenses:
|
|||
|
||||
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_notification.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
|
||||
plus_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
bad_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
|
|
|
@ -71,6 +71,14 @@ elseif(ENABLE_FDK)
|
|||
target_compile_definitions(audio_core PUBLIC HAVE_FDK)
|
||||
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)
|
||||
target_link_libraries(audio_core PRIVATE SDL2)
|
||||
target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
|
||||
|
@ -80,4 +88,3 @@ if(ENABLE_CUBEB)
|
|||
target_link_libraries(audio_core PRIVATE cubeb)
|
||||
target_compile_definitions(audio_core PUBLIC HAVE_CUBEB)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "audio_core/hle/wmf_decoder.h"
|
||||
#elif HAVE_FFMPEG
|
||||
#include "audio_core/hle/ffmpeg_decoder.h"
|
||||
#elif ANDROID
|
||||
#include "audio_core/hle/mediandk_decoder.h"
|
||||
#elif HAVE_FDK
|
||||
#include "audio_core/hle/fdk_decoder.h"
|
||||
#endif
|
||||
|
@ -126,6 +128,8 @@ DspHle::Impl::Impl(DspHle& parent_, Memory::MemorySystem& memory) : parent(paren
|
|||
decoder = std::make_unique<HLE::WMFDecoder>(memory);
|
||||
#elif defined(HAVE_FFMPEG)
|
||||
decoder = std::make_unique<HLE::FFMPEGDecoder>(memory);
|
||||
#elif ANDROID
|
||||
decoder = std::make_unique<HLE::MediaNDKDecoder>(memory);
|
||||
#elif defined(HAVE_FDK)
|
||||
decoder = std::make_unique<HLE::FDKDecoder>(memory);
|
||||
#else
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -128,6 +128,10 @@ void Config::ReadValues() {
|
|||
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
|
||||
Settings::values.use_vsync_new =
|
||||
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>(
|
||||
sdl2_config->GetInteger("Renderer", "render_3d", 0));
|
||||
|
|
|
@ -126,6 +126,10 @@ use_disk_shader_cache =
|
|||
# factor for the 3DS resolution
|
||||
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
|
||||
# 0: Off, 1: On (default)
|
||||
use_frame_limit =
|
||||
|
|
|
@ -448,6 +448,13 @@ void Config::ReadRendererValues() {
|
|||
Settings::values.bg_green = ReadSetting(QStringLiteral("bg_green"), 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();
|
||||
}
|
||||
|
||||
|
@ -879,6 +886,12 @@ void Config::SaveRendererValues() {
|
|||
WriteSetting(QStringLiteral("bg_green"), (double)Settings::values.bg_green, 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -8,10 +8,18 @@
|
|||
#include "core/settings.h"
|
||||
#include "ui_configure_enhancements.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)
|
||||
: QWidget(parent), ui(new Ui::ConfigureEnhancements) {
|
||||
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();
|
||||
|
||||
ui->layoutBox->setEnabled(!Settings::values.custom_layout);
|
||||
|
@ -52,6 +60,15 @@ void ConfigureEnhancements::SetConfiguration() {
|
|||
ui->factor_3d->setValue(Settings::values.factor_3d);
|
||||
updateShaders(Settings::values.render_3d);
|
||||
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->swap_screen->setChecked(Settings::values.swap_screen);
|
||||
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() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
@ -101,6 +129,8 @@ void ConfigureEnhancements::ApplyConfiguration() {
|
|||
Settings::values.pp_shader_name =
|
||||
ui->shader_combobox->itemText(ui->shader_combobox->currentIndex()).toStdString();
|
||||
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 =
|
||||
static_cast<Settings::LayoutOption>(ui->layout_combobox->currentIndex());
|
||||
Settings::values.swap_screen = ui->swap_screen->isChecked();
|
||||
|
|
|
@ -27,6 +27,7 @@ public:
|
|||
|
||||
private:
|
||||
void updateShaders(Settings::StereoRenderOption stereo_option);
|
||||
void updateTextureFilter(int index);
|
||||
|
||||
Ui::ConfigureEnhancements* ui;
|
||||
QColor bg_color;
|
||||
|
|
|
@ -117,6 +117,56 @@
|
|||
</item>
|
||||
</layout>
|
||||
</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>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -1556,7 +1556,7 @@ void GMainWindow::ToggleWindowMode() {
|
|||
// Render in the main window...
|
||||
render_window->BackupGeometry();
|
||||
ui.horizontalLayout->addWidget(render_window);
|
||||
render_window->setFocusPolicy(Qt::ClickFocus);
|
||||
render_window->setFocusPolicy(Qt::StrongFocus);
|
||||
if (emulation_running) {
|
||||
render_window->setVisible(true);
|
||||
render_window->setFocus();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
|
@ -541,11 +542,11 @@ void CopyDir(const std::string& source_path, const std::string& dest_path) {
|
|||
std::optional<std::string> GetCurrentDir() {
|
||||
// Get the current working directory (getcwd uses malloc)
|
||||
#ifdef _WIN32
|
||||
wchar_t* dir;
|
||||
if (!(dir = _wgetcwd(nullptr, 0))) {
|
||||
wchar_t* dir = _wgetcwd(nullptr, 0);
|
||||
if (!dir) {
|
||||
#else
|
||||
char* dir;
|
||||
if (!(dir = getcwd(nullptr, 0))) {
|
||||
char* dir = getcwd(nullptr, 0);
|
||||
if (!dir) {
|
||||
#endif
|
||||
LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
|
||||
return {};
|
||||
|
@ -891,16 +892,16 @@ IOFile::~IOFile() {
|
|||
Close();
|
||||
}
|
||||
|
||||
IOFile::IOFile(IOFile&& other) {
|
||||
IOFile::IOFile(IOFile&& other) noexcept {
|
||||
Swap(other);
|
||||
}
|
||||
|
||||
IOFile& IOFile::operator=(IOFile&& other) {
|
||||
IOFile& IOFile::operator=(IOFile&& other) noexcept {
|
||||
Swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void IOFile::Swap(IOFile& other) {
|
||||
void IOFile::Swap(IOFile& other) noexcept {
|
||||
std::swap(m_file, other.m_file);
|
||||
std::swap(m_good, other.m_good);
|
||||
std::swap(filename, other.filename);
|
||||
|
@ -915,15 +916,16 @@ bool IOFile::Open() {
|
|||
if (flags != 0) {
|
||||
m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(),
|
||||
Common::UTF8ToUTF16W(openmode).c_str(), flags);
|
||||
m_good = m_file != nullptr;
|
||||
} else {
|
||||
_wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(),
|
||||
Common::UTF8ToUTF16W(openmode).c_str());
|
||||
m_good = _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(),
|
||||
Common::UTF8ToUTF16W(openmode).c_str()) == 0;
|
||||
}
|
||||
#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
|
||||
|
||||
m_good = IsOpen();
|
||||
return m_good;
|
||||
}
|
||||
|
||||
|
@ -953,7 +955,7 @@ u64 IOFile::Tell() const {
|
|||
if (IsOpen())
|
||||
return ftello(m_file);
|
||||
|
||||
return -1;
|
||||
return std::numeric_limits<u64>::max();
|
||||
}
|
||||
|
||||
bool IOFile::Flush() {
|
||||
|
|
|
@ -219,10 +219,10 @@ public:
|
|||
|
||||
~IOFile();
|
||||
|
||||
IOFile(IOFile&& other);
|
||||
IOFile& operator=(IOFile&& other);
|
||||
IOFile(IOFile&& other) noexcept;
|
||||
IOFile& operator=(IOFile&& other) noexcept;
|
||||
|
||||
void Swap(IOFile& other);
|
||||
void Swap(IOFile& other) noexcept;
|
||||
|
||||
bool Close();
|
||||
|
||||
|
|
|
@ -28,11 +28,8 @@ namespace Common {
|
|||
#ifdef _MSC_VER
|
||||
|
||||
// Sets the debugger-visible name of the current thread.
|
||||
// Uses undocumented (actually, it is now documented) trick.
|
||||
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsdebug/html/vxtsksettingthreadname.asp
|
||||
|
||||
// This is implemented much nicer in upcoming msvc++, see:
|
||||
// http://msdn.microsoft.com/en-us/library/xcb2z8hs(VS.100).aspx
|
||||
// Uses trick documented in:
|
||||
// https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-set-a-thread-name-in-native-code
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
static const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
||||
|
||||
|
@ -47,7 +44,7 @@ void SetCurrentThreadName(const char* name) {
|
|||
|
||||
info.dwType = 0x1000;
|
||||
info.szName = name;
|
||||
info.dwThreadID = -1; // dwThreadID;
|
||||
info.dwThreadID = static_cast<DWORD>(-1);
|
||||
info.dwFlags = 0;
|
||||
|
||||
__try {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <boost/serialization/deque.hpp>
|
||||
|
|
|
@ -70,6 +70,7 @@ void Module::PortConfig::Clear() {
|
|||
completion_event->Clear();
|
||||
buffer_error_interrupt_event->Clear();
|
||||
vsync_interrupt_event->Clear();
|
||||
vsync_timings.clear();
|
||||
is_receiving = false;
|
||||
is_active = false;
|
||||
is_pending_receiving = false;
|
||||
|
@ -143,6 +144,27 @@ void Module::CompletionEventCallBack(u64 port_id, s64) {
|
|||
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) {
|
||||
PortConfig& port = ports[port_id];
|
||||
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].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>
|
||||
|
@ -641,6 +666,7 @@ void Module::Interface::Activate(Kernel::HLERequestContext& ctx) {
|
|||
cam->ports[i].is_busy = false;
|
||||
}
|
||||
cam->ports[i].is_active = false;
|
||||
cam->system.CoreTiming().UnscheduleEvent(cam->vsync_interrupt_event_callback, i);
|
||||
}
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
} else if (camera_select[0] && camera_select[1]) {
|
||||
|
@ -870,6 +896,34 @@ void Module::Interface::SynchronizeVsyncTiming(Kernel::HLERequestContext& ctx) {
|
|||
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) {
|
||||
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(
|
||||
"CAM::CompletionEventCallBack",
|
||||
[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() {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
@ -629,6 +630,21 @@ public:
|
|||
*/
|
||||
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
|
||||
* applications.
|
||||
|
@ -729,6 +745,7 @@ public:
|
|||
|
||||
private:
|
||||
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
|
||||
// and is_receiving = false.
|
||||
|
@ -767,7 +784,7 @@ private:
|
|||
std::unique_ptr<Camera::CameraInterface> impl;
|
||||
std::array<ContextConfig, 2> contexts;
|
||||
int current_context{0};
|
||||
FrameRate frame_rate{FrameRate::Rate_5};
|
||||
FrameRate frame_rate{FrameRate::Rate_15};
|
||||
|
||||
private:
|
||||
template <class Archive>
|
||||
|
@ -806,6 +823,8 @@ private:
|
|||
std::shared_ptr<Kernel::Event> buffer_error_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.
|
||||
Kernel::Process* dest_process{nullptr};
|
||||
VAddr dest{0}; // the destination address of the receiving process
|
||||
|
@ -843,8 +862,8 @@ private:
|
|||
Core::System& system;
|
||||
std::array<CameraConfig, NumCameras> cameras;
|
||||
std::array<PortConfig, 2> ports;
|
||||
// TODO: Make this *const
|
||||
const Core::TimingEventType* completion_event_callback;
|
||||
Core::TimingEventType* completion_event_callback;
|
||||
Core::TimingEventType* vsync_interrupt_event_callback;
|
||||
std::atomic<bool> is_camera_reload_pending{false};
|
||||
|
||||
template <class Archive>
|
||||
|
|
|
@ -51,7 +51,7 @@ CAM_C::CAM_C(std::shared_ptr<Module> cam) : Module::Interface(std::move(cam), "c
|
|||
{0x00270140, nullptr, "SetAutoWhiteBalanceWindow"},
|
||||
{0x00280080, nullptr, "SetNoiseFilter"},
|
||||
{0x00290080, &CAM_C::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"},
|
||||
{0x002A0080, nullptr, "GetLatestVsyncTiming"},
|
||||
{0x002A0080, &CAM_C::GetLatestVsyncTiming, "GetLatestVsyncTiming"},
|
||||
{0x002B0000, &CAM_C::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"},
|
||||
{0x002C0400, nullptr, "SetStereoCameraCalibrationData"},
|
||||
{0x002D00C0, nullptr, "WriteRegisterI2c"},
|
||||
|
|
|
@ -51,7 +51,7 @@ CAM_S::CAM_S(std::shared_ptr<Module> cam) : Module::Interface(std::move(cam), "c
|
|||
{0x00270140, nullptr, "SetAutoWhiteBalanceWindow"},
|
||||
{0x00280080, nullptr, "SetNoiseFilter"},
|
||||
{0x00290080, &CAM_S::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"},
|
||||
{0x002A0080, nullptr, "GetLatestVsyncTiming"},
|
||||
{0x002A0080, &CAM_S::GetLatestVsyncTiming, "GetLatestVsyncTiming"},
|
||||
{0x002B0000, &CAM_S::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"},
|
||||
{0x002C0400, nullptr, "SetStereoCameraCalibrationData"},
|
||||
{0x002D00C0, nullptr, "WriteRegisterI2c"},
|
||||
|
|
|
@ -51,7 +51,7 @@ CAM_U::CAM_U(std::shared_ptr<Module> cam) : Module::Interface(std::move(cam), "c
|
|||
{0x00270140, nullptr, "SetAutoWhiteBalanceWindow"},
|
||||
{0x00280080, nullptr, "SetNoiseFilter"},
|
||||
{0x00290080, &CAM_U::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"},
|
||||
{0x002A0080, nullptr, "GetLatestVsyncTiming"},
|
||||
{0x002A0080, &CAM_U::GetLatestVsyncTiming, "GetLatestVsyncTiming"},
|
||||
{0x002B0000, &CAM_U::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"},
|
||||
{0x002C0400, nullptr, "SetStereoCameraCalibrationData"},
|
||||
{0x002D00C0, nullptr, "WriteRegisterI2c"},
|
||||
|
|
|
@ -101,7 +101,8 @@ static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exact
|
|||
} // namespace
|
||||
|
||||
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 UsernameBlock CONSOLE_USERNAME_BLOCK = {u"CITRA", 0, 0};
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -522,7 +535,8 @@ ResultCode Module::FormatConfig() {
|
|||
if (!res.IsSuccess())
|
||||
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())
|
||||
return res;
|
||||
|
||||
|
@ -536,7 +550,7 @@ ResultCode Module::FormatConfig() {
|
|||
if (!res.IsSuccess())
|
||||
return res;
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
} // namespace Service::CFG
|
||||
|
||||
ResultCode Module::LoadConfigNANDSaveFile() {
|
||||
std::string nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
|
||||
|
|
|
@ -104,7 +104,7 @@ void File::Write(Kernel::HLERequestContext& ctx) {
|
|||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||
|
||||
const FileSessionSlot* file = GetSessionData(ctx.Session());
|
||||
FileSessionSlot* file = GetSessionData(ctx.Session());
|
||||
|
||||
// Subfiles can not be written to
|
||||
if (file->subfile) {
|
||||
|
@ -117,6 +117,10 @@ void File::Write(Kernel::HLERequestContext& ctx) {
|
|||
std::vector<u8> data(length);
|
||||
buffer.Read(data.data(), 0, data.size());
|
||||
ResultVal<std::size_t> written = backend->Write(offset, data.size(), flush != 0, data.data());
|
||||
|
||||
// Update file size
|
||||
file->size = backend->GetSize();
|
||||
|
||||
if (written.Failed()) {
|
||||
rb.Push(written.Code());
|
||||
rb.Push<u32>(0);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "core/hle/service/mic_u.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_opengl/texture_filters/texture_filter_manager.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace Settings {
|
||||
|
@ -38,6 +39,9 @@ void Apply() {
|
|||
VideoCore::g_renderer_sampler_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();
|
||||
if (system.IsPoweredOn()) {
|
||||
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_PostProcessingShader", Settings::values.pp_shader_name);
|
||||
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_Factor3d", Settings::values.factor_3d);
|
||||
LogSetting("Layout_LayoutOption", static_cast<int>(Settings::values.layout_option));
|
||||
|
|
|
@ -147,6 +147,8 @@ struct Values {
|
|||
u16 resolution_factor;
|
||||
bool use_frame_limit;
|
||||
u16 frame_limit;
|
||||
u16 texture_filter_factor;
|
||||
std::string texture_filter_name;
|
||||
|
||||
LayoutOption layout_option;
|
||||
bool swap_screen;
|
||||
|
|
|
@ -50,6 +50,15 @@ add_library(video_core STATIC
|
|||
renderer_opengl/post_processing_opengl.h
|
||||
renderer_opengl/renderer_opengl.cpp
|
||||
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/shader.cpp
|
||||
shader/shader.h
|
||||
|
@ -80,6 +89,35 @@ add_library(video_core STATIC
|
|||
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)
|
||||
target_sources(video_core
|
||||
PRIVATE
|
||||
|
|
|
@ -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()
|
|
@ -34,6 +34,7 @@
|
|||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_state.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/video_core.h"
|
||||
|
||||
|
@ -42,12 +43,6 @@ namespace OpenGL {
|
|||
using SurfaceType = SurfaceParams::SurfaceType;
|
||||
using PixelFormat = SurfaceParams::PixelFormat;
|
||||
|
||||
struct FormatTuple {
|
||||
GLint internal_format;
|
||||
GLenum format;
|
||||
GLenum type;
|
||||
};
|
||||
|
||||
static constexpr std::array<FormatTuple, 5> fb_format_tuples = {{
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8}, // RGBA8
|
||||
{GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE}, // RGB8
|
||||
|
@ -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
|
||||
}};
|
||||
|
||||
static constexpr FormatTuple tex_tuple = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
|
||||
|
||||
static const FormatTuple& GetFormatTuple(PixelFormat pixel_format) {
|
||||
const FormatTuple& GetFormatTuple(PixelFormat pixel_format) {
|
||||
const SurfaceType type = SurfaceParams::GetFormatType(pixel_format);
|
||||
if (type == SurfaceType::Color) {
|
||||
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)
|
||||
return;
|
||||
|
||||
if (gl_buffer == nullptr) {
|
||||
gl_buffer_size = width * height * GetGLBytesPerPixel(pixel_format);
|
||||
gl_buffer.reset(new u8[gl_buffer_size]);
|
||||
if (gl_buffer.empty()) {
|
||||
gl_buffer.resize(width * height * GetGLBytesPerPixel(pixel_format));
|
||||
}
|
||||
|
||||
// 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)
|
||||
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
|
||||
// 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,
|
||||
Common::Rectangle<u32>& custom_rect) {
|
||||
bool CachedSurface::LoadCustomTexture(u64 tex_hash, Core::CustomTexInfo& tex_info) {
|
||||
bool result = false;
|
||||
auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache();
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
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) {
|
||||
if (type == SurfaceType::Fill)
|
||||
return;
|
||||
|
||||
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
|
||||
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)
|
||||
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)
|
||||
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
|
||||
GLint x0 = static_cast<GLint>(custom_rect.left);
|
||||
GLint y0 = static_cast<GLint>(custom_rect.bottom);
|
||||
GLint x0 = static_cast<GLint>(rect.left);
|
||||
GLint y0 = static_cast<GLint>(rect.bottom);
|
||||
std::size_t buffer_offset = (y0 * stride + x0) * GetGLBytesPerPixel(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
|
||||
// surface
|
||||
OGLTexture unscaled_tex;
|
||||
if (res_scale != 1) {
|
||||
if (res_scale != default_scale) {
|
||||
x0 = 0;
|
||||
y0 = 0;
|
||||
|
||||
|
@ -985,8 +969,8 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle<u32>& rect, GLuint r
|
|||
AllocateSurfaceTexture(unscaled_tex.handle, GetFormatTuple(PixelFormat::RGBA8),
|
||||
custom_tex_info.width, custom_tex_info.height);
|
||||
} else {
|
||||
AllocateSurfaceTexture(unscaled_tex.handle, tuple, custom_rect.GetWidth(),
|
||||
custom_rect.GetHeight());
|
||||
AllocateSurfaceTexture(unscaled_tex.handle, tuple, rect.GetWidth() * default_scale,
|
||||
rect.GetHeight() * default_scale);
|
||||
}
|
||||
target_tex = unscaled_tex.handle;
|
||||
}
|
||||
|
@ -1012,6 +996,16 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle<u32>& rect, GLuint r
|
|||
glActiveTexture(GL_TEXTURE0);
|
||||
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());
|
||||
} 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 {
|
||||
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);
|
||||
if (Settings::values.dump_textures && !is_custom)
|
||||
if (Settings::values.dump_textures && !is_custom && !texture_filter)
|
||||
DumpTexture(target_tex, tex_hash);
|
||||
|
||||
cur_state.texture_units[0].texture_2d = old_tex;
|
||||
cur_state.Apply();
|
||||
|
||||
if (res_scale != 1) {
|
||||
auto scaled_rect = custom_rect;
|
||||
if (res_scale != default_scale) {
|
||||
auto scaled_rect = rect;
|
||||
scaled_rect.left *= res_scale;
|
||||
scaled_rect.top *= res_scale;
|
||||
scaled_rect.right *= res_scale;
|
||||
scaled_rect.bottom *= res_scale;
|
||||
|
||||
BlitTextures(unscaled_tex.handle, {0, custom_rect.GetHeight(), custom_rect.GetWidth(), 0},
|
||||
texture.handle, scaled_rect, type, read_fb_handle, draw_fb_handle);
|
||||
auto from_rect =
|
||||
is_custom ? Common::Rectangle<u32>{0, custom_tex_info.height, custom_tex_info.width, 0}
|
||||
: 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();
|
||||
|
@ -1050,9 +1046,8 @@ void CachedSurface::DownloadGLTexture(const Common::Rectangle<u32>& rect, GLuint
|
|||
|
||||
MICROPROFILE_SCOPE(OpenGL_TextureDL);
|
||||
|
||||
if (gl_buffer == nullptr) {
|
||||
gl_buffer_size = width * height * GetGLBytesPerPixel(pixel_format);
|
||||
gl_buffer.reset(new u8[gl_buffer_size]);
|
||||
if (gl_buffer.empty()) {
|
||||
gl_buffer.resize(width * height * GetGLBytesPerPixel(pixel_format));
|
||||
}
|
||||
|
||||
OpenGLState state = OpenGLState::GetCurState();
|
||||
|
@ -1090,7 +1085,7 @@ void CachedSurface::DownloadGLTexture(const Common::Rectangle<u32>& rect, GLuint
|
|||
if (GLES) {
|
||||
GetTexImageOES(GL_TEXTURE_2D, 0, tuple.format, tuple.type, rect.GetHeight(),
|
||||
rect.GetWidth(), 0, &gl_buffer[buffer_offset],
|
||||
gl_buffer_size - buffer_offset);
|
||||
gl_buffer.size() - buffer_offset);
|
||||
} else {
|
||||
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) {
|
||||
u16 target_res_scale = params.res_scale;
|
||||
if (match_res_scale != ScaleMatch::Exact) {
|
||||
// This surface may have a subrect of another surface with a higher res_scale, find it
|
||||
// to adjust our params
|
||||
// This surface may have a subrect of another surface with a higher res_scale, find
|
||||
// it to adjust our params
|
||||
SurfaceParams find_params = params;
|
||||
Surface expandable = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>(
|
||||
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,
|
||||
ScaleMatch::Ignore);
|
||||
if (surface != nullptr) {
|
||||
ASSERT(surface->res_scale < params.res_scale);
|
||||
SurfaceParams new_params = *surface;
|
||||
new_params.res_scale = params.res_scale;
|
||||
|
||||
|
@ -1501,6 +1495,11 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInf
|
|||
params.height = info.height;
|
||||
params.is_tiled = true;
|
||||
params.pixel_format = SurfaceParams::PixelFormatFromTextureFormat(info.format);
|
||||
TextureFilterInterface* filter{};
|
||||
|
||||
params.res_scale = (filter = TextureFilterManager::GetInstance().GetTextureFilter())
|
||||
? filter->scale_factor
|
||||
: 1;
|
||||
params.UpdateParams();
|
||||
|
||||
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);
|
||||
if (!surface)
|
||||
return nullptr;
|
||||
|
||||
// Update mipmap if necessary
|
||||
if (max_level != 0) {
|
||||
|
@ -1544,8 +1545,8 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInf
|
|||
width = surface->custom_tex_info.width;
|
||||
height = surface->custom_tex_info.height;
|
||||
} else {
|
||||
width = surface->width * surface->res_scale;
|
||||
height = surface->height * surface->res_scale;
|
||||
width = surface->GetScaledWidth();
|
||||
height = surface->GetScaledHeight();
|
||||
}
|
||||
for (u32 level = surface->max_level + 1; level <= max_level; ++level) {
|
||||
glTexImage2D(GL_TEXTURE_2D, level, format_tuple.internal_format, width >> level,
|
||||
|
@ -1643,9 +1644,10 @@ const CachedTextureCube& RasterizerCacheOpenGL::GetTextureCube(const TextureCube
|
|||
if (surface) {
|
||||
face.watcher = surface->CreateWatcher();
|
||||
} else {
|
||||
// Can occur when texture address is invalid. We mark the watcher with nullptr in
|
||||
// this case and the content of the face wouldn't get updated. These are usually
|
||||
// leftover setup in the texture unit and games are not supposed to draw using them.
|
||||
// Can occur when texture address is invalid. We mark the watcher with nullptr
|
||||
// in this case and the content of the face wouldn't get updated. These are
|
||||
// usually leftover setup in the texture unit and games are not supposed to draw
|
||||
// using them.
|
||||
face.watcher = nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -1711,7 +1713,9 @@ SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(
|
|||
|
||||
// update resolution_scale_factor and reset cache if changed
|
||||
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();
|
||||
FlushAll();
|
||||
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)) {
|
||||
// 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
|
||||
// that region, anything higher than 8 you're guaranteed it comes from a service
|
||||
// the point is to avoid thousands of small writes every frame if the cpu decides to
|
||||
// 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;
|
||||
auto& surface = pair.second;
|
||||
|
||||
|
@ -2054,7 +2058,7 @@ Surface RasterizerCacheOpenGL::CreateSurface(const SurfaceParams& params) {
|
|||
|
||||
surface->texture.Create();
|
||||
|
||||
surface->gl_buffer_size = 0;
|
||||
surface->gl_buffer.resize(0);
|
||||
surface->invalid_regions.insert(surface->GetInterval());
|
||||
AllocateSurfaceTexture(surface->texture.handle, GetFormatTuple(surface->pixel_format),
|
||||
surface->GetScaledWidth(), surface->GetScaledHeight());
|
||||
|
|
|
@ -382,21 +382,18 @@ struct CachedSurface : SurfaceParams, std::enable_shared_from_this<CachedSurface
|
|||
: SurfaceParams::GetFormatBpp(format) / 8;
|
||||
}
|
||||
|
||||
std::unique_ptr<u8[]> gl_buffer;
|
||||
std::size_t gl_buffer_size = 0;
|
||||
std::vector<u8> gl_buffer;
|
||||
|
||||
// Read/Write data in 3DS memory to/from gl_buffer
|
||||
void LoadGLBuffer(PAddr load_start, PAddr load_end);
|
||||
void FlushGLBuffer(PAddr flush_start, PAddr flush_end);
|
||||
|
||||
// Custom texture loading and dumping
|
||||
bool LoadCustomTexture(u64 tex_hash, Core::CustomTexInfo& tex_info,
|
||||
Common::Rectangle<u32>& custom_rect);
|
||||
bool LoadCustomTexture(u64 tex_hash, Core::CustomTexInfo& tex_info);
|
||||
void DumpTexture(GLuint target_tex, u64 tex_hash);
|
||||
|
||||
// Upload/Download data in gl_buffer in/to this surface's texture
|
||||
void UploadGLTexture(const Common::Rectangle<u32>& rect, GLuint read_fb_handle,
|
||||
GLuint draw_fb_handle);
|
||||
void UploadGLTexture(Common::Rectangle<u32> rect, GLuint read_fb_handle, GLuint draw_fb_handle);
|
||||
void DownloadGLTexture(const Common::Rectangle<u32>& rect, GLuint read_fb_handle,
|
||||
GLuint draw_fb_handle);
|
||||
|
||||
|
@ -528,4 +525,14 @@ private:
|
|||
|
||||
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
|
||||
|
|
|
@ -38,6 +38,13 @@ constexpr GLuint ShadowTexturePZ = 5;
|
|||
constexpr GLuint ShadowTextureNZ = 6;
|
||||
} // namespace ImageUnits
|
||||
|
||||
struct Viewport {
|
||||
GLint x;
|
||||
GLint y;
|
||||
GLsizei width;
|
||||
GLsizei height;
|
||||
};
|
||||
|
||||
class OpenGLState {
|
||||
public:
|
||||
struct {
|
||||
|
@ -135,12 +142,7 @@ public:
|
|||
GLsizei height;
|
||||
} scissor;
|
||||
|
||||
struct {
|
||||
GLint x;
|
||||
GLint y;
|
||||
GLsizei width;
|
||||
GLsizei height;
|
||||
} viewport;
|
||||
Viewport viewport;
|
||||
|
||||
std::array<bool, 2> clip_distance; // GL_CLIP_DISTANCE
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#include "video_core/renderer_opengl/post_processing_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"
|
||||
|
||||
namespace Frontend {
|
||||
|
@ -1178,10 +1179,14 @@ VideoCore::ResultStatus RendererOpenGL::Init() {
|
|||
|
||||
RefreshRasterizerSetting();
|
||||
|
||||
TextureFilterManager::GetInstance().Reset();
|
||||
|
||||
return VideoCore::ResultStatus::Success;
|
||||
}
|
||||
|
||||
/// Shutdown the renderer
|
||||
void RendererOpenGL::ShutDown() {}
|
||||
void RendererOpenGL::ShutDown() {
|
||||
TextureFilterManager::GetInstance().Destroy();
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -59,6 +59,7 @@ ResultStatus Init(Frontend::EmuWindow& emu_window, Memory::MemorySystem& memory)
|
|||
void Shutdown() {
|
||||
Pica::Shutdown();
|
||||
|
||||
g_renderer->ShutDown();
|
||||
g_renderer.reset();
|
||||
|
||||
LOG_DEBUG(Render, "shutdown OK");
|
||||
|
|
Reference in New Issue