[game] improve loading (#1049)

* better load

* fix sky, cleanup
This commit is contained in:
water111
2022-01-07 11:52:24 -05:00
committed by GitHub
parent 026c7245a2
commit 3c42d222cb
10 changed files with 543 additions and 313 deletions
+6 -1
View File
@@ -4,6 +4,7 @@
#include "decompiler/level_extractor/BspHeader.h"
#include "decompiler/level_extractor/extract_tfrag.h"
#include "decompiler/level_extractor/extract_tie.h"
#include "common/util/compress.h"
#include "common/util/FileUtil.h"
namespace decompiler {
@@ -107,8 +108,12 @@ void extract_from_level(ObjectFileDB& db,
Serializer ser;
tfrag_level.serialize(ser);
auto compressed =
compression::compress_zstd(ser.get_save_result().first, ser.get_save_result().second);
fmt::print("compressed: {} -> {} ({:.2f}%)\n", ser.get_save_result().second, compressed.size(),
100.f * compressed.size() / ser.get_save_result().second);
file_util::write_binary_file(file_util::get_file_path({fmt::format(
"assets/{}.fr3", dgo_name.substr(0, dgo_name.length() - 4))}),
ser.get_save_result().first, ser.get_save_result().second);
compressed.data(), compressed.size());
}
} // namespace decompiler
@@ -525,11 +525,6 @@ void update_proto_info(std::vector<TieProtoInfo>* out,
}
}
// just for debug
for (int g = 0; g < 4; g++) {
frag_info.point_sizes.push_back(proto.geometry[g].tie_fragments[frag_idx].point_ref.size());
}
info.frags.push_back(std::move(frag_info));
}
}
+54 -10
View File
@@ -1,6 +1,7 @@
#include "Loader.h"
#include "common/util/Timer.h"
#include "common/util/FileUtil.h"
#include "common/util/compress.h"
namespace {
std::string uppercase_string(const std::string& s) {
@@ -13,23 +14,66 @@ std::string uppercase_string(const std::string& s) {
} // namespace
tfrag3::Level* Loader::get_tfrag3_level(const std::string& level_name) {
std::unique_lock<std::mutex> lk(m_loader_mutex);
const auto& existing = m_tfrag3_levels.find(level_name);
if (existing == m_tfrag3_levels.end()) {
fmt::print("Loader needs to load tfrag3 level: {}\n", level_name);
if (m_level_to_load.empty()) {
fmt::print("[pc loader] starting load for {}\n", level_name);
m_level_to_load = level_name;
lk.unlock();
m_loader_cv.notify_all();
return nullptr;
} else {
return nullptr;
}
} else {
existing->second.frames_since_last_used = 0;
return existing->second.level.get();
}
}
void Loader::loader_thread() {
while (!m_want_shutdown) {
std::unique_lock<std::mutex> lk(m_loader_mutex);
m_loader_cv.wait(lk, [&] { return !m_level_to_load.empty() || m_want_shutdown; });
if (m_want_shutdown) {
return;
}
std::string lev = m_level_to_load;
lk.unlock();
Timer disk_timer;
auto data = file_util::read_binary_file(
file_util::get_file_path({fmt::format("assets/{}.fr3", uppercase_string(level_name))}));
file_util::get_file_path({fmt::format("assets/{}.fr3", uppercase_string(lev))}));
double disk_load_time = disk_timer.getSeconds();
Timer decomp_timer;
auto decomp_data = compression::decompress_zstd(data.data(), data.size());
double decomp_time = decomp_timer.getSeconds();
Timer import_timer;
auto& result = m_tfrag3_levels[level_name];
Serializer ser(data.data(), data.size());
result.serialize(ser);
auto result = std::make_unique<tfrag3::Level>();
Serializer ser(decomp_data.data(), decomp_data.size());
result->serialize(ser);
double import_time = import_timer.getSeconds();
fmt::print("------------> Load from file: {:.3f}s, import {:.3f}s\n", disk_load_time,
import_time);
return &result;
} else {
return &existing->second;
fmt::print("------------> Load from file: {:.3f}s, import {:.3f}s, decomp {:.3f}s\n",
disk_load_time, import_time, decomp_time);
lk.lock();
m_level_to_load = "";
m_tfrag3_levels[lev].level = std::move(result);
}
}
Loader::Loader() {
m_loader_thread = std::thread(&Loader::loader_thread, this);
}
Loader::~Loader() {
{
std::lock_guard<std::mutex> lk(m_loader_mutex);
m_want_shutdown = true;
m_loader_cv.notify_all();
}
m_loader_thread.join();
}
+20 -1
View File
@@ -1,11 +1,30 @@
#pragma once
#include <thread>
#include <mutex>
#include <condition_variable>
#include "common/custom_data/Tfrag3Data.h"
class Loader {
public:
Loader();
~Loader();
tfrag3::Level* get_tfrag3_level(const std::string& level_name);
private:
std::unordered_map<std::string, tfrag3::Level> m_tfrag3_levels;
struct Level {
std::unique_ptr<tfrag3::Level> level;
int frames_since_last_used = 0;
};
void loader_thread();
std::unordered_map<std::string, Level> m_tfrag3_levels;
std::string m_level_to_load;
std::thread m_loader_thread;
std::mutex m_loader_mutex;
std::condition_variable m_loader_cv;
bool m_want_shutdown = false;
};
+191 -126
View File
@@ -27,6 +27,10 @@ Tfrag3::Tfrag3() {
(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() {
@@ -34,139 +38,203 @@ Tfrag3::~Tfrag3() {
glDeleteVertexArrays(1, &m_debug_vao);
}
void Tfrag3::setup_for_level(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
bool Tfrag3::update_load(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
const tfrag3::Level* lev_data) {
switch (m_load_state.state) {
case State::DISCARD_TREE:
discard_tree_cache();
m_load_state.state = State::FREE_OLD_TREES;
break;
case State::FREE_OLD_TREES:
m_cached_trees.clear();
m_load_state.state = State::INIT_NEW_TREES;
break;
case State::INIT_NEW_TREES:
{
size_t time_of_day_count = 0;
size_t vis_temp_len = 0;
size_t max_draw = 0;
for (size_t tree_idx = 0; tree_idx < lev_data->tfrag_trees.size(); tree_idx++) {
size_t idx_buffer_len = 0;
const auto& tree = lev_data->tfrag_trees[tree_idx];
m_cached_trees.emplace_back();
auto& tree_cache = m_cached_trees.back();
tree_cache.kind = tree.kind;
if (std::find(tree_kinds.begin(), tree_kinds.end(), tree.kind) != tree_kinds.end()) {
max_draw = std::max(tree.draws.size(), max_draw);
for (auto& draw : tree.draws) {
idx_buffer_len += draw.vertex_index_stream.size();
}
time_of_day_count = std::max(tree.colors.size(), time_of_day_count);
u32 verts = tree.vertices.size();
glGenVertexArrays(1, &tree_cache.vao);
glBindVertexArray(tree_cache.vao);
glGenBuffers(1, &tree_cache.vertex_buffer);
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.tod_cache = swizzle_time_of_day(tree.colors);
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.index_buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tree_cache.index_buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr,
GL_STREAM_DRAW);
tree_cache.index_list.resize(idx_buffer_len);
glGenTextures(1, &tree_cache.time_of_day_texture);
glBindTexture(GL_TEXTURE_1D, tree_cache.time_of_day_texture);
// just fill with zeros. this lets use use the faster texsubimage later
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA,
GL_UNSIGNED_INT_8_8_8_8, m_color_result.data());
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.draw_idx_temp.resize(max_draw);
assert(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT);
m_load_state.state = UPLOAD_VERTS;
m_load_state.vert = 0;
} break;
case State::UPLOAD_VERTS: {
constexpr u32 MAX_VERTS = 40000;
bool remaining = false;
for (size_t tree_idx = 0; tree_idx < lev_data->tie_trees.size(); tree_idx++) {
const auto& tree = lev_data->tfrag_trees[tree_idx];
if (std::find(tree_kinds.begin(), tree_kinds.end(), tree.kind) != tree_kinds.end()) {
u32 verts = tree.vertices.size();
u32 start_vert = (m_load_state.vert) * MAX_VERTS;
u32 end_vert = std::min(verts, (m_load_state.vert + 1) * MAX_VERTS);
if (end_vert > start_vert) {
glBindVertexArray(m_cached_trees[tree_idx].vao);
glBindBuffer(GL_ARRAY_BUFFER, m_cached_trees[tree_idx].vertex_buffer);
glBufferSubData(GL_ARRAY_BUFFER, start_vert * sizeof(tfrag3::PreloadedVertex),
(end_vert - start_vert) * sizeof(tfrag3::PreloadedVertex),
tree.vertices.data() + start_vert);
if (end_vert < verts) {
remaining = true;
}
}
}
}
m_load_state.vert++;
if (!remaining) {
m_load_state.state = INIT_TEX;
m_load_state.tex_id = 0;
}
} break;
case State::INIT_TEX:
for (size_t max_tex =
std::min((size_t)m_load_state.tex_id + MAX_TEX_PER_FRAME, lev_data->textures.size());
m_load_state.tex_id < max_tex; m_load_state.tex_id++) {
auto& tex = lev_data->textures[m_load_state.tex_id];
GLuint gl_tex;
glGenTextures(1, &gl_tex);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.w, tex.h, 0, GL_RGBA,
GL_UNSIGNED_INT_8_8_8_8_REV, tex.data.data());
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glGenerateMipmap(GL_TEXTURE_2D);
float aniso = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, aniso);
m_textures.push_back(gl_tex);
}
return m_load_state.tex_id == lev_data->textures.size();
break;
default:
assert(false);
}
return false;
}
bool Tfrag3::setup_for_level(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
const std::string& level,
SharedRenderState* render_state) {
// 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);
// first, get the level in memory
Timer tfrag3_setup_timer;
// make sure we have the level data.
auto lev_data = render_state->loader.get_tfrag3_level(level);
if (m_level_name != level) {
Timer tfrag3_setup_timer;
fmt::print("new level for tfrag3: {} -> {}\n", m_level_name, level);
fmt::print("discarding old stuff\n");
discard_tree_cache();
fmt::print("level has {} trees\n", lev_data->tfrag_trees.size());
m_cached_trees.clear();
size_t time_of_day_count = 0;
size_t vis_temp_len = 0;
size_t max_draw = 0;
for (size_t tree_idx = 0; tree_idx < lev_data->tfrag_trees.size(); tree_idx++) {
size_t idx_buffer_len = 0;
const auto& tree = lev_data->tfrag_trees[tree_idx];
m_cached_trees.emplace_back();
auto& tree_cache = m_cached_trees.back();
tree_cache.kind = tree.kind;
if (std::find(tree_kinds.begin(), tree_kinds.end(), tree.kind) != tree_kinds.end()) {
max_draw = std::max(tree.draws.size(), max_draw);
for (auto& draw : tree.draws) {
idx_buffer_len += draw.vertex_index_stream.size();
}
time_of_day_count = std::max(tree.colors.size(), time_of_day_count);
u32 verts = tree.vertices.size();
fmt::print(" tree {} has {} verts ({} kB) and {} draws\n", tree_idx, verts,
verts * sizeof(tfrag3::PreloadedVertex) / 1024.f, tree.draws.size());
glGenVertexArrays(1, &tree_cache.vao);
glBindVertexArray(tree_cache.vao);
glGenBuffers(1, &tree_cache.vertex_buffer);
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.tod_cache = swizzle_time_of_day(tree.colors);
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);
glBufferSubData(GL_ARRAY_BUFFER, 0, verts * sizeof(tfrag3::PreloadedVertex),
tree.vertices.data());
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.index_buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tree_cache.index_buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr,
GL_STREAM_DRAW);
tree_cache.index_list.resize(idx_buffer_len);
glGenTextures(1, &tree_cache.time_of_day_texture);
glBindTexture(GL_TEXTURE_1D, tree_cache.time_of_day_texture);
// just fill with zeros. this lets use use the faster texsubimage later
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA,
GL_UNSIGNED_INT_8_8_8_8, m_color_result.data());
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindVertexArray(0);
}
}
fmt::print("TFRAG temporary vis output size: {}\n", vis_temp_len);
m_cache.vis_temp.resize(vis_temp_len);
fmt::print("TFRAG max draws/tree: {}\n", max_draw);
m_cache.draw_idx_temp.resize(max_draw);
fmt::print("level has {} textures\n", lev_data->textures.size());
for (auto& tex : lev_data->textures) {
GLuint gl_tex;
// fmt::print(" tex: {} x {} {} {}\n", tex.w, tex.h, tex.debug_name, tex.debug_tpage_name);
glGenTextures(1, &gl_tex);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.w, tex.h, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV,
tex.data.data());
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glGenerateMipmap(GL_TEXTURE_2D);
float aniso = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, aniso);
m_textures.push_back(gl_tex);
}
fmt::print("level max time of day: {}\n", time_of_day_count);
assert(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT);
m_level_name = level;
fmt::print("TFRAG3 setup: {:.3f}\n", tfrag3_setup_timer.getSeconds());
if (!lev_data) {
m_has_level = false;
return false;
}
int init_load_state = m_load_state.state;
if (m_level_name != level) {
m_has_level = false;
if (!m_load_state.loading) {
m_load_state.loading = true;
m_load_state.state = State::FIRST;
}
if (update_load(tree_kinds, lev_data)) {
m_has_level = true;
m_level_name = level;
m_load_state.loading = false;
}
} else {
m_has_level = true;
}
if (tfrag3_setup_timer.getMs() > 5) {
fmt::print("TFRAG slow setup: {:.1f}ms s {}\n", tfrag3_setup_timer.getMs(), init_load_state);
}
return m_has_level;
}
void Tfrag3::render_tree(const TfragRenderSettings& settings,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
if (!m_has_level) {
return;
}
auto& tree = m_cached_trees.at(settings.tree_idx);
assert(tree.kind != tfrag3::TFragmentTreeKind::INVALID);
@@ -338,9 +406,6 @@ void Tfrag3::discard_tree_cache() {
glDeleteVertexArrays(1, &tree.vao);
}
}
// delete textures and stuff.
m_cached_trees.clear();
}
namespace {
+22 -1
View File
@@ -29,7 +29,7 @@ class Tfrag3 {
SharedRenderState* render_state,
ScopedProfilerNode& prof);
void setup_for_level(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
bool setup_for_level(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
const std::string& level,
SharedRenderState* render_state);
void discard_tree_cache();
@@ -44,6 +44,9 @@ class Tfrag3 {
math::Vector4f rgba;
};
bool update_load(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
const tfrag3::Level* lev_data);
private:
struct TreeCache {
tfrag3::TFragmentTreeKind kind;
@@ -93,5 +96,23 @@ class Tfrag3 {
static constexpr int DEBUG_TRI_COUNT = 4096;
std::vector<DebugVertex> m_debug_vert_data;
bool m_has_level = false;
bool m_use_fast_time_of_day = true;
enum State : u32 {
FIRST = 0,
DISCARD_TREE = 0,
FREE_OLD_TREES = 1,
INIT_NEW_TREES = 2,
UPLOAD_VERTS = 3,
INIT_TEX = 4,
};
struct {
bool loading = false;
State state;
u32 tex_id = 0;
u32 vert = 0;
} m_load_state;
static constexpr int MAX_TEX_PER_FRAME = 4;
};
+219 -157
View File
@@ -2,177 +2,236 @@
#include "third-party/imgui/imgui.h"
Tie3::Tie3(const std::string& name, BucketId my_id) : BucketRenderer(name, my_id) {}
Tie3::Tie3(const std::string& name, BucketId my_id) : BucketRenderer(name, my_id) {
// 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);
}
Tie3::~Tie3() {
discard_tree_cache();
}
bool Tie3::update_load(const tfrag3::Level* lev_data) {
switch (m_load_state.state) {
case DISCARD_TREE:
m_wind_vectors.clear();
// We changed level!
discard_tree_cache();
m_trees.resize(lev_data->tie_trees.size());
m_load_state.state = INIT_NEW_TREES;
break;
case INIT_NEW_TREES: {
size_t time_of_day_count = 0;
size_t vis_temp_len = 0;
size_t max_draw = 0;
size_t max_idx_per_draw = 0;
u16 max_wind_idx = 0;
// set up each tree
for (size_t tree_idx = 0; tree_idx < lev_data->tie_trees.size(); tree_idx++) {
size_t idx_buffer_len = 0;
size_t wind_idx_buffer_len = 0;
const auto& tree = lev_data->tie_trees[tree_idx];
max_draw = std::max(tree.static_draws.size(), max_draw);
for (auto& draw : tree.static_draws) {
idx_buffer_len += draw.vertex_index_stream.size();
max_idx_per_draw = std::max(max_idx_per_draw, draw.vertex_index_stream.size());
}
for (auto& draw : tree.instanced_wind_draws) {
wind_idx_buffer_len += draw.vertex_index_stream.size();
max_idx_per_draw = std::max(max_idx_per_draw, draw.vertex_index_stream.size());
}
for (auto& inst : tree.instance_info) {
max_wind_idx = std::max(max_wind_idx, inst.wind_idx);
}
time_of_day_count = std::max(tree.colors.size(), time_of_day_count);
u32 verts = tree.vertices.size();
fmt::print(" tree {} has {} verts ({} kB) and {} draws\n", tree_idx, verts,
verts * sizeof(tfrag3::PreloadedVertex) / 1024.f, tree.static_draws.size());
glGenVertexArrays(1, &m_trees[tree_idx].vao);
glBindVertexArray(m_trees[tree_idx].vao);
glGenBuffers(1, &m_trees[tree_idx].vertex_buffer);
m_trees[tree_idx].vert_count = verts;
m_trees[tree_idx].draws = &tree.static_draws; // todo - should we just copy this?
m_trees[tree_idx].colors = &tree.colors;
m_trees[tree_idx].vis = &tree.bvh;
m_trees[tree_idx].instance_info = &tree.instance_info;
m_trees[tree_idx].wind_draws = &tree.instanced_wind_draws;
vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size());
m_trees[tree_idx].tod_cache = swizzle_time_of_day(tree.colors);
glBindBuffer(GL_ARRAY_BUFFER, m_trees[tree_idx].vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, verts * sizeof(tfrag3::PreloadedVertex), nullptr,
GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
// glBufferSubData(GL_ARRAY_BUFFER, 0, verts * sizeof(tfrag3::PreloadedVertex),
// tree.vertices.data());
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, &m_trees[tree_idx].index_buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_trees[tree_idx].index_buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr,
GL_STREAM_DRAW);
m_trees[tree_idx].index_list.resize(idx_buffer_len);
if (wind_idx_buffer_len > 0) {
m_trees[tree_idx].wind_matrix_cache.resize(tree.instance_info.size());
m_trees[tree_idx].has_wind = true;
glGenBuffers(1, &m_trees[tree_idx].wind_vertex_index_buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_trees[tree_idx].wind_vertex_index_buffer);
std::vector<u32> temp;
temp.resize(wind_idx_buffer_len);
u32 off = 0;
for (auto& draw : tree.instanced_wind_draws) {
m_trees[tree_idx].wind_vertex_index_offsets.push_back(off);
memcpy(temp.data() + off, draw.vertex_index_stream.data(),
draw.vertex_index_stream.size() * sizeof(u32));
off += draw.vertex_index_stream.size();
}
glBufferData(GL_ELEMENT_ARRAY_BUFFER, wind_idx_buffer_len * sizeof(u32), temp.data(),
GL_STATIC_DRAW);
}
glActiveTexture(GL_TEXTURE10);
glGenTextures(1, &m_trees[tree_idx].time_of_day_texture);
glBindTexture(GL_TEXTURE_1D, m_trees[tree_idx].time_of_day_texture);
// just fill with zeros. this lets use use the faster texsubimage later
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA,
GL_UNSIGNED_INT_8_8_8_8, m_color_result.data());
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindVertexArray(0);
}
fmt::print("TIE temporary vis output size: {}\n", vis_temp_len);
m_cache.vis_temp.resize(vis_temp_len);
fmt::print("TIE max draws/tree: {}\n", max_draw);
m_cache.draw_idx_temp.resize(max_draw);
fmt::print("TIE draw with the most verts: {}\n", max_idx_per_draw);
fmt::print("wind: {}\n", max_wind_idx);
m_wind_vectors.resize(4 * max_wind_idx + 4); // 4x u32's per wind.
fmt::print("level max time of day: {}\n", time_of_day_count);
assert(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT);
}
m_load_state.state = UPLOAD_VERTS;
m_load_state.vert = 0;
break;
case State::UPLOAD_VERTS: {
constexpr u32 MAX_VERTS = 40000;
bool remaining = false;
for (size_t tree_idx = 0; tree_idx < lev_data->tie_trees.size(); tree_idx++) {
const auto& tree = lev_data->tie_trees[tree_idx];
u32 verts = tree.vertices.size();
u32 start_vert = (m_load_state.vert) * MAX_VERTS;
u32 end_vert = std::min(verts, (m_load_state.vert + 1) * MAX_VERTS);
if (end_vert > start_vert) {
glBindVertexArray(m_trees[tree_idx].vao);
glBindBuffer(GL_ARRAY_BUFFER, m_trees[tree_idx].vertex_buffer);
glBufferSubData(GL_ARRAY_BUFFER, start_vert * sizeof(tfrag3::PreloadedVertex),
(end_vert - start_vert) * sizeof(tfrag3::PreloadedVertex),
tree.vertices.data() + start_vert);
if (end_vert < verts) {
remaining = true;
}
}
}
m_load_state.vert++;
if (!remaining) {
m_load_state.state = INIT_TEX;
m_load_state.tex = 0;
}
} break;
case State::INIT_TEX:
for (size_t max_tex = std::min((size_t)m_load_state.tex + 3, lev_data->textures.size());
m_load_state.tex < max_tex; m_load_state.tex++) {
auto& tex = lev_data->textures[m_load_state.tex];
GLuint gl_tex;
glGenTextures(1, &gl_tex);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.w, tex.h, 0, GL_RGBA,
GL_UNSIGNED_INT_8_8_8_8_REV, tex.data.data());
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glGenerateMipmap(GL_TEXTURE_2D);
float aniso = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, aniso);
m_textures.push_back(gl_tex);
}
return m_load_state.tex == lev_data->textures.size();
break;
default:
assert(false);
}
return false;
}
/*!
* Set up all OpenGL and temporary buffers for a given level name.
* The level name should be the 3 character short name.
*/
void Tie3::setup_for_level(const std::string& level, SharedRenderState* render_state) {
bool Tie3::setup_for_level(const std::string& level, SharedRenderState* render_state) {
// make sure we have the level data.
// TODO: right now this will wait to load from disk and unpack it.
Timer tfrag3_setup_timer;
auto lev_data = render_state->loader.get_tfrag3_level(level);
// 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);
if (!lev_data) {
return false;
}
int init_load_state = m_load_state.state;
if (m_level_name != level) {
Timer tie_setup_timer;
m_wind_vectors.clear();
// We changed level!
fmt::print("TIE3 level change! {} -> {}\n", m_level_name, level);
fmt::print(" Removing old level...\n");
discard_tree_cache();
fmt::print(" New level has {} tie trees\n", lev_data->tie_trees.size());
m_trees.resize(lev_data->tie_trees.size());
size_t time_of_day_count = 0;
size_t vis_temp_len = 0;
size_t max_draw = 0;
size_t max_idx_per_draw = 0;
u16 max_wind_idx = 0;
// set up each tree
for (size_t tree_idx = 0; tree_idx < lev_data->tie_trees.size(); tree_idx++) {
size_t idx_buffer_len = 0;
size_t wind_idx_buffer_len = 0;
const auto& tree = lev_data->tie_trees[tree_idx];
max_draw = std::max(tree.static_draws.size(), max_draw);
for (auto& draw : tree.static_draws) {
idx_buffer_len += draw.vertex_index_stream.size();
max_idx_per_draw = std::max(max_idx_per_draw, draw.vertex_index_stream.size());
}
for (auto& draw : tree.instanced_wind_draws) {
wind_idx_buffer_len += draw.vertex_index_stream.size();
max_idx_per_draw = std::max(max_idx_per_draw, draw.vertex_index_stream.size());
}
for (auto& inst : tree.instance_info) {
max_wind_idx = std::max(max_wind_idx, inst.wind_idx);
}
time_of_day_count = std::max(tree.colors.size(), time_of_day_count);
u32 verts = tree.vertices.size();
fmt::print(" tree {} has {} verts ({} kB) and {} draws\n", tree_idx, verts,
verts * sizeof(tfrag3::PreloadedVertex) / 1024.f, tree.static_draws.size());
glGenVertexArrays(1, &m_trees[tree_idx].vao);
glBindVertexArray(m_trees[tree_idx].vao);
glGenBuffers(1, &m_trees[tree_idx].vertex_buffer);
m_trees[tree_idx].vert_count = verts;
m_trees[tree_idx].draws = &tree.static_draws; // todo - should we just copy this?
m_trees[tree_idx].colors = &tree.colors;
m_trees[tree_idx].vis = &tree.bvh;
m_trees[tree_idx].instance_info = &tree.instance_info;
m_trees[tree_idx].wind_draws = &tree.instanced_wind_draws;
vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size());
m_trees[tree_idx].tod_cache = swizzle_time_of_day(tree.colors);
glBindBuffer(GL_ARRAY_BUFFER, m_trees[tree_idx].vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, verts * sizeof(tfrag3::PreloadedVertex), nullptr,
GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glBufferSubData(GL_ARRAY_BUFFER, 0, verts * sizeof(tfrag3::PreloadedVertex),
tree.vertices.data());
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, &m_trees[tree_idx].index_buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_trees[tree_idx].index_buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr, GL_STREAM_DRAW);
m_trees[tree_idx].index_list.resize(idx_buffer_len);
if (wind_idx_buffer_len > 0) {
m_trees[tree_idx].wind_matrix_cache.resize(tree.instance_info.size());
m_trees[tree_idx].has_wind = true;
glGenBuffers(1, &m_trees[tree_idx].wind_vertex_index_buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_trees[tree_idx].wind_vertex_index_buffer);
std::vector<u32> temp;
temp.resize(wind_idx_buffer_len);
u32 off = 0;
for (auto& draw : tree.instanced_wind_draws) {
m_trees[tree_idx].wind_vertex_index_offsets.push_back(off);
memcpy(temp.data() + off, draw.vertex_index_stream.data(),
draw.vertex_index_stream.size() * sizeof(u32));
off += draw.vertex_index_stream.size();
}
glBufferData(GL_ELEMENT_ARRAY_BUFFER, wind_idx_buffer_len * sizeof(u32), temp.data(),
GL_STATIC_DRAW);
}
glActiveTexture(GL_TEXTURE10);
glGenTextures(1, &m_trees[tree_idx].time_of_day_texture);
glBindTexture(GL_TEXTURE_1D, m_trees[tree_idx].time_of_day_texture);
// just fill with zeros. this lets use use the faster texsubimage later
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA,
GL_UNSIGNED_INT_8_8_8_8, m_color_result.data());
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindVertexArray(0);
m_has_level = false;
if (!m_load_state.loading) {
m_load_state.loading = true;
m_load_state.state = State::FIRST;
}
fmt::print("TIE temporary vis output size: {}\n", vis_temp_len);
m_cache.vis_temp.resize(vis_temp_len);
fmt::print("TIE max draws/tree: {}\n", max_draw);
m_cache.draw_idx_temp.resize(max_draw);
fmt::print("TIE draw with the most verts: {}\n", max_idx_per_draw);
// todo share textures
fmt::print("level has {} textures\n", lev_data->textures.size());
for (auto& tex : lev_data->textures) {
GLuint gl_tex;
glGenTextures(1, &gl_tex);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.w, tex.h, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV,
tex.data.data());
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glGenerateMipmap(GL_TEXTURE_2D);
float aniso = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, aniso);
m_textures.push_back(gl_tex);
if (update_load(lev_data)) {
m_has_level = true;
m_level_name = level;
m_load_state.loading = false;
}
fmt::print("level max time of day: {}\n", time_of_day_count);
assert(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT);
fmt::print("wind: {}\n", max_wind_idx);
m_wind_vectors.resize(4 * max_wind_idx + 4); // 4x u32's per wind.
m_level_name = level;
fmt::print("TIE setup: {:.3f}\n", tie_setup_timer.getSeconds());
} else {
m_has_level = true;
}
if (tfrag3_setup_timer.getMs() > 5) {
fmt::print("TIE setup: {:.1f}ms s {}\n", tfrag3_setup_timer.getMs(), init_load_state);
}
return m_has_level;
}
void vector_min_in_place(math::Vector4f& v, float val) {
@@ -315,7 +374,7 @@ void Tie3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfi
}
if (m_override_level && m_pending_user_level) {
setup_for_level(*m_pending_user_level, render_state);
m_has_level = setup_for_level(*m_pending_user_level, render_state);
m_pending_user_level = {};
}
@@ -383,7 +442,7 @@ void Tie3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfi
}
}
if (!m_override_level) {
setup_for_level(m_pc_port_data.level_name, render_state);
m_has_level = setup_for_level(m_pc_port_data.level_name, render_state);
}
render_all_trees(settings, render_state, prof);
// todo render all...
@@ -394,7 +453,7 @@ void Tie3::render_all_trees(const TfragRenderSettings& settings,
ScopedProfilerNode& prof) {
Timer all_tree_timer;
if (m_override_level && m_pending_user_level) {
setup_for_level(*m_pending_user_level, render_state);
m_has_level = setup_for_level(*m_pending_user_level, render_state);
m_pending_user_level = {};
}
for (u32 i = 0; i < m_trees.size(); i++) {
@@ -525,6 +584,9 @@ void Tie3::render_tree(int idx,
tree.perf.verts = 0;
tree.perf.full_draws = 0;
tree.perf.wind_draws = 0;
if (!m_has_level) {
return;
}
if (m_color_result.size() < tree.colors->size()) {
m_color_result.resize(tree.colors->size());
+18 -1
View File
@@ -21,7 +21,7 @@ class Tie3 : public BucketRenderer {
const TfragRenderSettings& settings,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
void setup_for_level(const std::string& str, SharedRenderState* render_state);
bool setup_for_level(const std::string& str, SharedRenderState* render_state);
struct WindWork {
u32 paused;
@@ -35,6 +35,7 @@ class Tie3 : public BucketRenderer {
} m_wind_data;
private:
bool update_load(const tfrag3::Level* lev_data);
void discard_tree_cache();
void render_tree_wind(int idx,
const TfragRenderSettings& settings,
@@ -88,6 +89,7 @@ class Tie3 : public BucketRenderer {
static constexpr int TIME_OF_DAY_COLOR_COUNT = 8192;
bool m_has_level = false;
char m_user_level[255] = "vi1";
std::optional<std::string> m_pending_user_level = std::nullopt;
bool m_override_level = false;
@@ -104,4 +106,19 @@ class Tie3 : public BucketRenderer {
float m_wind_multiplier = 1.f;
static_assert(sizeof(WindWork) == 84 * 16);
enum State : u32 {
FIRST = 0,
DISCARD_TREE = 0,
INIT_NEW_TREES = 1,
UPLOAD_VERTS = 2,
INIT_TEX = 4,
};
struct {
bool loading = false;
State state;
u32 tex = 0;
u32 vert = 0;
} m_load_state;
};
+1 -1
View File
@@ -159,7 +159,7 @@ std::vector<std::shared_ptr<TextureRecord>> TexturePool::convert_textures(const
// the sizes given aren't the actual sizes in memory, so if you just use that, you get the
// wrong answer. I solved this in the decompiler by using the size of the actual data, but we
// don't really have that here.
u32 size = ((sizes[0] + sizes[1] + sizes[2] + 255) / 256) * 256;
u32 size = ((sizes[0] + sizes[1] + sizes[2] + 2047) / 256) * 256;
m_tex_converter.upload(memory_base + texture_page.segment[0].block_data_ptr,
texture_page.segment[0].dest, size);
+12 -10
View File
@@ -158,16 +158,18 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; tfrag
(when (logtest? *texture-enable-user* 1)
(dotimes (gp-1 (-> *level* length))
(let ((a1-2 (-> *level* level gp-1)))
(if (= (-> a1-2 status) 'active)
(add-tex-to-dma! *texture-pool* a1-2 0)
)
)
)
)
;; note: PC port uses preconverted tfrag textures.
(#unless PC_PORT
(when (logtest? *texture-enable-user* 1)
(dotimes (gp-1 (-> *level* length))
(let ((a1-2 (-> *level* level gp-1)))
(if (= (-> a1-2 status) 'active)
(add-tex-to-dma! *texture-pool* a1-2 0)
)
)
)
)
)
;; pris