mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/master' into feature/savestates-2
This commit is contained in:
		
						commit
						7049af744f
					
				
					 77 changed files with 18323 additions and 11473 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue