Runtime texture replacement API

This commit is contained in:
Luke Street
2026-05-29 21:11:39 -06:00
parent 0b689d7c6a
commit e30fe08670
11 changed files with 554 additions and 433 deletions
+1 -2
View File
@@ -6,5 +6,4 @@ set_target_properties(aurora_dvd PROPERTIES FOLDER "aurora")
target_compile_definitions(aurora_dvd PUBLIC AURORA TARGET_PC)
target_include_directories(aurora_dvd PUBLIC include)
target_link_libraries(aurora_dvd PUBLIC nod::nod ${AURORA_SDL3_TARGET})
target_link_libraries(aurora_dvd PRIVATE fmt::fmt)
target_link_libraries(aurora_dvd PUBLIC fmt::fmt nod::nod ${AURORA_SDL3_TARGET})
-1
View File
@@ -90,7 +90,6 @@ typedef struct {
bool startFullscreen;
bool allowJoystickBackgroundEvents;
bool pauseOnFocusLost;
bool allowTextureReplacements;
bool allowTextureDumps;
bool allowCpuAdapter;
int32_t windowPosX;
-2
View File
@@ -31,8 +31,6 @@ typedef struct {
const AuroraStats* aurora_get_stats();
void aurora_enable_vsync(bool enabled);
void aurora_set_texture_replacements_enabled(bool enabled);
void aurora_reload_texture_replacements();
#ifdef __cplusplus
}
-39
View File
@@ -1,39 +0,0 @@
#ifndef AURORA_HD_TEXTURE_HPP
#define AURORA_HD_TEXTURE_HPP
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
namespace aurora::gfx {
// HD texture replacement: dusk's tphd layer registers a pixel-pointer ->
// decoded HD buffer mapping at arc-load time; init_texobj_common swaps in
// the HD bytes when the game calls GXInitTexObj with a registered pointer.
struct HdReplacement {
std::vector<uint8_t> bytes;
uint32_t width;
uint32_t height;
uint32_t gxFormat;
uint32_t mipCount = 1;
};
void hd_register_replacement(const void* gcDataPtr, HdReplacement replacement) noexcept;
const HdReplacement* hd_lookup_replacement(const void* gcDataPtr) noexcept;
void hd_clear_replacements() noexcept;
struct HdArcRange {
const void* begin;
size_t size;
std::string label;
};
void hd_register_arc_range(const void* begin, size_t size, std::string_view label) noexcept;
const HdArcRange* hd_find_arc_range(const void* ptr, size_t* out_remaining) noexcept;
void hd_clear_arc_ranges() noexcept;
} // namespace aurora::gfx
#endif
+68
View File
@@ -0,0 +1,68 @@
#ifndef AURORA_TEXTURE_HPP
#define AURORA_TEXTURE_HPP
#include <cstdint>
#include <filesystem>
#include <span>
#include <string_view>
#include <variant>
#include <vector>
namespace aurora::texture {
struct TextureSourceKey {
uint64_t textureHash = 0;
uint64_t tlutHash = 0;
uint32_t width = 0;
uint32_t height = 0;
uint32_t format = 0;
bool hasTlut = false;
bool operator==(const TextureSourceKey&) const = default;
};
struct TexturePointerKey {
const void* data = nullptr;
bool operator==(const TexturePointerKey&) const = default;
};
using ReplacementKey = std::variant<TexturePointerKey, TextureSourceKey>;
struct RawTextureReplacement {
std::span<const uint8_t> bytes;
uint32_t width = 0;
uint32_t height = 0;
uint32_t mipCount = 1;
uint32_t gxFormat = 0;
std::string_view label = {};
};
struct ReplacementOptions {
int32_t priority = 0;
};
struct ReplacementRegistration {
uint64_t id = 0;
ReplacementKey key;
};
struct ReplacementGroup {
std::vector<ReplacementRegistration> registrations;
};
ReplacementRegistration register_replacement(ReplacementKey key, RawTextureReplacement replacement,
ReplacementOptions options = {});
void unregister_replacement(const ReplacementRegistration& registration);
void unregister_replacements(std::span<const ReplacementRegistration> registrations);
void unregister_replacements(const ReplacementGroup& group);
void unregister_replacements(const ReplacementKey& key);
void clear_replacements();
ReplacementGroup load_replacement_directory(const std::filesystem::path& root, ReplacementOptions options = {});
void reload_replacement_directory(const std::filesystem::path& root, ReplacementGroup& group,
ReplacementOptions options = {});
} // namespace aurora::texture
#endif
-66
View File
@@ -4,68 +4,11 @@
#include "../../gfx/texture.hpp"
#include "../../gfx/texture_replacement.hpp"
#include "dolphin/gx/GXAurora.h"
#include <aurora/hd_texture.hpp>
#include <algorithm>
#include <shared_mutex>
#include <unordered_map>
#include "tracy/Tracy.hpp"
namespace aurora::gfx {
namespace {
std::unordered_map<const void*, HdReplacement> s_hdReplacements;
std::shared_mutex s_hdReplacementsMutex;
std::vector<HdArcRange> s_hdArcRanges;
std::shared_mutex s_hdArcRangesMutex;
}
void hd_register_replacement(const void* gcDataPtr, HdReplacement replacement) noexcept {
if (gcDataPtr == nullptr) return;
std::unique_lock lk(s_hdReplacementsMutex);
s_hdReplacements[gcDataPtr] = std::move(replacement);
}
const HdReplacement* hd_lookup_replacement(const void* gcDataPtr) noexcept {
if (gcDataPtr == nullptr) return nullptr;
std::shared_lock lk(s_hdReplacementsMutex);
auto it = s_hdReplacements.find(gcDataPtr);
return (it == s_hdReplacements.end()) ? nullptr : &it->second;
}
void hd_clear_replacements() noexcept {
std::unique_lock lk(s_hdReplacementsMutex);
s_hdReplacements.clear();
}
void hd_register_arc_range(const void* begin, size_t size, std::string_view label) noexcept {
if (begin == nullptr || size == 0) return;
std::unique_lock lk(s_hdArcRangesMutex);
s_hdArcRanges.push_back(HdArcRange{begin, size, std::string(label)});
}
const HdArcRange* hd_find_arc_range(const void* ptr, size_t* out_remaining) noexcept {
if (ptr == nullptr) return nullptr;
const auto p = reinterpret_cast<uintptr_t>(ptr);
std::shared_lock lk(s_hdArcRangesMutex);
for (const auto& r : s_hdArcRanges) {
const auto begin = reinterpret_cast<uintptr_t>(r.begin);
const auto end = begin + r.size;
if (p >= begin && p < end) {
if (out_remaining) *out_remaining = static_cast<size_t>(end - p);
return &r;
}
}
return nullptr;
}
void hd_clear_arc_ranges() noexcept {
std::unique_lock lk(s_hdArcRangesMutex);
s_hdArcRanges.clear();
}
} // namespace aurora::gfx
namespace {
constexpr u8 GXTexMode0Ids[8] = {0x80, 0x81, 0x82, 0x83, 0xA0, 0xA1, 0xA2, 0xA3};
constexpr u8 GXTexMode1Ids[8] = {0x84, 0x85, 0x86, 0x87, 0xA4, 0xA5, 0xA6, 0xA7};
@@ -107,15 +50,6 @@ int __cntlzw(unsigned int val) {
void init_texobj_common(GXTexObj_& obj, const void* data, u16 width, u16 height, u32 format, GXTexWrapMode wrapS,
GXTexWrapMode wrapT, GXBool mipmap) {
// HD texture replacement
const auto* repl = aurora::gfx::hd_lookup_replacement(data);
if (repl != nullptr) {
data = repl->bytes.data();
width = static_cast<u16>(repl->width);
height = static_cast<u16>(repl->height);
format = repl->gxFormat;
mipmap = (repl->mipCount > 1) ? GX_TRUE : GX_FALSE;
}
obj = {};
obj.mWidth = width;
obj.mHeight = height;
-7
View File
@@ -1089,10 +1089,3 @@ void pop_debug_group() {
}
const AuroraStats* aurora_get_stats() { return &aurora::gfx::g_stats; }
void aurora_set_texture_replacements_enabled(const bool enabled) {
aurora::g_config.allowTextureReplacements = enabled;
aurora::gfx::texture_replacement::reload();
}
void aurora_reload_texture_replacements() { aurora::gfx::texture_replacement::reload(); }
File diff suppressed because it is too large Load Diff
-1
View File
@@ -6,7 +6,6 @@
namespace aurora::gfx::texture_replacement {
void initialize() noexcept;
void shutdown() noexcept;
void reload() noexcept;
void register_tlut(const GXTlutObj* obj, const void* data, GXTlutFmt format, uint16_t entries) noexcept;
void load_tlut(const GXTlutObj* obj, uint32_t idx) noexcept;
std::optional<TextureHandle> find_replacement(const GXTexObj_& obj) noexcept;
-1
View File
@@ -1,7 +1,6 @@
#include "command_processor.hpp"
#include "../gfx/common.hpp"
#include "../gfx/texture_replacement.hpp"
#include "dolphin/gx/GXAurora.h"
#include "gx.hpp"
#include "gx_fmt.hpp"
+16 -4
View File
@@ -15,6 +15,7 @@
#include <absl/container/flat_hash_set.h>
#include <tracy/Tracy.hpp>
#include <atomic>
#include <bit>
#include <cfloat>
#include <mutex>
@@ -79,6 +80,14 @@ struct TlutObjectCache {
absl::flat_hash_map<u32, CachedTextureEntry> s_textureObjectCaches;
absl::flat_hash_map<u32, TlutObjectCache> s_tlutObjectCaches;
std::atomic_bool s_staticTextureCacheClearPending = false;
void do_clear_static_texture_cache() noexcept {
s_textureObjectCaches.clear();
for (auto& [_, cache] : s_tlutObjectCaches) {
cache.staticTextureUsers.clear();
}
}
DynamicPaletteKey make_dynamic_palette_key(const GXTexObj_& obj, const GXState::CopyTextureRef& source) {
return {
@@ -148,6 +157,9 @@ gfx::TextureHandle get_tlut_texture(const GXTlutObj_& tlut) {
gfx::TextureHandle resolve_static_texture(const GXTexObj_& obj) {
ZoneScoped;
if (s_staticTextureCacheClearPending.exchange(false, std::memory_order_acq_rel)) {
do_clear_static_texture_cache();
}
if (obj.texObjId != 0) {
if (const auto it = s_textureObjectCaches.find(obj.texObjId); it != s_textureObjectCaches.end()) {
@@ -180,6 +192,9 @@ gfx::TextureHandle resolve_static_texture(const GXTexObj_& obj) {
gfx::TextureHandle resolve_static_palette_texture(const GXTexObj_& obj, const GXTlutObj_& tlut) {
ZoneScoped;
if (s_staticTextureCacheClearPending.exchange(false, std::memory_order_acq_rel)) {
do_clear_static_texture_cache();
}
if (obj.texObjId != 0) {
if (const auto it = s_textureObjectCaches.find(obj.texObjId); it != s_textureObjectCaches.end()) {
@@ -371,10 +386,7 @@ void clear_copy_texture_cache() noexcept {
}
void clear_static_texture_cache() noexcept {
s_textureObjectCaches.clear();
for (auto& [_, cache] : s_tlutObjectCaches) {
cache.staticTextureUsers.clear();
}
s_staticTextureCacheClearPending.store(true, std::memory_order_release);
}
void evict_copy_texture(const void* dest) noexcept {