mirror of
https://github.com/open-goal/jak-project
synced 2026-07-07 14:13:45 -04:00
[jak2] More texture animations (#2831)
Added framework to do texture animations entirely in C++. Currently only works on relatively simple ones, and doesn't handle updating all parameters - only the speeds. Connected texture animations to merc and tfrag for skull gems, dark bomb, and scrolling conveyors. Cleaned up Tfragment/Tfrag3, which used to be two classes. This was one of the first C++ renderers, so it had a weird design.
This commit is contained in:
@@ -720,6 +720,9 @@ void Level::memory_usage(MemoryUsageTracker* tracker) const {
|
||||
for (const auto& texture : textures) {
|
||||
texture.memory_usage(tracker);
|
||||
}
|
||||
for (const auto& texture : index_textures) {
|
||||
texture.memory_usage(tracker);
|
||||
}
|
||||
for (const auto& tftk : tfrag_trees) {
|
||||
for (const auto& tree : tftk) {
|
||||
tree.memory_usage(tracker);
|
||||
|
||||
@@ -180,7 +180,7 @@ struct PackedShrubVertices {
|
||||
// check visibility.
|
||||
struct StripDraw {
|
||||
DrawMode mode; // the OpenGL draw settings.
|
||||
u32 tree_tex_id = 0; // the texture that should be bound for the draw
|
||||
s32 tree_tex_id = 0; // the texture that should be bound for the draw (negative for anim slot)
|
||||
|
||||
struct {
|
||||
u32 idx_of_first_idx_in_full_buffer = 0;
|
||||
|
||||
@@ -22,6 +22,12 @@ std::vector<std::string> jak2_slots = {
|
||||
//"kor-head-formorph-noreflect",
|
||||
//"kor-lowercaps-formorph",
|
||||
//"kor-uppercaps-formorph",
|
||||
"skull-gem-dest",
|
||||
"bomb-gradient",
|
||||
"cas-conveyor-dest",
|
||||
"cas-conveyor-dest-01",
|
||||
"cas-conveyor-dest-02",
|
||||
"cas-conveyor-dest-03",
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -491,7 +491,25 @@
|
||||
"jakb-eyelid-dark",
|
||||
"jakb-finger",
|
||||
"jakb-finger-norm",
|
||||
"jakb-finger-dark"
|
||||
"jakb-finger-dark",
|
||||
|
||||
// Skull Gem
|
||||
"skull-gem-dest",
|
||||
"skull-gem-alpha-00",
|
||||
"skull-gem-alpha-01",
|
||||
"skull-gem-alpha-02",
|
||||
|
||||
// BOMB
|
||||
"bomb-gradient",
|
||||
"bomb-gradient-rim",
|
||||
"bomb-gradient-flames",
|
||||
|
||||
// CAS conveyor
|
||||
"cas-conveyor",
|
||||
"cas-conveyor-dest",
|
||||
"cas-conveyor-dest-01",
|
||||
"cas-conveyor-dest-02",
|
||||
"cas-conveyor-dest-03"
|
||||
|
||||
// "kor-eyeeffect-formorph",
|
||||
// "kor-eyeeffect-formorph-start",
|
||||
|
||||
@@ -504,6 +504,12 @@ TPageResultStats process_tpage(ObjectFileData& data,
|
||||
|
||||
if (animated_textures.count(tex.name) && !ignore_animated) {
|
||||
switch (tex.psm) {
|
||||
case int(PSM::PSMCT32):
|
||||
// no need.
|
||||
break;
|
||||
case int(PSM::PSMT4):
|
||||
// currently not needed.
|
||||
break;
|
||||
case int(PSM::PSMT8):
|
||||
ASSERT(tex.clutpsm == int(CPSM::PSMCT32));
|
||||
{
|
||||
|
||||
@@ -183,8 +183,8 @@ void decompile(const fs::path& iso_data_path, const std::string& data_subfolder)
|
||||
auto level_out_path =
|
||||
file_util::get_jak_project_dir() / "out" / game_version_names[config.game_version] / "fr3";
|
||||
file_util::create_dir_if_needed(level_out_path);
|
||||
extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config.hacks,
|
||||
config.rip_levels, config.extract_collision, level_out_path);
|
||||
extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config, config.rip_levels,
|
||||
config.extract_collision, level_out_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,22 @@ bool is_valid_bsp(const decompiler::LinkedObjectFile& file) {
|
||||
return true;
|
||||
}
|
||||
|
||||
tfrag3::Texture make_texture(u32 id,
|
||||
const TextureDB::TextureData& tex,
|
||||
const std::string& tpage_name,
|
||||
bool pool_load) {
|
||||
tfrag3::Texture new_tex;
|
||||
new_tex.combo_id = id;
|
||||
new_tex.w = tex.w;
|
||||
new_tex.h = tex.h;
|
||||
new_tex.debug_tpage_name = tpage_name;
|
||||
new_tex.debug_name = tex.name;
|
||||
new_tex.data = tex.rgba_bytes;
|
||||
new_tex.combo_id = id;
|
||||
new_tex.load_to_pool = pool_load;
|
||||
return new_tex;
|
||||
}
|
||||
|
||||
void add_all_textures_from_level(tfrag3::Level& lev,
|
||||
const std::string& level_name,
|
||||
const TextureDB& tex_db) {
|
||||
@@ -79,16 +95,7 @@ void add_all_textures_from_level(tfrag3::Level& lev,
|
||||
if (level_it != tex_db.texture_ids_per_level.end()) {
|
||||
for (auto id : level_it->second) {
|
||||
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 = tex.name;
|
||||
new_tex.data = tex.rgba_bytes;
|
||||
new_tex.combo_id = id;
|
||||
new_tex.load_to_pool = true;
|
||||
lev.textures.push_back(make_texture(id, tex, tex_db.tpage_names.at(tex.page), true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,7 +231,8 @@ void extract_common(const ObjectFileDB& db,
|
||||
const TextureDB& tex_db,
|
||||
const std::string& dgo_name,
|
||||
bool dump_levels,
|
||||
const fs::path& output_folder) {
|
||||
const fs::path& output_folder,
|
||||
const Config& config) {
|
||||
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;
|
||||
@@ -241,11 +249,28 @@ void extract_common(const ObjectFileDB& db,
|
||||
add_all_textures_from_level(tfrag_level, dgo_name, tex_db);
|
||||
extract_art_groups_from_level(db, tex_db, {}, dgo_name, tfrag_level);
|
||||
|
||||
std::set<std::string> textures_we_have;
|
||||
|
||||
// put _all_ index textures in common.
|
||||
for (const auto& [id, tex] : tex_db.index_textures_by_combo_id) {
|
||||
tfrag_level.index_textures.push_back(tex);
|
||||
}
|
||||
|
||||
for (const auto& t : tfrag_level.textures) {
|
||||
textures_we_have.insert(t.debug_name);
|
||||
}
|
||||
|
||||
// add animated textures that are missing.
|
||||
for (const auto& [id, normal_texture] : tex_db.textures) {
|
||||
if (config.animated_textures.count(normal_texture.name) &&
|
||||
!textures_we_have.count(normal_texture.name)) {
|
||||
lg::warn("adding anim normal texture {} ", normal_texture.name);
|
||||
textures_we_have.insert(normal_texture.name);
|
||||
tfrag_level.textures.push_back(
|
||||
make_texture(id, normal_texture, tex_db.tpage_names.at(normal_texture.page), false));
|
||||
}
|
||||
}
|
||||
|
||||
Serializer ser;
|
||||
tfrag_level.serialize(ser);
|
||||
auto compressed =
|
||||
@@ -269,7 +294,7 @@ void extract_common(const ObjectFileDB& db,
|
||||
void extract_from_level(const ObjectFileDB& db,
|
||||
const TextureDB& tex_db,
|
||||
const std::string& dgo_name,
|
||||
const DecompileHacks& hacks,
|
||||
const Config& config,
|
||||
bool dump_level,
|
||||
bool extract_collision,
|
||||
const fs::path& output_folder) {
|
||||
@@ -282,7 +307,7 @@ void extract_from_level(const ObjectFileDB& db,
|
||||
|
||||
// the bsp header file data
|
||||
auto tex_remap =
|
||||
extract_bsp_from_level(db, tex_db, dgo_name, hacks, extract_collision, level_data);
|
||||
extract_bsp_from_level(db, tex_db, dgo_name, config.hacks, extract_collision, level_data);
|
||||
extract_art_groups_from_level(db, tex_db, tex_remap, dgo_name, level_data);
|
||||
|
||||
Serializer ser;
|
||||
@@ -313,15 +338,15 @@ void extract_all_levels(const ObjectFileDB& db,
|
||||
const TextureDB& tex_db,
|
||||
const std::vector<std::string>& dgo_names,
|
||||
const std::string& common_name,
|
||||
const DecompileHacks& hacks,
|
||||
const Config& config,
|
||||
bool debug_dump_level,
|
||||
bool extract_collision,
|
||||
const fs::path& output_path) {
|
||||
extract_common(db, tex_db, common_name, debug_dump_level, output_path);
|
||||
extract_common(db, tex_db, common_name, debug_dump_level, output_path, config);
|
||||
SimpleThreadGroup threads;
|
||||
threads.run(
|
||||
[&](int idx) {
|
||||
extract_from_level(db, tex_db, dgo_names[idx], hacks, debug_dump_level, extract_collision,
|
||||
extract_from_level(db, tex_db, dgo_names[idx], config, debug_dump_level, extract_collision,
|
||||
output_path);
|
||||
},
|
||||
dgo_names.size());
|
||||
|
||||
@@ -13,7 +13,7 @@ void extract_all_levels(const ObjectFileDB& db,
|
||||
const TextureDB& tex_db,
|
||||
const std::vector<std::string>& dgo_names,
|
||||
const std::string& common_name,
|
||||
const DecompileHacks& hacks,
|
||||
const Config& config,
|
||||
bool debug_dump_level,
|
||||
bool extract_collision,
|
||||
const fs::path& path);
|
||||
|
||||
@@ -796,12 +796,8 @@ s32 find_or_add_texture_to_level(tfrag3::Level& out,
|
||||
const auto& level_tex = out.textures.at(idx_in_level_texture);
|
||||
const auto& it = tex_db.animated_tex_output_to_anim_slot.find(level_tex.debug_name);
|
||||
if (it != tex_db.animated_tex_output_to_anim_slot.end()) {
|
||||
lg::error("Animated slot {} -> {}", level_tex.debug_name, it->second);
|
||||
return -int(it->second) - 1;
|
||||
} else {
|
||||
// lg::warn("no anim: {}", level_tex.debug_name);
|
||||
}
|
||||
|
||||
return idx_in_level_texture;
|
||||
}
|
||||
|
||||
|
||||
@@ -2007,6 +2007,65 @@ std::map<u32, std::vector<GroupedDraw>> make_draw_groups(std::vector<TFragDraw>&
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 find_or_add_texture_to_level(u32 combo_tex_id,
|
||||
std::vector<tfrag3::Texture>& texture_pool,
|
||||
const TextureDB& tdb,
|
||||
const std::vector<std::pair<int, int>>& expected_missing_textures,
|
||||
const std::string& level_name) {
|
||||
// first, let's see if we have a texture for this.
|
||||
s32 tfrag3_tex_id = INT32_MAX;
|
||||
for (u32 i = 0; i < texture_pool.size(); i++) {
|
||||
if (texture_pool[i].combo_id == combo_tex_id) {
|
||||
tfrag3_tex_id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tfrag3_tex_id == INT32_MAX) {
|
||||
// nope. we are a new texture.
|
||||
auto tex_it = tdb.textures.find(combo_tex_id);
|
||||
if (tex_it == tdb.textures.end()) {
|
||||
int tpage = combo_tex_id >> 16;
|
||||
int idx = combo_tex_id & 0xffff;
|
||||
bool ok_to_miss =
|
||||
std::find(expected_missing_textures.begin(), expected_missing_textures.end(),
|
||||
std::make_pair(tpage, idx)) != expected_missing_textures.end();
|
||||
if (ok_to_miss) {
|
||||
// we're missing a texture, just use the first one.
|
||||
tex_it = tdb.textures.begin();
|
||||
} else {
|
||||
ASSERT_MSG(
|
||||
false,
|
||||
fmt::format("texture {} wasn't found. make sure it is loaded somehow. You may need "
|
||||
"to include "
|
||||
"ART.DGO or GAME.DGO in addition to the level DGOs for shared textures."
|
||||
"tpage is {}. id is {} (0x{:x}) for level {}",
|
||||
combo_tex_id, combo_tex_id >> 16, combo_tex_id & 0xffff,
|
||||
combo_tex_id & 0xffff, level_name));
|
||||
}
|
||||
}
|
||||
tfrag3_tex_id = texture_pool.size();
|
||||
texture_pool.emplace_back();
|
||||
auto& new_tex = texture_pool.back();
|
||||
new_tex.combo_id = combo_tex_id;
|
||||
new_tex.w = tex_it->second.w;
|
||||
new_tex.h = tex_it->second.h;
|
||||
new_tex.debug_name = tex_it->second.name;
|
||||
new_tex.debug_tpage_name = tdb.tpage_names.at(tex_it->second.page);
|
||||
new_tex.data = tex_it->second.rgba_bytes;
|
||||
}
|
||||
|
||||
// map animated textures to the animation slot.
|
||||
const auto& level_tex = texture_pool.at(tfrag3_tex_id);
|
||||
const auto& it = tdb.animated_tex_output_to_anim_slot.find(level_tex.debug_name);
|
||||
if (it != tdb.animated_tex_output_to_anim_slot.end()) {
|
||||
// lg::warn("tfrag3 animated texture: {}", level_tex.debug_name);
|
||||
return -int(it->second) - 1;
|
||||
}
|
||||
|
||||
return tfrag3_tex_id;
|
||||
}
|
||||
|
||||
void make_tfrag3_data(std::map<u32, std::vector<GroupedDraw>>& draws,
|
||||
tfrag3::TfragTree& tree_out,
|
||||
std::vector<tfrag3::PreloadedVertex>& vertices,
|
||||
@@ -2020,49 +2079,8 @@ void make_tfrag3_data(std::map<u32, std::vector<GroupedDraw>>& draws,
|
||||
// and link textures.
|
||||
|
||||
for (auto& [combo_tex_id, draw_list] : draws) {
|
||||
// first, let's see if we have a texture for this.
|
||||
u32 tfrag3_tex_id = UINT32_MAX;
|
||||
for (u32 i = 0; i < texture_pool.size(); i++) {
|
||||
if (texture_pool[i].combo_id == combo_tex_id) {
|
||||
tfrag3_tex_id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tfrag3_tex_id == UINT32_MAX) {
|
||||
// nope. we are a new texture.
|
||||
auto tex_it = tdb.textures.find(combo_tex_id);
|
||||
if (tex_it == tdb.textures.end()) {
|
||||
int tpage = combo_tex_id >> 16;
|
||||
int idx = combo_tex_id & 0xffff;
|
||||
bool ok_to_miss =
|
||||
std::find(expected_missing_textures.begin(), expected_missing_textures.end(),
|
||||
std::make_pair(tpage, idx)) != expected_missing_textures.end();
|
||||
if (ok_to_miss) {
|
||||
// we're missing a texture, just use the first one.
|
||||
tex_it = tdb.textures.begin();
|
||||
} else {
|
||||
ASSERT_MSG(
|
||||
false,
|
||||
fmt::format("texture {} wasn't found. make sure it is loaded somehow. You may need "
|
||||
"to include "
|
||||
"ART.DGO or GAME.DGO in addition to the level DGOs for shared textures."
|
||||
"tpage is {}. id is {} (0x{:x}) for level {}",
|
||||
combo_tex_id, combo_tex_id >> 16, combo_tex_id & 0xffff,
|
||||
combo_tex_id & 0xffff, level_name));
|
||||
}
|
||||
}
|
||||
tfrag3_tex_id = texture_pool.size();
|
||||
texture_pool.emplace_back();
|
||||
auto& new_tex = texture_pool.back();
|
||||
new_tex.combo_id = combo_tex_id;
|
||||
new_tex.w = tex_it->second.w;
|
||||
new_tex.h = tex_it->second.h;
|
||||
new_tex.debug_name = tex_it->second.name;
|
||||
new_tex.debug_tpage_name = tdb.tpage_names.at(tex_it->second.page);
|
||||
new_tex.data = tex_it->second.rgba_bytes;
|
||||
}
|
||||
|
||||
s32 tfrag3_tex_id = find_or_add_texture_to_level(combo_tex_id, texture_pool, tdb,
|
||||
expected_missing_textures, level_name);
|
||||
// now, add draws
|
||||
for (auto& draw : draw_list) {
|
||||
tfrag3::StripDraw tdraw;
|
||||
|
||||
+2
-2
@@ -294,8 +294,8 @@ int main(int argc, char** argv) {
|
||||
auto level_out_path =
|
||||
file_util::get_jak_project_dir() / "out" / game_version_names[config.game_version] / "fr3";
|
||||
file_util::create_dir_if_needed(level_out_path);
|
||||
extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config.hacks,
|
||||
config.rip_levels, config.extract_collision, level_out_path);
|
||||
extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config, config.rip_levels,
|
||||
config.extract_collision, level_out_path);
|
||||
}
|
||||
|
||||
mem_log("After extraction: {} MB", get_peak_rss() / (1024 * 1024));
|
||||
|
||||
@@ -35,7 +35,6 @@ set(RUNTIME_SOURCE
|
||||
graphics/jak2_texture_remap.cpp
|
||||
graphics/opengl_renderer/background/background_common.cpp
|
||||
graphics/opengl_renderer/background/Shrub.cpp
|
||||
graphics/opengl_renderer/background/Tfrag3.cpp
|
||||
graphics/opengl_renderer/background/TFragment.cpp
|
||||
graphics/opengl_renderer/background/Tie3.cpp
|
||||
graphics/opengl_renderer/BlitDisplays.cpp
|
||||
|
||||
@@ -102,8 +102,7 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr<TexturePool> texture_pool,
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
m_merc2 = std::make_shared<Merc2>(m_render_state.shaders,
|
||||
m_texture_animator ? m_texture_animator->slots() : nullptr);
|
||||
m_merc2 = std::make_shared<Merc2>(m_render_state.shaders, anim_slot_array());
|
||||
m_generic2 = std::make_shared<Generic2>(m_render_state.shaders);
|
||||
|
||||
// initialize all renderers
|
||||
@@ -143,7 +142,7 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
|
||||
init_bucket_renderer<TFragment>(
|
||||
fmt::format("tfrag-l{}-tfrag", i), BucketCategory::TFRAG,
|
||||
GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_L0_TFRAG, BucketId::TFRAG_L1_TFRAG, i),
|
||||
std::vector{tfrag3::TFragmentTreeKind::NORMAL}, false, i);
|
||||
std::vector{tfrag3::TFragmentTreeKind::NORMAL}, false, i, anim_slot_array());
|
||||
Tie3* tie = init_bucket_renderer<Tie3>(
|
||||
fmt::format("tie-l{}-tfrag", i), BucketCategory::TIE,
|
||||
GET_BUCKET_ID_FOR_LIST(BucketId::TIE_L0_TFRAG, BucketId::TIE_L1_TFRAG, i), i);
|
||||
@@ -181,7 +180,7 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
|
||||
init_bucket_renderer<TFragment>(
|
||||
fmt::format("tfrag-t-l{}-alpha", i), BucketCategory::TFRAG,
|
||||
GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_T_L0_ALPHA, BucketId::TFRAG_T_L1_ALPHA, i),
|
||||
std::vector{tfrag3::TFragmentTreeKind::TRANS}, false, i);
|
||||
std::vector{tfrag3::TFragmentTreeKind::TRANS}, false, i, anim_slot_array());
|
||||
init_bucket_renderer<Tie3AnotherCategory>(
|
||||
fmt::format("tie-t-l{}-alpha", i), BucketCategory::TIE,
|
||||
GET_BUCKET_ID_FOR_LIST(BucketId::TIE_T_L0_ALPHA, BucketId::TIE_T_L1_ALPHA, i), tie,
|
||||
@@ -236,7 +235,7 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
|
||||
init_bucket_renderer<TFragment>(
|
||||
fmt::format("tfrag-w-l{}-alpha", i), BucketCategory::TFRAG,
|
||||
GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_W_L0_WATER, BucketId::TFRAG_W_L1_WATER, i),
|
||||
std::vector{tfrag3::TFragmentTreeKind::WATER}, false, i);
|
||||
std::vector{tfrag3::TFragmentTreeKind::WATER}, false, i, anim_slot_array());
|
||||
init_bucket_renderer<Tie3AnotherCategory>(
|
||||
fmt::format("tie-w-l{}-water", i), BucketCategory::TIE,
|
||||
GET_BUCKET_ID_FOR_LIST(BucketId::TIE_W_L0_WATER, BucketId::TIE_W_L1_WATER, i), tie,
|
||||
@@ -355,7 +354,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
|
||||
BucketId::TFRAG_TEX_LEVEL0, m_texture_animator);
|
||||
// 6 : TFRAG_LEVEL0
|
||||
init_bucket_renderer<TFragment>("l0-tfrag-tfrag", BucketCategory::TFRAG, BucketId::TFRAG_LEVEL0,
|
||||
normal_tfrags, false, 0);
|
||||
normal_tfrags, false, 0, anim_slot_array());
|
||||
// 7 : TFRAG_NEAR_LEVEL0
|
||||
// 8 : TIE_NEAR_LEVEL0
|
||||
// 9 : TIE_LEVEL0
|
||||
@@ -377,7 +376,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
|
||||
BucketId::TFRAG_TEX_LEVEL1, m_texture_animator);
|
||||
// 13 : TFRAG_LEVEL1
|
||||
init_bucket_renderer<TFragment>("l1-tfrag-tfrag", BucketCategory::TFRAG, BucketId::TFRAG_LEVEL1,
|
||||
normal_tfrags, false, 1);
|
||||
normal_tfrags, false, 1, anim_slot_array());
|
||||
// 14 : TFRAG_NEAR_LEVEL1
|
||||
// 15 : TIE_NEAR_LEVEL1
|
||||
// 16 : TIE_LEVEL1
|
||||
@@ -430,14 +429,15 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
|
||||
BucketId::ALPHA_TEX_LEVEL0, m_texture_animator); // 31
|
||||
init_bucket_renderer<SkyBlendHandler>("l0-alpha-sky-blend-and-tfrag-trans", BucketCategory::OTHER,
|
||||
BucketId::TFRAG_TRANS0_AND_SKY_BLEND_LEVEL0, 0,
|
||||
sky_gpu_blender, sky_cpu_blender); // 32
|
||||
sky_gpu_blender, sky_cpu_blender, anim_slot_array()); // 32
|
||||
// 33
|
||||
init_bucket_renderer<TFragment>("l0-alpha-tfrag", BucketCategory::TFRAG,
|
||||
BucketId::TFRAG_DIRT_LEVEL0, dirt_tfrags, false,
|
||||
0); // 34
|
||||
BucketId::TFRAG_DIRT_LEVEL0, dirt_tfrags, false, 0,
|
||||
anim_slot_array()); // 34
|
||||
// 35
|
||||
init_bucket_renderer<TFragment>("l0-alpha-tfrag-ice", BucketCategory::TFRAG,
|
||||
BucketId::TFRAG_ICE_LEVEL0, ice_tfrags, false, 0);
|
||||
BucketId::TFRAG_ICE_LEVEL0, ice_tfrags, false, 0,
|
||||
anim_slot_array());
|
||||
// 37
|
||||
|
||||
//-----------------------
|
||||
@@ -447,14 +447,15 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
|
||||
BucketId::ALPHA_TEX_LEVEL1, m_texture_animator); // 38
|
||||
init_bucket_renderer<SkyBlendHandler>("l1-alpha-sky-blend-and-tfrag-trans", BucketCategory::OTHER,
|
||||
BucketId::TFRAG_TRANS1_AND_SKY_BLEND_LEVEL1, 1,
|
||||
sky_gpu_blender, sky_cpu_blender); // 39
|
||||
sky_gpu_blender, sky_cpu_blender, anim_slot_array()); // 39
|
||||
// 40
|
||||
init_bucket_renderer<TFragment>("l1-alpha-tfrag-dirt", BucketCategory::TFRAG,
|
||||
BucketId::TFRAG_DIRT_LEVEL1, dirt_tfrags, false,
|
||||
1); // 41
|
||||
BucketId::TFRAG_DIRT_LEVEL1, dirt_tfrags, false, 1,
|
||||
anim_slot_array()); // 41
|
||||
// 42
|
||||
init_bucket_renderer<TFragment>("l1-alpha-tfrag-ice", BucketCategory::TFRAG,
|
||||
BucketId::TFRAG_ICE_LEVEL1, ice_tfrags, false, 1);
|
||||
BucketId::TFRAG_ICE_LEVEL1, ice_tfrags, false, 1,
|
||||
anim_slot_array());
|
||||
// 44
|
||||
|
||||
init_bucket_renderer<Merc2BucketRenderer>("common-alpha-merc", BucketCategory::MERC,
|
||||
|
||||
@@ -98,6 +98,10 @@ class OpenGLRenderer {
|
||||
return ret;
|
||||
}
|
||||
|
||||
const std::vector<GLuint>* anim_slot_array() {
|
||||
return m_texture_animator ? m_texture_animator->slots() : nullptr;
|
||||
}
|
||||
|
||||
SharedRenderState m_render_state;
|
||||
Profiler m_profiler;
|
||||
SmallProfiler m_small_profiler;
|
||||
|
||||
@@ -24,7 +24,8 @@ SkyBlendHandler::SkyBlendHandler(const std::string& name,
|
||||
int my_id,
|
||||
int level_id,
|
||||
std::shared_ptr<SkyBlendGPU> shared_blender,
|
||||
std::shared_ptr<SkyBlendCPU> shared_blender_cpu)
|
||||
std::shared_ptr<SkyBlendCPU> shared_blender_cpu,
|
||||
const std::vector<GLuint>* anim_slots)
|
||||
: BucketRenderer(name, my_id),
|
||||
m_shared_gpu_blender(shared_blender),
|
||||
m_shared_cpu_blender(shared_blender_cpu),
|
||||
@@ -32,7 +33,8 @@ SkyBlendHandler::SkyBlendHandler(const std::string& name,
|
||||
my_id,
|
||||
{tfrag3::TFragmentTreeKind::TRANS, tfrag3::TFragmentTreeKind::LOWRES_TRANS},
|
||||
true,
|
||||
level_id) {}
|
||||
level_id,
|
||||
anim_slots) {}
|
||||
|
||||
void SkyBlendHandler::init_shaders(ShaderLibrary& shaders) {
|
||||
m_tfrag_renderer.init_shaders(shaders);
|
||||
|
||||
@@ -16,7 +16,8 @@ class SkyBlendHandler : public BucketRenderer {
|
||||
int my_id,
|
||||
int level_id,
|
||||
std::shared_ptr<SkyBlendGPU> shared_gpu_blender,
|
||||
std::shared_ptr<SkyBlendCPU> shared_cpu_blender);
|
||||
std::shared_ptr<SkyBlendCPU> shared_cpu_blender,
|
||||
const std::vector<GLuint>* anim_slots);
|
||||
void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override;
|
||||
void draw_debug_window() override;
|
||||
void init_shaders(ShaderLibrary& shaders) override;
|
||||
|
||||
@@ -14,12 +14,39 @@
|
||||
#define dprintf(...)
|
||||
#define dfmt(...)
|
||||
|
||||
// -- Texture Animations
|
||||
// The game has a number of "texture animation arrays".
|
||||
// On the original PS2, there wasn't enough VRAM to hold all textures for a frame, so they would
|
||||
// upload a single "tpage" at a time. Along with this, they would dynamically generate some animated
|
||||
// textures. Each tpage has an associated texture animation array.
|
||||
|
||||
// -- Our approach
|
||||
// This part of the code has turned out to be pretty bad in terms of performance.
|
||||
// We also have the challenge of actually getting all the renderers to look at the right animated
|
||||
// textures, which is tricky because we rewrote them for jak 1, which doesn't have this.
|
||||
//
|
||||
// So there's a lot of tricks here to try to speed things up. We modified the GOAL code to work
|
||||
// better with this code. We have three different approaches to handling a texture animation array:
|
||||
// - Emulation (slowest). This basically pretends to be the PS2, and is the most flexible. It reads
|
||||
// the DMA and maps it to OpenGL operations right now it's used for the clouds (though slightly
|
||||
// wrong).
|
||||
// - Clut-blending. This special cases animations which are just blends between CLUTs.
|
||||
// We optimize this by only doing work if the blend weights change (they didn't on PS2 because
|
||||
// they don't have the vram to store the texture). We also avoid the use of render-to-texture.
|
||||
// Jak's hair/skin/fingers always use this texture.
|
||||
// - "Fixed Animation". For animations that use only basic features, we have a way to run them
|
||||
// entirely in C++. This avoids repeated switches between framebuffers, and lets us precompute
|
||||
// more stuff.
|
||||
|
||||
// -- OpenGL performance.
|
||||
// So there's a lot of stupid-looking OpenGL stuff going on here.
|
||||
// The motivation for this is to avoid an issue where some operations take about 5-10ms.
|
||||
// As far as I can tell, this slow operation is actually the driver forcing this thread to sync
|
||||
// with some internal stuff. It seems to be triggered on:
|
||||
// - deleting a texture that's in use
|
||||
// - glTexImage2D to modify a texture with a different sized texture.
|
||||
// - deleting a texture that was used on the previous frame (so in use by the driver thread).
|
||||
// this is a "safe" operation, but I suspect it forces the driver thread to synchronize).
|
||||
// - glTexImage2D to modify a texture with a different sized texture. (likely hits same case as
|
||||
// above)
|
||||
|
||||
// TODO:
|
||||
// clouds aren't really working right. The final operation of move rb to ba is a guess.
|
||||
@@ -35,12 +62,13 @@ OpenGLTexturePool::OpenGLTexturePool() {
|
||||
struct Alloc {
|
||||
u64 w, h, n;
|
||||
};
|
||||
// list of sizes to preallocate.
|
||||
// list of sizes to preallocate: {width, height, count}.
|
||||
for (const auto& a : std::vector<Alloc>{{16, 16, 5}, //
|
||||
{32, 16, 1},
|
||||
{32, 32, 5},
|
||||
{32, 32, 8},
|
||||
{32, 64, 1},
|
||||
{64, 64, 8},
|
||||
{64, 32, 4},
|
||||
{64, 64, 9},
|
||||
{64, 128, 4},
|
||||
{128, 128, 5},
|
||||
{256, 1, 2},
|
||||
@@ -62,6 +90,9 @@ OpenGLTexturePool::~OpenGLTexturePool() {
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get a preallocated texture with the given size, or fatal error if we are out.
|
||||
*/
|
||||
GLuint OpenGLTexturePool::allocate(u64 w, u64 h) {
|
||||
const auto& it = textures.find((w << 32) | h);
|
||||
if (it == textures.end()) {
|
||||
@@ -77,10 +108,19 @@ GLuint OpenGLTexturePool::allocate(u64 w, u64 h) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Return a texture to the pool. The size must be provided.
|
||||
*/
|
||||
void OpenGLTexturePool::free(GLuint texture, u64 w, u64 h) {
|
||||
textures[(w << 32) | h].push_back(texture);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get an index-format texture from the given tfrag3 level.
|
||||
* In cases where multiple original-game-levels both provide a texture, but the data is different,
|
||||
* prefer the one from the given level.
|
||||
* This is slow and intended to be used an init time.
|
||||
*/
|
||||
const tfrag3::IndexTexture* itex_by_name(const tfrag3::Level* level,
|
||||
const std::string& name,
|
||||
const std::optional<std::string>& level_name) {
|
||||
@@ -115,6 +155,36 @@ const tfrag3::IndexTexture* itex_by_name(const tfrag3::Level* level,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get a RGBA format texture by name from the given tfrag3 level. Slow, and intended for init time.
|
||||
*/
|
||||
const tfrag3::Texture* tex_by_name(const tfrag3::Level* level, const std::string& name) {
|
||||
const tfrag3::Texture* ret = nullptr;
|
||||
for (const auto& t : level->textures) {
|
||||
if (t.debug_name == name) {
|
||||
if (ret) {
|
||||
lg::error("Multiple textures named {}", name);
|
||||
ASSERT(ret->data == t.data);
|
||||
}
|
||||
ret = &t;
|
||||
}
|
||||
}
|
||||
if (!ret) {
|
||||
lg::error("no texture named {}", name);
|
||||
for (const auto& t : level->textures) {
|
||||
fmt::print("texture: {}\n", t.debug_name);
|
||||
}
|
||||
lg::die("no texture named {}", name);
|
||||
} else {
|
||||
// lg::info("got idx: {}", name);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get a texture animation slot index for the given name. Fatal error if there is no animated
|
||||
* texture slot with this name. Slow, and intended for init time.
|
||||
*/
|
||||
int output_slot_by_idx(GameVersion version, const std::string& name) {
|
||||
const std::vector<std::string>* v = nullptr;
|
||||
switch (version) {
|
||||
@@ -134,27 +204,52 @@ int output_slot_by_idx(GameVersion version, const std::string& name) {
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Upload a texture and generate mipmaps. Assumes the usual RGBA format.
|
||||
*/
|
||||
void opengl_upload_texture(GLint dest, const void* data, int w, int h) {
|
||||
glBindTexture(GL_TEXTURE_2D, dest);
|
||||
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);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Utility class to grab CLUTs from the source textures, blend them, and produce a destination RGBA
|
||||
* texture using the index data in dest.
|
||||
*/
|
||||
ClutBlender::ClutBlender(const std::string& dest,
|
||||
const std::vector<std::string>& sources,
|
||||
const std::optional<std::string>& level_name,
|
||||
const tfrag3::Level* level,
|
||||
OpenGLTexturePool* tpool) {
|
||||
// find the destination texture
|
||||
m_dest = itex_by_name(level, dest, level_name);
|
||||
// find the clut source textures
|
||||
for (const auto& sname : sources) {
|
||||
m_cluts.push_back(&itex_by_name(level, sname, level_name)->color_table);
|
||||
m_current_weights.push_back(0);
|
||||
}
|
||||
// opengl texture that we'll write to
|
||||
m_texture = tpool->allocate(m_dest->w, m_dest->h);
|
||||
m_temp_rgba.resize(m_dest->w * m_dest->h);
|
||||
|
||||
// default to the first one.
|
||||
std::vector<float> init_weights(m_current_weights.size(), 0);
|
||||
init_weights.at(0) = 1.f;
|
||||
run(init_weights.data());
|
||||
}
|
||||
|
||||
/*!
|
||||
* Blend cluts and create an output texture.
|
||||
*/
|
||||
GLuint ClutBlender::run(const float* weights) {
|
||||
bool needs_run = false;
|
||||
|
||||
// check if weights changed or not.
|
||||
for (size_t i = 0; i < m_current_weights.size(); i++) {
|
||||
if (weights[i] != m_current_weights[i]) {
|
||||
needs_run = true;
|
||||
@@ -166,10 +261,12 @@ GLuint ClutBlender::run(const float* weights) {
|
||||
return m_texture;
|
||||
}
|
||||
|
||||
// update weights
|
||||
for (size_t i = 0; i < m_current_weights.size(); i++) {
|
||||
m_current_weights[i] = weights[i];
|
||||
}
|
||||
|
||||
// blend cluts
|
||||
for (int i = 0; i < 256; i++) {
|
||||
math::Vector4f v = math::Vector4f::zero();
|
||||
for (size_t j = 0; j < m_current_weights.size(); j++) {
|
||||
@@ -178,22 +275,65 @@ GLuint ClutBlender::run(const float* weights) {
|
||||
m_temp_clut[i] = v.cast<u8>();
|
||||
}
|
||||
|
||||
// do texture lookups
|
||||
for (int i = 0; i < m_temp_rgba.size(); i++) {
|
||||
memcpy(&m_temp_rgba[i], m_temp_clut[m_dest->index_data[i]].data(), 4);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, m_texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_dest->w, m_dest->h, 0, GL_RGBA,
|
||||
GL_UNSIGNED_INT_8_8_8_8_REV, m_temp_rgba.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);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
// send to GPU.
|
||||
opengl_upload_texture(m_texture, m_temp_rgba.data(), m_dest->w, m_dest->h);
|
||||
|
||||
return m_texture;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Utility class to show what happens if you take a PSM32 texture, upload it as PSM32, then read it
|
||||
* back as PSM8. Byte i from the input data ends up in destinations_per_byte[i].
|
||||
*/
|
||||
Psm32ToPsm8Scrambler::Psm32ToPsm8Scrambler(int w, int h, int write_tex_width, int read_tex_width) {
|
||||
struct InAddr {
|
||||
int x = -1, y = -1, c = -1;
|
||||
};
|
||||
struct OutAddr {
|
||||
int x = -1, y = -1;
|
||||
};
|
||||
|
||||
std::vector<InAddr> vram_from_in(w * h * 4);
|
||||
std::vector<OutAddr> vram_from_out(w * h * 4);
|
||||
|
||||
// loop over pixels in input
|
||||
for (int y = 0; y < h; y++) {
|
||||
for (int x = 0; x < w; x++) {
|
||||
int byte_addr = psmct32_addr(x, y, write_tex_width);
|
||||
for (int c = 0; c < 4; c++) {
|
||||
auto& s = vram_from_in.at(byte_addr + c);
|
||||
s.x = x;
|
||||
s.y = y;
|
||||
s.c = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// output
|
||||
for (int y = 0; y < h * 2; y++) {
|
||||
for (int x = 0; x < w * 2; x++) {
|
||||
int byte_addr = psmt8_addr(x, y, read_tex_width);
|
||||
auto& s = vram_from_out.at(byte_addr);
|
||||
s.x = x;
|
||||
s.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
destinations_per_byte.resize(4 * w * h);
|
||||
for (size_t i = 0; i < vram_from_out.size(); i++) {
|
||||
auto& in = vram_from_in.at(i);
|
||||
auto& out = vram_from_out.at(i);
|
||||
if (in.c >= 0) {
|
||||
destinations_per_byte.at(in.c + in.x * 4 + in.y * 4 * w) = out.x + out.y * w * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextureAnimator::TextureAnimator(ShaderLibrary& shaders, const tfrag3::Level* common_level)
|
||||
: m_common_level(common_level),
|
||||
m_psm32_to_psm8_8_8(8, 8, 8, 64),
|
||||
@@ -232,15 +372,24 @@ TextureAnimator::TextureAnimator(ShaderLibrary& shaders, const tfrag3::Level* co
|
||||
m_uniforms.uvs = glGetUniformLocation(shader.id(), "uvs");
|
||||
m_uniforms.channel_scramble = glGetUniformLocation(shader.id(), "channel_scramble");
|
||||
m_uniforms.tcc = glGetUniformLocation(shader.id(), "tcc");
|
||||
m_uniforms.alpha_multiply = glGetUniformLocation(shader.id(), "alpha_multiply");
|
||||
|
||||
// create a single "dummy texture" with all 0 data.
|
||||
// this is faster and easier than switching shaders to one without texturing, and is used
|
||||
// only rarely
|
||||
glGenTextures(1, &m_dummy_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, m_dummy_texture);
|
||||
std::vector<u8> data(16 * 16 * 4);
|
||||
std::vector<u32> data(16 * 16);
|
||||
u32 c0 = 0xa0303030;
|
||||
u32 c1 = 0xa0e0e0e0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int j = 0; j < 16; j++) {
|
||||
data[i * 16 + j] = (((i / 4) & 1) ^ ((j / 4) & 1)) ? c1 : c0;
|
||||
}
|
||||
}
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||
data.data());
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
shader.activate();
|
||||
@@ -262,51 +411,81 @@ TextureAnimator::TextureAnimator(ShaderLibrary& shaders, const tfrag3::Level* co
|
||||
m_index_to_clut_addr[i] = clx + cly * 16;
|
||||
}
|
||||
|
||||
m_output_slots.resize(jak2_animated_texture_slots().size(), m_dummy_texture);
|
||||
m_public_output_slots.resize(jak2_animated_texture_slots().size(), m_dummy_texture);
|
||||
m_private_output_slots = m_public_output_slots;
|
||||
m_output_debug_flags.resize(jak2_animated_texture_slots().size());
|
||||
|
||||
// DARKJAK
|
||||
m_darkjak_clut_blender_idx = create_clut_blender_group(
|
||||
{"jakbsmall-eyebrow", "jakbsmall-face", "jakbsmall-finger", "jakbsmall-hair"}, "-norm",
|
||||
"-dark", {});
|
||||
// animation-specific stuff
|
||||
setup_texture_anims();
|
||||
}
|
||||
|
||||
// PRISON
|
||||
// MISSING EYELID
|
||||
m_jakb_prison_clut_blender_idx = create_clut_blender_group(
|
||||
{"jak-orig-arm-formorph", "jak-orig-eyebrow-formorph", "jak-orig-finger-formorph"}, "-start",
|
||||
"-end", "LDJAKBRN.DGO");
|
||||
add_to_clut_blender_group(m_jakb_prison_clut_blender_idx,
|
||||
{"jakb-facelft", "jakb-facert", "jakb-hairtrans"}, "-norm", "-dark",
|
||||
"LDJAKBRN.DGO");
|
||||
/*!
|
||||
* Add a fixed texture animator for the given definition. Returns an index that can later be used to
|
||||
* run it.
|
||||
*/
|
||||
int TextureAnimator::create_fixed_anim_array(const std::vector<FixedAnimDef>& defs) {
|
||||
int ret = m_fixed_anim_arrays.size();
|
||||
auto& anim_array = m_fixed_anim_arrays.emplace_back();
|
||||
|
||||
// ORACLE
|
||||
// MISSING FINGER
|
||||
m_jakb_oracle_clut_blender_idx = create_clut_blender_group(
|
||||
{"jakb-eyebrow", "jakb-eyelid", "jakb-facelft", "jakb-facert", "jakb-hairtrans"}, "-norm",
|
||||
"-dark", "ORACLE.DGO");
|
||||
for (const auto& def : defs) {
|
||||
auto& anim = anim_array.anims.emplace_back();
|
||||
anim.def = def;
|
||||
|
||||
// NEST
|
||||
// MISSING FINGER
|
||||
m_jakb_nest_clut_blender_idx = create_clut_blender_group(
|
||||
{"jakb-eyebrow", "jakb-eyelid", "jakb-facelft", "jakb-facert", "jakb-hairtrans"}, "-norm",
|
||||
"-dark", "NEB.DGO");
|
||||
// set up the destination texture.
|
||||
anim.dest_slot = output_slot_by_idx(GameVersion::Jak2, anim.def.tex_name);
|
||||
auto* dtex = tex_by_name(m_common_level, anim.def.tex_name);
|
||||
if (anim.def.override_size) {
|
||||
anim.fbt.emplace(anim.def.override_size->x(), anim.def.override_size->y(),
|
||||
GL_UNSIGNED_INT_8_8_8_8_REV);
|
||||
} else {
|
||||
anim.fbt.emplace(dtex->w, dtex->h, GL_UNSIGNED_INT_8_8_8_8_REV);
|
||||
opengl_upload_texture(anim.fbt->texture(), dtex->data.data(), dtex->w, dtex->h);
|
||||
}
|
||||
|
||||
// KOR (doesn't work??)
|
||||
m_kor_transform_clut_blender_idx = create_clut_blender_group(
|
||||
{
|
||||
// "kor-eyeeffect-formorph",
|
||||
// "kor-hair-formorph",
|
||||
// "kor-head-formorph",
|
||||
// "kor-head-formorph-noreflect",
|
||||
// "kor-lowercaps-formorph",
|
||||
// "kor-uppercaps-formorph",
|
||||
},
|
||||
"-start", "-end", {});
|
||||
m_private_output_slots.at(anim.dest_slot) = anim.fbt->texture();
|
||||
|
||||
// set up the source textures
|
||||
for (const auto& layer : def.layers) {
|
||||
auto* stex = tex_by_name(m_common_level, layer.tex_name);
|
||||
GLint gl_texture = m_opengl_texture_pool.allocate(stex->w, stex->h);
|
||||
anim.src_textures.push_back(gl_texture);
|
||||
opengl_upload_texture(gl_texture, stex->data.data(), stex->w, stex->h);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void TextureAnimator::draw_debug_window() {
|
||||
ImGui::Checkbox("fast-scrambler", &m_debug.use_fast_scrambler);
|
||||
|
||||
auto& slots = jak2_animated_texture_slots();
|
||||
for (size_t i = 0; i < slots.size(); i++) {
|
||||
ImGui::Text("Slot %d %s", (int)i, slots[i].c_str());
|
||||
glBindTexture(GL_TEXTURE_2D, m_private_output_slots[i]);
|
||||
int w, h;
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
|
||||
ImGui::Image((void*)m_private_output_slots[i], ImVec2(w, h));
|
||||
ImGui::Checkbox(fmt::format("mark {}", i).c_str(), &m_output_debug_flags.at(i).b);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
void TextureAnimator::copy_private_to_public() {
|
||||
auto& slots = jak2_animated_texture_slots();
|
||||
for (size_t i = 0; i < slots.size(); i++) {
|
||||
if (m_output_debug_flags[i].b) {
|
||||
m_public_output_slots[i] = m_dummy_texture;
|
||||
} else {
|
||||
m_public_output_slots[i] = m_private_output_slots[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Create a clut-blending animator. Returns an index that can later be used to run it.
|
||||
*/
|
||||
int TextureAnimator::create_clut_blender_group(const std::vector<std::string>& textures,
|
||||
const std::string& suffix0,
|
||||
const std::string& suffix1,
|
||||
@@ -317,6 +496,9 @@ int TextureAnimator::create_clut_blender_group(const std::vector<std::string>& t
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Add a texture to an existing blender group created with create_clut_blender_group.
|
||||
*/
|
||||
void TextureAnimator::add_to_clut_blender_group(int idx,
|
||||
const std::vector<std::string>& textures,
|
||||
const std::string& suffix0,
|
||||
@@ -327,7 +509,7 @@ void TextureAnimator::add_to_clut_blender_group(int idx,
|
||||
grp.blenders.emplace_back(prefix, std::vector<std::string>{prefix + suffix0, prefix + suffix1},
|
||||
dgo, m_common_level, &m_opengl_texture_pool);
|
||||
grp.outputs.push_back(output_slot_by_idx(GameVersion::Jak2, prefix));
|
||||
m_output_slots.at(grp.outputs.back()) = grp.blenders.back().texture();
|
||||
m_private_output_slots.at(grp.outputs.back()) = grp.blenders.back().texture();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,8 +520,8 @@ TextureAnimator::~TextureAnimator() {
|
||||
}
|
||||
|
||||
GLuint TextureAnimator::get_by_slot(int idx) {
|
||||
ASSERT(idx >= 0 && idx < (int)m_output_slots.size());
|
||||
return m_output_slots[idx];
|
||||
ASSERT(idx >= 0 && idx < (int)m_public_output_slots.size());
|
||||
return m_public_output_slots[idx];
|
||||
}
|
||||
|
||||
// IDs sent from GOAL telling us what texture operation to perform.
|
||||
@@ -357,7 +539,10 @@ enum PcTextureAnimCodes {
|
||||
PRISON_JAK = 23,
|
||||
ORACLE_JAK = 24,
|
||||
NEST_JAK = 25,
|
||||
KOR_TRANSFORM = 26
|
||||
KOR_TRANSFORM = 26,
|
||||
SKULL_GEM = 27,
|
||||
BOMB = 28,
|
||||
CAS_CONVEYOR = 29,
|
||||
};
|
||||
|
||||
// metadata for an upload from GOAL memory
|
||||
@@ -466,6 +651,22 @@ void TextureAnimator::handle_texture_anim_data(DmaFollower& dma,
|
||||
auto p = scoped_prof("kor");
|
||||
run_clut_blender_group(tf, m_kor_transform_clut_blender_idx);
|
||||
} break;
|
||||
case SKULL_GEM: {
|
||||
auto p = scoped_prof("skull-gem");
|
||||
ASSERT(tf.size_bytes == 16);
|
||||
const float* floats = (const float*)tf.data;
|
||||
run_fixed_animation_array(m_skull_gem_fixed_anim_array_idx, floats);
|
||||
} break;
|
||||
case BOMB: {
|
||||
auto p = scoped_prof("bomb");
|
||||
ASSERT(tf.size_bytes == 16);
|
||||
const float* floats = (const float*)tf.data;
|
||||
run_fixed_animation_array(m_bomb_fixed_anim_array_idx, floats);
|
||||
} break;
|
||||
case CAS_CONVEYOR: {
|
||||
auto p = scoped_prof("cas-conveyor");
|
||||
run_fixed_animation_array(m_cas_conveyor_anim_array_idx, (const float*)tf.data);
|
||||
} break;
|
||||
default:
|
||||
fmt::print("bad imm: {}\n", vif0.immediate);
|
||||
ASSERT_NOT_REACHED();
|
||||
@@ -541,6 +742,7 @@ void TextureAnimator::handle_texture_anim_data(DmaFollower& dma,
|
||||
glDepthMask(GL_TRUE);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glColorMask(true, true, true, true);
|
||||
copy_private_to_public();
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -644,6 +846,8 @@ void TextureAnimator::handle_rg_to_ba(const DmaTransfer& tf) {
|
||||
glUniform1i(m_uniforms.enable_tex, 1);
|
||||
glUniform4f(m_uniforms.rgba, 256, 256, 256, 128); // TODO - seems wrong.
|
||||
glUniform4i(m_uniforms.channel_scramble, 0, 1, 0, 1);
|
||||
// not sure if this is right or not: the entire cloud stuff is kinda broken.
|
||||
glUniform1f(m_uniforms.alpha_multiply, 1.f);
|
||||
glBindTexture(GL_TEXTURE_2D, src->second.tex.value().texture());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
@@ -679,7 +883,9 @@ void TextureAnimator::handle_rg_to_ba(const DmaTransfer& tf) {
|
||||
}
|
||||
|
||||
void TextureAnimator::handle_set_clut_alpha(const DmaTransfer& tf) {
|
||||
ASSERT_NOT_REACHED();
|
||||
ASSERT_NOT_REACHED(); // TODO: if re-enabling, needs alpha multiplier stuff
|
||||
glUniform1f(m_uniforms.alpha_multiply, 1.f);
|
||||
|
||||
dprintf("[tex anim] set clut alpha\n");
|
||||
ASSERT(tf.size_bytes == sizeof(TextureAnimPcTransform));
|
||||
auto* data = (const TextureAnimPcTransform*)(tf.data);
|
||||
@@ -707,7 +913,9 @@ void TextureAnimator::handle_set_clut_alpha(const DmaTransfer& tf) {
|
||||
}
|
||||
|
||||
void TextureAnimator::handle_copy_clut_alpha(const DmaTransfer& tf) {
|
||||
ASSERT_NOT_REACHED();
|
||||
ASSERT_NOT_REACHED(); // TODO: if re-enabling, needs alpha multiplier stuff
|
||||
glUniform1f(m_uniforms.alpha_multiply, 1.f);
|
||||
|
||||
dprintf("[tex anim] __copy__ clut alpha\n");
|
||||
ASSERT(tf.size_bytes == sizeof(TextureAnimPcTransform));
|
||||
auto* data = (const TextureAnimPcTransform*)(tf.data);
|
||||
@@ -749,7 +957,7 @@ void TextureAnimator::run_clut_blender_group(DmaTransfer& tf, int idx) {
|
||||
float weights[2] = {1.f - f, f};
|
||||
auto& blender = m_clut_blender_groups.at(idx);
|
||||
for (size_t i = 0; i < blender.blenders.size(); i++) {
|
||||
m_output_slots[blender.outputs[i]] = blender.blenders[i].run(weights);
|
||||
m_private_output_slots[blender.outputs[i]] = blender.blenders[i].run(weights);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -899,6 +1107,8 @@ void TextureAnimator::handle_erase_dest(DmaFollower& dma) {
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glColorMask(true, true, true, true);
|
||||
// write the exact specified alpha (texture holds game-style alphas)
|
||||
glUniform1f(m_uniforms.alpha_multiply, 1.f);
|
||||
{
|
||||
auto p = scoped_prof("erase-draw");
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
@@ -1046,14 +1256,27 @@ void TextureAnimator::handle_draw(DmaFollower& dma, TexturePool& texture_pool) {
|
||||
}
|
||||
|
||||
// use ADGIF shader data to set OpenGL state
|
||||
set_up_opengl_for_shader(m_current_shader, gpu_texture, true); // ABE forced on here.
|
||||
bool writes_alpha =
|
||||
set_up_opengl_for_shader(m_current_shader, gpu_texture, true); // ABE forced on here.
|
||||
|
||||
// set up uniform buffers for the coordinates for this draw.
|
||||
set_uniforms_from_draw_data(draw_data, dest_te.tex_width, dest_te.tex_height);
|
||||
|
||||
ASSERT(dest_te.tex);
|
||||
// draw!
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
if (writes_alpha) {
|
||||
glColorMask(true, true, true, false);
|
||||
glUniform1f(m_uniforms.alpha_multiply, 2.f);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
glColorMask(false, false, false, true);
|
||||
glUniform1f(m_uniforms.alpha_multiply, 1.f);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
} else {
|
||||
// we don't write alpha out. So apply alpha multiplier for blending.
|
||||
glUniform1f(m_uniforms.alpha_multiply, 1.f);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
// debug_save_opengl_texture("opengl_draw_result.png", dest_te.tex->texture());
|
||||
// debug_save_opengl_texture("opengl_test.png", gpu_texture);
|
||||
} else {
|
||||
@@ -1226,7 +1449,75 @@ GLuint TextureAnimator::make_or_get_gpu_texture_for_current_shader(TexturePool&
|
||||
}
|
||||
}
|
||||
|
||||
void TextureAnimator::set_up_opengl_for_shader(const ShaderContext& shader,
|
||||
void TextureAnimator::set_up_opengl_for_fixed(const FixedLayerDef& def,
|
||||
std::optional<GLint> texture) {
|
||||
if (texture) {
|
||||
glBindTexture(GL_TEXTURE_2D, *texture);
|
||||
glUniform1i(m_uniforms.enable_tex, 1);
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, m_dummy_texture);
|
||||
glUniform1i(m_uniforms.enable_tex, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
}
|
||||
// tex0
|
||||
// assuming default-texture-anim-layer-func, which sets 1.
|
||||
glUniform1i(m_uniforms.tcc, 1);
|
||||
|
||||
// ASSERT(shader.tex0.tfx() == GsTex0::TextureFunction::MODULATE);
|
||||
// tex1
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
glColorMask(def.channel_masks[0], def.channel_masks[1], def.channel_masks[2],
|
||||
def.channel_masks[3]);
|
||||
if (def.z_test) {
|
||||
ASSERT_NOT_REACHED();
|
||||
} else {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
if (def.clamp_u) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
} else {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
}
|
||||
|
||||
if (def.clamp_v) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
} else {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
}
|
||||
|
||||
if (def.blend_enable) {
|
||||
auto blend_a = def.blend_modes[0];
|
||||
auto blend_b = def.blend_modes[1];
|
||||
auto blend_c = def.blend_modes[2];
|
||||
auto blend_d = def.blend_modes[3];
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
// 0 2 0 1
|
||||
if (blend_a == GsAlpha::BlendMode::SOURCE && blend_b == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
||||
blend_c == GsAlpha::BlendMode::SOURCE && blend_d == GsAlpha::BlendMode::DEST) {
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
|
||||
} else if (blend_a == GsAlpha::BlendMode::SOURCE && blend_b == GsAlpha::BlendMode::DEST &&
|
||||
blend_c == GsAlpha::BlendMode::SOURCE && blend_d == GsAlpha::BlendMode::DEST) {
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
|
||||
} else {
|
||||
fmt::print("unhandled blend: {} {} {} {}\n", (int)blend_a, (int)blend_b, (int)blend_c,
|
||||
(int)blend_d);
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
} else {
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
glUniform4i(m_uniforms.channel_scramble, 0, 1, 2, 3);
|
||||
}
|
||||
|
||||
bool TextureAnimator::set_up_opengl_for_shader(const ShaderContext& shader,
|
||||
std::optional<GLuint> texture,
|
||||
bool prim_abe) {
|
||||
if (texture) {
|
||||
@@ -1290,7 +1581,9 @@ void TextureAnimator::set_up_opengl_for_shader(const ShaderContext& shader,
|
||||
do_alpha_test = false;
|
||||
}
|
||||
|
||||
bool writes_alpha = true;
|
||||
if (alpha_test_mask_alpha_trick) {
|
||||
writes_alpha = false;
|
||||
glColorMask(true, true, true, false);
|
||||
} else {
|
||||
glColorMask(true, true, true, true);
|
||||
@@ -1351,6 +1644,7 @@ void TextureAnimator::set_up_opengl_for_shader(const ShaderContext& shader,
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
glUniform4i(m_uniforms.channel_scramble, 0, 1, 2, 3);
|
||||
return writes_alpha;
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -1394,3 +1688,367 @@ void TextureAnimator::set_uniforms_from_draw_data(const DrawData& dd, int dest_w
|
||||
// fmt::print("fan vt {}: {:.3f} {:.3f} \n", i, uv[i * 2], uv[1 + i * 2]);
|
||||
// }
|
||||
}
|
||||
|
||||
void TextureAnimator::run_fixed_animation_array(int idx, const float* times) {
|
||||
auto& array = m_fixed_anim_arrays.at(idx);
|
||||
for (size_t i = 0; i < array.anims.size(); i++) {
|
||||
auto& anim = array.anims[i];
|
||||
run_fixed_animation(anim, times[i]);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void interpolate_1(float interp, T* out, const T& in_start, const T& in_end) {
|
||||
*out = in_start + (in_end - in_start) * interp;
|
||||
}
|
||||
|
||||
void interpolate_layer_values(float interp,
|
||||
LayerVals* out,
|
||||
const LayerVals& start,
|
||||
const LayerVals& end) {
|
||||
interpolate_1(interp, &out->color, start.color, end.color);
|
||||
interpolate_1(interp, &out->scale, start.scale, end.scale);
|
||||
interpolate_1(interp, &out->offset, start.offset, end.offset);
|
||||
interpolate_1(interp, &out->st_scale, start.st_scale, end.st_scale);
|
||||
interpolate_1(interp, &out->st_offset, start.st_offset, end.st_offset);
|
||||
interpolate_1(interp, &out->qs, start.qs, end.qs);
|
||||
}
|
||||
|
||||
void TextureAnimator::set_draw_data_from_interpolated(DrawData* result,
|
||||
const LayerVals& vals,
|
||||
int w,
|
||||
int h) {
|
||||
result->color = (vals.color * 128.f).cast<u32>();
|
||||
math::Vector2f pos_scale(vals.scale.x() * w, vals.scale.y() * h);
|
||||
math::Vector2f pos_offset(2048.f + (vals.offset.x() * w), 2048.f + (vals.offset.y() * h));
|
||||
math::Vector2f st_scale = vals.st_scale;
|
||||
math::Vector2f st_offset = vals.st_offset;
|
||||
const math::Vector2f corners[4] = {math::Vector2f{-0.5, -0.5}, math::Vector2f{0.5, -0.5},
|
||||
math::Vector2f{-0.5, 0.5}, math::Vector2f{0.5, 0.5}};
|
||||
math::Vector2f sts[4];
|
||||
math::Vector2<u32> poss[4];
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
sts[i] = corners[i].elementwise_multiply(st_scale) + st_offset;
|
||||
poss[i] = ((corners[i].elementwise_multiply(pos_scale) + pos_offset) * 16.f).cast<u32>();
|
||||
}
|
||||
|
||||
result->st0.x() = sts[0].x();
|
||||
result->st0.y() = sts[0].y();
|
||||
result->st1.x() = sts[1].x();
|
||||
result->st1.y() = sts[1].y();
|
||||
result->st2.x() = sts[2].x();
|
||||
result->st2.y() = sts[2].y();
|
||||
result->st3.x() = sts[3].x();
|
||||
result->st3.y() = sts[3].y();
|
||||
|
||||
result->pos0.x() = poss[0].x();
|
||||
result->pos0.y() = poss[0].y();
|
||||
result->pos1.x() = poss[1].x();
|
||||
result->pos1.y() = poss[1].y();
|
||||
result->pos2.x() = poss[2].x();
|
||||
result->pos2.y() = poss[2].y();
|
||||
result->pos3.x() = poss[3].x();
|
||||
result->pos3.y() = poss[3].y();
|
||||
}
|
||||
|
||||
void TextureAnimator::run_fixed_animation(FixedAnim& anim, float time) {
|
||||
{
|
||||
FramebufferTexturePairContext ctxt(anim.fbt.value());
|
||||
// Clear
|
||||
{
|
||||
float positions[3 * 4] = {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0};
|
||||
glUniform3fv(m_uniforms.positions, 4, positions);
|
||||
glUniform1i(m_uniforms.enable_tex, 0);
|
||||
glUniform4f(m_uniforms.rgba, anim.def.color[0], anim.def.color[1], anim.def.color[2],
|
||||
anim.def.color[3]);
|
||||
glUniform4i(m_uniforms.channel_scramble, 0, 1, 2, 3);
|
||||
glBindTexture(GL_TEXTURE_2D, m_dummy_texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glColorMask(true, true, true, true);
|
||||
glUniform1f(m_uniforms.alpha_multiply, 1.f);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
}
|
||||
|
||||
LayerVals interpolated_values;
|
||||
DrawData draw_data;
|
||||
|
||||
// Loop over layers
|
||||
for (size_t layer_idx = 0; layer_idx < anim.def.layers.size(); layer_idx++) {
|
||||
auto& layer_def = anim.def.layers[layer_idx];
|
||||
// skip layer if out the range when it is active
|
||||
if (time < layer_def.start_time || time > layer_def.end_time) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// interpolate
|
||||
interpolate_layer_values(
|
||||
(time - layer_def.start_time) / (layer_def.end_time - layer_def.start_time),
|
||||
&interpolated_values, layer_def.start_vals, layer_def.end_vals);
|
||||
|
||||
// shader setup
|
||||
set_up_opengl_for_fixed(layer_def, anim.src_textures.at(layer_idx));
|
||||
|
||||
set_draw_data_from_interpolated(&draw_data, interpolated_values, anim.fbt->width(),
|
||||
anim.fbt->height());
|
||||
set_uniforms_from_draw_data(draw_data, anim.fbt->width(), anim.fbt->height());
|
||||
|
||||
if (true) { // todo
|
||||
glColorMask(true, true, true, false);
|
||||
glUniform1f(m_uniforms.alpha_multiply, 2.f);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
glColorMask(false, false, false, true);
|
||||
glUniform1f(m_uniforms.alpha_multiply, 1.f);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
} else {
|
||||
// we don't write alpha out. So apply alpha multiplier for blending.
|
||||
glUniform1f(m_uniforms.alpha_multiply, 2.f);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finish
|
||||
m_private_output_slots.at(anim.dest_slot) = anim.fbt->texture();
|
||||
glBindTexture(GL_TEXTURE_2D, anim.fbt->texture());
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
void TextureAnimator::setup_texture_anims() {
|
||||
// DARKJAK
|
||||
m_darkjak_clut_blender_idx = create_clut_blender_group(
|
||||
{"jakbsmall-eyebrow", "jakbsmall-face", "jakbsmall-finger", "jakbsmall-hair"}, "-norm",
|
||||
"-dark", {});
|
||||
|
||||
// PRISON
|
||||
// MISSING EYELID
|
||||
m_jakb_prison_clut_blender_idx = create_clut_blender_group(
|
||||
{"jak-orig-arm-formorph", "jak-orig-eyebrow-formorph", "jak-orig-finger-formorph"}, "-start",
|
||||
"-end", "LDJAKBRN.DGO");
|
||||
add_to_clut_blender_group(m_jakb_prison_clut_blender_idx,
|
||||
{"jakb-facelft", "jakb-facert", "jakb-hairtrans"}, "-norm", "-dark",
|
||||
"LDJAKBRN.DGO");
|
||||
|
||||
// ORACLE
|
||||
// MISSING FINGER
|
||||
m_jakb_oracle_clut_blender_idx = create_clut_blender_group(
|
||||
{"jakb-eyebrow", "jakb-eyelid", "jakb-facelft", "jakb-facert", "jakb-hairtrans"}, "-norm",
|
||||
"-dark", "ORACLE.DGO");
|
||||
|
||||
// NEST
|
||||
// MISSING FINGER
|
||||
m_jakb_nest_clut_blender_idx = create_clut_blender_group(
|
||||
{"jakb-eyebrow", "jakb-eyelid", "jakb-facelft", "jakb-facert", "jakb-hairtrans"}, "-norm",
|
||||
"-dark", "NEB.DGO");
|
||||
|
||||
// KOR (doesn't work??)
|
||||
m_kor_transform_clut_blender_idx = create_clut_blender_group(
|
||||
{
|
||||
// "kor-eyeeffect-formorph",
|
||||
// "kor-hair-formorph",
|
||||
// "kor-head-formorph",
|
||||
// "kor-head-formorph-noreflect",
|
||||
// "kor-lowercaps-formorph",
|
||||
// "kor-uppercaps-formorph",
|
||||
},
|
||||
"-start", "-end", {});
|
||||
|
||||
// Skull Gem
|
||||
{
|
||||
FixedAnimDef skull_gem;
|
||||
skull_gem.tex_name = "skull-gem-dest";
|
||||
skull_gem.color = math::Vector4<u8>{0, 0, 0, 0x80};
|
||||
|
||||
auto& skull_gem_0 = skull_gem.layers.emplace_back();
|
||||
skull_gem_0.end_time = 300.;
|
||||
skull_gem_0.tex_name = "skull-gem-alpha-00";
|
||||
skull_gem_0.set_blend_b2_d1();
|
||||
skull_gem_0.set_no_z_write_no_z_test();
|
||||
skull_gem_0.start_vals.color = math::Vector4f{1, 1, 1, 1};
|
||||
skull_gem_0.start_vals.scale = math::Vector2f{1, 1};
|
||||
skull_gem_0.start_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
skull_gem_0.start_vals.st_scale = math::Vector2f{1, 1};
|
||||
skull_gem_0.start_vals.st_offset = math::Vector2f{0.5, 0.0};
|
||||
skull_gem_0.end_vals.color = math::Vector4f{1, 1, 1, 1};
|
||||
skull_gem_0.end_vals.scale = math::Vector2f{1, 1};
|
||||
skull_gem_0.end_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
skull_gem_0.end_vals.st_scale = math::Vector2f{1, 1};
|
||||
skull_gem_0.end_vals.st_offset = math::Vector2f{0.5, 1.0};
|
||||
|
||||
auto& skull_gem_1 = skull_gem.layers.emplace_back();
|
||||
skull_gem_1.end_time = 300.;
|
||||
skull_gem_1.tex_name = "skull-gem-alpha-01";
|
||||
skull_gem_1.set_blend_b2_d1();
|
||||
skull_gem_1.set_no_z_write_no_z_test();
|
||||
skull_gem_1.start_vals.color = math::Vector4f{0.6, 0.6, 0.6, 1};
|
||||
skull_gem_1.start_vals.scale = math::Vector2f{1, 1};
|
||||
skull_gem_1.start_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
skull_gem_1.start_vals.st_scale = math::Vector2f{1, 1};
|
||||
skull_gem_1.start_vals.st_offset = math::Vector2f{2.0, 1.0};
|
||||
skull_gem_1.end_vals.color = math::Vector4f{0.6, 0.5, 0.6, 1};
|
||||
skull_gem_1.end_vals.scale = math::Vector2f{1, 1};
|
||||
skull_gem_1.end_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
skull_gem_1.end_vals.st_scale = math::Vector2f{1, 1};
|
||||
skull_gem_1.end_vals.st_offset = math::Vector2f{0, 0};
|
||||
|
||||
auto& skull_gem_2 = skull_gem.layers.emplace_back();
|
||||
skull_gem_2.end_time = 300.;
|
||||
skull_gem_2.tex_name = "skull-gem-alpha-02";
|
||||
skull_gem_2.set_blend_b2_d1();
|
||||
skull_gem_2.set_no_z_write_no_z_test();
|
||||
skull_gem_2.start_vals.color = math::Vector4f{0.6, 0.6, 0.6, 1};
|
||||
skull_gem_2.start_vals.scale = math::Vector2f{1, 1};
|
||||
skull_gem_2.start_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
skull_gem_2.start_vals.st_scale = math::Vector2f{1, 1};
|
||||
skull_gem_2.start_vals.st_offset = math::Vector2f{0, 0};
|
||||
skull_gem_2.end_vals.color = math::Vector4f{0.6, 0.6, 0.6, 1};
|
||||
skull_gem_2.end_vals.scale = math::Vector2f{1, 1};
|
||||
skull_gem_2.end_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
skull_gem_2.end_vals.st_scale = math::Vector2f{1, 1};
|
||||
skull_gem_2.end_vals.st_offset = math::Vector2f{2, 2};
|
||||
|
||||
m_skull_gem_fixed_anim_array_idx = create_fixed_anim_array({skull_gem});
|
||||
}
|
||||
|
||||
// Bomb
|
||||
{
|
||||
FixedAnimDef bomb;
|
||||
bomb.tex_name = "bomb-gradient";
|
||||
bomb.color = math::Vector4<u8>{0, 0, 0, 0x80};
|
||||
|
||||
auto& bomb_0 = bomb.layers.emplace_back();
|
||||
bomb_0.end_time = 300.;
|
||||
bomb_0.tex_name = "bomb-gradient-rim";
|
||||
bomb_0.set_blend_b2_d1();
|
||||
bomb_0.set_no_z_write_no_z_test();
|
||||
// :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always))
|
||||
bomb_0.channel_masks[3] = false; // no alpha writes.
|
||||
bomb_0.start_vals.color = math::Vector4f{1, 1, 1, 1};
|
||||
bomb_0.start_vals.scale = math::Vector2f{1, 1};
|
||||
bomb_0.start_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
bomb_0.start_vals.st_scale = math::Vector2f{1, 1};
|
||||
bomb_0.start_vals.st_offset = math::Vector2f{0.5, 0.0};
|
||||
bomb_0.end_vals.color = math::Vector4f{1, 1, 1, 1};
|
||||
bomb_0.end_vals.scale = math::Vector2f{1, 1};
|
||||
bomb_0.end_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
bomb_0.end_vals.st_scale = math::Vector2f{1, 1};
|
||||
bomb_0.end_vals.st_offset = math::Vector2f{0.5, 4.0};
|
||||
|
||||
auto& bomb_1 = bomb.layers.emplace_back();
|
||||
bomb_1.end_time = 300.;
|
||||
bomb_1.tex_name = "bomb-gradient-flames";
|
||||
bomb_1.set_blend_b2_d1();
|
||||
bomb_1.set_no_z_write_no_z_test();
|
||||
// :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always))
|
||||
bomb_1.channel_masks[3] = false; // no alpha writes.
|
||||
bomb_1.start_vals.color = math::Vector4f{1, 1, 1, 1};
|
||||
bomb_1.start_vals.scale = math::Vector2f{1, 1};
|
||||
bomb_1.start_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
bomb_1.start_vals.st_scale = math::Vector2f{1, 1};
|
||||
bomb_1.start_vals.st_offset = math::Vector2f{0.0, 0.5};
|
||||
bomb_1.end_vals.color = math::Vector4f{1, 1, 1, 1};
|
||||
bomb_1.end_vals.scale = math::Vector2f{1, 1};
|
||||
bomb_1.end_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
bomb_1.end_vals.st_scale = math::Vector2f{1, 1};
|
||||
bomb_1.end_vals.st_offset = math::Vector2f{-6.0, 0.5};
|
||||
|
||||
m_bomb_fixed_anim_array_idx = create_fixed_anim_array({bomb});
|
||||
}
|
||||
|
||||
// CAS conveyor
|
||||
{
|
||||
FixedAnimDef conveyor_0;
|
||||
conveyor_0.tex_name = "cas-conveyor-dest";
|
||||
conveyor_0.color = math::Vector4<u8>(0, 0, 0, 0x80);
|
||||
conveyor_0.override_size = math::Vector2<int>(64, 32);
|
||||
auto& c0 = conveyor_0.layers.emplace_back();
|
||||
c0.set_blend_b2_d1();
|
||||
c0.set_no_z_write_no_z_test();
|
||||
// :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always))
|
||||
c0.channel_masks[3] = false; // no alpha writes.
|
||||
c0.end_time = 300.;
|
||||
c0.tex_name = "cas-conveyor";
|
||||
c0.start_vals.color = math::Vector4f{1, 1, 1, 1};
|
||||
c0.start_vals.scale = math::Vector2f{1, 1};
|
||||
c0.start_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
c0.start_vals.st_scale = math::Vector2f{1, 1};
|
||||
c0.start_vals.st_offset = math::Vector2f{0.5, 0.0};
|
||||
c0.end_vals.color = math::Vector4f{1, 1, 1, 1};
|
||||
c0.end_vals.scale = math::Vector2f{1, 1};
|
||||
c0.end_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
c0.end_vals.st_scale = math::Vector2f{1, 1};
|
||||
c0.end_vals.st_offset = math::Vector2f{0.5, 1.0};
|
||||
|
||||
FixedAnimDef conveyor_1;
|
||||
conveyor_1.tex_name = "cas-conveyor-dest-01";
|
||||
conveyor_1.color = math::Vector4<u8>(0, 0, 0, 0x80);
|
||||
conveyor_1.override_size = math::Vector2<int>(64, 32);
|
||||
auto& c1 = conveyor_1.layers.emplace_back();
|
||||
c1.set_blend_b2_d1();
|
||||
c1.set_no_z_write_no_z_test();
|
||||
// :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always))
|
||||
c1.channel_masks[3] = false; // no alpha writes.
|
||||
c1.end_time = 300.;
|
||||
c1.tex_name = "cas-conveyor";
|
||||
c1.start_vals.color = math::Vector4f{1, 1, 1, 1};
|
||||
c1.start_vals.scale = math::Vector2f{1, 1};
|
||||
c1.start_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
c1.start_vals.st_scale = math::Vector2f{1, 1};
|
||||
c1.start_vals.st_offset = math::Vector2f{0.5, 0.0};
|
||||
c1.end_vals.color = math::Vector4f{1, 1, 1, 1};
|
||||
c1.end_vals.scale = math::Vector2f{1, 1};
|
||||
c1.end_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
c1.end_vals.st_scale = math::Vector2f{1, 1};
|
||||
c1.end_vals.st_offset = math::Vector2f{0.5, 1.0};
|
||||
|
||||
FixedAnimDef conveyor_2;
|
||||
conveyor_2.tex_name = "cas-conveyor-dest-02";
|
||||
conveyor_2.color = math::Vector4<u8>(0, 0, 0, 0x80);
|
||||
conveyor_2.override_size = math::Vector2<int>(64, 32);
|
||||
auto& c2 = conveyor_2.layers.emplace_back();
|
||||
c2.set_blend_b2_d1();
|
||||
c2.set_no_z_write_no_z_test();
|
||||
// :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always))
|
||||
c2.channel_masks[3] = false; // no alpha writes.
|
||||
c2.end_time = 300.;
|
||||
c2.tex_name = "cas-conveyor";
|
||||
c2.start_vals.color = math::Vector4f{1, 1, 1, 1};
|
||||
c2.start_vals.scale = math::Vector2f{1, 1};
|
||||
c2.start_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
c2.start_vals.st_scale = math::Vector2f{1, 1};
|
||||
c2.start_vals.st_offset = math::Vector2f{0.5, 0.0};
|
||||
c2.end_vals.color = math::Vector4f{1, 1, 1, 1};
|
||||
c2.end_vals.scale = math::Vector2f{1, 1};
|
||||
c2.end_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
c2.end_vals.st_scale = math::Vector2f{1, 1};
|
||||
c2.end_vals.st_offset = math::Vector2f{0.5, 1.0};
|
||||
|
||||
FixedAnimDef conveyor_3;
|
||||
conveyor_3.tex_name = "cas-conveyor-dest-03";
|
||||
conveyor_3.color = math::Vector4<u8>(0, 0, 0, 0x80);
|
||||
conveyor_3.override_size = math::Vector2<int>(64, 32);
|
||||
auto& c3 = conveyor_3.layers.emplace_back();
|
||||
c3.set_blend_b2_d1();
|
||||
c3.set_no_z_write_no_z_test();
|
||||
// :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always))
|
||||
c3.channel_masks[3] = false; // no alpha writes.
|
||||
c3.end_time = 300.;
|
||||
c3.tex_name = "cas-conveyor";
|
||||
c3.start_vals.color = math::Vector4f{1, 1, 1, 1};
|
||||
c3.start_vals.scale = math::Vector2f{1, 1};
|
||||
c3.start_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
c3.start_vals.st_scale = math::Vector2f{1, 1};
|
||||
c3.start_vals.st_offset = math::Vector2f{0.5, 0.0};
|
||||
c3.end_vals.color = math::Vector4f{1, 1, 1, 1};
|
||||
c3.end_vals.scale = math::Vector2f{1, 1};
|
||||
c3.end_vals.offset = math::Vector2f{0.5, 0.5};
|
||||
c3.end_vals.st_scale = math::Vector2f{1, 1};
|
||||
c3.end_vals.st_offset = math::Vector2f{0.5, 1.0};
|
||||
|
||||
m_cas_conveyor_anim_array_idx =
|
||||
create_fixed_anim_array({conveyor_0, conveyor_1, conveyor_2, conveyor_3});
|
||||
}
|
||||
}
|
||||
@@ -82,50 +82,7 @@ class ClutBlender {
|
||||
};
|
||||
|
||||
struct Psm32ToPsm8Scrambler {
|
||||
Psm32ToPsm8Scrambler(int w, int h, int write_tex_width, int read_tex_width) {
|
||||
struct InAddr {
|
||||
int x = -1, y = -1, c = -1;
|
||||
};
|
||||
struct OutAddr {
|
||||
int x = -1, y = -1;
|
||||
};
|
||||
|
||||
std::vector<InAddr> vram_from_in(w * h * 4);
|
||||
std::vector<OutAddr> vram_from_out(w * h * 4);
|
||||
|
||||
// loop over pixels in input
|
||||
for (int y = 0; y < h; y++) {
|
||||
for (int x = 0; x < w; x++) {
|
||||
int byte_addr = psmct32_addr(x, y, write_tex_width);
|
||||
for (int c = 0; c < 4; c++) {
|
||||
auto& s = vram_from_in.at(byte_addr + c);
|
||||
s.x = x;
|
||||
s.y = y;
|
||||
s.c = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// output
|
||||
for (int y = 0; y < h * 2; y++) {
|
||||
for (int x = 0; x < w * 2; x++) {
|
||||
int byte_addr = psmt8_addr(x, y, read_tex_width);
|
||||
auto& s = vram_from_out.at(byte_addr);
|
||||
s.x = x;
|
||||
s.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
destinations_per_byte.resize(4 * w * h);
|
||||
for (size_t i = 0; i < vram_from_out.size(); i++) {
|
||||
auto& in = vram_from_in.at(i);
|
||||
auto& out = vram_from_out.at(i);
|
||||
if (in.c >= 0) {
|
||||
destinations_per_byte.at(in.c + in.x * 4 + in.y * 4 * w) = out.x + out.y * w * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Psm32ToPsm8Scrambler(int w, int h, int write_tex_width, int read_tex_width);
|
||||
std::vector<int> destinations_per_byte;
|
||||
};
|
||||
|
||||
@@ -154,6 +111,70 @@ struct ClutReader {
|
||||
}
|
||||
};
|
||||
|
||||
struct LayerVals {
|
||||
math::Vector4f color = math::Vector4f::zero();
|
||||
math::Vector2f scale = math::Vector2f::zero();
|
||||
math::Vector2f offset = math::Vector2f::zero();
|
||||
math::Vector2f st_scale = math::Vector2f::zero();
|
||||
math::Vector2f st_offset = math::Vector2f::zero();
|
||||
math::Vector4f qs = math::Vector4f(1, 1, 1, 1);
|
||||
};
|
||||
|
||||
/*!
|
||||
* A single layer in a FixedAnimationDef.
|
||||
*/
|
||||
struct FixedLayerDef {
|
||||
enum class Kind {
|
||||
DEFAULT_ANIM_LAYER,
|
||||
} kind = Kind::DEFAULT_ANIM_LAYER;
|
||||
float start_time = 0;
|
||||
float end_time = 0;
|
||||
std::string tex_name;
|
||||
bool z_writes = false;
|
||||
bool z_test = false;
|
||||
bool clamp_u = false;
|
||||
bool clamp_v = false;
|
||||
bool blend_enable = true;
|
||||
bool channel_masks[4] = {true, true, true, true};
|
||||
GsAlpha::BlendMode blend_modes[4]; // abcd
|
||||
u8 blend_fix = 0;
|
||||
LayerVals start_vals, end_vals;
|
||||
|
||||
void set_blend_b2_d1() {
|
||||
blend_modes[0] = GsAlpha::BlendMode::SOURCE;
|
||||
blend_modes[1] = GsAlpha::BlendMode::ZERO_OR_FIXED;
|
||||
blend_modes[2] = GsAlpha::BlendMode::SOURCE;
|
||||
blend_modes[3] = GsAlpha::BlendMode::DEST;
|
||||
blend_fix = 0;
|
||||
}
|
||||
|
||||
void set_no_z_write_no_z_test() {
|
||||
z_writes = false;
|
||||
z_test = false;
|
||||
}
|
||||
};
|
||||
|
||||
struct FixedAnimDef {
|
||||
math::Vector4<u8> color; // clear color
|
||||
std::string tex_name;
|
||||
std::optional<math::Vector2<int>> override_size;
|
||||
// assuming (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always))
|
||||
// alpha blend off, so alpha doesn't matter i think.
|
||||
std::vector<FixedLayerDef> layers;
|
||||
};
|
||||
|
||||
struct FixedAnim {
|
||||
FixedAnimDef def;
|
||||
// GLint dest_texture;
|
||||
std::optional<FramebufferTexturePair> fbt;
|
||||
int dest_slot;
|
||||
std::vector<GLint> src_textures;
|
||||
};
|
||||
|
||||
struct FixedAnimArray {
|
||||
std::vector<FixedAnim> anims;
|
||||
};
|
||||
|
||||
class TexturePool;
|
||||
|
||||
class TextureAnimator {
|
||||
@@ -163,9 +184,11 @@ class TextureAnimator {
|
||||
void handle_texture_anim_data(DmaFollower& dma, const u8* ee_mem, TexturePool* texture_pool);
|
||||
GLuint get_by_slot(int idx);
|
||||
void draw_debug_window();
|
||||
const std::vector<GLuint>* slots() { return &m_output_slots; }
|
||||
const std::vector<GLuint>* slots() { return &m_public_output_slots; }
|
||||
|
||||
private:
|
||||
void copy_private_to_public();
|
||||
void setup_texture_anims();
|
||||
void handle_upload_clut_16_16(const DmaTransfer& tf, const u8* ee_mem);
|
||||
void handle_generic_upload(const DmaTransfer& tf, const u8* ee_mem);
|
||||
void handle_erase_dest(DmaFollower& dma);
|
||||
@@ -177,9 +200,10 @@ class TextureAnimator {
|
||||
|
||||
VramEntry* setup_vram_entry_for_gpu_texture(int w, int h, int tbp);
|
||||
|
||||
void set_up_opengl_for_shader(const ShaderContext& shader,
|
||||
bool set_up_opengl_for_shader(const ShaderContext& shader,
|
||||
std::optional<GLuint> texture,
|
||||
bool prim_abe);
|
||||
void set_up_opengl_for_fixed(const FixedLayerDef& def, std::optional<GLint> texture);
|
||||
|
||||
void load_clut_to_converter();
|
||||
const u32* get_clut_16_16_psm32(int cbp);
|
||||
@@ -189,6 +213,10 @@ class TextureAnimator {
|
||||
GLuint make_or_get_gpu_texture_for_current_shader(TexturePool& texture_pool);
|
||||
void force_to_gpu(int tbp);
|
||||
|
||||
int create_fixed_anim_array(const std::vector<FixedAnimDef>& defs);
|
||||
void run_fixed_animation_array(int idx, const float* times);
|
||||
void run_fixed_animation(FixedAnim& anim, float time);
|
||||
|
||||
struct DrawData {
|
||||
u8 tmpl1[16];
|
||||
math::Vector<u32, 4> color;
|
||||
@@ -207,6 +235,7 @@ class TextureAnimator {
|
||||
};
|
||||
|
||||
void set_uniforms_from_draw_data(const DrawData& dd, int dest_w, int dest_h);
|
||||
void set_draw_data_from_interpolated(DrawData* result, const LayerVals& vals, int w, int h);
|
||||
|
||||
PcTextureId get_id_for_tbp(TexturePool* pool, u32 tbp);
|
||||
|
||||
@@ -243,6 +272,7 @@ class TextureAnimator {
|
||||
GLuint enable_tex;
|
||||
GLuint channel_scramble;
|
||||
GLuint tcc;
|
||||
GLuint alpha_multiply;
|
||||
} m_uniforms;
|
||||
|
||||
struct {
|
||||
@@ -255,7 +285,13 @@ class TextureAnimator {
|
||||
u8 m_index_to_clut_addr[256];
|
||||
OpenGLTexturePool m_opengl_texture_pool;
|
||||
|
||||
std::vector<GLuint> m_output_slots;
|
||||
std::vector<GLuint> m_private_output_slots;
|
||||
std::vector<GLuint> m_public_output_slots;
|
||||
|
||||
struct Bool {
|
||||
bool b = false;
|
||||
};
|
||||
std::vector<Bool> m_output_debug_flags;
|
||||
|
||||
struct ClutBlenderGroup {
|
||||
std::vector<ClutBlender> blenders;
|
||||
@@ -283,4 +319,9 @@ class TextureAnimator {
|
||||
Psm32ToPsm8Scrambler m_psm32_to_psm8_8_8, m_psm32_to_psm8_16_16, m_psm32_to_psm8_32_32,
|
||||
m_psm32_to_psm8_64_64;
|
||||
ClutReader m_clut_table;
|
||||
|
||||
int m_skull_gem_fixed_anim_array_idx = -1;
|
||||
int m_bomb_fixed_anim_array_idx = -1;
|
||||
int m_cas_conveyor_anim_array_idx = -1;
|
||||
std::vector<FixedAnimArray> m_fixed_anim_arrays;
|
||||
};
|
||||
|
||||
@@ -20,16 +20,52 @@ TFragment::TFragment(const std::string& name,
|
||||
int my_id,
|
||||
const std::vector<tfrag3::TFragmentTreeKind>& trees,
|
||||
bool child_mode,
|
||||
int level_id)
|
||||
int level_id,
|
||||
const std::vector<GLuint>* anim_slot_array)
|
||||
: BucketRenderer(name, my_id),
|
||||
m_child_mode(child_mode),
|
||||
m_tree_kinds(trees),
|
||||
m_level_id(level_id) {
|
||||
m_level_id(level_id),
|
||||
m_anim_slot_array(anim_slot_array) {
|
||||
for (auto& buf : m_buffered_data) {
|
||||
for (auto& x : buf.pad) {
|
||||
x = 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
glGenVertexArrays(1, &m_debug_vao);
|
||||
glBindVertexArray(m_debug_vao);
|
||||
glGenBuffers(1, &m_debug_verts);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_debug_verts);
|
||||
glBufferData(GL_ARRAY_BUFFER, DEBUG_TRI_COUNT * 3 * sizeof(DebugVertex), nullptr,
|
||||
GL_DYNAMIC_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(0, // location 0 in the shader
|
||||
3, // 3 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(DebugVertex), // stride
|
||||
(void*)offsetof(DebugVertex, position) // offset (0)
|
||||
);
|
||||
|
||||
glVertexAttribPointer(1, // location 1 in the shader
|
||||
4, // 4 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(DebugVertex), // stride
|
||||
(void*)offsetof(DebugVertex, rgba) // offset (0)
|
||||
);
|
||||
glBindVertexArray(0);
|
||||
// regardless of how many we use some fixed max
|
||||
// we won't actually interp or upload to gpu the unused ones, but we need a fixed maximum so
|
||||
// indexing works properly.
|
||||
m_color_result.resize(TIME_OF_DAY_COLOR_COUNT);
|
||||
}
|
||||
|
||||
TFragment::~TFragment() {
|
||||
discard_tree_cache();
|
||||
glDeleteVertexArrays(1, &m_debug_vao);
|
||||
}
|
||||
|
||||
void TFragment::render(DmaFollower& dma,
|
||||
@@ -113,7 +149,7 @@ void TFragment::render(DmaFollower& dma,
|
||||
return;
|
||||
}
|
||||
{
|
||||
m_tfrag3.setup_for_level(m_tree_kinds, level_name, render_state);
|
||||
setup_for_level(m_tree_kinds, level_name, render_state);
|
||||
TfragRenderSettings settings;
|
||||
settings.hvdf_offset = m_tfrag_data.hvdf_offset;
|
||||
settings.fog = m_tfrag_data.fog;
|
||||
@@ -132,7 +168,7 @@ void TFragment::render(DmaFollower& dma,
|
||||
}
|
||||
|
||||
auto t3prof = prof.make_scoped_child("t3");
|
||||
m_tfrag3.render_matching_trees(m_tfrag3.lod(), m_tree_kinds, settings, render_state, t3prof);
|
||||
render_matching_trees(lod(), m_tree_kinds, settings, render_state, t3prof);
|
||||
}
|
||||
|
||||
while (dma.current_tag_offset() != render_state->next_bucket) {
|
||||
@@ -142,11 +178,33 @@ void TFragment::render(DmaFollower& dma,
|
||||
}
|
||||
|
||||
void TFragment::draw_debug_window() {
|
||||
m_tfrag3.draw_debug_window();
|
||||
for (int i = 0; i < (int)m_cached_trees.at(lod()).size(); i++) {
|
||||
auto& tree = m_cached_trees.at(lod()).at(i);
|
||||
if (tree.kind == tfrag3::TFragmentTreeKind::INVALID) {
|
||||
continue;
|
||||
}
|
||||
ImGui::PushID(i);
|
||||
ImGui::Text("[%d] %10s", i, tfrag3::tfrag_tree_names[(int)m_cached_trees[lod()][i].kind]);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Allow?", &tree.allowed);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Force?", &tree.forced);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("cull debug (slow)", &tree.cull_debug);
|
||||
ImGui::PopID();
|
||||
if (tree.rendered_this_frame) {
|
||||
ImGui::Checkbox("freeze itimes", &tree.freeze_itimes);
|
||||
ImGui::Text(" tris: %d draws: %d", tree.tris_this_frame, tree.draws_this_frame);
|
||||
for (int j = 0; j < 4; j++) {
|
||||
ImGui::Text(" itimes[%d] 0x%x 0x%x 0x%x 0x%x", j, tree.itimes_debug[j][0],
|
||||
tree.itimes_debug[j][1], tree.itimes_debug[j][2], tree.itimes_debug[j][3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TFragment::init_shaders(ShaderLibrary& shaders) {
|
||||
m_tfrag3.init_shaders(shaders);
|
||||
m_uniforms.decal = glGetUniformLocation(shaders[ShaderId::TFRAG3].id(), "decal");
|
||||
}
|
||||
|
||||
void TFragment::handle_initialization(DmaFollower& dma) {
|
||||
@@ -209,3 +267,457 @@ std::string TFragData::print() const {
|
||||
result += fmt::format("k1s[1]: {}\n", k1s[1].to_string_aligned());
|
||||
return result;
|
||||
}
|
||||
|
||||
void TFragment::update_load(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const LevelData* loader_data) {
|
||||
const auto* lev_data = loader_data->level.get();
|
||||
discard_tree_cache();
|
||||
for (int geom = 0; geom < GEOM_MAX; ++geom) {
|
||||
m_cached_trees[geom].clear();
|
||||
}
|
||||
|
||||
size_t time_of_day_count = 0;
|
||||
size_t vis_temp_len = 0;
|
||||
size_t max_draws = 0;
|
||||
size_t max_num_grps = 0;
|
||||
size_t max_inds = 0;
|
||||
|
||||
for (int geom = 0; geom < GEOM_MAX; ++geom) {
|
||||
for (size_t tree_idx = 0; tree_idx < lev_data->tfrag_trees[geom].size(); tree_idx++) {
|
||||
const auto& tree = lev_data->tfrag_trees[geom][tree_idx];
|
||||
|
||||
if (std::find(tree_kinds.begin(), tree_kinds.end(), tree.kind) != tree_kinds.end()) {
|
||||
auto& tree_cache = m_cached_trees[geom].emplace_back();
|
||||
tree_cache.kind = tree.kind;
|
||||
max_draws = std::max(tree.draws.size(), max_draws);
|
||||
size_t num_grps = 0;
|
||||
for (auto& draw : tree.draws) {
|
||||
num_grps += draw.vis_groups.size();
|
||||
}
|
||||
max_num_grps = std::max(max_num_grps, num_grps);
|
||||
max_inds = std::max(tree.unpacked.indices.size(), max_inds);
|
||||
time_of_day_count = std::max(tree.colors.size(), time_of_day_count);
|
||||
u32 verts = tree.packed_vertices.vertices.size();
|
||||
glGenVertexArrays(1, &tree_cache.vao);
|
||||
glBindVertexArray(tree_cache.vao);
|
||||
// glGenBuffers(1, &tree_cache.vertex_buffer);
|
||||
tree_cache.vertex_buffer = loader_data->tfrag_vertex_data[geom][tree_idx];
|
||||
tree_cache.vert_count = verts;
|
||||
tree_cache.draws = &tree.draws; // todo - should we just copy this?
|
||||
tree_cache.colors = &tree.colors;
|
||||
tree_cache.vis = &tree.bvh;
|
||||
tree_cache.index_data = tree.unpacked.indices.data();
|
||||
tree_cache.tod_cache = swizzle_time_of_day(tree.colors);
|
||||
tree_cache.draw_mode = tree.use_strips ? GL_TRIANGLE_STRIP : GL_TRIANGLES;
|
||||
vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size());
|
||||
glBindBuffer(GL_ARRAY_BUFFER, tree_cache.vertex_buffer);
|
||||
// glBufferData(GL_ARRAY_BUFFER, verts * sizeof(tfrag3::PreloadedVertex),
|
||||
// nullptr,
|
||||
// GL_STREAM_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glEnableVertexAttribArray(2);
|
||||
|
||||
glVertexAttribPointer(0, // location 0 in the shader
|
||||
3, // 3 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, x) // offset (0)
|
||||
);
|
||||
|
||||
glVertexAttribPointer(1, // location 1 in the shader
|
||||
3, // 3 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, s) // offset (0)
|
||||
);
|
||||
|
||||
glVertexAttribIPointer(2, // location 2 in the shader
|
||||
2, // 1 values per vert
|
||||
GL_UNSIGNED_SHORT, // u16
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, color_index) // offset (0)
|
||||
);
|
||||
glGenBuffers(1, &tree_cache.single_draw_index_buffer);
|
||||
glGenBuffers(1, &tree_cache.index_buffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tree_cache.index_buffer);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, tree.unpacked.indices.size() * sizeof(u32),
|
||||
tree.unpacked.indices.data(), GL_STREAM_DRAW);
|
||||
|
||||
glGenTextures(1, &tree_cache.time_of_day_texture);
|
||||
glBindTexture(GL_TEXTURE_1D, tree_cache.time_of_day_texture);
|
||||
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA,
|
||||
GL_UNSIGNED_INT_8_8_8_8, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_cache.vis_temp.resize(vis_temp_len);
|
||||
m_cache.multidraw_offset_per_stripdraw.resize(max_draws);
|
||||
m_cache.multidraw_count_buffer.resize(max_num_grps);
|
||||
m_cache.multidraw_index_offset_buffer.resize(max_num_grps);
|
||||
m_cache.draw_idx_temp.resize(max_draws);
|
||||
m_cache.index_temp.resize(max_inds);
|
||||
ASSERT(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT);
|
||||
}
|
||||
|
||||
bool TFragment::setup_for_level(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const std::string& level,
|
||||
SharedRenderState* render_state) {
|
||||
// make sure we have the level data.
|
||||
Timer tfrag3_setup_timer;
|
||||
auto lev_data = render_state->loader->get_tfrag3_level(level);
|
||||
|
||||
if (!lev_data) {
|
||||
// not loaded
|
||||
m_has_level = false;
|
||||
m_textures = nullptr;
|
||||
m_level_name = "";
|
||||
discard_tree_cache();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_has_level && lev_data->load_id != m_load_id) {
|
||||
m_has_level = false;
|
||||
m_textures = nullptr;
|
||||
m_level_name = "";
|
||||
discard_tree_cache();
|
||||
return setup_for_level(tree_kinds, level, render_state);
|
||||
}
|
||||
|
||||
m_load_id = lev_data->load_id;
|
||||
|
||||
if (m_level_name != level) {
|
||||
update_load(tree_kinds, lev_data);
|
||||
m_has_level = true;
|
||||
m_textures = &lev_data->textures;
|
||||
m_level_name = level;
|
||||
} else {
|
||||
m_has_level = true;
|
||||
}
|
||||
|
||||
if (tfrag3_setup_timer.getMs() > 5) {
|
||||
lg::info("TFRAG setup: {:.1f}ms", tfrag3_setup_timer.getMs());
|
||||
}
|
||||
|
||||
return m_has_level;
|
||||
}
|
||||
|
||||
void TFragment::render_tree(int geom,
|
||||
const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof) {
|
||||
if (!m_has_level) {
|
||||
return;
|
||||
}
|
||||
auto& tree = m_cached_trees.at(geom).at(settings.tree_idx);
|
||||
const auto* itimes = settings.itimes;
|
||||
|
||||
if (tree.freeze_itimes) {
|
||||
itimes = tree.itimes_debug;
|
||||
} else {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
tree.itimes_debug[i] = settings.itimes[i];
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(tree.kind != tfrag3::TFragmentTreeKind::INVALID);
|
||||
|
||||
if (m_color_result.size() < tree.colors->size()) {
|
||||
m_color_result.resize(tree.colors->size());
|
||||
}
|
||||
#ifndef __aarch64__
|
||||
if (m_use_fast_time_of_day) {
|
||||
interp_time_of_day_fast(settings.itimes, tree.tod_cache, m_color_result.data());
|
||||
} else {
|
||||
interp_time_of_day_slow(settings.itimes, *tree.colors, m_color_result.data());
|
||||
}
|
||||
#else
|
||||
interp_time_of_day_slow(settings.itimes, *tree.colors, m_color_result.data());
|
||||
#endif
|
||||
glActiveTexture(GL_TEXTURE10);
|
||||
glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture);
|
||||
glTexSubImage1D(GL_TEXTURE_1D, 0, 0, tree.colors->size(), GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||
m_color_result.data());
|
||||
|
||||
first_tfrag_draw_setup(settings, render_state, ShaderId::TFRAG3);
|
||||
|
||||
glBindVertexArray(tree.vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, tree.vertex_buffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||
render_state->no_multidraw ? tree.single_draw_index_buffer : tree.index_buffer);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glEnable(GL_PRIMITIVE_RESTART);
|
||||
glPrimitiveRestartIndex(UINT32_MAX);
|
||||
|
||||
cull_check_all_slow(settings.planes, tree.vis->vis_nodes, settings.occlusion_culling,
|
||||
m_cache.vis_temp.data());
|
||||
|
||||
u32 total_tris;
|
||||
if (render_state->no_multidraw) {
|
||||
u32 idx_buffer_size = make_index_list_from_vis_string(
|
||||
m_cache.draw_idx_temp.data(), m_cache.index_temp.data(), *tree.draws, m_cache.vis_temp,
|
||||
tree.index_data, &total_tris);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size * sizeof(u32), m_cache.index_temp.data(),
|
||||
GL_STREAM_DRAW);
|
||||
} else {
|
||||
total_tris = make_multidraws_from_vis_string(
|
||||
m_cache.multidraw_offset_per_stripdraw.data(), m_cache.multidraw_count_buffer.data(),
|
||||
m_cache.multidraw_index_offset_buffer.data(), *tree.draws, m_cache.vis_temp);
|
||||
}
|
||||
|
||||
prof.add_tri(total_tris);
|
||||
|
||||
for (size_t draw_idx = 0; draw_idx < tree.draws->size(); draw_idx++) {
|
||||
const auto& draw = tree.draws->operator[](draw_idx);
|
||||
const auto& multidraw_indices = m_cache.multidraw_offset_per_stripdraw[draw_idx];
|
||||
const auto& singledraw_indices = m_cache.draw_idx_temp[draw_idx];
|
||||
|
||||
if (render_state->no_multidraw) {
|
||||
if (singledraw_indices.second == 0) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (multidraw_indices.second == 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(m_textures);
|
||||
s32 tex_idx = draw.tree_tex_id;
|
||||
if (tex_idx >= 0) {
|
||||
glBindTexture(GL_TEXTURE_2D, m_textures->at(draw.tree_tex_id));
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, m_anim_slot_array->at(-(tex_idx + 1)));
|
||||
}
|
||||
auto double_draw = setup_tfrag_shader(render_state, draw.mode, ShaderId::TFRAG3);
|
||||
glUniform1i(m_uniforms.decal, draw.mode.get_decal() ? 1 : 0);
|
||||
tree.tris_this_frame += draw.num_triangles;
|
||||
tree.draws_this_frame++;
|
||||
|
||||
prof.add_draw_call();
|
||||
if (render_state->no_multidraw) {
|
||||
glDrawElements(tree.draw_mode, singledraw_indices.second, GL_UNSIGNED_INT,
|
||||
(void*)(singledraw_indices.first * sizeof(u32)));
|
||||
} else {
|
||||
glMultiDrawElements(tree.draw_mode, &m_cache.multidraw_count_buffer[multidraw_indices.first],
|
||||
GL_UNSIGNED_INT,
|
||||
&m_cache.multidraw_index_offset_buffer[multidraw_indices.first],
|
||||
multidraw_indices.second);
|
||||
}
|
||||
|
||||
switch (double_draw.kind) {
|
||||
case DoubleDrawKind::NONE:
|
||||
break;
|
||||
case DoubleDrawKind::AFAIL_NO_DEPTH_WRITE:
|
||||
prof.add_draw_call();
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_min"),
|
||||
-10.f);
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_max"),
|
||||
double_draw.aref_second);
|
||||
glDepthMask(GL_FALSE);
|
||||
if (render_state->no_multidraw) {
|
||||
glDrawElements(tree.draw_mode, singledraw_indices.second, GL_UNSIGNED_INT,
|
||||
(void*)(singledraw_indices.first * sizeof(u32)));
|
||||
} else {
|
||||
glMultiDrawElements(
|
||||
tree.draw_mode, &m_cache.multidraw_count_buffer[multidraw_indices.first],
|
||||
GL_UNSIGNED_INT, &m_cache.multidraw_index_offset_buffer[multidraw_indices.first],
|
||||
multidraw_indices.second);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
}
|
||||
}
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Render all trees with settings for the given tree.
|
||||
* This is intended to be used only for debugging when we can't easily get commands for all trees
|
||||
* working.
|
||||
*/
|
||||
void TFragment::render_all_trees(int geom,
|
||||
const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof) {
|
||||
TfragRenderSettings settings_copy = settings;
|
||||
for (size_t i = 0; i < m_cached_trees[geom].size(); i++) {
|
||||
if (m_cached_trees[geom][i].kind != tfrag3::TFragmentTreeKind::INVALID) {
|
||||
settings_copy.tree_idx = i;
|
||||
render_tree(geom, settings_copy, render_state, prof);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TFragment::render_matching_trees(int geom,
|
||||
const std::vector<tfrag3::TFragmentTreeKind>& trees,
|
||||
const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof) {
|
||||
TfragRenderSettings settings_copy = settings;
|
||||
for (size_t i = 0; i < m_cached_trees[geom].size(); i++) {
|
||||
auto& tree = m_cached_trees[geom][i];
|
||||
tree.reset_stats();
|
||||
if (!tree.allowed) {
|
||||
continue;
|
||||
}
|
||||
if (std::find(trees.begin(), trees.end(), tree.kind) != trees.end() || tree.forced) {
|
||||
tree.rendered_this_frame = true;
|
||||
settings_copy.tree_idx = i;
|
||||
render_tree(geom, settings_copy, render_state, prof);
|
||||
if (tree.cull_debug) {
|
||||
render_tree_cull_debug(settings_copy, render_state, prof);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TFragment::discard_tree_cache() {
|
||||
m_textures = nullptr;
|
||||
for (int geom = 0; geom < GEOM_MAX; ++geom) {
|
||||
for (auto& tree : m_cached_trees[geom]) {
|
||||
if (tree.kind != tfrag3::TFragmentTreeKind::INVALID) {
|
||||
glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture);
|
||||
glDeleteTextures(1, &tree.time_of_day_texture);
|
||||
glDeleteBuffers(1, &tree.single_draw_index_buffer);
|
||||
glDeleteBuffers(1, &tree.index_buffer);
|
||||
glDeleteVertexArrays(1, &tree.vao);
|
||||
}
|
||||
}
|
||||
m_cached_trees[geom].clear();
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
float frac(float in) {
|
||||
return in - (int)in;
|
||||
}
|
||||
|
||||
void debug_vis_draw(int first_root,
|
||||
int tree,
|
||||
int num,
|
||||
int depth,
|
||||
const std::vector<tfrag3::VisNode>& nodes,
|
||||
std::vector<TFragment::DebugVertex>& verts_out) {
|
||||
for (int ki = 0; ki < num; ki++) {
|
||||
auto& node = nodes.at(ki + tree - first_root);
|
||||
ASSERT(node.child_id != 0xffff);
|
||||
math::Vector4f rgba{frac(0.4 * depth), frac(0.7 * depth), frac(0.2 * depth), 0.06};
|
||||
math::Vector3f center = node.bsphere.xyz();
|
||||
float rad = node.bsphere.w();
|
||||
math::Vector3f corners[8] = {center, center, center, center};
|
||||
corners[0].x() += rad;
|
||||
corners[1].x() += rad;
|
||||
corners[2].x() -= rad;
|
||||
corners[3].x() -= rad;
|
||||
|
||||
corners[0].y() += rad;
|
||||
corners[1].y() -= rad;
|
||||
corners[2].y() += rad;
|
||||
corners[3].y() -= rad;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
corners[i + 4] = corners[i];
|
||||
corners[i].z() += rad;
|
||||
corners[i + 4].z() -= rad;
|
||||
}
|
||||
|
||||
if (true) {
|
||||
for (int i : {0, 4}) {
|
||||
verts_out.push_back({corners[0 + i], rgba});
|
||||
verts_out.push_back({corners[1 + i], rgba});
|
||||
verts_out.push_back({corners[2 + i], rgba});
|
||||
|
||||
verts_out.push_back({corners[1 + i], rgba}); // 0
|
||||
verts_out.push_back({corners[3 + i], rgba});
|
||||
verts_out.push_back({corners[2 + i], rgba});
|
||||
}
|
||||
|
||||
for (int i : {2, 6, 7, 2, 3, 7, 0, 4, 5, 0, 5, 1, 0, 6, 4, 0, 6, 2, 1, 3, 7, 1, 5, 7}) {
|
||||
verts_out.push_back({corners[i], rgba});
|
||||
}
|
||||
|
||||
constexpr int border0[12] = {0, 4, 6, 2, 2, 6, 3, 7, 0, 1, 2, 3};
|
||||
constexpr int border1[12] = {1, 5, 7, 3, 0, 4, 1, 5, 4, 5, 6, 7};
|
||||
rgba.w() = 1.0;
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
auto p0 = corners[border0[i]];
|
||||
auto p1 = corners[border1[i]];
|
||||
auto diff = (p1 - p0).normalized();
|
||||
math::Vector3f px = diff.z() == 0 ? math::Vector3f{1, 0, 1} : math::Vector3f{0, 1, 1};
|
||||
auto off = diff.cross(px) * 2000;
|
||||
|
||||
verts_out.push_back({p0 + off, rgba});
|
||||
verts_out.push_back({p0 - off, rgba});
|
||||
verts_out.push_back({p1 - off, rgba});
|
||||
|
||||
verts_out.push_back({p0 + off, rgba});
|
||||
verts_out.push_back({p1 + off, rgba});
|
||||
verts_out.push_back({p1 - off, rgba});
|
||||
}
|
||||
}
|
||||
|
||||
if (node.flags) {
|
||||
debug_vis_draw(first_root, node.child_id, node.num_kids, depth + 1, nodes, verts_out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void TFragment::render_tree_cull_debug(const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof) {
|
||||
// generate debug verts:
|
||||
m_debug_vert_data.clear();
|
||||
auto& tree = m_cached_trees.at(settings.tree_idx).at(lod());
|
||||
|
||||
debug_vis_draw(tree.vis->first_root, tree.vis->first_root, tree.vis->num_roots, 1,
|
||||
tree.vis->vis_nodes, m_debug_vert_data);
|
||||
|
||||
render_state->shaders[ShaderId::TFRAG3_NO_TEX].activate();
|
||||
glUniformMatrix4fv(
|
||||
glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "camera"), 1,
|
||||
GL_FALSE, settings.math_camera.data());
|
||||
glUniform4f(
|
||||
glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "hvdf_offset"),
|
||||
settings.hvdf_offset[0], settings.hvdf_offset[1], settings.hvdf_offset[2],
|
||||
settings.hvdf_offset[3]);
|
||||
glUniform1f(
|
||||
glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "fog_constant"),
|
||||
settings.fog.x());
|
||||
// glDisable(GL_DEPTH_TEST);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_GEQUAL);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // ?
|
||||
glDepthMask(GL_FALSE);
|
||||
|
||||
glBindVertexArray(m_debug_vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_debug_verts);
|
||||
|
||||
int remaining = m_debug_vert_data.size();
|
||||
int start = 0;
|
||||
|
||||
while (remaining > 0) {
|
||||
int to_do = std::min(DEBUG_TRI_COUNT * 3, remaining);
|
||||
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, to_do * sizeof(DebugVertex),
|
||||
m_debug_vert_data.data() + start);
|
||||
glDrawArrays(GL_TRIANGLES, 0, to_do);
|
||||
prof.add_draw_call();
|
||||
prof.add_tri(to_do / 3);
|
||||
|
||||
remaining -= to_do;
|
||||
start += to_do;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "game/graphics/opengl_renderer/BucketRenderer.h"
|
||||
#include "game/graphics/opengl_renderer/DirectRenderer.h"
|
||||
#include "game/graphics/opengl_renderer/background/Tfrag3.h"
|
||||
#include "game/graphics/opengl_renderer/background/Tie3.h"
|
||||
|
||||
using math::Matrix4f;
|
||||
@@ -40,14 +39,49 @@ class TFragment : public BucketRenderer {
|
||||
int my_id,
|
||||
const std::vector<tfrag3::TFragmentTreeKind>& trees,
|
||||
bool child_mode,
|
||||
int level_id);
|
||||
int level_id,
|
||||
const std::vector<GLuint>* anim_slot_array);
|
||||
~TFragment();
|
||||
void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override;
|
||||
void draw_debug_window() override;
|
||||
void init_shaders(ShaderLibrary& shaders) override;
|
||||
|
||||
void render_all_trees(int geom,
|
||||
const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof);
|
||||
|
||||
void render_matching_trees(int geom,
|
||||
const std::vector<tfrag3::TFragmentTreeKind>& trees,
|
||||
const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof);
|
||||
|
||||
void render_tree(int geom,
|
||||
const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof);
|
||||
|
||||
bool setup_for_level(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const std::string& level,
|
||||
SharedRenderState* render_state);
|
||||
void discard_tree_cache();
|
||||
|
||||
void render_tree_cull_debug(const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof);
|
||||
|
||||
void update_load(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const LevelData* loader_data);
|
||||
|
||||
int lod() const { return Gfx::g_global_settings.lod_tfrag; }
|
||||
struct DebugVertex {
|
||||
math::Vector3f position;
|
||||
math::Vector4f rgba;
|
||||
};
|
||||
|
||||
private:
|
||||
void handle_initialization(DmaFollower& dma);
|
||||
|
||||
bool m_child_mode = false;
|
||||
|
||||
// GS setup data
|
||||
@@ -76,7 +110,75 @@ class TFragment : public BucketRenderer {
|
||||
TFragSetup = 0,
|
||||
};
|
||||
|
||||
Tfrag3 m_tfrag3;
|
||||
std::vector<tfrag3::TFragmentTreeKind> m_tree_kinds;
|
||||
int m_level_id;
|
||||
|
||||
static constexpr int GEOM_MAX = 3;
|
||||
|
||||
struct TreeCache {
|
||||
tfrag3::TFragmentTreeKind kind = tfrag3::TFragmentTreeKind::INVALID;
|
||||
GLuint vertex_buffer = -1;
|
||||
GLuint index_buffer = -1;
|
||||
GLuint single_draw_index_buffer = -1;
|
||||
GLuint time_of_day_texture = -1;
|
||||
GLuint vao = -1;
|
||||
u32 vert_count = 0;
|
||||
const std::vector<tfrag3::StripDraw>* draws = nullptr;
|
||||
const std::vector<tfrag3::TimeOfDayColor>* colors = nullptr;
|
||||
const tfrag3::BVH* vis = nullptr;
|
||||
const u32* index_data = nullptr;
|
||||
SwizzledTimeOfDay tod_cache;
|
||||
u64 draw_mode = 0;
|
||||
|
||||
void reset_stats() {
|
||||
rendered_this_frame = false;
|
||||
tris_this_frame = 0;
|
||||
draws_this_frame = 0;
|
||||
}
|
||||
bool rendered_this_frame = false;
|
||||
int tris_this_frame = 0;
|
||||
int draws_this_frame = 0;
|
||||
bool allowed = true;
|
||||
bool forced = false;
|
||||
bool cull_debug = false;
|
||||
|
||||
bool freeze_itimes = false;
|
||||
math::Vector<s32, 4> itimes_debug[4];
|
||||
};
|
||||
|
||||
struct {
|
||||
GLuint decal;
|
||||
} m_uniforms;
|
||||
|
||||
struct Cache {
|
||||
std::vector<u8> vis_temp;
|
||||
std::vector<std::pair<int, int>> draw_idx_temp;
|
||||
std::vector<u32> index_temp;
|
||||
std::vector<std::pair<int, int>> multidraw_offset_per_stripdraw;
|
||||
std::vector<GLsizei> multidraw_count_buffer;
|
||||
std::vector<void*> multidraw_index_offset_buffer;
|
||||
} m_cache;
|
||||
|
||||
std::string m_level_name;
|
||||
|
||||
const std::vector<GLuint>* m_textures = nullptr;
|
||||
std::array<std::vector<TreeCache>, GEOM_MAX> m_cached_trees;
|
||||
|
||||
std::vector<math::Vector<u8, 4>> m_color_result;
|
||||
|
||||
GLuint m_debug_vao = -1;
|
||||
GLuint m_debug_verts = -1;
|
||||
|
||||
u64 m_load_id = -1;
|
||||
|
||||
// in theory could be up to 4096, I think, but we don't see that many...
|
||||
// should be easy to increase
|
||||
static constexpr int TIME_OF_DAY_COLOR_COUNT = 8192;
|
||||
|
||||
static constexpr int DEBUG_TRI_COUNT = 4096;
|
||||
std::vector<DebugVertex> m_debug_vert_data;
|
||||
|
||||
bool m_has_level = false;
|
||||
bool m_use_fast_time_of_day = true;
|
||||
const std::vector<GLuint>* m_anim_slot_array;
|
||||
};
|
||||
|
||||
@@ -1,520 +0,0 @@
|
||||
#include "Tfrag3.h"
|
||||
|
||||
#include "common/log/log.h"
|
||||
|
||||
#include "third-party/imgui/imgui.h"
|
||||
|
||||
Tfrag3::Tfrag3() {
|
||||
glGenVertexArrays(1, &m_debug_vao);
|
||||
glBindVertexArray(m_debug_vao);
|
||||
glGenBuffers(1, &m_debug_verts);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_debug_verts);
|
||||
glBufferData(GL_ARRAY_BUFFER, DEBUG_TRI_COUNT * 3 * sizeof(DebugVertex), nullptr,
|
||||
GL_DYNAMIC_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(0, // location 0 in the shader
|
||||
3, // 3 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(DebugVertex), // stride
|
||||
(void*)offsetof(DebugVertex, position) // offset (0)
|
||||
);
|
||||
|
||||
glVertexAttribPointer(1, // location 1 in the shader
|
||||
4, // 4 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(DebugVertex), // stride
|
||||
(void*)offsetof(DebugVertex, rgba) // offset (0)
|
||||
);
|
||||
glBindVertexArray(0);
|
||||
// regardless of how many we use some fixed max
|
||||
// we won't actually interp or upload to gpu the unused ones, but we need a fixed maximum so
|
||||
// indexing works properly.
|
||||
m_color_result.resize(TIME_OF_DAY_COLOR_COUNT);
|
||||
}
|
||||
|
||||
Tfrag3::~Tfrag3() {
|
||||
discard_tree_cache();
|
||||
glDeleteVertexArrays(1, &m_debug_vao);
|
||||
}
|
||||
|
||||
void Tfrag3::init_shaders(ShaderLibrary& shaders) {
|
||||
m_uniforms.decal = glGetUniformLocation(shaders[ShaderId::TFRAG3].id(), "decal");
|
||||
}
|
||||
|
||||
void Tfrag3::update_load(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const LevelData* loader_data) {
|
||||
const auto* lev_data = loader_data->level.get();
|
||||
discard_tree_cache();
|
||||
for (int geom = 0; geom < GEOM_MAX; ++geom) {
|
||||
m_cached_trees[geom].clear();
|
||||
}
|
||||
|
||||
size_t time_of_day_count = 0;
|
||||
size_t vis_temp_len = 0;
|
||||
size_t max_draws = 0;
|
||||
size_t max_num_grps = 0;
|
||||
size_t max_inds = 0;
|
||||
|
||||
for (int geom = 0; geom < GEOM_MAX; ++geom) {
|
||||
for (size_t tree_idx = 0; tree_idx < lev_data->tfrag_trees[geom].size(); tree_idx++) {
|
||||
const auto& tree = lev_data->tfrag_trees[geom][tree_idx];
|
||||
|
||||
if (std::find(tree_kinds.begin(), tree_kinds.end(), tree.kind) != tree_kinds.end()) {
|
||||
auto& tree_cache = m_cached_trees[geom].emplace_back();
|
||||
tree_cache.kind = tree.kind;
|
||||
max_draws = std::max(tree.draws.size(), max_draws);
|
||||
size_t num_grps = 0;
|
||||
for (auto& draw : tree.draws) {
|
||||
num_grps += draw.vis_groups.size();
|
||||
}
|
||||
max_num_grps = std::max(max_num_grps, num_grps);
|
||||
max_inds = std::max(tree.unpacked.indices.size(), max_inds);
|
||||
time_of_day_count = std::max(tree.colors.size(), time_of_day_count);
|
||||
u32 verts = tree.packed_vertices.vertices.size();
|
||||
glGenVertexArrays(1, &tree_cache.vao);
|
||||
glBindVertexArray(tree_cache.vao);
|
||||
// glGenBuffers(1, &tree_cache.vertex_buffer);
|
||||
tree_cache.vertex_buffer = loader_data->tfrag_vertex_data[geom][tree_idx];
|
||||
tree_cache.vert_count = verts;
|
||||
tree_cache.draws = &tree.draws; // todo - should we just copy this?
|
||||
tree_cache.colors = &tree.colors;
|
||||
tree_cache.vis = &tree.bvh;
|
||||
tree_cache.index_data = tree.unpacked.indices.data();
|
||||
tree_cache.tod_cache = swizzle_time_of_day(tree.colors);
|
||||
tree_cache.draw_mode = tree.use_strips ? GL_TRIANGLE_STRIP : GL_TRIANGLES;
|
||||
vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size());
|
||||
glBindBuffer(GL_ARRAY_BUFFER, tree_cache.vertex_buffer);
|
||||
// glBufferData(GL_ARRAY_BUFFER, verts * sizeof(tfrag3::PreloadedVertex),
|
||||
// nullptr,
|
||||
// GL_STREAM_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glEnableVertexAttribArray(2);
|
||||
|
||||
glVertexAttribPointer(0, // location 0 in the shader
|
||||
3, // 3 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, x) // offset (0)
|
||||
);
|
||||
|
||||
glVertexAttribPointer(1, // location 1 in the shader
|
||||
3, // 3 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, s) // offset (0)
|
||||
);
|
||||
|
||||
glVertexAttribIPointer(2, // location 2 in the shader
|
||||
2, // 1 values per vert
|
||||
GL_UNSIGNED_SHORT, // u16
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, color_index) // offset (0)
|
||||
);
|
||||
glGenBuffers(1, &tree_cache.single_draw_index_buffer);
|
||||
glGenBuffers(1, &tree_cache.index_buffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tree_cache.index_buffer);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, tree.unpacked.indices.size() * sizeof(u32),
|
||||
tree.unpacked.indices.data(), GL_STREAM_DRAW);
|
||||
|
||||
glGenTextures(1, &tree_cache.time_of_day_texture);
|
||||
glBindTexture(GL_TEXTURE_1D, tree_cache.time_of_day_texture);
|
||||
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA,
|
||||
GL_UNSIGNED_INT_8_8_8_8, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_cache.vis_temp.resize(vis_temp_len);
|
||||
m_cache.multidraw_offset_per_stripdraw.resize(max_draws);
|
||||
m_cache.multidraw_count_buffer.resize(max_num_grps);
|
||||
m_cache.multidraw_index_offset_buffer.resize(max_num_grps);
|
||||
m_cache.draw_idx_temp.resize(max_draws);
|
||||
m_cache.index_temp.resize(max_inds);
|
||||
ASSERT(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT);
|
||||
}
|
||||
|
||||
bool Tfrag3::setup_for_level(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const std::string& level,
|
||||
SharedRenderState* render_state) {
|
||||
// make sure we have the level data.
|
||||
Timer tfrag3_setup_timer;
|
||||
auto lev_data = render_state->loader->get_tfrag3_level(level);
|
||||
|
||||
if (!lev_data) {
|
||||
// not loaded
|
||||
m_has_level = false;
|
||||
m_textures = nullptr;
|
||||
m_level_name = "";
|
||||
discard_tree_cache();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_has_level && lev_data->load_id != m_load_id) {
|
||||
m_has_level = false;
|
||||
m_textures = nullptr;
|
||||
m_level_name = "";
|
||||
discard_tree_cache();
|
||||
return setup_for_level(tree_kinds, level, render_state);
|
||||
}
|
||||
|
||||
m_load_id = lev_data->load_id;
|
||||
|
||||
if (m_level_name != level) {
|
||||
update_load(tree_kinds, lev_data);
|
||||
m_has_level = true;
|
||||
m_textures = &lev_data->textures;
|
||||
m_level_name = level;
|
||||
} else {
|
||||
m_has_level = true;
|
||||
}
|
||||
|
||||
if (tfrag3_setup_timer.getMs() > 5) {
|
||||
lg::info("TFRAG setup: {:.1f}ms", tfrag3_setup_timer.getMs());
|
||||
}
|
||||
|
||||
return m_has_level;
|
||||
}
|
||||
|
||||
void Tfrag3::render_tree(int geom,
|
||||
const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof) {
|
||||
if (!m_has_level) {
|
||||
return;
|
||||
}
|
||||
auto& tree = m_cached_trees.at(geom).at(settings.tree_idx);
|
||||
const auto* itimes = settings.itimes;
|
||||
|
||||
if (tree.freeze_itimes) {
|
||||
itimes = tree.itimes_debug;
|
||||
} else {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
tree.itimes_debug[i] = settings.itimes[i];
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(tree.kind != tfrag3::TFragmentTreeKind::INVALID);
|
||||
|
||||
if (m_color_result.size() < tree.colors->size()) {
|
||||
m_color_result.resize(tree.colors->size());
|
||||
}
|
||||
#ifndef __aarch64__
|
||||
if (m_use_fast_time_of_day) {
|
||||
interp_time_of_day_fast(settings.itimes, tree.tod_cache, m_color_result.data());
|
||||
} else {
|
||||
interp_time_of_day_slow(settings.itimes, *tree.colors, m_color_result.data());
|
||||
}
|
||||
#else
|
||||
interp_time_of_day_slow(settings.itimes, *tree.colors, m_color_result.data());
|
||||
#endif
|
||||
glActiveTexture(GL_TEXTURE10);
|
||||
glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture);
|
||||
glTexSubImage1D(GL_TEXTURE_1D, 0, 0, tree.colors->size(), GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||
m_color_result.data());
|
||||
|
||||
first_tfrag_draw_setup(settings, render_state, ShaderId::TFRAG3);
|
||||
|
||||
glBindVertexArray(tree.vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, tree.vertex_buffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||
render_state->no_multidraw ? tree.single_draw_index_buffer : tree.index_buffer);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glEnable(GL_PRIMITIVE_RESTART);
|
||||
glPrimitiveRestartIndex(UINT32_MAX);
|
||||
|
||||
cull_check_all_slow(settings.planes, tree.vis->vis_nodes, settings.occlusion_culling,
|
||||
m_cache.vis_temp.data());
|
||||
|
||||
u32 total_tris;
|
||||
if (render_state->no_multidraw) {
|
||||
u32 idx_buffer_size = make_index_list_from_vis_string(
|
||||
m_cache.draw_idx_temp.data(), m_cache.index_temp.data(), *tree.draws, m_cache.vis_temp,
|
||||
tree.index_data, &total_tris);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size * sizeof(u32), m_cache.index_temp.data(),
|
||||
GL_STREAM_DRAW);
|
||||
} else {
|
||||
total_tris = make_multidraws_from_vis_string(
|
||||
m_cache.multidraw_offset_per_stripdraw.data(), m_cache.multidraw_count_buffer.data(),
|
||||
m_cache.multidraw_index_offset_buffer.data(), *tree.draws, m_cache.vis_temp);
|
||||
}
|
||||
|
||||
prof.add_tri(total_tris);
|
||||
|
||||
for (size_t draw_idx = 0; draw_idx < tree.draws->size(); draw_idx++) {
|
||||
const auto& draw = tree.draws->operator[](draw_idx);
|
||||
const auto& multidraw_indices = m_cache.multidraw_offset_per_stripdraw[draw_idx];
|
||||
const auto& singledraw_indices = m_cache.draw_idx_temp[draw_idx];
|
||||
|
||||
if (render_state->no_multidraw) {
|
||||
if (singledraw_indices.second == 0) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (multidraw_indices.second == 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(m_textures);
|
||||
glBindTexture(GL_TEXTURE_2D, m_textures->at(draw.tree_tex_id));
|
||||
auto double_draw = setup_tfrag_shader(render_state, draw.mode, ShaderId::TFRAG3);
|
||||
glUniform1i(m_uniforms.decal, draw.mode.get_decal() ? 1 : 0);
|
||||
tree.tris_this_frame += draw.num_triangles;
|
||||
tree.draws_this_frame++;
|
||||
|
||||
prof.add_draw_call();
|
||||
if (render_state->no_multidraw) {
|
||||
glDrawElements(tree.draw_mode, singledraw_indices.second, GL_UNSIGNED_INT,
|
||||
(void*)(singledraw_indices.first * sizeof(u32)));
|
||||
} else {
|
||||
glMultiDrawElements(tree.draw_mode, &m_cache.multidraw_count_buffer[multidraw_indices.first],
|
||||
GL_UNSIGNED_INT,
|
||||
&m_cache.multidraw_index_offset_buffer[multidraw_indices.first],
|
||||
multidraw_indices.second);
|
||||
}
|
||||
|
||||
switch (double_draw.kind) {
|
||||
case DoubleDrawKind::NONE:
|
||||
break;
|
||||
case DoubleDrawKind::AFAIL_NO_DEPTH_WRITE:
|
||||
prof.add_draw_call();
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_min"),
|
||||
-10.f);
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_max"),
|
||||
double_draw.aref_second);
|
||||
glDepthMask(GL_FALSE);
|
||||
if (render_state->no_multidraw) {
|
||||
glDrawElements(tree.draw_mode, singledraw_indices.second, GL_UNSIGNED_INT,
|
||||
(void*)(singledraw_indices.first * sizeof(u32)));
|
||||
} else {
|
||||
glMultiDrawElements(
|
||||
tree.draw_mode, &m_cache.multidraw_count_buffer[multidraw_indices.first],
|
||||
GL_UNSIGNED_INT, &m_cache.multidraw_index_offset_buffer[multidraw_indices.first],
|
||||
multidraw_indices.second);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
}
|
||||
}
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Render all trees with settings for the given tree.
|
||||
* This is intended to be used only for debugging when we can't easily get commands for all trees
|
||||
* working.
|
||||
*/
|
||||
void Tfrag3::render_all_trees(int geom,
|
||||
const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof) {
|
||||
TfragRenderSettings settings_copy = settings;
|
||||
for (size_t i = 0; i < m_cached_trees[geom].size(); i++) {
|
||||
if (m_cached_trees[geom][i].kind != tfrag3::TFragmentTreeKind::INVALID) {
|
||||
settings_copy.tree_idx = i;
|
||||
render_tree(geom, settings_copy, render_state, prof);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tfrag3::render_matching_trees(int geom,
|
||||
const std::vector<tfrag3::TFragmentTreeKind>& trees,
|
||||
const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof) {
|
||||
TfragRenderSettings settings_copy = settings;
|
||||
for (size_t i = 0; i < m_cached_trees[geom].size(); i++) {
|
||||
auto& tree = m_cached_trees[geom][i];
|
||||
tree.reset_stats();
|
||||
if (!tree.allowed) {
|
||||
continue;
|
||||
}
|
||||
if (std::find(trees.begin(), trees.end(), tree.kind) != trees.end() || tree.forced) {
|
||||
tree.rendered_this_frame = true;
|
||||
settings_copy.tree_idx = i;
|
||||
render_tree(geom, settings_copy, render_state, prof);
|
||||
if (tree.cull_debug) {
|
||||
render_tree_cull_debug(settings_copy, render_state, prof);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tfrag3::draw_debug_window() {
|
||||
for (int i = 0; i < (int)m_cached_trees.at(lod()).size(); i++) {
|
||||
auto& tree = m_cached_trees.at(lod()).at(i);
|
||||
if (tree.kind == tfrag3::TFragmentTreeKind::INVALID) {
|
||||
continue;
|
||||
}
|
||||
ImGui::PushID(i);
|
||||
ImGui::Text("[%d] %10s", i, tfrag3::tfrag_tree_names[(int)m_cached_trees[lod()][i].kind]);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Allow?", &tree.allowed);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Force?", &tree.forced);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("cull debug (slow)", &tree.cull_debug);
|
||||
ImGui::PopID();
|
||||
if (tree.rendered_this_frame) {
|
||||
ImGui::Checkbox("freeze itimes", &tree.freeze_itimes);
|
||||
ImGui::Text(" tris: %d draws: %d", tree.tris_this_frame, tree.draws_this_frame);
|
||||
for (int j = 0; j < 4; j++) {
|
||||
ImGui::Text(" itimes[%d] 0x%x 0x%x 0x%x 0x%x", j, tree.itimes_debug[j][0],
|
||||
tree.itimes_debug[j][1], tree.itimes_debug[j][2], tree.itimes_debug[j][3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tfrag3::discard_tree_cache() {
|
||||
m_textures = nullptr;
|
||||
for (int geom = 0; geom < GEOM_MAX; ++geom) {
|
||||
for (auto& tree : m_cached_trees[geom]) {
|
||||
if (tree.kind != tfrag3::TFragmentTreeKind::INVALID) {
|
||||
glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture);
|
||||
glDeleteTextures(1, &tree.time_of_day_texture);
|
||||
glDeleteBuffers(1, &tree.single_draw_index_buffer);
|
||||
glDeleteBuffers(1, &tree.index_buffer);
|
||||
glDeleteVertexArrays(1, &tree.vao);
|
||||
}
|
||||
}
|
||||
m_cached_trees[geom].clear();
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
float frac(float in) {
|
||||
return in - (int)in;
|
||||
}
|
||||
|
||||
void debug_vis_draw(int first_root,
|
||||
int tree,
|
||||
int num,
|
||||
int depth,
|
||||
const std::vector<tfrag3::VisNode>& nodes,
|
||||
std::vector<Tfrag3::DebugVertex>& verts_out) {
|
||||
for (int ki = 0; ki < num; ki++) {
|
||||
auto& node = nodes.at(ki + tree - first_root);
|
||||
ASSERT(node.child_id != 0xffff);
|
||||
math::Vector4f rgba{frac(0.4 * depth), frac(0.7 * depth), frac(0.2 * depth), 0.06};
|
||||
math::Vector3f center = node.bsphere.xyz();
|
||||
float rad = node.bsphere.w();
|
||||
math::Vector3f corners[8] = {center, center, center, center};
|
||||
corners[0].x() += rad;
|
||||
corners[1].x() += rad;
|
||||
corners[2].x() -= rad;
|
||||
corners[3].x() -= rad;
|
||||
|
||||
corners[0].y() += rad;
|
||||
corners[1].y() -= rad;
|
||||
corners[2].y() += rad;
|
||||
corners[3].y() -= rad;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
corners[i + 4] = corners[i];
|
||||
corners[i].z() += rad;
|
||||
corners[i + 4].z() -= rad;
|
||||
}
|
||||
|
||||
if (true) {
|
||||
for (int i : {0, 4}) {
|
||||
verts_out.push_back({corners[0 + i], rgba});
|
||||
verts_out.push_back({corners[1 + i], rgba});
|
||||
verts_out.push_back({corners[2 + i], rgba});
|
||||
|
||||
verts_out.push_back({corners[1 + i], rgba}); // 0
|
||||
verts_out.push_back({corners[3 + i], rgba});
|
||||
verts_out.push_back({corners[2 + i], rgba});
|
||||
}
|
||||
|
||||
for (int i : {2, 6, 7, 2, 3, 7, 0, 4, 5, 0, 5, 1, 0, 6, 4, 0, 6, 2, 1, 3, 7, 1, 5, 7}) {
|
||||
verts_out.push_back({corners[i], rgba});
|
||||
}
|
||||
|
||||
constexpr int border0[12] = {0, 4, 6, 2, 2, 6, 3, 7, 0, 1, 2, 3};
|
||||
constexpr int border1[12] = {1, 5, 7, 3, 0, 4, 1, 5, 4, 5, 6, 7};
|
||||
rgba.w() = 1.0;
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
auto p0 = corners[border0[i]];
|
||||
auto p1 = corners[border1[i]];
|
||||
auto diff = (p1 - p0).normalized();
|
||||
math::Vector3f px = diff.z() == 0 ? math::Vector3f{1, 0, 1} : math::Vector3f{0, 1, 1};
|
||||
auto off = diff.cross(px) * 2000;
|
||||
|
||||
verts_out.push_back({p0 + off, rgba});
|
||||
verts_out.push_back({p0 - off, rgba});
|
||||
verts_out.push_back({p1 - off, rgba});
|
||||
|
||||
verts_out.push_back({p0 + off, rgba});
|
||||
verts_out.push_back({p1 + off, rgba});
|
||||
verts_out.push_back({p1 - off, rgba});
|
||||
}
|
||||
}
|
||||
|
||||
if (node.flags) {
|
||||
debug_vis_draw(first_root, node.child_id, node.num_kids, depth + 1, nodes, verts_out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Tfrag3::render_tree_cull_debug(const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof) {
|
||||
// generate debug verts:
|
||||
m_debug_vert_data.clear();
|
||||
auto& tree = m_cached_trees.at(settings.tree_idx).at(lod());
|
||||
|
||||
debug_vis_draw(tree.vis->first_root, tree.vis->first_root, tree.vis->num_roots, 1,
|
||||
tree.vis->vis_nodes, m_debug_vert_data);
|
||||
|
||||
render_state->shaders[ShaderId::TFRAG3_NO_TEX].activate();
|
||||
glUniformMatrix4fv(
|
||||
glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "camera"), 1,
|
||||
GL_FALSE, settings.math_camera.data());
|
||||
glUniform4f(
|
||||
glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "hvdf_offset"),
|
||||
settings.hvdf_offset[0], settings.hvdf_offset[1], settings.hvdf_offset[2],
|
||||
settings.hvdf_offset[3]);
|
||||
glUniform1f(
|
||||
glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "fog_constant"),
|
||||
settings.fog.x());
|
||||
// glDisable(GL_DEPTH_TEST);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_GEQUAL);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // ?
|
||||
glDepthMask(GL_FALSE);
|
||||
|
||||
glBindVertexArray(m_debug_vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_debug_verts);
|
||||
|
||||
int remaining = m_debug_vert_data.size();
|
||||
int start = 0;
|
||||
|
||||
while (remaining > 0) {
|
||||
int to_do = std::min(DEBUG_TRI_COUNT * 3, remaining);
|
||||
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, to_do * sizeof(DebugVertex),
|
||||
m_debug_vert_data.data() + start);
|
||||
glDrawArrays(GL_TRIANGLES, 0, to_do);
|
||||
prof.add_draw_call();
|
||||
prof.add_tri(to_do / 3);
|
||||
|
||||
remaining -= to_do;
|
||||
start += to_do;
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/custom_data/Tfrag3Data.h"
|
||||
#include "common/math/Vector.h"
|
||||
|
||||
#include "game/graphics/gfx.h"
|
||||
#include "game/graphics/opengl_renderer/BucketRenderer.h"
|
||||
#include "game/graphics/opengl_renderer/background/background_common.h"
|
||||
#include "game/graphics/pipelines/opengl.h"
|
||||
|
||||
class Tfrag3 {
|
||||
public:
|
||||
Tfrag3();
|
||||
~Tfrag3();
|
||||
|
||||
void init_shaders(ShaderLibrary& shaders);
|
||||
void render_all_trees(int geom,
|
||||
const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof);
|
||||
|
||||
void render_matching_trees(int geom,
|
||||
const std::vector<tfrag3::TFragmentTreeKind>& trees,
|
||||
const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof);
|
||||
|
||||
void render_tree(int geom,
|
||||
const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof);
|
||||
|
||||
bool setup_for_level(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const std::string& level,
|
||||
SharedRenderState* render_state);
|
||||
void discard_tree_cache();
|
||||
|
||||
void render_tree_cull_debug(const TfragRenderSettings& settings,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof);
|
||||
|
||||
void draw_debug_window();
|
||||
struct DebugVertex {
|
||||
math::Vector3f position;
|
||||
math::Vector4f rgba;
|
||||
};
|
||||
|
||||
void update_load(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const LevelData* loader_data);
|
||||
|
||||
int lod() const { return Gfx::g_global_settings.lod_tfrag; }
|
||||
|
||||
private:
|
||||
static constexpr int GEOM_MAX = 3;
|
||||
|
||||
struct TreeCache {
|
||||
tfrag3::TFragmentTreeKind kind = tfrag3::TFragmentTreeKind::INVALID;
|
||||
GLuint vertex_buffer = -1;
|
||||
GLuint index_buffer = -1;
|
||||
GLuint single_draw_index_buffer = -1;
|
||||
GLuint time_of_day_texture = -1;
|
||||
GLuint vao = -1;
|
||||
u32 vert_count = 0;
|
||||
const std::vector<tfrag3::StripDraw>* draws = nullptr;
|
||||
const std::vector<tfrag3::TimeOfDayColor>* colors = nullptr;
|
||||
const tfrag3::BVH* vis = nullptr;
|
||||
const u32* index_data = nullptr;
|
||||
SwizzledTimeOfDay tod_cache;
|
||||
u64 draw_mode = 0;
|
||||
|
||||
void reset_stats() {
|
||||
rendered_this_frame = false;
|
||||
tris_this_frame = 0;
|
||||
draws_this_frame = 0;
|
||||
}
|
||||
bool rendered_this_frame = false;
|
||||
int tris_this_frame = 0;
|
||||
int draws_this_frame = 0;
|
||||
bool allowed = true;
|
||||
bool forced = false;
|
||||
bool cull_debug = false;
|
||||
|
||||
bool freeze_itimes = false;
|
||||
math::Vector<s32, 4> itimes_debug[4];
|
||||
};
|
||||
|
||||
struct {
|
||||
GLuint decal;
|
||||
} m_uniforms;
|
||||
|
||||
struct Cache {
|
||||
std::vector<u8> vis_temp;
|
||||
std::vector<std::pair<int, int>> draw_idx_temp;
|
||||
std::vector<u32> index_temp;
|
||||
std::vector<std::pair<int, int>> multidraw_offset_per_stripdraw;
|
||||
std::vector<GLsizei> multidraw_count_buffer;
|
||||
std::vector<void*> multidraw_index_offset_buffer;
|
||||
} m_cache;
|
||||
|
||||
std::string m_level_name;
|
||||
|
||||
const std::vector<GLuint>* m_textures = nullptr;
|
||||
std::array<std::vector<TreeCache>, GEOM_MAX> m_cached_trees;
|
||||
|
||||
std::vector<math::Vector<u8, 4>> m_color_result;
|
||||
|
||||
GLuint m_debug_vao = -1;
|
||||
GLuint m_debug_verts = -1;
|
||||
|
||||
u64 m_load_id = -1;
|
||||
|
||||
// in theory could be up to 4096, I think, but we don't see that many...
|
||||
// should be easy to increase
|
||||
static constexpr int TIME_OF_DAY_COLOR_COUNT = 8192;
|
||||
|
||||
static constexpr int DEBUG_TRI_COUNT = 4096;
|
||||
std::vector<DebugVertex> m_debug_vert_data;
|
||||
|
||||
bool m_has_level = false;
|
||||
bool m_use_fast_time_of_day = true;
|
||||
};
|
||||
@@ -6,6 +6,7 @@ uniform vec4 rgba;
|
||||
uniform int enable_tex;
|
||||
uniform int tcc;
|
||||
uniform ivec4 channel_scramble;
|
||||
uniform float alpha_multiply;
|
||||
|
||||
in vec2 uv;
|
||||
|
||||
@@ -28,4 +29,6 @@ void main() {
|
||||
} else {
|
||||
color = (rgba / 128.);
|
||||
}
|
||||
|
||||
color.a *= alpha_multiply;
|
||||
}
|
||||
@@ -182,6 +182,9 @@
|
||||
(oracle-jak 24)
|
||||
(nest-jak 25)
|
||||
(kor-transform 26)
|
||||
(skull-gem 27)
|
||||
(bomb 28)
|
||||
(cas-conveyor 29)
|
||||
)
|
||||
|
||||
(deftype texture-anim-pc-upload (structure)
|
||||
@@ -212,6 +215,15 @@
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro pc-texture-anim-flag-id (kind buf &key (qwc 0))
|
||||
`(dma-buffer-add-cnt-vif2
|
||||
,buf
|
||||
,qwc
|
||||
(new 'static 'vif-tag :cmd (vif-cmd pc-port) :imm ,kind)
|
||||
(new 'static 'vif-tag)
|
||||
)
|
||||
)
|
||||
|
||||
(defun texture-anim-layer-add-shader ((arg0 dma-buffer) (arg1 texture-anim-layer) (arg2 int))
|
||||
"Add DMA to set the GS to use the given shader. This can be used to read from the texture."
|
||||
(let ((s5-0 (-> arg1 tex)))
|
||||
@@ -576,6 +588,58 @@
|
||||
(define-extern *darkjak-hires-texture-anim-array* (texture-anim-array texture-anim))
|
||||
(define-extern *darkjak-hires-nest-texture-anim-array* (texture-anim-array texture-anim))
|
||||
(define-extern *kor-transform-texture-anim-array* (texture-anim-array texture-anim))
|
||||
(define-extern *skull-gem-texture-anim-array* (texture-anim-array texture-anim))
|
||||
(define-extern *bomb-texture-anim-array* (texture-anim-array texture-anim))
|
||||
(define-extern *cas-conveyor-texture-anim-array* (texture-anim-array texture-anim))
|
||||
|
||||
(defun pc-update-fixed-anim ((bucket bucket-id) (anim-id texture-anim-pc) (anim-array texture-anim-array))
|
||||
"Run a 'fixed' texture-anim, which should run entirely in C++."
|
||||
(with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf))
|
||||
bucket
|
||||
)
|
||||
;; determine how many texture we have:
|
||||
(let ((num 0))
|
||||
(dotimes (i (-> anim-array length))
|
||||
(when (-> anim-array array-data i tex)
|
||||
(+! num 1)
|
||||
)
|
||||
)
|
||||
|
||||
;; (format 0 "pc-update-fixed-anim: ~d layers~%" num)
|
||||
(let ((num-qw (/ (+ num 3) 4)))
|
||||
(pc-texture-anim-flag start-anim-array dma-buf)
|
||||
(pc-texture-anim-flag-id anim-id dma-buf :qwc num-qw)
|
||||
|
||||
(dotimes (i num)
|
||||
(let ((anim (-> anim-array array-data i))
|
||||
(out-slot (the (pointer float) (&+ (-> dma-buf base) (* 4 i))))
|
||||
)
|
||||
|
||||
(set! (-> out-slot) (-> anim frame-time))
|
||||
|
||||
(when (not (paused?))
|
||||
(with-pp
|
||||
(let ((f0-2 (+ (-> anim frame-time) (* (-> anim frame-delta) (-> pp clock seconds-per-frame))))
|
||||
(f1-2 (-> anim frame-mod))
|
||||
)
|
||||
(set! (-> anim frame-time) (- f0-2 (* (the float (the int (/ f0-2 f1-2))) f1-2)))
|
||||
)
|
||||
(if (< (-> anim frame-time) 0.0)
|
||||
(+! (-> anim frame-time) (-> anim frame-mod))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(&+! (-> dma-buf base) (* 16 num-qw))
|
||||
(pc-texture-anim-flag finish-anim-array dma-buf)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(none)
|
||||
)
|
||||
|
||||
|
||||
(defun update-texture-anim ((bucket bucket-id) (anim-array texture-anim-array))
|
||||
@@ -593,6 +657,18 @@
|
||||
;; for sky, we basically emulate the full thing
|
||||
;; (format *stdcon* "doing sky to bucket ~d~%" bucket)
|
||||
)
|
||||
((= anim-array *skull-gem-texture-anim-array*)
|
||||
(pc-update-fixed-anim bucket (texture-anim-pc skull-gem) anim-array)
|
||||
(return #f)
|
||||
)
|
||||
((= anim-array *bomb-texture-anim-array*)
|
||||
(pc-update-fixed-anim bucket (texture-anim-pc bomb) anim-array)
|
||||
(return #f)
|
||||
)
|
||||
((= anim-array *cas-conveyor-texture-anim-array*)
|
||||
(pc-update-fixed-anim bucket (texture-anim-pc cas-conveyor) anim-array)
|
||||
(return #f)
|
||||
)
|
||||
((= anim-array *darkjak-texture-anim-array*)
|
||||
;; darkjak is simple, and we reimplemented it in C++.
|
||||
;; so we just have to send the frame-time value.
|
||||
|
||||
Reference in New Issue
Block a user