mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 11:26:18 -04:00
427 lines
14 KiB
C++
427 lines
14 KiB
C++
#include "TexturePool.h"
|
|
|
|
#include <algorithm>
|
|
#include <regex>
|
|
|
|
#include "common/log/log.h"
|
|
#include "common/util/Assert.h"
|
|
#include "common/util/Timer.h"
|
|
|
|
#include "game/graphics/pipelines/opengl.h"
|
|
#include "game/graphics/texture/jak1_tpage_dir.h"
|
|
#include "game/graphics/texture/jak2_tpage_dir.h"
|
|
#include "game/graphics/texture/jak3_tpage_dir.h"
|
|
|
|
#include "fmt/core.h"
|
|
#include "third-party/imgui/imgui.h"
|
|
|
|
namespace {
|
|
const char empty_string[] = "";
|
|
const char* goal_string(u32 ptr, const u8* memory_base) {
|
|
if (ptr == 0) {
|
|
return empty_string;
|
|
}
|
|
return (const char*)(memory_base + ptr + 4);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::string GoalTexturePage::print() const {
|
|
return fmt::format("Tpage id {} textures {} seg0 {} {} seg1 {} {} seg2 {} {}\n", id, length,
|
|
segment[0].size, segment[0].dest, segment[1].size, segment[1].dest,
|
|
segment[2].size, segment[2].dest);
|
|
}
|
|
|
|
u64 upload_to_gpu(const u8* data, u16 w, u16 h) {
|
|
GLuint tex_id;
|
|
glGenTextures(1, &tex_id);
|
|
GLint old_tex;
|
|
glGetIntegerv(GL_ACTIVE_TEXTURE, &old_tex);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, tex_id);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
|
|
glGenerateMipmap(GL_TEXTURE_2D);
|
|
float aniso = 0.0f;
|
|
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso);
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, aniso);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glActiveTexture(old_tex);
|
|
return tex_id;
|
|
}
|
|
|
|
GpuTexture* TexturePool::give_texture(const TextureInput& in) {
|
|
// const auto& it = m_loaded_textures.find(in.name);
|
|
const auto existing = m_loaded_textures.lookup_or_insert(in.id);
|
|
if (!existing.second) {
|
|
// nothing references this texture yet.
|
|
existing.first->tex_id = in.id;
|
|
existing.first->w = in.w;
|
|
existing.first->h = in.h;
|
|
existing.first->is_common = in.common;
|
|
existing.first->gpu_textures = {{in.gpu_texture, in.src_data}};
|
|
existing.first->is_placeholder = false;
|
|
*m_id_to_name.lookup_or_insert(in.id).first =
|
|
fmt::format("{}/{}", in.debug_page_name, in.debug_name);
|
|
return existing.first;
|
|
} else {
|
|
if (!existing.first->is_placeholder) {
|
|
// two sources for texture. this is fine.
|
|
ASSERT(!existing.first->gpu_textures.empty());
|
|
} else {
|
|
ASSERT(existing.first->gpu_textures.empty());
|
|
}
|
|
existing.first->is_placeholder = false;
|
|
existing.first->w = in.w;
|
|
existing.first->h = in.h;
|
|
existing.first->gpu_textures.push_back({in.gpu_texture, in.src_data});
|
|
existing.first->is_common = in.common;
|
|
refresh_links(*existing.first);
|
|
return existing.first;
|
|
}
|
|
}
|
|
|
|
GpuTexture* TexturePool::give_texture_and_load_to_vram(const TextureInput& in, u32 vram_slot) {
|
|
auto tex = give_texture(in);
|
|
move_existing_to_vram(tex, vram_slot);
|
|
return tex;
|
|
}
|
|
|
|
void TexturePool::move_existing_to_vram(GpuTexture* tex, u32 slot_addr) {
|
|
ASSERT(!tex->is_placeholder);
|
|
ASSERT(!tex->gpu_textures.empty());
|
|
auto& slot = m_textures[slot_addr];
|
|
if (std::find(tex->slots.begin(), tex->slots.end(), slot_addr) == tex->slots.end()) {
|
|
tex->slots.push_back(slot_addr);
|
|
}
|
|
if (slot.source) {
|
|
if (slot.source == tex) {
|
|
// we already have it, no need to do anything
|
|
} else {
|
|
slot.source->remove_slot(slot_addr);
|
|
slot.source = tex;
|
|
slot.gpu_texture = tex->gpu_textures.front().gl;
|
|
}
|
|
} else {
|
|
slot.source = tex;
|
|
slot.gpu_texture = tex->gpu_textures.front().gl;
|
|
}
|
|
}
|
|
|
|
void TexturePool::update_gl_texture(GpuTexture* gpu_texture,
|
|
u32 new_w,
|
|
u32 new_h,
|
|
GLuint new_gl_texture) {
|
|
ASSERT(gpu_texture->gpu_textures.size() == 1);
|
|
gpu_texture->gpu_textures[0].gl = new_gl_texture;
|
|
gpu_texture->w = new_w;
|
|
gpu_texture->h = new_h;
|
|
for (int si : gpu_texture->slots) {
|
|
auto& slot = m_textures[si];
|
|
ASSERT(slot.source == gpu_texture);
|
|
slot.gpu_texture = new_gl_texture;
|
|
}
|
|
}
|
|
|
|
void TexturePool::refresh_links(GpuTexture& texture) {
|
|
u64 tex_to_use =
|
|
texture.is_placeholder ? m_placeholder_texture_id : texture.gpu_textures.front().gl;
|
|
|
|
for (auto slot : texture.slots) {
|
|
auto& t = m_textures[slot];
|
|
ASSERT(t.source == &texture);
|
|
t.gpu_texture = tex_to_use;
|
|
}
|
|
|
|
for (auto slot : texture.mt4hh_slots) {
|
|
for (auto& tex : m_mt4hh_textures) {
|
|
if (tex.slot == slot) {
|
|
tex.ref.gpu_texture = tex_to_use;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TexturePool::unload_texture(PcTextureId tex_id, u64 gpu_id) {
|
|
auto* tex = m_loaded_textures.lookup_existing(tex_id);
|
|
ASSERT(tex);
|
|
if (tex->is_common) {
|
|
ASSERT(false);
|
|
return;
|
|
}
|
|
ASSERT_MSG(!tex->is_placeholder,
|
|
fmt::format("trying to unload something that was already placholdered: {} {}\n",
|
|
get_debug_texture_name(tex_id), tex->gpu_textures.size()));
|
|
auto it = std::find_if(tex->gpu_textures.begin(), tex->gpu_textures.end(),
|
|
[&](const auto& a) { return a.gl == gpu_id; });
|
|
ASSERT(it != tex->gpu_textures.end());
|
|
|
|
tex->gpu_textures.erase(it);
|
|
if (tex->gpu_textures.empty()) {
|
|
tex->is_placeholder = true;
|
|
}
|
|
refresh_links(*tex);
|
|
}
|
|
|
|
void GpuTexture::remove_slot(u32 slot) {
|
|
auto it = std::find(slots.begin(), slots.end(), slot);
|
|
ASSERT(it != slots.end());
|
|
slots.erase(it);
|
|
}
|
|
|
|
void GpuTexture::add_slot(u32 slot) {
|
|
ASSERT(std::find(slots.begin(), slots.end(), slot) == slots.end());
|
|
slots.push_back(slot);
|
|
}
|
|
|
|
/*!
|
|
* Handle a GOAL texture-page object being uploaded to VRAM.
|
|
* The strategy:
|
|
* - upload the texture-age to a fake 4MB VRAM, like the GOAL code would have done.
|
|
* - "download" each texture in a reasonable format for the PC Port (currently RGBA8888)
|
|
* - add this to the PC pool.
|
|
*
|
|
* The textures are scrambled around in a confusing way.
|
|
*
|
|
* NOTE: the actual conversion is currently done here, but this might be too slow.
|
|
* We could store textures in the right format to begin with, or spread the conversion out over
|
|
* multiple frames.
|
|
*/
|
|
void TexturePool::handle_upload_now(const u8* tpage,
|
|
int mode,
|
|
const u8* memory_base,
|
|
u32 s7_ptr,
|
|
bool debug) {
|
|
std::unique_lock<std::mutex> lk(m_mutex);
|
|
// extract the texture-page object. This is just a description of the page data.
|
|
GoalTexturePage texture_page;
|
|
memcpy(&texture_page, tpage, sizeof(GoalTexturePage));
|
|
|
|
bool has_segment[3] = {true, true, true};
|
|
|
|
if (mode == -1) {
|
|
} else if (mode == 2) {
|
|
has_segment[0] = false;
|
|
has_segment[1] = false;
|
|
} else if (mode == -2) {
|
|
has_segment[2] = false;
|
|
} else if (mode == 0) {
|
|
has_segment[1] = false;
|
|
has_segment[2] = false;
|
|
} else {
|
|
// no reason to skip this, other than
|
|
lg::error("TexturePool skipping upload now with mode {}.", mode);
|
|
return;
|
|
}
|
|
|
|
// loop over all texture in the tpage and download them.
|
|
for (int tex_idx = 0; tex_idx < texture_page.length; tex_idx++) {
|
|
GoalTexture tex;
|
|
if (texture_page.try_copy_texture_description(&tex, tex_idx, memory_base, tpage, s7_ptr)) {
|
|
if (debug) {
|
|
fmt::print("Pool upload {} to {}\n",
|
|
std::string(goal_string(texture_page.name_ptr, memory_base)) +
|
|
goal_string(tex.name_ptr, memory_base),
|
|
tex.dest[0]);
|
|
}
|
|
// each texture may have multiple mip levels.
|
|
for (int mip_idx = 0; mip_idx < tex.num_mips; mip_idx++) {
|
|
if (has_segment[tex.segment_of_mip(mip_idx)]) {
|
|
PcTextureId current_id(texture_page.id, tex_idx);
|
|
if (!m_id_to_name.lookup_existing(current_id)) {
|
|
auto name = std::string(goal_string(texture_page.name_ptr, memory_base)) +
|
|
goal_string(tex.name_ptr, memory_base);
|
|
*m_id_to_name.lookup_or_insert(current_id).first = name;
|
|
m_name_to_id[name] = current_id;
|
|
}
|
|
|
|
auto& slot = m_textures[tex.dest[mip_idx]];
|
|
|
|
if (slot.source) {
|
|
if (slot.source->tex_id == current_id) {
|
|
// we already have it, no need to do anything
|
|
} else {
|
|
slot.source->remove_slot(tex.dest[mip_idx]);
|
|
slot.source = get_gpu_texture_for_slot(current_id, tex.dest[mip_idx]);
|
|
ASSERT(slot.gpu_texture != (GLuint)-1);
|
|
}
|
|
} else {
|
|
slot.source = get_gpu_texture_for_slot(current_id, tex.dest[mip_idx]);
|
|
ASSERT(slot.gpu_texture != (GLuint)-1);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// texture was #f, skip it.
|
|
}
|
|
}
|
|
}
|
|
|
|
void TexturePool::relocate(u32 destination, u32 source, u32 format) {
|
|
std::unique_lock<std::mutex> lk(m_mutex);
|
|
GpuTexture* src = lookup_gpu_texture(source);
|
|
ASSERT(src);
|
|
if (format == 44) {
|
|
m_mt4hh_textures.emplace_back();
|
|
m_mt4hh_textures.back().slot = destination;
|
|
m_mt4hh_textures.back().ref.source = src;
|
|
m_mt4hh_textures.back().ref.gpu_texture = src->gpu_textures.at(0).gl;
|
|
src->mt4hh_slots.push_back(destination);
|
|
} else {
|
|
move_existing_to_vram(src, destination);
|
|
}
|
|
}
|
|
|
|
GpuTexture* TexturePool::get_gpu_texture_for_slot(PcTextureId id, u32 slot) {
|
|
auto it = m_loaded_textures.lookup_or_insert(id);
|
|
if (!it.second) {
|
|
GpuTexture& placeholder = *it.first;
|
|
placeholder.tex_id = id;
|
|
placeholder.is_placeholder = true;
|
|
placeholder.slots.push_back(slot);
|
|
|
|
// auto r = m_loaded_textures.insert({name, placeholder});
|
|
m_textures[slot].gpu_texture = m_placeholder_texture_id;
|
|
return it.first;
|
|
} else {
|
|
auto result = it.first;
|
|
result->add_slot(slot);
|
|
m_textures[slot].gpu_texture =
|
|
result->is_placeholder ? m_placeholder_texture_id : result->gpu_textures.at(0).gl;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
std::optional<u64> TexturePool::lookup_mt4hh(u32 location) {
|
|
for (auto& t : m_mt4hh_textures) {
|
|
if (t.slot == location) {
|
|
if (t.ref.source) {
|
|
return t.ref.gpu_texture;
|
|
}
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
namespace {
|
|
const std::vector<u32>& get_tpage_dir(GameVersion version) {
|
|
switch (version) {
|
|
case GameVersion::Jak1:
|
|
return get_jak1_tpage_dir();
|
|
case GameVersion::Jak2:
|
|
return get_jak2_tpage_dir();
|
|
case GameVersion::Jak3:
|
|
return get_jak3_tpage_dir();
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
TexturePool::TexturePool(GameVersion version)
|
|
: m_loaded_textures(get_tpage_dir(version)),
|
|
m_id_to_name(get_tpage_dir(version)),
|
|
m_tpage_dir_size(get_tpage_dir(version).size()) {
|
|
m_placeholder_data.resize(16 * 16);
|
|
u32 c0 = 0xa0303030;
|
|
u32 c1 = 0xa0e0e0e0;
|
|
for (int i = 0; i < 16; i++) {
|
|
for (int j = 0; j < 16; j++) {
|
|
m_placeholder_data[i * 16 + j] = (((i / 4) & 1) ^ ((j / 4) & 1)) ? c1 : c0;
|
|
}
|
|
}
|
|
m_placeholder_texture_id = upload_to_gpu((const u8*)(m_placeholder_data.data()), 16, 16);
|
|
}
|
|
|
|
void TexturePool::draw_debug_window() {
|
|
int id = 0;
|
|
int total_vram_bytes = 0;
|
|
int total_textures = 0;
|
|
int total_displayed_textures = 0;
|
|
int total_uploaded_textures = 0;
|
|
ImGui::InputText("texture search", m_regex_input, sizeof(m_regex_input));
|
|
bool use_regex = m_regex_input[0];
|
|
std::regex regex(use_regex ? m_regex_input : ".*");
|
|
|
|
for (size_t i = 0; i < m_textures.size(); i++) {
|
|
auto& record = m_textures[i];
|
|
total_textures++;
|
|
if (record.source) {
|
|
if (!use_regex || std::regex_search(get_debug_texture_name(record.source->tex_id), regex)) {
|
|
ImGui::PushID(id++);
|
|
draw_debug_for_tex(get_debug_texture_name(record.source->tex_id), record.source, i);
|
|
ImGui::PopID();
|
|
total_displayed_textures++;
|
|
}
|
|
if (!record.source->gpu_textures.empty()) {
|
|
total_vram_bytes +=
|
|
record.source->w * record.source->h * 4; // todo, if we support other formats
|
|
}
|
|
|
|
total_uploaded_textures++;
|
|
}
|
|
}
|
|
|
|
// todo mt4hh
|
|
ImGui::Text("Total Textures: %d Uploaded: %d Shown: %d VRAM: %.3f MB", total_textures,
|
|
total_uploaded_textures, total_displayed_textures,
|
|
(float)total_vram_bytes / (1024 * 1024));
|
|
}
|
|
|
|
void TexturePool::draw_debug_for_tex(const std::string& name, GpuTexture* tex, u32 slot) {
|
|
if (tex->is_placeholder) {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8, 0.3, 0.3, 1.0));
|
|
} else if (tex->gpu_textures.size() == 1) {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3, 0.8, 0.3, 1.0));
|
|
} else {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8, 0.8, 0.3, 1.0));
|
|
}
|
|
if (ImGui::TreeNode(fmt::format("{}) {}", slot, name).c_str())) {
|
|
ImGui::Text("P: %s sz: %d x %d", get_debug_texture_name(tex->tex_id).c_str(), tex->w, tex->h);
|
|
if (!tex->is_placeholder) {
|
|
ImGui::Image((void*)(u64)tex->gpu_textures.at(0).gl, ImVec2(tex->w, tex->h));
|
|
} else {
|
|
ImGui::Text("PLACEHOLDER");
|
|
}
|
|
|
|
ImGui::TreePop();
|
|
ImGui::Separator();
|
|
}
|
|
ImGui::PopStyleColor();
|
|
}
|
|
|
|
PcTextureId TexturePool::allocate_pc_port_texture(GameVersion version) {
|
|
ASSERT(m_next_pc_texture_to_allocate < EXTRA_PC_PORT_TEXTURE_COUNT);
|
|
switch (version) {
|
|
case GameVersion::Jak1:
|
|
return PcTextureId(get_jak1_tpage_dir().size() - 1, m_next_pc_texture_to_allocate++);
|
|
case GameVersion::Jak2:
|
|
return PcTextureId(get_jak2_tpage_dir().size() - 1, m_next_pc_texture_to_allocate++);
|
|
case GameVersion::Jak3:
|
|
return PcTextureId(get_jak3_tpage_dir().size() - 1, m_next_pc_texture_to_allocate++);
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
std::string TexturePool::get_debug_texture_name(PcTextureId id) {
|
|
auto it = m_id_to_name.lookup_existing(id);
|
|
if (it) {
|
|
return *it;
|
|
} else {
|
|
return "??? (missing PC id to name mapping)";
|
|
}
|
|
}
|
|
|
|
std::string TexturePool::get_debug_texture_name_from_tbp(u32 tbp) {
|
|
auto info = lookup_gpu_texture(tbp);
|
|
if (!info) {
|
|
return "??? (bad tbp)";
|
|
} else {
|
|
return get_debug_texture_name(info->tex_id);
|
|
}
|
|
}
|