mirror of
https://github.com/open-goal/jak-project
synced 2026-05-28 08:25:56 -04:00
Loader improvements (#1378)
* tfrag3 data for merc2 * dma hooks for merc2 * start designing merc2 opengl, seems like the simple approach will be the best here * before bone packing experiment * fix up bones.gc * use uniform buffer * speedup, fix faces and eyes * final fixes * first pass at loader updates, tie is still bad * temp * improved loader * run iop less often
This commit is contained in:
+2
-1
@@ -98,6 +98,8 @@ set(RUNTIME_SOURCE
|
||||
graphics/opengl_renderer/ocean/OceanNear_PS2.cpp
|
||||
graphics/opengl_renderer/ocean/OceanTexture.cpp
|
||||
graphics/opengl_renderer/ocean/OceanTexture_PC.cpp
|
||||
graphics/opengl_renderer/loader/Loader.cpp
|
||||
graphics/opengl_renderer/loader/LoaderStages.cpp
|
||||
graphics/opengl_renderer/BucketRenderer.cpp
|
||||
graphics/opengl_renderer/CollideMeshRenderer.cpp
|
||||
graphics/opengl_renderer/debug_gui.cpp
|
||||
@@ -105,7 +107,6 @@ set(RUNTIME_SOURCE
|
||||
graphics/opengl_renderer/DirectRenderer2.cpp
|
||||
graphics/opengl_renderer/EyeRenderer.cpp
|
||||
graphics/opengl_renderer/dma_helpers.cpp
|
||||
graphics/opengl_renderer/Loader.cpp
|
||||
graphics/opengl_renderer/MercProgram.cpp
|
||||
graphics/opengl_renderer/MercRenderer.cpp
|
||||
graphics/opengl_renderer/opengl_utils.cpp
|
||||
|
||||
@@ -6,10 +6,5 @@
|
||||
* Note that PLAY and PLAYER are different.
|
||||
*/
|
||||
|
||||
#ifndef JAK1_PLAYER_RPC_TYPES_H
|
||||
#define JAK1_PLAYER_RPC_TYPES_H
|
||||
|
||||
constexpr int PLAYER_RPC_ID = 0xdeb1;
|
||||
constexpr int PLAYER_RPC_CHANNEL = 0;
|
||||
|
||||
#endif // JAK1_PLAYER_RPC_TYPES_H
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "game/graphics/opengl_renderer/Shader.h"
|
||||
#include "game/graphics/texture/TexturePool.h"
|
||||
#include "game/graphics/opengl_renderer/Profiler.h"
|
||||
#include "game/graphics/opengl_renderer/Loader.h"
|
||||
#include "game/graphics/opengl_renderer/loader/Loader.h"
|
||||
#include "game/graphics/opengl_renderer/buckets.h"
|
||||
|
||||
struct LevelVis {
|
||||
|
||||
@@ -1,732 +0,0 @@
|
||||
#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) {
|
||||
std::string result;
|
||||
for (auto c : s) {
|
||||
result.push_back(toupper(c));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
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
|
||||
*/
|
||||
const Loader::LevelData* Loader::get_tfrag3_level(const std::string& level_name) {
|
||||
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
||||
const auto& existing = m_loaded_tfrag3_levels.find(level_name);
|
||||
if (existing == m_loaded_tfrag3_levels.end()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
existing->second->frames_since_last_used = 0;
|
||||
return existing->second.get();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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.
|
||||
*/
|
||||
void Loader::set_want_levels(const std::vector<std::string>& levels) {
|
||||
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
||||
m_desired_levels = levels;
|
||||
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;
|
||||
}
|
||||
|
||||
// loader isn't busy, try to load one of the requested levels.
|
||||
for (auto& lev : levels) {
|
||||
auto it = m_loaded_tfrag3_levels.find(lev);
|
||||
if (it == m_loaded_tfrag3_levels.end()) {
|
||||
// we haven't loaded it yet. Request this level to load and wake up the thread.
|
||||
m_level_to_load = lev;
|
||||
lk.unlock();
|
||||
m_loader_cv.notify_all();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get all levels that are in memory and used very recently.
|
||||
*/
|
||||
std::vector<Loader::LevelData*> Loader::get_in_use_levels() {
|
||||
std::vector<Loader::LevelData*> result;
|
||||
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
||||
|
||||
for (auto& lev : m_loaded_tfrag3_levels) {
|
||||
if (lev.second->frames_since_last_used < 5) {
|
||||
result.push_back(lev.second.get());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Loader function that runs in a completely separate thread.
|
||||
* This is used for file I/O and unpacking.
|
||||
*/
|
||||
void Loader::loader_thread() {
|
||||
while (!m_want_shutdown) {
|
||||
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
||||
|
||||
// this will keep us asleep until we've got a level to load.
|
||||
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;
|
||||
// don't hold the lock while reading the file.
|
||||
lk.unlock();
|
||||
|
||||
// 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
|
||||
Timer disk_timer;
|
||||
auto data = file_util::read_binary_file(
|
||||
file_util::get_file_path({fmt::format("assets/{}.fr3", uppercase_string(lev))}));
|
||||
double disk_load_time = disk_timer.getSeconds();
|
||||
|
||||
// the FR3 files are compressed
|
||||
Timer decomp_timer;
|
||||
auto decomp_data = compression::decompress_zstd(data.data(), data.size());
|
||||
double decomp_time = decomp_timer.getSeconds();
|
||||
|
||||
// Read back into the tfrag3::Level structure
|
||||
Timer import_timer;
|
||||
auto result = std::make_unique<tfrag3::Level>();
|
||||
Serializer ser(decomp_data.data(), decomp_data.size());
|
||||
result->serialize(ser);
|
||||
double import_time = import_timer.getSeconds();
|
||||
|
||||
// and finally "unpack", which creates the vertex data we'll upload to the GPU
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& shrub_tree : result->shrub_trees) {
|
||||
shrub_tree.unpack();
|
||||
}
|
||||
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());
|
||||
|
||||
// grab the lock again
|
||||
lk.lock();
|
||||
// move this level to "initializing" state.
|
||||
m_initializing_tfrag3_levels[lev] = std::make_unique<LevelData>(); // reset load state
|
||||
m_initializing_tfrag3_levels[lev]->level = std::move(result);
|
||||
m_level_to_load = "";
|
||||
m_file_load_done_cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Load a "common" FR3 file that has non-level textures.
|
||||
* This should be called during initialization, before any threaded loading goes on.
|
||||
*/
|
||||
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.level = std::make_unique<tfrag3::Level>();
|
||||
m_common_level.level->serialize(ser);
|
||||
for (auto& tex : m_common_level.level->textures) {
|
||||
m_common_level.textures.push_back(add_texture(tex_pool, tex, true));
|
||||
}
|
||||
|
||||
Timer tim;
|
||||
init_merc(tim, m_common_level);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Upload a texture to the GPU, and give it to the pool.
|
||||
*/
|
||||
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.debug_page_name = tex.debug_tpage_name;
|
||||
in.debug_name = tex.debug_name;
|
||||
in.w = tex.w;
|
||||
in.h = tex.h;
|
||||
in.gpu_texture = gl_tex;
|
||||
in.common = is_common;
|
||||
in.id = PcTextureId::from_combo_id(tex.combo_id);
|
||||
in.src_data = (const u8*)tex.data.data();
|
||||
pool.give_texture(in);
|
||||
}
|
||||
|
||||
return gl_tex;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
data.tie_next_tree = 0;
|
||||
}
|
||||
|
||||
data.tie_wind_indices_done = true;
|
||||
data.tie_load_done = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Loader::init_collide(Timer& /*timer*/, LevelData& data) {
|
||||
Timer t;
|
||||
glGenBuffers(1, &data.collide_vertices);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, data.collide_vertices);
|
||||
glBufferData(GL_ARRAY_BUFFER,
|
||||
data.level->collision.vertices.size() * sizeof(tfrag3::CollisionMesh::Vertex),
|
||||
data.level->collision.vertices.data(), GL_STATIC_DRAW);
|
||||
fmt::print("init_collide took {:.2f} ms\n", t.getMs());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Loader::init_merc(Timer& /*timer*/, LevelData& data) {
|
||||
Timer t;
|
||||
glGenBuffers(1, &data.merc_indices);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.merc_indices);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.level->merc_data.indices.size() * sizeof(u32),
|
||||
data.level->merc_data.indices.data(), GL_STATIC_DRAW);
|
||||
|
||||
glGenBuffers(1, &data.merc_vertices);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, data.merc_vertices);
|
||||
glBufferData(GL_ARRAY_BUFFER, data.level->merc_data.vertices.size() * sizeof(tfrag3::MercVertex),
|
||||
data.level->merc_data.vertices.data(), GL_STATIC_DRAW);
|
||||
|
||||
for (auto& model : data.level->merc_data.models) {
|
||||
data.merc_model_lookup[model.name] = &model;
|
||||
m_all_merc_models[model.name].push_back({&model, data.load_id, &data});
|
||||
}
|
||||
fmt::print("init_merc took {:.2f} ms\n", t.getMs());
|
||||
return true;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void Loader::update_blocking(TexturePool& tex_pool) {
|
||||
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) {
|
||||
update(tex_pool);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void Loader::update(TexturePool& texture_pool) {
|
||||
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;
|
||||
|
||||
// work on moving initializing to initialized.
|
||||
{
|
||||
// accessing initializing, should lock
|
||||
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
||||
// grab the first initializing level:
|
||||
const auto& it = m_initializing_tfrag3_levels.begin();
|
||||
if (it != m_initializing_tfrag3_levels.end()) {
|
||||
std::string name = it->first;
|
||||
auto& lev = it->second;
|
||||
it->second->load_id = m_id++;
|
||||
|
||||
// we're the only place that erases, so it's okay to unlock and hold a reference
|
||||
lk.unlock();
|
||||
if (!lev->tfrag_load_done) {
|
||||
did_gpu_stuff = true;
|
||||
}
|
||||
if (upload_textures(loader_timer, *lev, texture_pool)) {
|
||||
if (init_tie(loader_timer, *lev)) {
|
||||
if (init_tfrag(loader_timer, *lev)) {
|
||||
if (init_shrub(loader_timer, *lev)) {
|
||||
if (init_collide(loader_timer, *lev)) {
|
||||
if (init_merc(loader_timer, *lev)) {
|
||||
// we're done! lock before removing from loaded.
|
||||
lk.lock();
|
||||
|
||||
m_loaded_tfrag3_levels[name] = std::move(lev);
|
||||
m_initializing_tfrag3_levels.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!did_gpu_stuff) {
|
||||
// try to remove levels.
|
||||
if (m_loaded_tfrag3_levels.size() >= 3) {
|
||||
for (auto& lev : m_loaded_tfrag3_levels) {
|
||||
if (lev.second->frames_since_last_used > 180) {
|
||||
std::unique_lock<std::mutex> lk(texture_pool.mutex());
|
||||
fmt::print("------------------------- PC unloading {}\n", lev.first);
|
||||
for (size_t i = 0; i < lev.second->level->textures.size(); i++) {
|
||||
auto& tex = lev.second->level->textures[i];
|
||||
if (tex.load_to_pool) {
|
||||
texture_pool.unload_texture(PcTextureId::from_combo_id(tex.combo_id),
|
||||
lev.second->textures.at(i));
|
||||
}
|
||||
}
|
||||
lk.unlock();
|
||||
for (auto tex : lev.second->textures) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, tex);
|
||||
glDeleteTextures(1, &tex);
|
||||
}
|
||||
|
||||
for (auto& tie_geo : lev.second->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->tfrag_vertex_data) {
|
||||
for (auto& tfrag_buff : tfrag_geo) {
|
||||
glDeleteBuffers(1, &tfrag_buff);
|
||||
}
|
||||
}
|
||||
|
||||
glDeleteBuffers(1, &lev.second->collide_vertices);
|
||||
glDeleteBuffers(1, &lev.second->merc_vertices);
|
||||
glDeleteBuffers(1, &lev.second->merc_indices);
|
||||
|
||||
for (auto& model : lev.second->level->merc_data.models) {
|
||||
auto& mercs = m_all_merc_models.at(model.name);
|
||||
MercRef ref{&model, lev.second->load_id};
|
||||
auto it = std::find(mercs.begin(), mercs.end(), ref);
|
||||
ASSERT_MSG(it != mercs.end(), fmt::format("missing merc: {}\n", model.name));
|
||||
mercs.erase(it);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Loader::MercRef> Loader::get_merc_model(const char* model_name) {
|
||||
// don't think we need to lock here...
|
||||
const auto& it = m_all_merc_models.find(model_name);
|
||||
if (it != m_all_merc_models.end() && !it->second.empty()) {
|
||||
// it->second.front().parent_level->frames_since_last_used = 0;
|
||||
return it->second.front();
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "game/graphics/pipelines/opengl.h"
|
||||
#include "game/graphics/texture/TexturePool.h"
|
||||
#include "common/custom_data/Tfrag3Data.h"
|
||||
#include "common/util/Timer.h"
|
||||
|
||||
class Loader {
|
||||
public:
|
||||
static constexpr float TIE_LOAD_BUDGET = 1.5f;
|
||||
static constexpr float SHARED_TEXTURE_LOAD_BUDGET = 3.f;
|
||||
Loader();
|
||||
~Loader();
|
||||
void update(TexturePool& tex_pool);
|
||||
void update_blocking(TexturePool& tex_pool);
|
||||
|
||||
struct LevelData {
|
||||
std::unique_ptr<tfrag3::Level> level;
|
||||
std::vector<GLuint> textures;
|
||||
u64 load_id = 0;
|
||||
|
||||
struct TieOpenGL {
|
||||
GLuint vertex_buffer;
|
||||
bool has_wind = false;
|
||||
GLuint wind_indices;
|
||||
};
|
||||
std::array<std::vector<TieOpenGL>, tfrag3::TIE_GEOS> tie_data;
|
||||
std::array<std::vector<GLuint>, tfrag3::TIE_GEOS> tfrag_vertex_data;
|
||||
std::vector<GLuint> shrub_vertex_data;
|
||||
GLuint collide_vertices;
|
||||
|
||||
GLuint merc_vertices;
|
||||
GLuint merc_indices;
|
||||
std::unordered_map<std::string, const tfrag3::MercModel*> merc_model_lookup;
|
||||
|
||||
// internal load state
|
||||
bool tie_opengl_created = false;
|
||||
bool tie_verts_done = false;
|
||||
bool tie_wind_indices_done = false;
|
||||
bool tie_load_done = false;
|
||||
u32 tie_next_geo = 0;
|
||||
u32 tie_next_tree = 0;
|
||||
u32 tie_next_vert = 0;
|
||||
|
||||
bool tfrag_opengl_created = false;
|
||||
bool tfrag_load_done = false;
|
||||
u32 tfrag_next_geo = 0;
|
||||
u32 tfrag_next_tree = 0;
|
||||
u32 tfrag_next_vert = 0;
|
||||
|
||||
bool shrub_opengl_created = false;
|
||||
bool shrub_load_done = false;
|
||||
u32 shrub_next_tree = 0;
|
||||
u32 shrub_next_vert = 0;
|
||||
|
||||
int frames_since_last_used = 0;
|
||||
};
|
||||
|
||||
struct MercRef {
|
||||
const tfrag3::MercModel* model = nullptr;
|
||||
u64 load_id = 0;
|
||||
const LevelData* level = nullptr;
|
||||
bool operator==(const MercRef& other) const {
|
||||
return model == other.model && load_id == other.load_id;
|
||||
}
|
||||
};
|
||||
|
||||
const LevelData* get_tfrag3_level(const std::string& level_name);
|
||||
std::optional<MercRef> get_merc_model(const char* model_name);
|
||||
void load_common(TexturePool& tex_pool, const std::string& name);
|
||||
void set_want_levels(const std::vector<std::string>& levels);
|
||||
std::vector<LevelData*> get_in_use_levels();
|
||||
|
||||
private:
|
||||
void loader_thread();
|
||||
u64 add_texture(TexturePool& pool, const tfrag3::Texture& tex, bool is_common);
|
||||
|
||||
bool upload_textures(Timer& timer, LevelData& data, TexturePool& texture_pool);
|
||||
bool init_tie(Timer& timer, LevelData& data);
|
||||
bool init_tfrag(Timer& timer, LevelData& data);
|
||||
bool init_shrub(Timer& timer, LevelData& data);
|
||||
bool init_collide(Timer& timer, LevelData& data);
|
||||
bool init_merc(Timer& timer, LevelData& data);
|
||||
|
||||
// used by game and loader thread
|
||||
std::unordered_map<std::string, std::unique_ptr<LevelData>> m_initializing_tfrag3_levels;
|
||||
|
||||
LevelData m_common_level;
|
||||
|
||||
std::string m_level_to_load;
|
||||
|
||||
std::thread m_loader_thread;
|
||||
std::mutex m_loader_mutex;
|
||||
std::condition_variable m_loader_cv;
|
||||
std::condition_variable m_file_load_done_cv;
|
||||
bool m_want_shutdown = false;
|
||||
uint64_t m_id = 0;
|
||||
|
||||
// used only by game thread
|
||||
std::unordered_map<std::string, std::unique_ptr<LevelData>> m_loaded_tfrag3_levels;
|
||||
|
||||
std::unordered_map<std::string, std::vector<MercRef>> m_all_merc_models;
|
||||
|
||||
std::vector<std::string> m_desired_levels;
|
||||
};
|
||||
@@ -61,7 +61,7 @@ void Shrub::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProf
|
||||
render_all_trees(settings, render_state, prof);
|
||||
}
|
||||
|
||||
void Shrub::update_load(const Loader::LevelData* loader_data) {
|
||||
void Shrub::update_load(const LevelData* loader_data) {
|
||||
const tfrag3::Level* lev_data = loader_data->level.get();
|
||||
// We changed level!
|
||||
discard_tree_cache();
|
||||
|
||||
@@ -24,7 +24,7 @@ class Shrub : public BucketRenderer {
|
||||
void draw_debug_window() override;
|
||||
|
||||
private:
|
||||
void update_load(const Loader::LevelData* loader_data);
|
||||
void update_load(const LevelData* loader_data);
|
||||
void discard_tree_cache();
|
||||
|
||||
struct Tree {
|
||||
|
||||
@@ -39,7 +39,7 @@ Tfrag3::~Tfrag3() {
|
||||
}
|
||||
|
||||
void Tfrag3::update_load(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const Loader::LevelData* loader_data) {
|
||||
const LevelData* loader_data) {
|
||||
const auto* lev_data = loader_data->level.get();
|
||||
discard_tree_cache();
|
||||
for (int geom = 0; geom < GEOM_MAX; ++geom) {
|
||||
|
||||
@@ -44,7 +44,7 @@ class Tfrag3 {
|
||||
};
|
||||
|
||||
void update_load(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const Loader::LevelData* loader_data);
|
||||
const LevelData* loader_data);
|
||||
|
||||
int lod() const { return Gfx::g_global_settings.lod_tfrag; }
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Tie3::~Tie3() {
|
||||
discard_tree_cache();
|
||||
}
|
||||
|
||||
void Tie3::update_load(const Loader::LevelData* loader_data) {
|
||||
void Tie3::update_load(const LevelData* loader_data) {
|
||||
const tfrag3::Level* lev_data = loader_data->level.get();
|
||||
m_wind_vectors.clear();
|
||||
// We changed level!
|
||||
|
||||
@@ -40,7 +40,7 @@ class Tie3 : public BucketRenderer {
|
||||
int lod() const { return Gfx::g_global_settings.lod_tie; }
|
||||
|
||||
private:
|
||||
void update_load(const Loader::LevelData* loader_data);
|
||||
void update_load(const LevelData* loader_data);
|
||||
void discard_tree_cache();
|
||||
void render_tree_wind(int idx,
|
||||
int geom,
|
||||
|
||||
@@ -412,7 +412,7 @@ void Merc2::flush_pending_model(SharedRenderState* render_state, ScopedProfilerN
|
||||
return;
|
||||
}
|
||||
|
||||
const Loader::LevelData* lev = m_current_model->level;
|
||||
const LevelData* lev = m_current_model->level;
|
||||
const tfrag3::MercModel* model = m_current_model->model;
|
||||
|
||||
int bone_count = (model->max_bones + 31) & (~31); // todo
|
||||
|
||||
@@ -56,7 +56,7 @@ class Merc2 : public BucketRenderer {
|
||||
|
||||
u32 alloc_bones(int count, float scale);
|
||||
|
||||
std::optional<Loader::MercRef> m_current_model = std::nullopt;
|
||||
std::optional<MercRef> m_current_model = std::nullopt;
|
||||
u16 m_current_effect_enable_bits = 0;
|
||||
u16 m_current_ignore_alpha_bits = 0;
|
||||
|
||||
@@ -120,7 +120,7 @@ class Merc2 : public BucketRenderer {
|
||||
};
|
||||
|
||||
struct LevelDrawBucket {
|
||||
const Loader::LevelData* level = nullptr;
|
||||
const LevelData* level = nullptr;
|
||||
std::vector<Draw> draws;
|
||||
u32 next_free_draw = 0;
|
||||
|
||||
@@ -140,4 +140,4 @@ class Merc2 : public BucketRenderer {
|
||||
u32 m_next_free_bone = 0;
|
||||
|
||||
void flush_draw_buckets(SharedRenderState* render_state, ScopedProfilerNode& prof);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,410 @@
|
||||
#include "Loader.h"
|
||||
#include "common/util/Timer.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/util/compress.h"
|
||||
#include "game/graphics/opengl_renderer/loader/LoaderStages.h"
|
||||
|
||||
namespace {
|
||||
std::string uppercase_string(const std::string& s) {
|
||||
std::string result;
|
||||
for (auto c : s) {
|
||||
result.push_back(toupper(c));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Loader::Loader() {
|
||||
m_loader_thread = std::thread(&Loader::loader_thread, this);
|
||||
m_loader_stages = make_loader_stages();
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
const LevelData* Loader::get_tfrag3_level(const std::string& level_name) {
|
||||
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
||||
const auto& existing = m_loaded_tfrag3_levels.find(level_name);
|
||||
if (existing == m_loaded_tfrag3_levels.end()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
existing->second->frames_since_last_used = 0;
|
||||
return existing->second.get();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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.
|
||||
*/
|
||||
void Loader::set_want_levels(const std::vector<std::string>& levels) {
|
||||
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
||||
m_desired_levels = levels;
|
||||
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;
|
||||
}
|
||||
|
||||
// loader isn't busy, try to load one of the requested levels.
|
||||
for (auto& lev : levels) {
|
||||
auto it = m_loaded_tfrag3_levels.find(lev);
|
||||
if (it == m_loaded_tfrag3_levels.end()) {
|
||||
// we haven't loaded it yet. Request this level to load and wake up the thread.
|
||||
m_level_to_load = lev;
|
||||
lk.unlock();
|
||||
m_loader_cv.notify_all();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get all levels that are in memory and used very recently.
|
||||
*/
|
||||
std::vector<LevelData*> Loader::get_in_use_levels() {
|
||||
std::vector<LevelData*> result;
|
||||
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
||||
|
||||
for (auto& lev : m_loaded_tfrag3_levels) {
|
||||
if (lev.second->frames_since_last_used < 5) {
|
||||
result.push_back(lev.second.get());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Loader function that runs in a completely separate thread.
|
||||
* This is used for file I/O and unpacking.
|
||||
*/
|
||||
void Loader::loader_thread() {
|
||||
while (!m_want_shutdown) {
|
||||
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
||||
|
||||
// this will keep us asleep until we've got a level to load.
|
||||
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;
|
||||
// don't hold the lock while reading the file.
|
||||
lk.unlock();
|
||||
|
||||
// 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
|
||||
Timer disk_timer;
|
||||
auto data = file_util::read_binary_file(
|
||||
file_util::get_file_path({fmt::format("assets/{}.fr3", uppercase_string(lev))}));
|
||||
double disk_load_time = disk_timer.getSeconds();
|
||||
|
||||
// the FR3 files are compressed
|
||||
Timer decomp_timer;
|
||||
auto decomp_data = compression::decompress_zstd(data.data(), data.size());
|
||||
double decomp_time = decomp_timer.getSeconds();
|
||||
|
||||
// Read back into the tfrag3::Level structure
|
||||
Timer import_timer;
|
||||
auto result = std::make_unique<tfrag3::Level>();
|
||||
Serializer ser(decomp_data.data(), decomp_data.size());
|
||||
result->serialize(ser);
|
||||
double import_time = import_timer.getSeconds();
|
||||
|
||||
// and finally "unpack", which creates the vertex data we'll upload to the GPU
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& shrub_tree : result->shrub_trees) {
|
||||
shrub_tree.unpack();
|
||||
}
|
||||
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());
|
||||
|
||||
// grab the lock again
|
||||
lk.lock();
|
||||
// move this level to "initializing" state.
|
||||
m_initializing_tfrag3_levels[lev] = std::make_unique<LevelData>(); // reset load state
|
||||
m_initializing_tfrag3_levels[lev]->level = std::move(result);
|
||||
m_level_to_load = "";
|
||||
m_file_load_done_cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Load a "common" FR3 file that has non-level textures.
|
||||
* This should be called during initialization, before any threaded loading goes on.
|
||||
*/
|
||||
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.level = std::make_unique<tfrag3::Level>();
|
||||
m_common_level.level->serialize(ser);
|
||||
for (auto& tex : m_common_level.level->textures) {
|
||||
m_common_level.textures.push_back(add_texture(tex_pool, tex, true));
|
||||
}
|
||||
|
||||
Timer tim;
|
||||
MercLoaderStage mls;
|
||||
LoaderInput input;
|
||||
input.tex_pool = &tex_pool;
|
||||
input.mercs = &m_all_merc_models;
|
||||
input.lev_data = &m_common_level;
|
||||
bool done = false;
|
||||
while (!done) {
|
||||
done = mls.run(tim, input);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void Loader::update_blocking(TexturePool& tex_pool) {
|
||||
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) {
|
||||
update(tex_pool);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void Loader::update(TexturePool& texture_pool) {
|
||||
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;
|
||||
|
||||
// work on moving initializing to initialized.
|
||||
{
|
||||
// accessing initializing, should lock
|
||||
std::unique_lock<std::mutex> lk(m_loader_mutex);
|
||||
// grab the first initializing level:
|
||||
const auto& it = m_initializing_tfrag3_levels.begin();
|
||||
if (it != m_initializing_tfrag3_levels.end()) {
|
||||
did_gpu_stuff = true;
|
||||
std::string name = it->first;
|
||||
auto& lev = it->second;
|
||||
if (it->second->load_id == UINT64_MAX) {
|
||||
it->second->load_id = m_id++;
|
||||
}
|
||||
|
||||
// we're the only place that erases, so it's okay to unlock and hold a reference
|
||||
lk.unlock();
|
||||
bool done = true;
|
||||
LoaderInput loader_input;
|
||||
loader_input.lev_data = lev.get();
|
||||
loader_input.mercs = &m_all_merc_models;
|
||||
loader_input.tex_pool = &texture_pool;
|
||||
|
||||
for (auto& stage : m_loader_stages) {
|
||||
Timer stage_timer;
|
||||
done = stage->run(loader_timer, loader_input);
|
||||
if (stage_timer.getMs() > 5.f) {
|
||||
fmt::print("stage {} took {:.2f} ms\n", stage->name(), stage_timer.getMs());
|
||||
}
|
||||
if (!done) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (done) {
|
||||
lk.lock();
|
||||
m_loaded_tfrag3_levels[name] = std::move(lev);
|
||||
m_initializing_tfrag3_levels.erase(it);
|
||||
|
||||
for (auto& stage : m_loader_stages) {
|
||||
stage->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!did_gpu_stuff) {
|
||||
// try to remove levels.
|
||||
Timer unload_timer;
|
||||
if (m_loaded_tfrag3_levels.size() >= 3) {
|
||||
for (auto& lev : m_loaded_tfrag3_levels) {
|
||||
if (lev.second->frames_since_last_used > 180) {
|
||||
std::unique_lock<std::mutex> lk(texture_pool.mutex());
|
||||
fmt::print("------------------------- PC unloading {}\n", lev.first);
|
||||
for (size_t i = 0; i < lev.second->level->textures.size(); i++) {
|
||||
auto& tex = lev.second->level->textures[i];
|
||||
if (tex.load_to_pool) {
|
||||
texture_pool.unload_texture(PcTextureId::from_combo_id(tex.combo_id),
|
||||
lev.second->textures.at(i));
|
||||
}
|
||||
}
|
||||
lk.unlock();
|
||||
for (auto tex : lev.second->textures) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, tex);
|
||||
glDeleteTextures(1, &tex);
|
||||
}
|
||||
|
||||
for (auto& tie_geo : lev.second->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->tfrag_vertex_data) {
|
||||
for (auto& tfrag_buff : tfrag_geo) {
|
||||
glDeleteBuffers(1, &tfrag_buff);
|
||||
}
|
||||
}
|
||||
|
||||
glDeleteBuffers(1, &lev.second->collide_vertices);
|
||||
glDeleteBuffers(1, &lev.second->merc_vertices);
|
||||
glDeleteBuffers(1, &lev.second->merc_indices);
|
||||
|
||||
for (auto& model : lev.second->level->merc_data.models) {
|
||||
auto& mercs = m_all_merc_models.at(model.name);
|
||||
MercRef ref{&model, lev.second->load_id};
|
||||
auto it = std::find(mercs.begin(), mercs.end(), ref);
|
||||
ASSERT_MSG(it != mercs.end(), fmt::format("missing merc: {}\n", model.name));
|
||||
mercs.erase(it);
|
||||
}
|
||||
|
||||
m_loaded_tfrag3_levels.erase(lev.first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unload_timer.getMs() > 5.f) {
|
||||
fmt::print("Unload took {:.2f}\n", unload_timer.getMs());
|
||||
}
|
||||
}
|
||||
|
||||
if (loader_timer.getMs() > 5) {
|
||||
fmt::print("Loader::update slow setup: {:.1f}ms\n", loader_timer.getMs());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<MercRef> Loader::get_merc_model(const char* model_name) {
|
||||
// don't think we need to lock here...
|
||||
const auto& it = m_all_merc_models.find(model_name);
|
||||
if (it != m_all_merc_models.end() && !it->second.empty()) {
|
||||
// it->second.front().parent_level->frames_since_last_used = 0;
|
||||
return it->second.front();
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "game/graphics/pipelines/opengl.h"
|
||||
#include "game/graphics/texture/TexturePool.h"
|
||||
#include "common/custom_data/Tfrag3Data.h"
|
||||
#include "common/util/Timer.h"
|
||||
#include "game/graphics/opengl_renderer/loader/common.h"
|
||||
|
||||
class Loader {
|
||||
public:
|
||||
static constexpr float TIE_LOAD_BUDGET = 1.5f;
|
||||
static constexpr float SHARED_TEXTURE_LOAD_BUDGET = 3.f;
|
||||
Loader();
|
||||
~Loader();
|
||||
void update(TexturePool& tex_pool);
|
||||
void update_blocking(TexturePool& tex_pool);
|
||||
const LevelData* get_tfrag3_level(const std::string& level_name);
|
||||
std::optional<MercRef> get_merc_model(const char* model_name);
|
||||
void load_common(TexturePool& tex_pool, const std::string& name);
|
||||
void set_want_levels(const std::vector<std::string>& levels);
|
||||
std::vector<LevelData*> get_in_use_levels();
|
||||
|
||||
private:
|
||||
void loader_thread();
|
||||
bool upload_textures(Timer& timer, LevelData& data, TexturePool& texture_pool);
|
||||
|
||||
// used by game and loader thread
|
||||
std::unordered_map<std::string, std::unique_ptr<LevelData>> m_initializing_tfrag3_levels;
|
||||
|
||||
LevelData m_common_level;
|
||||
|
||||
std::string m_level_to_load;
|
||||
|
||||
std::thread m_loader_thread;
|
||||
std::mutex m_loader_mutex;
|
||||
std::condition_variable m_loader_cv;
|
||||
std::condition_variable m_file_load_done_cv;
|
||||
bool m_want_shutdown = false;
|
||||
uint64_t m_id = 0;
|
||||
|
||||
// used only by game thread
|
||||
std::unordered_map<std::string, std::unique_ptr<LevelData>> m_loaded_tfrag3_levels;
|
||||
|
||||
std::unordered_map<std::string, std::vector<MercRef>> m_all_merc_models;
|
||||
|
||||
std::vector<std::string> m_desired_levels;
|
||||
std::vector<std::unique_ptr<LoaderStage>> m_loader_stages;
|
||||
};
|
||||
@@ -0,0 +1,529 @@
|
||||
#include "Loader.h"
|
||||
#include "LoaderStages.h"
|
||||
|
||||
constexpr float LOAD_BUDGET = 2.5f;
|
||||
|
||||
/*!
|
||||
* Upload a texture to the GPU, and give it to the pool.
|
||||
*/
|
||||
u64 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.debug_page_name = tex.debug_tpage_name;
|
||||
in.debug_name = tex.debug_name;
|
||||
in.w = tex.w;
|
||||
in.h = tex.h;
|
||||
in.gpu_texture = gl_tex;
|
||||
in.common = is_common;
|
||||
in.id = PcTextureId::from_combo_id(tex.combo_id);
|
||||
in.src_data = (const u8*)tex.data.data();
|
||||
pool.give_texture(in);
|
||||
}
|
||||
|
||||
return gl_tex;
|
||||
}
|
||||
|
||||
class TextureLoaderStage : public LoaderStage {
|
||||
public:
|
||||
TextureLoaderStage() : LoaderStage("texture") {}
|
||||
bool run(Timer& timer, LoaderInput& data) override {
|
||||
constexpr int MAX_TEX_BYTES_PER_FRAME = 1024 * 512;
|
||||
|
||||
int bytes_this_run = 0;
|
||||
int tex_this_run = 0;
|
||||
if (data.lev_data->textures.size() < data.lev_data->level->textures.size()) {
|
||||
std::unique_lock<std::mutex> tpool_lock(data.tex_pool->mutex());
|
||||
while (data.lev_data->textures.size() < data.lev_data->level->textures.size()) {
|
||||
auto& tex = data.lev_data->level->textures[data.lev_data->textures.size()];
|
||||
data.lev_data->textures.push_back(add_texture(*data.tex_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() > LOAD_BUDGET) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return data.lev_data->textures.size() == data.lev_data->level->textures.size();
|
||||
}
|
||||
void reset() override {}
|
||||
};
|
||||
|
||||
class TfragLoadStage : public LoaderStage {
|
||||
public:
|
||||
TfragLoadStage() : LoaderStage("tfrag") {}
|
||||
bool run(Timer& timer, LoaderInput& data) override {
|
||||
if (m_done) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data.lev_data->level->tfrag_trees.front().empty()) {
|
||||
m_done = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_opengl_created) {
|
||||
for (int geo = 0; geo < tfrag3::TFRAG_GEOS; geo++) {
|
||||
auto& in_trees = data.lev_data->level->tfrag_trees[geo];
|
||||
for (auto& in_tree : in_trees) {
|
||||
GLuint& tree_out = data.lev_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);
|
||||
}
|
||||
}
|
||||
m_opengl_created = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr u32 CHUNK_SIZE = 32768;
|
||||
u32 uploaded_bytes = 0;
|
||||
u32 unique_buffers = 0;
|
||||
|
||||
while (true) {
|
||||
const auto& tree = data.lev_data->level->tfrag_trees[m_next_geo][m_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 - m_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 = m_next_vert;
|
||||
end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE;
|
||||
m_next_vert += CHUNK_SIZE;
|
||||
} else {
|
||||
// should do all!
|
||||
start_vert_for_chunk = m_next_vert;
|
||||
end_vert_for_chunk = end_vert_in_tree;
|
||||
complete_tree = true;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->tfrag_vertex_data[m_next_geo][m_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) {
|
||||
unique_buffers++;
|
||||
// and move on to next tree
|
||||
m_next_vert = 0;
|
||||
m_next_tree++;
|
||||
if (m_next_tree >= data.lev_data->level->tfrag_trees[m_next_geo].size()) {
|
||||
m_next_tree = 0;
|
||||
m_next_geo++;
|
||||
if (m_next_geo >= tfrag3::TFRAG_GEOS) {
|
||||
m_next_tree = true;
|
||||
m_next_tree = 0;
|
||||
m_next_geo = 0;
|
||||
m_next_vert = 0;
|
||||
m_done = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (timer.getMs() > LOAD_BUDGET || (uploaded_bytes / 1024) > 2048) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset() override {
|
||||
m_done = false;
|
||||
m_opengl_created = false;
|
||||
m_next_geo = 0;
|
||||
m_next_tree = 0;
|
||||
m_next_vert = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_done = false;
|
||||
bool m_opengl_created = false;
|
||||
u32 m_next_geo = 0;
|
||||
u32 m_next_tree = 0;
|
||||
u32 m_next_vert = 0;
|
||||
};
|
||||
|
||||
class ShrubLoadStage : public LoaderStage {
|
||||
public:
|
||||
ShrubLoadStage() : LoaderStage("shrub") {}
|
||||
bool run(Timer& timer, LoaderInput& data) override {
|
||||
if (m_done) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data.lev_data->level->shrub_trees.empty()) {
|
||||
m_done = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_opengl_created) {
|
||||
for (auto& in_tree : data.lev_data->level->shrub_trees) {
|
||||
GLuint& tree_out = data.lev_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);
|
||||
}
|
||||
m_opengl_created = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr u32 CHUNK_SIZE = 32768;
|
||||
u32 uploaded_bytes = 0;
|
||||
|
||||
while (true) {
|
||||
const auto& tree = data.lev_data->level->shrub_trees[m_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 - m_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 = m_next_vert;
|
||||
end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE;
|
||||
m_next_vert += CHUNK_SIZE;
|
||||
} else {
|
||||
// should do all!
|
||||
start_vert_for_chunk = m_next_vert;
|
||||
end_vert_for_chunk = end_vert_in_tree;
|
||||
complete_tree = true;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->shrub_vertex_data[m_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
|
||||
m_next_vert = 0;
|
||||
m_next_tree++;
|
||||
if (m_next_tree >= data.lev_data->level->shrub_trees.size()) {
|
||||
m_done = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (timer.getMs() > LOAD_BUDGET || (uploaded_bytes / 128) > 2048) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset() override {
|
||||
m_done = false;
|
||||
m_opengl_created = false;
|
||||
m_next_tree = 0;
|
||||
m_next_vert = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_done = false;
|
||||
bool m_opengl_created = false;
|
||||
u32 m_next_tree = 0;
|
||||
u32 m_next_vert = 0;
|
||||
};
|
||||
|
||||
class TieLoadStage : public LoaderStage {
|
||||
public:
|
||||
TieLoadStage() : LoaderStage("tie") {}
|
||||
bool run(Timer& timer, LoaderInput& data) override {
|
||||
if (m_done) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data.lev_data->level->tie_trees.front().empty()) {
|
||||
m_done = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_opengl_created) {
|
||||
for (int geo = 0; geo < tfrag3::TIE_GEOS; geo++) {
|
||||
auto& in_trees = data.lev_data->level->tie_trees[geo];
|
||||
for (auto& in_tree : in_trees) {
|
||||
LevelData::TieOpenGL& tree_out = data.lev_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);
|
||||
}
|
||||
}
|
||||
m_opengl_created = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_verts_done) {
|
||||
constexpr u32 CHUNK_SIZE = 32768;
|
||||
u32 uploaded_bytes = 0;
|
||||
|
||||
while (true) {
|
||||
const auto& tree = data.lev_data->level->tie_trees[m_next_geo][m_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 - m_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 = m_next_vert;
|
||||
end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE;
|
||||
m_next_vert += CHUNK_SIZE;
|
||||
} else {
|
||||
// should do all!
|
||||
start_vert_for_chunk = m_next_vert;
|
||||
end_vert_for_chunk = end_vert_in_tree;
|
||||
complete_tree = true;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER,
|
||||
data.lev_data->tie_data[m_next_geo][m_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
|
||||
m_next_vert = 0;
|
||||
m_next_tree++;
|
||||
if (m_next_tree >= data.lev_data->level->tie_trees[m_next_geo].size()) {
|
||||
m_next_tree = 0;
|
||||
m_next_geo++;
|
||||
if (m_next_geo >= tfrag3::TIE_GEOS) {
|
||||
m_verts_done = true;
|
||||
m_next_tree = 0;
|
||||
m_next_geo = 0;
|
||||
m_next_vert = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (timer.getMs() > LOAD_BUDGET || (uploaded_bytes / 1024) > 2048) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_wind_indices_done) {
|
||||
bool abort = false;
|
||||
for (; m_next_geo < tfrag3::TIE_GEOS; m_next_geo++) {
|
||||
auto& geo_trees = data.lev_data->level->tie_trees[m_next_geo];
|
||||
for (; m_next_tree < geo_trees.size(); m_next_tree++) {
|
||||
if (abort) {
|
||||
return false;
|
||||
}
|
||||
auto& in_tree = geo_trees[m_next_tree];
|
||||
auto& out_tree = data.lev_data->tie_data[m_next_geo][m_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;
|
||||
}
|
||||
}
|
||||
m_next_tree = 0;
|
||||
}
|
||||
|
||||
m_indices_done = true;
|
||||
m_done = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void reset() override {
|
||||
m_done = false;
|
||||
m_opengl_created = false;
|
||||
m_next_geo = 0;
|
||||
m_next_tree = 0;
|
||||
m_next_vert = 0;
|
||||
m_verts_done = false;
|
||||
m_indices_done = false;
|
||||
m_wind_indices_done = false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_done = false;
|
||||
bool m_opengl_created = false;
|
||||
bool m_verts_done = false;
|
||||
bool m_indices_done = false;
|
||||
bool m_wind_indices_done = false;
|
||||
u32 m_next_geo = 0;
|
||||
u32 m_next_tree = 0;
|
||||
u32 m_next_vert = 0;
|
||||
};
|
||||
|
||||
class CollideLoaderStage : public LoaderStage {
|
||||
public:
|
||||
CollideLoaderStage() : LoaderStage("collide") {}
|
||||
bool run(Timer& /*timer*/, LoaderInput& data) override {
|
||||
if (m_done) {
|
||||
return true;
|
||||
}
|
||||
if (!m_opengl_created) {
|
||||
glGenBuffers(1, &data.lev_data->collide_vertices);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->collide_vertices);
|
||||
glBufferData(
|
||||
GL_ARRAY_BUFFER,
|
||||
data.lev_data->level->collision.vertices.size() * sizeof(tfrag3::CollisionMesh::Vertex),
|
||||
nullptr, GL_STATIC_DRAW);
|
||||
m_opengl_created = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 start = m_vtx;
|
||||
u32 end = std::min((u32)data.lev_data->level->collision.vertices.size(), start + 32768);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->collide_vertices);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, start * sizeof(tfrag3::CollisionMesh::Vertex),
|
||||
(end - start) * sizeof(tfrag3::CollisionMesh::Vertex),
|
||||
data.lev_data->level->collision.vertices.data() + start);
|
||||
m_vtx = end;
|
||||
|
||||
if (m_vtx == data.lev_data->level->collision.vertices.size()) {
|
||||
m_done = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void reset() override {
|
||||
m_opengl_created = false;
|
||||
m_vtx = 0;
|
||||
m_done = false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_opengl_created = false;
|
||||
u32 m_vtx = 0;
|
||||
bool m_done = false;
|
||||
};
|
||||
|
||||
MercLoaderStage::MercLoaderStage() : LoaderStage("merc") {}
|
||||
void MercLoaderStage::reset() {
|
||||
m_done = false;
|
||||
m_opengl = false;
|
||||
m_vtx_uploaded = false;
|
||||
m_idx = 0;
|
||||
}
|
||||
|
||||
bool MercLoaderStage::run(Timer& /*timer*/, LoaderInput& data) {
|
||||
if (m_done) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_opengl) {
|
||||
glGenBuffers(1, &data.lev_data->merc_indices);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.lev_data->merc_indices);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||
data.lev_data->level->merc_data.indices.size() * sizeof(u32), nullptr,
|
||||
GL_STATIC_DRAW);
|
||||
|
||||
glGenBuffers(1, &data.lev_data->merc_vertices);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->merc_vertices);
|
||||
glBufferData(GL_ARRAY_BUFFER,
|
||||
data.lev_data->level->merc_data.vertices.size() * sizeof(tfrag3::MercVertex),
|
||||
nullptr, GL_STATIC_DRAW);
|
||||
m_opengl = true;
|
||||
}
|
||||
|
||||
if (!m_vtx_uploaded) {
|
||||
u32 start = m_idx;
|
||||
m_idx = std::min(start + 32768, (u32)data.lev_data->level->merc_data.indices.size());
|
||||
glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->merc_indices);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, start * sizeof(u32), (m_idx - start) * sizeof(u32),
|
||||
data.lev_data->level->merc_data.indices.data() + start);
|
||||
if (m_idx != data.lev_data->level->merc_data.indices.size()) {
|
||||
return false;
|
||||
} else {
|
||||
m_idx = 0;
|
||||
m_vtx_uploaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
u32 start = m_idx;
|
||||
m_idx = std::min(start + 32768, (u32)data.lev_data->level->merc_data.vertices.size());
|
||||
glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->merc_vertices);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, start * sizeof(tfrag3::MercVertex),
|
||||
(m_idx - start) * sizeof(tfrag3::MercVertex),
|
||||
data.lev_data->level->merc_data.vertices.data() + start);
|
||||
|
||||
if (m_idx != data.lev_data->level->merc_data.vertices.size()) {
|
||||
return false;
|
||||
} else {
|
||||
m_done = true;
|
||||
for (auto& model : data.lev_data->level->merc_data.models) {
|
||||
data.lev_data->merc_model_lookup[model.name] = &model;
|
||||
(*data.mercs)[model.name].push_back({&model, data.lev_data->load_id, data.lev_data});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<LoaderStage>> make_loader_stages() {
|
||||
std::vector<std::unique_ptr<LoaderStage>> ret;
|
||||
ret.push_back(std::make_unique<TieLoadStage>());
|
||||
ret.push_back(std::make_unique<TextureLoaderStage>());
|
||||
ret.push_back(std::make_unique<TfragLoadStage>());
|
||||
ret.push_back(std::make_unique<ShrubLoadStage>());
|
||||
ret.push_back(std::make_unique<CollideLoaderStage>());
|
||||
ret.push_back(std::make_unique<MercLoaderStage>());
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/graphics/opengl_renderer/loader/common.h"
|
||||
|
||||
std::vector<std::unique_ptr<LoaderStage>> make_loader_stages();
|
||||
u64 add_texture(TexturePool& pool, const tfrag3::Texture& tex, bool is_common);
|
||||
|
||||
class MercLoaderStage : public LoaderStage {
|
||||
public:
|
||||
MercLoaderStage();
|
||||
bool run(Timer& timer, LoaderInput& data) override;
|
||||
void reset() override;
|
||||
|
||||
private:
|
||||
bool m_done = false;
|
||||
bool m_opengl = false;
|
||||
bool m_vtx_uploaded = false;
|
||||
u32 m_idx = 0;
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
struct LevelData {
|
||||
std::unique_ptr<tfrag3::Level> level;
|
||||
std::vector<GLuint> textures;
|
||||
u64 load_id = UINT64_MAX;
|
||||
|
||||
struct TieOpenGL {
|
||||
GLuint vertex_buffer;
|
||||
bool has_wind = false;
|
||||
GLuint wind_indices;
|
||||
};
|
||||
std::array<std::vector<TieOpenGL>, tfrag3::TIE_GEOS> tie_data;
|
||||
std::array<std::vector<GLuint>, tfrag3::TIE_GEOS> tfrag_vertex_data;
|
||||
std::vector<GLuint> shrub_vertex_data;
|
||||
GLuint collide_vertices;
|
||||
|
||||
GLuint merc_vertices;
|
||||
GLuint merc_indices;
|
||||
std::unordered_map<std::string, const tfrag3::MercModel*> merc_model_lookup;
|
||||
|
||||
int frames_since_last_used = 0;
|
||||
};
|
||||
|
||||
struct MercRef {
|
||||
const tfrag3::MercModel* model = nullptr;
|
||||
u64 load_id = 0;
|
||||
const LevelData* level = nullptr;
|
||||
bool operator==(const MercRef& other) const {
|
||||
return model == other.model && load_id == other.load_id;
|
||||
}
|
||||
};
|
||||
|
||||
struct LoaderInput {
|
||||
LevelData* lev_data;
|
||||
TexturePool* tex_pool;
|
||||
std::unordered_map<std::string, std::vector<MercRef>>* mercs;
|
||||
};
|
||||
|
||||
class LoaderStage {
|
||||
public:
|
||||
LoaderStage(const std::string& name) : m_name(name) {}
|
||||
virtual bool run(Timer& timer, LoaderInput& data) = 0;
|
||||
virtual void reset() = 0;
|
||||
virtual ~LoaderStage() = default;
|
||||
const std::string& name() const { return m_name; }
|
||||
|
||||
protected:
|
||||
std::string m_name;
|
||||
};
|
||||
+2
-2
@@ -81,12 +81,12 @@ s32 sceSifCallRpc(sceSifClientData* bd,
|
||||
ASSERT(!end_para);
|
||||
ASSERT(mode == 1); // async
|
||||
iop->kernel.sif_rpc(bd->rpcd.id, fno, mode, send, ssize, recv, rsize);
|
||||
iop->signal_run_iop();
|
||||
iop->signal_run_iop(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 sceSifCheckStatRpc(sceSifRpcData* bd) {
|
||||
iop->signal_run_iop();
|
||||
iop->signal_run_iop(false);
|
||||
return iop->kernel.sif_busy(bd->id);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,76 +68,20 @@ void IOP::wait_run_iop() {
|
||||
|
||||
void IOP::kill_from_ee() {
|
||||
want_exit = true;
|
||||
signal_run_iop();
|
||||
signal_run_iop(true);
|
||||
}
|
||||
|
||||
void IOP::signal_run_iop() {
|
||||
void IOP::signal_run_iop(bool force) {
|
||||
std::unique_lock<std::mutex> lk(iters_mutex);
|
||||
iop_iters_des++; // todo, tune this
|
||||
if (iop_iters_des - iop_iters_act > 500) {
|
||||
iop_iters_des = iop_iters_act + 500;
|
||||
if (iop_iters_act == iop_iters_des || force) {
|
||||
iop_iters_des++; // todo, tune this
|
||||
if (iop_iters_des - iop_iters_act > 500) {
|
||||
iop_iters_des = iop_iters_act + 500;
|
||||
}
|
||||
iop_run_cv.notify_all();
|
||||
}
|
||||
iop_run_cv.notify_all();
|
||||
}
|
||||
|
||||
IOP::~IOP() {
|
||||
reset_allocator();
|
||||
}
|
||||
|
||||
// void launch_iop(SystemThreadInterface& interface) {
|
||||
// IOP iop;
|
||||
//
|
||||
// printf("\n\n\n[IOP] Restart!\n");
|
||||
// iop.reset_allocator();
|
||||
//
|
||||
//// dma_init_globals();
|
||||
//// iso_init_globals();
|
||||
//// fake_iso_init_globals();
|
||||
//// // iso_api
|
||||
//// iso_cd_init_globals();
|
||||
//// iso_queue_init_globals();
|
||||
//// // isocommon
|
||||
//// // overlord
|
||||
//// ramdisk_init_globals();
|
||||
//// // sbank
|
||||
//// // soundcommon
|
||||
//// srpc_init_globals();
|
||||
// // ssound
|
||||
// // stream
|
||||
//
|
||||
//// SCE_IOP::PS2_RegisterIOP(&iop);
|
||||
//// PS2_RegisterIOP_EE(&iop);
|
||||
// interface.initialization_complete();
|
||||
//
|
||||
// printf("[IOP] Wait for OVERLORD to be started...\n");
|
||||
// iop.wait_for_overlord_start_cmd();
|
||||
// if(iop.status == IOP_OVERLORD_INIT) {
|
||||
// printf("[IOP] Run!\n");
|
||||
// } else {
|
||||
// printf("[IOP] shutdown!\n");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// iop.reset_allocator();
|
||||
//
|
||||
// // init
|
||||
//#ifdef ENABLE_OVERLORD
|
||||
// start(iop.overlord_argc, iop.overlord_argv);
|
||||
//#endif
|
||||
//
|
||||
// // unblock the EE, the overlord is set up!
|
||||
// iop.signal_overlord_init_finish();
|
||||
//
|
||||
// // IOP Kernel loop
|
||||
// while(!interface.get_want_exit() && !iop.want_exit) {
|
||||
// // the IOP kernel just runs at full blast, so we only run the IOP when the EE is waiting on
|
||||
// the IOP.
|
||||
// // Each time the EE is waiting on the IOP, it will run an iteration of the IOP kernel.
|
||||
// iop.wait_run_iop();
|
||||
// iop.kernel.dispatchAll();
|
||||
// }
|
||||
//
|
||||
// // stop all threads in the iop kernel.
|
||||
// // if the threads are not stopped nicely, we will deadlock on trying to destroy the kernel's
|
||||
// condition variables. iop.kernel.shutdown();
|
||||
//}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class IOP {
|
||||
void wait_for_overlord_start_cmd();
|
||||
void wait_for_overlord_init_finish();
|
||||
void signal_overlord_init_finish();
|
||||
void signal_run_iop();
|
||||
void signal_run_iop(bool force);
|
||||
void wait_run_iop();
|
||||
void kill_from_ee();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user