// Copyright 2010 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include #include #include #include #include #include #include #include #include #include #include "Common/BitSet.h" #include "Common/CommonTypes.h" #include "Common/Flag.h" #include "Common/MathUtil.h" #include "VideoCommon/AbstractTexture.h" #include "VideoCommon/Assets/CustomAsset.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/HiresTextures.h" #include "VideoCommon/TextureConfig.h" #include "VideoCommon/TextureDecoder.h" #include "VideoCommon/TextureInfo.h" #include "VideoCommon/TextureUtils.h" #include "VideoCommon/VideoEvents.h" class AbstractFramebuffer; class AbstractStagingTexture; class PointerWrap; struct SamplerState; struct VideoConfig; namespace VideoCommon { class CustomTextureData; class GameTextureAsset; } // namespace VideoCommon constexpr std::string_view EFB_DUMP_PREFIX = "efb1"; constexpr std::string_view XFB_DUMP_PREFIX = "xfb1"; static constexpr int FRAMECOUNT_INVALID = 0; struct TextureAndTLUTFormat { TextureAndTLUTFormat(TextureFormat texfmt_ = TextureFormat::I4, TLUTFormat tlutfmt_ = TLUTFormat::IA8) : texfmt(texfmt_), tlutfmt(tlutfmt_) { } bool operator==(const TextureAndTLUTFormat& other) const { if (IsColorIndexed(texfmt)) return texfmt == other.texfmt && tlutfmt == other.tlutfmt; return texfmt == other.texfmt; } TextureFormat texfmt; TLUTFormat tlutfmt; }; struct EFBCopyParams { EFBCopyParams(PixelFormat efb_format_, EFBCopyFormat copy_format_, bool depth_, bool yuv_, bool all_copy_filter_coefs_needed_, bool copy_filter_can_overflow_, bool apply_gamma_) : efb_format(efb_format_), copy_format(copy_format_), depth(depth_), yuv(yuv_), all_copy_filter_coefs_needed(all_copy_filter_coefs_needed_), copy_filter_can_overflow(copy_filter_can_overflow_), apply_gamma(apply_gamma_) { } bool operator<(const EFBCopyParams& rhs) const { return std::tie(efb_format, copy_format, depth, yuv, all_copy_filter_coefs_needed, copy_filter_can_overflow, apply_gamma) < std::tie(rhs.efb_format, rhs.copy_format, rhs.depth, rhs.yuv, rhs.all_copy_filter_coefs_needed, rhs.copy_filter_can_overflow, rhs.apply_gamma); } PixelFormat efb_format; EFBCopyFormat copy_format; bool depth; bool yuv; bool all_copy_filter_coefs_needed; bool copy_filter_can_overflow; bool apply_gamma; }; template <> struct fmt::formatter { constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } template auto format(const EFBCopyParams& uid, FormatContext& ctx) const { std::string copy_format; if (uid.copy_format == EFBCopyFormat::XFB) copy_format = "XFB"; else copy_format = fmt::to_string(uid.copy_format); return fmt::format_to(ctx.out(), "format: {}, copy format: {}, depth: {}, yuv: {}, apply_gamma: {}, " "all_copy_filter_coefs_needed: {}, copy_filter_can_overflow: {}", uid.efb_format, copy_format, uid.depth, uid.yuv, uid.apply_gamma, uid.all_copy_filter_coefs_needed, uid.copy_filter_can_overflow); } }; class RcTcacheEntry; struct TexIndexNode; struct TCacheEntry { // common members /* owned: deleted with the entry unless handed back to the pool */ AbstractTexture* texture = nullptr; AbstractFramebuffer* framebuffer = nullptr; u32 addr = 0; u32 size_in_bytes = 0; u64 base_hash = 0; u64 hash = 0; // for paletted textures, hash = base_hash ^ palette_hash TextureAndTLUTFormat format; u32 memory_stride = 0; bool is_efb_copy = false; bool is_custom_tex = false; bool may_have_overlapping_textures = true; // indicates that the mips in this texture are arbitrary content, aren't just downscaled bool has_arbitrary_mips = false; bool should_force_safe_hashing = false; // for XFB bool is_xfb_copy = false; bool is_xfb_container = false; u64 id = 0; u32 content_semaphore = 0; // Counts up u32 refs = 0; // the RcTcacheEntry handles naming it // Indicates that this TCacheEntry has been invalided from m_textures_by_address bool invalidated = false; bool reference_changed = false; // used by xfb to determine when a reference xfb changed /* a copy a state brought back, whose hashes may be common_hash64_v1's */ bool hash_from_state = false; // Texture dimensions from the GameCube's point of view u32 native_width = 0; u32 native_height = 0; u32 native_levels = 0; // used to delete textures which haven't been used for TEXTURE_KILL_THRESHOLD frames int frameCount = FRAMECOUNT_INVALID; // Its node in m_textures_by_hash, if it is there, so it does not need to be searched when // removing the cache entry TexIndexNode* textures_by_hash_iter = nullptr; // This is used to keep track of both: // * efb copies used by this partially updated texture // * partially updated textures which refer to this efb copy // A few in place, more on the heap; each one once. enum { INLINE_REFERENCES = 4 }; TCacheEntry* inline_references[INLINE_REFERENCES]; TCacheEntry** references = inline_references; u32 num_references = 0; u32 references_cap = INLINE_REFERENCES; // Pending EFB copy AbstractStagingTexture* pending_efb_copy = nullptr; // owned u32 pending_efb_copy_width = 0; u32 pending_efb_copy_height = 0; std::string texture_info_name = ""; VideoCommon::CustomAsset::TimeType last_load_time; std::shared_ptr hires_texture; TCacheEntry(AbstractTexture* tex, AbstractFramebuffer* fb); ~TCacheEntry(); void SetGeneralParameters(u32 _addr, u32 _size, TextureAndTLUTFormat _format, bool force_safe_hashing) { addr = _addr; size_in_bytes = _size; format = _format; should_force_safe_hashing = force_safe_hashing; } void SetDimensions(unsigned int _native_width, unsigned int _native_height, unsigned int _native_levels) { native_width = _native_width; native_height = _native_height; native_levels = _native_levels; memory_stride = _native_width; } void SetHashes(u64 _base_hash, u64 _hash) { base_hash = _base_hash; hash = _hash; hash_from_state = false; } bool HasReference(const TCacheEntry* other_entry) const; void AddReference(TCacheEntry* other_entry); void RemoveReference(const TCacheEntry* other_entry); // This texture entry is used by the other entry as a sub-texture void CreateReference(TCacheEntry* other_entry) { // References are two-way, so they can easily be destroyed later this->AddReference(other_entry); other_entry->AddReference(this); } // Acquiring a content lock will lock the current contents and prevent texture cache from // reusing the same entry for a newer version of the texture. void AcquireContentLock() { content_semaphore++; } void ReleaseContentLock() { content_semaphore--; } // Can this be mutated? bool IsLocked() const { return content_semaphore > 0; } void SetXfbCopy(u32 stride); void SetEfbCopy(u32 stride); void SetNotCopy(); bool OverlapsMemoryRange(u32 range_address, u32 range_size) const; bool IsEfbCopy() const { return is_efb_copy; } bool IsCopy() const { return is_xfb_copy || is_efb_copy; } u32 NumBlocksX() const; u32 NumBlocksY() const; u32 BytesPerRow() const; u64 CalculateHash() const; /* Whether the memory still hashes to base_hash (base) or hash. */ bool MemoryUnchanged(bool base); int HashSampleSize() const; u32 GetWidth() const { return texture->GetConfig().width; } u32 GetHeight() const { return texture->GetConfig().height; } u32 GetNumLevels() const { return texture->GetConfig().levels; } u32 GetNumLayers() const { return texture->GetConfig().layers; } AbstractTextureFormat GetFormat() const { return texture->GetConfig().format; } void DoState(PointerWrap& p); }; /* Cache entries come from a pool kept for the core's life and are handed * back to it when the last handle goes: none is allocated once the pool * has grown to what a game keeps. Video thread only. */ /* Takes tex and fb, deleting them when out of memory. */ TCacheEntry* TCacheEntryNew(AbstractTexture* tex, AbstractFramebuffer* fb); void TCacheEntryRelease(TCacheEntry* entry); /* Frees the pool's memory when no entry is alive. */ void TCacheEntryPoolFree(); /* A counted handle to a cache entry. */ class RcTcacheEntry { public: RcTcacheEntry() : m_entry(nullptr) {} RcTcacheEntry(std::nullptr_t) : m_entry(nullptr) {} explicit RcTcacheEntry(TCacheEntry* entry) : m_entry(entry) { if (entry) entry->refs++; } RcTcacheEntry(const RcTcacheEntry& other) : m_entry(other.m_entry) { if (m_entry) m_entry->refs++; } RcTcacheEntry(RcTcacheEntry&& other) noexcept : m_entry(other.m_entry) { other.m_entry = nullptr; } ~RcTcacheEntry() { reset(); } RcTcacheEntry& operator=(const RcTcacheEntry& other) { if (other.m_entry) other.m_entry->refs++; reset(); m_entry = other.m_entry; return *this; } RcTcacheEntry& operator=(RcTcacheEntry&& other) noexcept { if (this != &other) { reset(); m_entry = other.m_entry; other.m_entry = nullptr; } return *this; } void reset() { TCacheEntry* const entry = m_entry; m_entry = nullptr; if (entry && --entry->refs == 0) TCacheEntryRelease(entry); } TCacheEntry* get() const { return m_entry; } TCacheEntry* operator->() const { return m_entry; } TCacheEntry& operator*() const { return *m_entry; } explicit operator bool() const { return m_entry != nullptr; } bool operator==(const RcTcacheEntry& other) const { return m_entry == other.m_entry; } bool operator!=(const RcTcacheEntry& other) const { return m_entry != other.m_entry; } bool operator==(std::nullptr_t) const { return m_entry == nullptr; } bool operator!=(std::nullptr_t) const { return m_entry != nullptr; } private: TCacheEntry* m_entry; }; /* An ordered index of cache entries by address or by hash, kept as a * multimap keeps one: equal keys in the order they went in, and a node * staying where it is while others come and go. Its nodes come from a * pool kept with it. Video thread only. */ struct TexIndexNode { TexIndexNode* left; TexIndexNode* right; TexIndexNode* parent; u64 key; u32 prio; RcTcacheEntry value; }; struct TexIndexChunk; struct TexIndex { TexIndexNode* root = nullptr; TexIndexNode* free_nodes = nullptr; TexIndexChunk* chunks = nullptr; size_t count = 0; u32 seed = 0x9e3779b9u; }; /* After those with an equal key; NULL out of memory. */ TexIndexNode* TexIndexInsert(TexIndex* index, u64 key, const RcTcacheEntry& value); /* Returns the node after it. */ TexIndexNode* TexIndexErase(TexIndex* index, TexIndexNode* node); TexIndexNode* TexIndexFirst(const TexIndex* index); TexIndexNode* TexIndexNext(const TexIndexNode* node); /* The first node with a key not less than / greater than key. */ TexIndexNode* TexIndexLowerBound(const TexIndex* index, u64 key); TexIndexNode* TexIndexUpperBound(const TexIndex* index, u64 key); /* Drops every entry; the nodes are kept for reuse. */ void TexIndexClear(TexIndex* index); void TexIndexFree(TexIndex* index); class TextureCacheBase final { public: // A texture and its framebuffer, if it has one, handed from the pool or newly made; a null // texture for none. struct TexPoolEntry { AbstractTexture* texture; AbstractFramebuffer* framebuffer; int frameCount; }; struct TextureCreationInfo { u64 base_hash; u64 full_hash; u32 bytes_per_block; u32 palette_size; }; TextureCacheBase(); ~TextureCacheBase(); bool Initialize(); void Shutdown(); void OnConfigChanged(const VideoConfig& config); // Removes textures which aren't used for more than TEXTURE_KILL_THRESHOLD frames, // frameCount is the current frame number. void Cleanup(int _frameCount); void Invalidate(); void ReleaseToPool(TCacheEntry* entry); TCacheEntry* Load(u32 stage); RcTcacheEntry GetTexture(const int textureCacheSafetyColorSampleSize, const TextureInfo& texture_info); RcTcacheEntry GetXFBTexture(u32 address, u32 width, u32 height, u32 stride, MathUtil::Rectangle* display_rect); void BindTextures(BitSet32 used_textures, const std::array& samplers); void CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstFormat, u32 width, u32 height, u32 dstStride, bool is_depth_copy, const MathUtil::Rectangle& srcRect, bool isIntensity, bool scaleByHalf, float y_scale, float gamma, bool clamp_top, bool clamp_bottom, const CopyFilterCoefficients::Values& filter_coefficients); void ScaleTextureCacheEntryTo(RcTcacheEntry& entry, u32 new_width, u32 new_height); // Flushes all pending EFB copies to emulated RAM. void FlushEFBCopies(); // Flush any Bound textures that can't be reused void FlushStaleBinds(); // Texture Serialization void SerializeTexture(AbstractTexture* tex, const TextureConfig& config, PointerWrap& p); TexPoolEntry DeserializeTexture(PointerWrap& p); // Save States void DoState(PointerWrap& p); static bool AllCopyFilterCoefsNeeded(const std::array& coefficients); static bool CopyFilterCanOverflow(const std::array& coefficients); // Get a new sampler state static SamplerState GetSamplerState(u32 index, float custom_tex_scale, bool custom_tex, bool has_arbitrary_mips); static void RenderPaletteEntry(u32 texel_buffer_offset, const RcTcacheEntry& entry, AbstractTexture* texture, TLUTFormat tlutfmt); static void RenderReinterpretEntry(const RcTcacheEntry& entry, AbstractTexture* texture, TextureFormat old_format, TextureFormat new_format); protected: // Decodes the specified data to the GPU texture specified by entry. // Returns false if the configuration is not supported. // width, height are the size of the image in pixels. // aligned_width, aligned_height are the size of the image in pixels, aligned to the block size. // row_stride is the number of bytes for a row of blocks, not pixels. bool DecodeTextureOnGPU(RcTcacheEntry& entry, u32 dst_level, const u8* data, u32 data_size, TextureFormat format, u32 width, u32 height, u32 aligned_width, u32 aligned_height, u32 row_stride, const u8* palette, TLUTFormat palette_format); void CopyEFB(AbstractStagingTexture* dst, const EFBCopyParams& params, u32 native_width, u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride, const MathUtil::Rectangle& src_rect, bool scale_by_half, bool linear_filter, float y_scale, float gamma, bool clamp_top, bool clamp_bottom, const std::array& filter_coefficients); void CopyEFBToCacheEntry(RcTcacheEntry& entry, bool is_depth_copy, const MathUtil::Rectangle& src_rect, bool scale_by_half, bool linear_filter, EFBCopyFormat dst_format, bool is_intensity, float gamma, bool clamp_top, bool clamp_bottom, const std::array& filter_coefficients); alignas(16) u8* m_temp = nullptr; size_t m_temp_size = 0; private: // the nodes from one to before another, as an equal_range gives them struct TexIndexRange { TexIndexNode* first; TexIndexNode* second; }; // A texture kept for reuse: by its config, packed into id to compare first struct TexPoolSlot { u64 id; TextureConfig config; AbstractTexture* texture; AbstractFramebuffer* framebuffer; size_t bytes; /* what the texture holds, roughly: what the pool's budget counts */ int frameCount; }; static bool DidLinkedAssetsChange(const TCacheEntry& entry); TCacheEntry* LoadImpl(u32 stage, bool force_reload); bool CreateUtilityTextures(); void SetBackupConfig(const VideoConfig& config); RcTcacheEntry CreateTextureEntry(const TextureCreationInfo& creation_info, const TextureInfo& texture_info, int safety_color_sample_size, VideoCommon::CustomTextureData* custom_texture_data, bool custom_arbitrary_mipmaps, bool skip_texture_dump); RcTcacheEntry GetXFBFromCache(u32 address, u32 width, u32 height, u32 stride); std::pair CreatePaletteEntryWithOffset(const RcTcacheEntry& entry, const u8* palette); RcTcacheEntry CreateReinterpretEntry(const RcTcacheEntry& existing_entry, TextureFormat new_format); RcTcacheEntry DoPartialTextureUpdates(RcTcacheEntry& entry_to_update, const u8* palette, TLUTFormat tlutfmt); void StitchXFBCopy(RcTcacheEntry& entry_to_update); void CheckTempSize(size_t required_size); RcTcacheEntry AllocateCacheEntry(const TextureConfig& config); TexPoolEntry AllocateTexture(const TextureConfig& config); // The slot of a pooled texture to reuse for config, or m_texture_pool_count. size_t FindMatchingTextureFromPool(const TextureConfig& config) const; // Takes the texture and framebuffer. void PoolTexture(AbstractTexture* texture, AbstractFramebuffer* framebuffer); // Deletes the slot's texture; the last slot takes its place. void DropPooledTexture(size_t slot); TexIndexNode* GetTexCacheIter(TCacheEntry* entry); // Return all possible overlapping textures. As addr+size of the textures is not // indexed, this may return false positives. TexIndexRange FindOverlappingTextures(u32 addr, u32 size_in_bytes); // Removes and unlinks texture from texture cache and returns it to the pool; the node after it TexIndexNode* InvalidateTexture(TexIndexNode* node, bool discard_pending_efb_copy = false); void UninitializeEFBMemory(u8* dst, u32 stride, u32 bytes_per_row, u32 num_blocks_y); void UninitializeXFBMemory(u8* dst, u32 stride, u32 bytes_per_row, u32 num_blocks_y); /* The hash of a copy's memory just filled with the uninitialized pattern * (UninitializeXFBMemory / UninitializeEFBMemory): the same bytes for the * same shape every time, so hashed once per shape. */ u64 FilledPatternHash(const TCacheEntry& entry, bool xfb); struct PatternHash { u32 bytes_per_row; u32 stride; u32 rows; u32 size; u32 samples; bool xfb; u64 hash; }; static constexpr u32 NUM_PATTERN_HASHES = 4; PatternHash m_pattern_hashes[NUM_PATTERN_HASHES] = {}; u32 m_num_pattern_hashes = 0; u32 m_next_pattern_hash = 0; // Precomputing the coefficients for the previous, current, and next lines for the copy filter. static std::array GetRAMCopyFilterCoefficients(const CopyFilterCoefficients::Values& coefficients); static std::array GetVRAMCopyFilterCoefficients(const CopyFilterCoefficients::Values& coefficients); // Flushes a pending EFB copy to RAM from the host to the guest RAM. void WriteEFBCopyToRAM(u8* dst_ptr, u32 width, u32 height, u32 stride, AbstractStagingTexture* staging_texture); void FlushEFBCopy(TCacheEntry* entry); // Returns a staging texture of the maximum EFB copy size. AbstractStagingTexture* GetEFBCopyStagingTexture(); // Returns an EFB copy staging texture to the pool, so it can be re-used. void ReleaseEFBCopyStagingTexture(AbstractStagingTexture* tex); bool CheckReadbackTexture(u32 width, u32 height, AbstractTextureFormat format); void DoSaveState(PointerWrap& p); void DoLoadState(PointerWrap& p); // m_textures_by_address is the authoritive version of what's actually "in" the texture cache // but it's possible for invalidated TCache entries to live on elsewhere TexIndex m_textures_by_address; // m_textures_by_hash is an alternative view of the texture cache // All textures in here will also be in m_textures_by_address TexIndex m_textures_by_hash; // m_bound_textures are actually active in the current draw // It's valid for textures to be in here after they've been invalidated std::array m_bound_textures{}; // the overlapping copies StitchXFBCopy draws, kept for the next call TCacheEntry** m_stitch_candidates = nullptr; size_t m_stitch_candidates_cap = 0; TexPoolSlot* m_texture_pool = nullptr; size_t m_texture_pool_count = 0; size_t m_texture_pool_cap = 0; size_t m_texture_pool_bytes = 0; u64 m_last_entry_id = 0; // Backup configuration values struct BackupConfig { int color_samples; bool texfmt_overlay; bool texfmt_overlay_center; bool hires_textures; bool cache_hires_textures; bool copy_cache_enable; bool stereo_3d; bool efb_mono_depth; bool gpu_texture_decoding; bool disable_vram_copies; bool arbitrary_mipmap_detection; bool graphics_mods; u32 graphics_mod_change_count; }; BackupConfig m_backup_config = {}; // Encoding texture used for EFB copies to RAM. AbstractTexture* m_efb_encoding_texture = nullptr; AbstractFramebuffer* m_efb_encoding_framebuffer = nullptr; // Decoding texture used for GPU texture decoding. AbstractTexture* m_decoding_texture = nullptr; // Pool of readback textures used for deferred EFB copies. AbstractStagingTexture** m_efb_copy_staging_texture_pool = nullptr; size_t m_num_efb_copy_staging_textures = 0; size_t m_efb_copy_staging_texture_pool_cap = 0; // List of pending EFB copies. It is important that the order is preserved for these, // so that overlapping textures are written to guest RAM in the order they are issued. // It's valid for textures to live be in here after they've been invalidated // EFB copies waiting for their RAM writes, in order, each counted as a handle would TCacheEntry** m_pending_efb_copies = nullptr; size_t m_num_pending_efb_copies = 0; size_t m_pending_efb_copies_cap = 0; void PushPendingEFBCopy(TCacheEntry* entry); void ClearPendingEFBCopies(); // Staging texture used for readbacks. // We store this in the class so that the same staging texture can be used for multiple // readbacks, saving the overhead of allocating a new buffer every time. AbstractStagingTexture* m_readback_texture = nullptr; void OnFrameEnd(); Common::EventHook m_frame_event = GetVideoEvents().after_frame_event.Register([this](Core::System&) { OnFrameEnd(); }); VideoCommon::TextureUtils::TextureDumper m_texture_dumper; }; extern TextureCacheBase* g_texture_cache;