diff --git a/src/citra/config.cpp b/src/citra/config.cpp index b2c878ddf6..e47820e182 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -128,6 +128,10 @@ void Config::ReadValues() { static_cast(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); Settings::values.use_vsync_new = static_cast(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( sdl2_config->GetInteger("Renderer", "render_3d", 0)); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 9c441e3542..c3ffb37d3e 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -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 = diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 82274bff09..1c0de30ba8 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -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(); } diff --git a/src/citra_qt/configuration/configure_enhancements.cpp b/src/citra_qt/configuration/configure_enhancements.cpp index 6c83018a32..e1c6b6bdd8 100644 --- a/src/citra_qt/configuration/configure_enhancements.cpp +++ b/src/citra_qt/configuration/configure_enhancements.cpp @@ -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::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(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(ui->layout_combobox->currentIndex()); Settings::values.swap_screen = ui->swap_screen->isChecked(); diff --git a/src/citra_qt/configuration/configure_enhancements.h b/src/citra_qt/configuration/configure_enhancements.h index 7155140bd9..9526d056f3 100644 --- a/src/citra_qt/configuration/configure_enhancements.h +++ b/src/citra_qt/configuration/configure_enhancements.h @@ -27,6 +27,7 @@ public: private: void updateShaders(Settings::StereoRenderOption stereo_option); + void updateTextureFilter(int index); Ui::ConfigureEnhancements* ui; QColor bg_color; diff --git a/src/citra_qt/configuration/configure_enhancements.ui b/src/citra_qt/configuration/configure_enhancements.ui index 289c1178e5..d3193e9ebe 100644 --- a/src/citra_qt/configuration/configure_enhancements.ui +++ b/src/citra_qt/configuration/configure_enhancements.ui @@ -117,6 +117,56 @@ + + + + + + Texture Filter + + + + + + + + + + + + + 16 + + + 0 + + + 0 + + + 0 + + + + + + + Texture Scale Factor + + + + + + + 1 + + + + + + + + diff --git a/src/core/settings.cpp b/src/core/settings.cpp index ec3a361158..f6792604bf 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -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(Settings::values.render_3d)); LogSetting("Stereoscopy_Factor3d", Settings::values.factor_3d); LogSetting("Layout_LayoutOption", static_cast(Settings::values.layout_option)); diff --git a/src/core/settings.h b/src/core/settings.h index 78b11912c5..bb04d9d9f9 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -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; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 4cb9763543..a7dbc42de1 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -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 diff --git a/src/video_core/generate_shaders.cmake b/src/video_core/generate_shaders.cmake new file mode 100644 index 0000000000..e0adca65fb --- /dev/null +++ b/src/video_core/generate_shaders.cmake @@ -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() diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index af2a918a6f..cbf7d49260 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -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 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 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(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& 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& rect, GLuint read_fb_handle, +void CachedSurface::UploadGLTexture(Common::Rectangle 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(custom_rect.left); - GLint y0 = static_cast(custom_rect.bottom); + GLint x0 = static_cast(rect.left); + GLint y0 = static_cast(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& 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& 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& 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(stride)); @@ -1022,21 +1016,23 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle& 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{0, custom_tex_info.height, custom_tex_info.width, 0} + : Common::Rectangle{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& 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& 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]); } @@ -1373,8 +1368,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( surface_cache, find_params, match_res_scale); @@ -1423,7 +1418,6 @@ SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams& surface = FindMatch(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; @@ -1503,6 +1497,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; @@ -1520,6 +1519,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) { @@ -1546,8 +1547,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, @@ -1645,9 +1646,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; } } @@ -1713,7 +1715,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()) @@ -1936,8 +1940,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; @@ -2031,7 +2035,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()); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index f484de2137..466ebb8d05 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -382,21 +382,18 @@ struct CachedSurface : SurfaceParams, std::enable_shared_from_this gl_buffer; - std::size_t gl_buffer_size = 0; + std::vector 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& 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& rect, GLuint read_fb_handle, - GLuint draw_fb_handle); + void UploadGLTexture(Common::Rectangle rect, GLuint read_fb_handle, GLuint draw_fb_handle); void DownloadGLTexture(const Common::Rectangle& rect, GLuint read_fb_handle, GLuint draw_fb_handle); @@ -525,4 +522,14 @@ private: std::unordered_map 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 diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index cc7a864a22..5d655a1274 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -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 clip_distance; // GL_CLIP_DISTANCE diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 5046895e09..e7e0bb72f4 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -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 diff --git a/src/video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.cpp b/src/video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.cpp new file mode 100644 index 0000000000..86b4a85b69 --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.cpp @@ -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& 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(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 diff --git a/src/video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h b/src/video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h new file mode 100644 index 0000000000..79a058fd53 --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h @@ -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; + return info; + } + + Anime4kUltrafast(u16 scale_factor); + void scale(CachedSurface& surface, const Common::Rectangle& 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 samplers; + + OGLProgram gradient_x_program, gradient_y_program, refine_program; +}; + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/texture_filters/anime4k/refine.frag b/src/video_core/renderer_opengl/texture_filters/anime4k/refine.frag new file mode 100644 index 0000000000..26f2526baa --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/anime4k/refine.frag @@ -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(); +} diff --git a/src/video_core/renderer_opengl/texture_filters/anime4k/refine.vert b/src/video_core/renderer_opengl/texture_filters/anime4k/refine.vert new file mode 100644 index 0000000000..552a218fb4 --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/anime4k/refine.vert @@ -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; +} diff --git a/src/video_core/renderer_opengl/texture_filters/anime4k/x_gradient.frag b/src/video_core/renderer_opengl/texture_filters/anime4k/x_gradient.frag new file mode 100644 index 0000000000..49502fac71 --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/anime4k/x_gradient.frag @@ -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); +} diff --git a/src/video_core/renderer_opengl/texture_filters/anime4k/y_gradient.frag b/src/video_core/renderer_opengl/texture_filters/anime4k/y_gradient.frag new file mode 100644 index 0000000000..a0e820001e --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/anime4k/y_gradient.frag @@ -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); +} diff --git a/src/video_core/renderer_opengl/texture_filters/anime4k/y_gradient.vert b/src/video_core/renderer_opengl/texture_filters/anime4k/y_gradient.vert new file mode 100644 index 0000000000..376a67b79c --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/anime4k/y_gradient.vert @@ -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; +} diff --git a/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.cpp b/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.cpp new file mode 100644 index 0000000000..16f5bd2c58 --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.cpp @@ -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& 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(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, NULL, 0); + + cur_state.Apply(); +} + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.frag b/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.frag new file mode 100644 index 0000000000..2bdab3cf62 --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.frag @@ -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); +} diff --git a/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.h b/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.h new file mode 100644 index 0000000000..a16bdafcf9 --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.h @@ -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; + return info; + } + + Bicubic(u16 scale_factor); + void scale(CachedSurface& surface, const Common::Rectangle& rect, + std::size_t buffer_offset) override; + +private: + OpenGLState state{}; + OGLProgram program{}; + OGLVertexArray vao{}; + OGLFramebuffer draw_fbo{}; + OGLSampler src_sampler{}; +}; +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/texture_filters/tex_coord.vert b/src/video_core/renderer_opengl/texture_filters/tex_coord.vert new file mode 100644 index 0000000000..e5e1533302 --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/tex_coord.vert @@ -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; +} diff --git a/src/video_core/renderer_opengl/texture_filters/texture_filter_interface.h b/src/video_core/renderer_opengl/texture_filters/texture_filter_interface.h new file mode 100644 index 0000000000..c2bf2a6863 --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/texture_filter_interface.h @@ -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 +#include +#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& rect, + std::size_t buffer_offset) = 0; + virtual ~TextureFilterInterface() = default; + +protected: + Viewport RectToViewport(const Common::Rectangle& 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(u16 scale_factor)> constructor; +}; + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/texture_filters/texture_filter_manager.cpp b/src/video_core/renderer_opengl/texture_filters/texture_filter_manager.cpp new file mode 100644 index 0000000000..77d07111e1 --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/texture_filter_manager.cpp @@ -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& rect) { + return { + static_cast(rect.left) * scale_factor, + static_cast(rect.top) * scale_factor, + static_cast(rect.GetWidth()) * scale_factor, + static_cast(rect.GetHeight()) * scale_factor, + }; +} + +namespace { +template +std::pair 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& +TextureFilterManager::TextureFilterMap() { + static const std::map filter_map{ + FilterMapPair(), + FilterMapPair(), + FilterMapPair(), + FilterMapPair(), + }; + 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 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 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 diff --git a/src/video_core/renderer_opengl/texture_filters/texture_filter_manager.h b/src/video_core/renderer_opengl/texture_filters/texture_filter_manager.h new file mode 100644 index 0000000000..1299a91511 --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/texture_filter_manager.h @@ -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 +#include +#include +#include +#include +#include +#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& 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 updated{false}; + std::mutex mutex; + std::string name{"none"}; + u16 scale_factor{1}; + + std::unique_ptr filter; +}; + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.cpp b/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.cpp new file mode 100644 index 0000000000..0916666273 --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.cpp @@ -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 . + +// 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(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& 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(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, NULL, 0); + + cur_state.Apply(); +} + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.frag b/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.frag new file mode 100644 index 0000000000..4868d18f7e --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.frag @@ -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; +} diff --git a/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.h b/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.h new file mode 100644 index 0000000000..aad10f308c --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.h @@ -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; + return info; + } + + XbrzFreescale(u16 scale_factor); + void scale(CachedSurface& surface, const Common::Rectangle& rect, + std::size_t buffer_offset) override; + +private: + OpenGLState state{}; + OGLProgram program{}; + OGLVertexArray vao{}; + OGLFramebuffer draw_fbo{}; + OGLSampler src_sampler{}; +}; +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.vert b/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.vert new file mode 100644 index 0000000000..adf45d5644 --- /dev/null +++ b/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.vert @@ -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; +} diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index b7e1d4885b..a7ed5fb9e8 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -57,6 +57,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");