2021-12-04 12:33:18 -05:00
|
|
|
#include "Loader.h"
|
|
|
|
#include "common/util/Timer.h"
|
|
|
|
#include "common/util/FileUtil.h"
|
2022-01-07 11:52:24 -05:00
|
|
|
#include "common/util/compress.h"
|
2021-12-04 12:33:18 -05:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
std::string uppercase_string(const std::string& s) {
|
|
|
|
std::string result;
|
|
|
|
for (auto c : s) {
|
|
|
|
result.push_back(toupper(c));
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Try to get a loaded level by name. It may fail and return nullptr.
|
|
|
|
* Getting a level will reset the counter for the level and prevent it from being kicked out
|
|
|
|
* for a little while.
|
|
|
|
*
|
|
|
|
* This is safe to call from the graphics thread
|
|
|
|
*/
|
2022-01-28 23:44:03 -05:00
|
|
|
const Loader::LevelData* Loader::get_tfrag3_level(const std::string& level_name) {
|
2022-01-07 11:52:24 -05:00
|
|
|
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
2022-01-28 23:44:03 -05:00
|
|
|
const auto& existing = m_loaded_tfrag3_levels.find(level_name);
|
|
|
|
if (existing == m_loaded_tfrag3_levels.end()) {
|
2022-03-02 20:01:37 -05:00
|
|
|
return nullptr;
|
2022-01-07 11:52:24 -05:00
|
|
|
} else {
|
|
|
|
existing->second.frames_since_last_used = 0;
|
2022-01-28 23:44:03 -05:00
|
|
|
return &existing->second.data;
|
2022-01-07 11:52:24 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
/*!
|
|
|
|
* The game calls this to give the loader a hint on which levels we want.
|
|
|
|
* If the loader is not busy, it will begin loading the level.
|
|
|
|
* This should be called on every frame.
|
|
|
|
*/
|
2022-03-02 20:01:37 -05:00
|
|
|
void Loader::set_want_levels(const std::vector<std::string>& levels) {
|
|
|
|
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
2022-03-26 15:53:44 -04:00
|
|
|
m_desired_levels = levels;
|
2022-03-02 20:01:37 -05:00
|
|
|
if (!m_level_to_load.empty()) {
|
|
|
|
// can't do anything, we're loading a level right now
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_initializing_tfrag3_levels.empty()) {
|
|
|
|
// can't do anything, we're initializing a level right now
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
// loader isn't busy, try to load one of the requested levels.
|
2022-03-02 20:01:37 -05:00
|
|
|
for (auto& lev : levels) {
|
|
|
|
auto it = m_loaded_tfrag3_levels.find(lev);
|
|
|
|
if (it == m_loaded_tfrag3_levels.end()) {
|
2022-03-26 15:53:44 -04:00
|
|
|
// we haven't loaded it yet. Request this level to load and wake up the thread.
|
2022-03-02 20:01:37 -05:00
|
|
|
m_level_to_load = lev;
|
|
|
|
lk.unlock();
|
|
|
|
m_loader_cv.notify_all();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
/*!
|
|
|
|
* Loader function that runs in a completely separate thread.
|
|
|
|
* This is used for file I/O and unpacking.
|
|
|
|
*/
|
2022-01-07 11:52:24 -05:00
|
|
|
void Loader::loader_thread() {
|
|
|
|
while (!m_want_shutdown) {
|
|
|
|
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
2022-03-26 15:53:44 -04:00
|
|
|
|
|
|
|
// this will keep us asleep until we've got a level to load.
|
2022-01-07 11:52:24 -05:00
|
|
|
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;
|
2022-03-26 15:53:44 -04:00
|
|
|
// don't hold the lock while reading the file.
|
2022-01-07 11:52:24 -05:00
|
|
|
lk.unlock();
|
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
// simulate slower hard drive (so that the loader thread can lose to the game loads)
|
|
|
|
// std::this_thread::sleep_for(std::chrono::milliseconds(1500));
|
|
|
|
|
|
|
|
// load the fr3 file
|
2021-12-04 12:33:18 -05:00
|
|
|
Timer disk_timer;
|
|
|
|
auto data = file_util::read_binary_file(
|
2022-01-07 11:52:24 -05:00
|
|
|
file_util::get_file_path({fmt::format("assets/{}.fr3", uppercase_string(lev))}));
|
2021-12-04 12:33:18 -05:00
|
|
|
double disk_load_time = disk_timer.getSeconds();
|
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
// the FR3 files are compressed
|
2022-01-07 11:52:24 -05:00
|
|
|
Timer decomp_timer;
|
|
|
|
auto decomp_data = compression::decompress_zstd(data.data(), data.size());
|
|
|
|
double decomp_time = decomp_timer.getSeconds();
|
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
// Read back into the tfrag3::Level structure
|
2021-12-04 12:33:18 -05:00
|
|
|
Timer import_timer;
|
2022-01-07 11:52:24 -05:00
|
|
|
auto result = std::make_unique<tfrag3::Level>();
|
|
|
|
Serializer ser(decomp_data.data(), decomp_data.size());
|
|
|
|
result->serialize(ser);
|
2021-12-04 12:33:18 -05:00
|
|
|
double import_time = import_timer.getSeconds();
|
2022-02-16 22:13:18 -05:00
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
// and finally "unpack", which creates the vertex data we'll upload to the GPU
|
2022-02-16 22:13:18 -05:00
|
|
|
Timer unpack_timer;
|
|
|
|
for (auto& tie_tree : result->tie_trees) {
|
|
|
|
for (auto& tree : tie_tree) {
|
|
|
|
tree.unpack();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (auto& t_tree : result->tfrag_trees) {
|
|
|
|
for (auto& tree : t_tree) {
|
|
|
|
tree.unpack();
|
|
|
|
}
|
|
|
|
}
|
2022-03-29 21:38:48 -04:00
|
|
|
|
|
|
|
for (auto& shrub_tree : result->shrub_trees) {
|
|
|
|
shrub_tree.unpack();
|
|
|
|
}
|
2022-02-16 22:13:18 -05:00
|
|
|
fmt::print(
|
|
|
|
"------------> Load from file: {:.3f}s, import {:.3f}s, decomp {:.3f}s unpack {:.3f}s\n",
|
|
|
|
disk_load_time, import_time, decomp_time, unpack_timer.getSeconds());
|
2022-01-07 11:52:24 -05:00
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
// grab the lock again
|
2022-01-07 11:52:24 -05:00
|
|
|
lk.lock();
|
2022-03-26 15:53:44 -04:00
|
|
|
// move this level to "initializing" state.
|
|
|
|
m_initializing_tfrag3_levels[lev].data = {}; // reset load state
|
2022-01-28 23:44:03 -05:00
|
|
|
m_initializing_tfrag3_levels[lev].data.level = std::move(result);
|
2022-01-07 11:52:24 -05:00
|
|
|
m_level_to_load = "";
|
2022-03-26 15:53:44 -04:00
|
|
|
m_file_load_done_cv.notify_all();
|
2022-01-07 11:52:24 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
/*!
|
|
|
|
* Load a "common" FR3 file that has non-level textures.
|
|
|
|
* This should be called during initialization, before any threaded loading goes on.
|
|
|
|
*/
|
2022-03-02 20:01:37 -05:00
|
|
|
void Loader::load_common(TexturePool& tex_pool, const std::string& name) {
|
|
|
|
auto data =
|
|
|
|
file_util::read_binary_file(file_util::get_file_path({fmt::format("assets/{}.fr3", name)}));
|
|
|
|
|
|
|
|
auto decomp_data = compression::decompress_zstd(data.data(), data.size());
|
|
|
|
Serializer ser(decomp_data.data(), decomp_data.size());
|
|
|
|
m_common_level.serialize(ser);
|
|
|
|
for (auto& tex : m_common_level.textures) {
|
|
|
|
add_texture(tex_pool, tex, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
/*!
|
|
|
|
* Upload a texture to the GPU, and give it to the pool.
|
|
|
|
*/
|
2022-03-02 20:01:37 -05:00
|
|
|
u64 Loader::add_texture(TexturePool& pool, const tfrag3::Texture& tex, bool is_common) {
|
|
|
|
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);
|
|
|
|
if (tex.load_to_pool) {
|
|
|
|
TextureInput in;
|
|
|
|
in.page_name = tex.debug_tpage_name;
|
|
|
|
in.name = tex.debug_name;
|
|
|
|
in.w = tex.w;
|
|
|
|
in.h = tex.h;
|
|
|
|
in.gpu_texture = gl_tex;
|
|
|
|
in.common = is_common;
|
|
|
|
in.combo_id = tex.combo_id;
|
|
|
|
in.src_data = (const u8*)tex.data.data();
|
|
|
|
pool.give_texture(in);
|
|
|
|
}
|
|
|
|
|
|
|
|
return gl_tex;
|
|
|
|
}
|
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
bool Loader::init_tfrag(Timer& timer, LevelData& data) {
|
|
|
|
if (data.tfrag_load_done) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data.level->tfrag_trees.front().empty()) {
|
|
|
|
data.tfrag_load_done = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!data.tfrag_opengl_created) {
|
|
|
|
for (int geo = 0; geo < tfrag3::TFRAG_GEOS; geo++) {
|
|
|
|
auto& in_trees = data.level->tfrag_trees[geo];
|
|
|
|
for (auto& in_tree : in_trees) {
|
|
|
|
GLuint& tree_out = data.tfrag_vertex_data[geo].emplace_back();
|
|
|
|
glGenBuffers(1, &tree_out);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, tree_out);
|
|
|
|
glBufferData(GL_ARRAY_BUFFER,
|
|
|
|
in_tree.unpacked.vertices.size() * sizeof(tfrag3::PreloadedVertex), nullptr,
|
|
|
|
GL_STATIC_DRAW);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
data.tfrag_opengl_created = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
constexpr u32 CHUNK_SIZE = 32768;
|
|
|
|
u32 uploaded_bytes = 0;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
const auto& tree = data.level->tfrag_trees[data.tfrag_next_geo][data.tfrag_next_tree];
|
|
|
|
u32 end_vert_in_tree = tree.unpacked.vertices.size();
|
|
|
|
// the number of vertices we'd need to finish the tree right now
|
|
|
|
size_t num_verts_left_in_tree = end_vert_in_tree - data.tfrag_next_vert;
|
|
|
|
size_t start_vert_for_chunk;
|
|
|
|
size_t end_vert_for_chunk;
|
|
|
|
|
|
|
|
bool complete_tree;
|
|
|
|
|
|
|
|
if (num_verts_left_in_tree > CHUNK_SIZE) {
|
|
|
|
complete_tree = false;
|
|
|
|
// should only do partial
|
|
|
|
start_vert_for_chunk = data.tfrag_next_vert;
|
|
|
|
end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE;
|
|
|
|
data.tfrag_next_vert += CHUNK_SIZE;
|
|
|
|
} else {
|
|
|
|
// should do all!
|
|
|
|
start_vert_for_chunk = data.tfrag_next_vert;
|
|
|
|
end_vert_for_chunk = end_vert_in_tree;
|
|
|
|
complete_tree = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// glBindVertexArray(m_trees[m_load_state.vert_geo][m_load_state.vert_tree].vao);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER,
|
|
|
|
data.tfrag_vertex_data[data.tfrag_next_geo][data.tfrag_next_tree]);
|
|
|
|
u32 upload_size = (end_vert_for_chunk - start_vert_for_chunk) * sizeof(tfrag3::PreloadedVertex);
|
|
|
|
glBufferSubData(GL_ARRAY_BUFFER, start_vert_for_chunk * sizeof(tfrag3::PreloadedVertex),
|
|
|
|
upload_size, tree.unpacked.vertices.data() + start_vert_for_chunk);
|
|
|
|
uploaded_bytes += upload_size;
|
|
|
|
|
|
|
|
if (complete_tree) {
|
|
|
|
// and move on to next tree
|
|
|
|
data.tfrag_next_vert = 0;
|
|
|
|
data.tfrag_next_tree++;
|
|
|
|
if (data.tfrag_next_tree >= data.level->tfrag_trees[data.tfrag_next_geo].size()) {
|
|
|
|
data.tfrag_next_tree = 0;
|
|
|
|
data.tfrag_next_geo++;
|
|
|
|
if (data.tfrag_next_geo >= tfrag3::TFRAG_GEOS) {
|
|
|
|
data.tfrag_load_done = true;
|
|
|
|
data.tfrag_next_tree = 0;
|
|
|
|
data.tfrag_next_geo = 0;
|
|
|
|
data.tfrag_next_vert = 0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (timer.getMs() > Loader::TIE_LOAD_BUDGET || (uploaded_bytes / 1024) > 2048) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-29 21:38:48 -04:00
|
|
|
bool Loader::init_shrub(Timer& timer, LevelData& data) {
|
|
|
|
if (data.shrub_load_done) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data.level->shrub_trees.empty()) {
|
|
|
|
data.shrub_load_done = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!data.shrub_opengl_created) {
|
|
|
|
for (auto& in_tree : data.level->shrub_trees) {
|
|
|
|
GLuint& tree_out = data.shrub_vertex_data.emplace_back();
|
|
|
|
glGenBuffers(1, &tree_out);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, tree_out);
|
|
|
|
glBufferData(GL_ARRAY_BUFFER,
|
|
|
|
in_tree.unpacked.vertices.size() * sizeof(tfrag3::ShrubGpuVertex), nullptr,
|
|
|
|
GL_STATIC_DRAW);
|
|
|
|
}
|
|
|
|
data.shrub_opengl_created = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
constexpr u32 CHUNK_SIZE = 32768;
|
|
|
|
u32 uploaded_bytes = 0;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
const auto& tree = data.level->shrub_trees[data.shrub_next_tree];
|
|
|
|
u32 end_vert_in_tree = tree.unpacked.vertices.size();
|
|
|
|
// the number of vertices we'd need to finish the tree right now
|
|
|
|
size_t num_verts_left_in_tree = end_vert_in_tree - data.shrub_next_vert;
|
|
|
|
size_t start_vert_for_chunk;
|
|
|
|
size_t end_vert_for_chunk;
|
|
|
|
|
|
|
|
bool complete_tree;
|
|
|
|
|
|
|
|
if (num_verts_left_in_tree > CHUNK_SIZE) {
|
|
|
|
complete_tree = false;
|
|
|
|
// should only do partial
|
|
|
|
start_vert_for_chunk = data.shrub_next_vert;
|
|
|
|
end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE;
|
|
|
|
data.shrub_next_vert += CHUNK_SIZE;
|
|
|
|
} else {
|
|
|
|
// should do all!
|
|
|
|
start_vert_for_chunk = data.shrub_next_vert;
|
|
|
|
end_vert_for_chunk = end_vert_in_tree;
|
|
|
|
complete_tree = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// glBindVertexArray(m_trees[m_load_state.vert_geo][m_load_state.vert_tree].vao);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, data.shrub_vertex_data[data.shrub_next_tree]);
|
|
|
|
u32 upload_size = (end_vert_for_chunk - start_vert_for_chunk) * sizeof(tfrag3::ShrubGpuVertex);
|
|
|
|
glBufferSubData(GL_ARRAY_BUFFER, start_vert_for_chunk * sizeof(tfrag3::ShrubGpuVertex),
|
|
|
|
upload_size, tree.unpacked.vertices.data() + start_vert_for_chunk);
|
|
|
|
uploaded_bytes += upload_size;
|
|
|
|
|
|
|
|
if (complete_tree) {
|
|
|
|
// and move on to next tree
|
|
|
|
data.shrub_next_vert = 0;
|
|
|
|
data.shrub_next_tree++;
|
|
|
|
if (data.shrub_next_tree >= data.level->shrub_trees.size()) {
|
|
|
|
data.shrub_load_done = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (timer.getMs() > Loader::TIE_LOAD_BUDGET || (uploaded_bytes / 1024) > 2048) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
bool Loader::init_tie(Timer& timer, LevelData& data) {
|
|
|
|
if (data.tie_load_done) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data.level->tie_trees.front().empty()) {
|
|
|
|
data.tie_load_done = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!data.tie_opengl_created) {
|
|
|
|
for (int geo = 0; geo < tfrag3::TIE_GEOS; geo++) {
|
|
|
|
auto& in_trees = data.level->tie_trees[geo];
|
|
|
|
for (auto& in_tree : in_trees) {
|
|
|
|
LevelData::TieOpenGL& tree_out = data.tie_data[geo].emplace_back();
|
|
|
|
glGenBuffers(1, &tree_out.vertex_buffer);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, tree_out.vertex_buffer);
|
|
|
|
glBufferData(GL_ARRAY_BUFFER,
|
|
|
|
in_tree.unpacked.vertices.size() * sizeof(tfrag3::PreloadedVertex), nullptr,
|
|
|
|
GL_STATIC_DRAW);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
data.tie_opengl_created = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!data.tie_verts_done) {
|
|
|
|
constexpr u32 CHUNK_SIZE = 32768;
|
|
|
|
u32 uploaded_bytes = 0;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
const auto& tree = data.level->tie_trees[data.tie_next_geo][data.tie_next_tree];
|
|
|
|
u32 end_vert_in_tree = tree.unpacked.vertices.size();
|
|
|
|
// the number of vertices we'd need to finish the tree right now
|
|
|
|
size_t num_verts_left_in_tree = end_vert_in_tree - data.tie_next_vert;
|
|
|
|
size_t start_vert_for_chunk;
|
|
|
|
size_t end_vert_for_chunk;
|
|
|
|
|
|
|
|
bool complete_tree;
|
|
|
|
|
|
|
|
if (num_verts_left_in_tree > CHUNK_SIZE) {
|
|
|
|
complete_tree = false;
|
|
|
|
// should only do partial
|
|
|
|
start_vert_for_chunk = data.tie_next_vert;
|
|
|
|
end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE;
|
|
|
|
data.tie_next_vert += CHUNK_SIZE;
|
|
|
|
} else {
|
|
|
|
// should do all!
|
|
|
|
start_vert_for_chunk = data.tie_next_vert;
|
|
|
|
end_vert_for_chunk = end_vert_in_tree;
|
|
|
|
complete_tree = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// glBindVertexArray(m_trees[m_load_state.vert_geo][m_load_state.vert_tree].vao);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER,
|
|
|
|
data.tie_data[data.tie_next_geo][data.tie_next_tree].vertex_buffer);
|
|
|
|
u32 upload_size =
|
|
|
|
(end_vert_for_chunk - start_vert_for_chunk) * sizeof(tfrag3::PreloadedVertex);
|
|
|
|
glBufferSubData(GL_ARRAY_BUFFER, start_vert_for_chunk * sizeof(tfrag3::PreloadedVertex),
|
|
|
|
upload_size, tree.unpacked.vertices.data() + start_vert_for_chunk);
|
|
|
|
uploaded_bytes += upload_size;
|
|
|
|
|
|
|
|
if (complete_tree) {
|
|
|
|
// and move on to next tree
|
|
|
|
data.tie_next_vert = 0;
|
|
|
|
data.tie_next_tree++;
|
|
|
|
if (data.tie_next_tree >= data.level->tie_trees[data.tie_next_geo].size()) {
|
|
|
|
data.tie_next_tree = 0;
|
|
|
|
data.tie_next_geo++;
|
|
|
|
if (data.tie_next_geo >= tfrag3::TIE_GEOS) {
|
|
|
|
data.tie_verts_done = true;
|
|
|
|
data.tie_next_tree = 0;
|
|
|
|
data.tie_next_geo = 0;
|
|
|
|
data.tie_next_vert = 0;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (timer.getMs() > Loader::TIE_LOAD_BUDGET || (uploaded_bytes / 1024) > 2048) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!data.tie_wind_indices_done) {
|
|
|
|
bool abort = false;
|
|
|
|
for (; data.tie_next_geo < tfrag3::TIE_GEOS; data.tie_next_geo++) {
|
|
|
|
auto& geo_trees = data.level->tie_trees[data.tie_next_geo];
|
|
|
|
for (; data.tie_next_tree < geo_trees.size(); data.tie_next_tree++) {
|
|
|
|
if (abort) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
auto& in_tree = geo_trees[data.tie_next_tree];
|
|
|
|
auto& out_tree = data.tie_data[data.tie_next_geo][data.tie_next_tree];
|
|
|
|
size_t wind_idx_buffer_len = 0;
|
|
|
|
for (auto& draw : in_tree.instanced_wind_draws) {
|
|
|
|
wind_idx_buffer_len += draw.vertex_index_stream.size();
|
|
|
|
}
|
|
|
|
if (wind_idx_buffer_len > 0) {
|
|
|
|
out_tree.has_wind = true;
|
|
|
|
glGenBuffers(1, &out_tree.wind_indices);
|
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, out_tree.wind_indices);
|
|
|
|
std::vector<u32> temp;
|
|
|
|
temp.resize(wind_idx_buffer_len);
|
|
|
|
u32 off = 0;
|
|
|
|
for (auto& draw : in_tree.instanced_wind_draws) {
|
|
|
|
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);
|
|
|
|
abort = true;
|
|
|
|
}
|
|
|
|
}
|
2022-04-10 12:25:35 -04:00
|
|
|
data.tie_next_tree = 0;
|
2022-03-26 15:53:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
data.tie_wind_indices_done = true;
|
|
|
|
data.tie_load_done = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Loader::upload_textures(Timer& timer, LevelData& data, TexturePool& texture_pool) {
|
|
|
|
// try to move level from initializing to initialized:
|
|
|
|
|
|
|
|
constexpr int MAX_TEX_BYTES_PER_FRAME = 1024 * 128;
|
|
|
|
|
|
|
|
int bytes_this_run = 0;
|
|
|
|
int tex_this_run = 0;
|
|
|
|
if (data.textures.size() < data.level->textures.size()) {
|
|
|
|
std::unique_lock<std::mutex> tpool_lock(texture_pool.mutex());
|
|
|
|
while (data.textures.size() < data.level->textures.size()) {
|
|
|
|
auto& tex = data.level->textures[data.textures.size()];
|
|
|
|
data.textures.push_back(add_texture(texture_pool, tex, false));
|
|
|
|
bytes_this_run += tex.w * tex.h * 4;
|
|
|
|
tex_this_run++;
|
|
|
|
if (tex_this_run > 20) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (bytes_this_run > MAX_TEX_BYTES_PER_FRAME || timer.getMs() > SHARED_TEXTURE_LOAD_BUDGET) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return data.textures.size() == data.level->textures.size();
|
|
|
|
}
|
|
|
|
|
2022-03-28 18:14:25 -04:00
|
|
|
void Loader::update_blocking(TexturePool& tex_pool) {
|
2022-03-26 15:53:44 -04:00
|
|
|
fmt::print("NOTE: coming out of blackout on next frame, doing all loads now...\n");
|
|
|
|
|
|
|
|
bool missing_levels = true;
|
|
|
|
while (missing_levels) {
|
|
|
|
bool needs_run = true;
|
|
|
|
|
|
|
|
while (needs_run) {
|
|
|
|
needs_run = false;
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
|
|
|
if (!m_level_to_load.empty()) {
|
|
|
|
m_file_load_done_cv.wait(lk, [&]() { return m_level_to_load.empty(); });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
needs_run = true;
|
|
|
|
|
|
|
|
while (needs_run) {
|
|
|
|
needs_run = false;
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
|
|
|
if (!m_initializing_tfrag3_levels.empty()) {
|
|
|
|
needs_run = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (needs_run) {
|
2022-03-28 18:14:25 -04:00
|
|
|
update(tex_pool);
|
2022-03-26 15:53:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
|
|
|
missing_levels = false;
|
|
|
|
for (auto& des : m_desired_levels) {
|
|
|
|
if (m_loaded_tfrag3_levels.find(des) == m_loaded_tfrag3_levels.end()) {
|
|
|
|
fmt::print("blackout loader doing additional level {}...\n", des);
|
|
|
|
missing_levels = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (missing_levels) {
|
|
|
|
set_want_levels(m_desired_levels);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt::print("Blackout loads done. Current status:");
|
|
|
|
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
|
|
|
for (auto& ld : m_loaded_tfrag3_levels) {
|
|
|
|
fmt::print(" {} is loaded.\n", ld.first);
|
|
|
|
}
|
2022-01-07 11:52:24 -05:00
|
|
|
}
|
|
|
|
|
2022-03-28 18:14:25 -04:00
|
|
|
void Loader::update(TexturePool& texture_pool) {
|
2022-01-28 23:44:03 -05:00
|
|
|
Timer loader_timer;
|
|
|
|
|
|
|
|
// only main thread can touch this.
|
|
|
|
for (auto& lev : m_loaded_tfrag3_levels) {
|
|
|
|
lev.second.frames_since_last_used++;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool did_gpu_stuff = false;
|
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
// work on moving initializing to initialized.
|
2022-01-28 23:44:03 -05:00
|
|
|
{
|
2022-03-26 15:53:44 -04:00
|
|
|
// accessing initializing, should lock
|
2022-01-28 23:44:03 -05:00
|
|
|
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
2022-03-26 15:53:44 -04:00
|
|
|
// grab the first initializing level:
|
2022-01-28 23:44:03 -05:00
|
|
|
const auto& it = m_initializing_tfrag3_levels.begin();
|
|
|
|
if (it != m_initializing_tfrag3_levels.end()) {
|
2022-03-26 15:53:44 -04:00
|
|
|
std::string name = it->first;
|
|
|
|
auto& lev = it->second;
|
|
|
|
// we're the only place that erases, so it's okay to unlock and hold a reference
|
|
|
|
lk.unlock();
|
2022-03-26 21:23:20 -04:00
|
|
|
if (!lev.data.tfrag_load_done) {
|
|
|
|
did_gpu_stuff = true;
|
|
|
|
}
|
2022-03-26 15:53:44 -04:00
|
|
|
if (upload_textures(loader_timer, lev.data, texture_pool)) {
|
|
|
|
if (init_tie(loader_timer, lev.data)) {
|
|
|
|
if (init_tfrag(loader_timer, lev.data)) {
|
2022-03-29 21:38:48 -04:00
|
|
|
if (init_shrub(loader_timer, lev.data)) {
|
|
|
|
// we're done! lock before removing from loaded.
|
|
|
|
lk.lock();
|
|
|
|
it->second.data.load_id = m_id++;
|
2022-01-28 23:44:03 -05:00
|
|
|
|
2022-03-29 21:38:48 -04:00
|
|
|
m_loaded_tfrag3_levels[name] = std::move(lev);
|
|
|
|
m_initializing_tfrag3_levels.erase(it);
|
|
|
|
}
|
2022-03-26 15:53:44 -04:00
|
|
|
}
|
|
|
|
}
|
2022-01-28 23:44:03 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!did_gpu_stuff) {
|
|
|
|
// try to remove levels.
|
|
|
|
if (m_loaded_tfrag3_levels.size() >= 3) {
|
|
|
|
for (const auto& lev : m_loaded_tfrag3_levels) {
|
|
|
|
if (lev.second.frames_since_last_used > 180) {
|
2022-03-02 20:01:37 -05:00
|
|
|
std::unique_lock<std::mutex> lk(texture_pool.mutex());
|
2022-01-28 23:44:03 -05:00
|
|
|
fmt::print("------------------------- PC unloading {}\n", lev.first);
|
2022-03-02 20:01:37 -05:00
|
|
|
for (size_t i = 0; i < lev.second.data.level->textures.size(); i++) {
|
|
|
|
auto& tex = lev.second.data.level->textures[i];
|
|
|
|
if (tex.load_to_pool) {
|
|
|
|
texture_pool.unload_texture(tex.debug_name, lev.second.data.textures.at(i));
|
|
|
|
}
|
|
|
|
}
|
2022-03-26 21:23:20 -04:00
|
|
|
lk.unlock();
|
2022-01-28 23:44:03 -05:00
|
|
|
for (auto tex : lev.second.data.textures) {
|
2022-03-02 20:01:37 -05:00
|
|
|
if (EXTRA_TEX_DEBUG) {
|
|
|
|
for (auto& slot : texture_pool.all_textures()) {
|
|
|
|
if (slot.source) {
|
|
|
|
ASSERT(slot.gpu_texture != tex);
|
|
|
|
} else {
|
|
|
|
ASSERT(slot.gpu_texture != tex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-28 23:44:03 -05:00
|
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
|
|
|
glDeleteTextures(1, &tex);
|
|
|
|
}
|
2022-03-02 20:01:37 -05:00
|
|
|
|
2022-03-26 15:53:44 -04:00
|
|
|
for (auto& tie_geo : lev.second.data.tie_data) {
|
|
|
|
for (auto& tie_tree : tie_geo) {
|
|
|
|
glDeleteBuffers(1, &tie_tree.vertex_buffer);
|
|
|
|
if (tie_tree.has_wind) {
|
|
|
|
glDeleteBuffers(1, &tie_tree.wind_indices);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto& tfrag_geo : lev.second.data.tfrag_vertex_data) {
|
|
|
|
for (auto& tfrag_buff : tfrag_geo) {
|
|
|
|
glDeleteBuffers(1, &tfrag_buff);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-28 23:44:03 -05:00
|
|
|
m_loaded_tfrag3_levels.erase(lev.first);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (loader_timer.getMs() > 5) {
|
|
|
|
fmt::print("Loader::update slow setup: {:.1f}ms\n", loader_timer.getMs());
|
|
|
|
}
|
|
|
|
}
|