diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 60cc34eba..d7112126d 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -311,13 +311,21 @@ static constexpr std::array gl }; // Allocate an uninitialized texture of appropriate size and format for the surface -static void AllocateSurfaceTexture(GLuint texture, const FormatTuple& format_tuple, u32 width, - u32 height) { - OpenGLState cur_state = OpenGLState::GetCurState(); +OGLTexture RasterizerCacheOpenGL::AllocateSurfaceTexture(const FormatTuple& format_tuple, u32 width, + u32 height) { + auto recycled_tex = host_texture_recycler.find({format_tuple, width, height}); + if (recycled_tex != host_texture_recycler.end()) { + OGLTexture texture = std::move(recycled_tex->second); + host_texture_recycler.erase(recycled_tex); + return texture; + } + OGLTexture texture; + texture.Create(); + OpenGLState cur_state = OpenGLState::GetCurState(); // Keep track of previous texture bindings GLuint old_tex = cur_state.texture_units[0].texture_2d; - cur_state.texture_units[0].texture_2d = texture; + cur_state.texture_units[0].texture_2d = texture.handle; cur_state.Apply(); glActiveTexture(GL_TEXTURE0); @@ -332,6 +340,8 @@ static void AllocateSurfaceTexture(GLuint texture, const FormatTuple& format_tup // Restore previous texture bindings cur_state.texture_units[0].texture_2d = old_tex; cur_state.Apply(); + + return texture; } static void AllocateTextureCube(GLuint texture, const FormatTuple& format_tuple, u32 width) { @@ -494,6 +504,17 @@ static bool FillSurface(const Surface& surface, const u8* fill_data, 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) && @@ -812,12 +833,11 @@ void CachedSurface::UploadGLTexture(Common::Rectangle rect, GLuint read_fb_ x0 = 0; y0 = 0; - unscaled_tex.Create(); if (is_custom) { - AllocateSurfaceTexture(unscaled_tex.handle, GetFormatTuple(PixelFormat::RGBA8), - custom_tex_info.width, custom_tex_info.height); + unscaled_tex = owner.AllocateSurfaceTexture( + GetFormatTuple(PixelFormat::RGBA8), custom_tex_info.width, custom_tex_info.height); } else { - AllocateSurfaceTexture(unscaled_tex.handle, tuple, rect.GetWidth(), rect.GetHeight()); + unscaled_tex = owner.AllocateSurfaceTexture(tuple, rect.GetWidth(), rect.GetHeight()); } target_tex = unscaled_tex.handle; } @@ -832,8 +852,8 @@ void CachedSurface::UploadGLTexture(Common::Rectangle rect, GLuint read_fb_ ASSERT(stride * GetGLBytesPerPixel(pixel_format) % 4 == 0); if (is_custom) { if (res_scale == 1) { - AllocateSurfaceTexture(texture.handle, GetFormatTuple(PixelFormat::RGBA8), - custom_tex_info.width, custom_tex_info.height); + 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(); } @@ -910,11 +930,9 @@ void CachedSurface::DownloadGLTexture(const Common::Rectangle& rect, GLuint scaled_rect.right *= res_scale; scaled_rect.bottom *= res_scale; - OGLTexture unscaled_tex; - unscaled_tex.Create(); - Common::Rectangle unscaled_tex_rect{0, rect.GetHeight(), rect.GetWidth(), 0}; - AllocateSurfaceTexture(unscaled_tex.handle, tuple, rect.GetWidth(), rect.GetHeight()); + 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); @@ -1734,15 +1752,12 @@ bool RasterizerCacheOpenGL::ValidateByReinterpretation(const Surface& surface, if (!texture_filterer->IsNull() && reinterpret_surface->res_scale == 1 && surface->res_scale == resolution_scale_factor) { // The destination surface is either a framebuffer, or a filtered texture. - OGLTexture tmp_tex; - tmp_tex.Create(); // 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}; - AllocateSurfaceTexture(tmp_tex.handle, - GetFormatTuple(reinterpreter->first.dst_format), - tmp_rect.right, tmp_rect.top); + 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); @@ -1857,9 +1872,9 @@ void RasterizerCacheOpenGL::InvalidateRegion(PAddr addr, u32 size, const Surface cached_surface->invalid_regions.insert(interval); cached_surface->InvalidateAllWatcher(); - // Remove only "empty" fill surfaces to avoid destroying and recreating OGL textures - if (cached_surface->type == SurfaceType::Fill && - cached_surface->IsSurfaceFullyInvalid()) { + // If the surface has no salvageable data it should be removed from the cache to avoid + // clogging the data structure + if (cached_surface->IsSurfaceFullyInvalid()) { remove_surfaces.emplace(cached_surface); } } @@ -1892,12 +1907,11 @@ Surface RasterizerCacheOpenGL::CreateSurface(const SurfaceParams& params) { Surface surface = std::make_shared(*this); static_cast(*surface) = params; - surface->texture.Create(); - - surface->gl_buffer.resize(0); surface->invalid_regions.insert(surface->GetInterval()); - AllocateSurfaceTexture(surface->texture.handle, GetFormatTuple(surface->pixel_format), - surface->GetScaledWidth(), surface->GetScaledHeight()); + + surface->texture = + AllocateSurfaceTexture(GetFormatTuple(surface->pixel_format), surface->GetScaledWidth(), + surface->GetScaledHeight()); return surface; } diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 1a9b6a3b3..1c4ab7174 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -36,6 +36,27 @@ class RasterizerCacheOpenGL; class TextureFilterer; class FormatReinterpreterOpenGL; +struct FormatTuple { + GLint internal_format; + GLenum format; + GLenum type; +}; + +constexpr FormatTuple tex_tuple = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}; + +const FormatTuple& GetFormatTuple(SurfaceParams::PixelFormat pixel_format); + +struct HostTextureTag { + FormatTuple format_tuple; + u32 width; + u32 height; + bool operator==(const HostTextureTag& rhs) const noexcept { + return std::tie(format_tuple.format, format_tuple.internal_format, width, height) == + std::tie(rhs.format_tuple.format, rhs.format_tuple.internal_format, rhs.width, + rhs.height); + }; +}; + struct TextureCubeConfig { PAddr px; PAddr nx; @@ -59,6 +80,18 @@ struct TextureCubeConfig { } // namespace OpenGL namespace std { +template <> +struct hash { + std::size_t operator()(const OpenGL::HostTextureTag& tag) const noexcept { + std::size_t hash = 0; + boost::hash_combine(hash, tag.format_tuple.format); + boost::hash_combine(hash, tag.format_tuple.internal_format); + boost::hash_combine(hash, tag.width); + boost::hash_combine(hash, tag.height); + return hash; + } +}; + template <> struct hash { std::size_t operator()(const OpenGL::TextureCubeConfig& config) const noexcept { @@ -139,6 +172,7 @@ private: 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; @@ -326,17 +360,14 @@ private: std::unordered_map texture_cube_cache; public: + OGLTexture AllocateSurfaceTexture(const FormatTuple& format_tuple, u32 width, u32 height); + + // Textures from destroyed surfaces are stored here to be recyled to reduce allocation overhead + // in the driver + std::unordered_multimap host_texture_recycler; + std::unique_ptr texture_filterer; std::unique_ptr format_reinterpreter; }; -struct FormatTuple { - GLint internal_format; - GLenum format; - GLenum type; -}; - -constexpr FormatTuple tex_tuple = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}; - -const FormatTuple& GetFormatTuple(SurfaceParams::PixelFormat pixel_format); } // namespace OpenGL