diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 2e738694e..5571346f4 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -45,7 +45,6 @@ RasterizerOpenGL::RasterizerOpenGL() // Create cubemap texture and sampler objects texture_cube_sampler.Create(); state.texture_cube_unit.sampler = texture_cube_sampler.sampler.handle; - texture_cube.Create(); // Generate VBO, VAO and UBO vertex_array.Create(); @@ -393,19 +392,18 @@ void RasterizerOpenGL::DrawTriangles() { switch (texture.config.type.Value()) { case TextureType::TextureCube: using CubeFace = Pica::TexturingRegs::CubeFace; - if (res_cache.FillTextureCube( - texture_cube.handle, texture, - regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveX), - regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeX), - regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveY), - regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeY), - regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveZ), - regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeZ))) { - state.texture_cube_unit.texture_cube = texture_cube.handle; - } else { - // Can occur when texture addr is null or its memory is unmapped/invalid - state.texture_cube_unit.texture_cube = 0; - } + TextureCubeConfig config; + config.px = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveX); + config.nx = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeX); + config.py = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveY); + config.ny = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeY); + config.pz = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveZ); + config.nz = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeZ); + config.width = texture.config.width; + config.format = texture.format; + state.texture_cube_unit.texture_cube = + res_cache.GetTextureCube(config).texture.handle; + texture_cube_sampler.SyncWithConfig(texture.config); state.texture_units[texture_index].texture_2d = 0; continue; // Texture unit 0 setup finished. Continue to next unit diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index c02d3ece7..1e996d3e2 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -252,8 +252,6 @@ private: GLint uniform_buffer_alignment; size_t uniform_size_aligned_fs; - // TODO (wwylele): consider caching texture cube in the rasterizer cache - OGLTexture texture_cube; SamplerInfo texture_cube_sampler; OGLBuffer lighting_lut_buffer; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 31787c86c..8def5e208 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -28,7 +28,6 @@ #include "video_core/pica_state.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_state.h" -#include "video_core/texture/texture_decode.h" #include "video_core/utils.h" #include "video_core/video_core.h" @@ -231,6 +230,32 @@ static void AllocateSurfaceTexture(GLuint texture, const FormatTuple& format_tup cur_state.Apply(); } +static void AllocateTextureCube(GLuint texture, const FormatTuple& format_tuple, u32 width) { + OpenGLState cur_state = OpenGLState::GetCurState(); + + // Keep track of previous texture bindings + GLuint old_tex = cur_state.texture_cube_unit.texture_cube; + cur_state.texture_cube_unit.texture_cube = texture; + cur_state.Apply(); + glActiveTexture(TextureUnits::TextureCube.Enum()); + + for (auto faces : { + GL_TEXTURE_CUBE_MAP_POSITIVE_X, + GL_TEXTURE_CUBE_MAP_POSITIVE_Y, + GL_TEXTURE_CUBE_MAP_POSITIVE_Z, + GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, + }) { + glTexImage2D(faces, 0, format_tuple.internal_format, width, width, 0, format_tuple.format, + format_tuple.type, nullptr); + } + + // Restore previous texture bindings + cur_state.texture_cube_unit.texture_cube = old_tex; + cur_state.Apply(); +} + static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle& src_rect, GLuint dst_tex, const MathUtil::Rectangle& dst_rect, SurfaceType type, GLuint read_fb_handle, GLuint draw_fb_handle) { @@ -761,6 +786,8 @@ void CachedSurface::UploadGLTexture(const MathUtil::Rectangle& rect, GLuint BlitTextures(unscaled_tex.handle, {0, rect.GetHeight(), rect.GetWidth(), 0}, texture.handle, scaled_rect, type, read_fb_handle, draw_fb_handle); } + + InvalidateAllWatcher(); } MICROPROFILE_DEFINE(OpenGL_TextureDL, "OpenGL", "Texture Download", MP_RGB(128, 192, 64)); @@ -1193,14 +1220,13 @@ SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams& } Surface RasterizerCacheOpenGL::GetTextureSurface( - const Pica::TexturingRegs::FullTextureConfig& config, PAddr addr_override) { + const Pica::TexturingRegs::FullTextureConfig& config) { Pica::Texture::TextureInfo info = Pica::Texture::TextureInfo::FromPicaRegister(config.config, config.format); + return GetTextureSurface(info); +} - if (addr_override != 0) { - info.physical_address = addr_override; - } - +Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInfo& info) { SurfaceParams params; params.addr = info.physical_address; params.width = info.width; @@ -1228,80 +1254,94 @@ Surface RasterizerCacheOpenGL::GetTextureSurface( return GetSurface(params, ScaleMatch::Ignore, true); } -bool RasterizerCacheOpenGL::FillTextureCube(GLuint dest_handle, - const Pica::TexturingRegs::FullTextureConfig& config, - PAddr px, PAddr nx, PAddr py, PAddr ny, PAddr pz, - PAddr nz) { - ASSERT(config.config.width == config.config.height); - struct FaceTuple { +const CachedTextureCube& RasterizerCacheOpenGL::GetTextureCube(const TextureCubeConfig& config) { + auto& cube = texture_cube_cache[config]; + + struct Face { + Face(std::shared_ptr& watcher, PAddr address, GLenum gl_face) + : watcher(watcher), address(address), gl_face(gl_face) {} + std::shared_ptr& watcher; PAddr address; GLenum gl_face; - Surface surface; }; - std::array faces{{ - {px, GL_TEXTURE_CUBE_MAP_POSITIVE_X}, - {nx, GL_TEXTURE_CUBE_MAP_NEGATIVE_X}, - {py, GL_TEXTURE_CUBE_MAP_POSITIVE_Y}, - {ny, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y}, - {pz, GL_TEXTURE_CUBE_MAP_POSITIVE_Z}, - {nz, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z}, + + const std::array faces{{ + {cube.px, config.px, GL_TEXTURE_CUBE_MAP_POSITIVE_X}, + {cube.nx, config.nx, GL_TEXTURE_CUBE_MAP_NEGATIVE_X}, + {cube.py, config.py, GL_TEXTURE_CUBE_MAP_POSITIVE_Y}, + {cube.ny, config.ny, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y}, + {cube.pz, config.pz, GL_TEXTURE_CUBE_MAP_POSITIVE_Z}, + {cube.nz, config.nz, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z}, }}; - u16 res_scale = 1; - for (auto& face : faces) { - face.surface = GetTextureSurface(config, face.address); - if (face.surface == nullptr) - return false; - res_scale = std::max(res_scale, face.surface->res_scale); + for (const Face& face : faces) { + if (!face.watcher || !face.watcher->Get()) { + Pica::Texture::TextureInfo info; + info.physical_address = face.address; + info.height = info.width = config.width; + info.format = config.format; + info.SetDefaultStride(); + auto surface = GetTextureSurface(info); + if (surface) { + face.watcher = surface->CreateWatcher(); + } else { + // Can occur when texture address is invalid. We mark the watcher with nullptr in + // this case and the content of the face wouldn't get updated. These are usually + // leftover setup in the texture unit and games are not supposed to draw using them. + face.watcher = nullptr; + } + } } - u32 scaled_size = res_scale * config.config.width; + if (cube.texture.handle == 0) { + for (const Face& face : faces) { + if (face.watcher) { + auto surface = face.watcher->Get(); + cube.res_scale = std::max(cube.res_scale, surface->res_scale); + } + } + + cube.texture.Create(); + AllocateTextureCube( + cube.texture.handle, + GetFormatTuple(CachedSurface::PixelFormatFromTextureFormat(config.format)), + cube.res_scale * config.width); + } + + u32 scaled_size = cube.res_scale * config.width; OpenGLState state = OpenGLState::GetCurState(); OpenGLState prev_state = state; SCOPE_EXIT({ prev_state.Apply(); }); - state.texture_cube_unit.texture_cube = dest_handle; - state.Apply(); - glActiveTexture(TextureUnits::TextureCube.Enum()); - FormatTuple format_tuple = GetFormatTuple(faces[0].surface->pixel_format); + state.draw.read_framebuffer = read_framebuffer.handle; + state.draw.draw_framebuffer = draw_framebuffer.handle; + state.ResetTexture(cube.texture.handle); - GLint cur_size, cur_format; - glGetTexLevelParameteriv(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_TEXTURE_WIDTH, &cur_size); - glGetTexLevelParameteriv(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_TEXTURE_INTERNAL_FORMAT, - &cur_format); + for (const Face& face : faces) { + if (face.watcher && !face.watcher->IsValid()) { + auto surface = face.watcher->Get(); + 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); - if (cur_size != scaled_size || cur_format != format_tuple.internal_format) { - for (auto& face : faces) { - glTexImage2D(face.gl_face, 0, format_tuple.internal_format, scaled_size, scaled_size, 0, - format_tuple.format, format_tuple.type, nullptr); + 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); + + 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(); } } - state.draw.read_framebuffer = read_framebuffer.handle; - state.draw.draw_framebuffer = draw_framebuffer.handle; - state.ResetTexture(dest_handle); - - for (auto& face : faces) { - state.ResetTexture(face.surface->texture.handle); - state.Apply(); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - face.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, dest_handle, - 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); - - auto src_rect = face.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); - } - - return true; + return cube; } SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces( @@ -1316,6 +1356,7 @@ SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces( FlushAll(); while (!surface_cache.empty()) UnregisterSurface(*surface_cache.begin()->second.begin()); + texture_cube_cache.clear(); } MathUtil::Rectangle viewport_clamped{ diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 2267af25c..519395fb6 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -17,6 +18,8 @@ #ifdef __GNUC__ #pragma GCC diagnostic pop #endif +#include +#include #include #include "common/assert.h" #include "common/common_funcs.h" @@ -26,6 +29,7 @@ #include "video_core/regs_framebuffer.h" #include "video_core/regs_texturing.h" #include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/texture/texture_decode.h" struct CachedSurface; using Surface = std::shared_ptr; @@ -253,7 +257,41 @@ struct SurfaceParams { SurfaceType type = SurfaceType::Invalid; }; -struct CachedSurface : SurfaceParams { +/** + * 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; +}; + +struct CachedSurface : SurfaceParams, std::enable_shared_from_this { bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const; bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const; @@ -294,6 +332,72 @@ struct CachedSurface : SurfaceParams { GLuint draw_fb_handle); void DownloadGLTexture(const MathUtil::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; + } + } + } + +private: + std::list> watchers; +}; + +struct TextureCubeConfig { + PAddr px; + PAddr nx; + PAddr py; + PAddr ny; + PAddr pz; + PAddr nz; + u32 width; + Pica::TexturingRegs::TextureFormat format; + + bool operator==(const TextureCubeConfig& rhs) const { + return std::tie(px, nx, py, ny, pz, nz, width, format) == + std::tie(rhs.px, rhs.nx, rhs.py, rhs.ny, rhs.pz, rhs.nz, rhs.width, rhs.format); + } + + bool operator!=(const TextureCubeConfig& rhs) const { + return !(*this == rhs); + } +}; + +namespace std { +template <> +struct hash { + size_t operator()(const TextureCubeConfig& config) const { + std::size_t hash = 0; + boost::hash_combine(hash, config.px); + boost::hash_combine(hash, config.nx); + boost::hash_combine(hash, config.py); + boost::hash_combine(hash, config.ny); + boost::hash_combine(hash, config.pz); + boost::hash_combine(hash, config.nz); + boost::hash_combine(hash, config.width); + boost::hash_combine(hash, static_cast(config.format)); + return hash; + } +}; +} // namespace std + +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 RasterizerCacheOpenGL : NonCopyable { @@ -322,12 +426,11 @@ public: bool load_if_create); /// Get a surface based on the texture configuration - Surface GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config, - PAddr addr_override = 0); + Surface GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config); + Surface GetTextureSurface(const Pica::Texture::TextureInfo& info); - /// Copy surfaces to a cubemap texture based on the texture configuration - bool FillTextureCube(GLuint dest_handle, const Pica::TexturingRegs::FullTextureConfig& config, - PAddr px, PAddr nx, PAddr py, PAddr ny, PAddr pz, PAddr nz); + /// Get a texture cube based on the texture configuration + const CachedTextureCube& GetTextureCube(const TextureCubeConfig& config); /// Get the color and depth surfaces based on the framebuffer configuration SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb, @@ -380,4 +483,6 @@ private: OGLProgram d24s8_abgr_shader; GLint d24s8_abgr_tbo_size_u_id; GLint d24s8_abgr_viewport_u_id; + + std::unordered_map texture_cube_cache; };