mirror of https://github.com/InoriRus/Kyty.git
2337 lines
64 KiB
C++
2337 lines
64 KiB
C++
#include "Emulator/Graphics/Objects/GpuMemory.h"
|
|
|
|
#include "Kyty/Core/Database.h"
|
|
#include "Kyty/Core/DbgAssert.h"
|
|
#include "Kyty/Core/Hashmap.h"
|
|
#include "Kyty/Core/MagicEnum.h"
|
|
#include "Kyty/Core/String.h"
|
|
#include "Kyty/Core/Threads.h"
|
|
#include "Kyty/Core/Vector.h"
|
|
|
|
#include "Emulator/Config.h"
|
|
#include "Emulator/Graphics/GraphicContext.h"
|
|
#include "Emulator/Graphics/GraphicsRun.h"
|
|
#include "Emulator/Profiler.h"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <vulkan/vk_enum_string_helper.h>
|
|
|
|
//#define XXH_INLINE_ALL
|
|
#include <xxhash/xxhash.h>
|
|
|
|
#ifdef KYTY_EMU_ENABLED
|
|
|
|
namespace Kyty::Libs::Graphics {
|
|
|
|
constexpr int VADDR_BLOCKS_MAX = 3;
|
|
|
|
enum class OverlapType : uint64_t
|
|
{
|
|
None,
|
|
Equals,
|
|
Crosses,
|
|
Contains,
|
|
IsContainedWithin,
|
|
|
|
Max
|
|
};
|
|
|
|
constexpr uint64_t ObjectsRelation(GpuMemoryObjectType b, OverlapType relation, GpuMemoryObjectType a)
|
|
{
|
|
return static_cast<uint64_t>(a) * static_cast<uint64_t>(GpuMemoryObjectType::Max) * static_cast<uint64_t>(OverlapType::Max) +
|
|
static_cast<uint64_t>(b) * static_cast<uint64_t>(OverlapType::Max) + static_cast<uint64_t>(relation);
|
|
}
|
|
|
|
static bool addr_in_block(uint64_t block_addr, uint64_t block_size, uint64_t addr)
|
|
{
|
|
return addr >= block_addr && addr < block_addr + block_size;
|
|
};
|
|
|
|
static OverlapType GetOverlapType(uint64_t vaddr_a, uint64_t size_a, uint64_t vaddr_b, uint64_t size_b)
|
|
{
|
|
// KYTY_PROFILER_FUNCTION();
|
|
|
|
EXIT_IF(size_a == 0 || size_b == 0);
|
|
|
|
if (vaddr_a == vaddr_b && size_a == size_b)
|
|
{
|
|
return OverlapType::Equals;
|
|
}
|
|
|
|
bool a_b = addr_in_block(vaddr_a, size_a, vaddr_b);
|
|
bool a_lb = addr_in_block(vaddr_a, size_a, vaddr_b + size_b - 1);
|
|
bool b_a = addr_in_block(vaddr_b, size_b, vaddr_a);
|
|
bool b_la = addr_in_block(vaddr_b, size_b, vaddr_a + size_a - 1);
|
|
|
|
if (a_b && a_lb)
|
|
{
|
|
return OverlapType::Contains;
|
|
}
|
|
|
|
if (b_a && b_la)
|
|
{
|
|
return OverlapType::IsContainedWithin;
|
|
}
|
|
|
|
if ((a_b && b_la) || (b_a && a_lb))
|
|
{
|
|
return OverlapType::Crosses;
|
|
}
|
|
|
|
return OverlapType::None;
|
|
}
|
|
|
|
class GpuMap1
|
|
{
|
|
public:
|
|
GpuMap1() = default;
|
|
~GpuMap1() = default;
|
|
|
|
KYTY_CLASS_NO_COPY(GpuMap1);
|
|
|
|
void Insert(uint64_t vaddr, int id)
|
|
{
|
|
auto& ids = m_map[vaddr];
|
|
if (!ids.Contains(id))
|
|
{
|
|
ids.Add(id);
|
|
}
|
|
}
|
|
|
|
void Erase(uint64_t vaddr, int id)
|
|
{
|
|
auto& ids = m_map[vaddr];
|
|
ids.Remove(id);
|
|
if (ids.IsEmpty())
|
|
{
|
|
m_map.Remove(vaddr);
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] Vector<int> FindAll(uint64_t vaddr) const { return m_map.Get(vaddr); }
|
|
|
|
[[nodiscard]] bool IsEmpty() const
|
|
{
|
|
int num = 0;
|
|
m_map.ForEach(
|
|
[](auto /*key*/, auto value, void* arg)
|
|
{
|
|
(*static_cast<int*>(arg)) += value->Size();
|
|
return true;
|
|
},
|
|
&num);
|
|
return num == 0;
|
|
}
|
|
|
|
private:
|
|
Core::Hashmap<uint64_t, Vector<int>> m_map;
|
|
};
|
|
|
|
class GpuMap2
|
|
{
|
|
public:
|
|
GpuMap2() = default;
|
|
~GpuMap2() = default;
|
|
|
|
KYTY_CLASS_NO_COPY(GpuMap2);
|
|
|
|
void Insert(uint64_t vaddr, uint64_t size, int id)
|
|
{
|
|
EXIT_IF(size == 0);
|
|
auto first_page = CalcPageId(vaddr);
|
|
auto last_page = CalcPageId(vaddr + size - 1);
|
|
EXIT_IF(last_page < first_page);
|
|
for (auto page = first_page; page <= last_page; page++)
|
|
{
|
|
auto& ids = m_map[page];
|
|
if (!ids.Contains(id))
|
|
{
|
|
ids.Add(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Erase(uint64_t vaddr, uint64_t size, int id)
|
|
{
|
|
EXIT_IF(size == 0);
|
|
auto first_page = CalcPageId(vaddr);
|
|
auto last_page = CalcPageId(vaddr + size - 1);
|
|
EXIT_IF(last_page < first_page);
|
|
for (auto page = first_page; page <= last_page; page++)
|
|
{
|
|
auto& ids = m_map[page];
|
|
ids.Remove(id);
|
|
if (ids.IsEmpty())
|
|
{
|
|
m_map.Remove(page);
|
|
}
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] Vector<int> FindAll(uint64_t vaddr, uint64_t size) const
|
|
{
|
|
Vector<int> ret;
|
|
EXIT_IF(size == 0);
|
|
auto first_page = CalcPageId(vaddr);
|
|
auto last_page = CalcPageId(vaddr + size - 1);
|
|
EXIT_IF(last_page < first_page);
|
|
for (auto page = first_page; page <= last_page; page++)
|
|
{
|
|
for (int id: m_map.Get(page))
|
|
{
|
|
if (!ret.Contains(id))
|
|
{
|
|
ret.Add(id);
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
[[nodiscard]] Vector<int> FindAll(const uint64_t* vaddr, const uint64_t* size, int vaddr_num) const
|
|
{
|
|
EXIT_IF(vaddr == nullptr);
|
|
EXIT_IF(size == nullptr);
|
|
Vector<int> ret;
|
|
for (int i = 0; i < vaddr_num; i++)
|
|
{
|
|
EXIT_IF(size[i] == 0);
|
|
auto first_page = CalcPageId(vaddr[i]);
|
|
auto last_page = CalcPageId(vaddr[i] + size[i] - 1);
|
|
EXIT_IF(last_page < first_page);
|
|
for (auto page = first_page; page <= last_page; page++)
|
|
{
|
|
for (int id: m_map.Get(page))
|
|
{
|
|
if (!ret.Contains(id))
|
|
{
|
|
ret.Add(id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
[[nodiscard]] bool IsEmpty() const
|
|
{
|
|
int num = 0;
|
|
m_map.ForEach(
|
|
[](auto /*key*/, auto value, void* arg)
|
|
{
|
|
(*static_cast<int*>(arg)) += value->Size();
|
|
return true;
|
|
},
|
|
&num);
|
|
return num == 0;
|
|
}
|
|
|
|
private:
|
|
static constexpr uint32_t PAGE_BITS = 14u;
|
|
|
|
static uint32_t CalcPageId(uint64_t vaddr)
|
|
{
|
|
EXIT_IF((vaddr >> (PAGE_BITS + 32u)) != 0);
|
|
return static_cast<uint32_t>(vaddr >> PAGE_BITS);
|
|
}
|
|
Core::Hashmap<uint32_t, Vector<int>> m_map;
|
|
};
|
|
|
|
class GpuMemory
|
|
{
|
|
public:
|
|
GpuMemory()
|
|
{
|
|
EXIT_NOT_IMPLEMENTED(!Core::Thread::IsMainThread());
|
|
DbgInit();
|
|
}
|
|
virtual ~GpuMemory() { KYTY_NOT_IMPLEMENTED; }
|
|
KYTY_CLASS_NO_COPY(GpuMemory);
|
|
|
|
bool IsAllocated(uint64_t vaddr, uint64_t size);
|
|
void SetAllocatedRange(uint64_t vaddr, uint64_t size);
|
|
void Free(GraphicContext* ctx, uint64_t vaddr, uint64_t size, bool unmap);
|
|
|
|
void* CreateObject(uint64_t submit_id, GraphicContext* ctx, CommandBuffer* buffer, const uint64_t* vaddr, const uint64_t* size,
|
|
int vaddr_num, const GpuObject& info);
|
|
void ResetHash(const uint64_t* vaddr, const uint64_t* size, int vaddr_num, GpuMemoryObjectType type);
|
|
void FrameDone();
|
|
|
|
Vector<GpuMemoryObject> FindObjects(const uint64_t* vaddr, const uint64_t* size, int vaddr_num, GpuMemoryObjectType type, bool exact,
|
|
bool only_first);
|
|
|
|
// Sync: GPU -> CPU
|
|
void WriteBack(GraphicContext* ctx, CommandProcessor* cp);
|
|
|
|
// Sync: CPU -> GPU
|
|
void Flush(GraphicContext* ctx, uint64_t vaddr, uint64_t size);
|
|
void FlushAll(GraphicContext* ctx);
|
|
|
|
void DbgInit();
|
|
void DbgDbDump();
|
|
void DbgDbSave(const String& file_name);
|
|
|
|
private:
|
|
static constexpr int OBJ_OVERLAPS_MAX = 2;
|
|
|
|
struct AllocatedRange
|
|
{
|
|
uint64_t vaddr = 0;
|
|
uint64_t size = 0;
|
|
};
|
|
|
|
struct ObjectInfo
|
|
{
|
|
GpuMemoryObject object;
|
|
uint64_t params[GpuObject::PARAMS_MAX] = {};
|
|
uint64_t hash[VADDR_BLOCKS_MAX] = {};
|
|
uint64_t cpu_update_time = 0;
|
|
uint64_t gpu_update_time = 0;
|
|
uint64_t submit_id = 0;
|
|
GpuObject::write_back_func_t write_back_func = nullptr;
|
|
GpuObject::delete_func_t delete_func = nullptr;
|
|
GpuObject::update_func_t update_func = nullptr;
|
|
uint64_t use_last_frame = 0;
|
|
uint64_t use_num = 0;
|
|
bool in_use = false;
|
|
bool read_only = false;
|
|
bool check_hash = false;
|
|
VulkanMemory mem;
|
|
};
|
|
|
|
struct OverlappedBlock
|
|
{
|
|
OverlapType relation = OverlapType::None;
|
|
int object_id = -1;
|
|
};
|
|
|
|
struct Block
|
|
{
|
|
uint64_t vaddr[VADDR_BLOCKS_MAX] = {};
|
|
uint64_t size[VADDR_BLOCKS_MAX] = {};
|
|
int vaddr_num = 0;
|
|
};
|
|
|
|
struct Object
|
|
{
|
|
Block block;
|
|
ObjectInfo info;
|
|
Vector<OverlappedBlock> others;
|
|
GpuMemoryScenario scenario = GpuMemoryScenario::Common;
|
|
bool free = true;
|
|
int next_free_id = -1;
|
|
};
|
|
|
|
struct Heap
|
|
{
|
|
AllocatedRange range;
|
|
Vector<Object> objects;
|
|
uint64_t objects_size = 0;
|
|
int first_free_id = -1;
|
|
GpuMap1* objects_map1 = nullptr;
|
|
GpuMap2* objects_map2 = nullptr;
|
|
};
|
|
|
|
struct Destructor
|
|
{
|
|
void* obj = nullptr;
|
|
GpuObject::delete_func_t delete_func = nullptr;
|
|
VulkanMemory mem;
|
|
};
|
|
|
|
[[nodiscard]] Destructor Free(int heap_id, int object_id);
|
|
|
|
Vector<OverlappedBlock> FindBlocks_slow(int heap_id, const uint64_t* vaddr, const uint64_t* size, int vaddr_num,
|
|
bool only_first = false);
|
|
Vector<OverlappedBlock> FindBlocks(int heap_id, const uint64_t* vaddr, const uint64_t* size, int vaddr_num, bool only_first = false);
|
|
bool FindFast(int heap_id, const uint64_t* vaddr, const uint64_t* size, int vaddr_num, GpuMemoryObjectType type, bool only_first,
|
|
int* id);
|
|
Block CreateBlock(const uint64_t* vaddr, const uint64_t* size, int vaddr_num, int heap_id, int obj_id);
|
|
void DeleteBlock(Block* b, int heap_id, int obj_id);
|
|
void Link(int heap_id, int id1, int id2, OverlapType rel, GpuMemoryScenario scenario);
|
|
int GetHeapId(uint64_t vaddr, uint64_t size);
|
|
|
|
// Update (CPU -> GPU)
|
|
void Update(uint64_t submit_id, GraphicContext* ctx, int heap_id, int obj_id);
|
|
|
|
bool create_existing(const Vector<OverlappedBlock>& others, const GpuObject& info, int heap_id, int* id);
|
|
bool create_generate_mips(const Vector<OverlappedBlock>& others, GpuMemoryObjectType type, int heap_id);
|
|
bool create_texture_triplet(const Vector<OverlappedBlock>& others, GpuMemoryObjectType type, int heap_id);
|
|
bool create_maybe_deleted(const Vector<OverlappedBlock>& others, GpuMemoryObjectType type, int heap_id);
|
|
bool create_all_the_same(const Vector<OverlappedBlock>& others, int heap_id);
|
|
|
|
[[nodiscard]] String create_dbg_exit(const String& msg, const uint64_t* vaddr, const uint64_t* size, int vaddr_num,
|
|
const Vector<OverlappedBlock>& others, GpuMemoryObjectType type);
|
|
|
|
Core::Mutex m_mutex;
|
|
|
|
Vector<Heap> m_heaps;
|
|
|
|
uint64_t m_current_frame = 0;
|
|
|
|
Core::Database::Connection m_db;
|
|
Core::Database::Statement* m_db_add_range = nullptr;
|
|
Core::Database::Statement* m_db_add_object = nullptr;
|
|
};
|
|
|
|
class GpuResources
|
|
{
|
|
public:
|
|
struct Info
|
|
{
|
|
uint32_t owner = 0;
|
|
bool free = true;
|
|
uint64_t memory = 0;
|
|
size_t size = 0;
|
|
String name;
|
|
uint32_t type = 0;
|
|
uint64_t user_data = 0;
|
|
};
|
|
|
|
GpuResources() { EXIT_NOT_IMPLEMENTED(!Core::Thread::IsMainThread()); }
|
|
virtual ~GpuResources() { KYTY_NOT_IMPLEMENTED; }
|
|
KYTY_CLASS_NO_COPY(GpuResources);
|
|
|
|
uint32_t AddOwner(const String& name);
|
|
uint32_t AddResource(uint32_t owner_handle, uint64_t memory, size_t size, const String& name, uint32_t type, uint64_t user_data);
|
|
void DeleteOwner(uint32_t owner_handle);
|
|
void DeleteResources(uint32_t owner_handle);
|
|
void DeleteResource(uint32_t resource_handle);
|
|
|
|
bool FindInfo(uint64_t memory, Info* dst);
|
|
|
|
private:
|
|
struct Owner
|
|
{
|
|
String name;
|
|
bool free = true;
|
|
};
|
|
|
|
Core::Mutex m_mutex;
|
|
|
|
Vector<Owner> m_owners;
|
|
Vector<Info> m_infos;
|
|
};
|
|
|
|
static GpuMemory* g_gpu_memory = nullptr;
|
|
static GpuResources* g_gpu_resources = nullptr;
|
|
|
|
uint32_t GpuResources::AddOwner(const String& name)
|
|
{
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
Owner n;
|
|
n.name = name;
|
|
n.free = false;
|
|
|
|
uint32_t index = 0;
|
|
for (auto& b: m_owners)
|
|
{
|
|
if (b.free)
|
|
{
|
|
b = n;
|
|
return index;
|
|
}
|
|
index++;
|
|
}
|
|
|
|
m_owners.Add(n);
|
|
|
|
return index;
|
|
}
|
|
|
|
uint32_t GpuResources::AddResource(uint32_t owner_handle, uint64_t memory, size_t size, const String& name, uint32_t type,
|
|
uint64_t user_data)
|
|
{
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
EXIT_NOT_IMPLEMENTED(!m_owners.IndexValid(owner_handle));
|
|
EXIT_NOT_IMPLEMENTED(memory == 0);
|
|
|
|
Info info;
|
|
info.owner = owner_handle;
|
|
info.memory = memory;
|
|
info.free = false;
|
|
info.name = name;
|
|
info.size = size;
|
|
info.type = type;
|
|
info.user_data = user_data;
|
|
|
|
uint32_t index = 0;
|
|
for (auto& i: m_infos)
|
|
{
|
|
if (i.free)
|
|
{
|
|
i = info;
|
|
return index;
|
|
}
|
|
index++;
|
|
}
|
|
|
|
m_infos.Add(info);
|
|
|
|
return index;
|
|
}
|
|
|
|
void GpuResources::DeleteOwner(uint32_t owner_handle)
|
|
{
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
EXIT_NOT_IMPLEMENTED(!m_owners.IndexValid(owner_handle));
|
|
|
|
for (auto& i: m_infos)
|
|
{
|
|
if (!i.free && i.owner == owner_handle)
|
|
{
|
|
i.free = true;
|
|
}
|
|
}
|
|
|
|
EXIT_NOT_IMPLEMENTED(m_owners[owner_handle].free);
|
|
|
|
m_owners[owner_handle].free = true;
|
|
}
|
|
|
|
void GpuResources::DeleteResources(uint32_t owner_handle)
|
|
{
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
EXIT_NOT_IMPLEMENTED(!m_owners.IndexValid(owner_handle));
|
|
|
|
for (auto& i: m_infos)
|
|
{
|
|
if (!i.free && i.owner == owner_handle)
|
|
{
|
|
i.free = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GpuResources::DeleteResource(uint32_t resource_handle)
|
|
{
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
EXIT_NOT_IMPLEMENTED(!m_infos.IndexValid(resource_handle));
|
|
|
|
EXIT_NOT_IMPLEMENTED(m_infos[resource_handle].free);
|
|
|
|
m_infos[resource_handle].free = true;
|
|
}
|
|
|
|
bool GpuResources::FindInfo(uint64_t memory, Info* dst)
|
|
{
|
|
EXIT_IF(dst == nullptr);
|
|
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
// NOLINTNEXTLINE(readability-use-anyofallof)
|
|
for (const auto& i: m_infos)
|
|
{
|
|
if (!i.free && memory >= i.memory && memory < i.memory + i.size)
|
|
{
|
|
*dst = i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void GpuMemory::SetAllocatedRange(uint64_t vaddr, uint64_t size)
|
|
{
|
|
EXIT_IF(size == 0);
|
|
|
|
EXIT_NOT_IMPLEMENTED(IsAllocated(vaddr, size));
|
|
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
Heap h;
|
|
h.range.vaddr = vaddr;
|
|
h.range.size = size;
|
|
h.objects_map1 = new GpuMap1;
|
|
h.objects_map2 = new GpuMap2;
|
|
|
|
m_heaps.Add(h);
|
|
}
|
|
|
|
bool GpuMemory::IsAllocated(uint64_t vaddr, uint64_t size)
|
|
{
|
|
EXIT_IF(size == 0);
|
|
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
return (GetHeapId(vaddr, size) >= 0);
|
|
}
|
|
|
|
int GpuMemory::GetHeapId(uint64_t vaddr, uint64_t size)
|
|
{
|
|
int index = 0;
|
|
for (const auto& heap: m_heaps)
|
|
{
|
|
const auto& r = heap.range;
|
|
if ((vaddr >= r.vaddr && vaddr < r.vaddr + r.size) || ((vaddr + size - 1) >= r.vaddr && (vaddr + size - 1) < r.vaddr + r.size))
|
|
{
|
|
return index;
|
|
}
|
|
index++;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static uint64_t calc_hash(const uint8_t* buf, uint64_t size)
|
|
{
|
|
KYTY_PROFILER_FUNCTION();
|
|
|
|
return (size > 0 && buf != nullptr ? XXH64(buf, size, 0) : 0);
|
|
}
|
|
|
|
static uint64_t get_current_time()
|
|
{
|
|
static std::atomic_uint64_t t(0);
|
|
return ++t;
|
|
}
|
|
|
|
void GpuMemory::Link(int heap_id, int id1, int id2, OverlapType rel, GpuMemoryScenario scenario)
|
|
{
|
|
OverlapType other_rel = OverlapType::None;
|
|
switch (rel)
|
|
{
|
|
case OverlapType::Equals: other_rel = OverlapType::Equals; break;
|
|
case OverlapType::IsContainedWithin: other_rel = OverlapType::Contains; break;
|
|
case OverlapType::Contains: other_rel = OverlapType::IsContainedWithin; break;
|
|
default: EXIT("invalid rel: %s\n", Core::EnumName(rel).C_Str());
|
|
}
|
|
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
auto& h1 = heap.objects[id1];
|
|
EXIT_IF(h1.free);
|
|
|
|
auto& h2 = heap.objects[id2];
|
|
EXIT_IF(h2.free);
|
|
|
|
h1.others.Add({rel, id2});
|
|
h2.others.Add({other_rel, id1});
|
|
|
|
h1.scenario = scenario;
|
|
h2.scenario = scenario;
|
|
}
|
|
|
|
void GpuMemory::Update(uint64_t submit_id, GraphicContext* ctx, int heap_id, int obj_id)
|
|
{
|
|
KYTY_PROFILER_BLOCK("GpuMemory::Update");
|
|
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
auto& h = heap.objects[obj_id];
|
|
auto& o = h.info;
|
|
bool need_update = false;
|
|
|
|
bool mem_watch = false;
|
|
|
|
if ((mem_watch && o.cpu_update_time > o.gpu_update_time) || (!mem_watch && submit_id > o.submit_id))
|
|
{
|
|
uint64_t hash[VADDR_BLOCKS_MAX] = {};
|
|
|
|
for (int vi = 0; vi < h.block.vaddr_num; vi++)
|
|
{
|
|
EXIT_IF(h.block.size[vi] == 0);
|
|
|
|
if (o.check_hash)
|
|
{
|
|
hash[vi] = calc_hash(reinterpret_cast<const uint8_t*>(h.block.vaddr[vi]), h.block.size[vi]);
|
|
} else
|
|
{
|
|
hash[vi] = 0;
|
|
}
|
|
}
|
|
|
|
for (int vi = 0; vi < h.block.vaddr_num; vi++)
|
|
{
|
|
if (o.hash[vi] != hash[vi])
|
|
{
|
|
printf("Update (CPU -> GPU): type = %s, vaddr = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n",
|
|
Core::EnumName(o.object.type).C_Str(), h.block.vaddr[vi], h.block.size[vi]);
|
|
need_update = true;
|
|
o.hash[vi] = hash[vi];
|
|
}
|
|
}
|
|
|
|
if (submit_id != UINT64_MAX)
|
|
{
|
|
o.submit_id = submit_id;
|
|
}
|
|
}
|
|
|
|
if (need_update)
|
|
{
|
|
EXIT_IF(o.update_func == nullptr);
|
|
o.update_func(ctx, o.params, o.object.obj, h.block.vaddr, h.block.size, h.block.vaddr_num);
|
|
o.gpu_update_time = get_current_time();
|
|
}
|
|
}
|
|
|
|
bool GpuMemory::create_existing(const Vector<OverlappedBlock>& others, const GpuObject& info, int heap_id, int* id)
|
|
{
|
|
EXIT_IF(id == nullptr);
|
|
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
uint64_t max_gpu_update_time = 0;
|
|
const OverlappedBlock* latest_block = nullptr;
|
|
|
|
for (const auto& obj: others)
|
|
{
|
|
auto& h = heap.objects[obj.object_id];
|
|
EXIT_IF(h.free);
|
|
auto& o = h.info;
|
|
|
|
if (h.scenario == GpuMemoryScenario::Common && obj.relation == OverlapType::Equals && o.object.type == info.type &&
|
|
info.Equal(o.params))
|
|
{
|
|
*id = obj.object_id;
|
|
return true;
|
|
}
|
|
|
|
// if (h.scenario == GpuMemoryScenario::Common && obj.relation == OverlapType::Contains && o.object.type == info.type &&
|
|
// info.Reuse(o.params))
|
|
// {
|
|
// *id = obj.object_id;
|
|
// return true;
|
|
// }
|
|
|
|
if (o.gpu_update_time > max_gpu_update_time)
|
|
{
|
|
max_gpu_update_time = o.gpu_update_time;
|
|
latest_block = &obj;
|
|
}
|
|
}
|
|
|
|
if (latest_block != nullptr)
|
|
{
|
|
auto& h = heap.objects[latest_block->object_id];
|
|
auto& o = h.info;
|
|
|
|
if (h.scenario == GpuMemoryScenario::GenerateMips && latest_block->relation == OverlapType::Equals && o.object.type == info.type &&
|
|
info.Equal(o.params))
|
|
{
|
|
*id = latest_block->object_id;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GpuMemory::create_generate_mips(const Vector<OverlappedBlock>& others, GpuMemoryObjectType type, int heap_id)
|
|
{
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
if (others.Size() == 3 && type == GpuMemoryObjectType::RenderTexture)
|
|
{
|
|
const auto& b0 = others.At(0);
|
|
const auto& b1 = others.At(1);
|
|
const auto& b2 = others.At(2);
|
|
OverlapType rel0 = b0.relation;
|
|
OverlapType rel1 = b1.relation;
|
|
OverlapType rel2 = b2.relation;
|
|
const auto& o0 = heap.objects[b0.object_id];
|
|
const auto& o1 = heap.objects[b1.object_id];
|
|
const auto& o2 = heap.objects[b2.object_id];
|
|
GpuMemoryObjectType type0 = o0.info.object.type;
|
|
GpuMemoryObjectType type1 = o1.info.object.type;
|
|
GpuMemoryObjectType type2 = o2.info.object.type;
|
|
|
|
if (rel0 == OverlapType::Contains && rel1 == OverlapType::Contains && rel2 == OverlapType::Contains &&
|
|
type0 == GpuMemoryObjectType::StorageBuffer && type1 == GpuMemoryObjectType::Texture &&
|
|
type2 == GpuMemoryObjectType::StorageTexture &&
|
|
((o0.others.Size() == 2 && o0.scenario == GpuMemoryScenario::TextureTriplet && o1.others.Size() == 2 &&
|
|
o1.scenario == GpuMemoryScenario::TextureTriplet && o2.others.Size() == 2 &&
|
|
o2.scenario == GpuMemoryScenario::TextureTriplet) ||
|
|
(o0.others.Size() >= 3 && o0.scenario == GpuMemoryScenario::GenerateMips && o1.others.Size() >= 3 &&
|
|
o1.scenario == GpuMemoryScenario::GenerateMips && o2.others.Size() >= 3 && o2.scenario == GpuMemoryScenario::GenerateMips)))
|
|
{
|
|
return true;
|
|
}
|
|
} else if (others.Size() >= 3 && type == GpuMemoryObjectType::Texture)
|
|
{
|
|
const auto& b0 = others.At(0);
|
|
const auto& b1 = others.At(1);
|
|
const auto& b2 = others.At(2);
|
|
OverlapType rel0 = b0.relation;
|
|
OverlapType rel1 = b1.relation;
|
|
OverlapType rel2 = b2.relation;
|
|
const auto& o0 = heap.objects[b0.object_id];
|
|
const auto& o1 = heap.objects[b1.object_id];
|
|
const auto& o2 = heap.objects[b2.object_id];
|
|
GpuMemoryObjectType type0 = o0.info.object.type;
|
|
GpuMemoryObjectType type1 = o1.info.object.type;
|
|
GpuMemoryObjectType type2 = o2.info.object.type;
|
|
|
|
if (((rel0 == OverlapType::Contains && rel1 == OverlapType::Contains && rel2 == OverlapType::Contains) ||
|
|
(rel0 == OverlapType::Equals && rel1 == OverlapType::Equals && rel2 == OverlapType::Equals)) &&
|
|
type0 == GpuMemoryObjectType::StorageBuffer && type1 == GpuMemoryObjectType::Texture &&
|
|
type2 == GpuMemoryObjectType::StorageTexture && o0.scenario == GpuMemoryScenario::GenerateMips &&
|
|
o1.scenario == GpuMemoryScenario::GenerateMips && o2.scenario == GpuMemoryScenario::GenerateMips)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GpuMemory::create_texture_triplet(const Vector<OverlappedBlock>& others, GpuMemoryObjectType type, int heap_id)
|
|
{
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
if (others.Size() == 2 && type == GpuMemoryObjectType::StorageTexture)
|
|
{
|
|
const auto& b0 = others.At(0);
|
|
const auto& b1 = others.At(1);
|
|
OverlapType rel0 = b0.relation;
|
|
OverlapType rel1 = b1.relation;
|
|
const auto& o0 = heap.objects[b0.object_id];
|
|
const auto& o1 = heap.objects[b1.object_id];
|
|
GpuMemoryObjectType type0 = o0.info.object.type;
|
|
GpuMemoryObjectType type1 = o1.info.object.type;
|
|
|
|
if (rel0 == OverlapType::Equals && rel1 == OverlapType::Equals && type0 == GpuMemoryObjectType::StorageBuffer &&
|
|
type1 == GpuMemoryObjectType::Texture &&
|
|
(o0.others.Size() == 1 && o0.scenario == GpuMemoryScenario::Common && o1.others.Size() == 1 &&
|
|
o1.scenario == GpuMemoryScenario::Common))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool GpuMemory::create_maybe_deleted(const Vector<OverlappedBlock>& others, GpuMemoryObjectType type, int heap_id)
|
|
{
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
if (type == GpuMemoryObjectType::VertexBuffer || type == GpuMemoryObjectType::IndexBuffer)
|
|
{
|
|
return std::all_of(others.begin(), others.end(),
|
|
[heap](auto& r)
|
|
{
|
|
OverlapType rel = r.relation;
|
|
const auto& o = heap.objects[r.object_id];
|
|
GpuMemoryObjectType o_type = o.info.object.type;
|
|
return ((rel == OverlapType::IsContainedWithin || rel == OverlapType::Crosses) &&
|
|
(o_type == GpuMemoryObjectType::VertexBuffer || o_type == GpuMemoryObjectType::IndexBuffer));
|
|
});
|
|
}
|
|
if (type == GpuMemoryObjectType::Texture)
|
|
{
|
|
return std::all_of(others.begin(), others.end(),
|
|
[heap](auto& r)
|
|
{
|
|
OverlapType rel = r.relation;
|
|
const auto& o = heap.objects[r.object_id];
|
|
GpuMemoryObjectType o_type = o.info.object.type;
|
|
return ((rel == OverlapType::IsContainedWithin || rel == OverlapType::Crosses) &&
|
|
o_type == GpuMemoryObjectType::Texture);
|
|
});
|
|
}
|
|
if (type == GpuMemoryObjectType::RenderTexture)
|
|
{
|
|
return std::all_of(others.begin(), others.end(),
|
|
[heap](auto& r)
|
|
{
|
|
OverlapType rel = r.relation;
|
|
const auto& o = heap.objects[r.object_id];
|
|
GpuMemoryObjectType o_type = o.info.object.type;
|
|
return ((rel == OverlapType::IsContainedWithin || rel == OverlapType::Crosses) &&
|
|
(o_type == GpuMemoryObjectType::RenderTexture || o_type == GpuMemoryObjectType::DepthStencilBuffer));
|
|
});
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool GpuMemory::create_all_the_same(const Vector<OverlappedBlock>& others, int heap_id)
|
|
{
|
|
auto& heap = m_heaps[heap_id];
|
|
OverlapType rel = others.At(0).relation;
|
|
GpuMemoryObjectType type = heap.objects[others.At(0).object_id].info.object.type;
|
|
|
|
return std::all_of(others.begin(), others.end(),
|
|
[rel, type, heap](auto& r) { return (rel == r.relation && type == heap.objects[r.object_id].info.object.type); });
|
|
}
|
|
|
|
String GpuMemory::create_dbg_exit(const String& msg, const uint64_t* vaddr, const uint64_t* size, int vaddr_num,
|
|
const Vector<OverlappedBlock>& others, GpuMemoryObjectType type)
|
|
{
|
|
Core::StringList list;
|
|
list.Add(String::FromPrintf("Exit:"));
|
|
list.Add(msg);
|
|
for (int vi = 0; vi < vaddr_num; vi++)
|
|
{
|
|
list.Add(String::FromPrintf("\t vaddr = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "", vaddr[vi], size[vi]));
|
|
}
|
|
|
|
list.Add(String::FromPrintf("\t info.type = %s", Core::EnumName(type).C_Str()));
|
|
for (const auto& d: others)
|
|
{
|
|
list.Add(String::FromPrintf("\t id = %d, rel = %s", d.object_id, Core::EnumName(d.relation).C_Str()));
|
|
}
|
|
DbgDbDump();
|
|
DbgDbSave(U"_gpu_memory.db");
|
|
auto str = list.Concat(U'\n');
|
|
printf("%s\n", str.C_Str());
|
|
return str;
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
|
void* GpuMemory::CreateObject(uint64_t submit_id, GraphicContext* ctx, CommandBuffer* buffer, const uint64_t* vaddr, const uint64_t* size,
|
|
int vaddr_num, const GpuObject& info)
|
|
{
|
|
KYTY_PROFILER_BLOCK("GpuMemory::CreateObject", profiler::colors::Green300);
|
|
|
|
EXIT_IF(info.type == GpuMemoryObjectType::Invalid);
|
|
EXIT_IF(vaddr == nullptr || size == nullptr || vaddr_num > VADDR_BLOCKS_MAX || vaddr_num <= 0);
|
|
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
int heap_id = GetHeapId(vaddr[0], size[0]);
|
|
|
|
EXIT_NOT_IMPLEMENTED(heap_id < 0);
|
|
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
bool overlap = false;
|
|
bool delete_all = false;
|
|
bool create_from_objects = false;
|
|
|
|
GpuMemoryScenario scenario = GpuMemoryScenario::Common;
|
|
|
|
int fast_id = -1;
|
|
if (FindFast(heap_id, vaddr, size, vaddr_num, info.type, false, &fast_id))
|
|
{
|
|
auto& h = heap.objects[fast_id];
|
|
EXIT_IF(h.free);
|
|
auto& o = h.info;
|
|
|
|
if (h.scenario == GpuMemoryScenario::Common && info.Equal(o.params))
|
|
{
|
|
Update(submit_id, ctx, heap_id, fast_id);
|
|
|
|
o.use_num++;
|
|
o.use_last_frame = m_current_frame;
|
|
o.in_use = true;
|
|
o.read_only = info.read_only;
|
|
o.check_hash = info.check_hash;
|
|
|
|
return o.object.obj;
|
|
}
|
|
}
|
|
|
|
auto others = FindBlocks(heap_id, vaddr, size, vaddr_num);
|
|
|
|
if (!others.IsEmpty())
|
|
{
|
|
int existing_id = -1;
|
|
if (create_existing(others, info, heap_id, &existing_id))
|
|
{
|
|
auto& h = heap.objects[existing_id];
|
|
EXIT_IF(h.free);
|
|
auto& o = h.info;
|
|
|
|
Update(submit_id, ctx, heap_id, existing_id);
|
|
|
|
o.use_num++;
|
|
o.use_last_frame = m_current_frame;
|
|
o.in_use = true;
|
|
o.read_only = info.read_only;
|
|
o.check_hash = info.check_hash;
|
|
|
|
return o.object.obj;
|
|
}
|
|
|
|
if (others.Size() == 1)
|
|
{
|
|
const auto& obj = others.At(0);
|
|
auto& h = heap.objects[obj.object_id];
|
|
EXIT_IF(h.free);
|
|
auto& o = h.info;
|
|
|
|
switch (ObjectsRelation(o.object.type, obj.relation, info.type))
|
|
{
|
|
case ObjectsRelation(GpuMemoryObjectType::StorageBuffer, OverlapType::Equals, GpuMemoryObjectType::RenderTexture):
|
|
case ObjectsRelation(GpuMemoryObjectType::StorageBuffer, OverlapType::Equals, GpuMemoryObjectType::StorageTexture):
|
|
case ObjectsRelation(GpuMemoryObjectType::StorageBuffer, OverlapType::Equals, GpuMemoryObjectType::Texture):
|
|
case ObjectsRelation(GpuMemoryObjectType::VideoOutBuffer, OverlapType::Equals, GpuMemoryObjectType::StorageBuffer):
|
|
{
|
|
overlap = true;
|
|
break;
|
|
}
|
|
case ObjectsRelation(GpuMemoryObjectType::StorageBuffer, OverlapType::Contains, GpuMemoryObjectType::Label):
|
|
case ObjectsRelation(GpuMemoryObjectType::Label, OverlapType::Equals, GpuMemoryObjectType::Label):
|
|
case ObjectsRelation(GpuMemoryObjectType::DepthStencilBuffer, OverlapType::Contains,
|
|
GpuMemoryObjectType::DepthStencilBuffer):
|
|
case ObjectsRelation(GpuMemoryObjectType::IndexBuffer, OverlapType::Crosses, GpuMemoryObjectType::IndexBuffer):
|
|
case ObjectsRelation(GpuMemoryObjectType::IndexBuffer, OverlapType::Contains, GpuMemoryObjectType::IndexBuffer):
|
|
case ObjectsRelation(GpuMemoryObjectType::IndexBuffer, OverlapType::IsContainedWithin, GpuMemoryObjectType::IndexBuffer):
|
|
case ObjectsRelation(GpuMemoryObjectType::IndexBuffer, OverlapType::Crosses, GpuMemoryObjectType::VertexBuffer):
|
|
case ObjectsRelation(GpuMemoryObjectType::IndexBuffer, OverlapType::Contains, GpuMemoryObjectType::VertexBuffer):
|
|
case ObjectsRelation(GpuMemoryObjectType::IndexBuffer, OverlapType::IsContainedWithin, GpuMemoryObjectType::VertexBuffer):
|
|
case ObjectsRelation(GpuMemoryObjectType::VertexBuffer, OverlapType::Crosses, GpuMemoryObjectType::VertexBuffer):
|
|
case ObjectsRelation(GpuMemoryObjectType::VertexBuffer, OverlapType::Contains, GpuMemoryObjectType::VertexBuffer):
|
|
case ObjectsRelation(GpuMemoryObjectType::VertexBuffer, OverlapType::IsContainedWithin, GpuMemoryObjectType::VertexBuffer):
|
|
case ObjectsRelation(GpuMemoryObjectType::VertexBuffer, OverlapType::Crosses, GpuMemoryObjectType::IndexBuffer):
|
|
case ObjectsRelation(GpuMemoryObjectType::VertexBuffer, OverlapType::Contains, GpuMemoryObjectType::IndexBuffer):
|
|
case ObjectsRelation(GpuMemoryObjectType::VertexBuffer, OverlapType::IsContainedWithin, GpuMemoryObjectType::IndexBuffer):
|
|
case ObjectsRelation(GpuMemoryObjectType::Texture, OverlapType::Crosses, GpuMemoryObjectType::Texture):
|
|
case ObjectsRelation(GpuMemoryObjectType::Texture, OverlapType::Contains, GpuMemoryObjectType::Texture):
|
|
case ObjectsRelation(GpuMemoryObjectType::Texture, OverlapType::IsContainedWithin, GpuMemoryObjectType::Texture):
|
|
{
|
|
delete_all = true;
|
|
break;
|
|
}
|
|
case ObjectsRelation(GpuMemoryObjectType::StorageTexture, OverlapType::Equals, GpuMemoryObjectType::Texture):
|
|
{
|
|
overlap = true;
|
|
create_from_objects = true;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
auto msg = String::FromPrintf("unknown relation: %s - %s - %s\n", Core::EnumName(o.object.type).C_Str(),
|
|
Core::EnumName(obj.relation).C_Str(), Core::EnumName(info.type).C_Str());
|
|
EXIT("%s\n", create_dbg_exit(msg, vaddr, size, vaddr_num, others, info.type).C_Str());
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
if (create_generate_mips(others, info.type, heap_id))
|
|
{
|
|
overlap = true;
|
|
create_from_objects = true;
|
|
scenario = GpuMemoryScenario::GenerateMips;
|
|
} else if (create_texture_triplet(others, info.type, heap_id))
|
|
{
|
|
overlap = true;
|
|
scenario = GpuMemoryScenario::TextureTriplet;
|
|
} else if (create_maybe_deleted(others, info.type, heap_id))
|
|
{
|
|
delete_all = true;
|
|
} else
|
|
{
|
|
if (!create_all_the_same(others, heap_id))
|
|
{
|
|
EXIT("%s\n", create_dbg_exit(U"!create_all_the_same", vaddr, size, vaddr_num, others, info.type).C_Str());
|
|
}
|
|
|
|
OverlapType rel = others.At(0).relation;
|
|
GpuMemoryObjectType type = heap.objects[others.At(0).object_id].info.object.type;
|
|
|
|
switch (ObjectsRelation(type, rel, info.type))
|
|
{
|
|
case ObjectsRelation(GpuMemoryObjectType::Label, OverlapType::IsContainedWithin, GpuMemoryObjectType::StorageBuffer):
|
|
delete_all = true;
|
|
break;
|
|
case ObjectsRelation(GpuMemoryObjectType::RenderTexture, OverlapType::IsContainedWithin, GpuMemoryObjectType::Texture):
|
|
overlap = true;
|
|
create_from_objects = true;
|
|
break;
|
|
default:
|
|
{
|
|
auto msg = String::FromPrintf("unknown relation: %s - %s - %s\n", Core::EnumName(type).C_Str(),
|
|
Core::EnumName(rel).C_Str(), Core::EnumName(info.type).C_Str());
|
|
EXIT("%s\n", create_dbg_exit(msg, vaddr, size, vaddr_num, others, info.type).C_Str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
EXIT_IF(delete_all && overlap);
|
|
|
|
Vector<Destructor> destructors;
|
|
|
|
if (delete_all)
|
|
{
|
|
for (const auto& obj: others)
|
|
{
|
|
destructors.Add(Free(heap_id, obj.object_id));
|
|
}
|
|
}
|
|
|
|
for (int vi = 0; vi < vaddr_num; vi++)
|
|
{
|
|
EXIT_NOT_IMPLEMENTED(!IsAllocated(vaddr[vi], size[vi]));
|
|
}
|
|
|
|
ObjectInfo o {};
|
|
|
|
for (int i = 0; i < GpuObject::PARAMS_MAX; i++)
|
|
{
|
|
o.params[i] = info.params[i];
|
|
}
|
|
|
|
uint64_t hash[VADDR_BLOCKS_MAX] = {};
|
|
|
|
for (int vi = 0; vi < vaddr_num; vi++)
|
|
{
|
|
EXIT_IF(size[vi] == 0);
|
|
|
|
if (info.check_hash)
|
|
{
|
|
hash[vi] = calc_hash(reinterpret_cast<const uint8_t*>(vaddr[vi]), size[vi]);
|
|
} else
|
|
{
|
|
hash[vi] = 0;
|
|
}
|
|
}
|
|
|
|
o.object.type = info.type;
|
|
o.object.obj = nullptr;
|
|
for (int vi = 0; vi < vaddr_num; vi++)
|
|
{
|
|
o.hash[vi] = hash[vi];
|
|
}
|
|
o.cpu_update_time = get_current_time();
|
|
o.gpu_update_time = o.cpu_update_time;
|
|
o.submit_id = submit_id;
|
|
|
|
if (create_from_objects)
|
|
{
|
|
Vector<GpuMemoryObject> objects;
|
|
for (const auto& obj: others)
|
|
{
|
|
const auto& o2 = heap.objects[obj.object_id].info;
|
|
objects.Add(o2.object);
|
|
}
|
|
auto create_func = info.GetCreateFromObjectsFunc();
|
|
EXIT_IF(create_func == nullptr);
|
|
o.object.obj = create_func(ctx, buffer, o.params, scenario, objects, &o.mem);
|
|
} else
|
|
{
|
|
o.object.obj = info.GetCreateFunc()(ctx, o.params, vaddr, size, vaddr_num, &o.mem);
|
|
}
|
|
|
|
o.write_back_func = info.GetWriteBackFunc();
|
|
o.delete_func = info.GetDeleteFunc();
|
|
o.update_func = info.GetUpdateFunc();
|
|
o.use_num = 1;
|
|
o.use_last_frame = m_current_frame;
|
|
o.in_use = true;
|
|
o.read_only = info.read_only;
|
|
o.check_hash = info.check_hash;
|
|
|
|
int index = 0;
|
|
|
|
if (heap.first_free_id != -1)
|
|
{
|
|
index = heap.first_free_id;
|
|
auto& u = heap.objects[heap.first_free_id];
|
|
heap.first_free_id = u.next_free_id;
|
|
u.free = false;
|
|
u.block = CreateBlock(vaddr, size, vaddr_num, heap_id, index);
|
|
u.info = o;
|
|
u.others.Clear();
|
|
u.scenario = scenario;
|
|
} else
|
|
{
|
|
index = static_cast<int>(heap.objects.Size());
|
|
|
|
Object h {};
|
|
h.block = CreateBlock(vaddr, size, vaddr_num, heap_id, index);
|
|
h.info = o;
|
|
h.others.Clear();
|
|
h.scenario = scenario;
|
|
h.free = false;
|
|
heap.objects.Add(h);
|
|
}
|
|
|
|
if (overlap)
|
|
{
|
|
for (const auto& obj: others)
|
|
{
|
|
Link(heap_id, index, obj.object_id, obj.relation, scenario);
|
|
}
|
|
}
|
|
|
|
m_mutex.Unlock();
|
|
for (auto& d: destructors)
|
|
{
|
|
d.delete_func(ctx, d.obj, &d.mem);
|
|
}
|
|
m_mutex.Lock();
|
|
|
|
return o.object.obj;
|
|
}
|
|
|
|
Vector<GpuMemoryObject> GpuMemory::FindObjects(const uint64_t* vaddr, const uint64_t* size, int vaddr_num, GpuMemoryObjectType type,
|
|
bool exact, bool only_first)
|
|
{
|
|
KYTY_PROFILER_BLOCK("GpuMemory::FindObjects", profiler::colors::Green200);
|
|
|
|
EXIT_IF(vaddr == nullptr || size == nullptr || vaddr_num > VADDR_BLOCKS_MAX || vaddr_num <= 0);
|
|
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
Vector<GpuMemoryObject> ret;
|
|
|
|
int heap_id = GetHeapId(vaddr[0], size[0]);
|
|
|
|
EXIT_NOT_IMPLEMENTED(heap_id < 0);
|
|
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
if (exact)
|
|
{
|
|
int fast_id = -1;
|
|
if (FindFast(heap_id, vaddr, size, vaddr_num, type, only_first, &fast_id))
|
|
{
|
|
const auto& h = heap.objects[fast_id];
|
|
EXIT_IF(h.free);
|
|
ret.Add(h.info.object);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
auto objects = FindBlocks(heap_id, vaddr, size, vaddr_num, only_first);
|
|
|
|
for (const auto& obj: objects)
|
|
{
|
|
const auto& h = heap.objects[obj.object_id];
|
|
EXIT_IF(h.free);
|
|
if (h.info.object.type == type &&
|
|
(obj.relation == OverlapType::Equals || (!exact && obj.relation == OverlapType::IsContainedWithin)))
|
|
{
|
|
ret.Add(h.info.object);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void GpuMemory::ResetHash(const uint64_t* vaddr, const uint64_t* size, int vaddr_num, GpuMemoryObjectType type)
|
|
{
|
|
EXIT_IF(type == GpuMemoryObjectType::Invalid);
|
|
EXIT_IF(vaddr == nullptr || size == nullptr || vaddr_num > VADDR_BLOCKS_MAX || vaddr_num <= 0);
|
|
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
int heap_id = GetHeapId(vaddr[0], size[0]);
|
|
|
|
EXIT_NOT_IMPLEMENTED(heap_id < 0);
|
|
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
uint64_t new_hash = 0;
|
|
|
|
int fast_id = -1;
|
|
if (FindFast(heap_id, vaddr, size, vaddr_num, type, false, &fast_id))
|
|
{
|
|
auto& h = heap.objects[fast_id];
|
|
EXIT_IF(h.free);
|
|
auto& o = h.info;
|
|
|
|
if (h.scenario == GpuMemoryScenario::Common)
|
|
{
|
|
for (int vi = 0; vi < vaddr_num; vi++)
|
|
{
|
|
printf("ResetHash: type = %s, vaddr = 0x%016" PRIx64 ", size = 0x%016" PRIx64 ", old_hash = 0x%016" PRIx64
|
|
", new_hash = 0x%016" PRIx64 "\n",
|
|
Core::EnumName(o.object.type).C_Str(), vaddr[vi], size[vi], o.hash[vi], new_hash);
|
|
}
|
|
o.gpu_update_time = get_current_time();
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto object_ids = FindBlocks(heap_id, vaddr, size, vaddr_num);
|
|
|
|
if (!object_ids.IsEmpty())
|
|
{
|
|
for (const auto& obj: object_ids)
|
|
{
|
|
auto& h = heap.objects[obj.object_id];
|
|
EXIT_IF(h.free);
|
|
|
|
auto& o = h.info;
|
|
if (o.object.type == type)
|
|
{
|
|
EXIT_NOT_IMPLEMENTED(obj.relation != OverlapType::Equals);
|
|
|
|
for (int vi = 0; vi < vaddr_num; vi++)
|
|
{
|
|
printf("ResetHash: type = %s, vaddr = 0x%016" PRIx64 ", size = 0x%016" PRIx64 ", old_hash = 0x%016" PRIx64
|
|
", new_hash = 0x%016" PRIx64 "\n",
|
|
Core::EnumName(o.object.type).C_Str(), vaddr[vi], size[vi], o.hash[vi], new_hash);
|
|
}
|
|
o.gpu_update_time = get_current_time();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
|
void GpuMemory::Free(GraphicContext* ctx, uint64_t vaddr, uint64_t size, bool unmap)
|
|
{
|
|
KYTY_PROFILER_BLOCK("GpuMemory::Free", profiler::colors::Green300);
|
|
|
|
m_mutex.Lock();
|
|
|
|
printf("Release gpu objects:\n");
|
|
printf("\t gpu_vaddr = 0x%016" PRIx64 "\n", vaddr);
|
|
printf("\t size = 0x%016" PRIx64 "\n", size);
|
|
|
|
int heap_id = GetHeapId(vaddr, size);
|
|
|
|
EXIT_NOT_IMPLEMENTED(heap_id < 0);
|
|
|
|
auto object_ids = FindBlocks(heap_id, &vaddr, &size, 1);
|
|
|
|
Vector<Destructor> destructors;
|
|
|
|
for (const auto& obj: object_ids)
|
|
{
|
|
switch (obj.relation)
|
|
{
|
|
case OverlapType::IsContainedWithin:
|
|
case OverlapType::Crosses: destructors.Add(Free(heap_id, obj.object_id)); break;
|
|
default: GpuMemoryDbgDump(); EXIT("unknown obj.relation: %s\n", Core::EnumName(obj.relation).C_Str());
|
|
}
|
|
}
|
|
|
|
if (unmap)
|
|
{
|
|
EXIT_NOT_IMPLEMENTED(!IsAllocated(vaddr, size));
|
|
|
|
int index = 0;
|
|
for (auto& a: m_heaps)
|
|
{
|
|
if (a.range.vaddr == vaddr && a.range.size == size)
|
|
{
|
|
EXIT_IF(a.objects_map1 == nullptr);
|
|
EXIT_IF(a.objects_map2 == nullptr);
|
|
EXIT_NOT_IMPLEMENTED(heap_id != index);
|
|
EXIT_NOT_IMPLEMENTED(a.objects_size != 0);
|
|
EXIT_NOT_IMPLEMENTED(!a.objects_map1->IsEmpty());
|
|
EXIT_NOT_IMPLEMENTED(!a.objects_map2->IsEmpty());
|
|
|
|
delete a.objects_map1;
|
|
delete a.objects_map2;
|
|
|
|
m_heaps.RemoveAt(index);
|
|
break;
|
|
}
|
|
index++;
|
|
}
|
|
|
|
EXIT_NOT_IMPLEMENTED(IsAllocated(vaddr, size));
|
|
}
|
|
|
|
m_mutex.Unlock();
|
|
|
|
for (auto& d: destructors)
|
|
{
|
|
d.delete_func(ctx, d.obj, &d.mem);
|
|
}
|
|
}
|
|
|
|
GpuMemory::Destructor GpuMemory::Free(int heap_id, int object_id)
|
|
{
|
|
KYTY_PROFILER_BLOCK("GpuMemory::Free", profiler::colors::Green400);
|
|
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
auto& h = heap.objects[object_id];
|
|
EXIT_IF(h.free);
|
|
auto& o = h.info;
|
|
const auto& block = h.block;
|
|
|
|
EXIT_IF(o.delete_func == nullptr);
|
|
|
|
Destructor ret {};
|
|
|
|
if (o.delete_func != nullptr)
|
|
{
|
|
if (Config::GetPrintfDirection() != Log::Direction::Silent)
|
|
{
|
|
for (int vi = 0; vi < block.vaddr_num; vi++)
|
|
{
|
|
printf("Delete: type = %s, vaddr = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n", Core::EnumName(o.object.type).C_Str(),
|
|
block.vaddr[vi], block.size[vi]);
|
|
}
|
|
}
|
|
// o.delete_func(ctx, o.object.obj, &o.mem);
|
|
ret.delete_func = o.delete_func;
|
|
ret.obj = o.object.obj;
|
|
ret.mem = o.mem;
|
|
}
|
|
|
|
h.free = true;
|
|
h.next_free_id = heap.first_free_id;
|
|
heap.first_free_id = object_id;
|
|
DeleteBlock(&h.block, heap_id, object_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool GpuMemory::FindFast(int heap_id, const uint64_t* vaddr, const uint64_t* size, int vaddr_num, GpuMemoryObjectType type, bool only_first,
|
|
int* id)
|
|
{
|
|
KYTY_PROFILER_BLOCK("GpuMemory::FindFast", profiler::colors::Green200);
|
|
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
EXIT_IF(id == nullptr);
|
|
|
|
for (int vi = 0; vi < vaddr_num; vi++)
|
|
{
|
|
for (int obj_id: heap.objects_map1->FindAll(vaddr[vi]))
|
|
{
|
|
auto& b = heap.objects[obj_id];
|
|
EXIT_IF(b.free);
|
|
if (b.info.object.type == type)
|
|
{
|
|
bool equal = true;
|
|
if (b.block.vaddr_num == 1 || only_first)
|
|
{
|
|
if (GetOverlapType(b.block.vaddr[0], b.block.size[0], vaddr[0], size[0]) != OverlapType::Equals)
|
|
{
|
|
equal = false;
|
|
}
|
|
} else
|
|
{
|
|
for (int i = 0; i < vaddr_num; i++)
|
|
{
|
|
if (GetOverlapType(b.block.vaddr[i], b.block.size[i], vaddr[i], size[i]) != OverlapType::Equals)
|
|
{
|
|
equal = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (equal)
|
|
{
|
|
*id = obj_id;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
|
Vector<GpuMemory::OverlappedBlock> GpuMemory::FindBlocks_slow(int heap_id, const uint64_t* vaddr, const uint64_t* size, int vaddr_num,
|
|
bool only_first)
|
|
{
|
|
KYTY_PROFILER_BLOCK("GpuMemory::FindBlocks", profiler::colors::Green100);
|
|
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
EXIT_IF(vaddr_num <= 0 || vaddr_num > VADDR_BLOCKS_MAX);
|
|
EXIT_IF(vaddr == nullptr || size == nullptr);
|
|
EXIT_IF(only_first && vaddr_num != 1);
|
|
|
|
Vector<GpuMemory::OverlappedBlock> ret;
|
|
|
|
// TODO(): implement interval-tree
|
|
|
|
if (vaddr_num != 1)
|
|
{
|
|
int index = 0;
|
|
for (const auto& b: heap.objects)
|
|
{
|
|
if (!b.free)
|
|
{
|
|
bool equal = true;
|
|
for (int i = 0; i < vaddr_num; i++)
|
|
{
|
|
if (GetOverlapType(b.block.vaddr[i], b.block.size[i], vaddr[i], size[i]) != OverlapType::Equals)
|
|
{
|
|
equal = false;
|
|
break;
|
|
}
|
|
}
|
|
if (equal)
|
|
{
|
|
ret.Add({OverlapType::Equals, index});
|
|
} else
|
|
{
|
|
bool cross = false;
|
|
for (int i = 0; i < vaddr_num; i++)
|
|
{
|
|
for (int j = 0; j < b.block.vaddr_num; j++)
|
|
{
|
|
if (GetOverlapType(b.block.vaddr[j], b.block.size[j], vaddr[i], size[i]) != OverlapType::None)
|
|
{
|
|
cross = true;
|
|
break;
|
|
}
|
|
}
|
|
if (cross)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (cross)
|
|
{
|
|
ret.Add({OverlapType::Crosses, index});
|
|
}
|
|
}
|
|
}
|
|
index++;
|
|
}
|
|
} else
|
|
{
|
|
int index = 0;
|
|
for (const auto& b: heap.objects)
|
|
{
|
|
if (!b.free)
|
|
{
|
|
if (b.block.vaddr_num == 1 || only_first)
|
|
{
|
|
auto type = GetOverlapType(b.block.vaddr[0], b.block.size[0], vaddr[0], size[0]);
|
|
if (type != OverlapType::None)
|
|
{
|
|
ret.Add({type, index});
|
|
}
|
|
} else
|
|
{
|
|
for (int i = 0; i < b.block.vaddr_num; i++)
|
|
{
|
|
if (GetOverlapType(b.block.vaddr[i], b.block.size[i], vaddr[0], size[0]) != OverlapType::None)
|
|
{
|
|
ret.Add({OverlapType::Crosses, index});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
index++;
|
|
}
|
|
}
|
|
|
|
// printf("FindBlocks:\n");
|
|
// for (int vi = 0; vi < vaddr_num; vi++)
|
|
// {
|
|
// printf("\t vaddr = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n", vaddr[vi], size[vi]);
|
|
// }
|
|
// for (const auto& d: ret)
|
|
// {
|
|
// printf("\t id = %d, rel = %s\n", d.object_id, Core::EnumName(d.relation).C_Str());
|
|
// const auto& b = m_objects[d.object_id];
|
|
// for (int vi = 0; vi < b.block.vaddr_num; vi++)
|
|
// {
|
|
// printf("\t\t vaddr = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n", b.block.vaddr[vi], b.block.size[vi]);
|
|
// }
|
|
// }
|
|
|
|
return ret;
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
|
Vector<GpuMemory::OverlappedBlock> GpuMemory::FindBlocks(int heap_id, const uint64_t* vaddr, const uint64_t* size, int vaddr_num,
|
|
bool only_first)
|
|
{
|
|
KYTY_PROFILER_BLOCK("GpuMemory::FindBlocks", profiler::colors::Green100);
|
|
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
EXIT_IF(vaddr_num <= 0 || vaddr_num > VADDR_BLOCKS_MAX);
|
|
EXIT_IF(vaddr == nullptr || size == nullptr);
|
|
EXIT_IF(only_first && vaddr_num != 1);
|
|
|
|
Vector<GpuMemory::OverlappedBlock> ret;
|
|
|
|
// TODO(): implement interval-tree
|
|
|
|
if (vaddr_num != 1)
|
|
{
|
|
for (int index: heap.objects_map2->FindAll(vaddr, size, vaddr_num))
|
|
{
|
|
const auto& b = heap.objects[index];
|
|
if (!b.free)
|
|
{
|
|
bool equal = true;
|
|
for (int i = 0; i < vaddr_num; i++)
|
|
{
|
|
if (GetOverlapType(b.block.vaddr[i], b.block.size[i], vaddr[i], size[i]) != OverlapType::Equals)
|
|
{
|
|
equal = false;
|
|
break;
|
|
}
|
|
}
|
|
if (equal)
|
|
{
|
|
ret.Add({OverlapType::Equals, index});
|
|
} else
|
|
{
|
|
bool cross = false;
|
|
for (int i = 0; i < vaddr_num; i++)
|
|
{
|
|
for (int j = 0; j < b.block.vaddr_num; j++)
|
|
{
|
|
if (GetOverlapType(b.block.vaddr[j], b.block.size[j], vaddr[i], size[i]) != OverlapType::None)
|
|
{
|
|
cross = true;
|
|
break;
|
|
}
|
|
}
|
|
if (cross)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (cross)
|
|
{
|
|
ret.Add({OverlapType::Crosses, index});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
for (int index: heap.objects_map2->FindAll(vaddr[0], size[0]))
|
|
{
|
|
const auto& b = heap.objects[index];
|
|
if (!b.free)
|
|
{
|
|
if (b.block.vaddr_num == 1 || only_first)
|
|
{
|
|
auto type = GetOverlapType(b.block.vaddr[0], b.block.size[0], vaddr[0], size[0]);
|
|
if (type != OverlapType::None)
|
|
{
|
|
ret.Add({type, index});
|
|
}
|
|
} else
|
|
{
|
|
for (int i = 0; i < b.block.vaddr_num; i++)
|
|
{
|
|
if (GetOverlapType(b.block.vaddr[i], b.block.size[i], vaddr[0], size[0]) != OverlapType::None)
|
|
{
|
|
ret.Add({OverlapType::Crosses, index});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
KYTY_PROFILER_BLOCK("sort");
|
|
ret.Sort([](auto& b1, auto& b2) { return b1.object_id < b2.object_id; });
|
|
}
|
|
|
|
//
|
|
// printf("FindBlocks:\n");
|
|
// for (int vi = 0; vi < vaddr_num; vi++)
|
|
// {
|
|
// printf("\t vaddr = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n", vaddr[vi], size[vi]);
|
|
// }
|
|
// for (const auto& d: ret)
|
|
// {
|
|
// printf("\t id = %d, rel = %s\n", d.object_id, Core::EnumName(d.relation).C_Str());
|
|
// const auto& b = m_objects[d.object_id];
|
|
// for (int vi = 0; vi < b.block.vaddr_num; vi++)
|
|
// {
|
|
// printf("\t\t vaddr = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n", b.block.vaddr[vi], b.block.size[vi]);
|
|
// }
|
|
// }
|
|
|
|
return ret;
|
|
}
|
|
|
|
GpuMemory::Block GpuMemory::CreateBlock(const uint64_t* vaddr, const uint64_t* size, int vaddr_num, int heap_id, int obj_id)
|
|
{
|
|
EXIT_IF(vaddr_num > VADDR_BLOCKS_MAX);
|
|
EXIT_IF(vaddr == nullptr || size == nullptr);
|
|
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
Block nb {};
|
|
nb.vaddr_num = vaddr_num;
|
|
for (int vi = 0; vi < vaddr_num; vi++)
|
|
{
|
|
nb.vaddr[vi] = vaddr[vi];
|
|
nb.size[vi] = size[vi];
|
|
heap.objects_size += size[vi];
|
|
heap.objects_map1->Insert(vaddr[vi], obj_id);
|
|
heap.objects_map2->Insert(vaddr[vi], size[vi], obj_id);
|
|
}
|
|
return nb;
|
|
}
|
|
|
|
void GpuMemory::DeleteBlock(Block* b, int heap_id, int obj_id)
|
|
{
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
for (int vi = 0; vi < b->vaddr_num; vi++)
|
|
{
|
|
heap.objects_size -= b->size[vi];
|
|
heap.objects_map1->Erase(b->vaddr[vi], obj_id);
|
|
heap.objects_map2->Erase(b->vaddr[vi], b->size[vi], obj_id);
|
|
}
|
|
}
|
|
|
|
void GpuMemory::FrameDone()
|
|
{
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
m_current_frame++;
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
|
void GpuMemory::WriteBack(GraphicContext* ctx, CommandProcessor* cp)
|
|
{
|
|
GraphicsRunCommandProcessorLock(cp);
|
|
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
struct WriteBackObject
|
|
{
|
|
int heap_id = -1;
|
|
int object_id = -1;
|
|
};
|
|
|
|
Vector<WriteBackObject> objects;
|
|
|
|
int heap_id = 0;
|
|
for (auto& heap: m_heaps)
|
|
{
|
|
int index = 0;
|
|
for (auto& h: heap.objects)
|
|
{
|
|
if (!h.free)
|
|
{
|
|
auto& o = h.info;
|
|
if (o.in_use && o.write_back_func != nullptr && !o.read_only)
|
|
{
|
|
objects.Add(WriteBackObject({heap_id, index}));
|
|
}
|
|
}
|
|
index++;
|
|
}
|
|
heap_id++;
|
|
}
|
|
|
|
if (!objects.IsEmpty())
|
|
{
|
|
// Guarantee that any previously submitted commands have completed
|
|
GraphicsRunCommandProcessorFlush(cp);
|
|
GraphicsRunCommandProcessorWait(cp);
|
|
|
|
for (const auto& obj: objects)
|
|
{
|
|
auto& heap = m_heaps[obj.heap_id];
|
|
auto& h = heap.objects[obj.object_id];
|
|
auto& o = h.info;
|
|
auto& block = h.block;
|
|
|
|
o.write_back_func(ctx, o.params, o.object.obj, block.vaddr, block.size, block.vaddr_num);
|
|
o.cpu_update_time = get_current_time();
|
|
|
|
if (!h.others.IsEmpty())
|
|
{
|
|
EXIT_NOT_IMPLEMENTED(h.others.Size() != 1);
|
|
EXIT_NOT_IMPLEMENTED(h.others.At(0).relation != OverlapType::Equals);
|
|
|
|
auto& o2 = heap.objects[h.others.At(0).object_id].info;
|
|
|
|
o2.cpu_update_time = o.cpu_update_time;
|
|
o2.submit_id = 0;
|
|
for (int vi = 0; vi < block.vaddr_num; vi++)
|
|
{
|
|
o2.hash[vi] = 0;
|
|
}
|
|
Update(o.submit_id, ctx, obj.heap_id, h.others.At(0).object_id);
|
|
|
|
for (int vi = 0; vi < block.vaddr_num; vi++)
|
|
{
|
|
printf("WriteBack (GPU -> CPU): type = %s, vaddr = 0x%016" PRIx64 ", size = 0x%016" PRIx64 ", old_hash = 0x%016" PRIx64
|
|
", new_hash = 0x%016" PRIx64 "\n",
|
|
Core::EnumName(o.object.type).C_Str(), block.vaddr[vi], block.size[vi], o.hash[vi], o2.hash[vi]);
|
|
|
|
o.hash[vi] = o2.hash[vi];
|
|
}
|
|
} else
|
|
{
|
|
for (int vi = 0; vi < block.vaddr_num; vi++)
|
|
{
|
|
uint64_t new_hash = 0;
|
|
|
|
if (o.check_hash)
|
|
{
|
|
new_hash = calc_hash(reinterpret_cast<const uint8_t*>(block.vaddr[vi]), block.size[vi]);
|
|
}
|
|
|
|
printf("WriteBack (GPU -> CPU): type = %s, vaddr = 0x%016" PRIx64 ", size = 0x%016" PRIx64 ", old_hash = 0x%016" PRIx64
|
|
", new_hash = 0x%016" PRIx64 "\n",
|
|
Core::EnumName(o.object.type).C_Str(), block.vaddr[vi], block.size[vi], o.hash[vi], new_hash);
|
|
|
|
o.hash[vi] = new_hash;
|
|
}
|
|
}
|
|
|
|
o.in_use = false;
|
|
}
|
|
}
|
|
|
|
GraphicsRunCommandProcessorUnlock(cp);
|
|
}
|
|
|
|
void GpuMemory::Flush(GraphicContext* ctx, uint64_t vaddr, uint64_t size)
|
|
{
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
int heap_id = GetHeapId(vaddr, size);
|
|
|
|
EXIT_NOT_IMPLEMENTED(heap_id < 0);
|
|
|
|
auto& heap = m_heaps[heap_id];
|
|
|
|
auto object_ids = FindBlocks(heap_id, &vaddr, &size, 1);
|
|
|
|
for (const auto& obj: object_ids)
|
|
{
|
|
auto& h = heap.objects[obj.object_id];
|
|
EXIT_IF(h.free);
|
|
|
|
Update(UINT64_MAX, ctx, heap_id, obj.object_id);
|
|
}
|
|
}
|
|
|
|
void GpuMemory::FlushAll(GraphicContext* ctx)
|
|
{
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
int heap_id = 0;
|
|
for (auto& heap: m_heaps)
|
|
{
|
|
int index = 0;
|
|
for (auto& h: heap.objects)
|
|
{
|
|
if (!h.free)
|
|
{
|
|
Update(UINT64_MAX, ctx, heap_id, index);
|
|
}
|
|
index++;
|
|
}
|
|
heap_id++;
|
|
}
|
|
}
|
|
|
|
void GpuMemory::DbgInit()
|
|
{
|
|
EXIT_IF(!m_db.IsInvalid());
|
|
[[maybe_unused]] bool create = m_db.CreateInMemory();
|
|
EXIT_IF(!create);
|
|
if (!m_db.IsInvalid())
|
|
{
|
|
m_db.Exec(R"(
|
|
CREATE TABLE [objects](
|
|
[dump_id] INT,
|
|
[heap_id] INTEGER,
|
|
[id] INTEGER,
|
|
[vaddr] TEXT,
|
|
[size] TEXT,
|
|
[vaddr2] TEXT,
|
|
[size2] TEXT,
|
|
[vaddr3] TEXT,
|
|
[size3] TEXT,
|
|
[obj] TEXT,
|
|
[type] TEXT,
|
|
[param0] INT64,
|
|
[param1] INT64,
|
|
[param2] INT64,
|
|
[param3] INT64,
|
|
[param4] INT64,
|
|
[param5] INT64,
|
|
[param6] INT64,
|
|
[param7] INT64,
|
|
[scenario] TEXT,
|
|
[others] TEXT,
|
|
[hash] TEXT,
|
|
[hash2] TEXT,
|
|
[hash3] TEXT,
|
|
[gpu_update_time] INT64,
|
|
[cpu_update_time] INT64,
|
|
[submit_id] INT64,
|
|
[write_back_func] TEXT,
|
|
[delete_func] TEXT,
|
|
[update_func] TEXT,
|
|
[use_last_frame] INT64,
|
|
[use_num] INT64,
|
|
[in_use] BOOL,
|
|
[read_only] BOOL,
|
|
[check_hash] BOOL,
|
|
[vk_mem_size] TEXT,
|
|
[vk_mem_alignment] TEXT,
|
|
[vk_mem_memoryTypeBits] INT,
|
|
[vk_mem_property] INT,
|
|
[vk_mem_memory] TEXT,
|
|
[vk_mem_offset] INT64,
|
|
[vk_mem_type] INT,
|
|
[vk_mem_unique_id] INT64,
|
|
PRIMARY KEY([heap_id], [id]),
|
|
UNIQUE([heap_id], [id])) WITHOUT ROWID;
|
|
|
|
CREATE TABLE [ranges](
|
|
[dump_id] INT,
|
|
[vaddr] TEXT,
|
|
[size] TEXT);
|
|
)");
|
|
|
|
m_db_add_range = m_db.Prepare("insert into ranges(dump_id, vaddr, size) values(:dump_id, :vaddr, :size)");
|
|
m_db_add_object = m_db.Prepare(
|
|
"insert into objects(dump_id, heap_id, id, vaddr, vaddr2, vaddr3, size, size2, size3, obj, param0, param1, param2, param3, "
|
|
"param4, "
|
|
"param5, param6, param7, type, hash, hash2, "
|
|
"hash3, gpu_update_time, cpu_update_time, submit_id, scenario, others, write_back_func, delete_func, update_func, "
|
|
"use_last_frame, "
|
|
"use_num, in_use, read_only, "
|
|
"check_hash, vk_mem_size, "
|
|
"vk_mem_alignment, vk_mem_memoryTypeBits, vk_mem_property, vk_mem_memory, vk_mem_offset, vk_mem_type, vk_mem_unique_id) "
|
|
"values(:dump_id, :heap_id, :id, :vaddr, :vaddr2, :vaddr3, :size, :size2, :size3, :obj, :param0, :param1, :param2, :param3, "
|
|
":param4, "
|
|
":param5, "
|
|
":param6, :param7, :type, :hash, "
|
|
":hash2, :hash3, :gpu_update_time, :cpu_update_time, :submit_id, :scenario, :others, :write_back_func, :delete_func, "
|
|
":update_func, "
|
|
":use_last_frame, :use_num, :in_use, "
|
|
":read_only, :check_hash, "
|
|
":vk_mem_size, :vk_mem_alignment, :vk_mem_memoryTypeBits, :vk_mem_property, :vk_mem_memory, :vk_mem_offset, :vk_mem_type, "
|
|
":vk_mem_unique_id)");
|
|
}
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
|
void GpuMemory::DbgDbDump()
|
|
{
|
|
KYTY_PROFILER_FUNCTION();
|
|
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
static int dump_id = 0;
|
|
|
|
auto hex = [](auto u)
|
|
{
|
|
auto u64 = reinterpret_cast<uint64_t>(u);
|
|
return (u64 == 0 ? U"0" : String::FromPrintf("0x%010" PRIx64, u64));
|
|
};
|
|
|
|
auto id = [](int id1, int id2) { return id1 * 1000000 + id2; };
|
|
|
|
if (!m_db.IsInvalid())
|
|
{
|
|
m_db.Exec("BEGIN TRANSACTION");
|
|
|
|
m_db.Exec("delete from ranges");
|
|
|
|
int heap_id = 0;
|
|
for (const auto& heap: m_heaps)
|
|
{
|
|
m_db_add_range->Reset();
|
|
m_db_add_range->BindInt(":dump_id", dump_id);
|
|
m_db_add_range->BindString(":vaddr", hex(heap.range.vaddr));
|
|
m_db_add_range->BindString(":size", hex(heap.range.size));
|
|
m_db_add_range->Step();
|
|
|
|
int index = 0;
|
|
for (const auto& r: heap.objects)
|
|
{
|
|
if (!r.free)
|
|
{
|
|
m_db_add_object->Reset();
|
|
m_db_add_object->BindInt(":dump_id", dump_id);
|
|
m_db_add_object->BindInt(":heap_id", heap_id);
|
|
m_db_add_object->BindInt(":id", id(dump_id, index));
|
|
(r.block.vaddr_num > 0 ? m_db_add_object->BindString(":vaddr", hex(r.block.vaddr[0]))
|
|
: m_db_add_object->BindNull(":vaddr"));
|
|
(r.block.vaddr_num > 1 ? m_db_add_object->BindString(":vaddr2", hex(r.block.vaddr[1]))
|
|
: m_db_add_object->BindNull(":vaddr2"));
|
|
(r.block.vaddr_num > 2 ? m_db_add_object->BindString(":vaddr3", hex(r.block.vaddr[2]))
|
|
: m_db_add_object->BindNull(":vaddr3"));
|
|
(r.block.vaddr_num > 0 ? m_db_add_object->BindString(":size", hex(r.block.size[0]))
|
|
: m_db_add_object->BindNull(":size"));
|
|
(r.block.vaddr_num > 1 ? m_db_add_object->BindString(":size2", hex(r.block.size[1]))
|
|
: m_db_add_object->BindNull(":size2"));
|
|
(r.block.vaddr_num > 2 ? m_db_add_object->BindString(":size3", hex(r.block.size[2]))
|
|
: m_db_add_object->BindNull(":size3"));
|
|
m_db_add_object->BindString(":obj", hex(r.info.object.obj));
|
|
int param0_index = m_db_add_object->GetIndex(":param0");
|
|
for (int i = 0; i < GpuObject::PARAMS_MAX; i++)
|
|
{
|
|
m_db_add_object->BindInt64(param0_index + i, static_cast<int64_t>(r.info.params[i]));
|
|
}
|
|
m_db_add_object->BindString(":type", Core::EnumName(r.info.object.type).C_Str());
|
|
int hash0_index = m_db_add_object->GetIndex(":hash");
|
|
for (int i = 0; i < VADDR_BLOCKS_MAX; i++)
|
|
{
|
|
m_db_add_object->BindString(hash0_index + i, hex(r.info.hash[i]));
|
|
}
|
|
m_db_add_object->BindString(":write_back_func", hex(r.info.write_back_func));
|
|
m_db_add_object->BindString(":delete_func", hex(r.info.delete_func));
|
|
m_db_add_object->BindString(":update_func", hex(r.info.update_func));
|
|
m_db_add_object->BindInt64(":use_last_frame", static_cast<int64_t>(r.info.use_last_frame));
|
|
m_db_add_object->BindInt64(":use_num", static_cast<int64_t>(r.info.use_num));
|
|
m_db_add_object->BindInt(":in_use", static_cast<int>(r.info.in_use));
|
|
m_db_add_object->BindInt(":read_only", static_cast<int>(r.info.read_only));
|
|
m_db_add_object->BindInt(":check_hash", static_cast<int>(r.info.check_hash));
|
|
m_db_add_object->BindString(":vk_mem_size", hex(r.info.mem.requirements.size));
|
|
m_db_add_object->BindString(":vk_mem_alignment", hex(r.info.mem.requirements.alignment));
|
|
m_db_add_object->BindInt(":vk_mem_memoryTypeBits", static_cast<int>(r.info.mem.requirements.memoryTypeBits));
|
|
m_db_add_object->BindInt(":vk_mem_property", static_cast<int>(r.info.mem.property));
|
|
m_db_add_object->BindString(":vk_mem_memory", hex(r.info.mem.memory));
|
|
m_db_add_object->BindInt64(":vk_mem_offset", static_cast<int64_t>(r.info.mem.offset));
|
|
m_db_add_object->BindInt(":vk_mem_type", static_cast<int>(r.info.mem.type));
|
|
m_db_add_object->BindInt64(":vk_mem_unique_id", static_cast<int64_t>(r.info.mem.unique_id));
|
|
m_db_add_object->BindString(":scenario", Core::EnumName(r.scenario).C_Str());
|
|
m_db_add_object->BindInt64(":gpu_update_time", static_cast<int64_t>(r.info.gpu_update_time));
|
|
m_db_add_object->BindInt64(":cpu_update_time", static_cast<int64_t>(r.info.cpu_update_time));
|
|
m_db_add_object->BindInt64(":submit_id", static_cast<int64_t>(r.info.submit_id));
|
|
|
|
if (r.others.Size() > 0)
|
|
{
|
|
Core::StringList others;
|
|
for (const auto& s: r.others)
|
|
{
|
|
others.Add(String::FromPrintf("[%s,%d]", Core::EnumName(s.relation).C_Str(), id(dump_id, s.object_id)));
|
|
}
|
|
m_db_add_object->BindString(":others", others.Concat(U','));
|
|
} else
|
|
{
|
|
m_db_add_object->BindNull(":others");
|
|
}
|
|
|
|
m_db_add_object->Step();
|
|
}
|
|
index++;
|
|
}
|
|
heap_id++;
|
|
}
|
|
|
|
m_db.Exec("END TRANSACTION");
|
|
}
|
|
|
|
dump_id++;
|
|
}
|
|
|
|
void GpuMemory::DbgDbSave(const String& file_name)
|
|
{
|
|
KYTY_PROFILER_FUNCTION();
|
|
|
|
Core::LockGuard lock(m_mutex);
|
|
|
|
if (!m_db.IsInvalid())
|
|
{
|
|
Core::Database::Connection db;
|
|
if (!db.Create(file_name) && !db.Open(file_name, Core::Database::Connection::Mode::ReadWrite))
|
|
{
|
|
printf("Can't open file: %s\n", file_name.C_Str());
|
|
return;
|
|
}
|
|
m_db.CopyTo(&db);
|
|
db.Close();
|
|
}
|
|
}
|
|
|
|
struct VulkanMemoryStat
|
|
{
|
|
std::atomic_uint64_t allocated[VK_MAX_MEMORY_TYPES];
|
|
std::atomic_uint64_t count[VK_MAX_MEMORY_TYPES];
|
|
};
|
|
|
|
static VulkanMemoryStat* g_mem_stat = nullptr;
|
|
|
|
void GpuMemoryInit()
|
|
{
|
|
EXIT_IF(g_gpu_memory != nullptr);
|
|
EXIT_IF(g_gpu_resources != nullptr);
|
|
|
|
g_gpu_memory = new GpuMemory;
|
|
g_gpu_resources = new GpuResources;
|
|
|
|
g_mem_stat = new VulkanMemoryStat;
|
|
|
|
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++)
|
|
{
|
|
g_mem_stat->allocated[i] = 0;
|
|
g_mem_stat->count[i] = 0;
|
|
}
|
|
}
|
|
|
|
void GpuMemorySetAllocatedRange(uint64_t vaddr, uint64_t size)
|
|
{
|
|
EXIT_IF(g_gpu_memory == nullptr);
|
|
|
|
g_gpu_memory->SetAllocatedRange(vaddr, size);
|
|
}
|
|
|
|
void GpuMemoryFree(GraphicContext* ctx, uint64_t vaddr, uint64_t size, bool unmap)
|
|
{
|
|
EXIT_IF(g_gpu_memory == nullptr);
|
|
EXIT_IF(ctx == nullptr);
|
|
|
|
g_gpu_memory->Free(ctx, vaddr, size, unmap);
|
|
}
|
|
|
|
void* GpuMemoryCreateObject(uint64_t submit_id, GraphicContext* ctx, CommandBuffer* buffer, uint64_t vaddr, uint64_t size,
|
|
const GpuObject& info)
|
|
{
|
|
EXIT_IF(g_gpu_memory == nullptr);
|
|
EXIT_IF(ctx == nullptr);
|
|
|
|
return g_gpu_memory->CreateObject(submit_id, ctx, buffer, &vaddr, &size, 1, info);
|
|
}
|
|
|
|
void* GpuMemoryCreateObject(uint64_t submit_id, GraphicContext* ctx, CommandBuffer* buffer, const uint64_t* vaddr, const uint64_t* size,
|
|
int vaddr_num, const GpuObject& info)
|
|
{
|
|
EXIT_IF(g_gpu_memory == nullptr);
|
|
EXIT_IF(ctx == nullptr);
|
|
|
|
return g_gpu_memory->CreateObject(submit_id, ctx, buffer, vaddr, size, vaddr_num, info);
|
|
}
|
|
|
|
Vector<GpuMemoryObject> GpuMemoryFindObjects(uint64_t vaddr, uint64_t size, GpuMemoryObjectType type, bool exact, bool only_first)
|
|
{
|
|
EXIT_IF(g_gpu_memory == nullptr);
|
|
|
|
return g_gpu_memory->FindObjects(&vaddr, &size, 1, type, exact, only_first);
|
|
}
|
|
|
|
void GpuMemoryResetHash(const uint64_t* vaddr, const uint64_t* size, int vaddr_num, GpuMemoryObjectType type)
|
|
{
|
|
EXIT_IF(g_gpu_memory == nullptr);
|
|
|
|
g_gpu_memory->ResetHash(vaddr, size, vaddr_num, type);
|
|
}
|
|
|
|
void GpuMemoryDbgDump()
|
|
{
|
|
EXIT_IF(g_gpu_memory == nullptr);
|
|
|
|
// g_gpu_memory->DbgDbDump();
|
|
// g_gpu_memory->DbgDbSave(U"_gpu_memory.db");
|
|
|
|
// static int test_ms = 0; // Core::mem_new_state();
|
|
|
|
// Core::Thread::Sleep(2000);
|
|
// Core::MemStats test_mem_stat = {test_ms, 0, 0};
|
|
// Core::mem_get_stat(&test_mem_stat);
|
|
// size_t ut_total_allocated = test_mem_stat.total_allocated;
|
|
// uint32_t ut_blocks_num = test_mem_stat.blocks_num;
|
|
// std::printf("mem stat: state = %d, blocks_num = %u, total_allocated = %" PRIu64 "\n", test_ms, ut_blocks_num, ut_total_allocated);
|
|
// Core::mem_print(6);
|
|
// test_ms = Core::mem_new_state();
|
|
}
|
|
|
|
void GpuMemoryFlush(GraphicContext* ctx, uint64_t vaddr, uint64_t size)
|
|
{
|
|
EXIT_IF(g_gpu_memory == nullptr);
|
|
EXIT_IF(ctx == nullptr);
|
|
|
|
// update vulkan objects after CPU-drawing
|
|
g_gpu_memory->Flush(ctx, vaddr, size);
|
|
}
|
|
|
|
void GpuMemoryFlushAll(GraphicContext* ctx)
|
|
{
|
|
EXIT_IF(g_gpu_memory == nullptr);
|
|
EXIT_IF(ctx == nullptr);
|
|
|
|
// update vulkan objects after CPU-drawing
|
|
g_gpu_memory->FlushAll(ctx);
|
|
}
|
|
|
|
void GpuMemoryFrameDone()
|
|
{
|
|
EXIT_IF(g_gpu_memory == nullptr);
|
|
|
|
g_gpu_memory->FrameDone();
|
|
}
|
|
|
|
void GpuMemoryWriteBack(GraphicContext* ctx, CommandProcessor* cp)
|
|
{
|
|
EXIT_IF(g_gpu_memory == nullptr);
|
|
EXIT_IF(ctx == nullptr);
|
|
|
|
// update CPU memory after GPU-drawing
|
|
g_gpu_memory->WriteBack(ctx, cp);
|
|
}
|
|
|
|
bool GpuMemoryCheckAccessViolation(uint64_t /*vaddr*/, uint64_t /*size*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool GpuMemoryWatcherEnabled()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool VulkanAllocate(GraphicContext* ctx, VulkanMemory* mem)
|
|
{
|
|
KYTY_PROFILER_FUNCTION();
|
|
|
|
static std::atomic_uint64_t seq = 0;
|
|
|
|
EXIT_IF(ctx == nullptr);
|
|
EXIT_IF(mem == nullptr);
|
|
EXIT_IF(mem->memory != nullptr);
|
|
EXIT_IF(mem->requirements.size == 0);
|
|
|
|
VkPhysicalDeviceMemoryProperties memory_properties {};
|
|
vkGetPhysicalDeviceMemoryProperties(ctx->physical_device, &memory_properties);
|
|
|
|
uint32_t index = 0;
|
|
for (; index < memory_properties.memoryTypeCount; index++)
|
|
{
|
|
if ((mem->requirements.memoryTypeBits & (static_cast<uint32_t>(1) << index)) != 0 &&
|
|
(memory_properties.memoryTypes[index].propertyFlags & mem->property) == mem->property)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
mem->type = index;
|
|
mem->offset = 0;
|
|
|
|
VkMemoryAllocateInfo alloc_info {};
|
|
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
alloc_info.pNext = nullptr;
|
|
alloc_info.allocationSize = mem->requirements.size;
|
|
alloc_info.memoryTypeIndex = index;
|
|
|
|
mem->unique_id = ++seq;
|
|
|
|
auto result = vkAllocateMemory(ctx->device, &alloc_info, nullptr, &mem->memory);
|
|
|
|
if (result == VK_SUCCESS)
|
|
{
|
|
g_mem_stat->allocated[index] += mem->requirements.size;
|
|
g_mem_stat->count[index]++;
|
|
return true;
|
|
}
|
|
|
|
Core::StringList stat;
|
|
for (uint32_t i = 0; i < memory_properties.memoryTypeCount; i++)
|
|
{
|
|
uint64_t allocated = g_mem_stat->allocated[i];
|
|
uint64_t count = g_mem_stat->count[i];
|
|
stat.Add(String::FromPrintf("%u, %" PRIu64 ", %" PRIu64 "", i, count, allocated));
|
|
}
|
|
g_gpu_memory->DbgDbDump();
|
|
g_gpu_memory->DbgDbSave(U"_gpu_memory.db");
|
|
EXIT("size = %" PRIu64 ", index = %u, error: %s:%s\n", mem->requirements.size, index, string_VkResult(result),
|
|
stat.Concat(U'\n').C_Str());
|
|
|
|
return false;
|
|
}
|
|
|
|
void VulkanFree(GraphicContext* ctx, VulkanMemory* mem)
|
|
{
|
|
KYTY_PROFILER_FUNCTION();
|
|
|
|
EXIT_IF(ctx == nullptr);
|
|
EXIT_IF(mem == nullptr);
|
|
|
|
vkFreeMemory(ctx->device, mem->memory, nullptr);
|
|
|
|
g_mem_stat->allocated[mem->type] -= mem->requirements.size;
|
|
g_mem_stat->count[mem->type]--;
|
|
|
|
mem->memory = nullptr;
|
|
}
|
|
|
|
void VulkanMapMemory(GraphicContext* ctx, VulkanMemory* mem, void** data)
|
|
{
|
|
KYTY_PROFILER_FUNCTION();
|
|
|
|
EXIT_IF(ctx == nullptr);
|
|
EXIT_IF(mem == nullptr);
|
|
EXIT_IF(data == nullptr);
|
|
|
|
vkMapMemory(ctx->device, mem->memory, mem->offset, mem->requirements.size, 0, data);
|
|
}
|
|
|
|
void VulkanUnmapMemory(GraphicContext* ctx, VulkanMemory* mem)
|
|
{
|
|
KYTY_PROFILER_FUNCTION();
|
|
|
|
EXIT_IF(ctx == nullptr);
|
|
EXIT_IF(mem == nullptr);
|
|
|
|
vkUnmapMemory(ctx->device, mem->memory);
|
|
}
|
|
|
|
void VulkanBindImageMemory(GraphicContext* ctx, VulkanImage* image, VulkanMemory* mem)
|
|
{
|
|
KYTY_PROFILER_FUNCTION();
|
|
|
|
EXIT_IF(ctx == nullptr);
|
|
EXIT_IF(mem == nullptr);
|
|
EXIT_IF(image == nullptr);
|
|
|
|
vkBindImageMemory(ctx->device, image->image, mem->memory, mem->offset);
|
|
}
|
|
|
|
void VulkanBindBufferMemory(GraphicContext* ctx, VulkanBuffer* buffer, VulkanMemory* mem)
|
|
{
|
|
KYTY_PROFILER_FUNCTION();
|
|
|
|
EXIT_IF(ctx == nullptr);
|
|
EXIT_IF(mem == nullptr);
|
|
EXIT_IF(buffer == nullptr);
|
|
|
|
vkBindBufferMemory(ctx->device, buffer->buffer, mem->memory, mem->offset);
|
|
}
|
|
|
|
void GpuMemoryRegisterOwner(uint32_t* owner_handle, const char* name)
|
|
{
|
|
EXIT_IF(g_gpu_resources == nullptr);
|
|
EXIT_IF(owner_handle == nullptr);
|
|
EXIT_IF(name == nullptr);
|
|
|
|
*owner_handle = g_gpu_resources->AddOwner(String::FromUtf8(name));
|
|
}
|
|
|
|
void GpuMemoryRegisterResource(uint32_t* resource_handle, uint32_t owner_handle, const void* memory, size_t size, const char* name,
|
|
uint32_t type, uint64_t user_data)
|
|
{
|
|
EXIT_IF(g_gpu_resources == nullptr);
|
|
EXIT_IF(resource_handle == nullptr);
|
|
EXIT_IF(name == nullptr);
|
|
|
|
*resource_handle =
|
|
g_gpu_resources->AddResource(owner_handle, reinterpret_cast<uint64_t>(memory), size, String::FromUtf8(name), type, user_data);
|
|
}
|
|
|
|
void GpuMemoryUnregisterAllResourcesForOwner(uint32_t owner_handle)
|
|
{
|
|
EXIT_IF(g_gpu_resources == nullptr);
|
|
|
|
g_gpu_resources->DeleteResources(owner_handle);
|
|
}
|
|
|
|
void GpuMemoryUnregisterOwnerAndResources(uint32_t owner_handle)
|
|
{
|
|
EXIT_IF(g_gpu_resources == nullptr);
|
|
|
|
g_gpu_resources->DeleteOwner(owner_handle);
|
|
}
|
|
|
|
void GpuMemoryUnregisterResource(uint32_t resource_handle)
|
|
{
|
|
EXIT_IF(g_gpu_resources == nullptr);
|
|
|
|
g_gpu_resources->DeleteResource(resource_handle);
|
|
}
|
|
|
|
} // namespace Kyty::Libs::Graphics
|
|
|
|
#endif // KYTY_EMU_ENABLED
|