Files
water111 5b04be2fa0 Add hfrag, clean up some background renderer stuff (#3509)
This adds hfrag, but with a few remaining issues:
- The textures aren't animated. Instead, it just uses one texture.
- The texture filtering isn't as good as at it could be.

I also cleaned up a few issues with the background renderers:
- Cleaned up some stuff that is common to hfrag, tie, tfrag, shrub
- Moved time-of-day color packing stuff to FR3 creation, rather than at
level load. This appears to reduce the frame time spikes when a level is
first drawn by about 5 or 6 ms in big levels.
- Cleaned up the x86 specific stuff used in time of day. Now there's
only one place where we have an `ifdef`, rather than spreading it all
over the rendering code.
2024-05-09 20:11:43 -04:00

706 lines
26 KiB
C++

#include "TFragment.h"
#include "game/graphics/opengl_renderer/dma_helpers.h"
#include "third-party/imgui/imgui.h"
namespace {
bool looks_like_tfragment_dma(const DmaFollower& follow) {
return follow.current_tag_vifcode0().kind == VifCode::Kind::STCYCL;
}
bool looks_like_tfrag_init(const DmaFollower& follow) {
return follow.current_tag_vifcode0().kind == VifCode::Kind::NOP &&
follow.current_tag_vifcode1().kind == VifCode::Kind::DIRECT &&
follow.current_tag_vifcode1().immediate == 2;
}
} // namespace
TFragment::TFragment(const std::string& name,
int my_id,
const std::vector<tfrag3::TFragmentTreeKind>& trees,
bool child_mode,
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_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,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
if (!m_enabled) {
while (dma.current_tag_offset() != render_state->next_bucket) {
dma.read_and_advance();
}
return;
}
// First thing should be a NEXT with two nops.
// unless we are a child, in which case our parent took this already.
if (!m_child_mode) {
auto data0 = dma.read_and_advance();
ASSERT(data0.vifcode1().kind == VifCode::Kind::NOP);
ASSERT(data0.vif0() == 0 || 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;
}
if (m_my_id == render_state->bucket_for_vis_copy &&
dma.current_tag_vifcode1().kind == VifCode::Kind::PC_PORT) {
DmaTransfer transfers[20];
for (int i = 0; i < render_state->num_vis_to_copy; i++) {
transfers[i] = dma.read_and_advance();
auto next0 = dma.read_and_advance();
ASSERT(next0.size_bytes == 0);
}
for (int i = 0; i < render_state->num_vis_to_copy; i++) {
if (transfers[i].size_bytes == 128 * 16) {
if (render_state->use_occlusion_culling) {
render_state->occlusion_vis[i].valid = true;
memcpy(render_state->occlusion_vis[i].data, transfers[i].data, 128 * 16);
}
} else {
ASSERT(transfers[i].size_bytes == 16);
}
}
}
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;
}
std::string level_name;
while (looks_like_tfrag_init(dma)) {
handle_initialization(dma);
if (level_name.empty()) {
level_name = m_pc_port_data.level_name;
} else if (level_name != m_pc_port_data.level_name) {
ASSERT(false);
}
while (looks_like_tfragment_dma(dma)) {
dma.read_and_advance();
}
}
while (dma.current_tag_offset() != render_state->next_bucket) {
dma.read_and_advance();
}
if (level_name.empty()) {
return;
}
{
setup_for_level(m_tree_kinds, level_name, render_state);
TfragRenderSettings settings;
settings.camera = m_pc_port_data.camera;
settings.tree_idx = 0;
if (render_state->occlusion_vis[m_level_id].valid) {
settings.occlusion_culling = render_state->occlusion_vis[m_level_id].data;
}
update_render_state_from_pc_settings(render_state, m_pc_port_data);
auto t3prof = prof.make_scoped_child("t3");
render_matching_trees(lod(), m_tree_kinds, settings, render_state, t3prof);
}
while (dma.current_tag_offset() != render_state->next_bucket) {
auto tag = dma.current_tag().print();
dma.read_and_advance();
}
}
void TFragment::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_uniforms.decal = glGetUniformLocation(shaders[ShaderId::TFRAG3].id(), "decal");
}
void TFragment::handle_initialization(DmaFollower& dma) {
// Set up test (different between different renderers)
auto setup_test = dma.read_and_advance();
ASSERT(setup_test.vif0() == 0);
ASSERT(setup_test.vifcode1().kind == VifCode::Kind::DIRECT);
ASSERT(setup_test.vifcode1().immediate == 2);
ASSERT(setup_test.size_bytes == 32);
memcpy(m_test_setup, setup_test.data, 32);
// matrix 0
auto mat0_upload = dma.read_and_advance();
unpack_to_stcycl(&m_buffered_data[0].pad[TFragDataMem::TFragMatrix0 * 16], mat0_upload,
VifCode::Kind::UNPACK_V4_32, 4, 4, 64, TFragDataMem::TFragMatrix0, false, false);
// matrix 1
auto mat1_upload = dma.read_and_advance();
unpack_to_stcycl(&m_buffered_data[1].pad[TFragDataMem::TFragMatrix0 * 16], mat1_upload,
VifCode::Kind::UNPACK_V4_32, 4, 4, 64, TFragDataMem::TFragMatrix1, false, false);
// data
auto data_upload = dma.read_and_advance();
(void)data_upload;
// call the setup program
auto mscal_setup = dma.read_and_advance();
verify_mscal(mscal_setup, TFragProgMem::TFragSetup);
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';
// setup double buffering.
auto db_setup = dma.read_and_advance();
ASSERT(db_setup.size_bytes == 0);
ASSERT(db_setup.vifcode0().kind == VifCode::Kind::BASE &&
db_setup.vifcode0().immediate == Buffer0_Start);
ASSERT(db_setup.vifcode1().kind == VifCode::Kind::OFFSET &&
db_setup.vifcode1().immediate == (Buffer1_Start - Buffer0_Start));
}
std::string TFragData::print() const {
std::string result;
result += fmt::format("fog: {}\n", fog.to_string_aligned());
result += fmt::format("val: {}\n", val.to_string_aligned());
result += fmt::format("str-gif: {}\n", str_gif.print());
result += fmt::format("fan-gif: {}\n", fan_gif.print());
result += fmt::format("ad-gif: {}\n", ad_gif.print());
result += fmt::format("hvdf_offset: {}\n", hvdf_offset.to_string_aligned());
result += fmt::format("hmge_scale: {}\n", hmge_scale.to_string_aligned());
result += fmt::format("invh_scale: {}\n", invh_scale.to_string_aligned());
result += fmt::format("ambient: {}\n", ambient.to_string_aligned());
result += fmt::format("guard: {}\n", guard.to_string_aligned());
result += fmt::format("k0s[0]: {}\n", k0s[0].to_string_aligned());
result += fmt::format("k0s[1]: {}\n", k0s[1].to_string_aligned());
result += fmt::format("k1s[0]: {}\n", k1s[0].to_string_aligned());
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();
}
u32 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.color_count, 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.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);
[[maybe_unused]] const auto* itimes = settings.camera.itimes;
if (tree.freeze_itimes) {
itimes = tree.itimes_debug;
} else {
for (int i = 0; i < 4; i++) {
tree.itimes_debug[i] = settings.camera.itimes[i];
}
}
ASSERT(tree.kind != tfrag3::TFragmentTreeKind::INVALID);
if (m_color_result.size() < tree.colors->color_count) {
m_color_result.resize(tree.colors->color_count);
}
interp_time_of_day(settings.camera.itimes, *tree.colors, m_color_result.data());
glActiveTexture(GL_TEXTURE10);
glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture);
glTexSubImage1D(GL_TEXTURE_1D, 0, 0, tree.colors->color_count, GL_RGBA,
GL_UNSIGNED_INT_8_8_8_8_REV, m_color_result.data());
first_tfrag_draw_setup(settings.camera, 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.camera.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.camera.camera[0].data());
glUniform4f(
glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "hvdf_offset"),
settings.camera.hvdf_off[0], settings.camera.hvdf_off[1], settings.camera.hvdf_off[2],
settings.camera.hvdf_off[3]);
glUniform1f(
glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "fog_constant"),
settings.camera.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;
}
}