diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index d45b3a379b..97b6a368fe 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -707,6 +707,11 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) { { auto prof = m_profiler.root()->make_scoped_child("buckets"); dispatch_buckets(dma, prof, settings.gpu_sync); + if (m_texture_animator) { + // if animation requests weren't made, assume the level is unloaded and the textures should + // reset. + m_texture_animator->clear_stale_textures(m_render_state.frame_idx); + } } // apply effects done with PCRTC registers diff --git a/game/graphics/opengl_renderer/TextureAnimator.cpp b/game/graphics/opengl_renderer/TextureAnimator.cpp index 6a8f88b3aa..5c5e5fd107 100644 --- a/game/graphics/opengl_renderer/TextureAnimator.cpp +++ b/game/graphics/opengl_renderer/TextureAnimator.cpp @@ -223,17 +223,18 @@ void opengl_upload_texture(GLint dest, const void* data, int w, int h) { * texture using the index data in dest. */ ClutBlender::ClutBlender(const std::string& dest, - const std::vector& sources, + const std::array& sources, const std::optional& level_name, const tfrag3::Level* level, OpenGLTexturePool* tpool) { // find the destination texture m_dest = itex_by_name(level, dest, level_name); // find the clut source textures - for (const auto& sname : sources) { - m_cluts.push_back(&itex_by_name(level, sname, level_name)->color_table); - m_current_weights.push_back(0); + for (int i = 0; i < 2; i++) { + m_cluts[i] = &itex_by_name(level, sources[i], level_name)->color_table; + m_current_weights[i] = 0; } + // opengl texture that we'll write to m_texture = tpool->allocate(m_dest->w, m_dest->h); m_temp_rgba.resize(m_dest->w * m_dest->h); @@ -510,8 +511,9 @@ void TextureAnimator::add_to_clut_blender_group(int idx, const std::optional& dgo) { auto& grp = m_clut_blender_groups.at(idx); for (auto& prefix : textures) { - grp.blenders.emplace_back(prefix, std::vector{prefix + suffix0, prefix + suffix1}, - dgo, m_common_level, &m_opengl_texture_pool); + grp.blenders.emplace_back(prefix, + std::array{prefix + suffix0, prefix + suffix1}, dgo, + m_common_level, &m_opengl_texture_pool); grp.outputs.push_back(output_slot_by_idx(GameVersion::Jak2, prefix)); m_private_output_slots.at(grp.outputs.back()) = grp.blenders.back().texture(); } @@ -584,7 +586,8 @@ struct TextureAnimPcTransform { */ void TextureAnimator::handle_texture_anim_data(DmaFollower& dma, const u8* ee_mem, - TexturePool* texture_pool) { + TexturePool* texture_pool, + u64 frame_idx) { dprintf("animator\n"); m_current_shader = {}; glBindVertexArray(m_vao); @@ -645,23 +648,23 @@ void TextureAnimator::handle_texture_anim_data(DmaFollower& dma, } break; case DARKJAK: { auto p = scoped_prof("darkjak"); - run_clut_blender_group(tf, m_darkjak_clut_blender_idx); + run_clut_blender_group(tf, m_darkjak_clut_blender_idx, frame_idx); } break; case PRISON_JAK: { auto p = scoped_prof("prisonjak"); - run_clut_blender_group(tf, m_jakb_prison_clut_blender_idx); + run_clut_blender_group(tf, m_jakb_prison_clut_blender_idx, frame_idx); } break; case ORACLE_JAK: { auto p = scoped_prof("oraclejak"); - run_clut_blender_group(tf, m_jakb_oracle_clut_blender_idx); + run_clut_blender_group(tf, m_jakb_oracle_clut_blender_idx, frame_idx); } break; case NEST_JAK: { auto p = scoped_prof("nestjak"); - run_clut_blender_group(tf, m_jakb_nest_clut_blender_idx); + run_clut_blender_group(tf, m_jakb_nest_clut_blender_idx, frame_idx); } break; case KOR_TRANSFORM: { auto p = scoped_prof("kor"); - run_clut_blender_group(tf, m_kor_transform_clut_blender_idx); + run_clut_blender_group(tf, m_kor_transform_clut_blender_idx, frame_idx); } break; case SKULL_GEM: { auto p = scoped_prof("skull-gem"); @@ -990,17 +993,31 @@ void TextureAnimator::handle_copy_clut_alpha(const DmaTransfer& tf) { glColorMask(true, true, true, true); } -void TextureAnimator::run_clut_blender_group(DmaTransfer& tf, int idx) { +void TextureAnimator::run_clut_blender_group(DmaTransfer& tf, int idx, u64 frame_idx) { float f; ASSERT(tf.size_bytes == 16); memcpy(&f, tf.data, sizeof(float)); float weights[2] = {1.f - f, f}; auto& blender = m_clut_blender_groups.at(idx); + blender.last_updated_frame = frame_idx; for (size_t i = 0; i < blender.blenders.size(); i++) { m_private_output_slots[blender.outputs[i]] = blender.blenders[i].run(weights); } } +void TextureAnimator::clear_stale_textures(u64 frame_idx) { + for (auto& group : m_clut_blender_groups) { + if (frame_idx > group.last_updated_frame) { + for (auto& blender : group.blenders) { + if (!blender.at_default()) { + float weights[2] = {1, 0}; + blender.run(weights); + } + } + } + } +} + /*! * Create an entry for a 16x16 clut texture upload. Leaves it on the CPU. * They upload cluts as PSM32, so there's no funny addressing stuff, other than @@ -1948,6 +1965,8 @@ void TextureAnimator::setup_texture_anims() { skull_gem.move_to_pool = true; skull_gem.tex_name = "skull-gem-dest"; skull_gem.color = math::Vector4{0, 0, 0, 0x80}; + // overriden in texture-finish.gc + skull_gem.override_size = math::Vector2(32, 32); auto& skull_gem_0 = skull_gem.layers.emplace_back(); skull_gem_0.end_time = 300.; diff --git a/game/graphics/opengl_renderer/TextureAnimator.h b/game/graphics/opengl_renderer/TextureAnimator.h index 52f7b3c058..1060a3d6e1 100644 --- a/game/graphics/opengl_renderer/TextureAnimator.h +++ b/game/graphics/opengl_renderer/TextureAnimator.h @@ -65,17 +65,18 @@ struct OpenGLTexturePool { class ClutBlender { public: ClutBlender(const std::string& dest, - const std::vector& sources, + const std::array& sources, const std::optional& level_name, const tfrag3::Level* level, OpenGLTexturePool* tpool); GLuint run(const float* weights); GLuint texture() const { return m_texture; } + bool at_default() const { return m_current_weights[0] == 1.f && m_current_weights[1] == 0.f; } private: const tfrag3::IndexTexture* m_dest; - std::vector, 256>*> m_cluts; - std::vector m_current_weights; + std::array, 256>*, 2> m_cluts; + std::array m_current_weights; GLuint m_texture; std::array, 256> m_temp_clut; std::vector m_temp_rgba; @@ -199,10 +200,14 @@ class TextureAnimator { public: TextureAnimator(ShaderLibrary& shaders, const tfrag3::Level* common_level); ~TextureAnimator(); - void handle_texture_anim_data(DmaFollower& dma, const u8* ee_mem, TexturePool* texture_pool); + void handle_texture_anim_data(DmaFollower& dma, + const u8* ee_mem, + TexturePool* texture_pool, + u64 frame_idx); GLuint get_by_slot(int idx); void draw_debug_window(); const std::vector* slots() { return &m_public_output_slots; } + void clear_stale_textures(u64 frame_idx); private: void copy_private_to_public(); @@ -314,6 +319,7 @@ class TextureAnimator { struct ClutBlenderGroup { std::vector blenders; std::vector outputs; + u64 last_updated_frame = 0; }; std::vector m_clut_blender_groups; @@ -332,7 +338,7 @@ class TextureAnimator { const std::string& suffix0, const std::string& suffix1, const std::optional& dgo); - void run_clut_blender_group(DmaTransfer& tf, int idx); + void run_clut_blender_group(DmaTransfer& tf, int idx, u64 frame_idx); Psm32ToPsm8Scrambler m_psm32_to_psm8_8_8, m_psm32_to_psm8_16_16, m_psm32_to_psm8_32_32, m_psm32_to_psm8_64_64; diff --git a/game/graphics/opengl_renderer/TextureUploadHandler.cpp b/game/graphics/opengl_renderer/TextureUploadHandler.cpp index 5b9cee142b..28d4226c27 100644 --- a/game/graphics/opengl_renderer/TextureUploadHandler.cpp +++ b/game/graphics/opengl_renderer/TextureUploadHandler.cpp @@ -30,7 +30,8 @@ void TextureUploadHandler::render(DmaFollower& dma, dma.read_and_advance(); auto p = scoped_prof("texture-animator"); m_texture_animator->handle_texture_anim_data(dma, (const u8*)render_state->ee_main_memory, - render_state->texture_pool.get()); + render_state->texture_pool.get(), + render_state->frame_idx); } // does it look like data to do eye rendering? if (dma_tag.qwc == (128 / 16)) {