mirror of
https://github.com/open-goal/jak-project
synced 2026-06-14 06:25:53 -04:00
Loader improvements and blackout (#1254)
* temp * also tfrag * fix order
This commit is contained in:
@@ -39,6 +39,7 @@ struct GfxRendererModule {
|
||||
std::function<void(u32, u32, u32)> texture_relocate;
|
||||
std::function<void()> poll_events;
|
||||
std::function<void(const std::vector<std::string>&)> set_levels;
|
||||
std::function<void(float)> set_pmode_alp;
|
||||
GfxPipeline pipeline;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
@@ -13,6 +13,26 @@ std::string uppercase_string(const std::string& s) {
|
||||
}
|
||||
} // 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);
|
||||
@@ -24,8 +44,14 @@ const Loader::LevelData* Loader::get_tfrag3_level(const std::string& level_name)
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
@@ -36,9 +62,11 @@ void Loader::set_want_levels(const std::vector<std::string>& levels) {
|
||||
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();
|
||||
@@ -47,31 +75,45 @@ void Loader::set_want_levels(const std::vector<std::string>& levels) {
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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) {
|
||||
@@ -93,12 +135,20 @@ void Loader::loader_thread() {
|
||||
"------------> 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].data = {}; // reset load state
|
||||
m_initializing_tfrag3_levels[lev].data.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)}));
|
||||
@@ -111,6 +161,9 @@ void Loader::load_common(TexturePool& tex_pool, const std::string& name) {
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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);
|
||||
@@ -140,8 +193,295 @@ u64 Loader::add_texture(TexturePool& pool, const tfrag3::Texture& tex, bool is_c
|
||||
return gl_tex;
|
||||
}
|
||||
|
||||
Loader::Loader() {
|
||||
m_loader_thread = std::thread(&Loader::loader_thread, this);
|
||||
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_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_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();
|
||||
}
|
||||
|
||||
void Loader::update_blocking(std::string& status_out, 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(status_out, 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(std::string& status_out, TexturePool& texture_pool) {
|
||||
@@ -154,39 +494,28 @@ void Loader::update(std::string& status_out, TexturePool& texture_pool) {
|
||||
|
||||
bool did_gpu_stuff = false;
|
||||
|
||||
// work on moving initializing to initialized.
|
||||
{
|
||||
// try to move level from 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;
|
||||
constexpr int MAX_TEX_BYTES_PER_FRAME = 1024 * 128;
|
||||
auto& data = it->second.data;
|
||||
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();
|
||||
if (upload_textures(loader_timer, lev.data, texture_pool)) {
|
||||
if (init_tie(loader_timer, lev.data)) {
|
||||
if (init_tfrag(loader_timer, lev.data)) {
|
||||
// we're done! lock before removing from loaded.
|
||||
lk.lock();
|
||||
it->second.data.load_id = m_id++;
|
||||
|
||||
int bytes_this_run = 0;
|
||||
int tex_this_run = 0;
|
||||
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()];
|
||||
it->second.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) {
|
||||
status_out += fmt::format("LOAD tex {} kB\n", bytes_this_run / 1024);
|
||||
break;
|
||||
m_loaded_tfrag3_levels[name] = std::move(lev);
|
||||
m_initializing_tfrag3_levels.erase(it);
|
||||
}
|
||||
}
|
||||
if (bytes_this_run > MAX_TEX_BYTES_PER_FRAME ||
|
||||
loader_timer.getMs() > SHARED_TEXTURE_LOAD_BUDGET) {
|
||||
status_out += fmt::format("LOAD tex {} kB\n", bytes_this_run / 1024);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (data.textures.size() == data.level->textures.size()) {
|
||||
fmt::print("Loader texture complete: {}\n", it->first);
|
||||
it->second.data.load_id = m_id++;
|
||||
|
||||
m_loaded_tfrag3_levels[it->first] = std::move(it->second);
|
||||
m_initializing_tfrag3_levels.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,6 +548,21 @@ void Loader::update(std::string& status_out, TexturePool& texture_pool) {
|
||||
glDeleteTextures(1, &tex);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
m_loaded_tfrag3_levels.erase(lev.first);
|
||||
break;
|
||||
}
|
||||
@@ -230,23 +574,3 @@ void Loader::update(std::string& status_out, TexturePool& texture_pool) {
|
||||
fmt::print("Loader::update slow setup: {:.1f}ms\n", loader_timer.getMs());
|
||||
}
|
||||
}
|
||||
|
||||
Loader::~Loader() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_loader_mutex);
|
||||
m_want_shutdown = true;
|
||||
m_loader_cv.notify_all();
|
||||
}
|
||||
m_loader_thread.join();
|
||||
}
|
||||
|
||||
void Loader::hack_scramble_textures() {
|
||||
for (auto& it : m_loaded_tfrag3_levels) {
|
||||
int n = it.second.data.textures.size();
|
||||
for (int i = 0; i < n; i++) {
|
||||
int a = rand() % n;
|
||||
int b = rand() % n;
|
||||
std::swap(it.second.data.textures[a], it.second.data.textures[b]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#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:
|
||||
@@ -15,17 +16,39 @@ class Loader {
|
||||
Loader();
|
||||
~Loader();
|
||||
void update(std::string& status_out, TexturePool& tex_pool);
|
||||
void update_blocking(std::string& status_out, 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;
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
const LevelData* get_tfrag3_level(const std::string& level_name);
|
||||
void hack_scramble_textures();
|
||||
void load_common(TexturePool& tex_pool, const std::string& name);
|
||||
|
||||
void set_want_levels(const std::vector<std::string>& levels);
|
||||
|
||||
private:
|
||||
@@ -37,7 +60,11 @@ class Loader {
|
||||
void loader_thread();
|
||||
u64 add_texture(TexturePool& pool, const tfrag3::Texture& tex, bool is_common);
|
||||
|
||||
std::unordered_map<std::string, Level> m_loaded_tfrag3_levels;
|
||||
bool upload_textures(Timer& timer, LevelData& data, TexturePool& texture_pool);
|
||||
bool init_tie(Timer& timer, LevelData& data);
|
||||
bool init_tfrag(Timer& timer, LevelData& data);
|
||||
|
||||
// used by game and loader thread
|
||||
std::unordered_map<std::string, Level> m_initializing_tfrag3_levels;
|
||||
|
||||
tfrag3::Level m_common_level;
|
||||
@@ -47,6 +74,12 @@ class Loader {
|
||||
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, Level> m_loaded_tfrag3_levels;
|
||||
|
||||
std::vector<std::string> m_desired_levels;
|
||||
};
|
||||
|
||||
@@ -287,13 +287,30 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) {
|
||||
settings.lbox_height_px);
|
||||
}
|
||||
|
||||
// draw_test_triangle();
|
||||
{
|
||||
auto prof = m_profiler.root()->make_scoped_child("loader");
|
||||
if (m_last_pmode_alp == 0 && settings.pmode_alp_register != 0) {
|
||||
// blackout, load everything and don't worry about frame rate
|
||||
m_render_state.loader->update_blocking(m_render_state.load_status_debug,
|
||||
*m_render_state.texture_pool);
|
||||
|
||||
} else {
|
||||
m_render_state.loader->update(m_render_state.load_status_debug, *m_render_state.texture_pool);
|
||||
}
|
||||
}
|
||||
|
||||
// render the buckets!
|
||||
{
|
||||
auto prof = m_profiler.root()->make_scoped_child("buckets");
|
||||
dispatch_buckets(dma, prof);
|
||||
}
|
||||
|
||||
// apply effects done with PCRTC registers
|
||||
{
|
||||
auto prof = m_profiler.root()->make_scoped_child("pcrtc");
|
||||
do_pcrtc_effects(settings.pmode_alp_register, &m_render_state, prof);
|
||||
}
|
||||
|
||||
if (settings.draw_render_debug_window) {
|
||||
auto prof = m_profiler.root()->make_scoped_child("render-window");
|
||||
draw_renderer_selection_window();
|
||||
@@ -301,10 +318,7 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) {
|
||||
vif_interrupt_callback();
|
||||
}
|
||||
|
||||
{
|
||||
auto prof = m_profiler.root()->make_scoped_child("loader");
|
||||
m_render_state.loader->update(m_render_state.load_status_debug, *m_render_state.texture_pool);
|
||||
}
|
||||
m_last_pmode_alp = settings.pmode_alp_register;
|
||||
|
||||
m_profiler.finish();
|
||||
if (settings.draw_profiler_window) {
|
||||
@@ -434,64 +448,6 @@ void OpenGLRenderer::dispatch_buckets(DmaFollower dma, ScopedProfilerNode& prof)
|
||||
// TODO ending data.
|
||||
}
|
||||
|
||||
void OpenGLRenderer::draw_test_triangle() {
|
||||
// just remembering how to use opengl here.
|
||||
|
||||
//////////
|
||||
// Setup
|
||||
//////////
|
||||
|
||||
// create "buffer object names"
|
||||
GLuint vertex_buffer, color_buffer, vao;
|
||||
glGenBuffers(1, &vertex_buffer);
|
||||
glGenBuffers(1, &color_buffer);
|
||||
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
|
||||
// set vertex data
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
|
||||
const float verts[9] = {0.0, 0.8, 0, -0.5, -0.5 * .866, 0, 0.5, -0.5 * .866, 0};
|
||||
glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), verts, GL_STATIC_DRAW);
|
||||
|
||||
// set color data
|
||||
glBindBuffer(GL_ARRAY_BUFFER, color_buffer);
|
||||
const float colors[12] = {1., 0, 0., 1., 0., 1., 0., 1., 0., 0., 1., 1.};
|
||||
glBufferData(GL_ARRAY_BUFFER, 12 * sizeof(float), colors, GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
//////////
|
||||
// Draw!
|
||||
//////////
|
||||
m_render_state.shaders[ShaderId::TEST_SHADER].activate();
|
||||
|
||||
// location 0: the vertices
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, // location 0 in the shader
|
||||
3, // 3 floats per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized, ignored,
|
||||
0, // tightly packed
|
||||
0 // offset in array (why is is this a pointer...)
|
||||
);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, color_buffer);
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)0);
|
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glBindVertexArray(0);
|
||||
|
||||
////////////
|
||||
// Clean Up
|
||||
////////////
|
||||
// delete buffer
|
||||
glDeleteBuffers(1, &color_buffer);
|
||||
glDeleteBuffers(1, &vertex_buffer);
|
||||
glDeleteVertexArrays(1, &vao);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Take a screenshot!
|
||||
*/
|
||||
@@ -517,3 +473,18 @@ void OpenGLRenderer::finish_screenshot(const std::string& output_name,
|
||||
}
|
||||
file_util::write_rgba_png(output_name, buffer.data(), width, height);
|
||||
}
|
||||
|
||||
void OpenGLRenderer::do_pcrtc_effects(float alp,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof) {
|
||||
if (alp < 1) {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
|
||||
m_blackout_renderer.draw(Vector4f(0, 0, 0, 1.f - alp), render_state, prof);
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "game/graphics/opengl_renderer/Shader.h"
|
||||
#include "game/graphics/opengl_renderer/BucketRenderer.h"
|
||||
#include "game/graphics/opengl_renderer/Profiler.h"
|
||||
#include "game/graphics/opengl_renderer/opengl_utils.h"
|
||||
|
||||
struct RenderOptions {
|
||||
int window_height_px = 0;
|
||||
@@ -19,6 +20,8 @@ struct RenderOptions {
|
||||
|
||||
bool save_screenshot = false;
|
||||
std::string screenshot_path;
|
||||
|
||||
float pmode_alp_register = 0.f;
|
||||
};
|
||||
|
||||
class OpenGLRenderer {
|
||||
@@ -28,11 +31,10 @@ class OpenGLRenderer {
|
||||
|
||||
private:
|
||||
void setup_frame(int window_width_px, int window_height_px, int offset_x, int offset_y);
|
||||
void draw_test_triangle();
|
||||
void dispatch_buckets(DmaFollower dma, ScopedProfilerNode& prof);
|
||||
void do_pcrtc_effects(float alp, SharedRenderState* render_state, ScopedProfilerNode& prof);
|
||||
void init_bucket_renderers();
|
||||
void draw_renderer_selection_window();
|
||||
|
||||
void finish_screenshot(const std::string& output_name, int px, int py, int x, int y);
|
||||
|
||||
template <typename T, class... Args>
|
||||
@@ -55,4 +57,7 @@ class OpenGLRenderer {
|
||||
std::array<BucketCategory, (int)BucketId::MAX_BUCKETS> m_bucket_categories;
|
||||
|
||||
std::array<float, (int)BucketCategory::MAX_CATEGORIES> m_category_times;
|
||||
FullScreenDraw m_blackout_renderer;
|
||||
|
||||
float m_last_pmode_alp = 1.;
|
||||
};
|
||||
|
||||
@@ -65,7 +65,7 @@ void Shader::activate() {
|
||||
}
|
||||
|
||||
ShaderLibrary::ShaderLibrary() {
|
||||
at(ShaderId::TEST_SHADER) = {"test_shader"};
|
||||
at(ShaderId::SOLID_COLOR) = {"solid_color"};
|
||||
at(ShaderId::DIRECT_BASIC) = {"direct_basic"};
|
||||
at(ShaderId::DIRECT_BASIC_TEXTURED) = {"direct_basic_textured"};
|
||||
at(ShaderId::DEBUG_RED) = {"debug_red"};
|
||||
|
||||
@@ -22,7 +22,7 @@ class Shader {
|
||||
|
||||
// note: update the constructor in Shader.cpp
|
||||
enum class ShaderId {
|
||||
TEST_SHADER = 0,
|
||||
SOLID_COLOR = 0,
|
||||
DIRECT_BASIC = 1,
|
||||
DIRECT_BASIC_TEXTURED = 2,
|
||||
DEBUG_RED = 3,
|
||||
|
||||
@@ -193,17 +193,9 @@ void TFragment::render(DmaFollower& dma,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_hack_scrambler) {
|
||||
render_state->loader->hack_scramble_textures();
|
||||
m_hack_scrambler = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TFragment::draw_debug_window() {
|
||||
if (ImGui::Button("Scrambler")) {
|
||||
m_hack_scrambler = true;
|
||||
}
|
||||
ImGui::Checkbox("Manual Time of Day", &m_override_time_of_day);
|
||||
if (m_override_time_of_day) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
|
||||
@@ -47,7 +47,6 @@ class TFragment : public BucketRenderer {
|
||||
void handle_initialization(DmaFollower& dma);
|
||||
|
||||
std::string m_debug_string;
|
||||
bool m_hack_scrambler = false;
|
||||
bool m_child_mode = false;
|
||||
bool m_hack_test_many_levels = false;
|
||||
bool m_override_time_of_day = false;
|
||||
|
||||
@@ -38,204 +38,124 @@ Tfrag3::~Tfrag3() {
|
||||
glDeleteVertexArrays(1, &m_debug_vao);
|
||||
}
|
||||
|
||||
bool Tfrag3::update_load(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const tfrag3::Level* lev_data,
|
||||
std::string& status_out) {
|
||||
switch (m_load_state.state) {
|
||||
case State::DISCARD_TREE:
|
||||
discard_tree_cache();
|
||||
m_load_state.state = State::FREE_OLD_TREES;
|
||||
break;
|
||||
case State::FREE_OLD_TREES:
|
||||
for (int geom = 0; geom < GEOM_MAX; ++geom) {
|
||||
m_cached_trees[geom].clear();
|
||||
}
|
||||
m_load_state.state = State::INIT_NEW_TREES;
|
||||
break;
|
||||
case State::INIT_NEW_TREES:
|
||||
|
||||
{
|
||||
size_t time_of_day_count = 0;
|
||||
size_t vis_temp_len = 0;
|
||||
size_t max_draw = 0;
|
||||
|
||||
for (int geom = 0; geom < GEOM_MAX; ++geom) {
|
||||
for (size_t tree_idx = 0; tree_idx < lev_data->tfrag_trees[geom].size(); tree_idx++) {
|
||||
size_t idx_buffer_len = 0;
|
||||
|
||||
const auto& tree = lev_data->tfrag_trees[geom][tree_idx];
|
||||
|
||||
auto& tree_cache = m_cached_trees[geom].emplace_back();
|
||||
|
||||
tree_cache.kind = tree.kind;
|
||||
if (std::find(tree_kinds.begin(), tree_kinds.end(), tree.kind) != tree_kinds.end()) {
|
||||
max_draw = std::max(tree.draws.size(), max_draw);
|
||||
for (auto& draw : tree.draws) {
|
||||
idx_buffer_len += draw.unpacked.vertex_index_stream.size();
|
||||
}
|
||||
time_of_day_count = std::max(tree.colors.size(), time_of_day_count);
|
||||
u32 verts = tree.packed_vertices.vertices.size();
|
||||
glGenVertexArrays(1, &tree_cache.vao);
|
||||
glBindVertexArray(tree_cache.vao);
|
||||
glGenBuffers(1, &tree_cache.vertex_buffer);
|
||||
tree_cache.vert_count = verts;
|
||||
tree_cache.draws = &tree.draws; // todo - should we just copy this?
|
||||
tree_cache.colors = &tree.colors;
|
||||
tree_cache.vis = &tree.bvh;
|
||||
tree_cache.tod_cache = swizzle_time_of_day(tree.colors);
|
||||
vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size());
|
||||
glBindBuffer(GL_ARRAY_BUFFER, tree_cache.vertex_buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, verts * sizeof(tfrag3::PreloadedVertex), nullptr,
|
||||
GL_STREAM_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glEnableVertexAttribArray(2);
|
||||
|
||||
glVertexAttribPointer(0, // location 0 in the shader
|
||||
3, // 3 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, x) // offset (0)
|
||||
);
|
||||
|
||||
glVertexAttribPointer(1, // location 1 in the shader
|
||||
3, // 3 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, s) // offset (0)
|
||||
);
|
||||
|
||||
glVertexAttribIPointer(
|
||||
2, // location 2 in the shader
|
||||
1, // 1 values per vert
|
||||
GL_UNSIGNED_SHORT, // u16
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, color_index) // offset (0)
|
||||
);
|
||||
|
||||
glGenBuffers(1, &tree_cache.index_buffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tree_cache.index_buffer);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr,
|
||||
GL_STREAM_DRAW);
|
||||
tree_cache.index_list.resize(idx_buffer_len);
|
||||
|
||||
glGenTextures(1, &tree_cache.time_of_day_texture);
|
||||
glBindTexture(GL_TEXTURE_1D, tree_cache.time_of_day_texture);
|
||||
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA,
|
||||
GL_UNSIGNED_INT_8_8_8_8, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_cache.vis_temp.resize(vis_temp_len);
|
||||
m_cache.draw_idx_temp.resize(max_draw);
|
||||
|
||||
ASSERT(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT);
|
||||
m_load_state.state = UPLOAD_VERTS;
|
||||
m_load_state.vert_geo = 0;
|
||||
m_load_state.vert_tree = 0;
|
||||
m_load_state.vert = 0;
|
||||
m_load_state.vert_debug_bytes = 0;
|
||||
} break;
|
||||
|
||||
case State::UPLOAD_VERTS: {
|
||||
constexpr u32 MAX_VERTS = 20000; // about 1.6 MB
|
||||
u32 remaining_verts = MAX_VERTS;
|
||||
|
||||
// loop over geos/trees, picking up where we left off last time
|
||||
while (m_load_state.vert_geo < tfrag3::TFRAG_GEOS) {
|
||||
while (m_load_state.vert_tree < lev_data->tfrag_trees[m_load_state.vert_geo].size()) {
|
||||
const auto& tree = lev_data->tfrag_trees[m_load_state.vert_geo][m_load_state.vert_tree];
|
||||
|
||||
if (std::find(tree_kinds.begin(), tree_kinds.end(), tree.kind) != tree_kinds.end()) {
|
||||
// the number of vertices we'd need to finish the tree right now
|
||||
size_t num_verts_left_in_tree = tree.unpacked.vertices.size() - m_load_state.vert;
|
||||
// the last vertex in the tree
|
||||
u32 last_vert = tree.unpacked.vertices.size();
|
||||
|
||||
bool need_more = false;
|
||||
if (num_verts_left_in_tree > remaining_verts) {
|
||||
need_more = true;
|
||||
last_vert = m_load_state.vert + remaining_verts;
|
||||
} else {
|
||||
remaining_verts -= num_verts_left_in_tree;
|
||||
}
|
||||
|
||||
glBindVertexArray(m_cached_trees[m_load_state.vert_geo][m_load_state.vert_tree].vao);
|
||||
glBindBuffer(
|
||||
GL_ARRAY_BUFFER,
|
||||
m_cached_trees[m_load_state.vert_geo][m_load_state.vert_tree].vertex_buffer);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, m_load_state.vert * sizeof(tfrag3::PreloadedVertex),
|
||||
(last_vert - m_load_state.vert) * sizeof(tfrag3::PreloadedVertex),
|
||||
tree.unpacked.vertices.data() + m_load_state.vert);
|
||||
|
||||
m_load_state.vert_debug_bytes +=
|
||||
(last_vert - m_load_state.vert) * sizeof(tfrag3::PreloadedVertex);
|
||||
|
||||
m_load_state.vert = last_vert;
|
||||
if (need_more) {
|
||||
status_out +=
|
||||
fmt::format("TFRAG vertex add: {} kB\n", m_load_state.vert_debug_bytes / 1024);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_load_state.vert_tree++;
|
||||
m_load_state.vert = 0;
|
||||
}
|
||||
m_load_state.vert_geo++;
|
||||
m_load_state.vert_tree = 0;
|
||||
}
|
||||
return true;
|
||||
} break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
void Tfrag3::update_load(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const Loader::LevelData* loader_data) {
|
||||
const auto* lev_data = loader_data->level.get();
|
||||
discard_tree_cache();
|
||||
for (int geom = 0; geom < GEOM_MAX; ++geom) {
|
||||
m_cached_trees[geom].clear();
|
||||
}
|
||||
|
||||
return false;
|
||||
size_t time_of_day_count = 0;
|
||||
size_t vis_temp_len = 0;
|
||||
size_t max_draw = 0;
|
||||
|
||||
for (int geom = 0; geom < GEOM_MAX; ++geom) {
|
||||
for (size_t tree_idx = 0; tree_idx < lev_data->tfrag_trees[geom].size(); tree_idx++) {
|
||||
size_t idx_buffer_len = 0;
|
||||
|
||||
const auto& tree = lev_data->tfrag_trees[geom][tree_idx];
|
||||
|
||||
auto& tree_cache = m_cached_trees[geom].emplace_back();
|
||||
|
||||
tree_cache.kind = tree.kind;
|
||||
if (std::find(tree_kinds.begin(), tree_kinds.end(), tree.kind) != tree_kinds.end()) {
|
||||
max_draw = std::max(tree.draws.size(), max_draw);
|
||||
for (auto& draw : tree.draws) {
|
||||
idx_buffer_len += draw.unpacked.vertex_index_stream.size();
|
||||
}
|
||||
time_of_day_count = std::max(tree.colors.size(), time_of_day_count);
|
||||
u32 verts = tree.packed_vertices.vertices.size();
|
||||
glGenVertexArrays(1, &tree_cache.vao);
|
||||
glBindVertexArray(tree_cache.vao);
|
||||
// glGenBuffers(1, &tree_cache.vertex_buffer);
|
||||
tree_cache.vertex_buffer = loader_data->tfrag_vertex_data[geom][tree_idx];
|
||||
tree_cache.vert_count = verts;
|
||||
tree_cache.draws = &tree.draws; // todo - should we just copy this?
|
||||
tree_cache.colors = &tree.colors;
|
||||
tree_cache.vis = &tree.bvh;
|
||||
tree_cache.tod_cache = swizzle_time_of_day(tree.colors);
|
||||
vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size());
|
||||
glBindBuffer(GL_ARRAY_BUFFER, tree_cache.vertex_buffer);
|
||||
// glBufferData(GL_ARRAY_BUFFER, verts * sizeof(tfrag3::PreloadedVertex),
|
||||
// nullptr,
|
||||
// GL_STREAM_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glEnableVertexAttribArray(2);
|
||||
|
||||
glVertexAttribPointer(0, // location 0 in the shader
|
||||
3, // 3 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, x) // offset (0)
|
||||
);
|
||||
|
||||
glVertexAttribPointer(1, // location 1 in the shader
|
||||
3, // 3 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, s) // offset (0)
|
||||
);
|
||||
|
||||
glVertexAttribIPointer(2, // location 2 in the shader
|
||||
1, // 1 values per vert
|
||||
GL_UNSIGNED_SHORT, // u16
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, color_index) // offset (0)
|
||||
);
|
||||
|
||||
glGenBuffers(1, &tree_cache.index_buffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tree_cache.index_buffer);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr,
|
||||
GL_STREAM_DRAW);
|
||||
tree_cache.index_list.resize(idx_buffer_len);
|
||||
|
||||
glGenTextures(1, &tree_cache.time_of_day_texture);
|
||||
glBindTexture(GL_TEXTURE_1D, tree_cache.time_of_day_texture);
|
||||
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA,
|
||||
GL_UNSIGNED_INT_8_8_8_8, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_cache.vis_temp.resize(vis_temp_len);
|
||||
m_cache.draw_idx_temp.resize(max_draw);
|
||||
|
||||
ASSERT(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT);
|
||||
}
|
||||
|
||||
bool Tfrag3::setup_for_level(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const std::string& level,
|
||||
SharedRenderState* render_state) {
|
||||
// first, get the level in memory
|
||||
// make sure we have the level data.
|
||||
Timer tfrag3_setup_timer;
|
||||
|
||||
const auto* lev_data = render_state->loader->get_tfrag3_level(level);
|
||||
auto lev_data = render_state->loader->get_tfrag3_level(level);
|
||||
if (!lev_data || (m_has_level && lev_data->load_id != m_load_id)) {
|
||||
m_has_level = false;
|
||||
m_textures = nullptr;
|
||||
m_level_name = "";
|
||||
|
||||
discard_tree_cache();
|
||||
return false;
|
||||
}
|
||||
int init_load_state = m_load_state.state;
|
||||
m_textures = &lev_data->textures;
|
||||
m_load_id = lev_data->load_id;
|
||||
|
||||
if (m_level_name != level) {
|
||||
m_has_level = false;
|
||||
if (!m_load_state.loading) {
|
||||
m_load_state.loading = true;
|
||||
m_load_state.state = State::FIRST;
|
||||
}
|
||||
if (update_load(tree_kinds, lev_data->level.get(), render_state->load_status_debug)) {
|
||||
m_has_level = true;
|
||||
m_level_name = level;
|
||||
m_load_state.loading = false;
|
||||
}
|
||||
|
||||
update_load(tree_kinds, lev_data);
|
||||
m_has_level = true;
|
||||
m_textures = &lev_data->textures;
|
||||
m_level_name = level;
|
||||
} else {
|
||||
m_has_level = true;
|
||||
}
|
||||
|
||||
if (tfrag3_setup_timer.getMs() > 5) {
|
||||
fmt::print("TFRAG slow setup: {:.1f}ms s {}\n", tfrag3_setup_timer.getMs(), init_load_state);
|
||||
fmt::print("TFRAG setup: {:.1f}ms\n", tfrag3_setup_timer.getMs());
|
||||
}
|
||||
|
||||
return m_has_level;
|
||||
@@ -290,6 +210,7 @@ void Tfrag3::render_tree(int geom,
|
||||
continue;
|
||||
}
|
||||
|
||||
ASSERT(m_textures);
|
||||
glBindTexture(GL_TEXTURE_2D, m_textures->at(draw.tree_tex_id));
|
||||
auto double_draw = setup_tfrag_shader(render_state, draw.mode);
|
||||
tree.tris_this_frame += draw.num_triangles;
|
||||
@@ -415,7 +336,7 @@ void Tfrag3::discard_tree_cache() {
|
||||
if (tree.kind != tfrag3::TFragmentTreeKind::INVALID) {
|
||||
glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture);
|
||||
glDeleteTextures(1, &tree.time_of_day_texture);
|
||||
glDeleteBuffers(1, &tree.vertex_buffer);
|
||||
// glDeleteBuffers(1, &tree.vertex_buffer);
|
||||
glDeleteBuffers(1, &tree.index_buffer);
|
||||
glDeleteVertexArrays(1, &tree.vao);
|
||||
}
|
||||
|
||||
@@ -48,9 +48,8 @@ class Tfrag3 {
|
||||
math::Vector4f rgba;
|
||||
};
|
||||
|
||||
bool update_load(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const tfrag3::Level* lev_data,
|
||||
std::string& status_out);
|
||||
void update_load(const std::vector<tfrag3::TFragmentTreeKind>& tree_kinds,
|
||||
const Loader::LevelData* loader_data);
|
||||
|
||||
int lod() const { return Gfx::g_global_settings.lod_tfrag; }
|
||||
|
||||
@@ -109,22 +108,4 @@ class Tfrag3 {
|
||||
|
||||
bool m_has_level = false;
|
||||
bool m_use_fast_time_of_day = true;
|
||||
|
||||
enum State : u32 {
|
||||
FIRST = 0,
|
||||
DISCARD_TREE = 0,
|
||||
FREE_OLD_TREES = 1,
|
||||
INIT_NEW_TREES = 2,
|
||||
UPLOAD_VERTS = 3,
|
||||
};
|
||||
|
||||
struct {
|
||||
bool loading = false;
|
||||
State state;
|
||||
u32 vert = 0;
|
||||
u32 vert_geo = 0;
|
||||
u32 vert_tree = 0;
|
||||
u32 vert_debug_bytes = 0;
|
||||
} m_load_state;
|
||||
static constexpr int MAX_TEX_PER_FRAME = 4;
|
||||
};
|
||||
|
||||
@@ -14,234 +14,117 @@ Tie3::~Tie3() {
|
||||
discard_tree_cache();
|
||||
}
|
||||
|
||||
bool Tie3::update_load(const tfrag3::Level* lev_data, std::string& status_out) {
|
||||
switch (m_load_state.state) {
|
||||
case DISCARD_TREE:
|
||||
m_wind_vectors.clear();
|
||||
// We changed level!
|
||||
discard_tree_cache();
|
||||
for (int geo = 0; geo < 4; ++geo) {
|
||||
m_trees[geo].resize(lev_data->tie_trees[geo].size());
|
||||
}
|
||||
m_load_state.state = INIT_NEW_TREES;
|
||||
m_load_state.vert = 0;
|
||||
m_load_state.vert_tree = 0;
|
||||
m_load_state.vert_geo = 0;
|
||||
|
||||
m_load_state.time_of_day_count = 0;
|
||||
m_load_state.vis_temp_len = 0;
|
||||
m_load_state.max_draw = 0;
|
||||
m_load_state.max_idx_per_draw = 0;
|
||||
m_load_state.max_wind_idx = 0;
|
||||
status_out += "TIE cleanup\n";
|
||||
break;
|
||||
case INIT_NEW_TREES: {
|
||||
// set up each tree for each lod
|
||||
bool should_abort = false;
|
||||
for (; m_load_state.vert_geo < tfrag3::TIE_GEOS; m_load_state.vert_geo++) {
|
||||
for (; m_load_state.vert_tree < lev_data->tie_trees[m_load_state.vert_geo].size();
|
||||
m_load_state.vert_tree++) {
|
||||
if (should_abort) {
|
||||
status_out += "TIE tree add\n";
|
||||
return false;
|
||||
}
|
||||
const auto tree_idx = m_load_state.vert_tree;
|
||||
size_t idx_buffer_len = 0;
|
||||
size_t wind_idx_buffer_len = 0;
|
||||
const auto& tree = lev_data->tie_trees[m_load_state.vert_geo][tree_idx];
|
||||
m_load_state.max_draw = std::max(tree.static_draws.size(), m_load_state.max_draw);
|
||||
for (auto& draw : tree.static_draws) {
|
||||
idx_buffer_len += draw.unpacked.vertex_index_stream.size();
|
||||
m_load_state.max_idx_per_draw =
|
||||
std::max(m_load_state.max_idx_per_draw, draw.unpacked.vertex_index_stream.size());
|
||||
}
|
||||
for (auto& draw : tree.instanced_wind_draws) {
|
||||
wind_idx_buffer_len += draw.vertex_index_stream.size();
|
||||
m_load_state.max_idx_per_draw =
|
||||
std::max(m_load_state.max_idx_per_draw, draw.vertex_index_stream.size());
|
||||
}
|
||||
for (auto& inst : tree.wind_instance_info) {
|
||||
m_load_state.max_wind_idx = std::max(m_load_state.max_wind_idx, inst.wind_idx);
|
||||
}
|
||||
m_load_state.time_of_day_count =
|
||||
std::max(tree.colors.size(), m_load_state.time_of_day_count);
|
||||
u32 verts = tree.packed_vertices.color_indices.size();
|
||||
auto& lod_tree = m_trees.at(m_load_state.vert_geo);
|
||||
glGenVertexArrays(1, &lod_tree[tree_idx].vao);
|
||||
glBindVertexArray(lod_tree[tree_idx].vao);
|
||||
glGenBuffers(1, &lod_tree[tree_idx].vertex_buffer);
|
||||
lod_tree[tree_idx].vert_count = verts;
|
||||
lod_tree[tree_idx].draws = &tree.static_draws; // todo - should we just copy this?
|
||||
lod_tree[tree_idx].colors = &tree.colors;
|
||||
lod_tree[tree_idx].vis = &tree.bvh;
|
||||
lod_tree[tree_idx].instance_info = &tree.wind_instance_info;
|
||||
lod_tree[tree_idx].wind_draws = &tree.instanced_wind_draws;
|
||||
m_load_state.vis_temp_len =
|
||||
std::max(m_load_state.vis_temp_len, tree.bvh.vis_nodes.size());
|
||||
lod_tree[tree_idx].tod_cache = swizzle_time_of_day(tree.colors);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, lod_tree[tree_idx].vertex_buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, verts * sizeof(tfrag3::PreloadedVertex), nullptr,
|
||||
GL_STATIC_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glEnableVertexAttribArray(2);
|
||||
|
||||
glVertexAttribPointer(0, // location 0 in the shader
|
||||
3, // 3 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, x) // offset (0)
|
||||
);
|
||||
|
||||
glVertexAttribPointer(1, // location 1 in the shader
|
||||
3, // 3 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, s) // offset (0)
|
||||
);
|
||||
|
||||
glVertexAttribIPointer(
|
||||
2, // location 2 in the shader
|
||||
1, // 1 values per vert
|
||||
GL_UNSIGNED_SHORT, // u16
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, color_index) // offset (0)
|
||||
);
|
||||
|
||||
glGenBuffers(1, &lod_tree[tree_idx].index_buffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lod_tree[tree_idx].index_buffer);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr,
|
||||
GL_STREAM_DRAW);
|
||||
lod_tree[tree_idx].index_list.resize(idx_buffer_len);
|
||||
|
||||
if (wind_idx_buffer_len > 0) {
|
||||
lod_tree[tree_idx].wind_matrix_cache.resize(tree.wind_instance_info.size());
|
||||
lod_tree[tree_idx].has_wind = true;
|
||||
glGenBuffers(1, &lod_tree[tree_idx].wind_vertex_index_buffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lod_tree[tree_idx].wind_vertex_index_buffer);
|
||||
std::vector<u32> temp;
|
||||
temp.resize(wind_idx_buffer_len);
|
||||
u32 off = 0;
|
||||
for (auto& draw : tree.instanced_wind_draws) {
|
||||
lod_tree[tree_idx].wind_vertex_index_offsets.push_back(off);
|
||||
memcpy(temp.data() + off, draw.vertex_index_stream.data(),
|
||||
draw.vertex_index_stream.size() * sizeof(u32));
|
||||
off += draw.vertex_index_stream.size();
|
||||
}
|
||||
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, wind_idx_buffer_len * sizeof(u32), temp.data(),
|
||||
GL_STATIC_DRAW);
|
||||
should_abort = true;
|
||||
}
|
||||
|
||||
glActiveTexture(GL_TEXTURE10);
|
||||
glGenTextures(1, &lod_tree[tree_idx].time_of_day_texture);
|
||||
glBindTexture(GL_TEXTURE_1D, lod_tree[tree_idx].time_of_day_texture);
|
||||
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA,
|
||||
GL_UNSIGNED_INT_8_8_8_8, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
m_load_state.vert_tree = 0;
|
||||
}
|
||||
|
||||
// fmt::print("TIE temporary vis output size: {}\n", vis_temp_len);
|
||||
m_cache.vis_temp.resize(m_load_state.vis_temp_len);
|
||||
// fmt::print("TIE max draws/tree: {}\n", max_draw);
|
||||
m_cache.draw_idx_temp.resize(m_load_state.max_draw);
|
||||
// fmt::print("TIE draw with the most verts: {}\n", max_idx_per_draw);
|
||||
// fmt::print("wind: {}\n", max_wind_idx);
|
||||
m_wind_vectors.resize(4 * m_load_state.max_wind_idx + 4); // 4x u32's per wind.
|
||||
// fmt::print("level max time of day: {}\n", time_of_day_count);
|
||||
ASSERT(m_load_state.time_of_day_count <= TIME_OF_DAY_COLOR_COUNT);
|
||||
}
|
||||
m_load_state.state = UPLOAD_VERTS;
|
||||
|
||||
m_load_state.vert_geo = 0;
|
||||
m_load_state.vert_tree = 0;
|
||||
m_load_state.vert = 0;
|
||||
m_load_state.vert_debug_bytes = 0;
|
||||
|
||||
break;
|
||||
|
||||
case State::UPLOAD_VERTS: {
|
||||
constexpr u32 CHUNK_SIZE = 30000;
|
||||
|
||||
Timer timer;
|
||||
u32 uploaded_bytes = 0;
|
||||
|
||||
// loop over geos/trees, picking up where we left off last time
|
||||
while (true) {
|
||||
const auto& tree = lev_data->tie_trees[m_load_state.vert_geo][m_load_state.vert_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_load_state.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_load_state.vert;
|
||||
end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE;
|
||||
m_load_state.vert += CHUNK_SIZE;
|
||||
} else {
|
||||
// should do all!
|
||||
start_vert_for_chunk = m_load_state.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,
|
||||
m_trees[m_load_state.vert_geo][m_load_state.vert_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_load_state.vert = 0;
|
||||
m_load_state.vert_tree++;
|
||||
if (m_load_state.vert_tree >= lev_data->tie_trees[m_load_state.vert_geo].size()) {
|
||||
m_load_state.vert_tree = 0;
|
||||
m_load_state.vert_geo++;
|
||||
if (m_load_state.vert_geo >= tfrag3::TIE_GEOS) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (timer.getMs() > Loader::TIE_LOAD_BUDGET || (uploaded_bytes / 1024) > 2048) {
|
||||
status_out +=
|
||||
fmt::format("TIE vertex {:6d} kB, {:3.2f}ms\n", uploaded_bytes / 1024, timer.getMs());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} break;
|
||||
|
||||
default:
|
||||
ASSERT(false);
|
||||
void Tie3::update_load(const Loader::LevelData* loader_data) {
|
||||
const tfrag3::Level* lev_data = loader_data->level.get();
|
||||
m_wind_vectors.clear();
|
||||
// We changed level!
|
||||
discard_tree_cache();
|
||||
for (int geo = 0; geo < 4; ++geo) {
|
||||
m_trees[geo].resize(lev_data->tie_trees[geo].size());
|
||||
}
|
||||
|
||||
return false;
|
||||
size_t vis_temp_len = 0;
|
||||
size_t max_draws = 0;
|
||||
u16 max_wind_idx = 0;
|
||||
size_t time_of_day_count = 0;
|
||||
for (u32 l_geo = 0; l_geo < tfrag3::TIE_GEOS; l_geo++) {
|
||||
for (u32 l_tree = 0; l_tree < lev_data->tie_trees[l_geo].size(); l_tree++) {
|
||||
size_t idx_buffer_len = 0;
|
||||
size_t wind_idx_buffer_len = 0;
|
||||
const auto& tree = lev_data->tie_trees[l_geo][l_tree];
|
||||
max_draws = std::max(tree.static_draws.size(), max_draws);
|
||||
for (auto& draw : tree.static_draws) {
|
||||
idx_buffer_len += draw.unpacked.vertex_index_stream.size();
|
||||
}
|
||||
for (auto& draw : tree.instanced_wind_draws) {
|
||||
wind_idx_buffer_len += draw.vertex_index_stream.size();
|
||||
}
|
||||
for (auto& inst : tree.wind_instance_info) {
|
||||
max_wind_idx = std::max(max_wind_idx, inst.wind_idx);
|
||||
}
|
||||
time_of_day_count = std::max(tree.colors.size(), time_of_day_count);
|
||||
u32 verts = tree.packed_vertices.color_indices.size();
|
||||
auto& lod_tree = m_trees.at(l_geo);
|
||||
glGenVertexArrays(1, &lod_tree[l_tree].vao);
|
||||
glBindVertexArray(lod_tree[l_tree].vao);
|
||||
lod_tree[l_tree].vertex_buffer = loader_data->tie_data[l_geo][l_tree].vertex_buffer;
|
||||
lod_tree[l_tree].vert_count = verts;
|
||||
lod_tree[l_tree].draws = &tree.static_draws;
|
||||
lod_tree[l_tree].colors = &tree.colors;
|
||||
lod_tree[l_tree].vis = &tree.bvh;
|
||||
lod_tree[l_tree].instance_info = &tree.wind_instance_info;
|
||||
lod_tree[l_tree].wind_draws = &tree.instanced_wind_draws;
|
||||
vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size());
|
||||
lod_tree[l_tree].tod_cache = swizzle_time_of_day(tree.colors);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, lod_tree[l_tree].vertex_buffer);
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glEnableVertexAttribArray(2);
|
||||
|
||||
glVertexAttribPointer(0, // location 0 in the shader
|
||||
3, // 3 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, x) // offset (0)
|
||||
);
|
||||
|
||||
glVertexAttribPointer(1, // location 1 in the shader
|
||||
3, // 3 values per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, s) // offset (0)
|
||||
);
|
||||
|
||||
glVertexAttribIPointer(2, // location 2 in the shader
|
||||
1, // 1 values per vert
|
||||
GL_UNSIGNED_SHORT, // u16
|
||||
sizeof(tfrag3::PreloadedVertex), // stride
|
||||
(void*)offsetof(tfrag3::PreloadedVertex, color_index) // offset (0)
|
||||
);
|
||||
|
||||
glGenBuffers(1, &lod_tree[l_tree].index_buffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lod_tree[l_tree].index_buffer);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr, GL_STREAM_DRAW);
|
||||
lod_tree[l_tree].index_list.resize(idx_buffer_len);
|
||||
|
||||
if (wind_idx_buffer_len > 0) {
|
||||
lod_tree[l_tree].wind_matrix_cache.resize(tree.wind_instance_info.size());
|
||||
lod_tree[l_tree].has_wind = true;
|
||||
lod_tree[l_tree].wind_vertex_index_buffer =
|
||||
loader_data->tie_data[l_geo][l_tree].wind_indices;
|
||||
u32 off = 0;
|
||||
for (auto& draw : tree.instanced_wind_draws) {
|
||||
lod_tree[l_tree].wind_vertex_index_offsets.push_back(off);
|
||||
off += draw.vertex_index_stream.size();
|
||||
}
|
||||
}
|
||||
|
||||
glActiveTexture(GL_TEXTURE10);
|
||||
glGenTextures(1, &lod_tree[l_tree].time_of_day_texture);
|
||||
glBindTexture(GL_TEXTURE_1D, lod_tree[l_tree].time_of_day_texture);
|
||||
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA,
|
||||
GL_UNSIGNED_INT_8_8_8_8, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
||||
m_cache.vis_temp.resize(vis_temp_len);
|
||||
m_cache.draw_idx_temp.resize(max_draws);
|
||||
m_wind_vectors.resize(4 * max_wind_idx + 4); // 4x u32's per wind.
|
||||
ASSERT(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set up all OpenGL and temporary buffers for a given level name.
|
||||
* The level name should be the 3 character short name.
|
||||
*/
|
||||
bool Tie3::setup_for_level(const std::string& level, SharedRenderState* render_state) {
|
||||
// make sure we have the level data.
|
||||
// TODO: right now this will wait to load from disk and unpack it.
|
||||
Timer tfrag3_setup_timer;
|
||||
auto lev_data = render_state->loader->get_tfrag3_level(level);
|
||||
if (!lev_data || (m_has_level && lev_data->load_id != m_load_id)) {
|
||||
@@ -253,25 +136,17 @@ bool Tie3::setup_for_level(const std::string& level, SharedRenderState* render_s
|
||||
}
|
||||
m_textures = &lev_data->textures;
|
||||
m_load_id = lev_data->load_id;
|
||||
int init_load_state = m_load_state.state;
|
||||
|
||||
if (m_level_name != level) {
|
||||
m_has_level = false;
|
||||
if (!m_load_state.loading) {
|
||||
m_load_state.loading = true;
|
||||
m_load_state.state = State::FIRST;
|
||||
}
|
||||
if (update_load(lev_data->level.get(), render_state->load_status_debug)) {
|
||||
m_has_level = true;
|
||||
m_level_name = level;
|
||||
m_load_state.loading = false;
|
||||
}
|
||||
update_load(lev_data);
|
||||
m_has_level = true;
|
||||
m_level_name = level;
|
||||
} else {
|
||||
m_has_level = true;
|
||||
}
|
||||
|
||||
if (tfrag3_setup_timer.getMs() > 5) {
|
||||
fmt::print("TIE setup: {:.1f}ms s {}\n", tfrag3_setup_timer.getMs(), init_load_state);
|
||||
fmt::print("TIE setup: {:.1f}ms\n", tfrag3_setup_timer.getMs());
|
||||
}
|
||||
|
||||
return m_has_level;
|
||||
@@ -392,12 +267,8 @@ void Tie3::discard_tree_cache() {
|
||||
for (auto& tree : m_trees[geo]) {
|
||||
glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture);
|
||||
glDeleteTextures(1, &tree.time_of_day_texture);
|
||||
glDeleteBuffers(1, &tree.vertex_buffer);
|
||||
glDeleteBuffers(1, &tree.index_buffer);
|
||||
glDeleteVertexArrays(1, &tree.vao);
|
||||
if (tree.has_wind) {
|
||||
glDeleteBuffers(1, &tree.wind_vertex_index_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
m_trees[geo].clear();
|
||||
|
||||
@@ -40,7 +40,7 @@ class Tie3 : public BucketRenderer {
|
||||
int lod() const { return Gfx::g_global_settings.lod_tie; }
|
||||
|
||||
private:
|
||||
bool update_load(const tfrag3::Level* lev_data, std::string& status_out);
|
||||
void update_load(const Loader::LevelData* loader_data);
|
||||
void discard_tree_cache();
|
||||
void render_tree_wind(int idx,
|
||||
int geom,
|
||||
@@ -116,31 +116,4 @@ class Tie3 : public BucketRenderer {
|
||||
int m_level_id;
|
||||
|
||||
static_assert(sizeof(WindWork) == 84 * 16);
|
||||
|
||||
enum State : u32 {
|
||||
FIRST = 0,
|
||||
DISCARD_TREE = 0,
|
||||
INIT_NEW_TREES = 1,
|
||||
UPLOAD_VERTS = 2,
|
||||
UPLOAD_WIND_INDEX = 3,
|
||||
};
|
||||
|
||||
struct {
|
||||
bool loading = false;
|
||||
State state;
|
||||
u32 tex = 0;
|
||||
|
||||
u32 vert_geo = 0;
|
||||
u32 vert_tree = 0;
|
||||
u32 vert = 0;
|
||||
|
||||
u32 vert_debug_bytes = 0;
|
||||
|
||||
size_t time_of_day_count = 0;
|
||||
size_t vis_temp_len = 0;
|
||||
size_t max_draw = 0;
|
||||
size_t max_idx_per_draw = 0;
|
||||
u16 max_wind_idx = 0;
|
||||
|
||||
} m_load_state;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include "opengl_utils.h"
|
||||
|
||||
#include "game/graphics/opengl_renderer/BucketRenderer.h"
|
||||
#include "common/util/Assert.h"
|
||||
#include <cstdio>
|
||||
#include <array>
|
||||
|
||||
FramebufferTexturePair::FramebufferTexturePair(int w, int h, u64 texture_format, int num_levels)
|
||||
: m_w(w), m_h(h) {
|
||||
@@ -92,4 +93,55 @@ void FramebufferTexturePairContext::switch_to(FramebufferTexturePair& fb) {
|
||||
FramebufferTexturePairContext::~FramebufferTexturePairContext() {
|
||||
glViewport(m_old_viewport[0], m_old_viewport[1], m_old_viewport[2], m_old_viewport[3]);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, m_old_framebuffer);
|
||||
}
|
||||
|
||||
FullScreenDraw::FullScreenDraw() {
|
||||
glGenVertexArrays(1, &m_vao);
|
||||
glGenBuffers(1, &m_vertex_buffer);
|
||||
glBindVertexArray(m_vao);
|
||||
|
||||
struct Vertex {
|
||||
float x, y;
|
||||
};
|
||||
|
||||
std::array<Vertex, 4> vertices = {
|
||||
Vertex{-1, -1},
|
||||
Vertex{-1, 1},
|
||||
Vertex{1, -1},
|
||||
Vertex{1, 1},
|
||||
};
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_vertex_buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * 4, vertices.data(), GL_STATIC_DRAW);
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, // location 0 in the shader
|
||||
2, // 2 floats per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_TRUE, // normalized, ignored,
|
||||
sizeof(Vertex), //
|
||||
nullptr //
|
||||
);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
FullScreenDraw::~FullScreenDraw() {
|
||||
glDeleteVertexArrays(1, &m_vao);
|
||||
glDeleteBuffers(1, &m_vertex_buffer);
|
||||
}
|
||||
|
||||
void FullScreenDraw::draw(const math::Vector4f& color,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof) {
|
||||
glBindVertexArray(m_vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_vertex_buffer);
|
||||
auto& shader = render_state->shaders[ShaderId::SOLID_COLOR];
|
||||
shader.activate();
|
||||
glUniform4f(glGetUniformLocation(shader.id(), "fragment_color"), color[0], color[1], color[2],
|
||||
color[3]);
|
||||
prof.add_tri(2);
|
||||
prof.add_draw_call();
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/graphics/pipelines/opengl.h"
|
||||
#include "common/math/Vector.h"
|
||||
|
||||
struct SharedRenderState;
|
||||
class ScopedProfilerNode;
|
||||
|
||||
/*!
|
||||
* This is a wrapper around a framebuffer and texture to make it easier to render to a texture.
|
||||
@@ -36,4 +40,19 @@ class FramebufferTexturePairContext {
|
||||
FramebufferTexturePair* m_fb;
|
||||
GLint m_old_viewport[4];
|
||||
GLint m_old_framebuffer;
|
||||
};
|
||||
|
||||
// draw over the full screen.
|
||||
// you must set alpha/ztest/etc.
|
||||
class FullScreenDraw {
|
||||
public:
|
||||
FullScreenDraw();
|
||||
~FullScreenDraw();
|
||||
FullScreenDraw(const FullScreenDraw&) = delete;
|
||||
FullScreenDraw& operator=(const FullScreenDraw&) = delete;
|
||||
void draw(const math::Vector4f& color, SharedRenderState* render_state, ScopedProfilerNode& prof);
|
||||
|
||||
private:
|
||||
GLuint m_vao;
|
||||
GLuint m_vertex_buffer;
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
#version 430 core
|
||||
|
||||
out vec4 color;
|
||||
|
||||
uniform vec4 fragment_color;
|
||||
|
||||
void main() {
|
||||
color = fragment_color;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#version 430 core
|
||||
|
||||
layout (location = 0) in vec2 position_in;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(position_in, 0, 1.0);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#version 430 core
|
||||
|
||||
out vec3 color;
|
||||
|
||||
in vec4 fragment_color;
|
||||
|
||||
void main() {
|
||||
color = fragment_color.xyz;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#version 430 core
|
||||
|
||||
layout (location = 0) in vec3 position_in;
|
||||
layout (location = 1) in vec4 rgba_in;
|
||||
|
||||
out vec4 fragment_color;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(position_in, 1.0);
|
||||
fragment_color = rgba_in;
|
||||
}
|
||||
@@ -60,6 +60,7 @@ struct GraphicsData {
|
||||
FrameLimiter frame_limiter;
|
||||
Timer engine_timer;
|
||||
double last_engine_time = 1. / 60.;
|
||||
float pmode_alp = 0.f;
|
||||
|
||||
GraphicsData()
|
||||
: dma_copier(EE_MAIN_MEM_SIZE),
|
||||
@@ -238,6 +239,7 @@ void render_game_frame(int width, int height, int lbox_width, int lbox_height) {
|
||||
options.draw_profiler_window = g_gfx_data->debug_gui.should_draw_profiler();
|
||||
options.save_screenshot = g_gfx_data->debug_gui.get_screenshot_flag();
|
||||
options.draw_small_profiler_window = g_gfx_data->debug_gui.small_profiler;
|
||||
options.pmode_alp_register = g_gfx_data->pmode_alp;
|
||||
if (options.save_screenshot) {
|
||||
options.screenshot_path = make_output_file_name(g_gfx_data->debug_gui.screenshot_name());
|
||||
}
|
||||
@@ -487,6 +489,10 @@ void gl_set_levels(const std::vector<std::string>& levels) {
|
||||
g_gfx_data->loader->set_want_levels(levels);
|
||||
}
|
||||
|
||||
void gl_set_pmode_alp(float val) {
|
||||
g_gfx_data->pmode_alp = val;
|
||||
}
|
||||
|
||||
const GfxRendererModule moduleOpenGL = {
|
||||
gl_init, // init
|
||||
gl_make_main_display, // make_main_display
|
||||
@@ -505,6 +511,7 @@ const GfxRendererModule moduleOpenGL = {
|
||||
gl_texture_relocate, // texture_relocate
|
||||
gl_poll_events, // poll_events
|
||||
gl_set_levels, // set_levels
|
||||
gl_set_pmode_alp, // set_pmode_alp
|
||||
GfxPipeline::OpenGL, // pipeline
|
||||
"OpenGL 4.3" // name
|
||||
};
|
||||
|
||||
@@ -716,7 +716,12 @@ void DecodeTime(u32 ptr) {
|
||||
}
|
||||
|
||||
// TODO PutDisplayEnv
|
||||
void PutDisplayEnv() {
|
||||
void PutDisplayEnv(u32 ptr) {
|
||||
u8 alp = Ptr<u8>(ptr).c()[1];
|
||||
auto* renderer = Gfx::GetCurrentRenderer();
|
||||
if (renderer) {
|
||||
renderer->set_pmode_alp(alp / 255.f);
|
||||
}
|
||||
// ASSERT(false);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user