mirror of
https://github.com/open-goal/jak-project
synced 2026-06-15 22:40:34 -04:00
a918e2d9de
The way we got/stored background matrices is a bit weird and full of leftovers from the first attempts at porting renderers. This doesn't work well with the Jak 2 "other camera" system where some stuff is rendered with a different camera matrix. This cleans most of it up. The exception is that the collide mesh renderer and the additional sprite culling I added still need to peek at some cached camera matrices. This fixes the problem where etie uses the wrong matrices for "other camera" levels. Now the "hole covers" go in the holes in the background of the throne room. 
1020 lines
36 KiB
C++
1020 lines
36 KiB
C++
#include "Tie3.h"
|
|
|
|
#include "common/global_profiler/GlobalProfiler.h"
|
|
#include "common/log/log.h"
|
|
#include "common/util/Assert.h"
|
|
|
|
#include "third-party/imgui/imgui.h"
|
|
|
|
Tie3::Tie3(const std::string& name, int my_id, int level_id, tfrag3::TieCategory category)
|
|
: BucketRenderer(name, my_id), m_level_id(level_id), m_default_category(category) {
|
|
// 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);
|
|
|
|
m_wind_data.paused = 0;
|
|
math::Vector4f ones(1, 1, 1, 1);
|
|
m_wind_data.wind_normal = ones;
|
|
m_wind_data.wind_temp = ones;
|
|
for (auto& wv : m_wind_data.wind_array) {
|
|
wv = ones;
|
|
}
|
|
for (auto& wf : m_wind_data.wind_force) {
|
|
wf = 1.f;
|
|
}
|
|
}
|
|
|
|
Tie3::~Tie3() {
|
|
discard_tree_cache();
|
|
}
|
|
|
|
void Tie3::init_shaders(ShaderLibrary& shaders) {
|
|
m_uniforms.decal = glGetUniformLocation(shaders[ShaderId::TFRAG3].id(), "decal");
|
|
|
|
m_etie_uniforms.persp0 = glGetUniformLocation(shaders[ShaderId::ETIE].id(), "persp0");
|
|
m_etie_uniforms.persp1 = glGetUniformLocation(shaders[ShaderId::ETIE].id(), "persp1");
|
|
m_etie_uniforms.cam_no_persp = glGetUniformLocation(shaders[ShaderId::ETIE].id(), "cam_no_persp");
|
|
m_etie_uniforms.envmap_tod_tint =
|
|
glGetUniformLocation(shaders[ShaderId::ETIE].id(), "envmap_tod_tint");
|
|
|
|
m_etie_base_uniforms.decal = glGetUniformLocation(shaders[ShaderId::ETIE_BASE].id(), "decal");
|
|
m_etie_base_uniforms.persp0 = glGetUniformLocation(shaders[ShaderId::ETIE_BASE].id(), "persp0");
|
|
m_etie_base_uniforms.persp1 = glGetUniformLocation(shaders[ShaderId::ETIE_BASE].id(), "persp1");
|
|
m_etie_base_uniforms.cam_no_persp =
|
|
glGetUniformLocation(shaders[ShaderId::ETIE_BASE].id(), "cam_no_persp");
|
|
}
|
|
|
|
/*!
|
|
* Load a TIE tree from FR3 data.
|
|
* This often causes stutters, so as much as possible, we move stuff to the loader,
|
|
* and this function just updates things to reference loader data.
|
|
*/
|
|
void Tie3::load_from_fr3_data(const LevelData* loader_data) {
|
|
auto ul = scoped_prof("update-load");
|
|
const tfrag3::Level* lev_data = loader_data->level.get();
|
|
m_wind_vectors.clear();
|
|
|
|
// We changed level! free opengl resources allocated for the previous
|
|
discard_tree_cache();
|
|
|
|
// resize for the number of trees in this level.
|
|
for (int geo = 0; geo < 4; ++geo) {
|
|
m_trees[geo].resize(lev_data->tie_trees[geo].size());
|
|
}
|
|
|
|
u16 max_wind_idx = 0;
|
|
// loop over all "geos" (level of details)
|
|
for (u32 l_geo = 0; l_geo < tfrag3::TIE_GEOS; l_geo++) {
|
|
// loop over all trees
|
|
for (u32 l_tree = 0; l_tree < lev_data->tie_trees[l_geo].size(); l_tree++) {
|
|
auto ul = scoped_prof("load-tree");
|
|
size_t wind_idx_buffer_len = 0;
|
|
size_t num_grps = 0;
|
|
const auto& tree = lev_data->tie_trees[l_geo][l_tree];
|
|
|
|
// compute maximum number of vis groups (leaf in the bvh)
|
|
for (auto& draw : tree.static_draws) {
|
|
num_grps += draw.vis_groups.size();
|
|
}
|
|
|
|
// compute wind buffer sizes
|
|
for (auto& draw : tree.instanced_wind_draws) {
|
|
wind_idx_buffer_len += draw.vertex_index_stream.size();
|
|
}
|
|
for (auto& inst : tree.wind_instance_info) {
|
|
max_wind_idx = std::max(max_wind_idx, inst.wind_idx);
|
|
}
|
|
|
|
// vertex buffer max
|
|
auto& lod_tree = m_trees.at(l_geo);
|
|
|
|
// set up resources: create a VAO
|
|
glGenVertexArrays(1, &lod_tree[l_tree].vao);
|
|
glBindVertexArray(lod_tree[l_tree].vao);
|
|
// openGL vertex buffer from loader
|
|
lod_tree[l_tree].vertex_buffer = loader_data->tie_data[l_geo][l_tree].vertex_buffer;
|
|
// draw array from FR3 data
|
|
lod_tree[l_tree].draws = &tree.static_draws;
|
|
// base TOD colors from FR3
|
|
lod_tree[l_tree].colors = &tree.colors;
|
|
// visibility BVH from FR3
|
|
lod_tree[l_tree].vis = &tree.bvh;
|
|
// indices from FR3 (needed on CPU for culling)
|
|
lod_tree[l_tree].index_data = tree.unpacked.indices.data();
|
|
// wind metadata
|
|
lod_tree[l_tree].instance_info = &tree.wind_instance_info;
|
|
lod_tree[l_tree].wind_draws = &tree.instanced_wind_draws;
|
|
// preprocess colors for faster interpolation (TODO: move to loader)
|
|
lod_tree[l_tree].tod_cache = swizzle_time_of_day(tree.colors);
|
|
// OpenGL index buffer (fixed index buffer for multidraw system)
|
|
lod_tree[l_tree].index_buffer = loader_data->tie_data[l_geo][l_tree].index_buffer;
|
|
lod_tree[l_tree].category_draw_indices = tree.category_draw_indices;
|
|
|
|
// set up vertex attributes
|
|
glBindBuffer(GL_ARRAY_BUFFER, lod_tree[l_tree].vertex_buffer);
|
|
glEnableVertexAttribArray(0);
|
|
glEnableVertexAttribArray(1);
|
|
glEnableVertexAttribArray(2);
|
|
glEnableVertexAttribArray(3);
|
|
glEnableVertexAttribArray(4);
|
|
|
|
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)
|
|
);
|
|
|
|
glVertexAttribPointer(3, // location 1 in the shader
|
|
4, // 3 values per vert
|
|
GL_INT_2_10_10_10_REV, // floats
|
|
GL_TRUE, // normalized
|
|
sizeof(tfrag3::PreloadedVertex), // stride
|
|
(void*)offsetof(tfrag3::PreloadedVertex, nor) // offset (0)
|
|
);
|
|
|
|
glVertexAttribPointer(4, // location 1 in the shader
|
|
4, // 3 values per vert
|
|
GL_UNSIGNED_BYTE, // floats
|
|
GL_TRUE, // normalized
|
|
sizeof(tfrag3::PreloadedVertex), // stride
|
|
(void*)offsetof(tfrag3::PreloadedVertex, r) // offset (0)
|
|
);
|
|
|
|
// allocate dynamic index buffer for the fallback "not multidraw" mode.
|
|
glGenBuffers(1, &lod_tree[l_tree].single_draw_index_buffer);
|
|
|
|
// set up wind
|
|
if (wind_idx_buffer_len > 0) {
|
|
lod_tree[l_tree].wind_matrix_cache.resize(tree.wind_instance_info.size());
|
|
lod_tree[l_tree].wind_vertex_index_buffer =
|
|
loader_data->tie_data[l_geo][l_tree].wind_indices;
|
|
u32 off = 0;
|
|
for (auto& draw : tree.instanced_wind_draws) {
|
|
lod_tree[l_tree].wind_vertex_index_offsets.push_back(off);
|
|
off += draw.vertex_index_stream.size();
|
|
}
|
|
}
|
|
|
|
// set up per-proto visibility. Jak 2 needs to enable/disable individual protos.
|
|
lod_tree[l_tree].has_proto_visibility = tree.has_per_proto_visibility_toggle;
|
|
if (tree.has_per_proto_visibility_toggle) {
|
|
lod_tree[l_tree].proto_visibility.init(tree.proto_names);
|
|
}
|
|
|
|
// set up time of day texture.
|
|
glActiveTexture(GL_TEXTURE10);
|
|
glGenTextures(1, &lod_tree[l_tree].time_of_day_texture);
|
|
glBindTexture(GL_TEXTURE_1D, lod_tree[l_tree].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);
|
|
|
|
lod_tree[l_tree].vis_temp.resize(tree.bvh.vis_nodes.size());
|
|
|
|
lod_tree[l_tree].draw_idx_temp.resize(tree.static_draws.size());
|
|
lod_tree[l_tree].index_temp.resize(tree.unpacked.indices.size());
|
|
lod_tree[l_tree].multidraw_offset_per_stripdraw.resize(tree.static_draws.size());
|
|
lod_tree[l_tree].multidraw_count_buffer.resize(num_grps);
|
|
lod_tree[l_tree].multidraw_index_offset_buffer.resize(num_grps);
|
|
}
|
|
}
|
|
|
|
// set up temporary caches. These are just temporary, so they don't need per-tree versions.
|
|
|
|
m_wind_vectors.resize(4 * max_wind_idx + 4); // 4x u32's per wind.
|
|
|
|
// ASSERT(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT);
|
|
}
|
|
|
|
/*!
|
|
* Try loading a level. Hopefully it has been preloaded and this is fast.
|
|
*/
|
|
bool Tie3::try_loading_level(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 try_loading_level(level, render_state);
|
|
}
|
|
|
|
// loading was successful. Link textures/load ID.
|
|
m_textures = &lev_data->textures;
|
|
m_load_id = lev_data->load_id;
|
|
|
|
// see if this is the first time we've gotten the level
|
|
if (m_level_name != level) {
|
|
// it is! do the one time load.
|
|
load_from_fr3_data(lev_data);
|
|
m_has_level = true;
|
|
m_level_name = level;
|
|
} else {
|
|
m_has_level = true;
|
|
}
|
|
|
|
if (tfrag3_setup_timer.getMs() > 5) {
|
|
lg::info("TIE setup: {:.1f}ms", tfrag3_setup_timer.getMs());
|
|
}
|
|
|
|
return m_has_level;
|
|
}
|
|
|
|
void Tie3::discard_tree_cache() {
|
|
for (int geo = 0; geo < 4; ++geo) {
|
|
for (auto& tree : m_trees[geo]) {
|
|
glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture);
|
|
glDeleteTextures(1, &tree.time_of_day_texture);
|
|
// glDeleteBuffers(1, &tree.index_buffer);
|
|
glDeleteBuffers(1, &tree.single_draw_index_buffer);
|
|
glDeleteVertexArrays(1, &tree.vao);
|
|
}
|
|
|
|
m_trees[geo].clear();
|
|
}
|
|
}
|
|
|
|
bool Tie3::set_up_common_data_from_dma(DmaFollower& dma, SharedRenderState* render_state) {
|
|
auto data0 = dma.read_and_advance();
|
|
ASSERT(data0.vif1() == 0 || data0.vifcode1().kind == VifCode::Kind::NOP);
|
|
ASSERT(data0.vif0() == 0 || data0.vifcode0().kind == VifCode::Kind::NOP ||
|
|
data0.vifcode0().kind == VifCode::Kind::MARK);
|
|
ASSERT(data0.size_bytes == 0);
|
|
|
|
if (dma.current_tag().kind == DmaTag::Kind::CALL) {
|
|
// renderer didn't run, let's just get out of here.
|
|
for (int i = 0; i < 4; i++) {
|
|
dma.read_and_advance();
|
|
}
|
|
ASSERT(dma.current_tag_offset() == render_state->next_bucket);
|
|
return false;
|
|
}
|
|
|
|
if (dma.current_tag_offset() == render_state->next_bucket) {
|
|
return false;
|
|
}
|
|
|
|
auto gs_test = dma.read_and_advance();
|
|
if (gs_test.size_bytes == 160) {
|
|
} else {
|
|
ASSERT(gs_test.size_bytes == 32);
|
|
|
|
auto tie_consts = dma.read_and_advance();
|
|
ASSERT(tie_consts.size_bytes == 9 * 16);
|
|
}
|
|
|
|
auto mscalf = dma.read_and_advance();
|
|
ASSERT(mscalf.size_bytes == 0);
|
|
|
|
auto row = dma.read_and_advance();
|
|
ASSERT(row.size_bytes == 32);
|
|
|
|
auto next = dma.read_and_advance();
|
|
if (next.size_bytes == 32) {
|
|
next = dma.read_and_advance();
|
|
}
|
|
ASSERT(next.size_bytes == 0);
|
|
|
|
auto pc_port_data = dma.read_and_advance();
|
|
ASSERT(pc_port_data.size_bytes == sizeof(TfragPcPortData));
|
|
memcpy(&m_pc_port_data, pc_port_data.data, sizeof(TfragPcPortData));
|
|
m_pc_port_data.level_name[11] = '\0';
|
|
|
|
if (render_state->version == GameVersion::Jak1) {
|
|
auto wind_data = dma.read_and_advance();
|
|
ASSERT(wind_data.size_bytes == sizeof(WindWork));
|
|
memcpy(&m_wind_data, wind_data.data, sizeof(WindWork));
|
|
}
|
|
|
|
if (render_state->version == GameVersion::Jak2) {
|
|
// jak 2 proto visibility
|
|
auto proto_mask_data = dma.read_and_advance();
|
|
m_common_data.proto_vis_data = proto_mask_data.data;
|
|
m_common_data.proto_vis_data_size = proto_mask_data.size_bytes;
|
|
}
|
|
|
|
// envmap color
|
|
auto envmap_color = dma.read_and_advance();
|
|
ASSERT(envmap_color.size_bytes == 16);
|
|
memcpy(m_common_data.envmap_color.data(), envmap_color.data, 16);
|
|
m_common_data.envmap_color /= 128.f;
|
|
if (render_state->version == GameVersion::Jak1) {
|
|
m_common_data.envmap_color *= 2;
|
|
}
|
|
m_common_data.envmap_color *= m_envmap_strength;
|
|
|
|
m_common_data.frame_idx = render_state->frame_idx;
|
|
|
|
while (dma.current_tag_offset() != render_state->next_bucket) {
|
|
dma.read_and_advance();
|
|
}
|
|
|
|
m_common_data.settings.camera = m_pc_port_data.camera;
|
|
|
|
m_common_data.settings.tree_idx = 0;
|
|
|
|
if (render_state->occlusion_vis[m_level_id].valid) {
|
|
m_common_data.settings.occlusion_culling = render_state->occlusion_vis[m_level_id].data;
|
|
} else {
|
|
m_common_data.settings.occlusion_culling = 0;
|
|
}
|
|
|
|
update_render_state_from_pc_settings(render_state, m_pc_port_data);
|
|
|
|
m_has_level = try_loading_level(m_pc_port_data.level_name, render_state);
|
|
return true;
|
|
}
|
|
/*!
|
|
* Render method called from bucket render system.
|
|
* Does common setup for all category, but only renderers default_category.
|
|
*/
|
|
void Tie3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) {
|
|
if (!m_enabled) {
|
|
while (dma.current_tag_offset() != render_state->next_bucket) {
|
|
dma.read_and_advance();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (set_up_common_data_from_dma(dma, render_state)) {
|
|
setup_all_trees(lod(), m_common_data.settings, m_common_data.proto_vis_data,
|
|
m_common_data.proto_vis_data_size, !render_state->no_multidraw, prof);
|
|
|
|
draw_matching_draws_for_all_trees(lod(), m_common_data.settings, render_state, prof,
|
|
m_default_category);
|
|
}
|
|
}
|
|
|
|
void Tie3::render_from_another(SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof,
|
|
tfrag3::TieCategory category) {
|
|
if (render_state->frame_idx != m_common_data.frame_idx) {
|
|
return;
|
|
}
|
|
draw_matching_draws_for_all_trees(lod(), m_common_data.settings, render_state, prof, category);
|
|
}
|
|
|
|
void Tie3::draw_matching_draws_for_all_trees(int geom,
|
|
const TfragRenderSettings& settings,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof,
|
|
tfrag3::TieCategory category) {
|
|
for (u32 i = 0; i < m_trees[geom].size(); i++) {
|
|
draw_matching_draws_for_tree(i, geom, settings, render_state, prof, category);
|
|
}
|
|
}
|
|
|
|
void Tie3::setup_all_trees(int geom,
|
|
const TfragRenderSettings& settings,
|
|
const u8* proto_vis_data,
|
|
size_t proto_vis_data_size,
|
|
bool use_multidraw,
|
|
ScopedProfilerNode& prof) {
|
|
for (u32 i = 0; i < m_trees[geom].size(); i++) {
|
|
setup_tree(i, geom, settings, proto_vis_data, proto_vis_data_size, use_multidraw, prof);
|
|
}
|
|
}
|
|
|
|
void Tie3::setup_tree(int idx,
|
|
int geom,
|
|
const TfragRenderSettings& settings,
|
|
const u8* proto_vis_data,
|
|
size_t proto_vis_data_size,
|
|
bool use_multidraw,
|
|
ScopedProfilerNode& prof) {
|
|
// reset perf
|
|
auto& tree = m_trees.at(geom).at(idx);
|
|
// don't render if we haven't loaded
|
|
if (!m_has_level) {
|
|
return;
|
|
}
|
|
|
|
// update time of day
|
|
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.camera.itimes, tree.tod_cache, m_color_result.data());
|
|
} else {
|
|
interp_time_of_day_slow(settings.camera.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());
|
|
|
|
// update proto vis mask
|
|
if (proto_vis_data) {
|
|
tree.proto_visibility.update(proto_vis_data, proto_vis_data_size);
|
|
}
|
|
|
|
if (!m_debug_all_visible) {
|
|
// need culling data
|
|
cull_check_all_slow(settings.camera.planes, tree.vis->vis_nodes, settings.occlusion_culling,
|
|
tree.vis_temp.data());
|
|
}
|
|
|
|
u32 num_tris = 0;
|
|
if (use_multidraw) {
|
|
if (m_debug_all_visible) {
|
|
num_tris = make_all_visible_multidraws(
|
|
tree.multidraw_offset_per_stripdraw.data(), tree.multidraw_count_buffer.data(),
|
|
tree.multidraw_index_offset_buffer.data(), *tree.draws);
|
|
} else {
|
|
Timer index_timer;
|
|
if (tree.has_proto_visibility) {
|
|
num_tris = make_multidraws_from_vis_and_proto_string(
|
|
tree.multidraw_offset_per_stripdraw.data(), tree.multidraw_count_buffer.data(),
|
|
tree.multidraw_index_offset_buffer.data(), *tree.draws, tree.vis_temp,
|
|
tree.proto_visibility.vis_flags);
|
|
} else {
|
|
num_tris = make_multidraws_from_vis_string(
|
|
tree.multidraw_offset_per_stripdraw.data(), tree.multidraw_count_buffer.data(),
|
|
tree.multidraw_index_offset_buffer.data(), *tree.draws, tree.vis_temp);
|
|
}
|
|
}
|
|
} else {
|
|
u32 idx_buffer_size;
|
|
if (m_debug_all_visible) {
|
|
idx_buffer_size =
|
|
make_all_visible_index_list(tree.draw_idx_temp.data(), tree.index_temp.data(),
|
|
*tree.draws, tree.index_data, &num_tris);
|
|
} else {
|
|
if (tree.has_proto_visibility) {
|
|
idx_buffer_size = make_index_list_from_vis_and_proto_string(
|
|
tree.draw_idx_temp.data(), tree.index_temp.data(), *tree.draws, tree.vis_temp,
|
|
tree.proto_visibility.vis_flags, tree.index_data, &num_tris);
|
|
} else {
|
|
idx_buffer_size =
|
|
make_index_list_from_vis_string(tree.draw_idx_temp.data(), tree.index_temp.data(),
|
|
*tree.draws, tree.vis_temp, tree.index_data, &num_tris);
|
|
}
|
|
}
|
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tree.single_draw_index_buffer);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size * sizeof(u32), tree.index_temp.data(),
|
|
GL_STREAM_DRAW);
|
|
}
|
|
|
|
prof.add_tri(num_tris);
|
|
}
|
|
|
|
namespace {
|
|
void set_uniform(GLuint uniform, const math::Vector4f& val) {
|
|
glUniform4f(uniform, val.x(), val.y(), val.z(), val.w());
|
|
}
|
|
} // namespace
|
|
|
|
void init_etie_cam_uniforms(const EtieUniforms& uniforms, const GoalBackgroundCameraData& data) {
|
|
glUniformMatrix4fv(uniforms.cam_no_persp, 1, GL_FALSE, data.rot[0].data());
|
|
|
|
math::Vector4f perspective[2];
|
|
float inv_fog = 1.f / data.fog[0];
|
|
auto& hvdf_off = data.hvdf_off;
|
|
float pxx = data.perspective[0].x();
|
|
float pyy = data.perspective[1].y();
|
|
float pzz = data.perspective[2].z();
|
|
float pzw = data.perspective[2].w();
|
|
float pwz = data.perspective[3].z();
|
|
float scale = pzw * inv_fog;
|
|
perspective[0].x() = scale * hvdf_off.x();
|
|
perspective[0].y() = scale * hvdf_off.y();
|
|
perspective[0].z() = scale * hvdf_off.z() + pzz;
|
|
perspective[0].w() = scale;
|
|
|
|
perspective[1].x() = pxx;
|
|
perspective[1].y() = pyy;
|
|
perspective[1].z() = pwz;
|
|
perspective[1].w() = 0;
|
|
|
|
set_uniform(uniforms.persp0, perspective[0]);
|
|
set_uniform(uniforms.persp1, perspective[1]);
|
|
}
|
|
|
|
void Tie3::draw_matching_draws_for_tree(int idx,
|
|
int geom,
|
|
const TfragRenderSettings& settings,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof,
|
|
tfrag3::TieCategory category) {
|
|
auto& tree = m_trees.at(geom).at(idx);
|
|
|
|
// don't render if we haven't loaded
|
|
if (!m_has_level) {
|
|
return;
|
|
}
|
|
bool use_envmap = tfrag3::is_envmap_first_draw_category(category);
|
|
auto shader_id = use_envmap ? ShaderId::ETIE_BASE : ShaderId::TFRAG3;
|
|
|
|
// setup OpenGL shader
|
|
first_tfrag_draw_setup(settings, render_state, shader_id);
|
|
|
|
if (use_envmap) {
|
|
// if we use envmap, use the envmap-style math for the base draw to avoid rounding issue.
|
|
init_etie_cam_uniforms(m_etie_base_uniforms, m_common_data.settings.camera);
|
|
}
|
|
|
|
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_TEXTURE10);
|
|
glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glEnable(GL_PRIMITIVE_RESTART);
|
|
glPrimitiveRestartIndex(UINT32_MAX);
|
|
|
|
int last_texture = -1;
|
|
for (size_t draw_idx = tree.category_draw_indices[(int)category];
|
|
draw_idx < tree.category_draw_indices[(int)category + 1]; draw_idx++) {
|
|
const auto& draw = tree.draws->operator[](draw_idx);
|
|
const auto& multidraw_indices = tree.multidraw_offset_per_stripdraw[draw_idx];
|
|
const auto& singledraw_indices = tree.draw_idx_temp[draw_idx];
|
|
|
|
if (render_state->no_multidraw) {
|
|
if (singledraw_indices.second == 0) {
|
|
continue;
|
|
}
|
|
} else {
|
|
if (multidraw_indices.second == 0) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ((int)draw.tree_tex_id != last_texture) {
|
|
glBindTexture(GL_TEXTURE_2D, m_textures->at(draw.tree_tex_id));
|
|
last_texture = draw.tree_tex_id;
|
|
}
|
|
|
|
auto double_draw = setup_tfrag_shader(render_state, draw.mode,
|
|
use_envmap ? ShaderId::ETIE_BASE : ShaderId::TFRAG3);
|
|
|
|
glUniform1i(use_envmap ? m_etie_base_uniforms.decal : m_uniforms.decal,
|
|
draw.mode.get_decal() ? 1 : 0);
|
|
|
|
prof.add_draw_call();
|
|
|
|
if (render_state->no_multidraw) {
|
|
glDrawElements(GL_TRIANGLE_STRIP, singledraw_indices.second, GL_UNSIGNED_INT,
|
|
(void*)(singledraw_indices.first * sizeof(u32)));
|
|
} else {
|
|
glMultiDrawElements(
|
|
GL_TRIANGLE_STRIP, &tree.multidraw_count_buffer[multidraw_indices.first], GL_UNSIGNED_INT,
|
|
&tree.multidraw_index_offset_buffer[multidraw_indices.first], multidraw_indices.second);
|
|
}
|
|
|
|
switch (double_draw.kind) {
|
|
case DoubleDrawKind::NONE:
|
|
break;
|
|
case DoubleDrawKind::AFAIL_NO_DEPTH_WRITE:
|
|
ASSERT(false);
|
|
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(GL_TRIANGLE_STRIP, singledraw_indices.second, GL_UNSIGNED_INT,
|
|
(void*)(singledraw_indices.first * sizeof(u32)));
|
|
} else {
|
|
glMultiDrawElements(
|
|
GL_TRIANGLE_STRIP, &tree.multidraw_count_buffer[multidraw_indices.first],
|
|
GL_UNSIGNED_INT, &tree.multidraw_index_offset_buffer[multidraw_indices.first],
|
|
multidraw_indices.second);
|
|
}
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
|
|
if (!m_hide_wind && category == tfrag3::TieCategory::NORMAL) {
|
|
auto wind_prof = prof.make_scoped_child("wind");
|
|
render_tree_wind(idx, geom, settings, render_state, wind_prof);
|
|
}
|
|
|
|
glBindVertexArray(0);
|
|
|
|
if (use_envmap && m_draw_envmap_second_draw) {
|
|
envmap_second_pass_draw(tree, settings, render_state, prof,
|
|
tfrag3::get_second_draw_category(category));
|
|
}
|
|
}
|
|
|
|
void Tie3::envmap_second_pass_draw(const Tree& tree,
|
|
const TfragRenderSettings& settings,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof,
|
|
tfrag3::TieCategory category) {
|
|
first_tfrag_draw_setup(settings, render_state, ShaderId::ETIE);
|
|
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);
|
|
|
|
init_etie_cam_uniforms(m_etie_uniforms, m_common_data.settings.camera);
|
|
set_uniform(m_etie_uniforms.envmap_tod_tint, m_common_data.envmap_color);
|
|
|
|
int last_texture = -1;
|
|
for (size_t draw_idx = tree.category_draw_indices[(int)category];
|
|
draw_idx < tree.category_draw_indices[(int)category + 1]; draw_idx++) {
|
|
const auto& draw = tree.draws->operator[](draw_idx);
|
|
const auto& multidraw_indices = tree.multidraw_offset_per_stripdraw[draw_idx];
|
|
const auto& singledraw_indices = tree.draw_idx_temp[draw_idx];
|
|
|
|
if (render_state->no_multidraw) {
|
|
if (singledraw_indices.second == 0) {
|
|
continue;
|
|
}
|
|
} else {
|
|
if (multidraw_indices.second == 0) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ((int)draw.tree_tex_id != last_texture) {
|
|
glBindTexture(GL_TEXTURE_2D, m_textures->at(draw.tree_tex_id));
|
|
last_texture = draw.tree_tex_id;
|
|
}
|
|
|
|
auto double_draw = setup_tfrag_shader(render_state, draw.mode, ShaderId::ETIE);
|
|
|
|
prof.add_draw_call();
|
|
|
|
if (render_state->no_multidraw) {
|
|
glDrawElements(GL_TRIANGLE_STRIP, singledraw_indices.second, GL_UNSIGNED_INT,
|
|
(void*)(singledraw_indices.first * sizeof(u32)));
|
|
} else {
|
|
glMultiDrawElements(
|
|
GL_TRIANGLE_STRIP, &tree.multidraw_count_buffer[multidraw_indices.first], GL_UNSIGNED_INT,
|
|
&tree.multidraw_index_offset_buffer[multidraw_indices.first], multidraw_indices.second);
|
|
}
|
|
|
|
switch (double_draw.kind) {
|
|
case DoubleDrawKind::NONE:
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Tie3::draw_debug_window() {
|
|
ImGui::Checkbox("envmap 2nd draw", &m_draw_envmap_second_draw);
|
|
ImGui::SliderFloat("envmap str", &m_envmap_strength, 0, 2);
|
|
ImGui::Checkbox("Fast ToD", &m_use_fast_time_of_day);
|
|
ImGui::SameLine();
|
|
ImGui::Checkbox("All Visible", &m_debug_all_visible);
|
|
ImGui::Checkbox("Hide Wind", &m_hide_wind);
|
|
ImGui::SliderFloat("Wind Multiplier", &m_wind_multiplier, 0., 40.f);
|
|
ImGui::Separator();
|
|
}
|
|
|
|
void TieProtoVisibility::init(const std::vector<std::string>& names) {
|
|
vis_flags.resize(names.size());
|
|
for (auto& x : vis_flags) {
|
|
x = 1;
|
|
}
|
|
all_visible = true;
|
|
name_to_idx.clear();
|
|
size_t i = 0;
|
|
for (auto& name : names) {
|
|
name_to_idx[name].push_back(i++);
|
|
}
|
|
}
|
|
|
|
void TieProtoVisibility::update(const u8* data, size_t size) {
|
|
char name_buffer[256]; // ??
|
|
|
|
if (!all_visible) {
|
|
for (auto& x : vis_flags) {
|
|
x = 1;
|
|
}
|
|
all_visible = true;
|
|
}
|
|
|
|
const u8* end = data + size;
|
|
|
|
while (true) {
|
|
int name_idx = 0;
|
|
while (*data) {
|
|
name_buffer[name_idx++] = *data;
|
|
data++;
|
|
}
|
|
if (name_idx) {
|
|
ASSERT(name_idx < 254);
|
|
name_buffer[name_idx] = '\0';
|
|
const auto& it = name_to_idx.find(name_buffer);
|
|
if (it != name_to_idx.end()) {
|
|
all_visible = false;
|
|
for (auto x : name_to_idx.at(name_buffer)) {
|
|
vis_flags[x] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
while (*data == 0) {
|
|
if (data >= end) {
|
|
return;
|
|
}
|
|
data++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void vector_min_in_place(math::Vector4f& v, float val) {
|
|
for (int i = 0; i < 4; i++) {
|
|
if (v[i] > val) {
|
|
v[i] = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
math::Vector4f vector_max(const math::Vector4f& v, float val) {
|
|
math::Vector4f result;
|
|
for (int i = 0; i < 4; i++) {
|
|
result[i] = std::max(val, v[i]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void do_wind_math(u16 wind_idx,
|
|
float* wind_vector_data,
|
|
const Tie3::WindWork& wind_work,
|
|
float stiffness,
|
|
std::array<math::Vector4f, 4>& mat) {
|
|
float* my_vector = wind_vector_data + (4 * wind_idx);
|
|
const auto& work_vector = wind_work.wind_array[(wind_work.wind_time + wind_idx) & 63];
|
|
constexpr float cx = 0.5;
|
|
constexpr float cy = 100.0;
|
|
constexpr float cz = 0.0166;
|
|
constexpr float cw = -1.0;
|
|
|
|
// ld s1, 8(s5) # load wind vector 1
|
|
// pextlw s1, r0, s1 # convert to 2x 64 bits, by shifting left
|
|
// qmtc2.i vf18, s1 # put in vf
|
|
float vf18_x = my_vector[2];
|
|
float vf18_z = my_vector[3];
|
|
|
|
// ld s2, 0(s5) # load wind vector 0
|
|
// pextlw s3, r0, s2 # convert to 2x 64 bits, by shifting left
|
|
// qmtc2.i vf17, s3 # put in vf
|
|
float vf17_x = my_vector[0];
|
|
float vf17_z = my_vector[1];
|
|
|
|
// lqc2 vf16, 12(s3) # load wind vector
|
|
math::Vector4f vf16 = work_vector;
|
|
|
|
// vmula.xyzw acc, vf16, vf1 # acc = vf16
|
|
// vmsubax.xyzw acc, vf18, vf19 # acc = vf16 - vf18 * wind_const.x
|
|
// vmsuby.xyzw vf16, vf17, vf19
|
|
// # vf16 -= (vf18 * wind_const.x) + (vf17 * wind_const.y)
|
|
vf16.x() -= cx * vf18_x + cy * vf17_x;
|
|
vf16.z() -= cx * vf18_z + cy * vf17_z;
|
|
|
|
// vmulaz.xyzw acc, vf16, vf19 # acc = vf16 * wind_const.z
|
|
// vmadd.xyzw vf18, vf1, vf18
|
|
// # vf18 += vf16 * wind_const.z
|
|
math::Vector4f vf18(vf18_x, 0.f, vf18_z, 0.f);
|
|
vf18 += vf16 * cz;
|
|
|
|
// vmulaz.xyzw acc, vf18, vf19 # acc = vf18 * wind_const.z
|
|
// vmadd.xyzw vf17, vf17, vf1
|
|
// # vf17 += vf18 * wind_const.z
|
|
math::Vector4f vf17(vf17_x, 0.f, vf17_z, 0.f);
|
|
vf17 += vf18 * cz;
|
|
|
|
// vitof12.xyzw vf11, vf11 # normal convert
|
|
// vitof12.xyzw vf12, vf12 # normal convert
|
|
|
|
// vminiw.xyzw vf17, vf17, vf0
|
|
vector_min_in_place(vf17, 1.f);
|
|
|
|
// qmfc2.i s3, vf18
|
|
// ppacw s3, r0, s3
|
|
|
|
// vmaxw.xyzw vf27, vf17, vf19
|
|
auto vf27 = vector_max(vf17, cw);
|
|
|
|
// vmulw.xyzw vf27, vf27, vf15
|
|
vf27 *= stiffness;
|
|
|
|
// vmulax.yw acc, vf0, vf0
|
|
// vmulay.xz acc, vf27, vf10
|
|
// vmadd.xyzw vf10, vf1, vf10
|
|
mat[0].x() += vf27.x() * mat[0].y();
|
|
mat[0].z() += vf27.z() * mat[0].y();
|
|
|
|
// qmfc2.i s2, vf27
|
|
if (!wind_work.paused) {
|
|
my_vector[0] = vf27.x();
|
|
my_vector[1] = vf27.z();
|
|
my_vector[2] = vf18.x();
|
|
my_vector[3] = vf18.z();
|
|
}
|
|
|
|
// vmulax.yw acc, vf0, vf0
|
|
// vmulay.xz acc, vf27, vf11
|
|
// vmadd.xyzw vf11, vf1, vf11
|
|
mat[1].x() += vf27.x() * mat[1].y();
|
|
mat[1].z() += vf27.z() * mat[1].y();
|
|
|
|
// ppacw s2, r0, s2
|
|
// vmulax.yw acc, vf0, vf0
|
|
// vmulay.xz acc, vf27, vf12
|
|
// vmadd.xyzw vf12, vf1, vf12
|
|
mat[2].x() += vf27.x() * mat[2].y();
|
|
mat[2].z() += vf27.z() * mat[2].y();
|
|
|
|
//
|
|
// if not paused
|
|
// sd s3, 8(s5)
|
|
// sd s2, 0(s5)
|
|
}
|
|
|
|
void Tie3::render_tree_wind(int idx,
|
|
int geom,
|
|
const TfragRenderSettings& settings,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
auto& tree = m_trees.at(geom).at(idx);
|
|
if (tree.wind_draws->empty()) {
|
|
return;
|
|
}
|
|
|
|
// note: this isn't the most efficient because we might compute wind matrices for invisible
|
|
// instances. TODO: add vis ids to the instance info to avoid this
|
|
memset(tree.wind_matrix_cache.data(), 0, sizeof(float) * 16 * tree.wind_matrix_cache.size());
|
|
auto& cam_bad = settings.camera.camera;
|
|
std::array<math::Vector4f, 4> cam;
|
|
for (int i = 0; i < 4; i++) {
|
|
cam[i] = cam_bad[i];
|
|
}
|
|
|
|
for (size_t inst_id = 0; inst_id < tree.instance_info->size(); inst_id++) {
|
|
auto& info = tree.instance_info->operator[](inst_id);
|
|
auto& out = tree.wind_matrix_cache[inst_id];
|
|
// auto& mat = tree.instance_info->operator[](inst_id).matrix;
|
|
auto mat = info.matrix;
|
|
|
|
ASSERT(info.wind_idx * 4 <= m_wind_vectors.size());
|
|
do_wind_math(info.wind_idx, m_wind_vectors.data(), m_wind_data,
|
|
info.stiffness * m_wind_multiplier, mat);
|
|
|
|
// vmulax.xyzw acc, vf20, vf10
|
|
// vmadday.xyzw acc, vf21, vf10
|
|
// vmaddz.xyzw vf10, vf22, vf10
|
|
out[0] = cam[0] * mat[0].x() + cam[1] * mat[0].y() + cam[2] * mat[0].z();
|
|
|
|
// vmulax.xyzw acc, vf20, vf11
|
|
// vmadday.xyzw acc, vf21, vf11
|
|
// vmaddz.xyzw vf11, vf22, vf11
|
|
out[1] = cam[0] * mat[1].x() + cam[1] * mat[1].y() + cam[2] * mat[1].z();
|
|
|
|
// vmulax.xyzw acc, vf20, vf12
|
|
// vmadday.xyzw acc, vf21, vf12
|
|
// vmaddz.xyzw vf12, vf22, vf12
|
|
out[2] = cam[0] * mat[2].x() + cam[1] * mat[2].y() + cam[2] * mat[2].z();
|
|
|
|
// vmulax.xyzw acc, vf20, vf13
|
|
// vmadday.xyzw acc, vf21, vf13
|
|
// vmaddaz.xyzw acc, vf22, vf13
|
|
// vmaddw.xyzw vf13, vf23, vf0
|
|
out[3] = cam[0] * mat[3].x() + cam[1] * mat[3].y() + cam[2] * mat[3].z() + cam[3];
|
|
}
|
|
|
|
int last_texture = -1;
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tree.wind_vertex_index_buffer);
|
|
|
|
for (size_t draw_idx = 0; draw_idx < tree.wind_draws->size(); draw_idx++) {
|
|
const auto& draw = tree.wind_draws->operator[](draw_idx);
|
|
|
|
if ((int)draw.tree_tex_id != last_texture) {
|
|
glBindTexture(GL_TEXTURE_2D, m_textures->at(draw.tree_tex_id));
|
|
last_texture = draw.tree_tex_id;
|
|
}
|
|
auto double_draw = setup_tfrag_shader(render_state, draw.mode, ShaderId::TFRAG3);
|
|
|
|
int off = 0;
|
|
for (auto& grp : draw.instance_groups) {
|
|
if (!m_debug_all_visible && !tree.vis_temp.at(grp.vis_idx)) {
|
|
off += grp.num;
|
|
continue; // invisible, skip.
|
|
}
|
|
|
|
glUniformMatrix4fv(
|
|
glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "camera"), 1, GL_FALSE,
|
|
tree.wind_matrix_cache.at(grp.instance_idx)[0].data());
|
|
|
|
prof.add_draw_call();
|
|
prof.add_tri(grp.num);
|
|
|
|
glDrawElements(GL_TRIANGLE_STRIP, grp.num, GL_UNSIGNED_INT,
|
|
(void*)((off + tree.wind_vertex_index_offsets.at(draw_idx)) * sizeof(u32)));
|
|
off += grp.num;
|
|
|
|
switch (double_draw.kind) {
|
|
case DoubleDrawKind::NONE:
|
|
break;
|
|
case DoubleDrawKind::AFAIL_NO_DEPTH_WRITE:
|
|
prof.add_draw_call();
|
|
prof.add_tri(grp.num);
|
|
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);
|
|
glDrawElements(GL_TRIANGLE_STRIP, draw.vertex_index_stream.size(), GL_UNSIGNED_INT,
|
|
(void*)0);
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Tie3AnotherCategory::Tie3AnotherCategory(const std::string& name,
|
|
int my_id,
|
|
Tie3* parent,
|
|
tfrag3::TieCategory category)
|
|
: BucketRenderer(name, my_id), m_parent(parent), m_category(category) {}
|
|
|
|
void Tie3AnotherCategory::draw_debug_window() {
|
|
ImGui::Text("Child of this renderer:");
|
|
m_parent->draw_debug_window();
|
|
}
|
|
|
|
void Tie3AnotherCategory::render(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
auto first_tag = dma.current_tag();
|
|
dma.read_and_advance();
|
|
if (first_tag.kind != DmaTag::Kind::CNT || first_tag.qwc != 0) {
|
|
fmt::print("Bucket renderer {} ({}) was supposed to be empty, but wasn't\n", m_my_id, m_name);
|
|
ASSERT(false);
|
|
}
|
|
m_parent->render_from_another(render_state, prof, m_category);
|
|
}
|
|
|
|
Tie3WithEnvmapJak1::Tie3WithEnvmapJak1(const std::string& name, int my_id, int level_id)
|
|
: Tie3(name, my_id, level_id, tfrag3::TieCategory::NORMAL) {}
|
|
|
|
void Tie3WithEnvmapJak1::render(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
Tie3::render(dma, render_state, prof);
|
|
if (m_enable_envmap) {
|
|
render_from_another(render_state, prof, tfrag3::TieCategory::NORMAL_ENVMAP);
|
|
}
|
|
}
|
|
|
|
void Tie3WithEnvmapJak1::draw_debug_window() {
|
|
ImGui::Checkbox("envmap", &m_enable_envmap);
|
|
Tie3::draw_debug_window();
|
|
}
|