From e38832365c5db1f7fada61e736fc50ee75fae3e2 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Sat, 26 Mar 2022 15:53:44 -0400 Subject: [PATCH] Loader improvements and blackout (#1254) * temp * also tfrag * fix order --- game/graphics/gfx.h | 1 + game/graphics/opengl_renderer/Loader.cpp | 422 ++++++++++++++++-- game/graphics/opengl_renderer/Loader.h | 39 +- .../opengl_renderer/OpenGLRenderer.cpp | 97 ++-- .../graphics/opengl_renderer/OpenGLRenderer.h | 9 +- game/graphics/opengl_renderer/Shader.cpp | 2 +- game/graphics/opengl_renderer/Shader.h | 2 +- .../opengl_renderer/background/TFragment.cpp | 8 - .../opengl_renderer/background/TFragment.h | 1 - .../opengl_renderer/background/Tfrag3.cpp | 275 ++++-------- .../opengl_renderer/background/Tfrag3.h | 23 +- .../opengl_renderer/background/Tie3.cpp | 341 +++++--------- .../opengl_renderer/background/Tie3.h | 29 +- .../graphics/opengl_renderer/opengl_utils.cpp | 54 ++- game/graphics/opengl_renderer/opengl_utils.h | 19 + .../opengl_renderer/shaders/solid_color.frag | 9 + .../opengl_renderer/shaders/solid_color.vert | 7 + .../opengl_renderer/shaders/test_shader.frag | 9 - .../opengl_renderer/shaders/test_shader.vert | 11 - game/graphics/pipelines/opengl.cpp | 7 + game/kernel/kmachine.cpp | 7 +- 21 files changed, 761 insertions(+), 611 deletions(-) create mode 100644 game/graphics/opengl_renderer/shaders/solid_color.frag create mode 100644 game/graphics/opengl_renderer/shaders/solid_color.vert delete mode 100644 game/graphics/opengl_renderer/shaders/test_shader.frag delete mode 100644 game/graphics/opengl_renderer/shaders/test_shader.vert diff --git a/game/graphics/gfx.h b/game/graphics/gfx.h index b50f772907..b1d2ee24b0 100644 --- a/game/graphics/gfx.h +++ b/game/graphics/gfx.h @@ -39,6 +39,7 @@ struct GfxRendererModule { std::function texture_relocate; std::function poll_events; std::function&)> set_levels; + std::function set_pmode_alp; GfxPipeline pipeline; const char* name; }; diff --git a/game/graphics/opengl_renderer/Loader.cpp b/game/graphics/opengl_renderer/Loader.cpp index 67d7b49031..37377e4ff4 100644 --- a/game/graphics/opengl_renderer/Loader.cpp +++ b/game/graphics/opengl_renderer/Loader.cpp @@ -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 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 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& levels) { std::unique_lock 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& 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& 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 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(); 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 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 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 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 lk(m_loader_mutex); + if (!m_initializing_tfrag3_levels.empty()) { + needs_run = true; + } + } + + if (needs_run) { + update(status_out, tex_pool); + } + } + + { + std::unique_lock 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 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 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 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 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]); - } - } -} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/Loader.h b/game/graphics/opengl_renderer/Loader.h index f500ad0869..bad9e933da 100644 --- a/game/graphics/opengl_renderer/Loader.h +++ b/game/graphics/opengl_renderer/Loader.h @@ -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 level; std::vector textures; u64 load_id = 0; + + struct TieOpenGL { + GLuint vertex_buffer; + bool has_wind = false; + GLuint wind_indices; + }; + std::array, tfrag3::TIE_GEOS> tie_data; + std::array, 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& 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 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 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 m_loaded_tfrag3_levels; + + std::vector m_desired_levels; }; diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index 695e64a835..79a81c4f9c 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -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); + } +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.h b/game/graphics/opengl_renderer/OpenGLRenderer.h index f084afd0f1..3471c807b2 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.h +++ b/game/graphics/opengl_renderer/OpenGLRenderer.h @@ -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 @@ -55,4 +57,7 @@ class OpenGLRenderer { std::array m_bucket_categories; std::array m_category_times; + FullScreenDraw m_blackout_renderer; + + float m_last_pmode_alp = 1.; }; diff --git a/game/graphics/opengl_renderer/Shader.cpp b/game/graphics/opengl_renderer/Shader.cpp index 8d6c82788f..fcba3a563f 100644 --- a/game/graphics/opengl_renderer/Shader.cpp +++ b/game/graphics/opengl_renderer/Shader.cpp @@ -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"}; diff --git a/game/graphics/opengl_renderer/Shader.h b/game/graphics/opengl_renderer/Shader.h index 5a889c4dc5..9d0a8bede9 100644 --- a/game/graphics/opengl_renderer/Shader.h +++ b/game/graphics/opengl_renderer/Shader.h @@ -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, diff --git a/game/graphics/opengl_renderer/background/TFragment.cpp b/game/graphics/opengl_renderer/background/TFragment.cpp index 80a25b97bf..8b685510f6 100644 --- a/game/graphics/opengl_renderer/background/TFragment.cpp +++ b/game/graphics/opengl_renderer/background/TFragment.cpp @@ -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++) { diff --git a/game/graphics/opengl_renderer/background/TFragment.h b/game/graphics/opengl_renderer/background/TFragment.h index b684ec61d2..1b091824ab 100644 --- a/game/graphics/opengl_renderer/background/TFragment.h +++ b/game/graphics/opengl_renderer/background/TFragment.h @@ -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; diff --git a/game/graphics/opengl_renderer/background/Tfrag3.cpp b/game/graphics/opengl_renderer/background/Tfrag3.cpp index 8eb80bcd8d..07e254dcc6 100644 --- a/game/graphics/opengl_renderer/background/Tfrag3.cpp +++ b/game/graphics/opengl_renderer/background/Tfrag3.cpp @@ -38,204 +38,124 @@ Tfrag3::~Tfrag3() { glDeleteVertexArrays(1, &m_debug_vao); } -bool Tfrag3::update_load(const std::vector& 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& 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& 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); } diff --git a/game/graphics/opengl_renderer/background/Tfrag3.h b/game/graphics/opengl_renderer/background/Tfrag3.h index 5e7f282b55..07fc50f9e7 100644 --- a/game/graphics/opengl_renderer/background/Tfrag3.h +++ b/game/graphics/opengl_renderer/background/Tfrag3.h @@ -48,9 +48,8 @@ class Tfrag3 { math::Vector4f rgba; }; - bool update_load(const std::vector& tree_kinds, - const tfrag3::Level* lev_data, - std::string& status_out); + void update_load(const std::vector& 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; }; diff --git a/game/graphics/opengl_renderer/background/Tie3.cpp b/game/graphics/opengl_renderer/background/Tie3.cpp index 65f7ad6cad..7cc8555d3e 100644 --- a/game/graphics/opengl_renderer/background/Tie3.cpp +++ b/game/graphics/opengl_renderer/background/Tie3.cpp @@ -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 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(); diff --git a/game/graphics/opengl_renderer/background/Tie3.h b/game/graphics/opengl_renderer/background/Tie3.h index 2478c25420..723b4a7bba 100644 --- a/game/graphics/opengl_renderer/background/Tie3.h +++ b/game/graphics/opengl_renderer/background/Tie3.h @@ -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; }; diff --git a/game/graphics/opengl_renderer/opengl_utils.cpp b/game/graphics/opengl_renderer/opengl_utils.cpp index d56cbaee34..1977649f1e 100644 --- a/game/graphics/opengl_renderer/opengl_utils.cpp +++ b/game/graphics/opengl_renderer/opengl_utils.cpp @@ -1,7 +1,8 @@ #include "opengl_utils.h" - +#include "game/graphics/opengl_renderer/BucketRenderer.h" #include "common/util/Assert.h" #include +#include 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 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); } \ No newline at end of file diff --git a/game/graphics/opengl_renderer/opengl_utils.h b/game/graphics/opengl_renderer/opengl_utils.h index 6d749a6d75..a9548206aa 100644 --- a/game/graphics/opengl_renderer/opengl_utils.h +++ b/game/graphics/opengl_renderer/opengl_utils.h @@ -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; }; \ No newline at end of file diff --git a/game/graphics/opengl_renderer/shaders/solid_color.frag b/game/graphics/opengl_renderer/shaders/solid_color.frag new file mode 100644 index 0000000000..66c0fd3156 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/solid_color.frag @@ -0,0 +1,9 @@ +#version 430 core + +out vec4 color; + +uniform vec4 fragment_color; + +void main() { + color = fragment_color; +} diff --git a/game/graphics/opengl_renderer/shaders/solid_color.vert b/game/graphics/opengl_renderer/shaders/solid_color.vert new file mode 100644 index 0000000000..f1a2c62147 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/solid_color.vert @@ -0,0 +1,7 @@ +#version 430 core + +layout (location = 0) in vec2 position_in; + +void main() { + gl_Position = vec4(position_in, 0, 1.0); +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/shaders/test_shader.frag b/game/graphics/opengl_renderer/shaders/test_shader.frag deleted file mode 100644 index 308f331abc..0000000000 --- a/game/graphics/opengl_renderer/shaders/test_shader.frag +++ /dev/null @@ -1,9 +0,0 @@ -#version 430 core - -out vec3 color; - -in vec4 fragment_color; - -void main() { - color = fragment_color.xyz; -} diff --git a/game/graphics/opengl_renderer/shaders/test_shader.vert b/game/graphics/opengl_renderer/shaders/test_shader.vert deleted file mode 100644 index 285fadf174..0000000000 --- a/game/graphics/opengl_renderer/shaders/test_shader.vert +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/game/graphics/pipelines/opengl.cpp b/game/graphics/pipelines/opengl.cpp index 9297f53743..fc39ebaf91 100644 --- a/game/graphics/pipelines/opengl.cpp +++ b/game/graphics/pipelines/opengl.cpp @@ -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& 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 }; diff --git a/game/kernel/kmachine.cpp b/game/kernel/kmachine.cpp index d2ddf4aff1..5d19c2dcd1 100644 --- a/game/kernel/kmachine.cpp +++ b/game/kernel/kmachine.cpp @@ -716,7 +716,12 @@ void DecodeTime(u32 ptr) { } // TODO PutDisplayEnv -void PutDisplayEnv() { +void PutDisplayEnv(u32 ptr) { + u8 alp = Ptr(ptr).c()[1]; + auto* renderer = Gfx::GetCurrentRenderer(); + if (renderer) { + renderer->set_pmode_alp(alp / 255.f); + } // ASSERT(false); }