Fix texture eviction & cleanup

This commit is contained in:
Luke Street
2026-04-08 23:32:23 -06:00
parent 1d267a83a9
commit b5d145fae3
11 changed files with 248 additions and 314 deletions
+4
View File
@@ -43,6 +43,10 @@ extern "C" {
#define GX_LOAD_AURORA_TLUT 0x0031
#define GX_LOAD_AURORA_DESTROY_TEXOBJ 0x0032
#define GX_LOAD_AURORA_DESTROY_TLUT 0x0033
/*
* Debug marker stuff
+2 -2
View File
@@ -63,7 +63,7 @@ typedef struct {
typedef struct {
#ifdef TARGET_PC
u32 dummy[18];
u32 dummy[16];
#else
u32 dummy[8];
#endif
@@ -71,7 +71,7 @@ typedef struct {
typedef struct {
#ifdef TARGET_PC
u32 dummy[12];
u32 dummy[10];
#else
u32 dummy[3];
#endif
+10 -31
View File
@@ -1,44 +1,23 @@
#include "gx.hpp"
#include "__gx.h"
#include "dolphin/gx/GXAurora.h"
extern "C" {
void GXDestroyTexObj(GXTexObj* obj_) {
auto* obj = reinterpret_cast<GXTexObj_*>(obj_);
if (obj->texObjId == 0) {
obj->texObjId = 1;
} else {
++obj->texObjId;
if (obj->texObjId == 0) {
obj->texObjId = 1;
}
}
if (obj->texDataVersion == 0) {
obj->texDataVersion = 1;
} else {
++obj->texDataVersion;
if (obj->texDataVersion == 0) {
obj->texDataVersion = 1;
}
if (obj->texObjId != 0) {
GX_WRITE_AURORA(GX_LOAD_AURORA_DESTROY_TEXOBJ);
GX_WRITE_U32(obj->texObjId);
}
obj->texObjId = 0;
}
void GXDestroyTlutObj(GXTlutObj* obj_) {
auto* obj = reinterpret_cast<GXTlutObj_*>(obj_);
if (obj->tlutObjId == 0) {
obj->tlutObjId = 1;
} else {
++obj->tlutObjId;
if (obj->tlutObjId == 0) {
obj->tlutObjId = 1;
}
}
if (obj->tlutDataVersion == 0) {
obj->tlutDataVersion = 1;
} else {
++obj->tlutDataVersion;
if (obj->tlutDataVersion == 0) {
obj->tlutDataVersion = 1;
}
if (obj->tlutObjId != 0) {
GX_WRITE_AURORA(GX_LOAD_AURORA_DESTROY_TLUT);
GX_WRITE_U32(obj->tlutObjId);
}
obj->tlutObjId = 0;
}
}
+6 -6
View File
@@ -129,7 +129,7 @@ GXFifoObj* GXInit(void* base, u32 size) {
GXClearVtxDesc();
GXInvalidateVtxCache();
GXSetLineWidth(6, GX_TO_ZERO);
// GXSetPointSize(6, GX_TO_ZERO);
GXSetPointSize(6, GX_TO_ZERO);
GXEnableTexOffsets(GX_TEXCOORD0, GX_DISABLE, GX_DISABLE);
GXEnableTexOffsets(GX_TEXCOORD1, GX_DISABLE, GX_DISABLE);
GXEnableTexOffsets(GX_TEXCOORD2, GX_DISABLE, GX_DISABLE);
@@ -159,9 +159,9 @@ GXFifoObj* GXInit(void* base, u32 size) {
GXLoadTexMtxImm(identity_mtx, GX_PTIDENTITY, GX_MTX3x4);
GXSetViewport(0.0f, 0.0f, rmode->fbWidth, rmode->xfbHeight, 0.0f, 1.0f);
// GXSetCoPlanar(GX_DISABLE);
GXSetCoPlanar(GX_DISABLE);
GXSetCullMode(GX_CULL_BACK);
// GXSetClipMode(GX_CLIP_ENABLE);
GXSetClipMode(GX_CLIP_ENABLE);
GXSetScissor(0, 0, rmode->fbWidth, rmode->efbHeight);
// GXSetScissorBoxOffset(0, 0);
@@ -228,7 +228,7 @@ GXFifoObj* GXInit(void* base, u32 size) {
GXSetIndTexCoordScale(GX_INDTEXSTAGE3, GX_ITS_1, GX_ITS_1);
GXSetFog(GX_FOG_NONE, 0.0f, 1.0f, 0.1f, 1.0f, black);
// GXSetFogRangeAdj(GX_DISABLE, 0, nullptr);
GXSetFogRangeAdj(GX_DISABLE, 0, nullptr);
GXSetBlendMode(GX_BM_NONE, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
GXSetColorUpdate(GX_ENABLE);
GXSetAlphaUpdate(GX_ENABLE);
@@ -237,8 +237,8 @@ GXFifoObj* GXInit(void* base, u32 size) {
GXSetDither(GX_ENABLE);
GXSetDstAlpha(GX_DISABLE, 0);
GXSetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR);
// GXSetFieldMask(GX_ENABLE, GX_ENABLE);
// GXSetFieldMode(rmode->field_rendering, ((rmode->viHeight == 2 * rmode->xfbHeight) ? GX_ENABLE : GX_DISABLE));
GXSetFieldMask(GX_ENABLE, GX_ENABLE);
GXSetFieldMode(rmode->field_rendering, ((rmode->viHeight == 2 * rmode->xfbHeight) ? GX_ENABLE : GX_DISABLE));
GXSetDispCopySrc(0, 0, rmode->fbWidth, rmode->efbHeight);
GXSetDispCopyDst(rmode->fbWidth, rmode->efbHeight);
+11 -21
View File
@@ -24,30 +24,21 @@ u32 sNextTlutObjId = 1;
u32 next_tex_obj_id() {
if (sNextTexObjId == 0) {
++sNextTexObjId;
FATAL("texObj ID overflow");
}
return sNextTexObjId++;
}
u32 next_tlut_obj_id() {
if (sNextTlutObjId == 0) {
++sNextTlutObjId;
FATAL("tlutObj ID overflow");
}
return sNextTlutObjId++;
}
void configure_tex_image_ptr(GXTexObj_& obj, const void* data) {
obj.data = data;
obj.imagePtr64 = reinterpret_cast<u64>(data);
SET_REG_FIELD(0, obj.image3, 21, 0, static_cast<u32>((obj.imagePtr64 >> 5) & 0x01FFFFFF));
}
void update_tex_upload_size(GXTexObj_& obj) {
obj.dataSize = aurora::gfx::texture_replacement::compute_texture_upload_size(obj);
}
int __cntlzw(unsigned int val) {
if (val == 0) return 32; // PowerPC returns 32 if the input is 0
if (val == 0)
return 32; // PowerPC returns 32 if the input is 0
#ifdef _MSC_VER
unsigned long idx;
_BitScanReverse(&idx, val);
@@ -83,17 +74,18 @@ void init_texobj_common(GXTexObj_& obj, const void* data, u16 width, u16 height,
SET_REG_FIELD(0, obj.image0, 10, 10, static_cast<u32>(height - 1));
SET_REG_FIELD(0, obj.image0, 4, 20, format & 0xF);
configure_tex_image_ptr(obj, data);
update_tex_upload_size(obj);
obj.data = data;
obj.dataSize = aurora::gfx::texture_replacement::compute_texture_upload_size(obj);
}
void emit_loaded_texobj_metadata(const GXTexObj_& obj, GXTexMapID id) {
GX_WRITE_AURORA(GX_LOAD_AURORA_TEXOBJ);
GX_WRITE_U8(static_cast<u8>(id));
GX_WRITE_U64(obj.imagePtr64);
GX_WRITE_U64(reinterpret_cast<u64>(obj.data));
GX_WRITE_U32(obj.width());
GX_WRITE_U32(obj.height());
GX_WRITE_U32(obj.raw_format());
GX_WRITE_U32(static_cast<u32>(obj.tlut));
GX_WRITE_U8(static_cast<u8>(obj.has_mips()));
GX_WRITE_U32(obj.texObjId);
GX_WRITE_U32(obj.texDataVersion);
@@ -102,7 +94,7 @@ void emit_loaded_texobj_metadata(const GXTexObj_& obj, GXTexMapID id) {
void emit_loaded_tlut_metadata(const GXTlutObj_& obj, u32 idx) {
GX_WRITE_AURORA(GX_LOAD_AURORA_TLUT);
GX_WRITE_U8(static_cast<u8>(idx));
GX_WRITE_U64(obj.tlutPtr64);
GX_WRITE_U64(reinterpret_cast<u64>(obj.data));
GX_WRITE_U32(static_cast<u32>(obj.format));
GX_WRITE_U16(obj.numEntries);
GX_WRITE_U32(obj.tlutObjId);
@@ -147,9 +139,9 @@ void GXInitTexObjLOD(GXTexObj* obj_, GXTexFilter minFilt, GXTexFilter magFilt, f
void GXInitTexObjData(GXTexObj* obj_, const void* data) {
auto* obj = reinterpret_cast<GXTexObj_*>(obj_);
configure_tex_image_ptr(*obj, data);
obj->data = data;
obj->dataSize = aurora::gfx::texture_replacement::compute_texture_upload_size(*obj);
++obj->texDataVersion;
update_tex_upload_size(*obj);
}
void GXInitTexObjWrapMode(GXTexObj* obj_, GXTexWrapMode wrapS, GXTexWrapMode wrapT) {
@@ -281,12 +273,10 @@ void GXInitTlutObj(GXTlutObj* obj_, const void* data, GXTlutFmt format, u16 entr
obj->data = data;
obj->format = format;
obj->numEntries = entries;
obj->tlutPtr64 = reinterpret_cast<u64>(data);
obj->tlutObjId = next_tlut_obj_id();
obj->tlutDataVersion = 1;
SET_REG_FIELD(0, obj->tlut, 2, 10, format);
SET_REG_FIELD(0, obj->loadTlut0, 21, 0, static_cast<u32>((obj->tlutPtr64 >> 5) & 0x01FFFFFF));
SET_REG_FIELD(0, obj->loadTlut0, 8, 24, 0x64);
aurora::gfx::texture_replacement::register_tlut(obj_, data, format, entries);
}
+5 -6
View File
@@ -1,6 +1,8 @@
#pragma once
#include <dolphin/gx.h>
#include <utility>
#include "common.hpp"
namespace aurora::gfx {
@@ -10,7 +12,7 @@ struct TextureUpload {
wgpu::Extent3D size;
TextureUpload(wgpu::TexelCopyBufferLayout layout, wgpu::TexelCopyTextureInfo tex, wgpu::Extent3D size) noexcept
: layout(layout), tex(tex), size(size) {}
: layout(layout), tex(std::move(tex)), size(size) {}
};
extern std::vector<TextureUpload> g_textureUploads;
@@ -58,10 +60,9 @@ struct GXTexObj_ {
u32 mHeight = 0;
u32 mFormat = aurora::gfx::InvalidTextureFormat;
GXTlut tlut = GX_TLUT0;
u8 flags = 0;
u64 imagePtr64 = 0;
u32 texObjId = 0;
u32 texDataVersion = 0;
u8 flags = 0;
static constexpr u32 get_bits(u32 reg, u32 size, u32 shift) noexcept { return (reg >> shift) & ((1u << size) - 1); }
@@ -91,10 +92,8 @@ struct GXTlutObj_ {
u32 tlut = 0;
u32 loadTlut0 = 0;
u16 numEntries = 0;
u16 _pad = 0;
const void* data = nullptr;
GXTlutFmt format = GX_TL_IA8;
u64 tlutPtr64 = 0;
u32 tlutObjId = 0;
u32 tlutDataVersion = 0;
};
@@ -106,7 +105,7 @@ struct TextureBind {
GXTexObj_ texObj;
TextureBind() noexcept = default;
TextureBind(GXTexObj_ obj, TextureHandle handle) noexcept : ref(std::move(handle)), texObj(std::move(obj)) {}
TextureBind(const GXTexObj_& obj, TextureHandle handle) noexcept : ref(std::move(handle)), texObj(obj) {}
void reset() noexcept { ref.reset(); }
[[nodiscard]] wgpu::SamplerDescriptor get_descriptor() const noexcept;
operator bool() const noexcept { return ref.operator bool(); }
+29 -17
View File
@@ -1,6 +1,7 @@
#include "command_processor.hpp"
#include "../gfx/common.hpp"
#include "../gfx/texture_replacement.hpp"
#include "gx.hpp"
#include "gx_fmt.hpp"
#include "pipeline.hpp"
@@ -783,7 +784,7 @@ static void handle_bp(u32 value, bool bigEndian) {
if (idx < MaxTluts) {
auto& slot = g_gxState.loadedTluts[idx];
slot.loadTlut0 = g_gxState.bpRegCache[0x64];
slot.entries = static_cast<u16>(bp_get(value, 10, 10) + 1);
slot.numEntries = static_cast<u16>(bp_get(value, 10, 10) + 1);
}
break;
}
@@ -1086,17 +1087,15 @@ static void handle_bp(u32 value, bool bigEndian) {
case TexBpRegMapping::Kind::Image0:
slot.image0 = value;
break;
case TexBpRegMapping::Kind::Image1:
slot.image1 = value;
break;
case TexBpRegMapping::Kind::Image2:
slot.image2 = value;
break;
case TexBpRegMapping::Kind::Image3:
slot.image3 = value;
break;
case TexBpRegMapping::Kind::Tlut:
slot.tlut = value;
// TLUT region's TMEM offset
break;
case TexBpRegMapping::Kind::Image1:
case TexBpRegMapping::Kind::Image2:
// GXTexRegion regs
break;
}
g_gxState.stateDirty = true;
@@ -1669,26 +1668,32 @@ void handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian) {
g_gxState.stateDirty = true;
}
} else if (subCmd == GX_LOAD_AURORA_TEXOBJ) {
CHECK(pos + 30 <= size, "GX_LOAD_AURORA_TEXOBJ read overrun");
CHECK(pos + 34 <= size, "GX_LOAD_AURORA_TEXOBJ read overrun");
const auto texMapId = data[pos];
pos += 1;
CHECK(texMapId < MaxTextures, "invalid texture map id {}", texMapId);
auto& slot = g_gxState.loadedTextures[texMapId];
slot.imageData = reinterpret_cast<const void*>(read_u64(data + pos, bigEndian));
slot.data = reinterpret_cast<const void*>(read_u64(data + pos, bigEndian));
pos += 8;
slot.width = read_u32(data + pos, bigEndian);
slot.mWidth = read_u32(data + pos, bigEndian);
pos += 4;
slot.height = read_u32(data + pos, bigEndian);
slot.mHeight = read_u32(data + pos, bigEndian);
pos += 4;
slot.format = static_cast<GXTexFmt>(read_u32(data + pos, bigEndian));
slot.mFormat = static_cast<GXTexFmt>(read_u32(data + pos, bigEndian));
pos += 4;
slot.hasMips = data[pos] != 0;
slot.tlut = static_cast<GXTlut>(read_u32(data + pos, bigEndian));
pos += 4;
if (data[pos] != 0) {
slot.flags |= 1u;
} else {
slot.flags &= ~1u;
}
pos += 1;
slot.texObjId = read_u32(data + pos, bigEndian);
pos += 4;
slot.texDataVersion = read_u32(data + pos, bigEndian);
pos += 4;
slot.valid = true;
slot.dataSize = gfx::texture_replacement::compute_texture_upload_size(slot);
g_gxState.stateDirty = true;
} else if (subCmd == GX_LOAD_AURORA_TLUT) {
CHECK(pos + 23 <= size, "GX_LOAD_AURORA_TLUT read overrun");
@@ -1700,14 +1705,21 @@ void handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian) {
pos += 8;
slot.format = static_cast<GXTlutFmt>(read_u32(data + pos, bigEndian));
pos += 4;
slot.entries = read_u16(data + pos, bigEndian);
slot.numEntries = read_u16(data + pos, bigEndian);
pos += 2;
slot.tlutObjId = read_u32(data + pos, bigEndian);
pos += 4;
slot.tlutDataVersion = read_u32(data + pos, bigEndian);
pos += 4;
slot.valid = true;
g_gxState.stateDirty = true;
} else if (subCmd == GX_LOAD_AURORA_DESTROY_TEXOBJ) {
CHECK(pos + 4 <= size, "GX_LOAD_AURORA_DESTROY_TEXOBJ read overrun");
evict_texture_object(read_u32(data + pos, bigEndian));
pos += 4;
} else if (subCmd == GX_LOAD_AURORA_DESTROY_TLUT) {
CHECK(pos + 4 <= size, "GX_LOAD_AURORA_DESTROY_TLUT read overrun");
evict_tlut_object(read_u32(data + pos, bigEndian));
pos += 4;
} else if (subCmd == GX_LOAD_AURORA_DEBUG_GROUP_PUSH) {
auto label = read_string(data, pos, size, bigEndian);
gfx::push_debug_group(std::move(label));
+135 -183
View File
@@ -2,7 +2,6 @@
#include "pipeline.hpp"
#include "../webgpu/gpu.hpp"
#include "../window.hpp"
#include "../internal.hpp"
#include "../gfx/common.hpp"
#include "../gfx/tex_palette_conv.hpp"
@@ -14,7 +13,6 @@
#include <absl/container/flat_hash_map.h>
#include <absl/container/flat_hash_set.h>
#include <cfloat>
#include <cstring>
#include <mutex>
#include <optional>
@@ -33,55 +31,8 @@ static wgpu::Texture sEmptyTexture;
static wgpu::TextureView sEmptyTextureView;
namespace {
struct StaticTextureKey {
u32 texObjId = 0;
u32 texDataVersion = 0;
u32 width = 0;
u32 height = 0;
u32 format = 0;
u32 mipCount = 0;
bool operator==(const StaticTextureKey& rhs) const = default;
template <typename H>
friend H AbslHashValue(H h, const StaticTextureKey& key) {
return H::combine(std::move(h), key.texObjId, key.texDataVersion, key.width, key.height, key.format, key.mipCount);
}
};
struct StaticPaletteKey {
u32 texObjId = 0;
u32 texDataVersion = 0;
u32 tlutObjId = 0;
u32 tlutDataVersion = 0;
u32 width = 0;
u32 height = 0;
u32 format = 0;
u32 mipCount = 0;
bool operator==(const StaticPaletteKey& rhs) const = default;
template <typename H>
friend H AbslHashValue(H h, const StaticPaletteKey& key) {
return H::combine(std::move(h), key.texObjId, key.texDataVersion, key.tlutObjId, key.tlutDataVersion, key.width,
key.height, key.format, key.mipCount);
}
};
struct TlutTextureKey {
u32 tlutObjId = 0;
u32 tlutDataVersion = 0;
u32 format = 0;
u16 entries = 0;
bool operator==(const TlutTextureKey& rhs) const = default;
template <typename H>
friend H AbslHashValue(H h, const TlutTextureKey& key) {
return H::combine(std::move(h), key.tlutObjId, key.tlutDataVersion, key.format, key.entries);
}
};
struct DynamicPaletteKey {
const void* sourceIdentity = nullptr;
u32 tlutObjId = 0;
u32 width = 0;
u32 height = 0;
u32 format = 0;
@@ -89,7 +40,7 @@ struct DynamicPaletteKey {
bool operator==(const DynamicPaletteKey& rhs) const = default;
template <typename H>
friend H AbslHashValue(H h, const DynamicPaletteKey& key) {
return H::combine(std::move(h), key.sourceIdentity, key.tlutObjId, key.width, key.height, key.format);
return H::combine(std::move(h), key.sourceIdentity, key.width, key.height, key.format);
}
};
@@ -99,23 +50,68 @@ struct DynamicPaletteEntry {
u32 tlutDataVersion = 0;
};
struct MissingPaletteKey {
u32 texObjId = 0;
struct CachedTextureEntry {
gfx::TextureHandle handle;
u32 texDataVersion = 0;
u32 tlut = 0;
bool operator==(const MissingPaletteKey& rhs) const = default;
template <typename H>
friend H AbslHashValue(H h, const MissingPaletteKey& key) {
return H::combine(std::move(h), key.texObjId, key.texDataVersion, key.tlut);
}
u32 tlutObjId = 0;
u32 tlutDataVersion = 0;
};
absl::flat_hash_map<StaticTextureKey, gfx::TextureHandle> s_staticTextureCache;
absl::flat_hash_map<StaticPaletteKey, gfx::TextureHandle> s_staticPaletteCache;
absl::flat_hash_map<TlutTextureKey, gfx::TextureHandle> s_tlutTextureCache;
absl::flat_hash_map<DynamicPaletteKey, DynamicPaletteEntry> s_dynamicPaletteCache;
absl::flat_hash_set<MissingPaletteKey> s_missingPaletteWarnings;
struct CachedTlutTextureEntry {
gfx::TextureHandle handle;
u32 tlutDataVersion = 0;
};
struct TlutObjectCache {
CachedTlutTextureEntry tlutTexture;
absl::flat_hash_map<DynamicPaletteKey, DynamicPaletteEntry> dynamicPaletteTextures;
absl::flat_hash_set<u32> staticTextureUsers;
};
absl::flat_hash_map<u32, CachedTextureEntry> s_textureObjectCaches;
absl::flat_hash_map<u32, TlutObjectCache> s_tlutObjectCaches;
DynamicPaletteKey make_dynamic_palette_key(const GXTexObj_& obj, const GXState::CopyTextureRef& source) {
return {
.sourceIdentity = source.handle.get(),
.width = obj.width(),
.height = obj.height(),
.format = obj.format(),
};
}
void clear_texture_dependency(u32 texObjId, u32 tlutObjId) {
if (texObjId == 0 || tlutObjId == 0) {
return;
}
if (auto it = s_tlutObjectCaches.find(tlutObjId); it != s_tlutObjectCaches.end()) {
it->second.staticTextureUsers.erase(texObjId);
if (!it->second.tlutTexture.handle && it->second.dynamicPaletteTextures.empty() &&
it->second.staticTextureUsers.empty()) {
s_tlutObjectCaches.erase(it);
}
}
}
void store_cached_texture(const GXTexObj_& obj, gfx::TextureHandle handle, u32 tlutObjId = 0, u32 tlutDataVersion = 0) {
if (obj.texObjId == 0) {
return;
}
auto& entry = s_textureObjectCaches[obj.texObjId];
if (entry.tlutObjId != tlutObjId) {
clear_texture_dependency(obj.texObjId, entry.tlutObjId);
}
entry.handle = std::move(handle);
entry.texDataVersion = obj.texDataVersion;
entry.tlutObjId = tlutObjId;
entry.tlutDataVersion = tlutDataVersion;
if (tlutObjId != 0) {
s_tlutObjectCaches[tlutObjId].staticTextureUsers.insert(obj.texObjId);
}
}
u32 mip_count_for(const GXTexObj_& obj) {
if (!obj.has_mips()) {
@@ -124,49 +120,27 @@ u32 mip_count_for(const GXTexObj_& obj) {
return std::max<u32>(static_cast<u32>(obj.max_lod()) + 1, 1u);
}
GXTexObj_ materialize_tex_obj(const GXState::LoadedTextureSlot& slot) {
GXTexObj_ obj{};
obj.mode0 = slot.mode0;
obj.mode1 = slot.mode1;
obj.image0 = slot.image0;
obj.image3 = slot.image3;
obj.data = slot.imageData;
obj.imagePtr64 = reinterpret_cast<u64>(slot.imageData);
obj.mWidth = slot.width;
obj.mHeight = slot.height;
obj.tlut = static_cast<GXTlut>(GXTexObj_::get_bits(slot.tlut, 10, 0));
if (slot.hasMips) {
obj.flags |= 1;
}
if (slot.format != obj.raw_format()) {
obj.mFormat = slot.format;
}
obj.texObjId = slot.texObjId;
obj.texDataVersion = slot.texDataVersion;
obj.dataSize = gfx::texture_replacement::compute_texture_upload_size(obj);
return obj;
}
gfx::TextureHandle get_tlut_texture(const GXState::LoadedTlutSlot& tlut) {
const TlutTextureKey key{
.tlutObjId = tlut.tlutObjId,
.tlutDataVersion = tlut.tlutDataVersion,
.format = static_cast<u32>(tlut.format),
.entries = tlut.entries,
};
if (const auto it = s_tlutTextureCache.find(key); it != s_tlutTextureCache.end()) {
return it->second;
}
const auto texFmt = gfx::tlut_texture_format(tlut.format);
if (texFmt == gfx::InvalidTextureFormat) {
FATAL("invalid tlut format {}", static_cast<int>(tlut.format));
gfx::TextureHandle get_tlut_texture(const GXTlutObj_& tlut) {
if (tlut.tlutObjId != 0) {
auto& cache = s_tlutObjectCaches[tlut.tlutObjId];
if (cache.tlutTexture.handle && cache.tlutTexture.tlutDataVersion == tlut.tlutDataVersion) {
return cache.tlutTexture.handle;
}
cache.dynamicPaletteTextures.clear();
for (const u32 texObjId : cache.staticTextureUsers) {
s_textureObjectCaches.erase(texObjId);
}
cache.staticTextureUsers.clear();
}
const auto handle = gfx::new_static_texture_2d(
tlut.entries, 1, 1, texFmt, {static_cast<const u8*>(tlut.data), static_cast<size_t>(tlut.entries) * sizeof(u16)},
true, "Loaded TLUT");
s_tlutTextureCache.emplace(key, handle);
tlut.numEntries, 1, 1, gfx::tlut_texture_format(tlut.format),
{static_cast<const u8*>(tlut.data), static_cast<size_t>(tlut.numEntries) * sizeof(u16)}, true, "Loaded TLUT");
if (tlut.tlutObjId != 0) {
auto& cache = s_tlutObjectCaches[tlut.tlutObjId];
cache.tlutTexture.handle = handle;
cache.tlutTexture.tlutDataVersion = tlut.tlutDataVersion;
}
return handle;
}
@@ -179,84 +153,58 @@ gfx::TextureHandle resolve_static_texture(const GXTexObj_& obj) {
return *replacement;
}
const StaticTextureKey key{
.texObjId = obj.texObjId,
.texDataVersion = obj.texDataVersion,
.width = obj.width(),
.height = obj.height(),
.format = obj.format(),
.mipCount = mip_count_for(obj),
};
if (const auto it = s_staticTextureCache.find(key); it != s_staticTextureCache.end()) {
return it->second;
if (obj.texObjId != 0) {
if (const auto it = s_textureObjectCaches.find(obj.texObjId); it != s_textureObjectCaches.end()) {
const auto& entry = it->second;
if (entry.handle && entry.texDataVersion == obj.texDataVersion && entry.tlutObjId == 0) {
return entry.handle;
}
}
}
const auto handle =
gfx::new_static_texture_2d(obj.width(), obj.height(), mip_count_for(obj), obj.format(),
{static_cast<const u8*>(obj.data), obj.dataSize}, false, "GX Static Texture");
s_staticTextureCache.emplace(key, handle);
store_cached_texture(obj, handle);
return handle;
}
gfx::TextureHandle resolve_static_palette_texture(const GXTexObj_& obj, const GXState::LoadedTlutSlot& tlut) {
gfx::TextureHandle resolve_static_palette_texture(const GXTexObj_& obj, const GXTlutObj_& tlut) {
if (const auto replacement = try_resolve_replacement_handle(obj); replacement.has_value()) {
return *replacement;
}
const StaticPaletteKey key{
.texObjId = obj.texObjId,
.texDataVersion = obj.texDataVersion,
.tlutObjId = tlut.tlutObjId,
.tlutDataVersion = tlut.tlutDataVersion,
.width = obj.width(),
.height = obj.height(),
.format = obj.format(),
.mipCount = mip_count_for(obj),
};
if (const auto it = s_staticPaletteCache.find(key); it != s_staticPaletteCache.end()) {
return it->second;
if (obj.texObjId != 0) {
if (const auto it = s_textureObjectCaches.find(obj.texObjId); it != s_textureObjectCaches.end()) {
const auto& entry = it->second;
if (entry.handle && entry.texDataVersion == obj.texDataVersion && entry.tlutObjId == tlut.tlutObjId &&
entry.tlutDataVersion == tlut.tlutDataVersion) {
return entry.handle;
}
}
}
auto decoded = gfx::convert_texture_palette(
obj.format(), obj.width(), obj.height(), mip_count_for(obj), {static_cast<const u8*>(obj.data), obj.dataSize},
tlut.format, tlut.entries, {static_cast<const u8*>(tlut.data), static_cast<size_t>(tlut.entries) * 2});
tlut.format, tlut.numEntries, {static_cast<const u8*>(tlut.data), static_cast<size_t>(tlut.numEntries) * 2});
if (decoded.empty()) {
return {};
}
const auto handle = gfx::new_static_texture_2d(obj.width(), obj.height(), mip_count_for(obj), GX_TF_RGBA8_PC,
{decoded.data(), decoded.size()}, false, "GX Static Palette Texture");
s_staticPaletteCache.emplace(key, handle);
store_cached_texture(obj, handle, tlut.tlutObjId, tlut.tlutDataVersion);
return handle;
}
void warn_missing_palette_state(const GXTexObj_& obj, const char* reason) {
const MissingPaletteKey key{
.texObjId = obj.texObjId,
.texDataVersion = obj.texDataVersion,
.tlut = static_cast<u32>(obj.tlut),
};
if (s_missingPaletteWarnings.insert(key).second) {
Log.warn("palette texture {} (fmt {}, tlut {}) unresolved at draw time: {}", obj.texObjId, obj.format(),
static_cast<u32>(obj.tlut), reason);
}
}
gfx::TextureHandle resolve_dynamic_palette_texture(const GXTexObj_& obj, const GXState::CopyTextureRef& source,
const GXState::LoadedTlutSlot& tlut) {
const DynamicPaletteKey key{
.sourceIdentity = source.handle.get(),
.tlutObjId = tlut.tlutObjId,
.width = obj.width(),
.height = obj.height(),
.format = obj.format(),
};
auto& entry = s_dynamicPaletteCache[key];
const GXTlutObj_& tlut) {
const auto tlutHandle = get_tlut_texture(tlut);
auto& tlutCache = s_tlutObjectCaches[tlut.tlutObjId];
auto& entry = tlutCache.dynamicPaletteTextures[make_dynamic_palette_key(obj, source)];
if (!entry.handle) {
entry.handle = gfx::new_conv_texture(obj.width(), obj.height(), GX_TF_RGBA8, "GX Dynamic Palette Texture");
}
if (entry.sourceRevision != source.revision || entry.tlutDataVersion != tlut.tlutDataVersion) {
const auto tlutHandle = get_tlut_texture(tlut);
gfx::queue_palette_conv({
.variant = obj.format() == GX_TF_C4 ? gfx::tex_palette_conv::Variant::FromFloat4
: gfx::tex_palette_conv::Variant::FromFloat8,
@@ -283,10 +231,28 @@ u32 resolved_format_for_handle(const gfx::TextureHandle& handle) {
const gfx::TextureBind& get_texture(GXTexMapID id) noexcept { return g_gxState.textures[static_cast<size_t>(id)]; }
void evict_texture_object(u32 texObjId) noexcept {
if (const auto it = s_textureObjectCaches.find(texObjId); it != s_textureObjectCaches.end()) {
clear_texture_dependency(texObjId, it->second.tlutObjId);
s_textureObjectCaches.erase(it);
}
}
void evict_tlut_object(u32 tlutObjId) noexcept {
if (const auto it = s_tlutObjectCaches.find(tlutObjId); it != s_tlutObjectCaches.end()) {
for (const u32 texObjId : it->second.staticTextureUsers) {
s_textureObjectCaches.erase(texObjId);
}
s_tlutObjectCaches.erase(it);
}
}
void clear_copy_texture_cache() noexcept {
g_gxState.copyTextures.clear();
g_gxState.copyTextureCache.clear();
s_dynamicPaletteCache.clear();
for (auto& [_, cache] : s_tlutObjectCaches) {
cache.dynamicPaletteTextures.clear();
}
}
void resolve_sampled_textures(const ShaderInfo& info) noexcept {
@@ -295,45 +261,34 @@ void resolve_sampled_textures(const ShaderInfo& info) noexcept {
continue;
}
const auto& slot = g_gxState.loadedTextures[i];
CHECK(slot.valid, "sampled texture {} has not been loaded", i);
GXTexObj_ obj = materialize_tex_obj(slot);
GXTexObj_ obj = g_gxState.loadedTextures[i];
gfx::TextureHandle handle;
const auto copyIt = g_gxState.copyTextures.find(obj.data);
const GXState::CopyTextureRef* copyRef = copyIt != g_gxState.copyTextures.end() ? &copyIt->second : nullptr;
if (obj.data == nullptr && copyRef == nullptr) {
CHECK(false, "sampled texture {} has no image data metadata", i);
} else if (is_palette_format(obj.format())) {
if (is_palette_format(obj.format())) {
const auto tlutIdx = static_cast<size_t>(obj.tlut);
if (tlutIdx >= g_gxState.loadedTluts.size()) {
warn_missing_palette_state(obj, "invalid TLUT slot");
} else {
if (tlutIdx < g_gxState.loadedTluts.size()) {
const auto& tlut = g_gxState.loadedTluts[tlutIdx];
if (!tlut.valid || tlut.data == nullptr) {
warn_missing_palette_state(obj, "TLUT not loaded");
} else if (copyRef != nullptr) {
handle = resolve_dynamic_palette_texture(obj, *copyRef, tlut);
} else {
handle = resolve_static_palette_texture(obj, tlut);
if (!handle) {
warn_missing_palette_state(obj, "palette decode unsupported or failed");
if (tlut.data != nullptr) {
if (copyRef != nullptr) {
handle = resolve_dynamic_palette_texture(obj, *copyRef, tlut);
} else {
handle = resolve_static_palette_texture(obj, tlut);
}
}
}
} else if (copyRef != nullptr) {
handle = copyRef->handle;
} else {
} else if (obj.data != nullptr) {
handle = resolve_static_texture(obj);
}
GXTexObj_ boundObj = obj;
if (handle) {
boundObj.mWidth = handle->size.width;
boundObj.mHeight = handle->size.height;
obj.mWidth = handle->size.width;
obj.mHeight = handle->size.height;
}
boundObj.mFormat = resolved_format_for_handle(handle);
g_gxState.textures[i] = gfx::TextureBind(boundObj, handle);
obj.mFormat = resolved_format_for_handle(handle);
g_gxState.textures[i] = gfx::TextureBind{obj, std::move(handle)};
}
}
@@ -946,11 +901,8 @@ void shutdown() noexcept {
std::lock_guard lock{g_gxCachedShadersMutex};
g_gxCachedShaders.clear();
}
s_staticTextureCache.clear();
s_staticPaletteCache.clear();
s_tlutTextureCache.clear();
s_dynamicPaletteCache.clear();
s_missingPaletteWarnings.clear();
s_textureObjectCaches.clear();
s_tlutObjectCaches.clear();
g_gxState.loadedTextures.fill({});
g_gxState.loadedTluts.fill({});
clear_copy_texture_cache();
+5 -32
View File
@@ -280,33 +280,6 @@ inline bool operator==(const AttrArray& lhs, const AttrArray& rhs) {
inline bool operator!=(const AttrArray& lhs, const AttrArray& rhs) { return !(lhs == rhs); }
struct GXState {
struct LoadedTextureSlot {
u32 mode0 = 0;
u32 mode1 = 0;
u32 image0 = 0;
u32 image1 = 0;
u32 image2 = 0;
u32 image3 = 0;
u32 tlut = 0;
const void* imageData = nullptr;
u32 width = 0;
u32 height = 0;
GXTexFmt format = GX_TF_I4;
GXBool hasMips = false;
u32 texObjId = 0;
u32 texDataVersion = 0;
bool valid = false;
};
struct LoadedTlutSlot {
u32 tlut = 0;
u32 loadTlut0 = 0;
const void* data = nullptr;
GXTlutFmt format = GX_TL_IA8;
u16 entries = 0;
u32 tlutObjId = 0;
u32 tlutDataVersion = 0;
bool valid = false;
};
struct CopyTextureRef {
gfx::TextureHandle handle;
u32 revision = 0;
@@ -343,8 +316,8 @@ struct GXState {
std::array<Light, GX::MaxLights> lights;
std::array<TevStage, MaxTevStages> tevStages;
std::array<gfx::TextureBind, MaxTextures> textures;
std::array<LoadedTextureSlot, MaxTextures> loadedTextures;
std::array<LoadedTlutSlot, MaxTluts> loadedTluts;
std::array<GXTexObj_, MaxTextures> loadedTextures;
std::array<GXTlutObj_, MaxTluts> loadedTluts;
std::array<Mat3x4<float>, MaxTexMtx> texMtxs;
std::array<Mat3x4<float>, MaxPTTexMtx> ptTexMtxs;
std::array<TcgConfig, MaxTexCoord> tcgs;
@@ -398,9 +371,7 @@ struct GXState {
return regs;
}();
void clearVtxSizeCache() {
lastVtxFmt = GX_MAX_VTXFMT;
}
void clearVtxSizeCache() { lastVtxFmt = GX_MAX_VTXFMT; }
};
extern GXState g_gxState;
struct ShaderInfo;
@@ -408,6 +379,8 @@ struct ShaderInfo;
void initialize() noexcept;
void shutdown() noexcept;
void clear_copy_texture_cache() noexcept;
void evict_texture_object(u32 texObjId) noexcept;
void evict_tlut_object(u32 tlutObjId) noexcept;
const gfx::TextureBind& get_texture(GXTexMapID id) noexcept;
void resolve_sampled_textures(const ShaderInfo& info) noexcept;
+39 -16
View File
@@ -1269,17 +1269,14 @@ TEST_F(GXFifoTest, LoadTexObj_EncodesSdkBpBurstAndAuroraMetadata) {
decode_fifo(bytes);
const auto& slot = gxState().loadedTextures[GX_TEXMAP2];
EXPECT_TRUE(slot.valid);
EXPECT_EQ(slot.imageData, image);
EXPECT_EQ(slot.width, 8u);
EXPECT_EQ(slot.height, 8u);
EXPECT_EQ(slot.format, GX_TF_RGB5A3);
EXPECT_FALSE(slot.hasMips);
EXPECT_EQ(slot.data, image);
EXPECT_EQ(slot.width(), 8u);
EXPECT_EQ(slot.height(), 8u);
EXPECT_EQ(slot.format(), GX_TF_RGB5A3);
EXPECT_FALSE(slot.has_mips());
EXPECT_EQ(slot.mode0 >> 24, 0x82u);
EXPECT_EQ(slot.mode1 >> 24, 0x86u);
EXPECT_EQ(slot.image0 >> 24, 0x8Au);
EXPECT_EQ(slot.image1 >> 24, 0x8Eu);
EXPECT_EQ(slot.image2 >> 24, 0x92u);
EXPECT_EQ(slot.image3 >> 24, 0x96u);
EXPECT_NE(slot.texObjId, 0u);
EXPECT_EQ(slot.texDataVersion, 1u);
@@ -1312,22 +1309,48 @@ TEST_F(GXFifoTest, LoadTexObjCiAndTlut_PopulatesTextureAndTlutSlots) {
decode_fifo(bytes);
const auto& texSlot = gxState().loadedTextures[GX_TEXMAP1];
EXPECT_TRUE(texSlot.valid);
EXPECT_EQ(texSlot.imageData, image);
EXPECT_EQ(texSlot.width, 8u);
EXPECT_EQ(texSlot.height, 8u);
EXPECT_EQ(texSlot.format, GX_TF_C4);
EXPECT_EQ(texSlot.tlut & 0x3FFu, GX_TLUT3);
EXPECT_EQ(texSlot.data, image);
EXPECT_EQ(texSlot.width(), 8u);
EXPECT_EQ(texSlot.height(), 8u);
EXPECT_EQ(texSlot.format(), GX_TF_C4);
EXPECT_EQ(texSlot.tlut, GX_TLUT3);
const auto& tlutSlot = gxState().loadedTluts[GX_TLUT3];
EXPECT_TRUE(tlutSlot.valid);
EXPECT_EQ(tlutSlot.data, palette);
EXPECT_EQ(tlutSlot.format, GX_TL_RGB565);
EXPECT_EQ(tlutSlot.entries, 16u);
EXPECT_EQ(tlutSlot.numEntries, 16u);
EXPECT_NE(tlutSlot.tlutObjId, 0u);
EXPECT_EQ(tlutSlot.tlutDataVersion, 1u);
}
TEST_F(GXFifoTest, DestroyTexObj_EmitsAuroraDestroyCommandAndClearsIdentity) {
alignas(32) u8 image[64]{};
GXTexObj obj{};
GXInitTexObj(&obj, image, 8, 8, GX_TF_RGB5A3, GX_REPEAT, GX_REPEAT, GX_FALSE);
GXDestroyTexObj(&obj);
auto bytes = capture_fifo();
EXPECT_TRUE(has_aurora_cmd(bytes, GX_LOAD_AURORA_DESTROY_TEXOBJ));
EXPECT_EQ(reinterpret_cast<const GXTexObj_*>(&obj)->texObjId, 0u);
reset_gx_state();
decode_fifo(bytes);
}
TEST_F(GXFifoTest, DestroyTlutObj_EmitsAuroraDestroyCommandAndClearsIdentity) {
alignas(32) u16 palette[16]{};
GXTlutObj obj{};
GXInitTlutObj(&obj, palette, GX_TL_RGB565, 16);
GXDestroyTlutObj(&obj);
auto bytes = capture_fifo();
EXPECT_TRUE(has_aurora_cmd(bytes, GX_LOAD_AURORA_DESTROY_TLUT));
EXPECT_EQ(reinterpret_cast<const GXTlutObj_*>(&obj)->tlutObjId, 0u);
reset_gx_state();
decode_fifo(bytes);
}
// ============================================================================
// BP genMode (requires __GXSetDirtyState() flush)
// ============================================================================
+2
View File
@@ -60,6 +60,8 @@ std::vector<TextureUpload> g_textureUploads;
// --- get_texture ---
namespace aurora::gx {
const gfx::TextureBind& get_texture(GXTexMapID id) noexcept { return g_gxState.textures[id]; }
void evict_texture_object(u32) noexcept {}
void evict_tlut_object(u32) noexcept {}
void shutdown() noexcept {}
} // namespace aurora::gx