From ab6f41d0c666ed5cd3fafe9a16edc92b77e1214a Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Fri, 7 Jan 2022 16:13:19 -0500 Subject: [PATCH] [gfx] sprite 3d hack and fix graphics dumps (#1057) * add sprite 3d * cleanup --- .../opengl_renderer/DirectRenderer.cpp | 16 +- .../graphics/opengl_renderer/DirectRenderer.h | 8 + game/graphics/opengl_renderer/SkyBlendCPU.cpp | 3 + .../opengl_renderer/SpriteRenderer.cpp | 262 +++++++++++++++++- .../graphics/opengl_renderer/SpriteRenderer.h | 5 + game/graphics/pipelines/opengl.cpp | 3 +- game/graphics/texture/TexturePool.cpp | 7 +- goal_src/engine/math/quaternion.gc | 18 +- 8 files changed, 310 insertions(+), 12 deletions(-) diff --git a/game/graphics/opengl_renderer/DirectRenderer.cpp b/game/graphics/opengl_renderer/DirectRenderer.cpp index 48619d2add..cba7407178 100644 --- a/game/graphics/opengl_renderer/DirectRenderer.cpp +++ b/game/graphics/opengl_renderer/DirectRenderer.cpp @@ -298,6 +298,10 @@ 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]; + if (!state.used) { + // nothing used this state, don't bother binding the texture. + return; + } if (state.using_mt4hh) { tex = render_state->texture_pool->lookup_mt4hh(state.texture_base_ptr); } else { @@ -659,8 +663,7 @@ void DirectRenderer::handle_tex1_1(u64 val, if (needs_state_flush()) { flush_pending(render_state, prof); m_texture_state[0] = *current_texture_state(); - m_texture_state[0].used = false; - m_current_texture_state = 0; + reset_texture_states(); m_stats.flush_from_state_exhaust++; } else { push_texture_state(); @@ -697,8 +700,7 @@ void DirectRenderer::handle_tex0_1(u64 val, if (needs_state_flush()) { flush_pending(render_state, prof); m_texture_state[0] = *current_texture_state(); - m_texture_state[0].used = false; - m_current_texture_state = 0; + reset_texture_states(); m_stats.flush_from_state_exhaust++; } else { push_texture_state(); @@ -833,8 +835,9 @@ void DirectRenderer::handle_clamp1(u64 val, if (needs_state_flush()) { flush_pending(render_state, prof); m_texture_state[0] = *current_texture_state(); - m_texture_state[0].used = false; - m_current_texture_state = 0; + reset_texture_states(); + // m_texture_state[0].used = false; + // m_current_texture_state = 0; m_stats.flush_from_state_exhaust++; } else { push_texture_state(); @@ -1046,6 +1049,7 @@ void DirectRenderer::reset_state() { m_texture_state[i] = TextureState(); } m_global_texture_state = TextureGlobalState(); + m_current_texture_state = 0; m_prim_building = PrimBuildState(); diff --git a/game/graphics/opengl_renderer/DirectRenderer.h b/game/graphics/opengl_renderer/DirectRenderer.h index e90c8f0c7a..18f816b43a 100644 --- a/game/graphics/opengl_renderer/DirectRenderer.h +++ b/game/graphics/opengl_renderer/DirectRenderer.h @@ -167,6 +167,14 @@ class DirectRenderer : public BucketRenderer { 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; + } + } + struct TextureGlobalState { bool needs_gl_update = true; } m_global_texture_state; diff --git a/game/graphics/opengl_renderer/SkyBlendCPU.cpp b/game/graphics/opengl_renderer/SkyBlendCPU.cpp index 7ab9664e87..864d38a733 100644 --- a/game/graphics/opengl_renderer/SkyBlendCPU.cpp +++ b/game/graphics/opengl_renderer/SkyBlendCPU.cpp @@ -137,6 +137,9 @@ SkyBlendStats SkyBlendCPU::do_sky_blends(DmaFollower& dma, } // put in pool. + if (render_state->dump_playback) { + return stats; + } for (int i = 0; i < 2; i++) { // todo - these are hardcoded and rely on the vram layout. u32 tbp = i == 0 ? 8064 : 8096; diff --git a/game/graphics/opengl_renderer/SpriteRenderer.cpp b/game/graphics/opengl_renderer/SpriteRenderer.cpp index 612fbf8e54..0faf3366f2 100644 --- a/game/graphics/opengl_renderer/SpriteRenderer.cpp +++ b/game/graphics/opengl_renderer/SpriteRenderer.cpp @@ -163,7 +163,15 @@ void SpriteRenderer::render_2d_group0(DmaFollower& dma, // HACK: this renderers 3D sprites with the 2D renderer. amazingly, it almost works. // assert(run.vifcode1().immediate == SpriteProgMem::Sprites2dGrp0); if (m_enabled) { - do_2d_group0_block_cpu(sprite_count, render_state, prof); + if (run.vifcode1().immediate == SpriteProgMem::Sprites2dGrp0) { + if (m_2d_enable) { + do_2d_group0_block_cpu(sprite_count, render_state, prof); + } + } else { + if (m_3d_enable) { + do_3d_block_cpu(sprite_count, render_state, prof); + } + } } } } @@ -218,7 +226,7 @@ void SpriteRenderer::render_2d_group1(DmaFollower& dma, assert(run.vifcode0().kind == VifCode::Kind::NOP); assert(run.vifcode1().kind == VifCode::Kind::MSCAL); assert(run.vifcode1().immediate == SpriteProgMem::Sprites2dHud); - if (m_enabled) { + if (m_enabled && m_2d_enable) { do_2d_group1_block_cpu(sprite_count, render_state, prof); } } @@ -293,6 +301,11 @@ void SpriteRenderer::draw_debug_window() { ImGui::Text("2D Group 1 (HUD) blocks: %d sprites: %d", m_debug_stats.blocks_2d_grp1, m_debug_stats.count_2d_grp1); ImGui::Checkbox("Extra Debug", &m_extra_debug); + ImGui::Checkbox("2d", &m_2d_enable); + ImGui::SameLine(); + ImGui::Checkbox("3d", &m_3d_enable); + ImGui::SameLine(); + ImGui::Checkbox("3d-debug", &m_3d_debug); if (ImGui::TreeNode("direct")) { m_sprite_renderer.draw_debug_window(); ImGui::TreePop(); @@ -718,6 +731,251 @@ void SpriteRenderer::do_2d_group1_block_cpu(u32 count, } } +std::array sprite_quat_to_rot(float qi, float qj, float qk) { + std::array result; + float qr = std::sqrt(std::abs(1.f - (qi * qi + qj * qj + qk * qk))); + // fmt::print("q: {} {} {} {}\n", qi, qj, qk, qr); + result[0][0] = 1.f - 2.f * (qj * qj + qk * qk); + result[1][0] = 2.f * (qi * qj - qk * qr); + result[2][0] = 2.f * (qi * qk + qj * qr); + result[0][1] = 2.f * (qi * qj + qk * qr); + result[1][1] = 1.f - 2.f * (qi * qi + qk * qk); + result[2][1] = 2.f * (qj * qk - qi * qr); + result[0][2] = 2.f * (qi * qk - qj * qr); + result[1][2] = 2.f * (qj * qk + qi * qr); + result[2][2] = 1.f - 2.f * (qi * qi + qj * qj); + return result; +} + +Vector4f sprite_transform2(const Vector4f& root, + const Vector4f& off, + const Matrix4f& cam, + const std::array& sprite_rot, + float sx, + float sy, + const Vector4f& hvdf_off, + float pfog0, + float fog_min, + float fog_max) { + Vector4f pos = root; + // fmt::print("root : {}\n", root.to_string_aligned()); + // fmt::print("off : {} s {} {}\n", off.to_string_aligned(), sx, sy); + + math::Vector3f offset = + sprite_rot[0] * off.x() * sx + sprite_rot[1] * off.y() + sprite_rot[2] * off.z() * sy; + // fmt::print("off (r): {}\n", offset.to_string_aligned()); + + pos.x() += offset.x(); + pos.y() += offset.y(); + pos.z() += offset.z(); + Vector4f transformed_pos = matrix_transform(cam, pos); + float Q = pfog0 / transformed_pos.w(); + transformed_pos.x() *= Q; + transformed_pos.y() *= Q; + transformed_pos.z() *= Q; + Vector4f offset_pos = transformed_pos + hvdf_off; + offset_pos.w() = std::max(offset_pos.w(), fog_max); + offset_pos.w() = std::min(offset_pos.w(), fog_min); + + return offset_pos; +} + +void SpriteRenderer::do_3d_block_cpu(u32 count, + SharedRenderState* render_state, + ScopedProfilerNode& prof) { + Matrix4f camera_matrix = m_3d_matrix_data.camera; // vf25, vf26, vf27, vf28 + for (u32 sprite_idx = 0; sprite_idx < count; sprite_idx++) { + SpriteHud2DPacket packet; + memset(&packet, 0, sizeof(packet)); + // ilw.y vi08, 1(vi02) | nop vi08 = matrix + u32 offset_selector = m_vec_data_2d[sprite_idx].matrix(); + // assert(offset_selector == 0 || offset_selector == 1); + // moved this out of the loop. + // lq.xyzw vf25, 900(vi00) | nop vf25 = cam_mat + // lq.xyzw vf26, 901(vi00) | nop + // lq.xyzw vf27, 902(vi00) | nop + // lq.xyzw vf28, 903(vi00) | nop + // lq.xyzw vf30, 904(vi00) | nop vf30 = hvdf_offset + // vf30 + Vector4f hvdf_offset = m_3d_matrix_data.hvdf_offset; + + // lqi.xyzw vf01, vi02 | nop + Vector4f pos_vf01 = m_vec_data_2d[sprite_idx].xyz_sx; + // lqi.xyzw vf05, vi02 | nop + Vector4f flags_vf05 = m_vec_data_2d[sprite_idx].flag_rot_sy; + // lqi.xyzw vf11, vi02 | nop + Vector4f color_vf11 = m_vec_data_2d[sprite_idx].rgba; + + // multiplications from the right column + Vector4f transformed_pos_vf02 = matrix_transform(camera_matrix, pos_vf01); + + Vector4f scales_vf01 = pos_vf01; // now used for something else. + // lq.xyzw vf12, 1020(vi00) | mulaw.xyzw ACC, vf28, vf00 + // vf12 is fog consts + Vector4f fog_consts_vf12(m_frame_data.fog_min, m_frame_data.fog_max, m_frame_data.max_scale, + m_frame_data.bonus); + // ilw.y vi08, 1(vi02) | maddax.xyzw ACC, vf25, vf01 + // load offset selector for the next round. + // nop | madday.xyzw ACC, vf26, vf01 + // nop | maddz.xyzw vf02, vf27, vf01 + + // move.w vf05, vf00 | addw.z vf01, vf00, vf05 + // scales_vf01.z = sy + scales_vf01.z() = flags_vf05.w(); // start building the scale vector + flags_vf05.w() = 1.f; // what are we building in flags right now?? + + // nop | nop + // div Q, vf31.x, vf02.w | muly.z vf05, vf05, vf31 + float Q = m_frame_data.pfog0 / transformed_pos_vf02.w(); + flags_vf05.z() *= m_frame_data.deg_to_rad; + // nop | mul.xyzw vf03, vf02, vf29 + Vector4f scaled_pos_vf03 = transformed_pos_vf02.elementwise_multiply(m_frame_data.hmge_scale); + // nop | nop + // nop | nop + // nop | mulz.z vf04, vf05, vf05 (ts) + // fmt::print("rot is {} degrees\n", flags_vf05.z() * 360.0 / (2.0 * M_PI)); + + // the load is for rotation stuff, + // lq.xyzw vf14, 1001(vi00) | clipw.xyz vf03, vf03 (used for fcand) + // iaddi vi06, vi00, 0x1 | adda.xyzw ACC, vf11, vf11 (used for fmand) + + // upcoming fcand with 0x3f, that checks all of them. + bool fcand_result = clip_xyz_plus_minus(scaled_pos_vf03); + bool fmand_result = color_vf11.w() == 0; // (really w+w, but I don't think it matters?) + + // L8: + // xgkick double buffer setup + // ior vi05, vi15, vi00 | mul.zw vf01, vf01, Q + scales_vf01.z() *= Q; // sy + scales_vf01.w() *= Q; // sx + + // lq.xyzw vf06, 998(vi00) | mulz.xyzw vf15, vf05, vf04 (ts) + auto adgif_vf06 = m_frame_data.adgif_giftag; + + // lq.xyzw vf14, 1002(vi00) ts| mula.xyzw ACC, vf05, vf14 (ts) + + // fmand vi01, vi06 | mul.xyz vf02, vf02, Q + transformed_pos_vf02.x() *= Q; + transformed_pos_vf02.y() *= Q; + transformed_pos_vf02.z() *= Q; + + // ibne vi00, vi01, L10 | addz.x vf01, vf00, vf01 + scales_vf01.x() = scales_vf01.z(); // = sy + if (fmand_result) { + continue; // reject! + } + + // lqi.xyzw vf07, vi03 | mulz.xyzw vf16, vf15, vf04 (ts) + // vf07 is first use adgif + + // lq.xyzw vf14, 1003(vi00) | madda.xyzw ACC, vf15, vf14 (ts both) + + // lqi.xyzw vf08, vi03 | add.xyzw vf10, vf02, vf30 + // vf08 is second user adgif + Vector4f offset_pos_vf10 = transformed_pos_vf02 + hvdf_offset; + // if (m_extra_debug) { + // ImGui::Text("sel %d", offset_selector); + // //ImGui::Text("hvdf off z: %f tf/w z: %f", hvdf_offset.z(), transformed_pos_vf02.z()); + // imgui_vec(hvdf_offset, "hvdf"); + // imgui_vec(transformed_pos_vf02, "tf'd"); + // } + + // lqi.xyzw vf09, vi03 | mulw.x vf01, vf01, vf01 + // vf09 is third user adgif + scales_vf01.x() *= scales_vf01.w(); // x = sx * sy + + // sqi.xyzw vf06, vi05 | mulz.xyzw vf15, vf16, vf04 (ts) + // FIRST ADGIF IS adgif_vf06 + packet.adgif_giftag = adgif_vf06; + + // just do all 5 now. + packet.user_adgif = m_adgif[sprite_idx]; + + offset_pos_vf10.w() = std::max(offset_pos_vf10.w(), m_frame_data.fog_max); + + scales_vf01.z() = std::max(scales_vf01.z(), m_frame_data.min_scale); + scales_vf01.w() = std::max(scales_vf01.w(), m_frame_data.min_scale); + + scales_vf01.x() *= m_frame_data.inv_area; // x = sx * sy * inv_area (area ratio) + + offset_pos_vf10.w() = std::min(offset_pos_vf10.w(), m_frame_data.fog_min); + + scales_vf01.z() = std::min(scales_vf01.z(), fog_consts_vf12.z()); + scales_vf01.w() = std::min(scales_vf01.w(), fog_consts_vf12.z()); + bool use_first_giftag = offset_selector == 0; + + auto flag_vi07 = m_vec_data_2d[sprite_idx].flag(); + + scales_vf01.x() = std::min(scales_vf01.x(), 1.f); + + transformed_pos_vf02.w() = offset_pos_vf10.w() - fog_consts_vf12.y(); + + color_vf11.w() *= scales_vf01.x(); // is this right? doesn't this stall?? + + // ibne vi00, vi09, L6 | nop + if (transformed_pos_vf02.w() != 0) { + use_first_giftag = false; + } + + flag_vi07 = 0; // todo hack + Vector4f* xy_array = m_frame_data.xyz_array + flag_vi07; + math::Vector color_integer_vf11 = color_vf11.cast(); + + packet.color = color_integer_vf11; + + if (fcand_result) { + continue; // reject (could move earlier) + } + + Vector4f transformed[4]; + + flags_vf05 = m_vec_data_2d[sprite_idx].flag_rot_sy; + // do rot + auto rot = sprite_quat_to_rot(flags_vf05.x(), flags_vf05.y(), flags_vf05.z()); + // fmt::print("root: {}\n", offset_pos_vf10.to_string_aligned()); + + // for (int i = 0; i < 3; i++) { + // fmt::print("M{}: {}\n", i, rot[i].to_string_aligned()); + // } + for (int i = 0; i < 4; i++) { + transformed[i] = + sprite_transform2(m_vec_data_2d[sprite_idx].xyz_sx, xy_array[i], camera_matrix, rot, + m_vec_data_2d[sprite_idx].sx(), m_vec_data_2d[sprite_idx].sy(), + m_3d_matrix_data.hvdf_offset, m_frame_data.pfog0, m_frame_data.fog_min, + m_frame_data.fog_max); + } + Vector4f xy0_vf19 = transformed[0]; + Vector4f xy1_vf20 = transformed[1]; + Vector4f xy2_vf21 = transformed[2]; + Vector4f xy3_vf22 = transformed[3]; + + packet.sprite_giftag = + use_first_giftag ? m_frame_data.sprite_2d_giftag : m_frame_data.sprite_2d_giftag2; + + Vector4f st0_vf06 = m_frame_data.st_array[0]; + Vector4f st1_vf07 = m_frame_data.st_array[1]; + Vector4f st2_vf08 = m_frame_data.st_array[2]; + Vector4f st3_vf09 = m_frame_data.st_array[3]; + + packet.st0 = st0_vf06; + packet.st1 = st1_vf07; + packet.st2 = st2_vf08; + packet.st3 = st3_vf09; + + auto xy0_vf19_int = (xy0_vf19 * 16.f).cast(); + auto xy1_vf20_int = (xy1_vf20 * 16.f).cast(); + auto xy2_vf21_int = (xy2_vf21 * 16.f).cast(); + auto xy3_vf22_int = (xy3_vf22 * 16.f).cast(); + + packet.xy0 = xy0_vf19_int; + packet.xy1 = xy1_vf20_int; + packet.xy2 = xy2_vf21_int; + packet.xy3 = xy3_vf22_int; + + m_sprite_renderer.render_gif((const u8*)&packet, sizeof(packet), render_state, prof); + } +} + void SpriteRenderer::do_2d_group0_block_cpu(u32 count, SharedRenderState* render_state, ScopedProfilerNode& prof) { diff --git a/game/graphics/opengl_renderer/SpriteRenderer.h b/game/graphics/opengl_renderer/SpriteRenderer.h index 79154c12e9..ac221b1b3e 100644 --- a/game/graphics/opengl_renderer/SpriteRenderer.h +++ b/game/graphics/opengl_renderer/SpriteRenderer.h @@ -165,6 +165,7 @@ class SpriteRenderer : public BucketRenderer { ScopedProfilerNode& prof); void do_2d_group1_block_cpu(u32 count, SharedRenderState* render_state, ScopedProfilerNode& prof); void do_2d_group0_block_cpu(u32 count, SharedRenderState* render_state, ScopedProfilerNode& prof); + void do_3d_block_cpu(u32 count, SharedRenderState* render_state, ScopedProfilerNode& prof); u8 m_sprite_distorter_setup[7 * 16]; // direct data u8 m_sprite_direct_setup[3 * 16]; @@ -183,6 +184,10 @@ class SpriteRenderer : public BucketRenderer { } m_debug_stats; bool m_extra_debug = false; + bool m_3d_debug = false; + + bool m_2d_enable = true; + bool m_3d_enable = true; DirectRenderer m_sprite_renderer; DirectRenderer m_direct_renderer; diff --git a/game/graphics/pipelines/opengl.cpp b/game/graphics/pipelines/opengl.cpp index 17e242a183..52ccf106e5 100644 --- a/game/graphics/pipelines/opengl.cpp +++ b/game/graphics/pipelines/opengl.cpp @@ -397,7 +397,8 @@ static void gl_render_display(GfxDisplay* display) { int lbox_h = (fbuf_h - height) / 2; if (g_gfx_data->debug_gui.want_dump_replay()) { - render_dump_frame(width, height, lbox_w, lbox_h); + // hack: no letterbox for dump frames, for now. + render_dump_frame(fbuf_w, fbuf_h, 0, 0); } else if (g_gfx_data->debug_gui.should_advance_frame()) { render_game_frame(width, height, lbox_w, lbox_h); } diff --git a/game/graphics/texture/TexturePool.cpp b/game/graphics/texture/TexturePool.cpp index 390f01ade0..613b87a945 100644 --- a/game/graphics/texture/TexturePool.cpp +++ b/game/graphics/texture/TexturePool.cpp @@ -393,13 +393,17 @@ void TexturePool::upload_to_gpu(TextureRecord* tex) { GLuint tex_id; glGenTextures(1, &tex_id); tex->gpu_texture = tex_id; + GLint old_tex; + glGetIntegerv(GL_ACTIVE_TEXTURE, &old_tex); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, tex_id); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->w, tex->h, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, tex->data.data()); glBindTexture(GL_TEXTURE_2D, 0); // we have to set these, imgui won't do it automatically - glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, tex->gpu_texture); glGenerateMipmap(GL_TEXTURE_2D); @@ -412,6 +416,7 @@ void TexturePool::upload_to_gpu(TextureRecord* tex) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glActiveTexture(old_tex); tex->on_gpu = true; } diff --git a/goal_src/engine/math/quaternion.gc b/goal_src/engine/math/quaternion.gc index c4b8fc2bb3..0cc01e3651 100644 --- a/goal_src/engine/math/quaternion.gc +++ b/goal_src/engine/math/quaternion.gc @@ -810,6 +810,18 @@ ) ) +(defmacro quaternion-vompula-hack (vf-out vf-in) + `(let ((in-vec (new 'stack-no-clear 'vector)) + (out-vec (new 'stack-no-clear 'vector))) + (.svf (&-> in-vec quad) ,vf-in) + (set! (-> out-vec x) (* (-> in-vec y) (-> in-vec z))) + (set! (-> out-vec y) (* (-> in-vec z) (-> in-vec x))) + (set! (-> out-vec z) (* (-> in-vec x) (-> in-vec y))) + ;;(set! (-> out-vec w) (-> in-vec w)) + (.lvf ,vf-out (&-> out-vec quad)) + ) + ) + (defun quaternion-zxy! ((arg0 quaternion) (arg1 vector)) (rlet ((acc :class vf) (vf0 :class vf) @@ -836,8 +848,10 @@ (.sub.vf vf4 vf0 vf4 :mask #b110) (.add.vf vf3 vf0 vf1 :mask #b111) (.mul.x.vf vf3 vf0 vf2 :mask #b1000) - (.outer.product.vf vf6 vf0 vf0) - (.outer.product.vf vf5 vf0 vf0) + ; (.outer.product.vf vf6 vf0 vf0) + (quaternion-vompula-hack vf6 vf1) + (quaternion-vompula-hack vf5 vf2) + ; (.outer.product.vf vf5 vf0 vf0) (.mul.x.vf vf6 vf0 vf6 :mask #b1000) (.mul.x.vf vf5 vf0 vf5 :mask #b1000) (.mul.vf acc vf6 vf4)