[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:
water111
2023-07-16 13:02:53 -04:00
committed by GitHub
parent e0bc7ce732
commit 72c27a6eaa
24 changed files with 1676 additions and 846 deletions
+3
View File
@@ -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);
+1 -1
View File
@@ -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;
+6
View File
@@ -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",
};
}
+19 -1
View File
@@ -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",
+6
View File
@@ -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));
{
+2 -2
View File
@@ -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);
}
}
+41 -16
View File
@@ -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());
+1 -1
View File
@@ -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;
}
+61 -43
View File
@@ -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
View File
@@ -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));
-1
View File
@@ -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);
+2 -1
View File
@@ -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;
+717 -59
View File
@@ -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});
}
}
+88 -47
View File
@@ -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.