mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-09-05 17:15:49 +00:00
VideoCommon: add a custom texture cache for graphics mods
This commit is contained in:
parent
2d789b9139
commit
40133144cc
2 changed files with 267 additions and 0 deletions
|
@ -0,0 +1,195 @@
|
||||||
|
// Copyright 2024 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache.h"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include "Common/Logging/Log.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/AbstractFramebuffer.h"
|
||||||
|
#include "VideoCommon/AbstractGfx.h"
|
||||||
|
#include "VideoCommon/AbstractTexture.h"
|
||||||
|
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||||
|
#include "VideoCommon/VideoEvents.h"
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
CustomTextureCache::CustomTextureCache()
|
||||||
|
{
|
||||||
|
m_frame_event =
|
||||||
|
AfterFrameEvent::Register([this](Core::System&) { OnFrameEnd(); }, "CustomTextureCache");
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomTextureCache::~CustomTextureCache() = default;
|
||||||
|
|
||||||
|
std::optional<CustomTextureCache::TextureResult> CustomTextureCache::GetTextureAsset(
|
||||||
|
CustomAssetLoader& loader, std::shared_ptr<CustomAssetLibrary> library,
|
||||||
|
const CustomAssetLibrary::AssetID& asset_id, AbstractTextureType texture_type)
|
||||||
|
{
|
||||||
|
auto asset = loader.LoadGameTexture(asset_id, library);
|
||||||
|
if (!asset)
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto [iter, inserted] = m_cached_texture_assets.try_emplace(asset_id, CachedTextureAsset{});
|
||||||
|
if (inserted ||
|
||||||
|
iter->second.cached_asset.m_asset->GetLastLoadedTime() >
|
||||||
|
iter->second.cached_asset.m_cached_write_time ||
|
||||||
|
asset_id != iter->second.cached_asset.m_asset->GetAssetId() ||
|
||||||
|
iter->second.texture->GetConfig().type != texture_type)
|
||||||
|
{
|
||||||
|
const auto loaded_time = asset->GetLastLoadedTime();
|
||||||
|
iter->second.cached_asset =
|
||||||
|
VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>{std::move(asset), loaded_time};
|
||||||
|
ReleaseToPool(&iter->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto texture_data = iter->second.cached_asset.m_asset->GetData();
|
||||||
|
if (!texture_data)
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iter->second.texture)
|
||||||
|
{
|
||||||
|
iter->second.time = std::chrono::steady_clock::now();
|
||||||
|
return TextureResult{iter->second.texture.get(), iter->second.framebuffer.get(), texture_data};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& first_slice = texture_data->m_texture.m_slices[0];
|
||||||
|
const TextureConfig texture_config(first_slice.m_levels[0].width, first_slice.m_levels[0].height,
|
||||||
|
static_cast<u32>(first_slice.m_levels.size()),
|
||||||
|
static_cast<u32>(texture_data->m_texture.m_slices.size()), 1,
|
||||||
|
first_slice.m_levels[0].format, 0, texture_type);
|
||||||
|
|
||||||
|
auto new_texture = AllocateTexture(texture_config);
|
||||||
|
if (!new_texture)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(VIDEO, "Custom texture creation failed due to texture allocation failure");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
iter->second.texture.swap(new_texture->texture);
|
||||||
|
iter->second.framebuffer.swap(new_texture->framebuffer);
|
||||||
|
for (std::size_t slice_index = 0; slice_index < texture_data->m_texture.m_slices.size();
|
||||||
|
slice_index++)
|
||||||
|
{
|
||||||
|
auto& slice = texture_data->m_texture.m_slices[slice_index];
|
||||||
|
for (u32 level_index = 0; level_index < static_cast<u32>(slice.m_levels.size()); ++level_index)
|
||||||
|
{
|
||||||
|
auto& level = slice.m_levels[level_index];
|
||||||
|
iter->second.texture->Load(level_index, level.width, level.height, level.row_length,
|
||||||
|
level.data.data(), level.data.size(),
|
||||||
|
static_cast<u32>(slice_index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iter->second.time = std::chrono::steady_clock::now();
|
||||||
|
return TextureResult{iter->second.texture.get(), iter->second.framebuffer.get(), texture_data};
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomTextureCache::TexPoolEntry::TexPoolEntry(std::unique_ptr<AbstractTexture> tex,
|
||||||
|
std::unique_ptr<AbstractFramebuffer> fb)
|
||||||
|
: texture(std::move(tex)), framebuffer(std::move(fb)), time(std::chrono::steady_clock::now())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<CustomTextureCache::TexPoolEntry>
|
||||||
|
CustomTextureCache::AllocateTexture(const TextureConfig& config)
|
||||||
|
{
|
||||||
|
TexPool::iterator iter = FindMatchingTextureFromPool(config);
|
||||||
|
if (iter != m_texture_pool.end())
|
||||||
|
{
|
||||||
|
auto entry = std::move(iter->second);
|
||||||
|
m_texture_pool.erase(iter);
|
||||||
|
return std::move(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AbstractTexture> texture = g_gfx->CreateTexture(config);
|
||||||
|
if (!texture)
|
||||||
|
{
|
||||||
|
WARN_LOG_FMT(VIDEO, "Failed to allocate a {}x{}x{} texture", config.width, config.height,
|
||||||
|
config.layers);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AbstractFramebuffer> framebuffer;
|
||||||
|
if (config.IsRenderTarget())
|
||||||
|
{
|
||||||
|
framebuffer = g_gfx->CreateFramebuffer(texture.get(), nullptr);
|
||||||
|
if (!framebuffer)
|
||||||
|
{
|
||||||
|
WARN_LOG_FMT(VIDEO, "Failed to allocate a {}x{}x{} framebuffer", config.width, config.height,
|
||||||
|
config.layers);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TexPoolEntry(std::move(texture), std::move(framebuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomTextureCache::TexPool::iterator
|
||||||
|
CustomTextureCache::FindMatchingTextureFromPool(const TextureConfig& config)
|
||||||
|
{
|
||||||
|
// Find a texture from the pool that does not have a frameCount of FRAMECOUNT_INVALID.
|
||||||
|
// This prevents a texture from being used twice in a single frame with different data,
|
||||||
|
// which potentially means that a driver has to maintain two copies of the texture anyway.
|
||||||
|
// Render-target textures are fine through, as they have to be generated in a seperated pass.
|
||||||
|
// As non-render-target textures are usually static, this should not matter much.
|
||||||
|
auto range = m_texture_pool.equal_range(config);
|
||||||
|
auto matching_iter = std::find_if(range.first, range.second,
|
||||||
|
[](const auto& iter) { return iter.first.IsRenderTarget(); });
|
||||||
|
return matching_iter != range.second ? matching_iter : m_texture_pool.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomTextureCache::ReleaseToPool(CachedTextureAsset* entry)
|
||||||
|
{
|
||||||
|
if (!entry->texture)
|
||||||
|
return;
|
||||||
|
auto config = entry->texture->GetConfig();
|
||||||
|
m_texture_pool.emplace(config,
|
||||||
|
TexPoolEntry(std::move(entry->texture), std::move(entry->framebuffer)));
|
||||||
|
entry->texture = nullptr;
|
||||||
|
entry->framebuffer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomTextureCache::OnFrameEnd()
|
||||||
|
{
|
||||||
|
// Iterate over outstanding textures
|
||||||
|
{
|
||||||
|
auto iter = m_cached_texture_assets.begin();
|
||||||
|
const auto end = m_cached_texture_assets.end();
|
||||||
|
while (iter != end)
|
||||||
|
{
|
||||||
|
if ((std::chrono::steady_clock::now() - iter->second.time) > std::chrono::milliseconds{500})
|
||||||
|
{
|
||||||
|
ReleaseToPool(&iter->second);
|
||||||
|
iter = m_cached_texture_assets.erase(iter);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++iter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over pool
|
||||||
|
{
|
||||||
|
auto iter = m_texture_pool.begin();
|
||||||
|
const auto end = m_texture_pool.end();
|
||||||
|
while (iter != end)
|
||||||
|
{
|
||||||
|
if ((std::chrono::steady_clock::now() - iter->second.time) > std::chrono::milliseconds{250})
|
||||||
|
{
|
||||||
|
iter = m_texture_pool.erase(iter);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++iter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace VideoCommon
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2024 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Common/HookableEvent.h"
|
||||||
|
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||||
|
#include "VideoCommon/Assets/TextureAsset.h"
|
||||||
|
#include "VideoCommon/TextureConfig.h"
|
||||||
|
|
||||||
|
class AbstractFramebuffer;
|
||||||
|
class AbstractTexture;
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
class CustomAssetLoader;
|
||||||
|
class CustomTextureCache
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CustomTextureCache();
|
||||||
|
~CustomTextureCache();
|
||||||
|
|
||||||
|
struct TextureResult
|
||||||
|
{
|
||||||
|
AbstractTexture* texture;
|
||||||
|
AbstractFramebuffer* framebuffer;
|
||||||
|
std::shared_ptr<VideoCommon::TextureData> data;
|
||||||
|
};
|
||||||
|
std::optional<TextureResult> GetTextureAsset(CustomAssetLoader& loader,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> library,
|
||||||
|
const CustomAssetLibrary::AssetID& asset_id,
|
||||||
|
AbstractTextureType texture_type);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct TexPoolEntry
|
||||||
|
{
|
||||||
|
std::unique_ptr<AbstractTexture> texture;
|
||||||
|
std::unique_ptr<AbstractFramebuffer> framebuffer;
|
||||||
|
std::chrono::steady_clock::time_point time;
|
||||||
|
|
||||||
|
TexPoolEntry(std::unique_ptr<AbstractTexture> tex, std::unique_ptr<AbstractFramebuffer> fb);
|
||||||
|
};
|
||||||
|
|
||||||
|
using TexPool = std::unordered_multimap<TextureConfig, TexPoolEntry>;
|
||||||
|
|
||||||
|
std::optional<TexPoolEntry> AllocateTexture(const TextureConfig& config);
|
||||||
|
TexPool::iterator FindMatchingTextureFromPool(const TextureConfig& config);
|
||||||
|
|
||||||
|
TexPool m_texture_pool;
|
||||||
|
|
||||||
|
struct CachedTextureAsset
|
||||||
|
{
|
||||||
|
VideoCommon::CachedAsset<VideoCommon::GameTextureAsset> cached_asset;
|
||||||
|
std::unique_ptr<AbstractTexture> texture;
|
||||||
|
std::unique_ptr<AbstractFramebuffer> framebuffer;
|
||||||
|
std::chrono::steady_clock::time_point time;
|
||||||
|
};
|
||||||
|
|
||||||
|
void ReleaseToPool(CachedTextureAsset* entry);
|
||||||
|
|
||||||
|
std::unordered_map<VideoCommon::CustomAssetLibrary::AssetID, CachedTextureAsset>
|
||||||
|
m_cached_texture_assets;
|
||||||
|
|
||||||
|
void OnFrameEnd();
|
||||||
|
Common::EventHook m_frame_event;
|
||||||
|
};
|
||||||
|
} // namespace VideoCommon
|
Loading…
Add table
Add a link
Reference in a new issue