Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[graphics] Rewrite of texture system #1212

Merged
merged 7 commits into from
Mar 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions common/custom_data/TFrag3Data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,11 @@ void BVH::serialize(Serializer& ser) {
void Texture::serialize(Serializer& ser) {
ser.from_ptr(&w);
ser.from_ptr(&h);
ser.from_ptr(&combo_id);
ser.from_pod_vector(&data);
ser.from_str(&debug_name);
ser.from_str(&debug_tpage_name);
ser.from_ptr(&load_to_pool);
}

void Level::serialize(Serializer& ser) {
Expand Down
10 changes: 7 additions & 3 deletions common/custom_data/Tfrag3Data.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ enum MemoryUsageCategory {
NUM_CATEGORIES
};

constexpr int TFRAG3_VERSION = 11;
constexpr int TFRAG3_VERSION = 12;

// These vertices should be uploaded to the GPU at load time and don't change
struct PreloadedVertex {
Expand Down Expand Up @@ -210,6 +210,7 @@ struct Texture {
std::vector<u32> data;
std::string debug_name;
std::string debug_tpage_name;
bool load_to_pool = false;
void serialize(Serializer& ser);
};

Expand Down Expand Up @@ -260,12 +261,15 @@ struct TieTree {
void unpack();
};

constexpr int TFRAG_GEOS = 3;
constexpr int TIE_GEOS = 4;

struct Level {
u16 version = TFRAG3_VERSION;
std::string level_name;
std::vector<Texture> textures;
std::array<std::vector<TfragTree>, 3> tfrag_trees;
std::array<std::vector<TieTree>, 4> tie_trees;
std::array<std::vector<TfragTree>, TFRAG_GEOS> tfrag_trees;
std::array<std::vector<TieTree>, TIE_GEOS> tie_trees;
u16 version2 = TFRAG3_VERSION;
void serialize(Serializer& ser);

Expand Down
29 changes: 28 additions & 1 deletion decompiler/data/TextureDB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include "third-party/fmt/core.h"
#include "common/util/Assert.h"
#include "third-party/stb_image.h"
#include <filesystem>

namespace decompiler {

Expand All @@ -11,7 +13,8 @@ void TextureDB::add_texture(u32 tpage,
u16 w,
u16 h,
const std::string& tex_name,
const std::string& tpage_name) {
const std::string& tpage_name,
const std::vector<std::string>& level_names) {
auto existing_tpage_name = tpage_names.find(tpage);
if (existing_tpage_name == tpage_names.end()) {
tpage_names[tpage] = tpage_name;
Expand All @@ -35,6 +38,30 @@ void TextureDB::add_texture(u32 tpage,
new_tex.h = h;
new_tex.page = tpage;
}
for (const auto& level_name : level_names) {
texture_ids_per_level[level_name].insert(combo_id);
}
}

void TextureDB::replace_textures(const std::string& path) {
std::filesystem::path base_path(path);
for (auto& tex : textures) {
std::filesystem::path full_path =
base_path / tpage_names.at(tex.second.page) / (tex.second.name + ".png");
if (std::filesystem::exists(full_path)) {
fmt::print("Replacing {}\n", full_path.string().c_str());
int w, h;
auto data = stbi_load(full_path.string().c_str(), &w, &h, 0, 4); // rgba channels
if (!data) {
fmt::print("failed to load PNG file: {}\n", full_path.string().c_str());
continue;
}
tex.second.rgba_bytes.resize(w * h);
memcpy(tex.second.rgba_bytes.data(), data, w * h * 4);
tex.second.w = w;
tex.second.h = h;
stbi_image_free(data);
}
}
}
} // namespace decompiler
7 changes: 6 additions & 1 deletion decompiler/data/TextureDB.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <vector>
#include <unordered_map>
#include <set>
#include <string>
#include "common/common_types.h"

Expand All @@ -16,13 +17,17 @@ struct TextureDB {

std::unordered_map<u32, TextureData> textures;
std::unordered_map<u32, std::string> tpage_names;
std::unordered_map<std::string, std::set<u32>> texture_ids_per_level;

void add_texture(u32 tpage,
u32 texid,
const std::vector<u32>& data,
u16 w,
u16 h,
const std::string& tex_name,
const std::string& tpage_name);
const std::string& tpage_name,
const std::vector<std::string>& level_names);

void replace_textures(const std::string& path);
};
} // namespace decompiler
36 changes: 16 additions & 20 deletions decompiler/data/tpage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ TexturePage read_texture_page(ObjectFileData& data,
TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db) {
TPageResultStats stats;
auto& words = data.linked_data.words_by_seg.at(0);
const auto& level_names = data.dgo_names;

// at the beginning there's a texture-page object.
// find the size first.
Expand Down Expand Up @@ -518,12 +519,11 @@ TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db) {
file_util::create_dir_if_needed(
file_util::get_file_path({"assets", "textures", texture_page.name}));
file_util::write_rgba_png(
fmt::format(file_util::get_file_path(
{"assets", "textures", texture_page.name, "{}-{}-{}-{}.png"}),
data.name_in_dgo, tex.name, tex.w, tex.h),
fmt::format(file_util::get_file_path({"assets", "textures", texture_page.name, "{}.png"}),
tex.name),
out.data(), tex.w, tex.h);
texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name,
texture_page.name);
texture_page.name, level_names);
stats.successful_textures++;
} else if (tex.psm == int(PSM::PSMT8) && tex.clutpsm == int(CPSM::PSMCT16)) {
// will store output pixels, rgba (8888)
Expand Down Expand Up @@ -566,12 +566,11 @@ TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db) {
file_util::create_dir_if_needed(
file_util::get_file_path({"assets", "textures", texture_page.name}));
file_util::write_rgba_png(
fmt::format(file_util::get_file_path(
{"assets", "textures", texture_page.name, "{}-{}-{}-{}.png"}),
data.name_in_dgo, tex.name, tex.w, tex.h),
fmt::format(file_util::get_file_path({"assets", "textures", texture_page.name, "{}.png"}),
tex.name),
out.data(), tex.w, tex.h);
texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name,
texture_page.name);
texture_page.name, level_names);
stats.successful_textures++;
} else if (tex.psm == int(PSM::PSMCT16) && tex.clutpsm == 0) {
// not a clut.
Expand All @@ -596,12 +595,11 @@ TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db) {
file_util::create_dir_if_needed(
file_util::get_file_path({"assets", "textures", texture_page.name}));
file_util::write_rgba_png(
fmt::format(file_util::get_file_path(
{"assets", "textures", texture_page.name, "{}-{}-{}-{}.png"}),
data.name_in_dgo, tex.name, tex.w, tex.h),
fmt::format(file_util::get_file_path({"assets", "textures", texture_page.name, "{}.png"}),
tex.name),
out.data(), tex.w, tex.h);
texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name,
texture_page.name);
texture_page.name, level_names);
stats.successful_textures++;
} else if (tex.psm == int(PSM::PSMT4) && tex.clutpsm == int(CPSM::PSMCT16)) {
// will store output pixels, rgba (8888)
Expand Down Expand Up @@ -642,12 +640,11 @@ TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db) {
file_util::create_dir_if_needed(
file_util::get_file_path({"assets", "textures", texture_page.name}));
file_util::write_rgba_png(
fmt::format(file_util::get_file_path(
{"assets", "textures", texture_page.name, "{}-{}-{}-{}.png"}),
data.name_in_dgo, tex.name, tex.w, tex.h),
fmt::format(file_util::get_file_path({"assets", "textures", texture_page.name, "{}.png"}),
tex.name),
out.data(), tex.w, tex.h);
texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name,
texture_page.name);
texture_page.name, level_names);
stats.successful_textures++;
} else if (tex.psm == int(PSM::PSMT4) && tex.clutpsm == int(CPSM::PSMCT32)) {
// will store output pixels, rgba (8888)
Expand Down Expand Up @@ -688,12 +685,11 @@ TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db) {
file_util::create_dir_if_needed(
file_util::get_file_path({"assets", "textures", texture_page.name}));
file_util::write_rgba_png(
fmt::format(file_util::get_file_path(
{"assets", "textures", texture_page.name, "{}-{}-{}-{}.png"}),
data.name_in_dgo, tex.name, tex.w, tex.h),
fmt::format(file_util::get_file_path({"assets", "textures", texture_page.name, "{}.png"}),
tex.name),
out.data(), tex.w, tex.h);
texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name,
texture_page.name);
texture_page.name, level_names);
stats.successful_textures++;
}

Expand Down
64 changes: 64 additions & 0 deletions decompiler/level_extractor/extract_level.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,68 @@ void print_memory_usage(const tfrag3::Level& lev, int uncompressed_data_size) {
}
}

void add_all_textures_from_level(tfrag3::Level& lev,
const std::string& level_name,
TextureDB& tex_db) {
ASSERT(lev.textures.empty());
for (auto id : tex_db.texture_ids_per_level[level_name]) {
const auto& tex = tex_db.textures.at(id);
lev.textures.emplace_back();
auto& new_tex = lev.textures.back();
new_tex.combo_id = id;
new_tex.w = tex.w;
new_tex.h = tex.h;
new_tex.debug_tpage_name = tex_db.tpage_names.at(tex.page);
new_tex.debug_name = new_tex.debug_tpage_name + tex.name;
new_tex.data = tex.rgba_bytes;
new_tex.combo_id = id;
new_tex.load_to_pool = true;
}
}

void confirm_textures_identical(TextureDB& tex_db) {
std::unordered_map<std::string, std::vector<u32>> tex_dupl;
for (auto& tex : tex_db.textures) {
auto name = tex_db.tpage_names[tex.second.page] + tex.second.name;
auto it = tex_dupl.find(name);
if (it == tex_dupl.end()) {
tex_dupl.insert({name, tex.second.rgba_bytes});
} else {
bool ok = it->second == tex.second.rgba_bytes;
if (!ok) {
fmt::print("BAD duplicate: {} {} vs {}\n", name, tex.second.rgba_bytes.size(),
it->second.size());
ASSERT(false);
}
}
}
}

/*!
* Extract common textures found in GAME.CGO
*/
void extract_common(ObjectFileDB& db, TextureDB& tex_db, const std::string& dgo_name) {
if (db.obj_files_by_dgo.count(dgo_name) == 0) {
lg::warn("Skipping common extract for {} because the DGO was not part of the input", dgo_name);
return;
}

confirm_textures_identical(tex_db);

tfrag3::Level tfrag_level;
add_all_textures_from_level(tfrag_level, dgo_name, tex_db);
Serializer ser;
tfrag_level.serialize(ser);
auto compressed =
compression::compress_zstd(ser.get_save_result().first, ser.get_save_result().second);
print_memory_usage(tfrag_level, ser.get_save_result().second);
fmt::print("compressed: {} -> {} ({:.2f}%)\n", ser.get_save_result().second, compressed.size(),
100.f * compressed.size() / ser.get_save_result().second);
file_util::write_binary_file(file_util::get_file_path({fmt::format(
"assets/{}.fr3", dgo_name.substr(0, dgo_name.length() - 4))}),
compressed.data(), compressed.size());
}

void extract_from_level(ObjectFileDB& db,
TextureDB& tex_db,
const std::string& dgo_name,
Expand Down Expand Up @@ -121,6 +183,8 @@ void extract_from_level(ObjectFileDB& db,
int i = 0;
tfrag3::Level tfrag_level;

add_all_textures_from_level(tfrag_level, dgo_name, tex_db);

for (auto& draw_tree : bsp_header.drawable_tree_array.trees) {
if (tfrag_trees.count(draw_tree->my_type())) {
auto as_tfrag_tree = dynamic_cast<level_tools::DrawableTreeTfrag*>(draw_tree.get());
Expand Down
3 changes: 2 additions & 1 deletion decompiler/level_extractor/extract_level.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ void extract_from_level(ObjectFileDB& db,
const std::string& dgo_name,
const DecompileHacks& hacks,
bool dump_level);
}
void extract_common(ObjectFileDB& db, TextureDB& tex_db, const std::string& dgo_name);
} // namespace decompiler
6 changes: 6 additions & 0 deletions decompiler/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ int main(int argc, char** argv) {
}

fmt::print("[Mem] After textures: {} MB\n", get_peak_rss() / (1024 * 1024));
// todo config
auto replacements_path = file_util::get_file_path({"texture_replacements"});
if (std::filesystem::exists(replacements_path)) {
tex_db.replace_textures(replacements_path);
}

if (config.process_game_count) {
auto result = db.process_game_count_file();
Expand All @@ -207,6 +212,7 @@ int main(int argc, char** argv) {
}

if (config.levels_extract) {
extract_common(db, tex_db, "GAME.CGO");
for (auto& lev : config.levels_to_extract) {
extract_from_level(db, tex_db, lev, config.hacks, config.rip_levels);
}
Expand Down
26 changes: 26 additions & 0 deletions docs/texture_replacement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# How to replace textures

Textures to be replaced should be saved in
```
jak-project/texture_replacements/page_name/texture_name.png
```
Where `page_name` is the name of the folder in `assets/textures` and `texture_name.png` is the name of the texture. You'll have to create the `texture_replacements` folder yourself.

# Recommended use
To make this easier to set up, you can copy the default textures from `assets`, and then modify those.

For example, you can copy the `common` folder from `assets/textures` to `texture_replacements`. Then you can modify the png files in `texture_replacements/common`

# Rebuilding the game with modified textures
Run the decompiler again to rebuild with modified textures.

If it worked, you will see:
```
Replacing jak-project/texture_replacements/common/jng-precursor-metal-plain-01-lores.png
```
in part of the output.

# Restrictions
Do not change the resolution of the sky, clouds, or eye textures. Other textures should let you change the size. Using extremely large textures will use more VRAM and will load slower.

The PNG file should have an alpha channel. Some textures use their alpha channels for transparency, or for indicating which parts should have environment mapping applied. It may be useful to look at how the original texture uses the alpha channel first, especially for particle effects.
6 changes: 6 additions & 0 deletions game/graphics/gfx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ void texture_relocate(u32 destination, u32 source, u32 format) {
}
}

void set_levels(const std::vector<std::string>& levels) {
if (GetCurrentRenderer()) {
GetCurrentRenderer()->set_levels(levels);
}
}

void poll_events() {
GetCurrentRenderer()->poll_events();
}
Expand Down
3 changes: 2 additions & 1 deletion game/graphics/gfx.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct GfxRendererModule {
std::function<void(const u8*, int, u32)> texture_upload_now;
std::function<void(u32, u32, u32)> texture_relocate;
std::function<void()> poll_events;

std::function<void(const std::vector<std::string>&)> set_levels;
GfxPipeline pipeline;
const char* name;
};
Expand Down Expand Up @@ -95,6 +95,7 @@ u32 sync_path();
void send_chain(const void* data, u32 offset);
void texture_upload_now(const u8* tpage, int mode, u32 s7_ptr);
void texture_relocate(u32 destination, u32 source, u32 format);
void set_levels(const std::vector<std::string>& levels);
void poll_events();
u64 get_window_width();
u64 get_window_height();
Expand Down
7 changes: 1 addition & 6 deletions game/graphics/opengl_renderer/BucketRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ void SharedRenderState::reset() {
for (auto& x : occlusion_vis) {
x.valid = false;
}
load_status_debug.clear();
}

RenderMux::RenderMux(const std::string& name,
Expand All @@ -83,12 +84,6 @@ void RenderMux::render(DmaFollower& dma,
m_renderers[m_render_idx]->render(dma, render_state, prof);
}

void RenderMux::serialize(Serializer& ser) {
for (auto& r : m_renderers) {
r->serialize(ser);
}
}

void RenderMux::draw_debug_window() {
ImGui::ListBox("Pick", &m_render_idx, m_name_str_ptrs.data(), m_renderers.size());
ImGui::Separator();
Expand Down
Loading