diff --git a/game/graphics/opengl_renderer/DirectRenderer.cpp b/game/graphics/opengl_renderer/DirectRenderer.cpp index ed7c5b502c..7dc44dd6b5 100644 --- a/game/graphics/opengl_renderer/DirectRenderer.cpp +++ b/game/graphics/opengl_renderer/DirectRenderer.cpp @@ -89,6 +89,28 @@ void DirectRenderer::render(DmaFollower& dma, } } +void DirectRenderer::reset_state() { + m_test_state_needs_gl_update = true; + m_test_state = TestState(); + + m_blend_state_needs_gl_update = true; + m_blend_state = BlendState(); + + m_prim_gl_state_needs_gl_update = true; + m_prim_gl_state = PrimGlState(); + + for (int i = 0; i < TEXTURE_STATE_COUNT; ++i) { + m_buffered_tex_state[i] = TextureState(); + } + m_tex_state_from_reg = {}; + m_next_free_tex_state = 0; + m_current_tex_state_idx = -1; + + m_prim_building = PrimBuildState(); + + m_stats = {}; +} + void DirectRenderer::draw_debug_window() { ImGui::Checkbox("Wireframe", &m_debug_state.wireframe); ImGui::SameLine(); @@ -151,15 +173,15 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state, ScopedProfil m_test_state_needs_gl_update = false; } - // I think it's important that this comes last. - if (m_global_texture_state.needs_gl_update) { - // fmt::print("flushing with {} states\n", m_current_texture_state + 1); - for (int i = 0; i <= m_current_texture_state; ++i) { + for (int i = 0; i < TEXTURE_STATE_COUNT; i++) { + auto& tex_state = m_buffered_tex_state[i]; + if (tex_state.used) { update_gl_texture(render_state, i); + tex_state.used = false; } - m_global_texture_state.needs_gl_update = false; - // fmt::print("tex state flush\n"); } + m_next_free_tex_state = 0; + m_current_tex_state_idx = -1; // NOTE: sometimes we want to update the GL state without actually rendering anything, such as sky // textures, so we only return after we've updated the full state @@ -186,7 +208,6 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state, ScopedProfil } glBindVertexArray(m_ogl.vao); - // render! // update buffers: glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer); @@ -263,8 +284,6 @@ void DirectRenderer::update_gl_prim(SharedRenderState* render_state) { "color_mult"), m_ogl.color_mult); } - // update_gl_texture(render_state); - m_global_texture_state.needs_gl_update = true; } else { if (m_mode == Mode::SKY) { render_state->shaders[ShaderId::SKY].activate(); @@ -291,7 +310,7 @@ void DirectRenderer::update_gl_prim(SharedRenderState* render_state) { void DirectRenderer::update_gl_texture(SharedRenderState* render_state, int unit) { TextureRecord* tex = nullptr; - auto& state = m_texture_state[unit]; + auto& state = m_buffered_tex_state[unit]; if (!state.used) { // nothing used this state, don't bother binding the texture. return; @@ -568,7 +587,7 @@ void DirectRenderer::render_gif(const u8* data, handle_prim_packed(data + offset, render_state, prof); break; case GifTag::RegisterDescriptor::TEX0_1: - handle_tex0_1_packed(data + offset, render_state, prof); + handle_tex0_1_packed(data + offset); break; default: fmt::print("Register {} is not supported in packed mode yet\n", @@ -643,14 +662,14 @@ void DirectRenderer::handle_ad(const u8* data, handle_pabe(value); break; case GsRegisterAddress::CLAMP_1: - handle_clamp1(value, render_state, prof); + handle_clamp1(value); break; case GsRegisterAddress::PRIM: handle_prim(value, render_state, prof); break; case GsRegisterAddress::TEX1_1: - handle_tex1_1(value, render_state, prof); + handle_tex1_1(value); break; case GsRegisterAddress::TEXA: handle_texa(value); @@ -665,7 +684,7 @@ void DirectRenderer::handle_ad(const u8* data, // TODO break; case GsRegisterAddress::TEX0_1: - handle_tex0_1(value, render_state, prof); + handle_tex0_1(value); break; case GsRegisterAddress::MIPTBP1_1: case GsRegisterAddress::MIPTBP2_1: @@ -679,29 +698,17 @@ void DirectRenderer::handle_ad(const u8* data, } } -void DirectRenderer::handle_tex1_1(u64 val, - SharedRenderState* render_state, - ScopedProfilerNode& prof) { +void DirectRenderer::handle_tex1_1(u64 val) { GsTex1 reg(val); // for now, we aren't going to handle mipmapping. I don't think it's used with direct. // ASSERT(reg.mxl() == 0); // if that's true, we can ignore LCM, MTBA, L, K bool want_tex_filt = reg.mmag(); - if (want_tex_filt != current_texture_state()->enable_tex_filt) { - if (current_texture_state()->used) { - if (needs_state_flush()) { - flush_pending(render_state, prof); - m_texture_state[0] = *current_texture_state(); - reset_texture_states(); - m_stats.flush_from_state_exhaust++; - } else { - push_texture_state(); - } - } - m_global_texture_state.needs_gl_update = true; - - current_texture_state()->enable_tex_filt = want_tex_filt; + if (want_tex_filt != m_tex_state_from_reg.enable_tex_filt) { + m_tex_state_from_reg.enable_tex_filt = want_tex_filt; + // we changed the state_from_reg, we no longer know if it points to a texture state. + m_current_tex_state_idx = -1; } // MMAG/MMIN specify texture filtering. For now, assume always linear @@ -711,37 +718,23 @@ void DirectRenderer::handle_tex1_1(u64 val, // } } -void DirectRenderer::handle_tex0_1_packed(const u8* data, - SharedRenderState* render_state, - ScopedProfilerNode& prof) { +void DirectRenderer::handle_tex0_1_packed(const u8* data) { u64 val; memcpy(&val, data, sizeof(u64)); - handle_tex0_1(val, render_state, prof); + handle_tex0_1(val); } -void DirectRenderer::handle_tex0_1(u64 val, - SharedRenderState* render_state, - ScopedProfilerNode& prof) { +void DirectRenderer::handle_tex0_1(u64 val) { GsTex0 reg(val); // update tbp - if (current_texture_state()->current_register != reg) { - if (current_texture_state()->used) { - if (needs_state_flush()) { - flush_pending(render_state, prof); - m_texture_state[0] = *current_texture_state(); - reset_texture_states(); - m_stats.flush_from_state_exhaust++; - } else { - push_texture_state(); - } - } - m_global_texture_state.needs_gl_update = true; + if (m_tex_state_from_reg.current_register != reg) { + m_tex_state_from_reg.texture_base_ptr = reg.tbp0(); + m_tex_state_from_reg.using_mt4hh = reg.psm() == GsTex0::PSM::PSMT4HH; + m_tex_state_from_reg.current_register = reg; + m_tex_state_from_reg.tcc = reg.tcc(); - current_texture_state()->texture_base_ptr = reg.tbp0(); - current_texture_state()->using_mt4hh = reg.psm() == GsTex0::PSM::PSMT4HH; - current_texture_state()->current_register = reg; - - current_texture_state()->tcc = reg.tcc(); + // we changed the state_from_reg, we no longer know if it points to a texture state. + m_current_tex_state_idx = -1; } // tbw: assume they got it right @@ -852,32 +845,17 @@ void DirectRenderer::handle_pabe(u64 val) { ASSERT(val == 0); // not really sure how to handle this yet. } -void DirectRenderer::handle_clamp1(u64 val, - SharedRenderState* render_state, - ScopedProfilerNode& prof) { +void DirectRenderer::handle_clamp1(u64 val) { if (!(val == 0b101 || val == 0 || val == 1 || val == 0b100)) { // fmt::print("clamp: 0x{:x}\n", val); // ASSERT(false); } - if (current_texture_state()->m_clamp_state.current_register != val) { - if (current_texture_state()->used) { - if (needs_state_flush()) { - flush_pending(render_state, prof); - m_texture_state[0] = *current_texture_state(); - reset_texture_states(); - // m_texture_state[0].used = false; - // m_current_texture_state = 0; - m_stats.flush_from_state_exhaust++; - } else { - push_texture_state(); - } - } - m_global_texture_state.needs_gl_update = true; - - current_texture_state()->m_clamp_state.current_register = val; - current_texture_state()->m_clamp_state.clamp_s = val & 0b001; - current_texture_state()->m_clamp_state.clamp_t = val & 0b100; + if (m_tex_state_from_reg.m_clamp_state.current_register != val) { + m_current_tex_state_idx = -1; + m_tex_state_from_reg.m_clamp_state.current_register = val; + m_tex_state_from_reg.m_clamp_state.clamp_s = val & 0b001; + m_tex_state_from_reg.m_clamp_state.clamp_t = val & 0b100; } } @@ -920,6 +898,25 @@ void DirectRenderer::handle_rgbaq(u64 val) { memcpy(m_prim_building.rgba_reg.data(), &val, 4); } +int DirectRenderer::get_texture_unit_for_current_reg(SharedRenderState* render_state, + ScopedProfilerNode& prof) { + if (m_current_tex_state_idx != -1) { + return m_current_tex_state_idx; + } + + if (m_next_free_tex_state >= TEXTURE_STATE_COUNT) { + m_stats.flush_from_state_exhaust++; + flush_pending(render_state, prof); + return get_texture_unit_for_current_reg(render_state, prof); + } else { + ASSERT(!m_buffered_tex_state[m_next_free_tex_state].used); + m_buffered_tex_state[m_next_free_tex_state] = m_tex_state_from_reg; + m_buffered_tex_state[m_next_free_tex_state].used = true; + m_current_tex_state_idx = m_next_free_tex_state++; + return m_current_tex_state_idx; + } +} + void DirectRenderer::handle_xyzf2_common(u32 x, u32 y, u32 z, @@ -946,6 +943,9 @@ void DirectRenderer::handle_xyzf2_common(u32 x, m_prim_building.building_idx++; + int tex_unit = get_texture_unit_for_current_reg(render_state, prof); + bool tcc = m_buffered_tex_state[tex_unit].tcc; + switch (m_prim_building.kind) { case GsPrim::Kind::SPRITE: { if (m_prim_building.building_idx == 2) { @@ -986,8 +986,7 @@ void DirectRenderer::handle_xyzf2_common(u32 x, if (advance) { for (int i = 0; i < 3; i++) { m_prim_buffer.push(m_prim_building.building_rgba[i], m_prim_building.building_vert[i], - m_prim_building.building_stq[i], m_current_texture_state, - current_texture_state()->tcc); + m_prim_building.building_stq[i], tex_unit, tcc); } } } @@ -999,8 +998,7 @@ void DirectRenderer::handle_xyzf2_common(u32 x, m_prim_building.building_idx = 0; for (int i = 0; i < 3; i++) { m_prim_buffer.push(m_prim_building.building_rgba[i], m_prim_building.building_vert[i], - m_prim_building.building_stq[i], m_current_texture_state, - current_texture_state()->tcc); + m_prim_building.building_stq[i], tex_unit, tcc); } } break; @@ -1016,8 +1014,7 @@ void DirectRenderer::handle_xyzf2_common(u32 x, } for (int i = 0; i < 3; i++) { m_prim_buffer.push(m_prim_building.building_rgba[i], m_prim_building.building_vert[i], - m_prim_building.building_stq[i], m_current_texture_state, - current_texture_state()->tcc); + m_prim_building.building_stq[i], tex_unit, tcc); } } } break; @@ -1054,8 +1051,6 @@ void DirectRenderer::handle_xyzf2_common(u32 x, fmt::print("prim type {} is unsupported in {}.\n", (int)m_prim_building.kind, name_and_id()); ASSERT(false); } - - current_texture_state()->used = true; } void DirectRenderer::handle_xyzf2(u64 val, @@ -1069,27 +1064,6 @@ void DirectRenderer::handle_xyzf2(u64 val, handle_xyzf2_common(x, y, z, f, render_state, prof, true); } -void DirectRenderer::reset_state() { - m_test_state_needs_gl_update = true; - m_test_state = TestState(); - - m_blend_state_needs_gl_update = true; - m_blend_state = BlendState(); - - m_prim_gl_state_needs_gl_update = true; - m_prim_gl_state = PrimGlState(); - - for (int i = 0; i < TEXTURE_STATE_COUNT; ++i) { - m_texture_state[i] = TextureState(); - } - m_global_texture_state = TextureGlobalState(); - m_current_texture_state = 0; - - m_prim_building = PrimBuildState(); - - m_stats = {}; -} - void DirectRenderer::TestState::from_register(GsTest reg) { current_register = reg; alpha_test_enable = reg.alpha_test_enable(); diff --git a/game/graphics/opengl_renderer/DirectRenderer.h b/game/graphics/opengl_renderer/DirectRenderer.h index c08fc04393..5a95d0e865 100644 --- a/game/graphics/opengl_renderer/DirectRenderer.h +++ b/game/graphics/opengl_renderer/DirectRenderer.h @@ -76,7 +76,7 @@ class DirectRenderer : public BucketRenderer { void handle_test1(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof); void handle_alpha1(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof); void handle_pabe(u64 val); - void handle_clamp1(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof); + void handle_clamp1(u64 val); void handle_prim(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof); void handle_prim_packed(const u8* data, SharedRenderState* render_state, @@ -88,11 +88,9 @@ class DirectRenderer : public BucketRenderer { void handle_xyzf2_packed(const u8* data, SharedRenderState* render_state, ScopedProfilerNode& prof); - void handle_tex0_1_packed(const u8* data, - SharedRenderState* render_state, - ScopedProfilerNode& prof); - void handle_tex0_1(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof); - void handle_tex1_1(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof); + void handle_tex0_1_packed(const u8* data); + void handle_tex0_1(u64 val); + void handle_tex1_1(u64 val); void handle_texa(u64 val); void handle_xyzf2_common(u32 x, @@ -172,33 +170,26 @@ class DirectRenderer : public BucketRenderer { } m_clamp_state; bool used = false; - } m_texture_state[TEXTURE_STATE_COUNT]; - void reset_texture_states() { - m_current_texture_state = 0; - m_texture_state[0].used = false; - for (auto& ts : m_texture_state) { - ts.used = false; + bool compatible_with(const TextureState& other) { + return current_register == other.current_register && + m_clamp_state.current_register == other.m_clamp_state.current_register && + enable_tex_filt == other.enable_tex_filt; } - } + }; - struct TextureGlobalState { - bool needs_gl_update = true; - } m_global_texture_state; + // vertices will reference these texture states + TextureState m_buffered_tex_state[TEXTURE_STATE_COUNT]; + int m_next_free_tex_state = 0; - int m_current_texture_state = 0; + // this texture state mirrors the current GS register. + TextureState m_tex_state_from_reg; - TextureState* current_texture_state() { return &m_texture_state[m_current_texture_state]; } - bool needs_state_flush() { return m_current_texture_state + 1 >= TEXTURE_STATE_COUNT; } - void push_texture_state() { - ++m_current_texture_state; - if (m_current_texture_state >= TEXTURE_STATE_COUNT) { - lg::error("fatal tex push {}!!!!", m_current_texture_state); - } - if (m_current_texture_state > 0) { - m_texture_state[m_current_texture_state] = m_texture_state[m_current_texture_state - 1]; - } - } + // if this is not -1, then it is the index of a texture state in m_buffered_tex_state that + // matches m_tex_state_from_reg. + int m_current_tex_state_idx = -1; + + int get_texture_unit_for_current_reg(SharedRenderState* render_state, ScopedProfilerNode& prof); // state set through the prim/rgbaq register that doesn't require changing GL stuff struct PrimBuildState { diff --git a/game/graphics/opengl_renderer/GenericRenderer.cpp b/game/graphics/opengl_renderer/GenericRenderer.cpp index dd38796e9d..8a6a6972bf 100644 --- a/game/graphics/opengl_renderer/GenericRenderer.cpp +++ b/game/graphics/opengl_renderer/GenericRenderer.cpp @@ -7,6 +7,7 @@ GenericRenderer::GenericRenderer(const std::string& name, BucketId my_id) void GenericRenderer::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) { + m_xgkick_idx = 0; m_skipped_tags = 0; m_debug.clear(); m_direct.reset_state(); @@ -175,6 +176,8 @@ void GenericRenderer::handle_dma_stream(const u8* data, void GenericRenderer::draw_debug_window() { ImGui::Text("Skipped %d tags", m_skipped_tags); + ImGui::InputInt("kick min", &m_min_xgkick); + ImGui::InputInt("kick max", &m_max_xgkick); ImGui::Text("Debug:\n%s\n", m_debug.c_str()); if (ImGui::TreeNode("Direct")) { m_direct.draw_debug_window(); @@ -292,7 +295,9 @@ void GenericRenderer::mscal(int imm, SharedRenderState* render_state, ScopedProf } void GenericRenderer::xgkick(u16 addr, SharedRenderState* render_state, ScopedProfilerNode& prof) { - if (render_state->enable_generic_xgkick) { + if (render_state->enable_generic_xgkick && m_xgkick_idx >= m_min_xgkick && + m_xgkick_idx < m_max_xgkick) { m_direct.render_gif(m_buffer.data + (16 * addr), UINT32_MAX, render_state, prof); } + m_xgkick_idx++; } \ No newline at end of file diff --git a/game/graphics/opengl_renderer/GenericRenderer.h b/game/graphics/opengl_renderer/GenericRenderer.h index 4f2c3b2dc3..bca1943079 100644 --- a/game/graphics/opengl_renderer/GenericRenderer.h +++ b/game/graphics/opengl_renderer/GenericRenderer.h @@ -59,4 +59,8 @@ class GenericRenderer : public BucketRenderer { struct alignas(16) BufferMemory { u8 data[1024 * 16]; } m_buffer; + + int m_xgkick_idx = 0; + int m_min_xgkick = 0; + int m_max_xgkick = 1000000; };