// Copyright 2019 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include "common/alignment.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/logging/log.h" #include "video_core/compatible_formats.h" #include "video_core/delayed_destruction_ring.h" #include "video_core/dirty_flags.h" #include "video_core/engines/fermi_2d.h" #include "video_core/engines/kepler_compute.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/memory_manager.h" #include "video_core/rasterizer_interface.h" #include "video_core/surface.h" #include "video_core/texture_cache/descriptor_table.h" #include "video_core/texture_cache/format_lookup_table.h" #include "video_core/texture_cache/formatter.h" #include "video_core/texture_cache/image_base.h" #include "video_core/texture_cache/image_info.h" #include "video_core/texture_cache/image_view_base.h" #include "video_core/texture_cache/image_view_info.h" #include "video_core/texture_cache/render_targets.h" #include "video_core/texture_cache/samples_helper.h" #include "video_core/texture_cache/slot_vector.h" #include "video_core/texture_cache/types.h" #include "video_core/texture_cache/util.h" #include "video_core/textures/texture.h" namespace VideoCommon { using Tegra::Texture::SwizzleSource; using Tegra::Texture::TextureType; using Tegra::Texture::TICEntry; using Tegra::Texture::TSCEntry; using VideoCore::Surface::GetFormatType; using VideoCore::Surface::IsCopyCompatible; using VideoCore::Surface::PixelFormat; using VideoCore::Surface::PixelFormatFromDepthFormat; using VideoCore::Surface::PixelFormatFromRenderTargetFormat; using VideoCore::Surface::SurfaceType; template class TextureCache { /// Address shift for caching images into a hash table static constexpr u64 PAGE_BITS = 20; /// Enables debugging features to the texture cache static constexpr bool ENABLE_VALIDATION = P::ENABLE_VALIDATION; /// Implement blits as copies between framebuffers static constexpr bool FRAMEBUFFER_BLITS = P::FRAMEBUFFER_BLITS; /// True when some copies have to be emulated static constexpr bool HAS_EMULATED_COPIES = P::HAS_EMULATED_COPIES; /// Image view ID for null descriptors static constexpr ImageViewId NULL_IMAGE_VIEW_ID{0}; /// Sampler ID for bugged sampler ids static constexpr SamplerId NULL_SAMPLER_ID{0}; using Runtime = typename P::Runtime; using Image = typename P::Image; using ImageAlloc = typename P::ImageAlloc; using ImageView = typename P::ImageView; using Sampler = typename P::Sampler; using Framebuffer = typename P::Framebuffer; struct BlitImages { ImageId dst_id; ImageId src_id; PixelFormat dst_format; PixelFormat src_format; }; template struct IdentityHash { [[nodiscard]] size_t operator()(T value) const noexcept { return static_cast(value); } }; public: explicit TextureCache(Runtime&, VideoCore::RasterizerInterface&, Tegra::Engines::Maxwell3D&, Tegra::Engines::KeplerCompute&, Tegra::MemoryManager&); /// Notify the cache that a new frame has been queued void TickFrame(); /// Return a constant reference to the given image view id [[nodiscard]] const ImageView& GetImageView(ImageViewId id) const noexcept; /// Return a reference to the given image view id [[nodiscard]] ImageView& GetImageView(ImageViewId id) noexcept; /// Fill image_view_ids with the graphics images in indices void FillGraphicsImageViews(std::span indices, std::span image_view_ids); /// Fill image_view_ids with the compute images in indices void FillComputeImageViews(std::span indices, std::span image_view_ids); /// Get the sampler from the graphics descriptor table in the specified index Sampler* GetGraphicsSampler(u32 index); /// Get the sampler from the compute descriptor table in the specified index Sampler* GetComputeSampler(u32 index); /// Refresh the state for graphics image view and sampler descriptors void SynchronizeGraphicsDescriptors(); /// Refresh the state for compute image view and sampler descriptors void SynchronizeComputeDescriptors(); /// Update bound render targets and upload memory if necessary /// @param is_clear True when the render targets are being used for clears void UpdateRenderTargets(bool is_clear); /// Find a framebuffer with the currently bound render targets /// UpdateRenderTargets should be called before this Framebuffer* GetFramebuffer(); /// Mark images in a range as modified from the CPU void WriteMemory(VAddr cpu_addr, size_t size); /// Download contents of host images to guest memory in a region void DownloadMemory(VAddr cpu_addr, size_t size); /// Remove images in a region void UnmapMemory(VAddr cpu_addr, size_t size); /// Blit an image with the given parameters void BlitImage(const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Surface& src, const Tegra::Engines::Fermi2D::Config& copy); /// Invalidate the contents of the color buffer index /// These contents become unspecified, the cache can assume aggressive optimizations. void InvalidateColorBuffer(size_t index); /// Invalidate the contents of the depth buffer /// These contents become unspecified, the cache can assume aggressive optimizations. void InvalidateDepthBuffer(); /// Try to find a cached image view in the given CPU address [[nodiscard]] ImageView* TryFindFramebufferImageView(VAddr cpu_addr); /// Return true when there are uncommitted images to be downloaded [[nodiscard]] bool HasUncommittedFlushes() const noexcept; /// Return true when the caller should wait for async downloads [[nodiscard]] bool ShouldWaitAsyncFlushes() const noexcept; /// Commit asynchronous downloads void CommitAsyncFlushes(); /// Pop asynchronous downloads void PopAsyncFlushes(); /// Return true when a CPU region is modified from the GPU [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size); std::mutex mutex; private: /// Iterate over all page indices in a range template static void ForEachPage(VAddr addr, size_t size, Func&& func) { static constexpr bool RETURNS_BOOL = std::is_same_v, bool>; const u64 page_end = (addr + size - 1) >> PAGE_BITS; for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) { if constexpr (RETURNS_BOOL) { if (func(page)) { break; } } else { func(page); } } } /// Fills image_view_ids in the image views in indices void FillImageViews(DescriptorTable& table, std::span cached_image_view_ids, std::span indices, std::span image_view_ids); /// Find or create an image view in the guest descriptor table ImageViewId VisitImageView(DescriptorTable& table, std::span cached_image_view_ids, u32 index); /// Find or create a framebuffer with the given render target parameters FramebufferId GetFramebufferId(const RenderTargets& key); /// Refresh the contents (pixel data) of an image void RefreshContents(Image& image); /// Upload data from guest to an image template void UploadImageContents(Image& image, StagingBuffer& staging_buffer); /// Find or create an image view from a guest descriptor [[nodiscard]] ImageViewId FindImageView(const TICEntry& config); /// Create a new image view from a guest descriptor [[nodiscard]] ImageViewId CreateImageView(const TICEntry& config); /// Find or create an image from the given parameters [[nodiscard]] ImageId FindOrInsertImage(const ImageInfo& info, GPUVAddr gpu_addr, RelaxedOptions options = RelaxedOptions{}); /// Find an image from the given parameters [[nodiscard]] ImageId FindImage(const ImageInfo& info, GPUVAddr gpu_addr, RelaxedOptions options); /// Create an image from the given parameters [[nodiscard]] ImageId InsertImage(const ImageInfo& info, GPUVAddr gpu_addr, RelaxedOptions options); /// Create a new image and join perfectly matching existing images /// Remove joined images from the cache [[nodiscard]] ImageId JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr); /// Return a blit image pair from the given guest blit parameters [[nodiscard]] BlitImages GetBlitImages(const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Surface& src); /// Find or create a sampler from a guest descriptor sampler [[nodiscard]] SamplerId FindSampler(const TSCEntry& config); /// Find or create an image view for the given color buffer index [[nodiscard]] ImageViewId FindColorBuffer(size_t index, bool is_clear); /// Find or create an image view for the depth buffer [[nodiscard]] ImageViewId FindDepthBuffer(bool is_clear); /// Find or create a view for a render target with the given image parameters [[nodiscard]] ImageViewId FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr, bool is_clear); /// Iterates over all the images in a region calling func template void ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func); /// Find or create an image view in the given image with the passed parameters [[nodiscard]] ImageViewId FindOrEmplaceImageView(ImageId image_id, const ImageViewInfo& info); /// Register image in the page table void RegisterImage(ImageId image); /// Unregister image from the page table void UnregisterImage(ImageId image); /// Track CPU reads and writes for image void TrackImage(ImageBase& image); /// Stop tracking CPU reads and writes for image void UntrackImage(ImageBase& image); /// Delete image from the cache void DeleteImage(ImageId image); /// Remove image views references from the cache void RemoveImageViewReferences(std::span removed_views); /// Remove framebuffers using the given image views from the cache void RemoveFramebuffers(std::span removed_views); /// Mark an image as modified from the GPU void MarkModification(ImageBase& image) noexcept; /// Synchronize image aliases, copying data if needed void SynchronizeAliases(ImageId image_id); /// Prepare an image to be used void PrepareImage(ImageId image_id, bool is_modification, bool invalidate); /// Prepare an image view to be used void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate); /// Execute copies from one image to the other, even if they are incompatible void CopyImage(ImageId dst_id, ImageId src_id, std::span copies); /// Bind an image view as render target, downloading resources preemtively if needed void BindRenderTarget(ImageViewId* old_id, ImageViewId new_id); /// Create a render target from a given image and image view parameters [[nodiscard]] std::pair RenderTargetFromImage( ImageId, const ImageViewInfo& view_info); /// Returns true if the current clear parameters clear the whole image of a given image view [[nodiscard]] bool IsFullClear(ImageViewId id); Runtime& runtime; VideoCore::RasterizerInterface& rasterizer; Tegra::Engines::Maxwell3D& maxwell3d; Tegra::Engines::KeplerCompute& kepler_compute; Tegra::MemoryManager& gpu_memory; DescriptorTable graphics_image_table{gpu_memory}; DescriptorTable graphics_sampler_table{gpu_memory}; std::vector graphics_sampler_ids; std::vector graphics_image_view_ids; DescriptorTable compute_image_table{gpu_memory}; DescriptorTable compute_sampler_table{gpu_memory}; std::vector compute_sampler_ids; std::vector compute_image_view_ids; RenderTargets render_targets; std::unordered_map image_views; std::unordered_map samplers; std::unordered_map framebuffers; std::unordered_map, IdentityHash> page_table; bool has_deleted_images = false; SlotVector slot_images; SlotVector slot_image_views; SlotVector slot_image_allocs; SlotVector slot_samplers; SlotVector slot_framebuffers; // TODO: This data structure is not optimal and it should be reworked std::vector uncommitted_downloads; std::queue> committed_downloads; static constexpr size_t TICKS_TO_DESTROY = 6; DelayedDestructionRing sentenced_images; DelayedDestructionRing sentenced_image_view; DelayedDestructionRing sentenced_framebuffers; std::unordered_map image_allocs_table; u64 modification_tick = 0; u64 frame_tick = 0; }; template TextureCache

::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface& rasterizer_, Tegra::Engines::Maxwell3D& maxwell3d_, Tegra::Engines::KeplerCompute& kepler_compute_, Tegra::MemoryManager& gpu_memory_) : runtime{runtime_}, rasterizer{rasterizer_}, maxwell3d{maxwell3d_}, kepler_compute{kepler_compute_}, gpu_memory{gpu_memory_} { // Configure null sampler TSCEntry sampler_descriptor{}; sampler_descriptor.min_filter.Assign(Tegra::Texture::TextureFilter::Linear); sampler_descriptor.mag_filter.Assign(Tegra::Texture::TextureFilter::Linear); sampler_descriptor.mipmap_filter.Assign(Tegra::Texture::TextureMipmapFilter::Linear); sampler_descriptor.cubemap_anisotropy.Assign(1); // Make sure the first index is reserved for the null resources // This way the null resource becomes a compile time constant void(slot_image_views.insert(runtime, NullImageParams{})); void(slot_samplers.insert(runtime, sampler_descriptor)); } template void TextureCache

::TickFrame() { // Tick sentenced resources in this order to ensure they are destroyed in the right order sentenced_images.Tick(); sentenced_framebuffers.Tick(); sentenced_image_view.Tick(); ++frame_tick; } template const typename P::ImageView& TextureCache

::GetImageView(ImageViewId id) const noexcept { return slot_image_views[id]; } template typename P::ImageView& TextureCache

::GetImageView(ImageViewId id) noexcept { return slot_image_views[id]; } template void TextureCache

::FillGraphicsImageViews(std::span indices, std::span image_view_ids) { FillImageViews(graphics_image_table, graphics_image_view_ids, indices, image_view_ids); } template void TextureCache

::FillComputeImageViews(std::span indices, std::span image_view_ids) { FillImageViews(compute_image_table, compute_image_view_ids, indices, image_view_ids); } template typename P::Sampler* TextureCache

::GetGraphicsSampler(u32 index) { [[unlikely]] if (index > graphics_sampler_table.Limit()) { LOG_ERROR(HW_GPU, "Invalid sampler index={}", index); return &slot_samplers[NULL_SAMPLER_ID]; } const auto [descriptor, is_new] = graphics_sampler_table.Read(index); SamplerId& id = graphics_sampler_ids[index]; [[unlikely]] if (is_new) { id = FindSampler(descriptor); } return &slot_samplers[id]; } template typename P::Sampler* TextureCache

::GetComputeSampler(u32 index) { [[unlikely]] if (index > compute_sampler_table.Limit()) { LOG_ERROR(HW_GPU, "Invalid sampler index={}", index); return &slot_samplers[NULL_SAMPLER_ID]; } const auto [descriptor, is_new] = compute_sampler_table.Read(index); SamplerId& id = compute_sampler_ids[index]; [[unlikely]] if (is_new) { id = FindSampler(descriptor); } return &slot_samplers[id]; } template void TextureCache

::SynchronizeGraphicsDescriptors() { using SamplerIndex = Tegra::Engines::Maxwell3D::Regs::SamplerIndex; const bool linked_tsc = maxwell3d.regs.sampler_index == SamplerIndex::ViaHeaderIndex; const u32 tic_limit = maxwell3d.regs.tic.limit; const u32 tsc_limit = linked_tsc ? tic_limit : maxwell3d.regs.tsc.limit; if (graphics_sampler_table.Synchornize(maxwell3d.regs.tsc.Address(), tsc_limit)) { graphics_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID); } if (graphics_image_table.Synchornize(maxwell3d.regs.tic.Address(), tic_limit)) { graphics_image_view_ids.resize(tic_limit + 1, CORRUPT_ID); } } template void TextureCache

::SynchronizeComputeDescriptors() { const bool linked_tsc = kepler_compute.launch_description.linked_tsc; const u32 tic_limit = kepler_compute.regs.tic.limit; const u32 tsc_limit = linked_tsc ? tic_limit : kepler_compute.regs.tsc.limit; const GPUVAddr tsc_gpu_addr = kepler_compute.regs.tsc.Address(); if (compute_sampler_table.Synchornize(tsc_gpu_addr, tsc_limit)) { compute_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID); } if (compute_image_table.Synchornize(kepler_compute.regs.tic.Address(), tic_limit)) { compute_image_view_ids.resize(tic_limit + 1, CORRUPT_ID); } } template void TextureCache

::UpdateRenderTargets(bool is_clear) { using namespace VideoCommon::Dirty; auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::RenderTargets]) { return; } flags[Dirty::RenderTargets] = false; // Render target control is used on all render targets, so force look ups when this one is up const bool force = flags[Dirty::RenderTargetControl]; flags[Dirty::RenderTargetControl] = false; for (size_t index = 0; index < NUM_RT; ++index) { ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index]; if (flags[Dirty::ColorBuffer0 + index] || force) { flags[Dirty::ColorBuffer0 + index] = false; BindRenderTarget(&color_buffer_id, FindColorBuffer(index, is_clear)); } PrepareImageView(color_buffer_id, true, is_clear && IsFullClear(color_buffer_id)); } if (flags[Dirty::ZetaBuffer] || force) { flags[Dirty::ZetaBuffer] = false; BindRenderTarget(&render_targets.depth_buffer_id, FindDepthBuffer(is_clear)); } const ImageViewId depth_buffer_id = render_targets.depth_buffer_id; PrepareImageView(depth_buffer_id, true, is_clear && IsFullClear(depth_buffer_id)); for (size_t index = 0; index < NUM_RT; ++index) { render_targets.draw_buffers[index] = static_cast(maxwell3d.regs.rt_control.Map(index)); } render_targets.size = Extent2D{ maxwell3d.regs.render_area.width, maxwell3d.regs.render_area.height, }; } template typename P::Framebuffer* TextureCache

::GetFramebuffer() { return &slot_framebuffers[GetFramebufferId(render_targets)]; } template void TextureCache

::FillImageViews(DescriptorTable& table, std::span cached_image_view_ids, std::span indices, std::span image_view_ids) { ASSERT(indices.size() <= image_view_ids.size()); do { has_deleted_images = false; std::ranges::transform(indices, image_view_ids.begin(), [&](u32 index) { return VisitImageView(table, cached_image_view_ids, index); }); } while (has_deleted_images); } template ImageViewId TextureCache

::VisitImageView(DescriptorTable& table, std::span cached_image_view_ids, u32 index) { if (index > table.Limit()) { LOG_ERROR(HW_GPU, "Invalid image view index={}", index); return NULL_IMAGE_VIEW_ID; } const auto [descriptor, is_new] = table.Read(index); ImageViewId& image_view_id = cached_image_view_ids[index]; if (is_new) { image_view_id = FindImageView(descriptor); } if (image_view_id != NULL_IMAGE_VIEW_ID) { PrepareImageView(image_view_id, false, false); } return image_view_id; } template FramebufferId TextureCache

::GetFramebufferId(const RenderTargets& key) { const auto [pair, is_new] = framebuffers.try_emplace(key); FramebufferId& framebuffer_id = pair->second; if (!is_new) { return framebuffer_id; } std::array color_buffers; std::ranges::transform(key.color_buffer_ids, color_buffers.begin(), [this](ImageViewId id) { return id ? &slot_image_views[id] : nullptr; }); ImageView* const depth_buffer = key.depth_buffer_id ? &slot_image_views[key.depth_buffer_id] : nullptr; framebuffer_id = slot_framebuffers.insert(runtime, color_buffers, depth_buffer, key); return framebuffer_id; } template void TextureCache

::WriteMemory(VAddr cpu_addr, size_t size) { ForEachImageInRegion(cpu_addr, size, [this](ImageId image_id, Image& image) { if (True(image.flags & ImageFlagBits::CpuModified)) { return; } image.flags |= ImageFlagBits::CpuModified; UntrackImage(image); }); } template void TextureCache

::DownloadMemory(VAddr cpu_addr, size_t size) { std::vector images; ForEachImageInRegion(cpu_addr, size, [this, &images](ImageId image_id, ImageBase& image) { // Skip images that were not modified from the GPU if (False(image.flags & ImageFlagBits::GpuModified)) { return; } // Skip images that .are. modified from the CPU // We don't want to write sensitive data from the guest if (True(image.flags & ImageFlagBits::CpuModified)) { return; } if (image.info.num_samples > 1) { LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented"); return; } image.flags &= ~ImageFlagBits::GpuModified; images.push_back(image_id); }); if (images.empty()) { return; } std::ranges::sort(images, [this](ImageId lhs, ImageId rhs) { return slot_images[lhs].modification_tick < slot_images[rhs].modification_tick; }); for (const ImageId image_id : images) { Image& image = slot_images[image_id]; auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes); const auto copies = FullDownloadCopies(image.info); image.DownloadMemory(map, copies); runtime.Finish(); SwizzleImage(gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span); } } template void TextureCache

::UnmapMemory(VAddr cpu_addr, size_t size) { std::vector deleted_images; ForEachImageInRegion(cpu_addr, size, [&](ImageId id, Image&) { deleted_images.push_back(id); }); for (const ImageId id : deleted_images) { Image& image = slot_images[id]; if (True(image.flags & ImageFlagBits::Tracked)) { UntrackImage(image); } UnregisterImage(id); DeleteImage(id); } } template void TextureCache

::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Surface& src, const Tegra::Engines::Fermi2D::Config& copy) { const BlitImages images = GetBlitImages(dst, src); const ImageId dst_id = images.dst_id; const ImageId src_id = images.src_id; PrepareImage(src_id, false, false); PrepareImage(dst_id, true, false); ImageBase& dst_image = slot_images[dst_id]; const ImageBase& src_image = slot_images[src_id]; // TODO: Deduplicate const std::optional dst_base = dst_image.TryFindBase(dst.Address()); const SubresourceRange dst_range{.base = dst_base.value(), .extent = {1, 1}}; const ImageViewInfo dst_view_info(ImageViewType::e2D, images.dst_format, dst_range); const auto [dst_framebuffer_id, dst_view_id] = RenderTargetFromImage(dst_id, dst_view_info); const auto [src_samples_x, src_samples_y] = SamplesLog2(src_image.info.num_samples); const std::array src_region{ Offset2D{.x = copy.src_x0 >> src_samples_x, .y = copy.src_y0 >> src_samples_y}, Offset2D{.x = copy.src_x1 >> src_samples_x, .y = copy.src_y1 >> src_samples_y}, }; const std::optional src_base = src_image.TryFindBase(src.Address()); const SubresourceRange src_range{.base = src_base.value(), .extent = {1, 1}}; const ImageViewInfo src_view_info(ImageViewType::e2D, images.src_format, src_range); const auto [src_framebuffer_id, src_view_id] = RenderTargetFromImage(src_id, src_view_info); const auto [dst_samples_x, dst_samples_y] = SamplesLog2(dst_image.info.num_samples); const std::array dst_region{ Offset2D{.x = copy.dst_x0 >> dst_samples_x, .y = copy.dst_y0 >> dst_samples_y}, Offset2D{.x = copy.dst_x1 >> dst_samples_x, .y = copy.dst_y1 >> dst_samples_y}, }; // Always call this after src_framebuffer_id was queried, as the address might be invalidated. Framebuffer* const dst_framebuffer = &slot_framebuffers[dst_framebuffer_id]; if constexpr (FRAMEBUFFER_BLITS) { // OpenGL blits from framebuffers, not images Framebuffer* const src_framebuffer = &slot_framebuffers[src_framebuffer_id]; runtime.BlitFramebuffer(dst_framebuffer, src_framebuffer, dst_region, src_region, copy.filter, copy.operation); } else { // Vulkan can blit images, but it lacks format reinterpretations // Provide a framebuffer in case it's necessary ImageView& dst_view = slot_image_views[dst_view_id]; ImageView& src_view = slot_image_views[src_view_id]; runtime.BlitImage(dst_framebuffer, dst_view, src_view, dst_region, src_region, copy.filter, copy.operation); } } template void TextureCache

::InvalidateColorBuffer(size_t index) { ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index]; color_buffer_id = FindColorBuffer(index, false); if (!color_buffer_id) { LOG_ERROR(HW_GPU, "Invalidating invalid color buffer in index={}", index); return; } // When invalidating a color buffer, the old contents are no longer relevant ImageView& color_buffer = slot_image_views[color_buffer_id]; Image& image = slot_images[color_buffer.image_id]; image.flags &= ~ImageFlagBits::CpuModified; image.flags &= ~ImageFlagBits::GpuModified; runtime.InvalidateColorBuffer(color_buffer, index); } template void TextureCache

::InvalidateDepthBuffer() { ImageViewId& depth_buffer_id = render_targets.depth_buffer_id; depth_buffer_id = FindDepthBuffer(false); if (!depth_buffer_id) { LOG_ERROR(HW_GPU, "Invalidating invalid depth buffer"); return; } // When invalidating the depth buffer, the old contents are no longer relevant ImageBase& image = slot_images[slot_image_views[depth_buffer_id].image_id]; image.flags &= ~ImageFlagBits::CpuModified; image.flags &= ~ImageFlagBits::GpuModified; ImageView& depth_buffer = slot_image_views[depth_buffer_id]; runtime.InvalidateDepthBuffer(depth_buffer); } template typename P::ImageView* TextureCache

::TryFindFramebufferImageView(VAddr cpu_addr) { // TODO: Properly implement this const auto it = page_table.find(cpu_addr >> PAGE_BITS); if (it == page_table.end()) { return nullptr; } const auto& image_ids = it->second; for (const ImageId image_id : image_ids) { const ImageBase& image = slot_images[image_id]; if (image.cpu_addr != cpu_addr) { continue; } if (image.image_view_ids.empty()) { continue; } return &slot_image_views[image.image_view_ids.at(0)]; } return nullptr; } template bool TextureCache

::HasUncommittedFlushes() const noexcept { return !uncommitted_downloads.empty(); } template bool TextureCache

::ShouldWaitAsyncFlushes() const noexcept { return !committed_downloads.empty() && !committed_downloads.front().empty(); } template void TextureCache

::CommitAsyncFlushes() { // This is intentionally passing the value by copy committed_downloads.push(uncommitted_downloads); uncommitted_downloads.clear(); } template void TextureCache

::PopAsyncFlushes() { if (committed_downloads.empty()) { return; } const std::span download_ids = committed_downloads.front(); if (download_ids.empty()) { committed_downloads.pop(); return; } size_t total_size_bytes = 0; for (const ImageId image_id : download_ids) { total_size_bytes += slot_images[image_id].unswizzled_size_bytes; } auto download_map = runtime.DownloadStagingBuffer(total_size_bytes); const size_t original_offset = download_map.offset; for (const ImageId image_id : download_ids) { Image& image = slot_images[image_id]; const auto copies = FullDownloadCopies(image.info); image.DownloadMemory(download_map, copies); download_map.offset += image.unswizzled_size_bytes; } // Wait for downloads to finish runtime.Finish(); download_map.offset = original_offset; std::span download_span = download_map.mapped_span; for (const ImageId image_id : download_ids) { const ImageBase& image = slot_images[image_id]; const auto copies = FullDownloadCopies(image.info); SwizzleImage(gpu_memory, image.gpu_addr, image.info, copies, download_span); download_map.offset += image.unswizzled_size_bytes; download_span = download_span.subspan(image.unswizzled_size_bytes); } committed_downloads.pop(); } template bool TextureCache

::IsRegionGpuModified(VAddr addr, size_t size) { bool is_modified = false; ForEachImageInRegion(addr, size, [&is_modified](ImageId, ImageBase& image) { if (False(image.flags & ImageFlagBits::GpuModified)) { return false; } is_modified = true; return true; }); return is_modified; } template void TextureCache

::RefreshContents(Image& image) { if (False(image.flags & ImageFlagBits::CpuModified)) { // Only upload modified images return; } image.flags &= ~ImageFlagBits::CpuModified; TrackImage(image); if (image.info.num_samples > 1) { LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented"); return; } auto staging = runtime.UploadStagingBuffer(MapSizeBytes(image)); UploadImageContents(image, staging); runtime.InsertUploadMemoryBarrier(); } template template void TextureCache

::UploadImageContents(Image& image, StagingBuffer& staging) { const std::span mapped_span = staging.mapped_span; const GPUVAddr gpu_addr = image.gpu_addr; if (True(image.flags & ImageFlagBits::AcceleratedUpload)) { gpu_memory.ReadBlockUnsafe(gpu_addr, mapped_span.data(), mapped_span.size_bytes()); const auto uploads = FullUploadSwizzles(image.info); runtime.AccelerateImageUpload(image, staging, uploads); } else if (True(image.flags & ImageFlagBits::Converted)) { std::vector unswizzled_data(image.unswizzled_size_bytes); auto copies = UnswizzleImage(gpu_memory, gpu_addr, image.info, unswizzled_data); ConvertImage(unswizzled_data, image.info, mapped_span, copies); image.UploadMemory(staging, copies); } else if (image.info.type == ImageType::Buffer) { const std::array copies{UploadBufferCopy(gpu_memory, gpu_addr, image, mapped_span)}; image.UploadMemory(staging, copies); } else { const auto copies = UnswizzleImage(gpu_memory, gpu_addr, image.info, mapped_span); image.UploadMemory(staging, copies); } } template ImageViewId TextureCache

::FindImageView(const TICEntry& config) { if (!IsValidAddress(gpu_memory, config)) { return NULL_IMAGE_VIEW_ID; } const auto [pair, is_new] = image_views.try_emplace(config); ImageViewId& image_view_id = pair->second; if (is_new) { image_view_id = CreateImageView(config); } return image_view_id; } template ImageViewId TextureCache

::CreateImageView(const TICEntry& config) { const ImageInfo info(config); const GPUVAddr image_gpu_addr = config.Address() - config.BaseLayer() * info.layer_stride; const ImageId image_id = FindOrInsertImage(info, image_gpu_addr); if (!image_id) { return NULL_IMAGE_VIEW_ID; } ImageBase& image = slot_images[image_id]; const SubresourceBase base = image.TryFindBase(config.Address()).value(); ASSERT(base.level == 0); const ImageViewInfo view_info(config, base.layer); const ImageViewId image_view_id = FindOrEmplaceImageView(image_id, view_info); ImageViewBase& image_view = slot_image_views[image_view_id]; image_view.flags |= ImageViewFlagBits::Strong; image.flags |= ImageFlagBits::Strong; return image_view_id; } template ImageId TextureCache

::FindOrInsertImage(const ImageInfo& info, GPUVAddr gpu_addr, RelaxedOptions options) { if (const ImageId image_id = FindImage(info, gpu_addr, options); image_id) { return image_id; } return InsertImage(info, gpu_addr, options); } template ImageId TextureCache

::FindImage(const ImageInfo& info, GPUVAddr gpu_addr, RelaxedOptions options) { const std::optional cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); if (!cpu_addr) { return ImageId{}; } const bool broken_views = runtime.HasBrokenTextureViewFormats(); const bool native_bgr = runtime.HasNativeBgr(); ImageId image_id; const auto lambda = [&](ImageId existing_image_id, ImageBase& existing_image) { if (info.type == ImageType::Linear || existing_image.info.type == ImageType::Linear) { const bool strict_size = False(options & RelaxedOptions::Size) && True(existing_image.flags & ImageFlagBits::Strong); const ImageInfo& existing = existing_image.info; if (existing_image.gpu_addr == gpu_addr && existing.type == info.type && existing.pitch == info.pitch && IsPitchLinearSameSize(existing, info, strict_size) && IsViewCompatible(existing.format, info.format, broken_views, native_bgr)) { image_id = existing_image_id; return true; } } else if (IsSubresource(info, existing_image, gpu_addr, options, broken_views, native_bgr)) { image_id = existing_image_id; return true; } return false; }; ForEachImageInRegion(*cpu_addr, CalculateGuestSizeInBytes(info), lambda); return image_id; } template ImageId TextureCache

::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr, RelaxedOptions options) { const std::optional cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); ASSERT_MSG(cpu_addr, "Tried to insert an image to an invalid gpu_addr=0x{:x}", gpu_addr); const ImageId image_id = JoinImages(info, gpu_addr, *cpu_addr); const Image& image = slot_images[image_id]; // Using "image.gpu_addr" instead of "gpu_addr" is important because it might be different const auto [it, is_new] = image_allocs_table.try_emplace(image.gpu_addr); if (is_new) { it->second = slot_image_allocs.insert(); } slot_image_allocs[it->second].images.push_back(image_id); return image_id; } template ImageId TextureCache

::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr) { ImageInfo new_info = info; const size_t size_bytes = CalculateGuestSizeInBytes(new_info); const bool broken_views = runtime.HasBrokenTextureViewFormats(); const bool native_bgr = runtime.HasNativeBgr(); std::vector overlap_ids; std::vector left_aliased_ids; std::vector right_aliased_ids; ForEachImageInRegion(cpu_addr, size_bytes, [&](ImageId overlap_id, ImageBase& overlap) { if (info.type != overlap.info.type) { return; } if (info.type == ImageType::Linear) { if (info.pitch == overlap.info.pitch && gpu_addr == overlap.gpu_addr) { // Alias linear images with the same pitch left_aliased_ids.push_back(overlap_id); } return; } static constexpr bool strict_size = true; const std::optional solution = ResolveOverlap( new_info, gpu_addr, cpu_addr, overlap, strict_size, broken_views, native_bgr); if (solution) { gpu_addr = solution->gpu_addr; cpu_addr = solution->cpu_addr; new_info.resources = solution->resources; overlap_ids.push_back(overlap_id); return; } static constexpr auto options = RelaxedOptions::Size | RelaxedOptions::Format; const ImageBase new_image_base(new_info, gpu_addr, cpu_addr); if (IsSubresource(new_info, overlap, gpu_addr, options, broken_views, native_bgr)) { left_aliased_ids.push_back(overlap_id); } else if (IsSubresource(overlap.info, new_image_base, overlap.gpu_addr, options, broken_views, native_bgr)) { right_aliased_ids.push_back(overlap_id); } }); const ImageId new_image_id = slot_images.insert(runtime, new_info, gpu_addr, cpu_addr); Image& new_image = slot_images[new_image_id]; // TODO: Only upload what we need RefreshContents(new_image); for (const ImageId overlap_id : overlap_ids) { Image& overlap = slot_images[overlap_id]; if (overlap.info.num_samples != new_image.info.num_samples) { LOG_WARNING(HW_GPU, "Copying between images with different samples is not implemented"); } else { const SubresourceBase base = new_image.TryFindBase(overlap.gpu_addr).value(); const auto copies = MakeShrinkImageCopies(new_info, overlap.info, base); runtime.CopyImage(new_image, overlap, copies); } if (True(overlap.flags & ImageFlagBits::Tracked)) { UntrackImage(overlap); } UnregisterImage(overlap_id); DeleteImage(overlap_id); } ImageBase& new_image_base = new_image; for (const ImageId aliased_id : right_aliased_ids) { ImageBase& aliased = slot_images[aliased_id]; AddImageAlias(new_image_base, aliased, new_image_id, aliased_id); } for (const ImageId aliased_id : left_aliased_ids) { ImageBase& aliased = slot_images[aliased_id]; AddImageAlias(aliased, new_image_base, aliased_id, new_image_id); } RegisterImage(new_image_id); return new_image_id; } template typename TextureCache

::BlitImages TextureCache

::GetBlitImages( const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Surface& src) { static constexpr auto FIND_OPTIONS = RelaxedOptions::Format | RelaxedOptions::Samples; const GPUVAddr dst_addr = dst.Address(); const GPUVAddr src_addr = src.Address(); ImageInfo dst_info(dst); ImageInfo src_info(src); ImageId dst_id; ImageId src_id; do { has_deleted_images = false; dst_id = FindImage(dst_info, dst_addr, FIND_OPTIONS); src_id = FindImage(src_info, src_addr, FIND_OPTIONS); const ImageBase* const dst_image = dst_id ? &slot_images[dst_id] : nullptr; const ImageBase* const src_image = src_id ? &slot_images[src_id] : nullptr; DeduceBlitImages(dst_info, src_info, dst_image, src_image); if (GetFormatType(dst_info.format) != GetFormatType(src_info.format)) { continue; } if (!dst_id) { dst_id = InsertImage(dst_info, dst_addr, RelaxedOptions{}); } if (!src_id) { src_id = InsertImage(src_info, src_addr, RelaxedOptions{}); } } while (has_deleted_images); return BlitImages{ .dst_id = dst_id, .src_id = src_id, .dst_format = dst_info.format, .src_format = src_info.format, }; } template SamplerId TextureCache

::FindSampler(const TSCEntry& config) { if (std::ranges::all_of(config.raw, [](u64 value) { return value == 0; })) { return NULL_SAMPLER_ID; } const auto [pair, is_new] = samplers.try_emplace(config); if (is_new) { pair->second = slot_samplers.insert(runtime, config); } return pair->second; } template ImageViewId TextureCache

::FindColorBuffer(size_t index, bool is_clear) { const auto& regs = maxwell3d.regs; if (index >= regs.rt_control.count) { return ImageViewId{}; } const auto& rt = regs.rt[index]; const GPUVAddr gpu_addr = rt.Address(); if (gpu_addr == 0) { return ImageViewId{}; } if (rt.format == Tegra::RenderTargetFormat::NONE) { return ImageViewId{}; } const ImageInfo info(regs, index); return FindRenderTargetView(info, gpu_addr, is_clear); } template ImageViewId TextureCache

::FindDepthBuffer(bool is_clear) { const auto& regs = maxwell3d.regs; if (!regs.zeta_enable) { return ImageViewId{}; } const GPUVAddr gpu_addr = regs.zeta.Address(); if (gpu_addr == 0) { return ImageViewId{}; } const ImageInfo info(regs); return FindRenderTargetView(info, gpu_addr, is_clear); } template ImageViewId TextureCache

::FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr, bool is_clear) { const auto options = is_clear ? RelaxedOptions::Samples : RelaxedOptions{}; const ImageId image_id = FindOrInsertImage(info, gpu_addr, options); if (!image_id) { return NULL_IMAGE_VIEW_ID; } Image& image = slot_images[image_id]; const ImageViewType view_type = RenderTargetImageViewType(info); SubresourceBase base; if (image.info.type == ImageType::Linear) { base = SubresourceBase{.level = 0, .layer = 0}; } else { base = image.TryFindBase(gpu_addr).value(); } const s32 layers = image.info.type == ImageType::e3D ? info.size.depth : info.resources.layers; const SubresourceRange range{ .base = base, .extent = {.levels = 1, .layers = layers}, }; return FindOrEmplaceImageView(image_id, ImageViewInfo(view_type, info.format, range)); } template template void TextureCache

::ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func) { using FuncReturn = typename std::invoke_result::type; static constexpr bool BOOL_BREAK = std::is_same_v; boost::container::small_vector images; ForEachPage(cpu_addr, size, [this, &images, cpu_addr, size, func](u64 page) { const auto it = page_table.find(page); if (it == page_table.end()) { if constexpr (BOOL_BREAK) { return false; } else { return; } } for (const ImageId image_id : it->second) { Image& image = slot_images[image_id]; if (True(image.flags & ImageFlagBits::Picked)) { continue; } if (!image.Overlaps(cpu_addr, size)) { continue; } image.flags |= ImageFlagBits::Picked; images.push_back(image_id); if constexpr (BOOL_BREAK) { if (func(image_id, image)) { return true; } } else { func(image_id, image); } } if constexpr (BOOL_BREAK) { return false; } }); for (const ImageId image_id : images) { slot_images[image_id].flags &= ~ImageFlagBits::Picked; } } template ImageViewId TextureCache

::FindOrEmplaceImageView(ImageId image_id, const ImageViewInfo& info) { Image& image = slot_images[image_id]; if (const ImageViewId image_view_id = image.FindView(info); image_view_id) { return image_view_id; } const ImageViewId image_view_id = slot_image_views.insert(runtime, info, image_id, image); image.InsertView(info, image_view_id); return image_view_id; } template void TextureCache

::RegisterImage(ImageId image_id) { ImageBase& image = slot_images[image_id]; ASSERT_MSG(False(image.flags & ImageFlagBits::Registered), "Trying to register an already registered image"); image.flags |= ImageFlagBits::Registered; ForEachPage(image.cpu_addr, image.guest_size_bytes, [this, image_id](u64 page) { page_table[page].push_back(image_id); }); } template void TextureCache

::UnregisterImage(ImageId image_id) { Image& image = slot_images[image_id]; ASSERT_MSG(True(image.flags & ImageFlagBits::Registered), "Trying to unregister an already registered image"); image.flags &= ~ImageFlagBits::Registered; ForEachPage(image.cpu_addr, image.guest_size_bytes, [this, image_id](u64 page) { const auto page_it = page_table.find(page); if (page_it == page_table.end()) { UNREACHABLE_MSG("Unregistering unregistered page=0x{:x}", page << PAGE_BITS); return; } std::vector& image_ids = page_it->second; const auto vector_it = std::ranges::find(image_ids, image_id); if (vector_it == image_ids.end()) { UNREACHABLE_MSG("Unregistering unregistered image in page=0x{:x}", page << PAGE_BITS); return; } image_ids.erase(vector_it); }); } template void TextureCache

::TrackImage(ImageBase& image) { ASSERT(False(image.flags & ImageFlagBits::Tracked)); image.flags |= ImageFlagBits::Tracked; rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, 1); } template void TextureCache

::UntrackImage(ImageBase& image) { ASSERT(True(image.flags & ImageFlagBits::Tracked)); image.flags &= ~ImageFlagBits::Tracked; rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, -1); } template void TextureCache

::DeleteImage(ImageId image_id) { ImageBase& image = slot_images[image_id]; const GPUVAddr gpu_addr = image.gpu_addr; const auto alloc_it = image_allocs_table.find(gpu_addr); if (alloc_it == image_allocs_table.end()) { UNREACHABLE_MSG("Trying to delete an image alloc that does not exist in address 0x{:x}", gpu_addr); return; } const ImageAllocId alloc_id = alloc_it->second; std::vector& alloc_images = slot_image_allocs[alloc_id].images; const auto alloc_image_it = std::ranges::find(alloc_images, image_id); if (alloc_image_it == alloc_images.end()) { UNREACHABLE_MSG("Trying to delete an image that does not exist"); return; } ASSERT_MSG(False(image.flags & ImageFlagBits::Tracked), "Image was not untracked"); ASSERT_MSG(False(image.flags & ImageFlagBits::Registered), "Image was not unregistered"); // Mark render targets as dirty auto& dirty = maxwell3d.dirty.flags; dirty[Dirty::RenderTargets] = true; dirty[Dirty::ZetaBuffer] = true; for (size_t rt = 0; rt < NUM_RT; ++rt) { dirty[Dirty::ColorBuffer0 + rt] = true; } const std::span image_view_ids = image.image_view_ids; for (const ImageViewId image_view_id : image_view_ids) { std::ranges::replace(render_targets.color_buffer_ids, image_view_id, ImageViewId{}); if (render_targets.depth_buffer_id == image_view_id) { render_targets.depth_buffer_id = ImageViewId{}; } } RemoveImageViewReferences(image_view_ids); RemoveFramebuffers(image_view_ids); for (const AliasedImage& alias : image.aliased_images) { ImageBase& other_image = slot_images[alias.id]; [[maybe_unused]] const size_t num_removed_aliases = std::erase_if(other_image.aliased_images, [image_id](const AliasedImage& other_alias) { return other_alias.id == image_id; }); ASSERT_MSG(num_removed_aliases == 1, "Invalid number of removed aliases: {}", num_removed_aliases); } for (const ImageViewId image_view_id : image_view_ids) { sentenced_image_view.Push(std::move(slot_image_views[image_view_id])); slot_image_views.erase(image_view_id); } sentenced_images.Push(std::move(slot_images[image_id])); slot_images.erase(image_id); alloc_images.erase(alloc_image_it); if (alloc_images.empty()) { image_allocs_table.erase(alloc_it); } if constexpr (ENABLE_VALIDATION) { std::ranges::fill(graphics_image_view_ids, CORRUPT_ID); std::ranges::fill(compute_image_view_ids, CORRUPT_ID); } graphics_image_table.Invalidate(); compute_image_table.Invalidate(); has_deleted_images = true; } template void TextureCache

::RemoveImageViewReferences(std::span removed_views) { auto it = image_views.begin(); while (it != image_views.end()) { const auto found = std::ranges::find(removed_views, it->second); if (found != removed_views.end()) { it = image_views.erase(it); } else { ++it; } } } template void TextureCache

::RemoveFramebuffers(std::span removed_views) { auto it = framebuffers.begin(); while (it != framebuffers.end()) { if (it->first.Contains(removed_views)) { it = framebuffers.erase(it); } else { ++it; } } } template void TextureCache

::MarkModification(ImageBase& image) noexcept { image.flags |= ImageFlagBits::GpuModified; image.modification_tick = ++modification_tick; } template void TextureCache

::SynchronizeAliases(ImageId image_id) { boost::container::small_vector aliased_images; ImageBase& image = slot_images[image_id]; u64 most_recent_tick = image.modification_tick; for (const AliasedImage& aliased : image.aliased_images) { ImageBase& aliased_image = slot_images[aliased.id]; if (image.modification_tick < aliased_image.modification_tick) { most_recent_tick = std::max(most_recent_tick, aliased_image.modification_tick); aliased_images.push_back(&aliased); } } if (aliased_images.empty()) { return; } image.modification_tick = most_recent_tick; std::ranges::sort(aliased_images, [this](const AliasedImage* lhs, const AliasedImage* rhs) { const ImageBase& lhs_image = slot_images[lhs->id]; const ImageBase& rhs_image = slot_images[rhs->id]; return lhs_image.modification_tick < rhs_image.modification_tick; }); for (const AliasedImage* const aliased : aliased_images) { CopyImage(image_id, aliased->id, aliased->copies); } } template void TextureCache

::PrepareImage(ImageId image_id, bool is_modification, bool invalidate) { Image& image = slot_images[image_id]; if (invalidate) { image.flags &= ~(ImageFlagBits::CpuModified | ImageFlagBits::GpuModified); if (False(image.flags & ImageFlagBits::Tracked)) { TrackImage(image); } } else { RefreshContents(image); SynchronizeAliases(image_id); } if (is_modification) { MarkModification(image); } image.frame_tick = frame_tick; } template void TextureCache

::PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate) { if (!image_view_id) { return; } const ImageViewBase& image_view = slot_image_views[image_view_id]; PrepareImage(image_view.image_id, is_modification, invalidate); } template void TextureCache

::CopyImage(ImageId dst_id, ImageId src_id, std::span copies) { Image& dst = slot_images[dst_id]; Image& src = slot_images[src_id]; const auto dst_format_type = GetFormatType(dst.info.format); const auto src_format_type = GetFormatType(src.info.format); if (src_format_type == dst_format_type) { if constexpr (HAS_EMULATED_COPIES) { if (!runtime.CanImageBeCopied(dst, src)) { return runtime.EmulateCopyImage(dst, src, copies); } } return runtime.CopyImage(dst, src, copies); } UNIMPLEMENTED_IF(dst.info.type != ImageType::e2D); UNIMPLEMENTED_IF(src.info.type != ImageType::e2D); for (const ImageCopy& copy : copies) { UNIMPLEMENTED_IF(copy.dst_subresource.num_layers != 1); UNIMPLEMENTED_IF(copy.src_subresource.num_layers != 1); UNIMPLEMENTED_IF(copy.src_offset != Offset3D{}); UNIMPLEMENTED_IF(copy.dst_offset != Offset3D{}); const SubresourceBase dst_base{ .level = copy.dst_subresource.base_level, .layer = copy.dst_subresource.base_layer, }; const SubresourceBase src_base{ .level = copy.src_subresource.base_level, .layer = copy.src_subresource.base_layer, }; const SubresourceExtent dst_extent{.levels = 1, .layers = 1}; const SubresourceExtent src_extent{.levels = 1, .layers = 1}; const SubresourceRange dst_range{.base = dst_base, .extent = dst_extent}; const SubresourceRange src_range{.base = src_base, .extent = src_extent}; const ImageViewInfo dst_view_info(ImageViewType::e2D, dst.info.format, dst_range); const ImageViewInfo src_view_info(ImageViewType::e2D, src.info.format, src_range); const auto [dst_framebuffer_id, dst_view_id] = RenderTargetFromImage(dst_id, dst_view_info); Framebuffer* const dst_framebuffer = &slot_framebuffers[dst_framebuffer_id]; const ImageViewId src_view_id = FindOrEmplaceImageView(src_id, src_view_info); ImageView& dst_view = slot_image_views[dst_view_id]; ImageView& src_view = slot_image_views[src_view_id]; [[maybe_unused]] const Extent3D expected_size{ .width = std::min(dst_view.size.width, src_view.size.width), .height = std::min(dst_view.size.height, src_view.size.height), .depth = std::min(dst_view.size.depth, src_view.size.depth), }; UNIMPLEMENTED_IF(copy.extent != expected_size); runtime.ConvertImage(dst_framebuffer, dst_view, src_view); } } template void TextureCache

::BindRenderTarget(ImageViewId* old_id, ImageViewId new_id) { if (*old_id == new_id) { return; } if (*old_id) { const ImageViewBase& old_view = slot_image_views[*old_id]; if (True(old_view.flags & ImageViewFlagBits::PreemtiveDownload)) { uncommitted_downloads.push_back(old_view.image_id); } } *old_id = new_id; } template std::pair TextureCache

::RenderTargetFromImage( ImageId image_id, const ImageViewInfo& view_info) { const ImageViewId view_id = FindOrEmplaceImageView(image_id, view_info); const ImageBase& image = slot_images[image_id]; const bool is_color = GetFormatType(image.info.format) == SurfaceType::ColorTexture; const ImageViewId color_view_id = is_color ? view_id : ImageViewId{}; const ImageViewId depth_view_id = is_color ? ImageViewId{} : view_id; const Extent3D extent = MipSize(image.info.size, view_info.range.base.level); const u32 num_samples = image.info.num_samples; const auto [samples_x, samples_y] = SamplesLog2(num_samples); const FramebufferId framebuffer_id = GetFramebufferId(RenderTargets{ .color_buffer_ids = {color_view_id}, .depth_buffer_id = depth_view_id, .size = {extent.width >> samples_x, extent.height >> samples_y}, }); return {framebuffer_id, view_id}; } template bool TextureCache

::IsFullClear(ImageViewId id) { if (!id) { return true; } const ImageViewBase& image_view = slot_image_views[id]; const ImageBase& image = slot_images[image_view.image_id]; const Extent3D size = image_view.size; const auto& regs = maxwell3d.regs; const auto& scissor = regs.scissor_test[0]; if (image.info.resources.levels > 1 || image.info.resources.layers > 1) { // Images with multiple resources can't be cleared in a single call return false; } if (regs.clear_flags.scissor == 0) { // If scissor testing is disabled, the clear is always full return true; } // Make sure the clear covers all texels in the subresource return scissor.min_x == 0 && scissor.min_y == 0 && scissor.max_x >= size.width && scissor.max_y >= size.height; } } // namespace VideoCommon