mirror of
https://github.com/open-goal/jak-project
synced 2026-05-23 23:05:43 -04:00
d3cc739e43
This attempts to get into master whatever work was done in this PR / it's earlier PR https://github.com/open-goal/jak-project/pull/3965 I don't want this work to be lost / floating around in massive PRs. However the changes are: - switch to ntsc_v1 instead of PAL as the development target, as we have done for all other games - remove most of the copied-from-jak2/3 changes as they need to be confirmed during the decompilation process not just assumed - avoids committing any changes to `game/kernel/common` as it was not clear to me if these were changes made in jak x's kernel that were not properly broken out into it's own functions. We don't want to accidentally introduce bugs into jak1-3's kernel code. - in other words, if the change in the kernel only happens in jak x...it should likely be specific to jak x's kernel, not common. --------- Co-authored-by: VodBox <dillon@vodbox.io> Co-authored-by: yodah <greenboyyodah@gmail.com>
429 lines
14 KiB
C++
429 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/format.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:
|
|
case GameVersion::JakX:
|
|
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((ImTextureID)(intptr_t)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:
|
|
case GameVersion::JakX:
|
|
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);
|
|
}
|
|
}
|