mirror of
https://github.com/citra-emu/citra.git
synced 2024-12-18 08:10:03 +00:00
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.
|
||||
|
||||
|
961
dist/languages/da_DK.ts
vendored
961
dist/languages/da_DK.ts
vendored
File diff suppressed because it is too large
Load Diff
1027
dist/languages/de.ts
vendored
1027
dist/languages/de.ts
vendored
File diff suppressed because it is too large
Load Diff
1041
dist/languages/es_ES.ts
vendored
1041
dist/languages/es_ES.ts
vendored
File diff suppressed because it is too large
Load Diff
997
dist/languages/fi.ts
vendored
997
dist/languages/fi.ts
vendored
File diff suppressed because it is too large
Load Diff
5256
dist/languages/fi_FI.ts
vendored
Normal file
5256
dist/languages/fi_FI.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1046
dist/languages/fr.ts
vendored
1046
dist/languages/fr.ts
vendored
File diff suppressed because it is too large
Load Diff
959
dist/languages/hu_HU.ts
vendored
959
dist/languages/hu_HU.ts
vendored
File diff suppressed because it is too large
Load Diff
1181
dist/languages/id.ts
vendored
1181
dist/languages/id.ts
vendored
File diff suppressed because it is too large
Load Diff
1233
dist/languages/it.ts
vendored
1233
dist/languages/it.ts
vendored
File diff suppressed because it is too large
Load Diff
1077
dist/languages/ja_JP.ts
vendored
1077
dist/languages/ja_JP.ts
vendored
File diff suppressed because it is too large
Load Diff
1050
dist/languages/ko_KR.ts
vendored
1050
dist/languages/ko_KR.ts
vendored
File diff suppressed because it is too large
Load Diff
961
dist/languages/lt_LT.ts
vendored
961
dist/languages/lt_LT.ts
vendored
File diff suppressed because it is too large
Load Diff
967
dist/languages/nb.ts
vendored
967
dist/languages/nb.ts
vendored
File diff suppressed because it is too large
Load Diff
1089
dist/languages/nl.ts
vendored
1089
dist/languages/nl.ts
vendored
File diff suppressed because it is too large
Load Diff
961
dist/languages/pl_PL.ts
vendored
961
dist/languages/pl_PL.ts
vendored
File diff suppressed because it is too large
Load Diff
1112
dist/languages/pt_BR.ts
vendored
1112
dist/languages/pt_BR.ts
vendored
File diff suppressed because it is too large
Load Diff
959
dist/languages/pt_PT.ts
vendored
959
dist/languages/pt_PT.ts
vendored
File diff suppressed because it is too large
Load Diff
961
dist/languages/ro_RO.ts
vendored
961
dist/languages/ro_RO.ts
vendored
File diff suppressed because it is too large
Load Diff
963
dist/languages/ru_RU.ts
vendored
963
dist/languages/ru_RU.ts
vendored
File diff suppressed because it is too large
Load Diff
977
dist/languages/tr_TR.ts
vendored
977
dist/languages/tr_TR.ts
vendored
File diff suppressed because it is too large
Load Diff
1237
dist/languages/vi_VN.ts
vendored
1237
dist/languages/vi_VN.ts
vendored
File diff suppressed because it is too large
Load Diff
969
dist/languages/zh_CN.ts
vendored
969
dist/languages/zh_CN.ts
vendored
File diff suppressed because it is too large
Load Diff
961
dist/languages/zh_TW.ts
vendored
961
dist/languages/zh_TW.ts
vendored
File diff suppressed because it is too large
Load Diff
6
dist/license.md
vendored
6
dist/license.md
vendored
@ -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
|
||||
|
BIN
dist/qt_themes/default/icons/16x16/checked.png
vendored
BIN
dist/qt_themes/default/icons/16x16/checked.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 451 B After Width: | Height: | Size: 657 B |
BIN
dist/qt_themes/default/icons/16x16/failed.png
vendored
BIN
dist/qt_themes/default/icons/16x16/failed.png
vendored
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
|
||||
|
239
src/audio_core/hle/mediandk_decoder.cpp
Normal file
239
src/audio_core/hle/mediandk_decoder.cpp
Normal file
@ -0,0 +1,239 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <media/NdkMediaCodec.h>
|
||||
#include <media/NdkMediaError.h>
|
||||
#include <media/NdkMediaFormat.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/hle/adts.h"
|
||||
#include "audio_core/hle/mediandk_decoder.h"
|
||||
|
||||
namespace AudioCore::HLE {
|
||||
|
||||
struct AMediaCodecRelease {
|
||||
void operator()(AMediaCodec* codec) const {
|
||||
AMediaCodec_stop(codec);
|
||||
AMediaCodec_delete(codec);
|
||||
};
|
||||
};
|
||||
|
||||
class MediaNDKDecoder::Impl {
|
||||
public:
|
||||
explicit Impl(Memory::MemorySystem& memory);
|
||||
~Impl();
|
||||
std::optional<BinaryResponse> ProcessRequest(const BinaryRequest& request);
|
||||
|
||||
bool SetMediaType(const ADTSData& adts_data);
|
||||
|
||||
private:
|
||||
std::optional<BinaryResponse> Initalize(const BinaryRequest& request);
|
||||
std::optional<BinaryResponse> Decode(const BinaryRequest& request);
|
||||
|
||||
Memory::MemorySystem& mMemory;
|
||||
std::unique_ptr<AMediaCodec, AMediaCodecRelease> mDecoder;
|
||||
// default: 2 channles, 48000 samplerate
|
||||
ADTSData mADTSData{/* MPEG2 */ false, /*profile*/ 2, /*channels*/ 2,
|
||||
/*channel_idx*/ 2, /*framecount*/ 0, /*samplerate_idx*/ 3,
|
||||
/*length*/ 0, /*samplerate*/ 48000};
|
||||
};
|
||||
|
||||
MediaNDKDecoder::Impl::Impl(Memory::MemorySystem& memory) : mMemory(memory) {
|
||||
SetMediaType(mADTSData);
|
||||
}
|
||||
|
||||
MediaNDKDecoder::Impl::~Impl() = default;
|
||||
|
||||
std::optional<BinaryResponse> MediaNDKDecoder::Impl::Initalize(const BinaryRequest& request) {
|
||||
BinaryResponse response;
|
||||
std::memcpy(&response, &request, sizeof(response));
|
||||
response.unknown1 = 0x0;
|
||||
return response;
|
||||
}
|
||||
|
||||
bool MediaNDKDecoder::Impl::SetMediaType(const ADTSData& adts_data) {
|
||||
const char* mime = "audio/mp4a-latm";
|
||||
if (mDecoder && mADTSData.profile == adts_data.profile &&
|
||||
mADTSData.channel_idx == adts_data.channel_idx &&
|
||||
mADTSData.samplerate_idx == adts_data.samplerate_idx) {
|
||||
return true;
|
||||
}
|
||||
mDecoder.reset(AMediaCodec_createDecoderByType(mime));
|
||||
if (mDecoder == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u8 csd_0[2];
|
||||
csd_0[0] = static_cast<u8>((adts_data.profile << 3) | (adts_data.samplerate_idx >> 1));
|
||||
csd_0[1] =
|
||||
static_cast<u8>(((adts_data.samplerate_idx << 7) & 0x80) | (adts_data.channel_idx << 3));
|
||||
AMediaFormat* format = AMediaFormat_new();
|
||||
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, mime);
|
||||
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, adts_data.samplerate);
|
||||
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, adts_data.channels);
|
||||
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_IS_ADTS, 1);
|
||||
AMediaFormat_setBuffer(format, "csd-0", csd_0, sizeof(csd_0));
|
||||
|
||||
media_status_t status = AMediaCodec_configure(mDecoder.get(), format, NULL, NULL, 0);
|
||||
if (status != AMEDIA_OK) {
|
||||
AMediaFormat_delete(format);
|
||||
mDecoder.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
status = AMediaCodec_start(mDecoder.get());
|
||||
if (status != AMEDIA_OK) {
|
||||
AMediaFormat_delete(format);
|
||||
mDecoder.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
AMediaFormat_delete(format);
|
||||
mADTSData = adts_data;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<BinaryResponse> MediaNDKDecoder::Impl::ProcessRequest(const BinaryRequest& request) {
|
||||
if (request.codec != DecoderCodec::AAC) {
|
||||
LOG_ERROR(Audio_DSP, "AAC Decoder cannot handle such codec: {}",
|
||||
static_cast<u16>(request.codec));
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (request.cmd) {
|
||||
case DecoderCommand::Init: {
|
||||
return Initalize(request);
|
||||
}
|
||||
case DecoderCommand::Decode: {
|
||||
return Decode(request);
|
||||
}
|
||||
case DecoderCommand::Unknown: {
|
||||
BinaryResponse response;
|
||||
std::memcpy(&response, &request, sizeof(response));
|
||||
response.unknown1 = 0x0;
|
||||
return response;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", static_cast<u16>(request.cmd));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<BinaryResponse> MediaNDKDecoder::Impl::Decode(const BinaryRequest& request) {
|
||||
BinaryResponse response;
|
||||
response.codec = request.codec;
|
||||
response.cmd = request.cmd;
|
||||
response.size = request.size;
|
||||
response.num_samples = 1024;
|
||||
|
||||
if (request.src_addr < Memory::FCRAM_PADDR ||
|
||||
request.src_addr + request.size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}", request.src_addr);
|
||||
return response;
|
||||
}
|
||||
|
||||
u8* data = mMemory.GetFCRAMPointer(request.src_addr - Memory::FCRAM_PADDR);
|
||||
ADTSData adts_data = ParseADTS(reinterpret_cast<const char*>(data));
|
||||
SetMediaType(adts_data);
|
||||
response.num_channels = adts_data.channels;
|
||||
if (!mDecoder) {
|
||||
LOG_ERROR(Audio_DSP, "Missing decoder for profile: {}, channels: {}, samplerate: {}",
|
||||
adts_data.profile, adts_data.channels, adts_data.samplerate);
|
||||
return {};
|
||||
}
|
||||
|
||||
// input
|
||||
constexpr int timeout = 160;
|
||||
std::size_t buffer_size = 0;
|
||||
u8* buffer = nullptr;
|
||||
ssize_t buffer_index = AMediaCodec_dequeueInputBuffer(mDecoder.get(), timeout);
|
||||
if (buffer_index < 0) {
|
||||
LOG_ERROR(Audio_DSP, "Failed to enqueue the input samples: {}", buffer_index);
|
||||
return response;
|
||||
}
|
||||
buffer = AMediaCodec_getInputBuffer(mDecoder.get(), buffer_index, &buffer_size);
|
||||
if (buffer_size < request.size) {
|
||||
return response;
|
||||
}
|
||||
std::memcpy(buffer, data, request.size);
|
||||
media_status_t status =
|
||||
AMediaCodec_queueInputBuffer(mDecoder.get(), buffer_index, 0, request.size, 0, 0);
|
||||
if (status != AMEDIA_OK) {
|
||||
LOG_WARNING(Audio_DSP, "Try queue input buffer again later!");
|
||||
return response;
|
||||
}
|
||||
|
||||
// output
|
||||
AMediaCodecBufferInfo info;
|
||||
std::array<std::vector<u16>, 2> out_streams;
|
||||
buffer_index = AMediaCodec_dequeueOutputBuffer(mDecoder.get(), &info, timeout);
|
||||
switch (buffer_index) {
|
||||
case AMEDIACODEC_INFO_TRY_AGAIN_LATER:
|
||||
LOG_WARNING(Audio_DSP, "Failed to dequeue output buffer: timeout!");
|
||||
break;
|
||||
case AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED:
|
||||
LOG_WARNING(Audio_DSP, "Failed to dequeue output buffer: buffers changed!");
|
||||
break;
|
||||
case AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED: {
|
||||
AMediaFormat* format = AMediaCodec_getOutputFormat(mDecoder.get());
|
||||
LOG_WARNING(Audio_DSP, "output format: {}", AMediaFormat_toString(format));
|
||||
AMediaFormat_delete(format);
|
||||
buffer_index = AMediaCodec_dequeueOutputBuffer(mDecoder.get(), &info, timeout);
|
||||
}
|
||||
default: {
|
||||
int offset = info.offset;
|
||||
buffer = AMediaCodec_getOutputBuffer(mDecoder.get(), buffer_index, &buffer_size);
|
||||
while (offset < info.size) {
|
||||
for (int channel = 0; channel < response.num_channels; channel++) {
|
||||
u16 pcm_data;
|
||||
std::memcpy(&pcm_data, buffer + offset, sizeof(pcm_data));
|
||||
out_streams[channel].push_back(pcm_data);
|
||||
offset += sizeof(pcm_data);
|
||||
}
|
||||
}
|
||||
AMediaCodec_releaseOutputBuffer(mDecoder.get(), buffer_index, info.size != 0);
|
||||
}
|
||||
}
|
||||
|
||||
// transfer the decoded buffer from vector to the FCRAM
|
||||
size_t stream0_size = out_streams[0].size() * sizeof(u16);
|
||||
if (stream0_size != 0) {
|
||||
if (request.dst_addr_ch0 < Memory::FCRAM_PADDR ||
|
||||
request.dst_addr_ch0 + stream0_size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch0 {:08x}", request.dst_addr_ch0);
|
||||
return response;
|
||||
}
|
||||
std::memcpy(mMemory.GetFCRAMPointer(request.dst_addr_ch0 - Memory::FCRAM_PADDR),
|
||||
out_streams[0].data(), stream0_size);
|
||||
}
|
||||
|
||||
size_t stream1_size = out_streams[1].size() * sizeof(u16);
|
||||
if (stream1_size != 0) {
|
||||
if (request.dst_addr_ch1 < Memory::FCRAM_PADDR ||
|
||||
request.dst_addr_ch1 + stream1_size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch1 {:08x}", request.dst_addr_ch1);
|
||||
return response;
|
||||
}
|
||||
std::memcpy(mMemory.GetFCRAMPointer(request.dst_addr_ch1 - Memory::FCRAM_PADDR),
|
||||
out_streams[1].data(), stream1_size);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
MediaNDKDecoder::MediaNDKDecoder(Memory::MemorySystem& memory)
|
||||
: impl(std::make_unique<Impl>(memory)) {}
|
||||
|
||||
MediaNDKDecoder::~MediaNDKDecoder() = default;
|
||||
|
||||
std::optional<BinaryResponse> MediaNDKDecoder::ProcessRequest(const BinaryRequest& request) {
|
||||
return impl->ProcessRequest(request);
|
||||
}
|
||||
|
||||
bool MediaNDKDecoder::IsValid() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::HLE
|
22
src/audio_core/hle/mediandk_decoder.h
Normal file
22
src/audio_core/hle/mediandk_decoder.h
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/hle/decoder.h"
|
||||
|
||||
namespace AudioCore::HLE {
|
||||
|
||||
class MediaNDKDecoder final : public DecoderBase {
|
||||
public:
|
||||
explicit MediaNDKDecoder(Memory::MemorySystem& memory);
|
||||
~MediaNDKDecoder() override;
|
||||
std::optional<BinaryResponse> ProcessRequest(const BinaryRequest& request) override;
|
||||
bool IsValid() const override;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::HLE
|
@ -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
|
||||
|
16
src/video_core/generate_shaders.cmake
Normal file
16
src/video_core/generate_shaders.cmake
Normal file
@ -0,0 +1,16 @@
|
||||
function(GetShaderHeaderFile shader_file_name)
|
||||
set(shader_header_file ${CMAKE_CURRENT_BINARY_DIR}/shaders/${shader_file_name} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
foreach(shader_file ${SHADER_FILES})
|
||||
file(READ ${shader_file} shader)
|
||||
get_filename_component(shader_file_name ${shader_file} NAME)
|
||||
string(REPLACE . _ shader_name ${shader_file_name})
|
||||
GetShaderHeaderFile(${shader_file_name})
|
||||
file(WRITE ${shader_header_file}
|
||||
"#pragma once\n"
|
||||
"constexpr std::string_view ${shader_name} = R\"(\n"
|
||||
"${shader}"
|
||||
")\";\n"
|
||||
)
|
||||
endforeach()
|
@ -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");
|
||||
|
Loading…
Reference in New Issue
Block a user