From 17ad594a62915177bf9227065c4349076db724df Mon Sep 17 00:00:00 2001 From: emufan4568 Date: Sun, 21 Aug 2022 00:50:20 +0300 Subject: [PATCH] rasterizer_cache: Introduce TextureRuntime and separate CachedSurface * This commit aims to both continue the rasterizer cache cleanup by separating CachedSurface into a dedicated header and to start weeding out the raw OpenGL code from the cache. * The latter is achieved by abstracting most texture operations in a new class called TextureRuntime. This has many benefits such as making it easier to port the functionality to other graphics APIs and the removal of the need to pass (read/draw) framebuffer handles everywhere. The filterer and reinterpreter get their own sets of FBOs due to this, something that might be a performance win since it reduces the state switching overhead on the runtime FBOs. --- src/video_core/CMakeLists.txt | 4 + .../rasterizer_cache/cached_surface.cpp | 479 ++++++++++ .../rasterizer_cache/cached_surface.h | 136 +++ .../rasterizer_cache/pixel_format.h | 16 +- .../rasterizer_cache/rasterizer_cache.cpp | 853 +++--------------- .../rasterizer_cache/rasterizer_cache.h | 131 +-- .../rasterizer_cache/rasterizer_cache_types.h | 2 +- .../rasterizer_cache/rasterizer_cache_utils.h | 3 - .../rasterizer_cache/texture_runtime.cpp | 196 ++++ .../rasterizer_cache/texture_runtime.h | 73 ++ .../gl_format_reinterpreter.cpp | 107 ++- .../renderer_opengl/gl_format_reinterpreter.h | 56 +- .../anime4k/anime4k_ultrafast.cpp | 13 +- .../anime4k/anime4k_ultrafast.h | 5 +- .../texture_filters/bicubic/bicubic.cpp | 13 +- .../texture_filters/bicubic/bicubic.h | 5 +- .../scale_force/scale_force.cpp | 13 +- .../texture_filters/scale_force/scale_force.h | 5 +- .../texture_filters/texture_filter_base.h | 22 +- .../texture_filters/texture_filterer.cpp | 13 +- .../texture_filters/texture_filterer.h | 16 +- .../texture_filters/xbrz/xbrz_freescale.cpp | 21 +- .../texture_filters/xbrz/xbrz_freescale.h | 6 +- 23 files changed, 1176 insertions(+), 1012 deletions(-) create mode 100644 src/video_core/rasterizer_cache/cached_surface.cpp create mode 100644 src/video_core/rasterizer_cache/cached_surface.h create mode 100644 src/video_core/rasterizer_cache/texture_runtime.cpp create mode 100644 src/video_core/rasterizer_cache/texture_runtime.h diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 6a1641c81..f117e3182 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -23,6 +23,8 @@ add_library(video_core STATIC regs_texturing.h renderer_base.cpp renderer_base.h + rasterizer_cache/cached_surface.cpp + rasterizer_cache/cached_surface.h rasterizer_cache/morton_swizzle.h rasterizer_cache/pixel_format.h rasterizer_cache/rasterizer_cache.cpp @@ -32,6 +34,8 @@ add_library(video_core STATIC rasterizer_cache/rasterizer_cache_utils.h rasterizer_cache/surface_params.cpp rasterizer_cache/surface_params.h + rasterizer_cache/texture_runtime.cpp + rasterizer_cache/texture_runtime.h renderer_opengl/frame_dumper_opengl.cpp renderer_opengl/frame_dumper_opengl.h renderer_opengl/gl_rasterizer.cpp diff --git a/src/video_core/rasterizer_cache/cached_surface.cpp b/src/video_core/rasterizer_cache/cached_surface.cpp new file mode 100644 index 000000000..136b37df6 --- /dev/null +++ b/src/video_core/rasterizer_cache/cached_surface.cpp @@ -0,0 +1,479 @@ +// Copyright 2022 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/microprofile.h" +#include "common/texture.h" +#include "common/scope_exit.h" +#include "core/core.h" +#include "video_core/rasterizer_cache/cached_surface.h" +#include "video_core/rasterizer_cache/morton_swizzle.h" +#include "video_core/rasterizer_cache/rasterizer_cache.h" +#include "video_core/renderer_opengl/texture_filters/texture_filterer.h" +#include "video_core/renderer_opengl/gl_state.h" +#include "video_core/renderer_opengl/texture_downloader_es.h" + +namespace OpenGL { + +static Aspect ToAspect(SurfaceType type) { + switch (type) { + case SurfaceType::Color: + case SurfaceType::Texture: + case SurfaceType::Fill: + return Aspect::Color; + case SurfaceType::Depth: + return Aspect::Depth; + case SurfaceType::DepthStencil: + return Aspect::DepthStencil; + default: + LOG_CRITICAL(Render_OpenGL, "Unknown SurfaceType {}", type); + UNREACHABLE(); + } + + return Aspect::Color; +} + +CachedSurface::~CachedSurface() { + if (texture.handle) { + auto tag = is_custom ? HostTextureTag{GetFormatTuple(PixelFormat::RGBA8), + custom_tex_info.width, custom_tex_info.height} + : HostTextureTag{GetFormatTuple(pixel_format), GetScaledWidth(), + GetScaledHeight()}; + + owner.host_texture_recycler.emplace(tag, std::move(texture)); + } +} + +MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 192, 64)); +void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) { + ASSERT(type != SurfaceType::Fill); + const bool need_swap = + GLES && (pixel_format == PixelFormat::RGBA8 || pixel_format == PixelFormat::RGB8); + + const u8* const texture_src_data = VideoCore::g_memory->GetPhysicalPointer(addr); + if (texture_src_data == nullptr) + return; + + if (gl_buffer.empty()) { + gl_buffer.resize(width * height * GetBytesPerPixel(pixel_format)); + } + + // TODO: Should probably be done in ::Memory:: and check for other regions too + if (load_start < Memory::VRAM_VADDR_END && load_end > Memory::VRAM_VADDR_END) + load_end = Memory::VRAM_VADDR_END; + + if (load_start < Memory::VRAM_VADDR && load_end > Memory::VRAM_VADDR) + load_start = Memory::VRAM_VADDR; + + MICROPROFILE_SCOPE(OpenGL_SurfaceLoad); + + ASSERT(load_start >= addr && load_end <= end); + const u32 start_offset = load_start - addr; + + if (!is_tiled) { + ASSERT(type == SurfaceType::Color); + if (need_swap) { + // TODO(liushuyu): check if the byteswap here is 100% correct + // cannot fully test this + if (pixel_format == PixelFormat::RGBA8) { + for (std::size_t i = start_offset; i < load_end - addr; i += 4) { + gl_buffer[i] = texture_src_data[i + 3]; + gl_buffer[i + 1] = texture_src_data[i + 2]; + gl_buffer[i + 2] = texture_src_data[i + 1]; + gl_buffer[i + 3] = texture_src_data[i]; + } + } else if (pixel_format == PixelFormat::RGB8) { + for (std::size_t i = start_offset; i < load_end - addr; i += 3) { + gl_buffer[i] = texture_src_data[i + 2]; + gl_buffer[i + 1] = texture_src_data[i + 1]; + gl_buffer[i + 2] = texture_src_data[i]; + } + } + } else { + std::memcpy(&gl_buffer[start_offset], texture_src_data + start_offset, + load_end - load_start); + } + } else { + if (type == SurfaceType::Texture) { + Pica::Texture::TextureInfo tex_info{}; + tex_info.width = width; + tex_info.height = height; + tex_info.format = static_cast(pixel_format); + tex_info.SetDefaultStride(); + tex_info.physical_address = addr; + + const SurfaceInterval load_interval(load_start, load_end); + const auto rect = GetSubRect(FromInterval(load_interval)); + ASSERT(FromInterval(load_interval).GetInterval() == load_interval); + + for (unsigned y = rect.bottom; y < rect.top; ++y) { + for (unsigned x = rect.left; x < rect.right; ++x) { + auto vec4 = + Pica::Texture::LookupTexture(texture_src_data, x, height - 1 - y, tex_info); + const std::size_t offset = (x + (width * y)) * 4; + std::memcpy(&gl_buffer[offset], vec4.AsArray(), 4); + } + } + } else { + morton_to_gl_fns[static_cast(pixel_format)](stride, height, &gl_buffer[0], + addr, load_start, load_end); + } + } +} + +MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64)); +void CachedSurface::FlushGLBuffer(PAddr flush_start, PAddr flush_end) { + u8* const dst_buffer = VideoCore::g_memory->GetPhysicalPointer(addr); + if (dst_buffer == nullptr) + return; + + ASSERT(gl_buffer.size() == width * height * GetBytesPerPixel(pixel_format)); + + // TODO: Should probably be done in ::Memory:: and check for other regions too + // same as loadglbuffer() + if (flush_start < Memory::VRAM_VADDR_END && flush_end > Memory::VRAM_VADDR_END) + flush_end = Memory::VRAM_VADDR_END; + + if (flush_start < Memory::VRAM_VADDR && flush_end > Memory::VRAM_VADDR) + flush_start = Memory::VRAM_VADDR; + + MICROPROFILE_SCOPE(OpenGL_SurfaceFlush); + + ASSERT(flush_start >= addr && flush_end <= end); + const u32 start_offset = flush_start - addr; + const u32 end_offset = flush_end - addr; + + if (type == SurfaceType::Fill) { + const u32 coarse_start_offset = start_offset - (start_offset % fill_size); + const u32 backup_bytes = start_offset % fill_size; + std::array backup_data; + if (backup_bytes) + std::memcpy(&backup_data[0], &dst_buffer[coarse_start_offset], backup_bytes); + + for (u32 offset = coarse_start_offset; offset < end_offset; offset += fill_size) { + std::memcpy(&dst_buffer[offset], &fill_data[0], + std::min(fill_size, end_offset - offset)); + } + + if (backup_bytes) + std::memcpy(&dst_buffer[coarse_start_offset], &backup_data[0], backup_bytes); + } else if (!is_tiled) { + ASSERT(type == SurfaceType::Color); + if (pixel_format == PixelFormat::RGBA8 && GLES) { + for (std::size_t i = start_offset; i < flush_end - addr; i += 4) { + dst_buffer[i] = gl_buffer[i + 3]; + dst_buffer[i + 1] = gl_buffer[i + 2]; + dst_buffer[i + 2] = gl_buffer[i + 1]; + dst_buffer[i + 3] = gl_buffer[i]; + } + } else if (pixel_format == PixelFormat::RGB8 && GLES) { + for (std::size_t i = start_offset; i < flush_end - addr; i += 3) { + dst_buffer[i] = gl_buffer[i + 2]; + dst_buffer[i + 1] = gl_buffer[i + 1]; + dst_buffer[i + 2] = gl_buffer[i]; + } + } else { + std::memcpy(dst_buffer + start_offset, &gl_buffer[start_offset], + flush_end - flush_start); + } + } else { + gl_to_morton_fns[static_cast(pixel_format)](stride, height, &gl_buffer[0], + addr, flush_start, flush_end); + } +} + +bool CachedSurface::LoadCustomTexture(u64 tex_hash) { + auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache(); + const auto& image_interface = Core::System::GetInstance().GetImageInterface(); + + if (custom_tex_cache.IsTextureCached(tex_hash)) { + custom_tex_info = custom_tex_cache.LookupTexture(tex_hash); + return true; + } + + if (!custom_tex_cache.CustomTextureExists(tex_hash)) { + return false; + } + + const auto& path_info = custom_tex_cache.LookupTexturePathInfo(tex_hash); + if (!image_interface->DecodePNG(custom_tex_info.tex, custom_tex_info.width, + custom_tex_info.height, path_info.path)) { + LOG_ERROR(Render_OpenGL, "Failed to load custom texture {}", path_info.path); + return false; + } + + const std::bitset<32> width_bits(custom_tex_info.width); + const std::bitset<32> height_bits(custom_tex_info.height); + if (width_bits.count() != 1 || height_bits.count() != 1) { + LOG_ERROR(Render_OpenGL, "Texture {} size is not a power of 2", path_info.path); + return false; + } + + LOG_DEBUG(Render_OpenGL, "Loaded custom texture from {}", path_info.path); + Common::FlipRGBA8Texture(custom_tex_info.tex, custom_tex_info.width, custom_tex_info.height); + custom_tex_cache.CacheTexture(tex_hash, custom_tex_info.tex, custom_tex_info.width, + custom_tex_info.height); + return true; +} + +void CachedSurface::DumpTexture(GLuint target_tex, u64 tex_hash) { + // Make sure the texture size is a power of 2 + // If not, the surface is actually a framebuffer + std::bitset<32> width_bits(width); + std::bitset<32> height_bits(height); + if (width_bits.count() != 1 || height_bits.count() != 1) { + LOG_WARNING(Render_OpenGL, "Not dumping {:016X} because size isn't a power of 2 ({}x{})", + tex_hash, width, height); + return; + } + + // Dump texture to RGBA8 and encode as PNG + const auto& image_interface = Core::System::GetInstance().GetImageInterface(); + auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache(); + std::string dump_path = + fmt::format("{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), + Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id); + if (!FileUtil::CreateFullPath(dump_path)) { + LOG_ERROR(Render, "Unable to create {}", dump_path); + return; + } + + dump_path += fmt::format("tex1_{}x{}_{:016X}_{}.png", width, height, tex_hash, pixel_format); + if (!custom_tex_cache.IsTextureDumped(tex_hash) && !FileUtil::Exists(dump_path)) { + custom_tex_cache.SetTextureDumped(tex_hash); + + LOG_INFO(Render_OpenGL, "Dumping texture to {}", dump_path); + std::vector decoded_texture; + decoded_texture.resize(width * height * 4); + OpenGLState state = OpenGLState::GetCurState(); + GLuint old_texture = state.texture_units[0].texture_2d; + state.Apply(); + /* + GetTexImageOES is used even if not using OpenGL ES to work around a small issue that + happens if using custom textures with texture dumping at the same. + Let's say there's 2 textures that are both 32x32 and one of them gets replaced with a + higher quality 256x256 texture. If the 256x256 texture is displayed first and the + 32x32 texture gets uploaded to the same underlying OpenGL texture, the 32x32 texture + will appear in the corner of the 256x256 texture. If texture dumping is enabled and + the 32x32 is undumped, Citra will attempt to dump it. Since the underlying OpenGL + texture is still 256x256, Citra crashes because it thinks the texture is only 32x32. + GetTexImageOES conveniently only dumps the specified region, and works on both + desktop and ES. + */ + // if the backend isn't OpenGL ES, this won't be initialized yet + if (!owner.texture_downloader_es) { + owner.texture_downloader_es = std::make_unique(false); + } + + owner.texture_downloader_es->GetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, + height, width, &decoded_texture[0]); + state.texture_units[0].texture_2d = old_texture; + state.Apply(); + Common::FlipRGBA8Texture(decoded_texture, width, height); + if (!image_interface->EncodePNG(dump_path, decoded_texture, width, height)) + LOG_ERROR(Render_OpenGL, "Failed to save decoded texture"); + } +} + +MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 192, 64)); +void CachedSurface::UploadGLTexture(Common::Rectangle rect) { + if (type == SurfaceType::Fill) { + return; + } + + MICROPROFILE_SCOPE(OpenGL_TextureUL); + ASSERT(gl_buffer.size() == width * height * GetBytesPerPixel(pixel_format)); + + u64 tex_hash = 0; + + if (Settings::values.dump_textures || Settings::values.custom_textures) { + tex_hash = Common::ComputeHash64(gl_buffer.data(), gl_buffer.size()); + } + + if (Settings::values.custom_textures) { + is_custom = LoadCustomTexture(tex_hash); + } + + // Load data from memory to the surface + GLint x0 = static_cast(rect.left); + GLint y0 = static_cast(rect.bottom); + std::size_t buffer_offset = (y0 * stride + x0) * GetBytesPerPixel(pixel_format); + + const FormatTuple& tuple = GetFormatTuple(pixel_format); + GLuint target_tex = texture.handle; + + // 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) { + x0 = 0; + y0 = 0; + + if (is_custom) { + const auto& tuple = GetFormatTuple(PixelFormat::RGBA8); + unscaled_tex = owner.AllocateSurfaceTexture(tuple, custom_tex_info.width, + custom_tex_info.height); + } else { + unscaled_tex = owner.AllocateSurfaceTexture(tuple, rect.GetWidth(), rect.GetHeight()); + } + + target_tex = unscaled_tex.handle; + } + + OpenGLState cur_state = OpenGLState::GetCurState(); + + GLuint old_tex = cur_state.texture_units[0].texture_2d; + cur_state.texture_units[0].texture_2d = target_tex; + cur_state.Apply(); + + // Ensure no bad interactions with GL_UNPACK_ALIGNMENT + ASSERT(stride * GetBytesPerPixel(pixel_format) % 4 == 0); + if (is_custom) { + if (res_scale == 1) { + texture = owner.AllocateSurfaceTexture(GetFormatTuple(PixelFormat::RGBA8), + custom_tex_info.width, custom_tex_info.height); + cur_state.texture_units[0].texture_2d = texture.handle; + cur_state.Apply(); + } + + // Always going to be using rgba8 + glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast(custom_tex_info.width)); + + 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 { + glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast(stride)); + + glActiveTexture(GL_TEXTURE0); + glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast(rect.GetWidth()), + static_cast(rect.GetHeight()), tuple.format, tuple.type, + &gl_buffer[buffer_offset]); + } + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + if (Settings::values.dump_textures && !is_custom) { + DumpTexture(target_tex, tex_hash); + } + + cur_state.texture_units[0].texture_2d = old_tex; + cur_state.Apply(); + + if (res_scale != 1) { + auto scaled_rect = rect; + scaled_rect.left *= res_scale; + scaled_rect.top *= res_scale; + scaled_rect.right *= res_scale; + scaled_rect.bottom *= res_scale; + + const u32 width = is_custom ? custom_tex_info.width : rect.GetWidth(); + const u32 height = is_custom ? custom_tex_info.height : rect.GetHeight(); + const Common::Rectangle from_rect{0, height, width, 0}; + + if (!owner.texture_filterer->Filter(unscaled_tex, from_rect, texture, scaled_rect, type)) { + const Aspect aspect = ToAspect(type); + runtime.BlitTextures(unscaled_tex, {aspect, from_rect}, + texture, {aspect, scaled_rect}); + } + } + + InvalidateAllWatcher(); +} + +MICROPROFILE_DEFINE(OpenGL_TextureDL, "OpenGL", "Texture Download", MP_RGB(128, 192, 64)); +void CachedSurface::DownloadGLTexture(const Common::Rectangle& rect) { + if (type == SurfaceType::Fill) { + return; + } + + MICROPROFILE_SCOPE(OpenGL_TextureDL); + + if (gl_buffer.empty()) { + gl_buffer.resize(width * height * GetBytesPerPixel(pixel_format)); + } + + OpenGLState state = OpenGLState::GetCurState(); + OpenGLState prev_state = state; + SCOPE_EXIT({ prev_state.Apply(); }); + + const FormatTuple& tuple = GetFormatTuple(pixel_format); + + // Ensure no bad interactions with GL_PACK_ALIGNMENT + ASSERT(stride * GetBytesPerPixel(pixel_format) % 4 == 0); + glPixelStorei(GL_PACK_ROW_LENGTH, static_cast(stride)); + const std::size_t buffer_offset = (rect.bottom * stride + rect.left) * GetBytesPerPixel(pixel_format); + + // If not 1x scale, blit scaled texture to a new 1x texture and use that to flush + const Aspect aspect = ToAspect(type); + if (res_scale != 1) { + auto scaled_rect = rect; + scaled_rect.left *= res_scale; + scaled_rect.top *= res_scale; + scaled_rect.right *= res_scale; + scaled_rect.bottom *= res_scale; + + const Common::Rectangle unscaled_tex_rect{0, rect.GetHeight(), rect.GetWidth(), 0}; + auto unscaled_tex = owner.AllocateSurfaceTexture(tuple, rect.GetWidth(), + rect.GetHeight()); + // Blit scaled texture to the unscaled one + runtime.BlitTextures(texture, {aspect, scaled_rect}, + unscaled_tex, {aspect, unscaled_tex_rect}); + + state.texture_units[0].texture_2d = unscaled_tex.handle; + state.Apply(); + + glActiveTexture(GL_TEXTURE0); + if (GLES) { + owner.texture_downloader_es->GetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, + rect.GetHeight(), rect.GetWidth(), + &gl_buffer[buffer_offset]); + } else { + glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]); + } + } else { + runtime.ReadTexture(texture, {aspect, rect}, tuple, gl_buffer.data()); + } + + glPixelStorei(GL_PACK_ROW_LENGTH, 0); +} + +bool CachedSurface::CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const { + if (type == SurfaceType::Fill && IsRegionValid(fill_interval) && + boost::icl::first(fill_interval) >= addr && + boost::icl::last_next(fill_interval) <= end && // dest_surface is within our fill range + dest_surface.FromInterval(fill_interval).GetInterval() == + fill_interval) { // make sure interval is a rectangle in dest surface + if (fill_size * 8 != dest_surface.GetFormatBpp()) { + // Check if bits repeat for our fill_size + const u32 dest_bytes_per_pixel = std::max(dest_surface.GetFormatBpp() / 8, 1u); + std::vector fill_test(fill_size * dest_bytes_per_pixel); + + for (u32 i = 0; i < dest_bytes_per_pixel; ++i) + std::memcpy(&fill_test[i * fill_size], &fill_data[0], fill_size); + + for (u32 i = 0; i < fill_size; ++i) + if (std::memcmp(&fill_test[dest_bytes_per_pixel * i], &fill_test[0], + dest_bytes_per_pixel) != 0) + return false; + + if (dest_surface.GetFormatBpp() == 4 && (fill_test[0] & 0xF) != (fill_test[0] >> 4)) + return false; + } + return true; + } + return false; +} + +bool CachedSurface::CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const { + SurfaceParams subrect_params = dest_surface.FromInterval(copy_interval); + ASSERT(subrect_params.GetInterval() == copy_interval); + if (CanSubRect(subrect_params)) + return true; + + if (CanFill(dest_surface, copy_interval)) + return true; + + return false; +} + +} // namespace OpenGL diff --git a/src/video_core/rasterizer_cache/cached_surface.h b/src/video_core/rasterizer_cache/cached_surface.h new file mode 100644 index 000000000..cd1bcaf1a --- /dev/null +++ b/src/video_core/rasterizer_cache/cached_surface.h @@ -0,0 +1,136 @@ +// Copyright 2022 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include "common/assert.h" +#include "core/custom_tex_cache.h" +#include "video_core/rasterizer_cache/surface_params.h" +#include "video_core/rasterizer_cache/texture_runtime.h" + +namespace OpenGL { + +/** + * A watcher that notifies whether a cached surface has been changed. This is useful for caching + * surface collection objects, including texture cube and mipmap. + */ +class SurfaceWatcher { + friend class CachedSurface; +public: + explicit SurfaceWatcher(std::weak_ptr&& surface) : + surface(std::move(surface)) {} + + /// Checks whether the surface has been changed. + bool IsValid() const { + return !surface.expired() && valid; + } + + /// Marks that the content of the referencing surface has been updated to the watcher user. + void Validate() { + ASSERT(!surface.expired()); + valid = true; + } + + /// Gets the referencing surface. Returns null if the surface has been destroyed + Surface Get() const { + return surface.lock(); + } + +private: + std::weak_ptr surface; + bool valid = false; +}; + +class RasterizerCacheOpenGL; + +class CachedSurface : public SurfaceParams, public std::enable_shared_from_this { +public: + CachedSurface(RasterizerCacheOpenGL& owner, TextureRuntime& runtime) : + owner(owner), runtime(runtime) {} + ~CachedSurface(); + + /// 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); + void DumpTexture(GLuint target_tex, u64 tex_hash); + + /// Upload/Download data in gl_buffer in/to this surface's texture + void UploadGLTexture(Common::Rectangle rect); + void DownloadGLTexture(const Common::Rectangle& rect); + + bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const; + bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const; + + bool IsRegionValid(SurfaceInterval interval) const { + return (invalid_regions.find(interval) == invalid_regions.end()); + } + + bool IsSurfaceFullyInvalid() const { + auto interval = GetInterval(); + return *invalid_regions.equal_range(interval).first == interval; + } + + std::shared_ptr CreateWatcher() { + auto watcher = std::make_shared(weak_from_this()); + watchers.push_front(watcher); + return watcher; + } + + void InvalidateAllWatcher() { + for (const auto& watcher : watchers) { + if (auto locked = watcher.lock()) { + locked->valid = false; + } + } + } + + void UnlinkAllWatcher() { + for (const auto& watcher : watchers) { + if (auto locked = watcher.lock()) { + locked->valid = false; + locked->surface.reset(); + } + } + + watchers.clear(); + } + +public: + bool registered = false; + SurfaceRegions invalid_regions; + std::vector gl_buffer; + + // Number of bytes to read from fill_data + u32 fill_size = 0; + std::array fill_data; + OGLTexture texture; + + // level_watchers[i] watches the (i+1)-th level mipmap source surface + std::array, 7> level_watchers; + u32 max_level = 0; + + // Information about custom textures + bool is_custom = false; + Core::CustomTexInfo custom_tex_info; + +private: + RasterizerCacheOpenGL& owner; + TextureRuntime& runtime; + std::list> watchers; +}; + +struct CachedTextureCube { + OGLTexture texture; + u16 res_scale = 1; + std::shared_ptr px; + std::shared_ptr nx; + std::shared_ptr py; + std::shared_ptr ny; + std::shared_ptr pz; + std::shared_ptr nz; +}; + +} // namespace OpenGL diff --git a/src/video_core/rasterizer_cache/pixel_format.h b/src/video_core/rasterizer_cache/pixel_format.h index a7a5b6fe6..951ad0cb7 100644 --- a/src/video_core/rasterizer_cache/pixel_format.h +++ b/src/video_core/rasterizer_cache/pixel_format.h @@ -10,6 +10,8 @@ namespace OpenGL { +constexpr u32 PIXEL_FORMAT_COUNT = 18; + enum class PixelFormat : u8 { // First 5 formats are shared between textures and color buffers RGBA8 = 0, @@ -43,7 +45,7 @@ enum class SurfaceType { Invalid = 5 }; -static constexpr std::string_view PixelFormatAsString(PixelFormat format) { +inline constexpr std::string_view PixelFormatAsString(PixelFormat format) { switch (format) { case PixelFormat::RGBA8: return "RGBA8"; @@ -84,23 +86,23 @@ static constexpr std::string_view PixelFormatAsString(PixelFormat format) { } } -static constexpr PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) { +inline constexpr PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) { const u32 format_index = static_cast(format); return (format_index < 14) ? static_cast(format) : PixelFormat::Invalid; } -static constexpr PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format) { +inline constexpr PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format) { const u32 format_index = static_cast(format); return (format_index < 5) ? static_cast(format) : PixelFormat::Invalid; } -static PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format) { +inline PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format) { const u32 format_index = static_cast(format); return (format_index < 4) ? static_cast(format_index + 14) : PixelFormat::Invalid; } -static constexpr PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) { +inline constexpr PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) { switch (format) { // RGB565 and RGB5A1 are switched in PixelFormat compared to ColorFormat case GPU::Regs::PixelFormat::RGB565: @@ -133,7 +135,7 @@ static constexpr SurfaceType GetFormatType(PixelFormat pixel_format) { return SurfaceType::Invalid; } -static constexpr bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format) { +inline constexpr bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format) { SurfaceType source_type = GetFormatType(source_format); SurfaceType dest_type = GetFormatType(dest_format); @@ -182,7 +184,7 @@ static constexpr u32 GetFormatBpp(PixelFormat format) { } } -static constexpr u32 GetBytesPerPixel(PixelFormat format) { +inline constexpr u32 GetBytesPerPixel(PixelFormat format) { // OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type if (format == PixelFormat::D24 || GetFormatType(format) == SurfaceType::Texture) { return 4; diff --git a/src/video_core/rasterizer_cache/rasterizer_cache.cpp b/src/video_core/rasterizer_cache/rasterizer_cache.cpp index a8a0eec86..88d2b5ab9 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache.cpp +++ b/src/video_core/rasterizer_cache/rasterizer_cache.cpp @@ -4,22 +4,74 @@ #include #include +#include "common/alignment.h" #include "common/logging/log.h" #include "common/microprofile.h" #include "common/scope_exit.h" -#include "common/texture.h" -#include "core/core.h" -#include "core/hle/kernel/process.h" #include "video_core/pica_state.h" -#include "video_core/rasterizer_cache/morton_swizzle.h" #include "video_core/rasterizer_cache/rasterizer_cache.h" +#include "video_core/renderer_opengl/texture_filters/texture_filterer.h" +#include "video_core/renderer_opengl/texture_downloader_es.h" #include "video_core/renderer_opengl/gl_format_reinterpreter.h" #include "video_core/renderer_opengl/gl_state.h" -#include "video_core/renderer_opengl/texture_downloader_es.h" -#include "video_core/renderer_opengl/texture_filters/texture_filterer.h" +#include "video_core/renderer_opengl/gl_vars.h" namespace OpenGL { +// TODO: Deduplicate this +static Aspect ToAspect(SurfaceType type) { + switch (type) { + case SurfaceType::Color: + case SurfaceType::Texture: + case SurfaceType::Fill: + return Aspect::Color; + case SurfaceType::Depth: + return Aspect::Depth; + case SurfaceType::DepthStencil: + return Aspect::DepthStencil; + default: + LOG_CRITICAL(Render_OpenGL, "Unknown SurfaceType {}", type); + UNREACHABLE(); + } + + return Aspect::Color; +} + +static ClearValue ToClearValue(Aspect aspect, PixelFormat format, const u8* fill_data) { + ClearValue result{}; + switch (aspect) { + case Aspect::Color: { + Pica::Texture::TextureInfo tex_info{}; + tex_info.format = static_cast(format); + + Common::Vec4 color = Pica::Texture::LookupTexture(fill_data, 0, 0, tex_info); + result.color = color / 255.f; + break; + } + case Aspect::Depth: { + u32 depth_uint = 0; + if (format == PixelFormat::D16) { + std::memcpy(&depth_uint, fill_data, 2); + result.depth = depth_uint / 65535.0f; // 2^16 - 1 + } else if (format == PixelFormat::D24) { + std::memcpy(&depth_uint, fill_data, 3); + result.depth = depth_uint / 16777215.0f; // 2^24 - 1 + } + break; + } + case Aspect::DepthStencil: { + u32 clear_value_uint; + std::memcpy(&clear_value_uint, fill_data, sizeof(u32)); + + result.depth = (clear_value_uint & 0xFFFFFF) / 16777215.0f; // 2^24 - 1 + result.stencil = (clear_value_uint >> 24); + break; + } + } + + return result; +} + template static constexpr auto RangeFromInterval(Map& map, const Interval& interval) { return boost::make_iterator_range(map.equal_range(interval)); @@ -96,192 +148,6 @@ static void AllocateTextureCube(GLuint texture, const FormatTuple& format_tuple, cur_state.Apply(); } -static bool BlitTextures(GLuint src_tex, const Common::Rectangle& src_rect, GLuint dst_tex, - const Common::Rectangle& dst_rect, SurfaceType type, - GLuint read_fb_handle, GLuint draw_fb_handle) { - OpenGLState prev_state = OpenGLState::GetCurState(); - SCOPE_EXIT({ prev_state.Apply(); }); - - OpenGLState state; - state.draw.read_framebuffer = read_fb_handle; - state.draw.draw_framebuffer = draw_fb_handle; - state.Apply(); - - u32 buffers = 0; - - if (type == SurfaceType::Color || type == SurfaceType::Texture) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, src_tex, - 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, - 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); - - buffers = GL_COLOR_BUFFER_BIT; - } else if (type == SurfaceType::Depth) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, src_tex, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dst_tex, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - - buffers = GL_DEPTH_BUFFER_BIT; - } else if (type == SurfaceType::DepthStencil) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - src_tex, 0); - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - dst_tex, 0); - - buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; - } - - // TODO (wwylele): use GL_NEAREST for shadow map texture - // Note: shadow map is treated as RGBA8 format in PICA, as well as in the rasterizer cache, but - // doing linear intepolation componentwise would cause incorrect value. However, for a - // well-programmed game this code path should be rarely executed for shadow map with - // inconsistent scale. - glBlitFramebuffer(src_rect.left, src_rect.bottom, src_rect.right, src_rect.top, dst_rect.left, - dst_rect.bottom, dst_rect.right, dst_rect.top, buffers, - buffers == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST); - - return true; -} - -static bool FillSurface(const Surface& surface, const u8* fill_data, - const Common::Rectangle& fill_rect, GLuint draw_fb_handle) { - OpenGLState prev_state = OpenGLState::GetCurState(); - SCOPE_EXIT({ prev_state.Apply(); }); - - OpenGLState state; - state.scissor.enabled = true; - state.scissor.x = static_cast(fill_rect.left); - state.scissor.y = static_cast(fill_rect.bottom); - state.scissor.width = static_cast(fill_rect.GetWidth()); - state.scissor.height = static_cast(fill_rect.GetHeight()); - - state.draw.draw_framebuffer = draw_fb_handle; - state.Apply(); - - surface->InvalidateAllWatcher(); - - if (surface->type == SurfaceType::Color || surface->type == SurfaceType::Texture) { - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - surface->texture.handle, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); - - Pica::Texture::TextureInfo tex_info{}; - tex_info.format = static_cast(surface->pixel_format); - Common::Vec4 color = Pica::Texture::LookupTexture(fill_data, 0, 0, tex_info); - - std::array color_values = {color.x / 255.f, color.y / 255.f, color.z / 255.f, - color.w / 255.f}; - - state.color_mask.red_enabled = GL_TRUE; - state.color_mask.green_enabled = GL_TRUE; - state.color_mask.blue_enabled = GL_TRUE; - state.color_mask.alpha_enabled = GL_TRUE; - state.Apply(); - glClearBufferfv(GL_COLOR, 0, &color_values[0]); - } else if (surface->type == SurfaceType::Depth) { - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - surface->texture.handle, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - - u32 value_32bit = 0; - GLfloat value_float; - - if (surface->pixel_format == PixelFormat::D16) { - std::memcpy(&value_32bit, fill_data, 2); - value_float = value_32bit / 65535.0f; // 2^16 - 1 - } else if (surface->pixel_format == PixelFormat::D24) { - std::memcpy(&value_32bit, fill_data, 3); - value_float = value_32bit / 16777215.0f; // 2^24 - 1 - } - - state.depth.write_mask = GL_TRUE; - state.Apply(); - glClearBufferfv(GL_DEPTH, 0, &value_float); - } else if (surface->type == SurfaceType::DepthStencil) { - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - surface->texture.handle, 0); - - u32 value_32bit; - std::memcpy(&value_32bit, fill_data, sizeof(u32)); - - GLfloat value_float = (value_32bit & 0xFFFFFF) / 16777215.0f; // 2^24 - 1 - GLint value_int = (value_32bit >> 24); - - state.depth.write_mask = GL_TRUE; - state.stencil.write_mask = -1; - state.Apply(); - glClearBufferfi(GL_DEPTH_STENCIL, 0, value_float, value_int); - } - return true; -} - -CachedSurface::~CachedSurface() { - if (texture.handle) { - auto tag = is_custom ? HostTextureTag{GetFormatTuple(PixelFormat::RGBA8), - custom_tex_info.width, custom_tex_info.height} - : HostTextureTag{GetFormatTuple(pixel_format), GetScaledWidth(), - GetScaledHeight()}; - - owner.host_texture_recycler.emplace(tag, std::move(texture)); - } -} - -bool CachedSurface::CanFill(const SurfaceParams& dest_surface, - SurfaceInterval fill_interval) const { - if (type == SurfaceType::Fill && IsRegionValid(fill_interval) && - boost::icl::first(fill_interval) >= addr && - boost::icl::last_next(fill_interval) <= end && // dest_surface is within our fill range - dest_surface.FromInterval(fill_interval).GetInterval() == - fill_interval) { // make sure interval is a rectangle in dest surface - if (fill_size * 8 != dest_surface.GetFormatBpp()) { - // Check if bits repeat for our fill_size - const u32 dest_bytes_per_pixel = std::max(dest_surface.GetFormatBpp() / 8, 1u); - std::vector fill_test(fill_size * dest_bytes_per_pixel); - - for (u32 i = 0; i < dest_bytes_per_pixel; ++i) - std::memcpy(&fill_test[i * fill_size], &fill_data[0], fill_size); - - for (u32 i = 0; i < fill_size; ++i) - if (std::memcmp(&fill_test[dest_bytes_per_pixel * i], &fill_test[0], - dest_bytes_per_pixel) != 0) - return false; - - if (dest_surface.GetFormatBpp() == 4 && (fill_test[0] & 0xF) != (fill_test[0] >> 4)) - return false; - } - return true; - } - return false; -} - -bool CachedSurface::CanCopy(const SurfaceParams& dest_surface, - SurfaceInterval copy_interval) const { - SurfaceParams subrect_params = dest_surface.FromInterval(copy_interval); - ASSERT(subrect_params.GetInterval() == copy_interval); - if (CanSubRect(subrect_params)) - return true; - - if (CanFill(dest_surface, copy_interval)) - return true; - - return false; -} - MICROPROFILE_DEFINE(OpenGL_CopySurface, "OpenGL", "CopySurface", MP_RGB(128, 192, 64)); void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surface& dst_surface, SurfaceInterval copy_interval) { @@ -289,10 +155,10 @@ void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surfac SurfaceParams subrect_params = dst_surface->FromInterval(copy_interval); ASSERT(subrect_params.GetInterval() == copy_interval); - ASSERT(src_surface != dst_surface); // This is only called when CanCopy is true, no need to run checks here + const Aspect aspect = ToAspect(dst_surface->type); if (src_surface->type == SurfaceType::Fill) { // FillSurface needs a 4 bytes buffer const u32 fill_offset = @@ -300,443 +166,30 @@ void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surfac std::array fill_buffer; u32 fill_buff_pos = fill_offset; - for (int i : {0, 1, 2, 3}) + for (std::size_t i = 0; i < fill_buffer.size(); i++) { fill_buffer[i] = src_surface->fill_data[fill_buff_pos++ % src_surface->fill_size]; + } - FillSurface(dst_surface, &fill_buffer[0], dst_surface->GetScaledSubRect(subrect_params), - draw_framebuffer.handle); + const auto clear_rect = dst_surface->GetScaledSubRect(subrect_params); + const ClearValue clear_value = ToClearValue(aspect, dst_surface->pixel_format, + fill_buffer.data()); + + runtime.ClearTexture(dst_surface->texture, {aspect, clear_rect}, clear_value); return; } + if (src_surface->CanSubRect(subrect_params)) { - BlitTextures(src_surface->texture.handle, src_surface->GetScaledSubRect(subrect_params), - dst_surface->texture.handle, dst_surface->GetScaledSubRect(subrect_params), - src_surface->type, read_framebuffer.handle, draw_framebuffer.handle); + const auto src_rect = src_surface->GetScaledSubRect(subrect_params); + const auto dst_rect = dst_surface->GetScaledSubRect(subrect_params); + + runtime.BlitTextures(src_surface->texture, {aspect, src_rect}, + dst_surface->texture, {aspect, dst_rect}); return; } + UNREACHABLE(); } -MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 192, 64)); -void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) { - ASSERT(type != SurfaceType::Fill); - const bool need_swap = - GLES && (pixel_format == PixelFormat::RGBA8 || pixel_format == PixelFormat::RGB8); - - const u8* const texture_src_data = VideoCore::g_memory->GetPhysicalPointer(addr); - if (texture_src_data == nullptr) - return; - - if (gl_buffer.empty()) { - gl_buffer.resize(width * height * GetBytesPerPixel(pixel_format)); - } - - // TODO: Should probably be done in ::Memory:: and check for other regions too - if (load_start < Memory::VRAM_VADDR_END && load_end > Memory::VRAM_VADDR_END) - load_end = Memory::VRAM_VADDR_END; - - if (load_start < Memory::VRAM_VADDR && load_end > Memory::VRAM_VADDR) - load_start = Memory::VRAM_VADDR; - - MICROPROFILE_SCOPE(OpenGL_SurfaceLoad); - - ASSERT(load_start >= addr && load_end <= end); - const u32 start_offset = load_start - addr; - - if (!is_tiled) { - ASSERT(type == SurfaceType::Color); - if (need_swap) { - // TODO(liushuyu): check if the byteswap here is 100% correct - // cannot fully test this - if (pixel_format == PixelFormat::RGBA8) { - for (std::size_t i = start_offset; i < load_end - addr; i += 4) { - gl_buffer[i] = texture_src_data[i + 3]; - gl_buffer[i + 1] = texture_src_data[i + 2]; - gl_buffer[i + 2] = texture_src_data[i + 1]; - gl_buffer[i + 3] = texture_src_data[i]; - } - } else if (pixel_format == PixelFormat::RGB8) { - for (std::size_t i = start_offset; i < load_end - addr; i += 3) { - gl_buffer[i] = texture_src_data[i + 2]; - gl_buffer[i + 1] = texture_src_data[i + 1]; - gl_buffer[i + 2] = texture_src_data[i]; - } - } - } else { - std::memcpy(&gl_buffer[start_offset], texture_src_data + start_offset, - load_end - load_start); - } - } else { - if (type == SurfaceType::Texture) { - Pica::Texture::TextureInfo tex_info{}; - tex_info.width = width; - tex_info.height = height; - tex_info.format = static_cast(pixel_format); - tex_info.SetDefaultStride(); - tex_info.physical_address = addr; - - const SurfaceInterval load_interval(load_start, load_end); - const auto rect = GetSubRect(FromInterval(load_interval)); - ASSERT(FromInterval(load_interval).GetInterval() == load_interval); - - for (unsigned y = rect.bottom; y < rect.top; ++y) { - for (unsigned x = rect.left; x < rect.right; ++x) { - auto vec4 = - Pica::Texture::LookupTexture(texture_src_data, x, height - 1 - y, tex_info); - const std::size_t offset = (x + (width * y)) * 4; - std::memcpy(&gl_buffer[offset], vec4.AsArray(), 4); - } - } - } else { - morton_to_gl_fns[static_cast(pixel_format)](stride, height, &gl_buffer[0], - addr, load_start, load_end); - } - } -} - -MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64)); -void CachedSurface::FlushGLBuffer(PAddr flush_start, PAddr flush_end) { - u8* const dst_buffer = VideoCore::g_memory->GetPhysicalPointer(addr); - if (dst_buffer == nullptr) - return; - - ASSERT(gl_buffer.size() == width * height * GetBytesPerPixel(pixel_format)); - - // TODO: Should probably be done in ::Memory:: and check for other regions too - // same as loadglbuffer() - if (flush_start < Memory::VRAM_VADDR_END && flush_end > Memory::VRAM_VADDR_END) - flush_end = Memory::VRAM_VADDR_END; - - if (flush_start < Memory::VRAM_VADDR && flush_end > Memory::VRAM_VADDR) - flush_start = Memory::VRAM_VADDR; - - MICROPROFILE_SCOPE(OpenGL_SurfaceFlush); - - ASSERT(flush_start >= addr && flush_end <= end); - const u32 start_offset = flush_start - addr; - const u32 end_offset = flush_end - addr; - - if (type == SurfaceType::Fill) { - const u32 coarse_start_offset = start_offset - (start_offset % fill_size); - const u32 backup_bytes = start_offset % fill_size; - std::array backup_data; - if (backup_bytes) - std::memcpy(&backup_data[0], &dst_buffer[coarse_start_offset], backup_bytes); - - for (u32 offset = coarse_start_offset; offset < end_offset; offset += fill_size) { - std::memcpy(&dst_buffer[offset], &fill_data[0], - std::min(fill_size, end_offset - offset)); - } - - if (backup_bytes) - std::memcpy(&dst_buffer[coarse_start_offset], &backup_data[0], backup_bytes); - } else if (!is_tiled) { - ASSERT(type == SurfaceType::Color); - if (pixel_format == PixelFormat::RGBA8 && GLES) { - for (std::size_t i = start_offset; i < flush_end - addr; i += 4) { - dst_buffer[i] = gl_buffer[i + 3]; - dst_buffer[i + 1] = gl_buffer[i + 2]; - dst_buffer[i + 2] = gl_buffer[i + 1]; - dst_buffer[i + 3] = gl_buffer[i]; - } - } else if (pixel_format == PixelFormat::RGB8 && GLES) { - for (std::size_t i = start_offset; i < flush_end - addr; i += 3) { - dst_buffer[i] = gl_buffer[i + 2]; - dst_buffer[i + 1] = gl_buffer[i + 1]; - dst_buffer[i + 2] = gl_buffer[i]; - } - } else { - std::memcpy(dst_buffer + start_offset, &gl_buffer[start_offset], - flush_end - flush_start); - } - } else { - gl_to_morton_fns[static_cast(pixel_format)](stride, height, &gl_buffer[0], - addr, flush_start, flush_end); - } -} - -bool CachedSurface::LoadCustomTexture(u64 tex_hash) { - auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache(); - const auto& image_interface = Core::System::GetInstance().GetImageInterface(); - - if (custom_tex_cache.IsTextureCached(tex_hash)) { - custom_tex_info = custom_tex_cache.LookupTexture(tex_hash); - return true; - } - - if (!custom_tex_cache.CustomTextureExists(tex_hash)) { - return false; - } - - const auto& path_info = custom_tex_cache.LookupTexturePathInfo(tex_hash); - if (!image_interface->DecodePNG(custom_tex_info.tex, custom_tex_info.width, - custom_tex_info.height, path_info.path)) { - LOG_ERROR(Render_OpenGL, "Failed to load custom texture {}", path_info.path); - return false; - } - - const std::bitset<32> width_bits(custom_tex_info.width); - const std::bitset<32> height_bits(custom_tex_info.height); - if (width_bits.count() != 1 || height_bits.count() != 1) { - LOG_ERROR(Render_OpenGL, "Texture {} size is not a power of 2", path_info.path); - return false; - } - - LOG_DEBUG(Render_OpenGL, "Loaded custom texture from {}", path_info.path); - Common::FlipRGBA8Texture(custom_tex_info.tex, custom_tex_info.width, custom_tex_info.height); - custom_tex_cache.CacheTexture(tex_hash, custom_tex_info.tex, custom_tex_info.width, - custom_tex_info.height); - return true; -} - -void CachedSurface::DumpTexture(GLuint target_tex, u64 tex_hash) { - // Make sure the texture size is a power of 2 - // If not, the surface is actually a framebuffer - std::bitset<32> width_bits(width); - std::bitset<32> height_bits(height); - if (width_bits.count() != 1 || height_bits.count() != 1) { - LOG_WARNING(Render_OpenGL, "Not dumping {:016X} because size isn't a power of 2 ({}x{})", - tex_hash, width, height); - return; - } - - // Dump texture to RGBA8 and encode as PNG - const auto& image_interface = Core::System::GetInstance().GetImageInterface(); - auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache(); - std::string dump_path = - fmt::format("{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), - Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id); - if (!FileUtil::CreateFullPath(dump_path)) { - LOG_ERROR(Render, "Unable to create {}", dump_path); - return; - } - - dump_path += fmt::format("tex1_{}x{}_{:016X}_{}.png", width, height, tex_hash, pixel_format); - if (!custom_tex_cache.IsTextureDumped(tex_hash) && !FileUtil::Exists(dump_path)) { - custom_tex_cache.SetTextureDumped(tex_hash); - - LOG_INFO(Render_OpenGL, "Dumping texture to {}", dump_path); - std::vector decoded_texture; - decoded_texture.resize(width * height * 4); - OpenGLState state = OpenGLState::GetCurState(); - GLuint old_texture = state.texture_units[0].texture_2d; - state.Apply(); - /* - GetTexImageOES is used even if not using OpenGL ES to work around a small issue that - happens if using custom textures with texture dumping at the same. - Let's say there's 2 textures that are both 32x32 and one of them gets replaced with a - higher quality 256x256 texture. If the 256x256 texture is displayed first and the - 32x32 texture gets uploaded to the same underlying OpenGL texture, the 32x32 texture - will appear in the corner of the 256x256 texture. If texture dumping is enabled and - the 32x32 is undumped, Citra will attempt to dump it. Since the underlying OpenGL - texture is still 256x256, Citra crashes because it thinks the texture is only 32x32. - GetTexImageOES conveniently only dumps the specified region, and works on both - desktop and ES. - */ - // if the backend isn't OpenGL ES, this won't be initialized yet - if (!owner.texture_downloader_es) - owner.texture_downloader_es = std::make_unique(false); - owner.texture_downloader_es->GetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, - height, width, &decoded_texture[0]); - state.texture_units[0].texture_2d = old_texture; - state.Apply(); - Common::FlipRGBA8Texture(decoded_texture, width, height); - if (!image_interface->EncodePNG(dump_path, decoded_texture, width, height)) - LOG_ERROR(Render_OpenGL, "Failed to save decoded texture"); - } -} - -MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 192, 64)); -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 * GetBytesPerPixel(pixel_format)); - - u64 tex_hash = 0; - - if (Settings::values.dump_textures || Settings::values.custom_textures) { - tex_hash = Common::ComputeHash64(gl_buffer.data(), gl_buffer.size()); - } - - if (Settings::values.custom_textures) { - is_custom = LoadCustomTexture(tex_hash); - } - - // Load data from memory to the surface - GLint x0 = static_cast(rect.left); - GLint y0 = static_cast(rect.bottom); - std::size_t buffer_offset = (y0 * stride + x0) * GetBytesPerPixel(pixel_format); - - const FormatTuple& tuple = GetFormatTuple(pixel_format); - GLuint target_tex = texture.handle; - - // 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) { - x0 = 0; - y0 = 0; - - if (is_custom) { - unscaled_tex = owner.AllocateSurfaceTexture( - GetFormatTuple(PixelFormat::RGBA8), custom_tex_info.width, custom_tex_info.height); - } else { - unscaled_tex = owner.AllocateSurfaceTexture(tuple, rect.GetWidth(), rect.GetHeight()); - } - target_tex = unscaled_tex.handle; - } - - OpenGLState cur_state = OpenGLState::GetCurState(); - - GLuint old_tex = cur_state.texture_units[0].texture_2d; - cur_state.texture_units[0].texture_2d = target_tex; - cur_state.Apply(); - - // Ensure no bad interactions with GL_UNPACK_ALIGNMENT - ASSERT(stride * GetBytesPerPixel(pixel_format) % 4 == 0); - if (is_custom) { - if (res_scale == 1) { - texture = owner.AllocateSurfaceTexture(GetFormatTuple(PixelFormat::RGBA8), - custom_tex_info.width, custom_tex_info.height); - cur_state.texture_units[0].texture_2d = texture.handle; - cur_state.Apply(); - } - // always going to be using rgba8 - glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast(custom_tex_info.width)); - - 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 { - glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast(stride)); - - glActiveTexture(GL_TEXTURE0); - glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast(rect.GetWidth()), - static_cast(rect.GetHeight()), tuple.format, tuple.type, - &gl_buffer[buffer_offset]); - } - - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - if (Settings::values.dump_textures && !is_custom) - DumpTexture(target_tex, tex_hash); - - cur_state.texture_units[0].texture_2d = old_tex; - cur_state.Apply(); - - if (res_scale != 1) { - auto scaled_rect = rect; - scaled_rect.left *= res_scale; - scaled_rect.top *= res_scale; - scaled_rect.right *= res_scale; - scaled_rect.bottom *= res_scale; - 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}; - if (!owner.texture_filterer->Filter(unscaled_tex.handle, from_rect, texture.handle, - scaled_rect, type, read_fb_handle, draw_fb_handle)) { - BlitTextures(unscaled_tex.handle, from_rect, texture.handle, scaled_rect, type, - read_fb_handle, draw_fb_handle); - } - } - - InvalidateAllWatcher(); -} - -MICROPROFILE_DEFINE(OpenGL_TextureDL, "OpenGL", "Texture Download", MP_RGB(128, 192, 64)); -void CachedSurface::DownloadGLTexture(const Common::Rectangle& rect, GLuint read_fb_handle, - GLuint draw_fb_handle) { - if (type == SurfaceType::Fill) { - return; - } - - MICROPROFILE_SCOPE(OpenGL_TextureDL); - - if (gl_buffer.empty()) { - gl_buffer.resize(width * height * GetBytesPerPixel(pixel_format)); - } - - OpenGLState state = OpenGLState::GetCurState(); - OpenGLState prev_state = state; - SCOPE_EXIT({ prev_state.Apply(); }); - - const FormatTuple& tuple = GetFormatTuple(pixel_format); - - // Ensure no bad interactions with GL_PACK_ALIGNMENT - ASSERT(stride * GetBytesPerPixel(pixel_format) % 4 == 0); - glPixelStorei(GL_PACK_ROW_LENGTH, static_cast(stride)); - std::size_t buffer_offset = - (rect.bottom * stride + rect.left) * GetBytesPerPixel(pixel_format); - - // If not 1x scale, blit scaled texture to a new 1x texture and use that to flush - if (res_scale != 1) { - auto scaled_rect = rect; - scaled_rect.left *= res_scale; - scaled_rect.top *= res_scale; - scaled_rect.right *= res_scale; - scaled_rect.bottom *= res_scale; - - Common::Rectangle unscaled_tex_rect{0, rect.GetHeight(), rect.GetWidth(), 0}; - OGLTexture unscaled_tex = - owner.AllocateSurfaceTexture(tuple, rect.GetWidth(), rect.GetHeight()); - BlitTextures(texture.handle, scaled_rect, unscaled_tex.handle, unscaled_tex_rect, type, - read_fb_handle, draw_fb_handle); - - state.texture_units[0].texture_2d = unscaled_tex.handle; - state.Apply(); - - glActiveTexture(GL_TEXTURE0); - if (GLES) { - owner.texture_downloader_es->GetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, - rect.GetHeight(), rect.GetWidth(), - &gl_buffer[buffer_offset]); - } else { - glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]); - } - } else { - state.ResetTexture(texture.handle); - state.draw.read_framebuffer = read_fb_handle; - state.Apply(); - - if (type == SurfaceType::Color || type == SurfaceType::Texture) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - texture.handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - } else if (type == SurfaceType::Depth) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - texture.handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - } else { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - texture.handle, 0); - } - switch (glCheckFramebufferStatus(GL_FRAMEBUFFER)) { - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - LOG_WARNING(Render_OpenGL, "Framebuffer incomplete attachment"); - break; - case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: - LOG_WARNING(Render_OpenGL, "Framebuffer incomplete dimensions"); - break; - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - LOG_WARNING(Render_OpenGL, "Framebuffer incomplete missing attachment"); - break; - case GL_FRAMEBUFFER_UNSUPPORTED: - LOG_WARNING(Render_OpenGL, "Framebuffer unsupported"); - break; - } - glReadPixels(static_cast(rect.left), static_cast(rect.bottom), - static_cast(rect.GetWidth()), static_cast(rect.GetHeight()), - tuple.format, tuple.type, &gl_buffer[buffer_offset]); - } - - glPixelStorei(GL_PACK_ROW_LENGTH, 0); -} - enum MatchFlags { Invalid = 1, // Flag that can be applied to other match types, invalid matches require // validation before they can be used @@ -845,11 +298,9 @@ RasterizerCacheOpenGL::RasterizerCacheOpenGL() { texture_filterer = std::make_unique(Settings::values.texture_filter_name, resolution_scale_factor); format_reinterpreter = std::make_unique(); - if (GLES) + if (GLES) { texture_downloader_es = std::make_unique(false); - - read_framebuffer.Create(); - draw_framebuffer.Create(); + } } RasterizerCacheOpenGL::~RasterizerCacheOpenGL() { @@ -866,14 +317,15 @@ bool RasterizerCacheOpenGL::BlitSurfaces(const Surface& src_surface, const Common::Rectangle& dst_rect) { MICROPROFILE_SCOPE(OpenGL_BlitSurface); - if (!CheckFormatsBlittable(src_surface->pixel_format, dst_surface->pixel_format)) - return false; + if (CheckFormatsBlittable(src_surface->pixel_format, dst_surface->pixel_format)) { + dst_surface->InvalidateAllWatcher(); - dst_surface->InvalidateAllWatcher(); + const Aspect aspect = ToAspect(src_surface->type); + return runtime.BlitTextures(src_surface->texture, {aspect, src_rect}, + dst_surface->texture, {aspect, dst_rect}); + } - return BlitTextures(src_surface->texture.handle, src_rect, dst_surface->texture.handle, - dst_rect, src_surface->type, read_framebuffer.handle, - draw_framebuffer.handle); + return false; } Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale, @@ -1051,47 +503,18 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInf LOG_CRITICAL(Render_OpenGL, "Unsupported mipmap level {}", max_level); return nullptr; } - OpenGLState prev_state = OpenGLState::GetCurState(); - OpenGLState state; - SCOPE_EXIT({ prev_state.Apply(); }); - auto format_tuple = GetFormatTuple(params.pixel_format); // Allocate more mipmap level if necessary if (surface->max_level < max_level) { - state.texture_units[0].texture_2d = surface->texture.handle; - state.Apply(); - glActiveTexture(GL_TEXTURE0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, max_level); - - // If we are using ARB_texture_storage then we've already allocated all of the mipmap - // levels - if (!GL_ARB_texture_storage) { - u32 width, height; - if (surface->is_custom) { - width = surface->custom_tex_info.width; - height = surface->custom_tex_info.height; - } else { - 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, - height >> level, 0, format_tuple.format, format_tuple.type, - nullptr); - } - } if (surface->is_custom || !texture_filterer->IsNull()) { // TODO: proper mipmap support for custom textures - glGenerateMipmap(GL_TEXTURE_2D); + runtime.GenerateMipmaps(surface->texture, max_level); } + surface->max_level = max_level; } // Blit mipmaps that have been invalidated - state.draw.read_framebuffer = read_framebuffer.handle; - state.draw.draw_framebuffer = draw_framebuffer.handle; - state.ResetTexture(surface->texture.handle); SurfaceParams surface_params = *surface; for (u32 level = 1; level <= max_level; ++level) { // In PICA all mipmap levels are stored next to each other @@ -1101,6 +524,7 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInf surface_params.height /= 2; surface_params.stride = 0; // reset stride and let UpdateParams re-initialize it surface_params.UpdateParams(); + auto& watcher = surface->level_watchers[level - 1]; if (!watcher || !watcher->Get()) { auto level_surface = GetSurface(surface_params, ScaleMatch::Ignore, true); @@ -1116,25 +540,16 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInf if (!level_surface->invalid_regions.empty()) { ValidateSurface(level_surface, level_surface->addr, level_surface->size); } - state.ResetTexture(level_surface->texture.handle); - state.Apply(); + if (!surface->is_custom && texture_filterer->IsNull()) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - level_surface->texture.handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, 0, 0); + const auto src_rect = level_surface->GetScaledRect(); + const auto dst_rect = surface_params.GetScaledRect(); + const Aspect aspect = ToAspect(surface->type); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - surface->texture.handle, level); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, 0, 0); - - auto src_rect = level_surface->GetScaledRect(); - auto dst_rect = surface_params.GetScaledRect(); - glBlitFramebuffer(src_rect.left, src_rect.bottom, src_rect.right, src_rect.top, - dst_rect.left, dst_rect.bottom, dst_rect.right, dst_rect.top, - GL_COLOR_BUFFER_BIT, GL_LINEAR); + runtime.BlitTextures(level_surface->texture, {aspect, src_rect}, + surface->texture, {aspect, dst_rect, level}); } + watcher->Validate(); } } @@ -1203,32 +618,19 @@ const CachedTextureCube& RasterizerCacheOpenGL::GetTextureCube(const TextureCube OpenGLState prev_state = OpenGLState::GetCurState(); SCOPE_EXIT({ prev_state.Apply(); }); - OpenGLState state; - state.draw.read_framebuffer = read_framebuffer.handle; - state.draw.draw_framebuffer = draw_framebuffer.handle; - state.ResetTexture(cube.texture.handle); - for (const Face& face : faces) { if (face.watcher && !face.watcher->IsValid()) { auto surface = face.watcher->Get(); if (!surface->invalid_regions.empty()) { ValidateSurface(surface, surface->addr, surface->size); } - state.ResetTexture(surface->texture.handle); - state.Apply(); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - surface->texture.handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, face.gl_face, - cube.texture.handle, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); + const auto src_rect = surface->GetScaledRect(); + const auto dst_rect = Common::Rectangle{0, scaled_size, scaled_size, 0}; + const Aspect aspect = ToAspect(surface->type); + runtime.BlitTextures(surface->texture, {aspect, src_rect}, + cube.texture, {aspect, dst_rect}); - auto src_rect = surface->GetScaledRect(); - glBlitFramebuffer(src_rect.left, src_rect.bottom, src_rect.right, src_rect.top, 0, 0, - scaled_size, scaled_size, GL_COLOR_BUFFER_BIT, GL_LINEAR); face.watcher->Validate(); } } @@ -1329,7 +731,7 @@ SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces( } Surface RasterizerCacheOpenGL::GetFillSurface(const GPU::Regs::MemoryFillConfig& config) { - Surface new_surface = std::make_shared(*this); + Surface new_surface = std::make_shared(*this, runtime); new_surface->addr = config.GetStartAddress(); new_surface->end = config.GetEndAddress(); @@ -1458,8 +860,7 @@ void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr, // Load data from 3DS memory FlushRegion(params.addr, params.size); surface->LoadGLBuffer(params.addr, params.end); - surface->UploadGLTexture(surface->GetSubRect(params), read_framebuffer.handle, - draw_framebuffer.handle); + surface->UploadGLTexture(surface->GetSubRect(params)); notify_validated(params.GetInterval()); } } @@ -1508,17 +909,20 @@ bool RasterizerCacheOpenGL::IntervalHasInvalidPixelFormat(SurfaceParams& params, bool RasterizerCacheOpenGL::ValidateByReinterpretation(const Surface& surface, SurfaceParams& params, const SurfaceInterval& interval) { - auto [cvt_begin, cvt_end] = - format_reinterpreter->GetPossibleReinterpretations(surface->pixel_format); - for (auto reinterpreter = cvt_begin; reinterpreter != cvt_end; ++reinterpreter) { - PixelFormat format = reinterpreter->first.src_format; - params.pixel_format = format; + const PixelFormat dst_format = surface->pixel_format; + const SurfaceType type = GetFormatType(dst_format); + const FormatTuple& tuple = GetFormatTuple(dst_format); + + for (auto& reinterpreter : + format_reinterpreter->GetPossibleReinterpretations(surface->pixel_format)) { + + params.pixel_format = reinterpreter->GetSourceFormat(); Surface reinterpret_surface = FindMatch(surface_cache, params, ScaleMatch::Ignore, interval); if (reinterpret_surface != nullptr) { - SurfaceInterval reinterpret_interval = params.GetCopyableInterval(reinterpret_surface); - SurfaceParams reinterpret_params = surface->FromInterval(reinterpret_interval); + auto reinterpret_interval = params.GetCopyableInterval(reinterpret_surface); + auto reinterpret_params = surface->FromInterval(reinterpret_interval); auto src_rect = reinterpret_surface->GetScaledSubRect(reinterpret_params); auto dest_rect = surface->GetScaledSubRect(reinterpret_params); @@ -1527,29 +931,30 @@ bool RasterizerCacheOpenGL::ValidateByReinterpretation(const Surface& surface, // The destination surface is either a framebuffer, or a filtered texture. // Create an intermediate surface to convert to before blitting to the // destination. - Common::Rectangle tmp_rect{0, dest_rect.GetHeight() / resolution_scale_factor, - dest_rect.GetWidth() / resolution_scale_factor, 0}; - OGLTexture tmp_tex = AllocateSurfaceTexture( - GetFormatTuple(reinterpreter->first.dst_format), tmp_rect.right, tmp_rect.top); - reinterpreter->second->Reinterpret(reinterpret_surface->texture.handle, src_rect, - read_framebuffer.handle, tmp_tex.handle, - tmp_rect, draw_framebuffer.handle); + const u32 width = dest_rect.GetHeight() / resolution_scale_factor; + const u32 height = dest_rect.GetWidth() / resolution_scale_factor; + const Common::Rectangle tmp_rect{0, width, height, 0}; - const SurfaceType type = GetFormatType(reinterpreter->first.dst_format); - if (!texture_filterer->Filter(tmp_tex.handle, tmp_rect, surface->texture.handle, - dest_rect, type, read_framebuffer.handle, - draw_framebuffer.handle)) { - BlitTextures(tmp_tex.handle, tmp_rect, surface->texture.handle, dest_rect, type, - read_framebuffer.handle, draw_framebuffer.handle); + OGLTexture tmp_tex = AllocateSurfaceTexture(tuple, height, width); + reinterpreter->Reinterpret(reinterpret_surface->texture, src_rect, + tmp_tex, tmp_rect); + + if (!texture_filterer->Filter(tmp_tex, tmp_rect, + surface->texture, dest_rect, type)) { + + const Aspect aspect = ToAspect(type); + runtime.BlitTextures(tmp_tex, {aspect, tmp_rect}, + surface->texture, {aspect, dest_rect}); } } else { - reinterpreter->second->Reinterpret(reinterpret_surface->texture.handle, src_rect, - read_framebuffer.handle, surface->texture.handle, - dest_rect, draw_framebuffer.handle); + reinterpreter->Reinterpret(reinterpret_surface->texture, src_rect, + surface->texture, dest_rect); } + return true; } } + return false; } @@ -1601,9 +1006,9 @@ void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u32 size, Surface flush_surf if (surface->type != SurfaceType::Fill) { SurfaceParams params = surface->FromInterval(interval); - surface->DownloadGLTexture(surface->GetSubRect(params), read_framebuffer.handle, - draw_framebuffer.handle); + surface->DownloadGLTexture(surface->GetSubRect(params)); } + surface->FlushGLBuffer(boost::icl::first(interval), boost::icl::last_next(interval)); flushed_intervals += interval; } @@ -1680,7 +1085,7 @@ void RasterizerCacheOpenGL::InvalidateRegion(PAddr addr, u32 size, const Surface } Surface RasterizerCacheOpenGL::CreateSurface(const SurfaceParams& params) { - Surface surface = std::make_shared(*this); + Surface surface = std::make_shared(*this, runtime); static_cast(*surface) = params; surface->invalid_regions.insert(surface->GetInterval()); diff --git a/src/video_core/rasterizer_cache/rasterizer_cache.h b/src/video_core/rasterizer_cache/rasterizer_cache.h index b95262674..2305dff86 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache.h @@ -4,11 +4,9 @@ #pragma once #include -#include "common/assert.h" -#include "core/custom_tex_cache.h" +#include "video_core/rasterizer_cache/cached_surface.h" #include "video_core/rasterizer_cache/rasterizer_cache_utils.h" #include "video_core/rasterizer_cache/surface_params.h" -#include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/texture/texture_decode.h" namespace OpenGL { @@ -19,129 +17,6 @@ enum class ScaleMatch { Ignore // accept every scaled res }; -/** - * A watcher that notifies whether a cached surface has been changed. This is useful for caching - * surface collection objects, including texture cube and mipmap. - */ -struct SurfaceWatcher { -public: - explicit SurfaceWatcher(std::weak_ptr&& surface) : surface(std::move(surface)) {} - - /** - * Checks whether the surface has been changed. - * @return false if the surface content has been changed since last Validate() call or has been - * destroyed; otherwise true - */ - bool IsValid() const { - return !surface.expired() && valid; - } - - /// Marks that the content of the referencing surface has been updated to the watcher user. - void Validate() { - ASSERT(!surface.expired()); - valid = true; - } - - /// Gets the referencing surface. Returns null if the surface has been destroyed - Surface Get() const { - return surface.lock(); - } - -private: - friend struct CachedSurface; - std::weak_ptr surface; - bool valid = false; -}; - -class RasterizerCacheOpenGL; - -struct CachedSurface : SurfaceParams, std::enable_shared_from_this { - CachedSurface(RasterizerCacheOpenGL& owner) : owner{owner} {} - ~CachedSurface(); - - bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const; - bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const; - - bool IsRegionValid(SurfaceInterval interval) const { - return (invalid_regions.find(interval) == invalid_regions.end()); - } - - bool IsSurfaceFullyInvalid() const { - auto interval = GetInterval(); - return *invalid_regions.equal_range(interval).first == interval; - } - - bool registered = false; - SurfaceRegions invalid_regions; - - u32 fill_size = 0; /// Number of bytes to read from fill_data - std::array fill_data; - - OGLTexture texture; - - /// max mipmap level that has been attached to the texture - u32 max_level = 0; - /// level_watchers[i] watches the (i+1)-th level mipmap source surface - std::array, 7> level_watchers; - - bool is_custom = false; - Core::CustomTexInfo custom_tex_info; - - 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); - void DumpTexture(GLuint target_tex, u64 tex_hash); - - // Upload/Download data in gl_buffer in/to this surface's texture - 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); - - std::shared_ptr CreateWatcher() { - auto watcher = std::make_shared(weak_from_this()); - watchers.push_front(watcher); - return watcher; - } - - void InvalidateAllWatcher() { - for (const auto& watcher : watchers) { - if (auto locked = watcher.lock()) { - locked->valid = false; - } - } - } - - void UnlinkAllWatcher() { - for (const auto& watcher : watchers) { - if (auto locked = watcher.lock()) { - locked->valid = false; - locked->surface.reset(); - } - } - watchers.clear(); - } - -private: - RasterizerCacheOpenGL& owner; - std::list> watchers; -}; - -struct CachedTextureCube { - OGLTexture texture; - u16 res_scale = 1; - std::shared_ptr px; - std::shared_ptr nx; - std::shared_ptr py; - std::shared_ptr ny; - std::shared_ptr pz; - std::shared_ptr nz; -}; - class TextureDownloaderES; class TextureFilterer; class FormatReinterpreterOpenGL; @@ -233,14 +108,12 @@ private: /// Increase/decrease the number of surface in pages touching the specified region void UpdatePagesCachedCount(PAddr addr, u32 size, int delta); + TextureRuntime runtime; SurfaceCache surface_cache; PageMap cached_pages; SurfaceMap dirty_regions; SurfaceSet remove_surfaces; - OGLFramebuffer read_framebuffer; - OGLFramebuffer draw_framebuffer; - u16 resolution_scale_factor; std::unordered_map texture_cube_cache; diff --git a/src/video_core/rasterizer_cache/rasterizer_cache_types.h b/src/video_core/rasterizer_cache/rasterizer_cache_types.h index 698f6ec7f..9b911dd00 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache_types.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache_types.h @@ -13,7 +13,7 @@ namespace OpenGL { -struct CachedSurface; +class CachedSurface; using Surface = std::shared_ptr; // Declare rasterizer interval types diff --git a/src/video_core/rasterizer_cache/rasterizer_cache_utils.h b/src/video_core/rasterizer_cache/rasterizer_cache_utils.h index 928bd4041..ee4a056e4 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache_utils.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache_utils.h @@ -4,9 +4,6 @@ #pragma once #include -#include -#include -#include #include "common/hash.h" #include "video_core/rasterizer_cache/pixel_format.h" diff --git a/src/video_core/rasterizer_cache/texture_runtime.cpp b/src/video_core/rasterizer_cache/texture_runtime.cpp new file mode 100644 index 000000000..b8406bd21 --- /dev/null +++ b/src/video_core/rasterizer_cache/texture_runtime.cpp @@ -0,0 +1,196 @@ +// Copyright 2022 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/scope_exit.h" +#include "video_core/rasterizer_cache/rasterizer_cache_utils.h" +#include "video_core/rasterizer_cache/texture_runtime.h" +#include "video_core/renderer_opengl/gl_state.h" + +namespace OpenGL { + +GLbitfield ToBufferMask(Aspect aspect) { + switch (aspect) { + case Aspect::Color: + return GL_COLOR_BUFFER_BIT; + case Aspect::Depth: + return GL_DEPTH_BUFFER_BIT; + case Aspect::DepthStencil: + return GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + } +} + +TextureRuntime::TextureRuntime() { + read_fbo.Create(); + draw_fbo.Create(); +} + +void TextureRuntime::ReadTexture(const OGLTexture& tex, Subresource subresource, + const FormatTuple& tuple, u8* pixels) { + + OpenGLState prev_state = OpenGLState::GetCurState(); + SCOPE_EXIT({ prev_state.Apply(); }); + + OpenGLState state; + state.ResetTexture(tex.handle); + state.draw.read_framebuffer = read_fbo.handle; + state.Apply(); + + const u32 level = subresource.level; + switch (subresource.aspect) { + case Aspect::Color: + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + tex.handle, level); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + 0, 0); + break; + case Aspect::Depth: + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, + tex.handle, level); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + break; + case Aspect::DepthStencil: + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + tex.handle, level); + break; + } + + const auto& rect = subresource.region; + glReadPixels(rect.left, rect.bottom, rect.GetWidth(), rect.GetHeight(), + tuple.format, tuple.type, pixels); +} + +bool TextureRuntime::ClearTexture(const OGLTexture& tex, Subresource subresource, + ClearValue value) { + OpenGLState prev_state = OpenGLState::GetCurState(); + SCOPE_EXIT({ prev_state.Apply(); }); + + // Setup scissor rectangle according to the clear rectangle + const auto& clear_rect = subresource.region; + OpenGLState state; + state.scissor.enabled = true; + state.scissor.x = clear_rect.left; + state.scissor.y = clear_rect.bottom; + state.scissor.width = clear_rect.GetWidth(); + state.scissor.height = clear_rect.GetHeight(); + state.draw.draw_framebuffer = draw_fbo.handle; + state.Apply(); + + const u32 level = subresource.level; + switch (subresource.aspect) { + case Aspect::Color: + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + tex.handle, level); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + 0, 0); + + state.color_mask.red_enabled = true; + state.color_mask.green_enabled = true; + state.color_mask.blue_enabled = true; + state.color_mask.alpha_enabled = true; + state.Apply(); + + glClearBufferfv(GL_COLOR, 0, value.color.AsArray()); + break; + case Aspect::Depth: + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, + tex.handle, level); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + state.depth.write_mask = GL_TRUE; + state.Apply(); + + glClearBufferfv(GL_DEPTH, 0, &value.depth); + break; + case Aspect::DepthStencil: + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + tex.handle, level); + + state.depth.write_mask = GL_TRUE; + state.stencil.write_mask = -1; + state.Apply(); + + glClearBufferfi(GL_DEPTH_STENCIL, 0, value.depth, value.stencil); + break; + } + + return true; +} + +bool TextureRuntime::CopyTextures(const OGLTexture& src_tex, Subresource src_subresource, + const OGLTexture& dst_tex, Subresource dst_subresource) { + return true; +} + +bool TextureRuntime::BlitTextures(const OGLTexture& src_tex, Subresource src_subresource, + const OGLTexture& dst_tex, Subresource dst_subresource) { + OpenGLState prev_state = OpenGLState::GetCurState(); + SCOPE_EXIT({ prev_state.Apply(); }); + + OpenGLState state; + state.draw.read_framebuffer = read_fbo.handle; + state.draw.draw_framebuffer = draw_fbo.handle; + state.Apply(); + + auto BindAttachment = [src_level = src_subresource.level, + dst_level = dst_subresource.level](GLenum target, u32 src_tex, + u32 dst_tex) -> void { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, target, GL_TEXTURE_2D, src_tex, src_level); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, target, GL_TEXTURE_2D, dst_tex, dst_level); + }; + + // Sanity check; Can't blit a color texture to a depth buffer + ASSERT(src_subresource.aspect == dst_subresource.aspect); + switch (src_subresource.aspect) { + case Aspect::Color: + // Bind only color + BindAttachment(GL_COLOR_ATTACHMENT0, src_tex.handle, dst_tex.handle); + BindAttachment(GL_DEPTH_STENCIL_ATTACHMENT, 0, 0); + break; + case Aspect::Depth: + // Bind only depth + BindAttachment(GL_COLOR_ATTACHMENT0, 0, 0); + BindAttachment(GL_DEPTH_ATTACHMENT, src_tex.handle, dst_tex.handle); + BindAttachment(GL_STENCIL_ATTACHMENT, 0, 0); + break; + case Aspect::DepthStencil: + // Bind to combined depth + stencil + BindAttachment(GL_COLOR_ATTACHMENT0, 0, 0); + BindAttachment(GL_DEPTH_STENCIL_ATTACHMENT, src_tex.handle, dst_tex.handle); + break; + } + + // TODO (wwylele): use GL_NEAREST for shadow map texture + // Note: shadow map is treated as RGBA8 format in PICA, as well as in the rasterizer cache, but + // doing linear intepolation componentwise would cause incorrect value. However, for a + // well-programmed game this code path should be rarely executed for shadow map with + // inconsistent scale. + const GLenum filter = src_subresource.aspect == Aspect::Color ? GL_LINEAR : GL_NEAREST; + const auto& src_rect = src_subresource.region; + const auto& dst_rect = dst_subresource.region; + glBlitFramebuffer(src_rect.left, src_rect.bottom, src_rect.right, src_rect.top, + dst_rect.left, dst_rect.bottom, dst_rect.right, dst_rect.top, + ToBufferMask(src_subresource.aspect), filter); + + return true; +} + +void TextureRuntime::GenerateMipmaps(const OGLTexture& tex, u32 max_level) { + OpenGLState prev_state = OpenGLState::GetCurState(); + SCOPE_EXIT({ prev_state.Apply(); }); + + OpenGLState state; + state.texture_units[0].texture_2d = tex.handle; + state.Apply(); + + glActiveTexture(GL_TEXTURE0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, max_level); + + glGenerateMipmap(GL_TEXTURE_2D); +} + +} // namespace OpenGL diff --git a/src/video_core/rasterizer_cache/texture_runtime.h b/src/video_core/rasterizer_cache/texture_runtime.h new file mode 100644 index 000000000..b14136e2f --- /dev/null +++ b/src/video_core/rasterizer_cache/texture_runtime.h @@ -0,0 +1,73 @@ +// Copyright 2022 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include "common/math_util.h" +#include "common/vector_math.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" + +namespace OpenGL { + +// Describes the type of data a texture holds +enum class Aspect { + Color = 0, + Depth = 1, + DepthStencil = 2 +}; + +// A union for both color and depth/stencil clear values +union ClearValue { + Common::Vec4f color; + struct { + float depth; + u8 stencil; + }; +}; + +struct Subresource { + Subresource(Aspect aspect, Common::Rectangle region, u32 level = 0, u32 layer = 0) : + aspect(aspect), region(region), level(level), layer(layer) {} + + Aspect aspect; + Common::Rectangle region; + u32 level = 0; + u32 layer = 0; +}; + +struct FormatTuple; + +/** + * Provides texture manipulation functions to the rasterizer cache + * Separating this into a class makes it easier to abstract graphics API code + */ +class TextureRuntime { +public: + TextureRuntime(); + ~TextureRuntime() = default; + + // Copies the GPU pixel data to the provided pixels buffer + void ReadTexture(const OGLTexture& tex, Subresource subresource, + const FormatTuple& tuple, u8* pixels); + + // Fills the rectangle of the texture with the clear value provided + bool ClearTexture(const OGLTexture& texture, Subresource subresource, ClearValue value); + + // Copies a rectangle of src_tex to another rectange of dst_rect + // NOTE: The width and height of the rectangles must be equal + bool CopyTextures(const OGLTexture& src_tex, Subresource src_subresource, + const OGLTexture& dst_tex, Subresource dst_subresource); + + // Copies a rectangle of src_tex to another rectange of dst_rect performing + // scaling and format conversions + bool BlitTextures(const OGLTexture& src_tex, Subresource src_subresource, + const OGLTexture& dst_tex, Subresource dst_subresource); + + // Generates mipmaps for all the available levels of the texture + void GenerateMipmaps(const OGLTexture& tex, u32 max_level); + +private: + OGLFramebuffer read_fbo, draw_fbo; +}; + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_format_reinterpreter.cpp b/src/video_core/renderer_opengl/gl_format_reinterpreter.cpp index 04fae3f88..d642e0400 100644 --- a/src/video_core/renderer_opengl/gl_format_reinterpreter.cpp +++ b/src/video_core/renderer_opengl/gl_format_reinterpreter.cpp @@ -1,14 +1,12 @@ -// Copyright 2020 Citra Emulator Project +// Copyright 2022 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include "common/assert.h" #include "common/scope_exit.h" #include "video_core/renderer_opengl/gl_format_reinterpreter.h" -#include "video_core/rasterizer_cache/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_filterer.h" namespace OpenGL { @@ -64,15 +62,18 @@ void main() { vao.Create(); } - void Reinterpret(GLuint src_tex, const Common::Rectangle& src_rect, GLuint read_fb_handle, - GLuint dst_tex, const Common::Rectangle& dst_rect, - GLuint draw_fb_handle) override { + PixelFormat GetSourceFormat() const override { + return PixelFormat::RGBA4; + } + + void Reinterpret(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect) override { OpenGLState prev_state = OpenGLState::GetCurState(); SCOPE_EXIT({ prev_state.Apply(); }); OpenGLState state; - state.texture_units[0].texture_2d = src_tex; - state.draw.draw_framebuffer = draw_fb_handle; + state.texture_units[0].texture_2d = src_tex.handle; + state.draw.draw_framebuffer = draw_fbo.handle; state.draw.shader_program = program.handle; state.draw.vertex_array = vao.handle; state.viewport = {static_cast(dst_rect.left), static_cast(dst_rect.bottom), @@ -80,10 +81,10 @@ void main() { static_cast(dst_rect.GetHeight())}; state.Apply(); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, - 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + dst_tex.handle, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + 0, 0); glUniform2i(dst_size_loc, dst_rect.GetWidth(), dst_rect.GetHeight()); glUniform2i(src_size_loc, src_rect.GetWidth(), src_rect.GetHeight()); @@ -148,15 +149,18 @@ void main() { ~PixelBufferD24S8toABGR() {} - void Reinterpret(GLuint src_tex, const Common::Rectangle& src_rect, GLuint read_fb_handle, - GLuint dst_tex, const Common::Rectangle& dst_rect, - GLuint draw_fb_handle) override { + PixelFormat GetSourceFormat() const override { + return PixelFormat::D24S8; + } + + void Reinterpret(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect) override { OpenGLState prev_state = OpenGLState::GetCurState(); SCOPE_EXIT({ prev_state.Apply(); }); OpenGLState state; - state.draw.read_framebuffer = read_fb_handle; - state.draw.draw_framebuffer = draw_fb_handle; + state.draw.read_framebuffer = read_fbo.handle; + state.draw.draw_framebuffer = draw_fbo.handle; state.Apply(); glBindBuffer(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer.handle); @@ -170,7 +174,8 @@ void main() { glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - src_tex, 0); + src_tex.handle, 0); + glReadPixels(static_cast(src_rect.left), static_cast(src_rect.bottom), static_cast(src_rect.GetWidth()), static_cast(src_rect.GetHeight()), GL_DEPTH_STENCIL, @@ -200,12 +205,11 @@ void main() { static_cast(state.viewport.width), static_cast(state.viewport.height)); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, - 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + dst_tex.handle, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + 0, 0); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - glBindTexture(GL_TEXTURE_BUFFER, 0); } @@ -292,19 +296,22 @@ void main() { } } - void Reinterpret(GLuint src_tex, const Common::Rectangle& src_rect, GLuint read_fb_handle, - GLuint dst_tex, const Common::Rectangle& dst_rect, - GLuint draw_fb_handle) override { + PixelFormat GetSourceFormat() const override { + return PixelFormat::D24S8; + } + + void Reinterpret(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect) override { OpenGLState prev_state = OpenGLState::GetCurState(); SCOPE_EXIT({ prev_state.Apply(); }); OpenGLState state; - state.texture_units[0].texture_2d = src_tex; + state.texture_units[0].texture_2d = src_tex.handle; if (use_texture_view) { temp_tex.Create(); glActiveTexture(GL_TEXTURE1); - glTextureView(temp_tex.handle, GL_TEXTURE_2D, src_tex, GL_DEPTH24_STENCIL8, 0, 1, 0, 1); + glTextureView(temp_tex.handle, GL_TEXTURE_2D, src_tex.handle, GL_DEPTH24_STENCIL8, 0, 1, 0, 1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else if (src_rect.top > temp_rect.top || src_rect.right > temp_rect.right) { @@ -320,7 +327,7 @@ void main() { } state.texture_units[1].texture_2d = temp_tex.handle; - state.draw.draw_framebuffer = draw_fb_handle; + state.draw.draw_framebuffer = draw_fbo.handle; state.draw.shader_program = program.handle; state.draw.vertex_array = vao.handle; state.viewport = {static_cast(dst_rect.left), static_cast(dst_rect.bottom), @@ -330,16 +337,16 @@ void main() { glActiveTexture(GL_TEXTURE1); if (!use_texture_view) { - glCopyImageSubData(src_tex, GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0, + glCopyImageSubData(src_tex.handle, GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0, temp_tex.handle, GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0, src_rect.GetWidth(), src_rect.GetHeight(), 1); } glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, - 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + dst_tex.handle, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + 0, 0); glUniform2i(dst_size_loc, dst_rect.GetWidth(), dst_rect.GetHeight()); glUniform2i(src_size_loc, src_rect.GetWidth(), src_rect.GetHeight()); @@ -363,32 +370,32 @@ private: FormatReinterpreterOpenGL::FormatReinterpreterOpenGL() { const std::string_view vendor{reinterpret_cast(glGetString(GL_VENDOR))}; const std::string_view version{reinterpret_cast(glGetString(GL_VERSION))}; + // Fallback to PBO path on obsolete intel drivers // intel`s GL_VERSION string - `3.3.0 - Build 25.20.100.6373` const bool intel_broken_drivers = vendor.find("Intel") != vendor.npos && (std::atoi(version.substr(14, 2).data()) < 30); - if ((!intel_broken_drivers && GLAD_GL_ARB_stencil_texturing && GLAD_GL_ARB_texture_storage && - GLAD_GL_ARB_copy_image) || - GLES) { - reinterpreters.emplace(PixelFormatPair{PixelFormat::RGBA8, PixelFormat::D24S8}, - std::make_unique()); + auto Register = [this](PixelFormat dest, std::unique_ptr&& obj) { + const u32 dst_index = static_cast(dest); + return reinterpreters[dst_index].push_back(std::move(obj)); + }; + + if ((!intel_broken_drivers && GLAD_GL_ARB_stencil_texturing && + GLAD_GL_ARB_texture_storage && GLAD_GL_ARB_copy_image) || GLES) { + Register(PixelFormat::RGBA8, std::make_unique()); LOG_INFO(Render_OpenGL, "Using shader for D24S8 to RGBA8 reinterpretation"); } else { - reinterpreters.emplace(PixelFormatPair{PixelFormat::RGBA8, PixelFormat::D24S8}, - std::make_unique()); - LOG_INFO(Render_OpenGL, "Using pbo for D24S8 to RGBA8 reinterpretation"); + Register(PixelFormat::RGBA8, std::make_unique()); + LOG_INFO(Render_OpenGL, "Using PBO for D24S8 to RGBA8 reinterpretation"); } - reinterpreters.emplace(PixelFormatPair{PixelFormat::RGB5A1, PixelFormat::RGBA4}, - std::make_unique()); + + Register(PixelFormat::RGB5A1, std::make_unique()); } -FormatReinterpreterOpenGL::~FormatReinterpreterOpenGL() = default; - -std::pair -FormatReinterpreterOpenGL::GetPossibleReinterpretations(PixelFormat dst_format) { - return reinterpreters.equal_range(dst_format); +auto FormatReinterpreterOpenGL::GetPossibleReinterpretations(PixelFormat dst_format) + -> const ReinterpreterList& { + return reinterpreters[static_cast(dst_format)]; } } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_format_reinterpreter.h b/src/video_core/renderer_opengl/gl_format_reinterpreter.h index 2efacde62..cd62d3059 100644 --- a/src/video_core/renderer_opengl/gl_format_reinterpreter.h +++ b/src/video_core/renderer_opengl/gl_format_reinterpreter.h @@ -1,62 +1,46 @@ -// Copyright 2020 Citra Emulator Project +// Copyright 2022 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once -#include -#include -#include -#include "common/common_types.h" +#include #include "common/math_util.h" #include "video_core/rasterizer_cache/pixel_format.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" namespace OpenGL { class RasterizerCacheOpenGL; -struct PixelFormatPair { - const PixelFormat dst_format, src_format; - - struct less { - using is_transparent = void; - constexpr bool operator()(PixelFormatPair lhs, PixelFormatPair rhs) const { - return std::tie(lhs.dst_format, lhs.src_format) < - std::tie(rhs.dst_format, rhs.src_format); - } - - constexpr bool operator()(PixelFormat lhs, PixelFormatPair rhs) const { - return lhs < rhs.dst_format; - } - - constexpr bool operator()(PixelFormatPair lhs, PixelFormat rhs) const { - return lhs.dst_format < rhs; - } - }; -}; - class FormatReinterpreterBase { public: + FormatReinterpreterBase() { + read_fbo.Create(); + draw_fbo.Create(); + } + virtual ~FormatReinterpreterBase() = default; - virtual void Reinterpret(GLuint src_tex, const Common::Rectangle& src_rect, - GLuint read_fb_handle, GLuint dst_tex, - const Common::Rectangle& dst_rect, GLuint draw_fb_handle) = 0; + virtual PixelFormat GetSourceFormat() const = 0; + virtual void Reinterpret(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect) = 0; + +protected: + OGLFramebuffer read_fbo, draw_fbo; }; +using ReinterpreterList = std::vector>; + class FormatReinterpreterOpenGL : NonCopyable { - using ReinterpreterMap = - std::map, PixelFormatPair::less>; - public: - explicit FormatReinterpreterOpenGL(); - ~FormatReinterpreterOpenGL(); + FormatReinterpreterOpenGL(); + ~FormatReinterpreterOpenGL() = default; - auto GetPossibleReinterpretations(PixelFormat dst_format) -> - std::pair; + const ReinterpreterList& GetPossibleReinterpretations(PixelFormat dst_format); private: - ReinterpreterMap reinterpreters; + std::array reinterpreters; }; } // 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 index ab2984da2..0a43d9f27 100644 --- a/src/video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.cpp +++ b/src/video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.cpp @@ -30,7 +30,6 @@ // 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/rasterizer_cache/rasterizer_cache.h" #include "video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h" #include "shaders/refine.frag" @@ -72,9 +71,8 @@ Anime4kUltrafast::Anime4kUltrafast(u16 scale_factor) : TextureFilterBase(scale_f cur_state.Apply(); } -void Anime4kUltrafast::Filter(GLuint src_tex, const Common::Rectangle& src_rect, - GLuint dst_tex, const Common::Rectangle& dst_rect, - GLuint read_fb_handle, GLuint draw_fb_handle) { +void Anime4kUltrafast::Filter(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect) { const OpenGLState cur_state = OpenGLState::GetCurState(); // These will have handles from the previous texture that was filtered, reset them to avoid @@ -112,7 +110,7 @@ void Anime4kUltrafast::Filter(GLuint src_tex, const Common::Rectangle& src_ static_cast(src_rect.bottom * internal_scale_factor), static_cast(src_rect.GetWidth() * internal_scale_factor), static_cast(src_rect.GetHeight() * internal_scale_factor)}; - state.texture_units[0].texture_2d = src_tex; + state.texture_units[0].texture_2d = src_tex.handle; state.texture_units[1].texture_2d = LUMAD.tex.handle; state.texture_units[2].texture_2d = XY.tex.handle; state.draw.draw_framebuffer = XY.fbo.handle; @@ -131,11 +129,12 @@ void Anime4kUltrafast::Filter(GLuint src_tex, const Common::Rectangle& src_ state.viewport = {static_cast(dst_rect.left), static_cast(dst_rect.bottom), static_cast(dst_rect.GetWidth()), static_cast(dst_rect.GetHeight())}; - state.draw.draw_framebuffer = draw_fb_handle; + state.draw.draw_framebuffer = draw_fbo.handle; state.draw.shader_program = refine_program.handle; state.Apply(); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + dst_tex.handle, 0); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 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 index 8175ed390..23e95ad4c 100644 --- a/src/video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h +++ b/src/video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h @@ -15,9 +15,8 @@ public: static constexpr std::string_view NAME = "Anime4K Ultrafast"; explicit Anime4kUltrafast(u16 scale_factor); - void Filter(GLuint src_tex, const Common::Rectangle& src_rect, GLuint dst_tex, - const Common::Rectangle& dst_rect, GLuint read_fb_handle, - GLuint draw_fb_handle) override; + void Filter(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect) override; private: static constexpr u8 internal_scale_factor = 2; diff --git a/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.cpp b/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.cpp index d96c7b54d..f1764889d 100644 --- a/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.cpp +++ b/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "video_core/rasterizer_cache/rasterizer_cache.h" #include "video_core/renderer_opengl/texture_filters/bicubic/bicubic.h" #include "shaders/bicubic.frag" @@ -26,18 +25,18 @@ Bicubic::Bicubic(u16 scale_factor) : TextureFilterBase(scale_factor) { glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } // namespace OpenGL -void Bicubic::Filter(GLuint src_tex, const Common::Rectangle& src_rect, GLuint dst_tex, - const Common::Rectangle& dst_rect, GLuint read_fb_handle, - GLuint draw_fb_handle) { +void Bicubic::Filter(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect) { const OpenGLState cur_state = OpenGLState::GetCurState(); - state.texture_units[0].texture_2d = src_tex; - state.draw.draw_framebuffer = draw_fb_handle; + state.texture_units[0].texture_2d = src_tex.handle; + state.draw.draw_framebuffer = draw_fbo.handle; state.viewport = {static_cast(dst_rect.left), static_cast(dst_rect.bottom), static_cast(dst_rect.GetWidth()), static_cast(dst_rect.GetHeight())}; state.Apply(); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + dst_tex.handle, 0); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); diff --git a/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.h b/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.h index b982cc973..c39b771ab 100644 --- a/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.h +++ b/src/video_core/renderer_opengl/texture_filters/bicubic/bicubic.h @@ -15,9 +15,8 @@ public: static constexpr std::string_view NAME = "Bicubic"; explicit Bicubic(u16 scale_factor); - void Filter(GLuint src_tex, const Common::Rectangle& src_rect, GLuint dst_tex, - const Common::Rectangle& dst_rect, GLuint read_fb_handle, - GLuint draw_fb_handle) override; + void Filter(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect) override; private: OpenGLState state{}; diff --git a/src/video_core/renderer_opengl/texture_filters/scale_force/scale_force.cpp b/src/video_core/renderer_opengl/texture_filters/scale_force/scale_force.cpp index 69ebd130e..8a5835f5c 100644 --- a/src/video_core/renderer_opengl/texture_filters/scale_force/scale_force.cpp +++ b/src/video_core/renderer_opengl/texture_filters/scale_force/scale_force.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "video_core/rasterizer_cache/rasterizer_cache.h" #include "video_core/renderer_opengl/texture_filters/scale_force/scale_force.h" #include "shaders/scale_force.frag" @@ -26,18 +25,18 @@ ScaleForce::ScaleForce(u16 scale_factor) : TextureFilterBase(scale_factor) { glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } -void ScaleForce::Filter(GLuint src_tex, const Common::Rectangle& src_rect, GLuint dst_tex, - const Common::Rectangle& dst_rect, GLuint read_fb_handle, - GLuint draw_fb_handle) { +void ScaleForce::Filter(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect) { const OpenGLState cur_state = OpenGLState::GetCurState(); - state.texture_units[0].texture_2d = src_tex; - state.draw.draw_framebuffer = draw_fb_handle; + state.texture_units[0].texture_2d = src_tex.handle; + state.draw.draw_framebuffer = draw_fbo.handle; state.viewport = {static_cast(dst_rect.left), static_cast(dst_rect.bottom), static_cast(dst_rect.GetWidth()), static_cast(dst_rect.GetHeight())}; state.Apply(); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + dst_tex.handle, 0); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); diff --git a/src/video_core/renderer_opengl/texture_filters/scale_force/scale_force.h b/src/video_core/renderer_opengl/texture_filters/scale_force/scale_force.h index d877b3205..b77f9feb6 100644 --- a/src/video_core/renderer_opengl/texture_filters/scale_force/scale_force.h +++ b/src/video_core/renderer_opengl/texture_filters/scale_force/scale_force.h @@ -15,9 +15,8 @@ public: static constexpr std::string_view NAME = "ScaleForce"; explicit ScaleForce(u16 scale_factor); - void Filter(GLuint src_tex, const Common::Rectangle& src_rect, GLuint dst_tex, - const Common::Rectangle& dst_rect, GLuint read_fb_handle, - GLuint draw_fb_handle) override; + void Filter(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect) override; private: OpenGLState state{}; diff --git a/src/video_core/renderer_opengl/texture_filters/texture_filter_base.h b/src/video_core/renderer_opengl/texture_filters/texture_filter_base.h index 328d59d90..d0858a0c2 100644 --- a/src/video_core/renderer_opengl/texture_filters/texture_filter_base.h +++ b/src/video_core/renderer_opengl/texture_filters/texture_filter_base.h @@ -3,23 +3,31 @@ // Refer to the license.txt file included. #pragma once - -#include +#include #include "common/common_types.h" #include "common/math_util.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" namespace OpenGL { +class TextureRuntime; +class OGLTexture; + class TextureFilterBase { friend class TextureFilterer; - virtual void Filter(GLuint src_tex, const Common::Rectangle& src_rect, GLuint dst_tex, - const Common::Rectangle& dst_rect, GLuint read_fb_handle, - GLuint draw_fb_handle) = 0; - public: - explicit TextureFilterBase(u16 scale_factor) : scale_factor{scale_factor} {}; + explicit TextureFilterBase(u16 scale_factor) : scale_factor(scale_factor) { + draw_fbo.Create(); + }; + virtual ~TextureFilterBase() = default; +private: + virtual void Filter(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect) = 0; + +protected: + OGLFramebuffer draw_fbo; const u16 scale_factor{}; }; diff --git a/src/video_core/renderer_opengl/texture_filters/texture_filterer.cpp b/src/video_core/renderer_opengl/texture_filters/texture_filterer.cpp index 95db3866b..b8c057451 100644 --- a/src/video_core/renderer_opengl/texture_filters/texture_filterer.cpp +++ b/src/video_core/renderer_opengl/texture_filters/texture_filterer.cpp @@ -58,16 +58,16 @@ bool TextureFilterer::IsNull() const { return !filter; } -bool TextureFilterer::Filter(GLuint src_tex, const Common::Rectangle& src_rect, GLuint dst_tex, - const Common::Rectangle& dst_rect, - SurfaceType type, GLuint read_fb_handle, - GLuint draw_fb_handle) { - // depth / stencil texture filtering is not supported for now +bool TextureFilterer::Filter(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect, + SurfaceType type) { + + // Depth/Stencil texture filtering is not supported for now if (IsNull() || (type != SurfaceType::Color && type != SurfaceType::Texture)) { return false; } - filter->Filter(src_tex, src_rect, dst_tex, dst_rect, read_fb_handle, draw_fb_handle); + filter->Filter(src_tex, src_rect, dst_tex, dst_rect); return true; } @@ -83,6 +83,7 @@ std::vector TextureFilterer::GetFilterNames() { return lhs_is_none && !rhs_is_none; return lhs < rhs; }); + return ret; } diff --git a/src/video_core/renderer_opengl/texture_filters/texture_filterer.h b/src/video_core/renderer_opengl/texture_filters/texture_filterer.h index 4402d12fb..cc8462960 100644 --- a/src/video_core/renderer_opengl/texture_filters/texture_filterer.h +++ b/src/video_core/renderer_opengl/texture_filters/texture_filterer.h @@ -16,15 +16,19 @@ class TextureFilterer { public: static constexpr std::string_view NONE = "none"; +public: explicit TextureFilterer(std::string_view filter_name, u16 scale_factor); - // returns true if the filter actually changed + + // Returns true if the filter actually changed bool Reset(std::string_view new_filter_name, u16 new_scale_factor); - // returns true if there is no active filter + + // Returns true if there is no active filter bool IsNull() const; - // returns true if the texture was able to be filtered - bool Filter(GLuint src_tex, const Common::Rectangle& src_rect, GLuint dst_tex, - const Common::Rectangle& dst_rect, SurfaceType type, - GLuint read_fb_handle, GLuint draw_fb_handle); + + // Returns true if the texture was able to be filtered + bool Filter(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect, + SurfaceType type); static std::vector GetFilterNames(); 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 index 9182214df..1f0a32343 100644 --- a/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.cpp +++ b/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.cpp @@ -40,7 +40,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include "video_core/rasterizer_cache/rasterizer_cache.h" #include "video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.h" #include "shaders/xbrz_freescale.frag" @@ -48,7 +47,9 @@ namespace OpenGL { -XbrzFreescale::XbrzFreescale(u16 scale_factor) : TextureFilterBase(scale_factor) { +XbrzFreescale::XbrzFreescale(u16 scale_factor) : + TextureFilterBase(scale_factor) { + const OpenGLState cur_state = OpenGLState::GetCurState(); program.Create(xbrz_freescale_vert.data(), xbrz_freescale_frag.data()); @@ -62,7 +63,9 @@ XbrzFreescale::XbrzFreescale(u16 scale_factor) : TextureFilterBase(scale_factor) 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)); + + const GLint scale_loc = glGetUniformLocation(program.handle, "scale"); + glUniform1f(scale_loc, static_cast(scale_factor)); cur_state.Apply(); state.draw.vertex_array = vao.handle; @@ -70,19 +73,19 @@ XbrzFreescale::XbrzFreescale(u16 scale_factor) : TextureFilterBase(scale_factor) state.texture_units[0].sampler = src_sampler.handle; } -void XbrzFreescale::Filter(GLuint src_tex, const Common::Rectangle& src_rect, GLuint dst_tex, - const Common::Rectangle& dst_rect, GLuint read_fb_handle, - GLuint draw_fb_handle) { +void XbrzFreescale::Filter(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect) { const OpenGLState cur_state = OpenGLState::GetCurState(); - state.texture_units[0].texture_2d = src_tex; - state.draw.draw_framebuffer = draw_fb_handle; + state.texture_units[0].texture_2d = src_tex.handle; + state.draw.draw_framebuffer = draw_fbo.handle; state.viewport = {static_cast(dst_rect.left), static_cast(dst_rect.bottom), static_cast(dst_rect.GetWidth()), static_cast(dst_rect.GetHeight())}; state.Apply(); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + dst_tex.handle, 0); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 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 index 02c6d5d7e..274fccf11 100644 --- a/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.h +++ b/src/video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.h @@ -3,7 +3,6 @@ // 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_base.h" @@ -15,9 +14,8 @@ public: static constexpr std::string_view NAME = "xBRZ freescale"; explicit XbrzFreescale(u16 scale_factor); - void Filter(GLuint src_tex, const Common::Rectangle& src_rect, GLuint dst_tex, - const Common::Rectangle& dst_rect, GLuint read_fb_handle, - GLuint draw_fb_handle) override; + void Filter(const OGLTexture& src_tex, Common::Rectangle src_rect, + const OGLTexture& dst_tex, Common::Rectangle dst_rect) override; private: OpenGLState state{};