mirror of
https://github.com/open-goal/jak-project
synced 2026-08-19 22:12:24 -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. 
716 lines
26 KiB
C++
716 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();
|
|
}
|
|
|
|
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.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->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());
|
|
|
|
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.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;
|
|
}
|
|
}
|