mirror of
https://github.com/open-goal/jak-project
synced 2026-07-08 22:45:00 -04:00
3d4dfb2077
- Add "tfrag-water" tfrag tree support (may just be the same as Jak 1's 'dirt' for the settings) - Add "tfrag-trans" tfrag tree support, reusing "trans-tfrag" from jak 1. - Add a hack to `LinkedObjectFileCreation` to handle `oracle`, which is accidentally multiply defined as a type leftover from jak 1 (an entity in village1), and level info for jak 2. - Add `VI1.DGO` - add `time-of-day.gc`, and a few other stub functions so it works - Set up some time of day stuff in GOAL for jak 2/PC renderers - Clean up time of day in c++ renderers, support the more complicated weight system used by jak 2 (backward compatible with jak 1, thankfully) The mood functions now run, so this could cause problems if they rely on stuff we don't have yet. But it seems fine for ctysluma and prison for now.        
486 lines
19 KiB
C++
486 lines
19 KiB
C++
#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::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
|
|
1, // 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 || (m_has_level && lev_data->load_id != m_load_id)) {
|
|
m_has_level = false;
|
|
m_textures = nullptr;
|
|
m_level_name = "";
|
|
discard_tree_cache();
|
|
return false;
|
|
}
|
|
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);
|
|
ASSERT(tree.kind != tfrag3::TFragmentTreeKind::INVALID);
|
|
|
|
if (m_color_result.size() < tree.colors->size()) {
|
|
m_color_result.resize(tree.colors->size());
|
|
}
|
|
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());
|
|
}
|
|
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);
|
|
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::Text(" tris: %d draws: %d", tree.tris_this_frame, tree.draws_this_frame);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|