// SPDX-License-Identifier: MPL-2.0 // Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) #include #include "texture_manager.h" namespace skyline::gpu { TextureManager::TextureManager(GPU &gpu) : gpu(gpu) {} std::shared_ptr TextureManager::FindOrCreate(const GuestTexture &guestTexture, ContextTag tag) { TRACE_EVENT("gpu", "TextureManager::FindOrCreate"); auto guestMapping{guestTexture.mappings.front()}; /* * Iterate over all textures that overlap with the first mapping of the guest texture and compare the mappings: * 1) All mappings match up perfectly, we check that the rest of the supplied mappings correspond to mappings in the texture * 1.1) If they match as well, we check for format/dimensions/tiling config matching the texture and return or move onto (3) * 2) Only a contiguous range of mappings match, we check for if the overlap is meaningful with layout math, it can go two ways: * 2.1) If there is a meaningful overlap, we check for format/dimensions/tiling config compatibility and return or move onto (3) * 2.2) If there isn't, we move onto (3) * 3) If there's another overlap we go back to (1) with it else we go to (4) * 4) We check all the overlapping texture for if they're in the texture pool: * 4.1) If they are, we do nothing to them * 4.2) If they aren't, we delete them from the map * 5) Create a new texture and insert it in the map then return it */ std::shared_ptr match{}; boost::container::small_vector, 4> matches{}; auto mappingEnd{std::upper_bound(textures.begin(), textures.end(), guestMapping, [guestMapping](const auto &value, const auto &element) { return guestMapping.end() < element.end(); })}, hostMapping{std::lower_bound(mappingEnd, textures.end(), guestMapping, [guestMapping](const auto &value, const auto &element) { return guestMapping.begin() < element.end(); })}; std::shared_ptr fullMatch{}; std::shared_ptr layerMipMatch{}; u32 matchLevel{}; u32 matchLayer{}; while (hostMapping != textures.begin() && (--hostMapping)->end() > guestMapping.begin()) { auto &hostMappings{hostMapping->texture->guest->mappings}; if (!hostMapping->contains(guestMapping) || hostMapping->texture->replaced) continue; // We need to check that all corresponding mappings in the candidate texture and the guest texture match up // Only the start of the first matched mapping and the end of the last mapping can not match up as this is the case for views auto firstHostMapping{hostMapping->iterator}; auto lastGuestMapping{guestTexture.mappings.back()}; auto lastHostMapping{std::find_if(firstHostMapping, hostMappings.end(), [&lastGuestMapping](const span &it) { return lastGuestMapping.begin() > it.begin() && lastGuestMapping.end() > it.end(); })}; //!< A past-the-end iterator for the last host mapping, the final valid mapping is prior to this iterator bool mappingMatch{std::equal(firstHostMapping, lastHostMapping, guestTexture.mappings.begin(), guestTexture.mappings.end(), [](const span &lhs, const span &rhs) { return lhs.end() == rhs.end(); // We check end() here to implicitly ignore any offset from the first mapping })}; if (firstHostMapping == hostMappings.begin() && firstHostMapping->begin() == guestMapping.begin() && mappingMatch && lastHostMapping == hostMappings.end() && lastGuestMapping.end() == std::prev(lastHostMapping)->end()) { // We've gotten a perfect 1:1 match for *all* mappings from the start to end, we just need to check for compatibility aside from this auto &matchGuestTexture{*hostMapping->texture->guest}; if (matchGuestTexture.format->IsCompatible(*guestTexture.format) && ((((matchGuestTexture.dimensions.width == guestTexture.dimensions.width && matchGuestTexture.dimensions.height == guestTexture.dimensions.height) || matchGuestTexture.CalculateLayerSize() == guestTexture.CalculateLayerSize()) && matchGuestTexture.GetViewDepth() <= guestTexture.GetViewDepth()) || matchGuestTexture.viewMipBase > 0) && matchGuestTexture.tileConfig == guestTexture.tileConfig) { fullMatch = hostMapping->texture; } else { matches.push_back(hostMapping->texture); } } else { auto &matchGuestTexture{*hostMapping->texture->guest}; if (matchGuestTexture.format->IsCompatible(*guestTexture.format) && matchGuestTexture.tileConfig == guestTexture.tileConfig && (!layerMipMatch || (matchGuestTexture.GetViewLayerCount() >= layerMipMatch->guest->GetViewLayerCount() && matchGuestTexture.mipLevelCount >= layerMipMatch->guest->mipLevelCount))) { size_t memOffset{static_cast(guestMapping.data() - hostMapping->texture->guest->mappings.front().data())}; size_t layerMemOffset{}; bool matched{}; for (u32 layer{}; layer < hostMapping->texture->layerCount; layer++) { u32 level{}; size_t levelMemOffset{}; for (auto &mipLevel : hostMapping->texture->mipLayouts) { if (layerMemOffset + levelMemOffset == memOffset) { if (mipLevel.blockLinearSize == guestTexture.CalculateLayerSize()) { matched = true; matchLayer = layer; matchLevel = level; break; } level++; levelMemOffset += mipLevel.blockLinearSize; } } if (matched) break; layerMemOffset += matchGuestTexture.GetLayerStride(); } if (matched) { if (layerMipMatch) layerMipMatch->replaced = true; if (fullMatch) fullMatch->replaced = true; layerMipMatch = hostMapping->texture; } } } } if (layerMipMatch) { ContextLock textureLock{tag, *layerMipMatch}; return layerMipMatch->GetView(guestTexture.viewType, vk::ImageSubresourceRange{ .aspectMask = guestTexture.aspect, .baseMipLevel = guestTexture.viewMipBase + matchLevel, .levelCount = guestTexture.viewMipCount, .baseArrayLayer = guestTexture.baseArrayLayer + matchLayer, .layerCount = guestTexture.GetViewLayerCount(), }, guestTexture.format, guestTexture.swizzle); } else if (fullMatch) { ContextLock textureLock{tag, *fullMatch}; return fullMatch->GetView(guestTexture.viewType, vk::ImageSubresourceRange{ .aspectMask = guestTexture.aspect, .baseMipLevel = guestTexture.viewMipBase, .levelCount = guestTexture.viewMipCount, .baseArrayLayer = guestTexture.baseArrayLayer, .layerCount = guestTexture.GetViewLayerCount(), }, guestTexture.format, guestTexture.swizzle); } for (auto &texture : matches) texture->SynchronizeGuest(false, true); // Create a texture as we cannot find one that matches auto texture{std::make_shared(gpu, guestTexture)}; texture->SetupGuestMappings(); texture->TransitionLayout(vk::ImageLayout::eGeneral); auto it{texture->guest->mappings.begin()}; textures.emplace(mappingEnd, TextureMapping{texture, it, guestMapping}); while ((++it) != texture->guest->mappings.end()) { guestMapping = *it; auto mapping{std::upper_bound(textures.begin(), textures.end(), guestMapping)}; // TODO: Delete overlapping textures that aren't in texture pool textures.emplace(mapping, TextureMapping{texture, it, guestMapping}); } return texture->GetView(guestTexture.viewType, vk::ImageSubresourceRange{ .aspectMask = guestTexture.aspect, .baseMipLevel = guestTexture.viewMipBase, .levelCount = guestTexture.viewMipCount, .baseArrayLayer = guestTexture.baseArrayLayer, .layerCount = guestTexture.GetViewLayerCount(), }, guestTexture.format, guestTexture.swizzle); } }