@@ -0,0 +1,279 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"

#include <string>
#include <string_view>
#include <variant>

#include "Common/Logging/Log.h"
#include "Common/VariantUtil.h"

#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h"
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h"
#include "VideoCommon/TextureInfo.h"

class GraphicsModManager::DecoratedAction final : public GraphicsModAction
{
public:
DecoratedAction(std::unique_ptr<GraphicsModAction> action, GraphicsModConfig mod)
: m_action_impl(std::move(action)), m_mod(std::move(mod))
{
}
void OnDrawStarted(bool* skip) override
{
if (!m_mod.m_enabled)
return;
m_action_impl->OnDrawStarted(skip);
}
void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width,
u32* scaled_height) override
{
if (!m_mod.m_enabled)
return;
m_action_impl->OnEFB(skip, texture_width, texture_height, scaled_width, scaled_height);
}
void OnProjection(Common::Matrix44* matrix) override
{
if (!m_mod.m_enabled)
return;
m_action_impl->OnProjection(matrix);
}
void OnProjectionAndTexture(Common::Matrix44* matrix) override
{
if (!m_mod.m_enabled)
return;
m_action_impl->OnProjectionAndTexture(matrix);
}
void OnTextureLoad() override
{
if (!m_mod.m_enabled)
return;
m_action_impl->OnTextureLoad();
}
void OnFrameEnd() override
{
if (!m_mod.m_enabled)
return;
m_action_impl->OnFrameEnd();
}

private:
GraphicsModConfig m_mod;
std::unique_ptr<GraphicsModAction> m_action_impl;
};

const std::vector<GraphicsModAction*>&
GraphicsModManager::GetProjectionActions(ProjectionType projection_type) const
{
if (const auto it = m_projection_target_to_actions.find(projection_type);
it != m_projection_target_to_actions.end())
{
return it->second;
}

return m_default;
}

const std::vector<GraphicsModAction*>&
GraphicsModManager::GetProjectionTextureActions(ProjectionType projection_type,
const std::string& texture_name) const
{
const auto lookup = fmt::format("{}_{}", texture_name, static_cast<int>(projection_type));
if (const auto it = m_projection_texture_target_to_actions.find(lookup);
it != m_projection_texture_target_to_actions.end())
{
return it->second;
}

return m_default;
}

const std::vector<GraphicsModAction*>&
GraphicsModManager::GetDrawStartedActions(const std::string& texture_name) const
{
if (const auto it = m_draw_started_target_to_actions.find(texture_name);
it != m_draw_started_target_to_actions.end())
{
return it->second;
}

return m_default;
}

const std::vector<GraphicsModAction*>&
GraphicsModManager::GetTextureLoadActions(const std::string& texture_name) const
{
if (const auto it = m_load_target_to_actions.find(texture_name);
it != m_load_target_to_actions.end())
{
return it->second;
}

return m_default;
}

const std::vector<GraphicsModAction*>& GraphicsModManager::GetEFBActions(const FBInfo& efb) const
{
if (const auto it = m_efb_target_to_actions.find(efb); it != m_efb_target_to_actions.end())
{
return it->second;
}

return m_default;
}

const std::vector<GraphicsModAction*>& GraphicsModManager::GetXFBActions(const FBInfo& xfb) const
{
if (const auto it = m_efb_target_to_actions.find(xfb); it != m_efb_target_to_actions.end())
{
return it->second;
}

return m_default;
}

void GraphicsModManager::Load(const GraphicsModGroupConfig& config)
{
Reset();

const auto& mods = config.GetMods();

std::map<std::string, std::vector<GraphicsTargetConfig>> group_to_targets;
for (const auto& mod : mods)
{
for (const GraphicsTargetGroupConfig& group : mod.m_groups)
{
if (m_groups.find(group.m_name) != m_groups.end())
{
WARN_LOG_FMT(
VIDEO,
"Specified graphics mod group '{}' for mod '{}' is already specified by another mod.",
group.m_name, mod.m_title);
}
m_groups.insert(group.m_name);

const auto internal_group = fmt::format("{}.{}", mod.m_title, group.m_name);
for (const GraphicsTargetConfig& target : group.m_targets)
{
group_to_targets[group.m_name].push_back(target);
group_to_targets[internal_group].push_back(target);
}
}
}

for (const auto& mod : mods)
{
for (const GraphicsModFeatureConfig& feature : mod.m_features)
{
const auto create_action = [](const std::string_view& action_name,
const picojson::value& json_data,
GraphicsModConfig mod) -> std::unique_ptr<GraphicsModAction> {
auto action = GraphicsModActionFactory::Create(action_name, json_data);
if (action == nullptr)
{
return nullptr;
}
return std::make_unique<DecoratedAction>(std::move(action), std::move(mod));
};

const auto internal_group = fmt::format("{}.{}", mod.m_title, feature.m_group);

const auto add_target = [&](const GraphicsTargetConfig& target, GraphicsModConfig mod) {
auto action = create_action(feature.m_action, feature.m_action_data, std::move(mod));
if (action == nullptr)
{
WARN_LOG_FMT(VIDEO, "Failed to create action '{}' for group '{}'.", feature.m_action,
feature.m_group);
return;
}
m_actions.push_back(std::move(action));
std::visit(
overloaded{
[&](const DrawStartedTextureTarget& the_target) {
m_draw_started_target_to_actions[the_target.m_texture_info_string].push_back(
m_actions.back().get());
},
[&](const LoadTextureTarget& the_target) {
m_load_target_to_actions[the_target.m_texture_info_string].push_back(
m_actions.back().get());
},
[&](const EFBTarget& the_target) {
FBInfo info;
info.m_height = the_target.m_height;
info.m_width = the_target.m_width;
info.m_texture_format = the_target.m_texture_format;
m_efb_target_to_actions[info].push_back(m_actions.back().get());
},
[&](const XFBTarget& the_target) {
FBInfo info;
info.m_height = the_target.m_height;
info.m_width = the_target.m_width;
info.m_texture_format = the_target.m_texture_format;
m_xfb_target_to_actions[info].push_back(m_actions.back().get());
},
[&](const ProjectionTarget& the_target) {
if (the_target.m_texture_info_string)
{
const auto lookup = fmt::format("{}_{}", *the_target.m_texture_info_string,
static_cast<int>(the_target.m_projection_type));
m_projection_texture_target_to_actions[lookup].push_back(
m_actions.back().get());
}
else
{
m_projection_target_to_actions[the_target.m_projection_type].push_back(
m_actions.back().get());
}
},
},
target);
};

// Prefer groups in the pack over groups from another pack
if (const auto local_it = group_to_targets.find(internal_group);
local_it != group_to_targets.end())
{
for (const GraphicsTargetConfig& target : local_it->second)
{
add_target(target, mod);
}
}
else if (const auto global_it = group_to_targets.find(feature.m_group);
global_it != group_to_targets.end())
{
for (const GraphicsTargetConfig& target : global_it->second)
{
add_target(target, mod);
}
}
else
{
WARN_LOG_FMT(VIDEO, "Specified graphics mod group '{}' was not found for mod '{}'",
feature.m_group, mod.m_title);
}
}
}
}

void GraphicsModManager::EndOfFrame()
{
for (auto&& action : m_actions)
{
action->OnFrameEnd();
}
}

void GraphicsModManager::Reset()
{
m_actions.clear();
m_groups.clear();
m_projection_target_to_actions.clear();
m_projection_texture_target_to_actions.clear();
m_draw_started_target_to_actions.clear();
m_load_target_to_actions.clear();
m_efb_target_to_actions.clear();
m_xfb_target_to_actions.clear();
}
@@ -0,0 +1,54 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <list>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h"
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
#include "VideoCommon/TextureInfo.h"
#include "VideoCommon/XFMemory.h"

class GraphicsModGroupConfig;
class GraphicsModManager
{
public:
const std::vector<GraphicsModAction*>& GetProjectionActions(ProjectionType projection_type) const;
const std::vector<GraphicsModAction*>&
GetProjectionTextureActions(ProjectionType projection_type,
const std::string& texture_name) const;
const std::vector<GraphicsModAction*>&
GetDrawStartedActions(const std::string& texture_name) const;
const std::vector<GraphicsModAction*>&
GetTextureLoadActions(const std::string& texture_name) const;
const std::vector<GraphicsModAction*>& GetEFBActions(const FBInfo& efb) const;
const std::vector<GraphicsModAction*>& GetXFBActions(const FBInfo& xfb) const;

void Load(const GraphicsModGroupConfig& config);

void EndOfFrame();

private:
void Reset();

class DecoratedAction;

static inline const std::vector<GraphicsModAction*> m_default = {};
std::list<std::unique_ptr<GraphicsModAction>> m_actions;
std::unordered_map<ProjectionType, std::vector<GraphicsModAction*>>
m_projection_target_to_actions;
std::unordered_map<std::string, std::vector<GraphicsModAction*>>
m_projection_texture_target_to_actions;
std::unordered_map<std::string, std::vector<GraphicsModAction*>> m_draw_started_target_to_actions;
std::unordered_map<std::string, std::vector<GraphicsModAction*>> m_load_target_to_actions;
std::unordered_map<FBInfo, std::vector<GraphicsModAction*>, FBInfoHasher> m_efb_target_to_actions;
std::unordered_map<FBInfo, std::vector<GraphicsModAction*>, FBInfoHasher> m_xfb_target_to_actions;

std::unordered_set<std::string> m_groups;
};
@@ -213,7 +213,7 @@ void HiresTexture::Prefetch()
10000);
}

std::string HiresTexture::GenBaseName(TextureInfo& texture_info, bool dump)
std::string HiresTexture::GenBaseName(const TextureInfo& texture_info, bool dump)
{
if (!dump && s_textureMap.empty())
return "";
@@ -261,7 +261,7 @@ u32 HiresTexture::CalculateMipCount(u32 width, u32 height)
return mip_count;
}

std::shared_ptr<HiresTexture> HiresTexture::Search(TextureInfo& texture_info)
std::shared_ptr<HiresTexture> HiresTexture::Search(const TextureInfo& texture_info)
{
const std::string base_filename = GenBaseName(texture_info);

@@ -433,17 +433,17 @@ std::set<std::string> GetTextureDirectoriesWithGameId(const std::string& root_di
}
}

const auto match_gameid = [game_id](const std::string& filename) {
const auto match_gameid_or_all = [game_id](const std::string& filename) {
std::string basename;
SplitPath(filename, nullptr, &basename, nullptr);
return basename == game_id || basename == game_id.substr(0, 3);
return basename == game_id || basename == game_id.substr(0, 3) || basename == "all";
};

// Look for any other directories that might be specific to the given gameid
const auto files = Common::DoFileSearch({root_directory}, {".txt"}, true);
for (const auto& file : files)
{
if (match_gameid(file))
if (match_gameid_or_all(file))
{
// The following code is used to calculate the top directory
// of a found gameid.txt file
@@ -25,9 +25,9 @@ class HiresTexture
static void Clear();
static void Shutdown();

static std::shared_ptr<HiresTexture> Search(TextureInfo& texture_info);
static std::shared_ptr<HiresTexture> Search(const TextureInfo& texture_info);

static std::string GenBaseName(TextureInfo& texture_info, bool dump = false);
static std::string GenBaseName(const TextureInfo& texture_info, bool dump = false);

static u32 CalculateMipCount(u32 width, u32 height);

@@ -67,6 +67,7 @@
#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/FramebufferShaderGen.h"
#include "VideoCommon/FreeLookCamera.h"
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h"
#include "VideoCommon/NetPlayChatUI.h"
#include "VideoCommon/NetPlayGolfUI.h"
#include "VideoCommon/OnScreenDisplay.h"
@@ -135,6 +136,22 @@ bool Renderer::Initialize()
return false;
}

if (g_ActiveConfig.bGraphicMods)
{
// If a config change occurred in a previous session,
// remember the old change count value. By setting
// our current change count to the old value, we
// avoid loading the stale data when we
// check for config changes.
const u32 old_game_mod_changes = g_ActiveConfig.graphics_mod_config ?
g_ActiveConfig.graphics_mod_config->GetChangeCount() :
0;
g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID());
g_ActiveConfig.graphics_mod_config->Load();
g_ActiveConfig.graphics_mod_config->SetChangeCount(old_game_mod_changes);
m_graphics_mod_manager.Load(*g_ActiveConfig.graphics_mod_config);
}

return true;
}

@@ -465,12 +482,27 @@ void Renderer::CheckForConfigChanges()
const bool old_force_filtering = g_ActiveConfig.bForceFiltering;
const bool old_vsync = g_ActiveConfig.bVSyncActive;
const bool old_bbox = g_ActiveConfig.bBBoxEnable;
const u32 old_game_mod_changes =
g_ActiveConfig.graphics_mod_config ? g_ActiveConfig.graphics_mod_config->GetChangeCount() : 0;
const bool old_graphics_mods_enabled = g_ActiveConfig.bGraphicMods;

UpdateActiveConfig();
FreeLook::UpdateActiveConfig();

g_freelook_camera.SetControlType(FreeLook::GetActiveConfig().camera_config.control_type);

if (g_ActiveConfig.bGraphicMods && !old_graphics_mods_enabled)
{
g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID());
g_ActiveConfig.graphics_mod_config->Load();
}

if (g_ActiveConfig.graphics_mod_config &&
(old_game_mod_changes != g_ActiveConfig.graphics_mod_config->GetChangeCount()))
{
m_graphics_mod_manager.Load(*g_ActiveConfig.graphics_mod_config);
}

// Update texture cache settings with any changed options.
g_texture_cache->OnConfigChanged(g_ActiveConfig);

@@ -1309,6 +1341,11 @@ void Renderer::Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u6
// behind the renderer.
FlushFrameDump();

if (g_ActiveConfig.bGraphicMods)
{
m_graphics_mod_manager.EndOfFrame();
}

if (xfb_addr && fb_width && fb_stride && fb_height)
{
// Get the current XFB from texture cache
@@ -1830,3 +1867,8 @@ std::unique_ptr<VideoCommon::AsyncShaderCompiler> Renderer::CreateAsyncShaderCom
{
return std::make_unique<VideoCommon::AsyncShaderCompiler>();
}

const GraphicsModManager& Renderer::GetGraphicsModManager() const
{
return m_graphics_mod_manager;
}
@@ -30,6 +30,7 @@
#include "VideoCommon/BPMemory.h"
#include "VideoCommon/FPSCounter.h"
#include "VideoCommon/FrameDump.h"
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"
#include "VideoCommon/RenderState.h"
#include "VideoCommon/TextureConfig.h"

@@ -268,6 +269,8 @@ class Renderer
// Will forcibly reload all textures on the next swap
void ForceReloadTextures();

const GraphicsModManager& GetGraphicsModManager() const;

protected:
// Bitmask containing information about which configuration has changed for the backend.
enum ConfigChangeBits : u32
@@ -447,6 +450,8 @@ class Renderer
std::unique_ptr<NetPlayChatUI> m_netplay_chat_ui;

Common::Flag m_force_reload_textures;

GraphicsModManager m_graphics_mod_manager;
};

extern std::unique_ptr<Renderer> g_renderer;
@@ -36,6 +36,7 @@
#include "VideoCommon/AbstractStagingTexture.h"
#include "VideoCommon/BPMemory.h"
#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h"
#include "VideoCommon/HiresTextures.h"
#include "VideoCommon/OpcodeDecoding.h"
#include "VideoCommon/PixelShaderManager.h"
@@ -55,6 +56,8 @@ static const u64 TEXHASH_INVALID = 0;
static const int TEXTURE_KILL_THRESHOLD = 64;
static const int TEXTURE_POOL_KILL_THRESHOLD = 3;

static int xfb_count = 0;

std::unique_ptr<TextureCacheBase> g_texture_cache;

TextureCacheBase::TCacheEntry::TCacheEntry(std::unique_ptr<AbstractTexture> tex,
@@ -153,14 +156,19 @@ void TextureCacheBase::OnConfigChanged(const VideoConfig& config)
HiresTexture::Update();
}

const u32 change_count =
config.graphics_mod_config ? config.graphics_mod_config->GetChangeCount() : 0;

// TODO: Invalidating texcache is really stupid in some of these cases
if (config.iSafeTextureCache_ColorSamples != backup_config.color_samples ||
config.bTexFmtOverlayEnable != backup_config.texfmt_overlay ||
config.bTexFmtOverlayCenter != backup_config.texfmt_overlay_center ||
config.bHiresTextures != backup_config.hires_textures ||
config.bEnableGPUTextureDecoding != backup_config.gpu_texture_decoding ||
config.bDisableCopyToVRAM != backup_config.disable_vram_copies ||
config.bArbitraryMipmapDetection != backup_config.arbitrary_mipmap_detection)
config.bArbitraryMipmapDetection != backup_config.arbitrary_mipmap_detection ||
config.bGraphicMods != backup_config.graphics_mods ||
change_count != backup_config.graphics_mod_change_count)
{
Invalidate();
TexDecoder_SetTexFmtOverlayOptions(config.bTexFmtOverlayEnable, config.bTexFmtOverlayCenter);
@@ -255,6 +263,9 @@ void TextureCacheBase::SetBackupConfig(const VideoConfig& config)
backup_config.gpu_texture_decoding = config.bEnableGPUTextureDecoding;
backup_config.disable_vram_copies = config.bDisableCopyToVRAM;
backup_config.arbitrary_mipmap_detection = config.bArbitraryMipmapDetection;
backup_config.graphics_mods = config.bGraphicMods;
backup_config.graphics_mod_change_count =
config.graphics_mod_config ? config.graphics_mod_config->GetChangeCount() : 0;
}

TextureCacheBase::TCacheEntry*
@@ -1205,15 +1216,15 @@ class ArbitraryMipmapDetector
std::vector<Level> levels;
};

TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const TextureInfo& texture_info)
{
// if this stage was not invalidated by changes to texture registers, keep the current texture
if (TMEM::IsValid(stage) && bound_textures[stage])
if (TMEM::IsValid(texture_info.GetStage()) && bound_textures[texture_info.GetStage()])
{
TCacheEntry* entry = bound_textures[stage];
TCacheEntry* entry = bound_textures[texture_info.GetStage()];
// If the TMEM configuration is such that this texture is more or less guaranteed to still
// be in TMEM, then we know we can reuse the old entry without even hashing the memory
if (TMEM::IsCached(stage))
if (TMEM::IsCached(texture_info.GetStage()))
{
return entry;
}
@@ -1226,26 +1237,29 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
}
}

TextureInfo texture_info = TextureInfo::FromStage(stage);

auto entry = GetTexture(g_ActiveConfig.iSafeTextureCache_ColorSamples, texture_info);

if (!entry)
return nullptr;

entry->frameCount = FRAMECOUNT_INVALID;
bound_textures[stage] = entry;
if (entry->texture_info_name.empty() && g_ActiveConfig.bGraphicMods)
{
entry->texture_info_name = texture_info.CalculateTextureName().GetFullName();
}
bound_textures[texture_info.GetStage()] = entry;

// We need to keep track of invalided textures until they have actually been replaced or
// re-loaded
TMEM::Bind(stage, entry->NumBlocksX(), entry->NumBlocksY(), entry->GetNumLevels() > 1,
entry->format == TextureFormat::RGBA8);
TMEM::Bind(texture_info.GetStage(), entry->NumBlocksX(), entry->NumBlocksY(),
entry->GetNumLevels() > 1, entry->format == TextureFormat::RGBA8);

return entry;
}

TextureCacheBase::TCacheEntry*
TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, TextureInfo& texture_info)
TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize,
const TextureInfo& texture_info)
{
u32 expanded_width = texture_info.GetExpandedWidth();
u32 expanded_height = texture_info.GetExpandedHeight();
@@ -1764,12 +1778,20 @@ TextureCacheBase::GetXFBTexture(u32 address, u32 width, u32 height, u32 stride,
SETSTAT(g_stats.num_textures_alive, static_cast<int>(textures_by_address.size()));
INCSTAT(g_stats.num_textures_uploaded);

if (g_ActiveConfig.bDumpXFBTarget)
if (g_ActiveConfig.bDumpXFBTarget || g_ActiveConfig.bGraphicMods)
{
// While this isn't really an xfb copy, we can treat it as such for dumping purposes
static int xfb_count = 0;
entry->texture->Save(
fmt::format("{}xfb_loaded_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX), xfb_count++), 0);
const std::string id = fmt::format("{}x{}", width, height);
if (g_ActiveConfig.bGraphicMods)
{
entry->texture_info_name = fmt::format("{}_{}", XFB_DUMP_PREFIX, id);
}

if (g_ActiveConfig.bDumpXFBTarget)
{
entry->texture->Save(fmt::format("{}{}_n{:06}_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX),
XFB_DUMP_PREFIX, xfb_count++, id),
0);
}
}

GetDisplayRectForXFBEntry(entry, width, height, display_rect);
@@ -2119,6 +2141,35 @@ void TextureCacheBase::CopyRenderTargetToTexture(
const u32 bytes_per_row = num_blocks_x * bytes_per_block;
const u32 covered_range = num_blocks_y * dstStride;

if (g_ActiveConfig.bGraphicMods)
{
FBInfo info;
info.m_width = tex_w;
info.m_height = tex_h;
info.m_texture_format = baseFormat;
if (is_xfb_copy)
{
for (const auto action : g_renderer->GetGraphicsModManager().GetXFBActions(info))
{
action->OnXFB();
}
}
else
{
bool skip = false;
for (const auto action : g_renderer->GetGraphicsModManager().GetEFBActions(info))
{
action->OnEFB(&skip, tex_w, tex_h, &scaled_tex_w, &scaled_tex_h);
}
if (skip == true)
{
if (copy_to_ram)
UninitializeEFBMemory(dst, dstStride, bytes_per_row, num_blocks_y);
return;
}
}
}

if (dstStride < bytes_per_row)
{
// This kind of efb copy results in a scrambled image.
@@ -2168,20 +2219,38 @@ void TextureCacheBase::CopyRenderTargetToTexture(
isIntensity, gamma, clamp_top, clamp_bottom,
GetVRAMCopyFilterCoefficients(filter_coefficients));

if (g_ActiveConfig.bDumpEFBTarget && !is_xfb_copy)
if (is_xfb_copy && (g_ActiveConfig.bDumpXFBTarget || g_ActiveConfig.bGraphicMods))
{
static int efb_count = 0;
entry->texture->Save(
fmt::format("{}efb_frame_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX), efb_count++),
0);
}
const std::string id = fmt::format("{}x{}", tex_w, tex_h);
if (g_ActiveConfig.bGraphicMods)
{
entry->texture_info_name = fmt::format("{}_{}", XFB_DUMP_PREFIX, id);
}

if (g_ActiveConfig.bDumpXFBTarget && is_xfb_copy)
if (g_ActiveConfig.bDumpXFBTarget)
{
entry->texture->Save(fmt::format("{}{}_n{:06}_{}.png",
File::GetUserPath(D_DUMPTEXTURES_IDX), XFB_DUMP_PREFIX,
xfb_count++, id),
0);
}
}
else if (g_ActiveConfig.bDumpEFBTarget || g_ActiveConfig.bGraphicMods)
{
static int xfb_count = 0;
entry->texture->Save(
fmt::format("{}xfb_copy_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX), xfb_count++),
0);
const std::string id = fmt::format("{}x{}_{}", tex_w, tex_h, static_cast<int>(baseFormat));
if (g_ActiveConfig.bGraphicMods)
{
entry->texture_info_name = fmt::format("{}_{}", EFB_DUMP_PREFIX, id);
}

if (g_ActiveConfig.bDumpEFBTarget)
{
static int efb_count = 0;
entry->texture->Save(fmt::format("{}{}_n{:06}_{}.png",
File::GetUserPath(D_DUMPTEXTURES_IDX), EFB_DUMP_PREFIX,
efb_count++, id),
0);
}
}
}
}
@@ -2225,15 +2294,7 @@ void TextureCacheBase::CopyRenderTargetToTexture(
}
else
{
// Hack: Most games don't actually need the correct texture data in RAM
// and we can just keep a copy in VRAM. We zero the memory so we
// can check it hasn't changed before using our copy in VRAM.
u8* ptr = dst;
for (u32 i = 0; i < num_blocks_y; i++)
{
std::memset(ptr, 0, bytes_per_row);
ptr += dstStride;
}
UninitializeEFBMemory(dst, dstStride, bytes_per_row, num_blocks_y);
}
}

@@ -2403,6 +2464,20 @@ void TextureCacheBase::ReleaseEFBCopyStagingTexture(std::unique_ptr<AbstractStag
m_efb_copy_staging_texture_pool.push_back(std::move(tex));
}

void TextureCacheBase::UninitializeEFBMemory(u8* dst, u32 stride, u32 bytes_per_row,
u32 num_blocks_y)
{
// Hack: Most games don't actually need the correct texture data in RAM
// and we can just keep a copy in VRAM. We zero the memory so we
// can check it hasn't changed before using our copy in VRAM.
u8* ptr = dst;
for (u32 i = 0; i < num_blocks_y; i++)
{
std::memset(ptr, 0, bytes_per_row);
ptr += stride;
}
}

void TextureCacheBase::UninitializeXFBMemory(u8* dst, u32 stride, u32 bytes_per_row,
u32 num_blocks_y)
{
@@ -10,6 +10,7 @@
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
@@ -29,6 +30,9 @@ class AbstractStagingTexture;
class PointerWrap;
struct VideoConfig;

constexpr std::string_view EFB_DUMP_PREFIX = "efb1";
constexpr std::string_view XFB_DUMP_PREFIX = "xfb1";

struct TextureAndTLUTFormat
{
TextureAndTLUTFormat(TextureFormat texfmt_ = TextureFormat::I4,
@@ -151,6 +155,8 @@ class TextureCacheBase
u32 pending_efb_copy_height = 0;
bool pending_efb_copy_invalidated = false;

std::string texture_info_name = "";

explicit TCacheEntry(std::unique_ptr<AbstractTexture> tex,
std::unique_ptr<AbstractFramebuffer> fb);

@@ -235,8 +241,9 @@ class TextureCacheBase

void Invalidate();

TCacheEntry* Load(const u32 stage);
TCacheEntry* GetTexture(const int textureCacheSafetyColorSampleSize, TextureInfo& texture_info);
TCacheEntry* Load(const TextureInfo& texture_info);
TCacheEntry* GetTexture(const int textureCacheSafetyColorSampleSize,
const TextureInfo& texture_info);
TCacheEntry* GetXFBTexture(u32 address, u32 width, u32 height, u32 stride,
MathUtil::Rectangle<int>* display_rect);

@@ -327,6 +334,7 @@ class TextureCacheBase
TexAddrCache::iterator InvalidateTexture(TexAddrCache::iterator t_iter,
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);

// Precomputing the coefficients for the previous, current, and next lines for the copy filter.
@@ -369,6 +377,8 @@ class TextureCacheBase
bool gpu_texture_decoding;
bool disable_vram_copies;
bool arbitrary_mipmap_detection;
bool graphics_mods;
u32 graphics_mod_change_count;
};
BackupConfig backup_config = {};

@@ -50,6 +50,11 @@ static inline bool IsColorIndexed(TextureFormat format)
format == TextureFormat::C14X2;
}

static inline bool IsValidTextureFormat(TextureFormat format)
{
return format <= TextureFormat::RGBA8 || IsColorIndexed(format) || format == TextureFormat::CMPR;
}

// The EFB Copy pipeline looks like:
//
// 1. Read EFB -> 2. Select color/depth -> 3. Downscale (optional)
@@ -39,20 +39,20 @@ TextureInfo TextureInfo::FromStage(u32 stage)

if (from_tmem)
{
return TextureInfo(&texMem[tmem_address_even], tlut_ptr, address, texture_format, tlut_format,
width, height, true, &texMem[tmem_address_odd], &texMem[tmem_address_even],
mip_count);
return TextureInfo(stage, &texMem[tmem_address_even], tlut_ptr, address, texture_format,
tlut_format, width, height, true, &texMem[tmem_address_odd],
&texMem[tmem_address_even], mip_count);
}

return TextureInfo(Memory::GetPointer(address), tlut_ptr, address, texture_format, tlut_format,
width, height, false, nullptr, nullptr, mip_count);
return TextureInfo(stage, Memory::GetPointer(address), tlut_ptr, address, texture_format,
tlut_format, width, height, false, nullptr, nullptr, mip_count);
}

TextureInfo::TextureInfo(const u8* ptr, const u8* tlut_ptr, u32 address,
TextureInfo::TextureInfo(u32 stage, const u8* ptr, const u8* tlut_ptr, u32 address,
TextureFormat texture_format, TLUTFormat tlut_format, u32 width,
u32 height, bool from_tmem, const u8* tmem_odd, const u8* tmem_even,
std::optional<u32> mip_count)
: m_ptr(ptr), m_tlut_ptr(tlut_ptr), m_address(address), m_from_tmem(from_tmem),
: m_stage(stage), m_ptr(ptr), m_tlut_ptr(tlut_ptr), m_address(address), m_from_tmem(from_tmem),
m_tmem_odd(tmem_odd), m_texture_format(texture_format), m_tlut_format(tlut_format),
m_raw_width(width), m_raw_height(height)
{
@@ -100,7 +100,7 @@ std::string TextureInfo::NameDetails::GetFullName() const
return fmt::format("{}_{}{}_{}", base_name, texture_name, tlut_name, format_name);
}

TextureInfo::NameDetails TextureInfo::CalculateTextureName()
TextureInfo::NameDetails TextureInfo::CalculateTextureName() const
{
if (!m_ptr)
return NameDetails{};
@@ -240,6 +240,11 @@ u32 TextureInfo::GetRawHeight() const
return m_raw_height;
}

u32 TextureInfo::GetStage() const
{
return m_stage;
}

bool TextureInfo::HasMipMaps() const
{
return !m_mip_levels.empty();
@@ -17,9 +17,10 @@ class TextureInfo
{
public:
static TextureInfo FromStage(u32 stage);
TextureInfo(const u8* ptr, const u8* tlut_ptr, u32 address, TextureFormat texture_format,
TLUTFormat tlut_format, u32 width, u32 height, bool from_tmem, const u8* tmem_odd,
const u8* tmem_even, std::optional<u32> mip_count);
TextureInfo(u32 stage, const u8* ptr, const u8* tlut_ptr, u32 address,
TextureFormat texture_format, TLUTFormat tlut_format, u32 width, u32 height,
bool from_tmem, const u8* tmem_odd, const u8* tmem_even,
std::optional<u32> mip_count);

struct NameDetails
{
@@ -30,7 +31,7 @@ class TextureInfo

std::string GetFullName() const;
};
NameDetails CalculateTextureName();
NameDetails CalculateTextureName() const;

const u8* GetData() const;
const u8* GetTlutAddress() const;
@@ -55,6 +56,8 @@ class TextureInfo
u32 GetRawWidth() const;
u32 GetRawHeight() const;

u32 GetStage() const;

class MipLevel
{
public:
@@ -115,4 +118,6 @@ class TextureInfo
u32 m_block_height;
u32 m_expanded_height;
u32 m_raw_height;

u32 m_stage;
};
@@ -7,7 +7,6 @@
#include <cmath>
#include <memory>

#include "Common/BitSet.h"
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Common/EnumMap.h"
@@ -30,6 +29,7 @@
#include "VideoCommon/RenderBase.h"
#include "VideoCommon/Statistics.h"
#include "VideoCommon/TextureCacheBase.h"
#include "VideoCommon/TextureInfo.h"
#include "VideoCommon/VertexLoaderManager.h"
#include "VideoCommon/VertexShaderManager.h"
#include "VideoCommon/VideoBackendBase.h"
@@ -337,7 +337,7 @@ bool VertexManagerBase::UploadTexelBuffer(const void* data, u32 data_size, Texel
return false;
}

void VertexManagerBase::LoadTextures()
BitSet32 VertexManagerBase::UsedTextures() const
{
BitSet32 usedtextures;
for (u32 i = 0; i < bpmem.genMode.numtevstages + 1u; ++i)
@@ -349,10 +349,7 @@ void VertexManagerBase::LoadTextures()
if (bpmem.tevind[i].IsActive() && bpmem.tevind[i].bt < bpmem.genMode.numindstages)
usedtextures[bpmem.tevindref.getTexMap(bpmem.tevind[i].bt)] = true;

for (unsigned int i : usedtextures)
g_texture_cache->Load(i);

g_texture_cache->BindTextures(usedtextures);
return usedtextures;
}

void VertexManagerBase::Flush()
@@ -455,7 +452,30 @@ void VertexManagerBase::Flush()

CalculateBinormals(VertexLoaderManager::GetCurrentVertexFormat());
// Calculate ZSlope for zfreeze
VertexShaderManager::SetConstants();
const auto used_textures = UsedTextures();
std::vector<std::string> texture_names;
if (!m_cull_all)
{
if (!g_ActiveConfig.bGraphicMods)
{
for (const u32 i : used_textures)
{
g_texture_cache->Load(TextureInfo::FromStage(i));
}
}
else
{
for (const u32 i : used_textures)
{
const auto cache_entry = g_texture_cache->Load(TextureInfo::FromStage(i));
if (cache_entry)
{
texture_names.push_back(cache_entry->texture_info_name);
}
}
}
}
VertexShaderManager::SetConstants(texture_names);
if (!bpmem.genMode.zfreeze)
{
// Must be done after VertexShaderManager::SetConstants()
@@ -469,6 +489,18 @@ void VertexManagerBase::Flush()

if (!m_cull_all)
{
for (const auto& texture_name : texture_names)
{
bool skip = false;
for (const auto action :
g_renderer->GetGraphicsModManager().GetDrawStartedActions(texture_name))
{
action->OnDrawStarted(&skip);
}
if (skip == true)
return;
}

// Now the vertices can be flushed to the GPU. Everything following the CommitBuffer() call
// must be careful to not upload any utility vertices, as the binding will be lost otherwise.
const u32 num_indices = m_index_generator.GetIndexLen();
@@ -480,7 +512,7 @@ void VertexManagerBase::Flush()
// Texture loading can cause palettes to be applied (-> uniforms -> draws).
// Palette application does not use vertices, only a full-screen quad, so this is okay.
// Same with GPU texture decoding, which uses compute shaders.
LoadTextures();
g_texture_cache->BindTextures(used_textures);

// Now we can upload uniforms, as nothing else will override them.
GeometryShaderManager::SetConstants();
@@ -6,6 +6,7 @@
#include <memory>
#include <vector>

#include "Common/BitSet.h"
#include "Common/CommonTypes.h"
#include "Common/MathUtil.h"
#include "VideoCommon/IndexGenerator.h"
@@ -173,7 +174,8 @@ class VertexManagerBase

void CalculateZSlope(NativeVertexFormat* format);
void CalculateBinormals(NativeVertexFormat* format);
void LoadTextures();

BitSet32 UsedTextures() const;

u8* m_cur_buffer_pointer = nullptr;
u8* m_base_buffer_pointer = nullptr;
@@ -85,7 +85,7 @@ void VertexShaderManager::Dirty()

// Syncs the shader constant buffers with xfmem
// TODO: A cleaner way to control the matrices without making a mess in the parameters field
void VertexShaderManager::SetConstants()
void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
{
if (constants.missing_color_hex != g_ActiveConfig.iMissingColorValue)
{
@@ -302,7 +302,27 @@ void VertexShaderManager::SetConstants()
g_stats.AddScissorRect();
}

if (bProjectionChanged || g_freelook_camera.GetController()->IsDirty())
std::vector<GraphicsModAction*> projection_actions;
if (g_ActiveConfig.bGraphicMods)
{
for (const auto action :
g_renderer->GetGraphicsModManager().GetProjectionActions(xfmem.projection.type))
{
projection_actions.push_back(action);
}

for (const auto& texture : textures)
{
for (const auto action : g_renderer->GetGraphicsModManager().GetProjectionTextureActions(
xfmem.projection.type, texture))
{
projection_actions.push_back(action);
}
}
}

if (bProjectionChanged || g_freelook_camera.GetController()->IsDirty() ||
!projection_actions.empty())
{
bProjectionChanged = false;

@@ -384,6 +404,11 @@ void VertexShaderManager::SetConstants()
if (g_freelook_camera.IsActive() && xfmem.projection.type == ProjectionType::Perspective)
corrected_matrix *= g_freelook_camera.GetView();

for (auto action : projection_actions)
{
action->OnProjection(&corrected_matrix);
}

memcpy(constants.projection.data(), corrected_matrix.data.data(), 4 * sizeof(float4));

g_freelook_camera.GetController()->SetClean();
@@ -4,6 +4,7 @@
#pragma once

#include <string>
#include <vector>

#include "Common/CommonTypes.h"
#include "VideoCommon/ConstantManager.h"
@@ -19,7 +20,7 @@ class VertexShaderManager
static void DoState(PointerWrap& p);

// constant management
static void SetConstants();
static void SetConstants(const std::vector<std::string>& textures);

static void InvalidateXFRange(int start, int end);
static void SetTexMatrixChangedA(u32 value);
@@ -145,6 +145,8 @@ void VideoConfig::Refresh()
bFastTextureSampling = Config::Get(Config::GFX_HACK_FAST_TEXTURE_SAMPLING);

bPerfQueriesEnable = Config::Get(Config::GFX_PERF_QUERIES_ENABLE);

bGraphicMods = Config::Get(Config::GFX_MODS_ENABLE);
}

void VideoConfig::VerifyValidity()
@@ -9,10 +9,12 @@

#pragma once

#include <optional>
#include <string>
#include <vector>

#include "Common/CommonTypes.h"
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h"
#include "VideoCommon/VideoCommon.h"

// Log in two categories, and save three other options in the same byte
@@ -111,6 +113,8 @@ struct VideoConfig final
bool bBorderlessFullscreen = false;
bool bEnableGPUTextureDecoding = false;
int iBitrateKbps = 0;
bool bGraphicMods = false;
std::optional<GraphicsModGroupConfig> graphics_mod_config;

// Hacks
bool bEFBAccessEnable = false;