From 2fa4a23ea1dbbd4db7fab37b95fc067644a6c437 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Fri, 17 Mar 2023 20:35:26 -0400 Subject: [PATCH] [jak 2] ETIE (#2326) Definitely needs a clean up pass, but I think the functionality is very close. There's a few "hacks" still: - I am using the emerc logic for environment mapping, which doesn't care about the length of the normals. I can't figure out how the normal scaling worked in etie. I want to do a little bit more experimentation with this before merging. - There is some part about adgifs for TIE and ETIE that I don't understand. The clearly correct behavior of TIE/ETIE is that the alpha settings from the adgif shader are overwritten by the settings from the renderer. But I can't figure out how this happens in all cases. - Fade out is completely disabled. I think this is fine because the performance difference isn't bad. But if you are comparing screenshots with PCSX2, it will make things look a tiny bit different. --- common/custom_data/TFrag3Data.cpp | 120 +- common/custom_data/Tfrag3Data.h | 75 +- decompiler/VuDisasm/VuDisassembler.cpp | 6 + decompiler/VuDisasm/VuInstruction.h | 4 +- decompiler/config/jak2/all-types.gc | 30 +- .../config/jak2/ntsc_v1/type_casts.jsonc | 12 + decompiler/data/TextureDB.cpp | 5 +- decompiler/data/TextureDB.h | 4 +- decompiler/data/tpage.cpp | 12 +- decompiler/level_extractor/BspHeader.cpp | 36 + decompiler/level_extractor/BspHeader.h | 7 +- decompiler/level_extractor/extract_tfrag.cpp | 5 +- decompiler/level_extractor/extract_tie.cpp | 855 +++-- docs/progress-notes/jak2/etie.md | 2520 ++++++++++++++ .../graphics/opengl_renderer/BucketRenderer.h | 9 + .../opengl_renderer/OpenGLRenderer.cpp | 25 +- game/graphics/opengl_renderer/Shader.cpp | 2 + game/graphics/opengl_renderer/Shader.h | 2 + .../opengl_renderer/background/Tie3.cpp | 1079 +++--- .../opengl_renderer/background/Tie3.h | 137 +- .../background/background_common.cpp | 17 +- .../background/background_common.h | 5 + .../opengl_renderer/shaders/emerc.vert | 4 +- .../opengl_renderer/shaders/etie.frag | 31 + .../opengl_renderer/shaders/etie.vert | 165 + .../opengl_renderer/shaders/etie_base.frag | 32 + .../opengl_renderer/shaders/etie_base.vert | 70 + .../opengl_renderer/shaders/tfrag3.frag | 1 + .../opengl_renderer/shaders/tfrag3.vert | 16 +- game/main.cpp | 10 +- game/runtime.cpp | 4 +- game/runtime.h | 2 +- goal_src/jak1/engine/gfx/tfrag/tfrag.gc | 37 +- .../jak2/engine/gfx/background/background.gc | 27 +- goal_src/jak2/engine/gfx/tie/tie-methods.gc | 42 +- goalc/build_level/build_level.cpp | 1 + goalc/build_level/gltf_mesh_extract.cpp | 5 - test/decompiler/test_VuDisasm.cpp | 8 +- .../vu_reference/jak2/etie-vu1-result.txt | 1535 +++++++++ .../decompiler/vu_reference/jak2/etie-vu1.txt | 2992 +++++++++++++++++ test/goalc/framework/test_runner.cpp | 10 +- 41 files changed, 9101 insertions(+), 858 deletions(-) create mode 100644 docs/progress-notes/jak2/etie.md create mode 100644 game/graphics/opengl_renderer/shaders/etie.frag create mode 100644 game/graphics/opengl_renderer/shaders/etie.vert create mode 100644 game/graphics/opengl_renderer/shaders/etie_base.frag create mode 100644 game/graphics/opengl_renderer/shaders/etie_base.vert create mode 100644 test/decompiler/vu_reference/jak2/etie-vu1-result.txt create mode 100644 test/decompiler/vu_reference/jak2/etie-vu1.txt diff --git a/common/custom_data/TFrag3Data.cpp b/common/custom_data/TFrag3Data.cpp index 84c9282e4..2cce4c544 100644 --- a/common/custom_data/TFrag3Data.cpp +++ b/common/custom_data/TFrag3Data.cpp @@ -72,6 +72,104 @@ void TfragTree::serialize(Serializer& ser) { ser.from_ptr(&use_strips); } +math::Vector3f vopmula(math::Vector3f a, math::Vector3f b) { + return math::Vector3f(a.y() * b.z(), a.z() * b.x(), a.x() * b.y()); +} + +math::Vector3f vopmsub(math::Vector3f acc, math::Vector3f a, math::Vector3f b) { + return acc - vopmula(a, b); +} + +/*! + * Compute the normal transformation for a TIE from the TIE matrix. + * Note that this isn't identical to the original game - we're missing the vf14 scaling factor + * For now, I just set this to 1, then normalize in the shader. Though I think we could avoid + * this by figuring out the value of vf14 here (I am just too lazy right now). + */ +std::array tie_normal_transform_v2(const std::array& m) { + // let: + // vf10, vf11, vf12, vf13 be the input matrix m + std::array result; + auto& vf10 = m[0]; + auto& vf11 = m[1]; + // auto& vf12 = m[2]; + + // vmulx.xyz vf16, vf10, vf14 + math::Vector3f vf16 = vf10.xyz() * 1.0; // TODO VF14 + + // vopmula.xyz acc, vf11, vf16 + math::Vector3f acc = vopmula(vf11.xyz(), vf16); + + // vopmsub.xyz vf17, vf16, vf11 + math::Vector3f vf17 = vopmsub(acc, vf16, vf11.xyz()); + + // vopmula.xyz acc, vf16, vf17 + acc = vopmula(vf16, vf17); + + // vopmsub.xyz vf17, vf17, vf16 + vf17 = vopmsub(acc, vf17, vf16); + + // vmul.xyz vf14, vf17, vf17 + math::Vector3f vf14 = vf17.elementwise_multiply(vf17); + + // vmulax.w acc, vf0, vf14 + // vmadday.w acc, vf0, vf14 + // vmaddz.w vf14, vf0, vf14 + float sum = vf14.x() + vf14.y() + vf14.z(); + + // vrsqrt Q, vf0.w, vf14.w + float Q = 1.f / std::sqrt(sum); + + // vmulax.xyzw acc, vf24, vf16 + // vmadday.xyzw acc, vf25, vf16 + // vmaddz.xyzw vf10, vf26, vf16 + // vf10 = vf16; // assume cam is identity here. + result[0] = vf16; + + // vwaitq + // vmulq.xyz vf17, vf17, Q + vf17 *= Q; + + // vopmula.xyz acc, vf16, vf17 + acc = vopmula(vf16, vf17); + // vopmsub.xyz vf18, vf17, vf16 + math::Vector3f vf18 = vopmsub(acc, vf17, vf16); + + // vmulax.xyzw acc, vf24, vf17 + // vmadday.xyzw acc, vf25, vf17 + // vmaddz.xyzw vf11, vf26, vf17 + result[1] = vf17; + + // vmulax.xyzw acc, vf24, vf18 + // vmadday.xyzw acc, vf25, vf18 + // vmaddz.xyzw vf12, vf26, vf18 + result[2] = vf18; + return result; + // + // sqc2 vf10, -112(t8) + // sqc2 vf11, -96(t8) + // sqc2 vf12, -80(t8) +} + +/*! + * Unpack tie normal by transforming and converting to s16 for OpenGL. + */ +math::Vector unpack_tie_normal(const std::array& mat, + s8 nx, + s8 ny, + s8 nz) { + // rotate the normal + math::Vector3f nrm = math::Vector3f::zero(); + nrm += mat[0] * nx; + nrm += mat[1] * ny; + nrm += mat[2] * nz; + // convert to s16 for OpenGL renderer + nrm *= 0.0078125; // number from EE asm + nrm *= 256.f * 128.f; // for normalize s16 -> float conversion by OpenGL. + + return nrm.cast(); +} + void TieTree::unpack() { unpacked.vertices.resize(packed_vertices.color_indices.size()); size_t i = 0; @@ -84,13 +182,21 @@ void TieTree::unpack() { vtx.x = proto_vtx.x; vtx.y = proto_vtx.y; vtx.z = proto_vtx.z; - vtx.q_unused = 1.f; vtx.s = proto_vtx.s; vtx.t = proto_vtx.t; + vtx.nx = proto_vtx.nx << 8; + vtx.ny = proto_vtx.ny << 8; + vtx.nz = proto_vtx.nz << 8; + vtx.r = proto_vtx.r; + vtx.g = proto_vtx.g; + vtx.b = proto_vtx.b; + vtx.a = proto_vtx.a; i++; } } else { const auto& mat = packed_vertices.matrices[grp.matrix_idx]; + auto nmat = tie_normal_transform_v2(mat); + for (u32 src_idx = grp.start_vert; src_idx < grp.end_vert; src_idx++) { auto& vtx = unpacked.vertices[i]; vtx.color_index = packed_vertices.color_indices[i]; @@ -99,9 +205,16 @@ void TieTree::unpack() { vtx.x = temp.x(); vtx.y = temp.y(); vtx.z = temp.z(); - vtx.q_unused = 1.f; vtx.s = proto_vtx.s; vtx.t = proto_vtx.t; + auto nrm = unpack_tie_normal(nmat, proto_vtx.nx, proto_vtx.ny, proto_vtx.nz); + vtx.nx = nrm.x(); + vtx.ny = nrm.y(); + vtx.nz = nrm.z(); + vtx.r = proto_vtx.r; + vtx.g = proto_vtx.g; + vtx.b = proto_vtx.b; + vtx.a = proto_vtx.a; i++; } } @@ -159,7 +272,6 @@ void TfragTree::unpack() { o.z = cz + in.zoff * rescale; o.s = in.s / (1024.f); o.t = in.t / (1024.f); - o.q_unused = 1.f; o.color_index = in.color_index; } @@ -188,6 +300,8 @@ void TieTree::serialize(Serializer& ser) { draw.serialize(ser); } + ser.from_ptr(&category_draw_indices); + if (ser.is_saving()) { ser.save(instanced_wind_draws.size()); } else { diff --git a/common/custom_data/Tfrag3Data.h b/common/custom_data/Tfrag3Data.h index 5823d5747..743768624 100644 --- a/common/custom_data/Tfrag3Data.h +++ b/common/custom_data/Tfrag3Data.h @@ -73,17 +73,22 @@ struct MemoryUsageTracker { void add(MemoryUsageCategory category, u32 size_bytes) { data[category] += size_bytes; } }; -constexpr int TFRAG3_VERSION = 27; +constexpr int TFRAG3_VERSION = 28; // These vertices should be uploaded to the GPU at load time and don't change struct PreloadedVertex { // the vertex position - float x, y, z; + float x = 0, y = 0, z = 0; // texture coordinates - float s, t, q_unused; + float s = 0, t = 0; + + u8 r = 0, g = 0, b = 0, a = 0; // envmap tint color, not used in == or hash. + // color table index - u16 color_index; - u16 pad[3]; + u16 color_index = 0; + + // not used in == or hash!! + s16 nx = 0, ny = 0, nz = 0; struct hash { std::size_t operator()(const PreloadedVertex& x) const; @@ -100,6 +105,8 @@ struct PackedTieVertices { struct Vertex { float x, y, z; float s, t; + s8 nx, ny, nz; + u8 r, g, b, a; }; struct MatrixGroup { @@ -313,10 +320,66 @@ struct TieWindInstance { void serialize(Serializer& ser); }; +// Tie draws are split into categories. +enum class TieCategory { + // normal tie buckets + NORMAL, + TRANS, // also called alpha + WATER, + + // first draw (normal base draw) for envmapped stuff + NORMAL_ENVMAP, + TRANS_ENVMAP, + WATER_ENVMAP, + + // second draw (shiny) for envmapped ties. + NORMAL_ENVMAP_SECOND_DRAW, + TRANS_ENVMAP_SECOND_DRAW, + WATER_ENVMAP_SECOND_DRAW, +}; +constexpr int kNumTieCategories = 9; + +constexpr bool is_envmap_first_draw_category(tfrag3::TieCategory category) { + switch (category) { + case tfrag3::TieCategory::NORMAL_ENVMAP: + case tfrag3::TieCategory::WATER_ENVMAP: + case tfrag3::TieCategory::TRANS_ENVMAP: + return true; + default: + return false; + } +} + +constexpr bool is_envmap_second_draw_category(tfrag3::TieCategory category) { + switch (category) { + case tfrag3::TieCategory::NORMAL_ENVMAP_SECOND_DRAW: + case tfrag3::TieCategory::WATER_ENVMAP_SECOND_DRAW: + case tfrag3::TieCategory::TRANS_ENVMAP_SECOND_DRAW: + return true; + default: + return false; + } +} + +constexpr TieCategory get_second_draw_category(tfrag3::TieCategory category) { + switch (category) { + case TieCategory::NORMAL_ENVMAP: + return TieCategory::NORMAL_ENVMAP_SECOND_DRAW; + case TieCategory::TRANS_ENVMAP: + return TieCategory::TRANS_ENVMAP_SECOND_DRAW; + case TieCategory::WATER_ENVMAP: + return TieCategory::WATER_ENVMAP_SECOND_DRAW; + default: + return TieCategory::NORMAL_ENVMAP; + } +} + // A tie model struct TieTree { BVH bvh; - std::vector static_draws; // the actual topology and settings + std::vector static_draws; + // Category n uses draws: static_draws[cdi[n]] to static_draws[cdi[n + 1]] + std::array category_draw_indices; PackedTieVertices packed_vertices; std::vector colors; // vertex colors (pre-interpolation) diff --git a/decompiler/VuDisasm/VuDisassembler.cpp b/decompiler/VuDisasm/VuDisassembler.cpp index 1c8e2be0e..3f1eefff3 100644 --- a/decompiler/VuDisasm/VuDisassembler.cpp +++ b/decompiler/VuDisasm/VuDisassembler.cpp @@ -225,6 +225,8 @@ VuDisassembler::VuDisassembler(VuKind kind) : m_kind(kind) { add_op(VuInstrK::SQRT, "sqrt").fsf_zero().ftf_0().vis_zero().dst_q().src_vft(); add_op(VuInstrK::SQD, "sqd").dst_mask().src_vfs().src_vit(); add_op(VuInstrK::ERLENG, "erleng").dst_mask().vft_zero().src_vfs().dst_p(); + add_op(VuInstrK::ESUM, "esum").dst_mask().vft_zero().src_vfs().dst_p(); + add_op(VuInstrK::ESADD, "esadd").dst_mask().vft_zero().src_vfs().dst_p(); add_op(VuInstrK::ELENG, "eleng").dst_mask().vft_zero().src_vfs().dst_p(); add_op(VuInstrK::MFP, "mfp").dst_mask().dst_vft().src_p(); } @@ -294,10 +296,14 @@ VuInstrK VuDisassembler::lower_kind(u32 in) { return VuInstrK::XTOP; case 0b11011'1111'00: return VuInstrK::XGKICK; + case 0b11100'1111'00: + return VuInstrK::ESADD; case 0b11100'1111'10: return VuInstrK::ELENG; case 0b11100'1111'11: return VuInstrK::ERLENG; + case 0b11101'1111'10: + return VuInstrK::ESUM; case 0b11110'1111'11: return VuInstrK::WAITP; } diff --git a/decompiler/VuDisasm/VuInstruction.h b/decompiler/VuDisasm/VuInstruction.h index 23849b7b7..9c6fe3db9 100644 --- a/decompiler/VuDisasm/VuInstruction.h +++ b/decompiler/VuDisasm/VuInstruction.h @@ -126,13 +126,13 @@ enum class VuInstrK { JALR, MFP, WAITP, - // ESADD, + ESADD, // ERSADD, ELENG, ERLENG, // EATANxy, // EATANxz, - // ESUM, + ESUM, // ERCPR, // ESQRT, // ERSQRT, diff --git a/decompiler/config/jak2/all-types.gc b/decompiler/config/jak2/all-types.gc index 69c54573d..7546cf739 100644 --- a/decompiler/config/jak2/all-types.gc +++ b/decompiler/config/jak2/all-types.gc @@ -24991,13 +24991,14 @@ ;; etie-vu1 ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -#| (deftype etie-consts (structure) ((gifbufs qword :inline :offset-assert 0) - (adgif qword :inline :offset-assert 16) - (alpha qword :inline :offset-assert 32) - (strgif qword :inline :offset-assert 48) - (envgif qword :inline :offset-assert 64) + ;(adgif qword :inline :offset-assert 16) + (adgif gs-gif-tag :inline :offset-assert 16) + ;;(alpha qword :inline :offset-assert 32) + (alpah gs-adcmd :inline) + (strgif gs-gif-tag :inline :offset-assert 48) + (envgif gs-gif-tag :inline :offset-assert 64) (envmap adgif-shader :inline :offset-assert 80) (pers0 vector :inline :offset-assert 160) (pers1 vector :inline :offset-assert 176) @@ -25006,27 +25007,24 @@ :size-assert #xc0 :flag-assert #x9000000c0 ) -|# -#| (deftype etie-matrix (structure) ((rmtx matrix :inline :offset-assert 0) (nmtx matrix3 :inline :offset-assert 64) - (morph float :offset-assert 76) - (fog float :offset-assert 92) - (fade uint32 :offset-assert 108) + (morph float :offset 76) + (fog float :offset 92) + (fade uint32 :offset 108) (tint qword :inline :offset-assert 112) ) :method-count-assert 9 :size-assert #x80 :flag-assert #x900000080 ) -|# -;; (define-extern etie-vu1-block object) -;; (define-extern etie-magic function) -;; (define-extern etie-init-consts function) -(define-extern etie-init-engine (function dma-buffer none)) +(define-extern etie-vu1-block vu-function) +(define-extern etie-magic (function int int)) +(define-extern etie-init-consts (function etie-consts gs-alpha none)) +(define-extern etie-init-engine (function dma-buffer gs-alpha gs-test none)) (define-extern etie-end-buffer (function dma-buffer none)) ;; (define-extern etie-float-reg-bp function) ;; (define-extern etie-float-reg function) @@ -25201,7 +25199,7 @@ (define-extern draw-drawable-tree-instance-tie (function drawable-tree-instance-tie level none)) (define-extern tie-init-scissor-buf (function bucket-id gs-alpha gs-test gs-test none)) (define-extern tie-init-buf (function bucket-id gs-alpha gs-test gs-test none)) -(define-extern tie-init-envmap-buf (function bucket-id gs-alpha int int none)) +(define-extern tie-init-envmap-buf (function bucket-id gs-alpha gs-test none)) (define-extern tie-init-envmap-scissor-buf (function bucket-id gs-alpha int int none)) (define-extern *tie-init-table* (inline-array tie-init-data)) (define-extern tie-vu1-init-buffers (function none)) diff --git a/decompiler/config/jak2/ntsc_v1/type_casts.jsonc b/decompiler/config/jak2/ntsc_v1/type_casts.jsonc index a1591c6db..05b599c1d 100644 --- a/decompiler/config/jak2/ntsc_v1/type_casts.jsonc +++ b/decompiler/config/jak2/ntsc_v1/type_casts.jsonc @@ -11005,5 +11005,17 @@ "(method 98 gun-buoy)": [ [46, "v1", "collide-shape-moving"], [76, "v1", "collide-shape-moving"] + ], + "etie-init-engine": [ + [[3, 10], "a0", "dma-packet"], + [[12, 19], "a0", "gs-gif-tag"], + [24, "a0", "(pointer gs-reg64)"], + [[34, 48], "a0", "dma-packet"], + [[58, 66], "a0", "dma-packet"], + [[68, 75], "a0", "dma-packet"], + [[79, 86], "a0", "(pointer uint32)"], + [[87, 94], "a0", "(pointer vif-tag)"] + + ] } diff --git a/decompiler/data/TextureDB.cpp b/decompiler/data/TextureDB.cpp index 9785bdcfb..87d10568d 100644 --- a/decompiler/data/TextureDB.cpp +++ b/decompiler/data/TextureDB.cpp @@ -16,7 +16,8 @@ void TextureDB::add_texture(u32 tpage, u16 h, const std::string& tex_name, const std::string& tpage_name, - const std::vector& level_names) { + const std::vector& level_names, + u32 num_mips) { auto existing_tpage_name = tpage_names.find(tpage); if (existing_tpage_name == tpage_names.end()) { tpage_names[tpage] = tpage_name; @@ -32,6 +33,7 @@ void TextureDB::add_texture(u32 tpage, ASSERT(existing_tex->second.h == h); ASSERT(existing_tex->second.rgba_bytes == data); ASSERT(existing_tex->second.page == tpage); + ASSERT(existing_tex->second.num_mips == num_mips); } else { auto& new_tex = textures[combo_id]; new_tex.rgba_bytes = data; @@ -39,6 +41,7 @@ void TextureDB::add_texture(u32 tpage, new_tex.w = w; new_tex.h = h; new_tex.page = tpage; + new_tex.num_mips = num_mips; } for (const auto& level_name : level_names) { texture_ids_per_level[level_name].insert(combo_id); diff --git a/decompiler/data/TextureDB.h b/decompiler/data/TextureDB.h index 48178f9a3..471d59374 100644 --- a/decompiler/data/TextureDB.h +++ b/decompiler/data/TextureDB.h @@ -15,6 +15,7 @@ struct TextureDB { std::string name; u32 page; std::vector rgba_bytes; + u32 num_mips = -1; }; std::unordered_map textures; @@ -28,7 +29,8 @@ struct TextureDB { u16 h, const std::string& tex_name, const std::string& tpage_name, - const std::vector& level_names); + const std::vector& level_names, + u32 num_mips); void replace_textures(const fs::path& path); }; diff --git a/decompiler/data/tpage.cpp b/decompiler/data/tpage.cpp index d532e78ac..a71e5347e 100644 --- a/decompiler/data/tpage.cpp +++ b/decompiler/data/tpage.cpp @@ -537,7 +537,7 @@ TPageResultStats process_tpage(ObjectFileData& data, file_util::write_rgba_png(texture_dump_dir / fmt::format("{}.png", tex.name), out.data(), tex.w, tex.h); texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name, - texture_page.name, level_names); + texture_page.name, level_names, tex.num_mips); stats.successful_textures++; } else if (tex.psm == int(PSM::PSMT8) && tex.clutpsm == int(CPSM::PSMCT16)) { // will store output pixels, rgba (8888) @@ -580,7 +580,7 @@ TPageResultStats process_tpage(ObjectFileData& data, file_util::write_rgba_png(texture_dump_dir / fmt::format("{}.png", tex.name), out.data(), tex.w, tex.h); texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name, - texture_page.name, level_names); + texture_page.name, level_names, tex.num_mips); stats.successful_textures++; } else if (tex.psm == int(PSM::PSMCT16) && tex.clutpsm == 0) { // not a clut. @@ -605,7 +605,7 @@ TPageResultStats process_tpage(ObjectFileData& data, file_util::write_rgba_png(texture_dump_dir / fmt::format("{}.png", tex.name), out.data(), tex.w, tex.h); texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name, - texture_page.name, level_names); + texture_page.name, level_names, tex.num_mips); stats.successful_textures++; } else if (tex.psm == int(PSM::PSMT4) && tex.clutpsm == int(CPSM::PSMCT16)) { // will store output pixels, rgba (8888) @@ -646,7 +646,7 @@ TPageResultStats process_tpage(ObjectFileData& data, file_util::write_rgba_png(texture_dump_dir / fmt::format("{}.png", tex.name), out.data(), tex.w, tex.h); texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name, - texture_page.name, level_names); + texture_page.name, level_names, tex.num_mips); stats.successful_textures++; } else if (tex.psm == int(PSM::PSMT4) && tex.clutpsm == int(CPSM::PSMCT32)) { // will store output pixels, rgba (8888) @@ -687,7 +687,7 @@ TPageResultStats process_tpage(ObjectFileData& data, file_util::write_rgba_png(texture_dump_dir / fmt::format("{}.png", tex.name), out.data(), tex.w, tex.h); texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name, - texture_page.name, level_names); + texture_page.name, level_names, tex.num_mips); stats.successful_textures++; } else if (tex.psm == int(PSM::PSMCT32) && tex.clutpsm == 0) { // not a clut. @@ -712,7 +712,7 @@ TPageResultStats process_tpage(ObjectFileData& data, file_util::write_rgba_png(texture_dump_dir / fmt::format("{}.png", tex.name), out.data(), tex.w, tex.h); texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name, - texture_page.name, level_names); + texture_page.name, level_names, tex.num_mips); stats.successful_textures++; } diff --git a/decompiler/level_extractor/BspHeader.cpp b/decompiler/level_extractor/BspHeader.cpp index c4a5051a7..29b9aa642 100644 --- a/decompiler/level_extractor/BspHeader.cpp +++ b/decompiler/level_extractor/BspHeader.cpp @@ -445,6 +445,15 @@ std::string TFragment::print(const PrintSettings& settings, int indent) const { return result; } +void memcpy_plain_data(u8* dst, const Ref& ref, size_t size_bytes) { + const auto& words = ref.data->words_by_seg.at(ref.seg); + for (size_t i = 0; i < size_bytes; i++) { + size_t byte_offset = ref.byte_offset + i; + u8 byte = words.at(byte_offset / 4).get_byte(byte_offset % 4); + memcpy(dst + i, &byte, sizeof(u8)); + } +} + void TieFragment::read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts, DrawStats* stats, @@ -492,6 +501,15 @@ void TieFragment::read_from_file(TypedRef ref, } stats->total_tie_prototype_tris += num_tris; + + if (version > GameVersion::Jak1) { + u16 normals_qwc = read_plain_data_field(ref, "normal-count", dts); + if (normals_qwc) { + normals.resize(16 * normals_qwc); + auto normals_data_ref = deref_label(get_field_ref(ref, "normal-ref", dts)); + memcpy_plain_data((u8*)normals.data(), normals_data_ref, normals_qwc * 16); + } + } } std::string TieFragment::print(const PrintSettings& /*settings*/, int indent) const { @@ -1037,6 +1055,24 @@ void PrototypeBucketTie::read_from_file(TypedRef ref, for (int i = 0; i < int(8 * time_of_day.height); i++) { time_of_day.colors.push_back(deref_u32(palette, 3 + i)); } + + if (version > GameVersion::Jak1) { + auto fr = get_field_ref(ref, "envmap-shader", dts); + const auto& word = fr.data->words_by_seg.at(fr.seg).at(fr.byte_offset / 4); + if (word.kind() == decompiler::LinkedWord::PTR) { + has_envmap_shader = true; + Ref envmap_shader_ref(deref_label(fr)); + for (int i = 0; i < 5 * 16; i++) { + int byte = envmap_shader_ref.byte_offset + i; + u8 val = + ref.ref.data->words_by_seg.at(envmap_shader_ref.seg).at(byte / 4).get_byte(byte % 4); + envmap_shader[i] = val; + } + } + + u32 tint = read_plain_data_field(ref, "tint-color", dts); + memcpy(tint_color.data(), &tint, 4); + } } std::string PrototypeBucketTie::print(const PrintSettings& settings, int indent) const { diff --git a/decompiler/level_extractor/BspHeader.h b/decompiler/level_extractor/BspHeader.h index 94ad1f429..d1d9a3f63 100644 --- a/decompiler/level_extractor/BspHeader.h +++ b/decompiler/level_extractor/BspHeader.h @@ -6,6 +6,7 @@ #include #include "common/common_types.h" +#include "common/math/Vector.h" #include "common/versions.h" #include "decompiler/level_extractor/common_formats.h" @@ -406,6 +407,8 @@ struct TieFragment : public Drawable { std::string debug_label_name; + std::vector normals; + // todo, lots more }; @@ -479,7 +482,9 @@ struct PrototypeBucketTie { TimeOfDayPalette time_of_day; - // todo envmap shader + bool has_envmap_shader = false; + u8 envmap_shader[5 * 16]; // jak 2 only + math::Vector tint_color; // todo collide-frag DrawableInlineArrayCollideFragment collide_frag; // todo tie-colors diff --git a/decompiler/level_extractor/extract_tfrag.cpp b/decompiler/level_extractor/extract_tfrag.cpp index 117209717..456d6060f 100644 --- a/decompiler/level_extractor/extract_tfrag.cpp +++ b/decompiler/level_extractor/extract_tfrag.cpp @@ -2070,9 +2070,8 @@ void make_tfrag3_data(std::map>& draws, vtx.z = vert.pre_cam_trans_pos.z(); vtx.s = vert.stq.x(); vtx.t = vert.stq.y(); - vtx.q_unused = vert.stq.z(); - // if this is true, we can remove a divide in the shader - ASSERT(vtx.q_unused == 1.f); + // because this is true, we can remove a divide in the shader + ASSERT(vert.stq.z() == 1.f); vtx.color_index = vert.rgba / 4; // ASSERT((vert.rgba >> 2) < 1024); spider cave has 2048? ASSERT((vert.rgba & 3) == 0); diff --git a/decompiler/level_extractor/extract_tie.cpp b/decompiler/level_extractor/extract_tie.cpp index 18024b706..a79bd1120 100644 --- a/decompiler/level_extractor/extract_tie.cpp +++ b/decompiler/level_extractor/extract_tie.cpp @@ -4,6 +4,7 @@ #include "common/log/log.h" #include "common/util/FileUtil.h" +#include "common/util/string_util.h" #include "decompiler/ObjectFile/LinkedObjectFile.h" @@ -227,6 +228,7 @@ struct AdgifInfo { u32 combo_tex; // PC texture ID u64 alpha_val; // alpha blend settings u64 clamp_val; // texture clamp settings + u32 num_mips = -1; }; // When the prototype is uploaded, it places a bunch of strgif tags in VU memory. @@ -242,17 +244,20 @@ struct StrGifInfo { struct TieProtoVertex { math::Vector pos; // position math::Vector tex; // texture coordinate + math::Vector nrm; // normal // NOTE: this is a double lookup. // first you look up the index in the _instance_ color table // then you look up the color in the _proto_'s interpolated color palette. u32 color_index_index; + math::Vector envmap_tint_color; }; // a tie fragment is made up of strips. Each strip has a single adgif info, and vertices // the vertices make up a triangle strip struct TieStrip { AdgifInfo adgif; + int adgif_idx = -1; std::vector verts; }; @@ -265,6 +270,8 @@ struct TieFrag { std::vector other_gif_data; // data sent from EE asm code, sizes/offsets/metadata std::vector points_data; // data sent from EE asm code, actual vertex data + std::vector> + normal_data_packed; // jak2 etie only, not unpacked like the others // number of "dverts" expected from game's metadata. we check our extraction from this. u32 expected_dverts = 0; @@ -286,6 +293,17 @@ struct TieFrag { return result; } + math::Vector get_normal_if_present(u32 nrm_idx) const { + if (normal_data_packed.empty()) { + // no normals on this model + return math::Vector::zero(); + } else { + ASSERT(nrm_idx < normal_data_packed.size()); + ASSERT(normal_data_packed.at(nrm_idx).w() == 0); + return normal_data_packed.at(nrm_idx).xyz(); + } + } + // simulate a load from points, but don't die if we load past the end // this can happen when pipelining. math::Vector lq_points_allow_past_end(u32 qw) const { @@ -342,10 +360,10 @@ struct TieFrag { struct TieProtoInfo { std::string name; std::vector instances; - bool uses_generic = false; u32 proto_flag; float stiffness = 0; // wind - u32 generic_flag; + std::optional envmap_adgif; + math::Vector tint_color = math::Vector::zero(); std::vector time_of_day_colors; // c++ type for time of day data std::vector frags; // the fragments of the prototype }; @@ -476,6 +494,134 @@ u32 remap_texture(u32 original, const std::vector& ma return original; } +AdgifInfo process_adgif(const std::vector& gif_data, + int tex_idx, + const std::vector& map, + bool* uses_magic_tex0_bit) { + AdgifInfo adgif; + // address for the first adgif shader qw. + u8 ra_tex0 = gif_data.at(16 * (tex_idx * 5 + 0) + 8); + // data for the first adgif shader qw. + u64 ra_tex0_val; + memcpy(&ra_tex0_val, &gif_data.at(16 * (tex_idx * 5 + 0)), 8); + + // always expecting TEX0_1 + ASSERT(ra_tex0 == (u8)GsRegisterAddress::TEX0_1); + + // the value is overwritten by the login function. We don't care about this value, it's + // specific to the PS2's texture system. + ASSERT(ra_tex0_val == 0 || ra_tex0_val == 0x800000000); // note: decal + // the original value is a flag. this means to use decal texture mode (todo) + if (uses_magic_tex0_bit) { + *uses_magic_tex0_bit = ra_tex0_val == 0x800000000; + } + // there's also a hidden value in the unused bits of the a+d data. it'll be used by the + // VU program. + memcpy(&adgif.first_w, &gif_data.at(16 * (tex_idx * 5 + 0) + 12), 4); + + // Second adgif. Similar to the first, except the original data value is a texture ID. + u8 ra_tex1 = gif_data.at(16 * (tex_idx * 5 + 1) + 8); + u64 ra_tex1_val; + memcpy(&ra_tex1_val, &gif_data.at(16 * (tex_idx * 5 + 1)), 8); + ASSERT(ra_tex1 == (u8)GsRegisterAddress::TEX1_1); + ASSERT(ra_tex1_val == 0x120); // some flag + u32 original_tex; + memcpy(&original_tex, &gif_data.at(16 * (tex_idx * 5 + 1) + 8), 4); + // try remapping it + u32 new_tex = remap_texture(original_tex, map); + if (original_tex != new_tex) { + lg::info("map from 0x{:x} to 0x{:x}", original_tex, new_tex); + } + // texture the texture page/texture index, and convert to a PC port texture ID + u32 tpage = new_tex >> 20; + u32 tidx = (new_tex >> 8) & 0b1111'1111'1111; + u32 tex_combo = (((u32)tpage) << 16) | tidx; + // remember the texture id (may be invalid, will be checked later) + adgif.combo_tex = tex_combo; + // and the hidden value in the unused a+d + memcpy(&adgif.second_w, &gif_data.at(16 * (tex_idx * 5 + 1) + 12), 4); + // todo: figure out if this matters. maybe this is decal? + if (ra_tex0_val == 0x800000000) { + // lg::print("texture {} in {} has weird tex setting\n", tex->second.name, proto.name); + } + + // mipmap settings. we ignore, but get the hidden value + u8 ra_mip = gif_data.at(16 * (tex_idx * 5 + 2) + 8); + ASSERT(ra_mip == (u8)GsRegisterAddress::MIPTBP1_1); + memcpy(&adgif.third_w, &gif_data.at(16 * (tex_idx * 5 + 2) + 12), 4); + // who cares about the value + + // clamp settings. we care about these. no hidden value. + u8 ra_clamp = gif_data.at(16 * (tex_idx * 5 + 3) + 8); + ASSERT(ra_clamp == (u8)GsRegisterAddress::CLAMP_1); + u64 clamp; + memcpy(&clamp, &gif_data.at(16 * (tex_idx * 5 + 3)), 8); + adgif.clamp_val = clamp; + + // alpha settings. we care about these, but no hidden value + u8 ra_alpha = gif_data.at(16 * (tex_idx * 5 + 4) + 8); + ASSERT(ra_alpha == (u8)GsRegisterAddress::ALPHA_1); + u64 alpha; + memcpy(&alpha, &gif_data.at(16 * (tex_idx * 5 + 4)), 8); + adgif.alpha_val = alpha; + return adgif; +} + +struct TieCategoryInfo { + tfrag3::TieCategory category = tfrag3::TieCategory::NORMAL; + tfrag3::TieCategory envmap_second_draw_category = tfrag3::TieCategory::NORMAL_ENVMAP; + bool uses_envmap = false; +}; + +TieCategoryInfo get_jak2_tie_category(u32 flags) { + constexpr int kJak2ProtoEnvmap = 2; + constexpr int kJak2ProtoTpageAlpha = 4; + constexpr int kJak2ProtoTpageWater = 128; + TieCategoryInfo result; + result.uses_envmap = flags & kJak2ProtoEnvmap; + + if (flags & kJak2ProtoTpageAlpha) { + if (result.uses_envmap) { + result.category = tfrag3::TieCategory::TRANS_ENVMAP; + result.envmap_second_draw_category = tfrag3::TieCategory::TRANS_ENVMAP_SECOND_DRAW; + } else { + result.category = tfrag3::TieCategory::TRANS; + } + ASSERT((flags & kJak2ProtoTpageWater) == 0); + } else if (flags & kJak2ProtoTpageWater) { + if (result.uses_envmap) { + result.category = tfrag3::TieCategory::WATER_ENVMAP; + result.envmap_second_draw_category = tfrag3::TieCategory::WATER_ENVMAP_SECOND_DRAW; + } else { + result.category = tfrag3::TieCategory::WATER; + } + ASSERT((flags & kJak2ProtoTpageAlpha) == 0); + } else { + if (result.uses_envmap) { + result.category = tfrag3::TieCategory::NORMAL_ENVMAP; + result.envmap_second_draw_category = tfrag3::TieCategory::NORMAL_ENVMAP_SECOND_DRAW; + } else { + result.category = tfrag3::TieCategory::NORMAL; + } + } + return result; +} + +u64 alpha_value_for_jak2_tie_or_etie_alpha_override(tfrag3::TieCategory category) { + switch (category) { + case tfrag3::TieCategory::NORMAL: + case tfrag3::TieCategory::NORMAL_ENVMAP: + return 0; + case tfrag3::TieCategory::TRANS: + case tfrag3::TieCategory::WATER: + case tfrag3::TieCategory::TRANS_ENVMAP: + case tfrag3::TieCategory::WATER_ENVMAP: + return 68; + default: + ASSERT_NOT_REACHED(); + } +} + /*! * Update per-proto information. */ @@ -488,14 +634,21 @@ void update_proto_info(std::vector* out, const auto& proto = protos[i]; auto& info = out->at(i); info.proto_flag = proto.flags; - // flag of 2 means it should use the generic renderer (determined from EE asm) - // for now, we ignore this and use TIE on everything. - info.uses_generic = (proto.flags == 2); // possibly different in jak 2 // for debug, remember the name info.name = proto.name; // wind "stiffness" nonzero value means it has the wind effect info.stiffness = proto.stiffness; - info.generic_flag = proto.flags & 2; + if (proto.has_envmap_shader) { + std::vector adgif; + for (auto x : proto.envmap_shader) { + adgif.push_back(x); + } + info.envmap_adgif = process_adgif(adgif, 0, map, nullptr); + info.tint_color = proto.tint_color; + } + + // bool use_crazy_jak2_etie_alpha_thing = proto.has_envmap_shader; + // the actual colors (rgba) used by time of day interpolation // there are "height" colors. Each color is actually 8 colors that are interpolated. info.time_of_day_colors.resize(proto.time_of_day.height); @@ -516,74 +669,12 @@ void update_proto_info(std::vector* out, // all TIE things have pretty normal adgif shaders // all the useful adgif data will be saved into this AdgifInfo - AdgifInfo adgif; // pointer to the level data auto& gif_data = proto.geometry[geo].tie_fragments[frag_idx].gif_data; - // address for the first adgif shader qw. - u8 ra_tex0 = gif_data.at(16 * (tex_idx * 5 + 0) + 8); - // data for the first adgif shader qw. - u64 ra_tex0_val; - memcpy(&ra_tex0_val, &gif_data.at(16 * (tex_idx * 5 + 0)), 8); + auto adgif = process_adgif(gif_data, tex_idx, map, &frag_info.has_magic_tex0_bit); - // always expecting TEX0_1 - ASSERT(ra_tex0 == (u8)GsRegisterAddress::TEX0_1); - - // the value is overwritten by the login function. We don't care about this value, it's - // specific to the PS2's texture system. - ASSERT(ra_tex0_val == 0 || ra_tex0_val == 0x800000000); // note: decal - // the original value is a flag. this means to use decal texture mode (todo) - frag_info.has_magic_tex0_bit = ra_tex0_val == 0x800000000; - // there's also a hidden value in the unused bits of the a+d data. it'll be used by the - // VU program. - memcpy(&adgif.first_w, &gif_data.at(16 * (tex_idx * 5 + 0) + 12), 4); - - // Second adgif. Similar to the first, except the original data value is a texture ID. - u8 ra_tex1 = gif_data.at(16 * (tex_idx * 5 + 1) + 8); - u64 ra_tex1_val; - memcpy(&ra_tex1_val, &gif_data.at(16 * (tex_idx * 5 + 1)), 8); - ASSERT(ra_tex1 == (u8)GsRegisterAddress::TEX1_1); - ASSERT(ra_tex1_val == 0x120); // some flag - u32 original_tex; - memcpy(&original_tex, &gif_data.at(16 * (tex_idx * 5 + 1) + 8), 4); - // try remapping it - u32 new_tex = remap_texture(original_tex, map); - if (original_tex != new_tex) { - lg::info("map from 0x{:x} to 0x{:x}", original_tex, new_tex); - } - // texture the texture page/texture index, and convert to a PC port texture ID - u32 tpage = new_tex >> 20; - u32 tidx = (new_tex >> 8) & 0b1111'1111'1111; - u32 tex_combo = (((u32)tpage) << 16) | tidx; - // remember the texture id (may be invalid, will be checked later) - adgif.combo_tex = tex_combo; - // and the hidden value in the unused a+d - memcpy(&adgif.second_w, &gif_data.at(16 * (tex_idx * 5 + 1) + 12), 4); - // todo: figure out if this matters. maybe this is decal? - if (ra_tex0_val == 0x800000000) { - // lg::print("texture {} in {} has weird tex setting\n", tex->second.name, proto.name); - } - - // mipmap settings. we ignore, but get the hidden value - u8 ra_mip = gif_data.at(16 * (tex_idx * 5 + 2) + 8); - ASSERT(ra_mip == (u8)GsRegisterAddress::MIPTBP1_1); - memcpy(&adgif.third_w, &gif_data.at(16 * (tex_idx * 5 + 2) + 12), 4); - // who cares about the value - - // clamp settings. we care about these. no hidden value. - u8 ra_clamp = gif_data.at(16 * (tex_idx * 5 + 3) + 8); - ASSERT(ra_clamp == (u8)GsRegisterAddress::CLAMP_1); - u64 clamp; - memcpy(&clamp, &gif_data.at(16 * (tex_idx * 5 + 3)), 8); - adgif.clamp_val = clamp; - - // alpha settings. we care about these, but no hidden value - u8 ra_alpha = gif_data.at(16 * (tex_idx * 5 + 4) + 8); - ASSERT(ra_alpha == (u8)GsRegisterAddress::ALPHA_1); - u64 alpha; - memcpy(&alpha, &gif_data.at(16 * (tex_idx * 5 + 4)), 8); - adgif.alpha_val = alpha; frag_info.adgifs.push_back(adgif); } @@ -613,6 +704,15 @@ void update_proto_info(std::vector* out, } } + // normals + const auto& normal_data = proto.geometry[geo].tie_fragments[frag_idx].normals; + frag_info.normal_data_packed.resize(normal_data.size() / 4); + for (size_t ni = 0; ni < normal_data.size() / 4; ni++) { + for (int j = 0; j < 4; j++) { + frag_info.normal_data_packed[ni][j] = normal_data[ni * 4 + j]; + } + } + info.frags.push_back(std::move(frag_info)); } } @@ -869,6 +969,19 @@ void emulate_tie_prototype_program(std::vector& protos) { // again, we do it in two parts. The extra gif data gives us offsets, // The extra gif stuff is unpacked immediately after adgifs. Unpacked with v8 4. // the above adgif loop will run off the end and vf02 will have the first byte in it's w. + /* + ((skip-bp2 uint8 :offset-assert 0) + (skip-ips uint8 :offset-assert 1) + (gifbuf-skip uint8 :offset-assert 2) + (strips uint8 :offset-assert 3) + (target-bp1 uint8 :offset-assert 4) + (target-bp2 uint8 :offset-assert 5) + (target-ip1 uint8 :offset-assert 6) + (target-ip2 uint8 :offset-assert 7) + (target-bps uint8 :offset-assert 8) + (target-ips uint8 :offset-assert 9) + (is-generic uint8 :offset-assert 10) + */ ASSERT(frag.other_gif_data.size() > 1); // mtir vi_ind, vf02.w | nop // vi_ind will contain the number of drawing packets for this fragment. @@ -1276,7 +1389,7 @@ void emulate_tie_prototype_program(std::vector& protos) { void debug_print_info(const std::vector& out) { for (auto& proto : out) { lg::debug("[{:40}]", proto.name); - lg::debug(" generic: {}", proto.uses_generic); + lg::debug(" flag: {}", proto.proto_flag); lg::debug(" use count: {}", proto.instances.size()); lg::debug(" stiffness: {}", proto.stiffness); } @@ -1296,6 +1409,13 @@ int get_fancy_base(int draw1, int draw2) { return total; } +struct NrmDebug { + int bp1 = 0; + int bp2 = 0; + int ip1 = 0; + int ip2 = 0; +}; + void emulate_tie_instance_program(std::vector& protos) { for (auto& proto : protos) { // bool first_instance = true; @@ -1307,6 +1427,10 @@ void emulate_tie_instance_program(std::vector& protos) { int draw_2_count = 0; int ip_1_count = 0; + int normal_table_offset = 0; + + NrmDebug nd; + ///////////////////////////////////// // SETUP ///////////////////////////////////// @@ -1454,9 +1578,12 @@ void emulate_tie_instance_program(std::vector& protos) { vertex_info.tex.x() = tex_coord.x(); vertex_info.tex.y() = tex_coord.y(); vertex_info.tex.z() = tex_coord.z(); + vertex_info.envmap_tint_color = proto.tint_color; + vertex_info.nrm = frag.get_normal_if_present(normal_table_offset++); bool inserted = frag.vertex_by_dest_addr.insert({(u32)dest_ptr, vertex_info}).second; ASSERT(inserted); + nd.bp1++; if (reached_target) { past_target++; @@ -1495,6 +1622,8 @@ void emulate_tie_instance_program(std::vector& protos) { vertex_info.tex.x() = tex_coord.x(); vertex_info.tex.y() = tex_coord.y(); vertex_info.tex.z() = tex_coord.z(); + vertex_info.envmap_tint_color = proto.tint_color; + vertex_info.nrm = frag.get_normal_if_present(normal_table_offset++); // lg::print("double draw: {} {}\n", dest_ptr, dest2_ptr); bool inserted = frag.vertex_by_dest_addr.insert({(u32)dest_ptr, vertex_info}).second; @@ -1512,6 +1641,7 @@ void emulate_tie_instance_program(std::vector& protos) { } draw_2_count++; + nd.bp2++; } // setup @@ -1616,10 +1746,12 @@ void emulate_tie_instance_program(std::vector& protos) { vertex_info.tex.x() = tex_coord.x(); vertex_info.tex.y() = tex_coord.y(); vertex_info.tex.z() = tex_coord.z(); + vertex_info.envmap_tint_color = proto.tint_color; + vertex_info.nrm = frag.get_normal_if_present(normal_table_offset++); bool inserted = frag.vertex_by_dest_addr.insert({(u32)dest_ptr, vertex_info}).second; ASSERT(inserted); - + nd.ip1++; ip_1_count++; } @@ -1648,6 +1780,8 @@ void emulate_tie_instance_program(std::vector& protos) { vertex_info.tex.x() = tex_coord.x(); vertex_info.tex.y() = tex_coord.y(); vertex_info.tex.z() = tex_coord.z(); + vertex_info.envmap_tint_color = proto.tint_color; + vertex_info.nrm = frag.get_normal_if_present(normal_table_offset++); bool inserted = frag.vertex_by_dest_addr.insert({(u32)dest_ptr, vertex_info}).second; ASSERT(inserted); @@ -1659,6 +1793,7 @@ void emulate_tie_instance_program(std::vector& protos) { if (!first_iter) { ASSERT(inserted2); } + nd.ip2++; first_iter = false; ip_1_count++; } @@ -1668,6 +1803,13 @@ void emulate_tie_instance_program(std::vector& protos) { ASSERT(frag.vertex_by_dest_addr.size() == frag.expected_dverts); program_end:; + if (!frag.normal_data_packed.empty()) { + // check that we have a normal per point, if we have normals + size_t rounded_up_dvert = (nd.bp1 + nd.bp2 + nd.ip1 + nd.ip2) + 3; + rounded_up_dvert /= 4; + rounded_up_dvert *= 4; + ASSERT(rounded_up_dvert == frag.normal_data_packed.size()); + } // ASSERT(false); } @@ -1696,6 +1838,7 @@ void emulate_kicks(std::vector& protos) { ASSERT(frag.prog_info.adgif_offset_in_gif_buf_qw.size() == frag.adgifs.size()); const AdgifInfo* adgif_info = nullptr; + int adgif_info_idx = -1; int expected_next_tag = 0; // loop over strgifs @@ -1705,6 +1848,7 @@ void emulate_kicks(std::vector& protos) { // yep int idx = adgif_it - frag.prog_info.adgif_offset_in_gif_buf_qw.begin(); adgif_info = &frag.adgifs.at(idx); + adgif_info_idx = idx; // the next strgif should come 6 qw's after expected_next_tag += 6; adgif_it++; @@ -1734,6 +1878,7 @@ void emulate_kicks(std::vector& protos) { frag.strips.emplace_back(); auto& strip = frag.strips.back(); strip.adgif = *adgif_info; + strip.adgif_idx = adgif_info_idx; // loop over all the vertices the strgif says we'll have for (int vtx = 0; vtx < str_it->nloop; vtx++) { // compute the address of this vertex (stored after the strgif) @@ -1968,6 +2113,15 @@ void update_mode_from_alpha1(u64 val, DrawMode& mode) { // Cv = (Cs - Cd) * FIX + Cd ASSERT(reg.fix() == 64); mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_FIX_DST); + } else if (reg.a_mode() == GsAlpha::BlendMode::SOURCE && + reg.b_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED && + reg.c_mode() == GsAlpha::BlendMode::DEST && reg.d_mode() == GsAlpha::BlendMode::DEST) { + mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_DST_DST); + } else if (reg.a_mode() == GsAlpha::BlendMode::SOURCE && + reg.b_mode() == GsAlpha::BlendMode::SOURCE && + reg.c_mode() == GsAlpha::BlendMode::SOURCE && + reg.d_mode() == GsAlpha::BlendMode::SOURCE) { + mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_SRC_SRC_SRC); } else { @@ -1978,35 +2132,102 @@ void update_mode_from_alpha1(u64 val, DrawMode& mode) { } } +/*! + * Get the draw mode settings that are pre-set for the entire bucket and not controlled by adgif + * shaders + */ +DrawMode get_base_draw_test_mode_jak2(bool use_tra, tfrag3::TieCategory category) { + DrawMode mode; + mode.enable_ab(); + switch (category) { + case tfrag3::TieCategory::NORMAL: + case tfrag3::TieCategory::TRANS: + mode.enable_zt(); + mode.set_depth_test(GsTest::ZTest::GEQUAL); + if (use_tra) { + mode.enable_at(); + mode.set_aref(0x26); + mode.set_alpha_fail(GsTest::AlphaFail::KEEP); + mode.set_alpha_test(DrawMode::AlphaTest::GEQUAL); + } else { + mode.disable_at(); + } + break; + case tfrag3::TieCategory::WATER: + case tfrag3::TieCategory::WATER_ENVMAP: + case tfrag3::TieCategory::WATER_ENVMAP_SECOND_DRAW: + // (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest greater-equal)) + mode.enable_zt(); + mode.set_depth_test(GsTest::ZTest::GEQUAL); + mode.enable_at(); + mode.set_aref(0); + mode.set_alpha_fail(GsTest::AlphaFail::FB_ONLY); + mode.set_alpha_test(DrawMode::AlphaTest::NEVER); + break; + + case tfrag3::TieCategory::NORMAL_ENVMAP: + case tfrag3::TieCategory::TRANS_ENVMAP: + case tfrag3::TieCategory::NORMAL_ENVMAP_SECOND_DRAW: + case tfrag3::TieCategory::TRANS_ENVMAP_SECOND_DRAW: + mode.enable_zt(); + mode.set_depth_test(GsTest::ZTest::GEQUAL); + mode.disable_at(); + break; + + default: + ASSERT(false); + } + return mode; +} + /*! * Convert adgif info into a C++ renderer DrawMode. */ -DrawMode process_draw_mode(const AdgifInfo& info, bool use_atest, bool use_decal) { +DrawMode process_draw_mode(const AdgifInfo& info, + bool use_tra, + bool use_decal, + GameVersion version, + tfrag3::TieCategory category) { DrawMode mode; - // some of these are set up once as part of tie initialization - mode.set_alpha_test(DrawMode::AlphaTest::GEQUAL); - - // the atest giftag is set up at the end of the VU program. - if (use_atest) { - mode.enable_at(); - mode.set_aref(0x26); - mode.set_alpha_fail(GsTest::AlphaFail::KEEP); + if (version == GameVersion::Jak1) { + // some of these are set up once as part of tie initialization mode.set_alpha_test(DrawMode::AlphaTest::GEQUAL); + + // the atest giftag is set up at the end of the VU program. + if (use_tra) { + mode.enable_at(); + mode.set_aref(0x26); + mode.set_alpha_fail(GsTest::AlphaFail::KEEP); + mode.set_alpha_test(DrawMode::AlphaTest::GEQUAL); + } else { + mode.disable_at(); + } + // set up once. + mode.enable_depth_write(); + mode.enable_zt(); // :zte #x1 + mode.set_depth_test(GsTest::ZTest::GEQUAL); // :ztst (gs-ztest greater-equal)) + mode.disable_ab(); + mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST); } else { - mode.disable_at(); + mode = get_base_draw_test_mode_jak2(use_tra, category); } + if (use_decal) { mode.enable_decal(); } - // set up once. - mode.enable_depth_write(); - mode.enable_zt(); // :zte #x1 - mode.set_depth_test(GsTest::ZTest::GEQUAL); // :ztst (gs-ztest greater-equal)) - mode.disable_ab(); - mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST); - // the alpha matters - update_mode_from_alpha1(info.alpha_val, mode); + if (version == GameVersion::Jak1) { + // use alpha from adgif shader, that's what we did in the past (could be wrong?) + update_mode_from_alpha1(info.alpha_val, mode); + } else { + if (tfrag3::is_envmap_second_draw_category(category)) { + // envmap shader gets to control its own alpha + update_mode_from_alpha1(info.alpha_val, mode); + } else { + // non-envmap always get overriden (both the first draw of etie, and normal tie) + update_mode_from_alpha1(alpha_value_for_jak2_tie_or_etie_alpha_override(category), mode); + } + } // the clamp matters if (!(info.clamp_val == 0b101 || info.clamp_val == 0 || info.clamp_val == 1 || @@ -2020,6 +2241,222 @@ DrawMode process_draw_mode(const AdgifInfo& info, bool use_atest, bool use_decal return mode; } +DrawMode process_envmap_draw_mode(const AdgifInfo& info, + GameVersion version, + tfrag3::TieCategory category) { + // this is overwritten at log-in time. + // (set! (-> envmap-shader tex1) (new 'static 'gs-tex1 :mmag #x1 :mmin #x1)) + // (set! (-> envmap-shader clamp) (new 'static 'gs-clamp :wms (gs-tex-wrap-mode clamp) :wmt + // (gs-tex-wrap-mode clamp))) (set! (-> envmap-shader alpha) (new 'static 'gs-alpha :b #x2 :c #x1 + // :d #x1)) + auto mode = process_draw_mode(info, false, false, version, category); + mode.set_filt_enable(true); + mode.set_clamp_s_enable(true); + mode.set_clamp_t_enable(true); + mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_DST_DST); + return mode; +} + +TieCategoryInfo get_jak1_tie_category(u32 flags) { + TieCategoryInfo result; + result.category = tfrag3::TieCategory::NORMAL; + result.uses_envmap = flags & 2; + return result; +} + +u32 get_or_add_texture(u32 combo_tex, tfrag3::Level& lev, const TextureDB& tdb) { + // try looking it up in the existing textures that we have in the C++ renderer data. + // (this is shared with tfrag) + u32 idx_in_lev_data = UINT32_MAX; + for (u32 i = 0; i < lev.textures.size(); i++) { + if (lev.textures[i].combo_id == combo_tex) { + idx_in_lev_data = i; + break; + } + } + + if (idx_in_lev_data == UINT32_MAX) { + if (combo_tex == 0) { + // lg::warn("unhandled texture 0 case in extract_tie for {} {}", + // lev.level_name, + // proto.name); + idx_in_lev_data = 0; + } else { + // didn't find it, have to add a new one texture. + auto tex_it = tdb.textures.find(combo_tex); + if (tex_it == tdb.textures.end()) { + bool ok_to_miss = false; // for TIE, there's no missing textures. + if (ok_to_miss) { + // we're missing a texture, just use the first one. + tex_it = tdb.textures.begin(); + } else { + ASSERT_MSG( + false, + fmt::format("texture {} wasn't found. make sure it is loaded somehow. You may need " + "to " + "include ART.DGO or GAME.DGO in addition to the level DGOs for shared " + "textures. tpage is {}. id is {} (0x{:x})", + combo_tex, combo_tex >> 16, combo_tex & 0xffff, combo_tex & 0xffff)); + } + } + // add a new texture to the level data + idx_in_lev_data = lev.textures.size(); + lev.textures.emplace_back(); + auto& new_tex = lev.textures.back(); + new_tex.combo_id = combo_tex; + new_tex.w = tex_it->second.w; + new_tex.h = tex_it->second.h; + new_tex.debug_name = tex_it->second.name; + new_tex.debug_tpage_name = tdb.tpage_names.at(tex_it->second.page); + new_tex.data = tex_it->second.rgba_bytes; + } + } + return idx_in_lev_data; +} + +void handle_wind_draw_for_strip( + tfrag3::TieTree& tree, + std::unordered_map>& wind_draws_by_tex, + const std::vector>>& packed_vert_indices, + u32 idx_in_lev_data, + DrawMode mode, + const TieStrip& strip, + const TieInstanceInfo& inst, + const TieInstanceFragInfo& ifrag, + u32 wind_instance_idx, + u32 frag_idx, + u32 strip_idx) { + // okay, we now have a texture and draw mode, let's see if we can add to an existing... + auto existing_draws_in_tex = wind_draws_by_tex.find(idx_in_lev_data); + tfrag3::InstancedStripDraw* draw_to_add_to = nullptr; + if (existing_draws_in_tex != wind_draws_by_tex.end()) { + for (auto idx : existing_draws_in_tex->second) { + if (tree.instanced_wind_draws.at(idx).mode == mode) { + draw_to_add_to = &tree.instanced_wind_draws[idx]; + } + } + } + + if (!draw_to_add_to) { + // nope no existing draw for these settings, need to create a new draw + tree.instanced_wind_draws.emplace_back(); + wind_draws_by_tex[idx_in_lev_data].push_back(tree.instanced_wind_draws.size() - 1); + draw_to_add_to = &tree.instanced_wind_draws.back(); + draw_to_add_to->mode = mode; + draw_to_add_to->tree_tex_id = idx_in_lev_data; + } + + // now we have a draw, time to add vertices. We make a vertex "group" which is a group + // of vertices that the renderer can decide to not draw based on visibility data. + tfrag3::InstancedStripDraw::InstanceGroup igroup; + // needs to be associated with this instance. + igroup.vis_idx = inst.vis_id; // associate with the instance for culling + // number of vertices. The +1 is for the primitive restart index, which tells opengl + // that the triangle strip is done. + igroup.num = strip.verts.size() + 1; + // groups for instances also need the instance idx to grab the appropriate wind/matrix + // data. + igroup.instance_idx = wind_instance_idx; + draw_to_add_to->num_triangles += strip.verts.size() - 2; + // note: this is a bit wasteful to duplicate the xyz/stq. + tfrag3::PackedTieVertices::MatrixGroup grp; + grp.matrix_idx = -1; + grp.start_vert = packed_vert_indices.at(frag_idx).at(strip_idx).first; + grp.end_vert = packed_vert_indices.at(frag_idx).at(strip_idx).second; + tree.packed_vertices.matrix_groups.push_back(grp); + for (auto& vert : strip.verts) { + u16 color_index = 0; + if (vert.color_index_index == UINT32_MAX) { + color_index = 0; + } else { + color_index = ifrag.color_indices.at(vert.color_index_index); + ASSERT(vert.color_index_index < ifrag.color_indices.size()); + color_index += ifrag.color_index_offset_in_big_palette; + } + + size_t vert_idx = tree.packed_vertices.color_indices.size(); + tree.packed_vertices.color_indices.push_back(color_index); + draw_to_add_to->vertex_index_stream.push_back(vert_idx); + } + + // the primitive restart index + draw_to_add_to->vertex_index_stream.push_back(UINT32_MAX); + draw_to_add_to->instance_groups.push_back(igroup); +} + +void handle_draw_for_strip(tfrag3::TieTree& tree, + std::unordered_map>& static_draws_by_tex, + std::vector& category_draws, + const std::vector>>& packed_vert_indices, + DrawMode mode, + u32 idx_in_lev_data, + const TieStrip& strip, + const TieInstanceInfo& inst, + const TieInstanceFragInfo& ifrag, + u32 proto_idx, + u32 frag_idx, + u32 strip_idx, + u32 matrix_idx) { + // okay, we now have a texture and draw mode, let's see if we can add to an existing... + auto existing_draws_in_tex = static_draws_by_tex.find(idx_in_lev_data); + tfrag3::StripDraw* draw_to_add_to = nullptr; + if (existing_draws_in_tex != static_draws_by_tex.end()) { + for (auto idx : existing_draws_in_tex->second) { + if (idx < category_draws.size() && category_draws.at(idx).mode == mode && + category_draws.at(idx).tree_tex_id == idx_in_lev_data) { + draw_to_add_to = &category_draws[idx]; + } + } + } + + if (!draw_to_add_to) { + // nope, need to create a new draw + category_draws.emplace_back(); + static_draws_by_tex[idx_in_lev_data].push_back(category_draws.size() - 1); + draw_to_add_to = &category_draws.back(); + draw_to_add_to->mode = mode; + draw_to_add_to->tree_tex_id = idx_in_lev_data; + } + + // now we have a draw, time to add vertices + tfrag3::StripDraw::VisGroup vgroup; + ASSERT(inst.vis_id < UINT16_MAX); + vgroup.vis_idx_in_pc_bvh = inst.vis_id; // associate with the instance for culling + + // only bother with tie proto idx if we use it + if (tree.has_per_proto_visibility_toggle) { + ASSERT(proto_idx < UINT16_MAX); + vgroup.tie_proto_idx = proto_idx; + } + + vgroup.num_inds = strip.verts.size() + 1; // one for the primitive restart! + vgroup.num_tris = strip.verts.size() - 2; + draw_to_add_to->num_triangles += strip.verts.size() - 2; + tfrag3::PackedTieVertices::MatrixGroup grp; + grp.matrix_idx = matrix_idx; + grp.start_vert = packed_vert_indices.at(frag_idx).at(strip_idx).first; + grp.end_vert = packed_vert_indices.at(frag_idx).at(strip_idx).second; + + tree.packed_vertices.matrix_groups.push_back(grp); + tfrag3::StripDraw::VertexRun run; + run.vertex0 = tree.packed_vertices.color_indices.size(); + run.length = strip.verts.size(); + for (auto& vert : strip.verts) { + u16 color_index = 0; + if (vert.color_index_index == UINT32_MAX) { + color_index = 0; + } else { + color_index = ifrag.color_indices.at(vert.color_index_index); + ASSERT(vert.color_index_index < ifrag.color_indices.size()); + color_index += ifrag.color_index_offset_in_big_palette; + } + + tree.packed_vertices.color_indices.push_back(color_index); + } + draw_to_add_to->runs.push_back(run); + draw_to_add_to->vis_groups.push_back(vgroup); +} + /*! * Convert TieProtoInfo's to C++ renderer format */ @@ -2033,6 +2470,8 @@ void add_vertices_and_static_draw(tfrag3::TieTree& tree, std::unordered_map> static_draws_by_tex; std::unordered_map> wind_draws_by_tex; + std::array, tfrag3::kNumTieCategories> draws_by_category; + if (version > GameVersion::Jak1) { tree.has_per_proto_visibility_toggle = true; } @@ -2044,13 +2483,34 @@ void add_vertices_and_static_draw(tfrag3::TieTree& tree, tree.proto_names.push_back(proto.name); } - if (proto.uses_generic) { - // generic ties go through generic + TieCategoryInfo info; + switch (version) { + case GameVersion::Jak1: + info = get_jak1_tie_category(proto.proto_flag); + break; + case GameVersion::Jak2: + info = get_jak2_tie_category(proto.proto_flag); + break; + default: + ASSERT_NOT_REACHED(); + } + + if (info.uses_envmap && version == GameVersion::Jak1) { + // envmap ties go through generic (for now...) continue; } + // bool using_wind = true; // hack, for testing bool using_wind = proto.stiffness != 0.f; + bool using_envmap = info.uses_envmap; + ASSERT(using_envmap == proto.envmap_adgif.has_value()); + DrawMode envmap_drawmode; + if (using_envmap) { + envmap_drawmode = process_envmap_draw_mode(proto.envmap_adgif.value(), version, + info.envmap_second_draw_category); + } + // create the model first std::vector>> packed_vert_indices; for (size_t frag_idx = 0; frag_idx < proto.frags.size(); frag_idx++) { @@ -2061,7 +2521,9 @@ void add_vertices_and_static_draw(tfrag3::TieTree& tree, int start = tree.packed_vertices.vertices.size(); for (auto& vert : strip.verts) { tree.packed_vertices.vertices.push_back( - {vert.pos.x(), vert.pos.y(), vert.pos.z(), vert.tex.x(), vert.tex.y()}); + {vert.pos.x(), vert.pos.y(), vert.pos.z(), vert.tex.x(), vert.tex.y(), vert.nrm.x(), + vert.nrm.y(), vert.nrm.z(), vert.envmap_tint_color.x(), vert.envmap_tint_color.y(), + vert.envmap_tint_color.z(), vert.envmap_tint_color.w()}); // TODO: check if this means anything. // ASSERT(vert.tex.z() == 1.); } @@ -2095,177 +2557,38 @@ void add_vertices_and_static_draw(tfrag3::TieTree& tree, // loop over triangle strips within the fragment for (size_t strip_idx = 0; strip_idx < frag.strips.size(); strip_idx++) { auto& strip = frag.strips[strip_idx]; - // what texture are we using? - u32 combo_tex = strip.adgif.combo_tex; - - // try looking it up in the existing textures that we have in the C++ renderer data. - // (this is shared with tfrag) - u32 idx_in_lev_data = UINT32_MAX; - for (u32 i = 0; i < lev.textures.size(); i++) { - if (lev.textures[i].combo_id == combo_tex) { - idx_in_lev_data = i; - break; - } - } - - if (idx_in_lev_data == UINT32_MAX) { - if (combo_tex == 0) { - lg::warn("unhandled texture 0 case in extract_tie for {} {}", lev.level_name, - proto.name); - idx_in_lev_data = 0; - } else { - // didn't find it, have to add a new one texture. - auto tex_it = tdb.textures.find(combo_tex); - if (tex_it == tdb.textures.end()) { - bool ok_to_miss = false; // for TIE, there's no missing textures. - if (ok_to_miss) { - // we're missing a texture, just use the first one. - tex_it = tdb.textures.begin(); - } else { - ASSERT_MSG( - false, - fmt::format( - "texture {} wasn't found. make sure it is loaded somehow. You may need " - "to " - "include ART.DGO or GAME.DGO in addition to the level DGOs for shared " - "textures. tpage is {}. id is {} (0x{:x})", - combo_tex, combo_tex >> 16, combo_tex & 0xffff, combo_tex & 0xffff)); - } - } - // add a new texture to the level data - idx_in_lev_data = lev.textures.size(); - lev.textures.emplace_back(); - auto& new_tex = lev.textures.back(); - new_tex.combo_id = combo_tex; - new_tex.w = tex_it->second.w; - new_tex.h = tex_it->second.h; - new_tex.debug_name = tex_it->second.name; - new_tex.debug_tpage_name = tdb.tpage_names.at(tex_it->second.page); - new_tex.data = tex_it->second.rgba_bytes; - } - } + u32 idx_in_lev_data = get_or_add_texture(strip.adgif.combo_tex, lev, tdb); // determine the draw mode - DrawMode mode = - process_draw_mode(strip.adgif, frag.prog_info.misc_x == 0, frag.has_magic_tex0_bit); + DrawMode mode = process_draw_mode(strip.adgif, frag.prog_info.misc_x == 0, + frag.has_magic_tex0_bit, version, info.category); if (using_wind) { - // okay, we now have a texture and draw mode, let's see if we can add to an existing... - auto existing_draws_in_tex = wind_draws_by_tex.find(idx_in_lev_data); - tfrag3::InstancedStripDraw* draw_to_add_to = nullptr; - if (existing_draws_in_tex != wind_draws_by_tex.end()) { - for (auto idx : existing_draws_in_tex->second) { - if (tree.instanced_wind_draws.at(idx).mode == mode) { - draw_to_add_to = &tree.instanced_wind_draws[idx]; - } - } - } - - if (!draw_to_add_to) { - // nope no existing draw for these settings, need to create a new draw - tree.instanced_wind_draws.emplace_back(); - wind_draws_by_tex[idx_in_lev_data].push_back(tree.instanced_wind_draws.size() - 1); - draw_to_add_to = &tree.instanced_wind_draws.back(); - draw_to_add_to->mode = mode; - draw_to_add_to->tree_tex_id = idx_in_lev_data; - } - - // now we have a draw, time to add vertices. We make a vertex "group" which is a group - // of vertices that the renderer can decide to not draw based on visibility data. - tfrag3::InstancedStripDraw::InstanceGroup igroup; - // needs to be associated with this instance. - igroup.vis_idx = inst.vis_id; // associate with the instance for culling - // number of vertices. The +1 is for the primitive restart index, which tells opengl - // that the triangle strip is done. - igroup.num = strip.verts.size() + 1; - // groups for instances also need the instance idx to grab the appropriate wind/matrix - // data. - igroup.instance_idx = wind_instance_idx; - draw_to_add_to->num_triangles += strip.verts.size() - 2; - // note: this is a bit wasteful to duplicate the xyz/stq. - tfrag3::PackedTieVertices::MatrixGroup grp; - grp.matrix_idx = -1; - grp.start_vert = packed_vert_indices.at(frag_idx).at(strip_idx).first; - grp.end_vert = packed_vert_indices.at(frag_idx).at(strip_idx).second; - tree.packed_vertices.matrix_groups.push_back(grp); - for (auto& vert : strip.verts) { - u16 color_index = 0; - if (vert.color_index_index == UINT32_MAX) { - color_index = 0; - } else { - color_index = ifrag.color_indices.at(vert.color_index_index); - ASSERT(vert.color_index_index < ifrag.color_indices.size()); - color_index += ifrag.color_index_offset_in_big_palette; - } - - size_t vert_idx = tree.packed_vertices.color_indices.size(); - tree.packed_vertices.color_indices.push_back(color_index); - draw_to_add_to->vertex_index_stream.push_back(vert_idx); - } - - // the primitive restart index - draw_to_add_to->vertex_index_stream.push_back(UINT32_MAX); - draw_to_add_to->instance_groups.push_back(igroup); - + handle_wind_draw_for_strip(tree, wind_draws_by_tex, packed_vert_indices, + idx_in_lev_data, mode, strip, inst, ifrag, wind_instance_idx, + frag_idx, strip_idx); } else { - // okay, we now have a texture and draw mode, let's see if we can add to an existing... - auto existing_draws_in_tex = static_draws_by_tex.find(idx_in_lev_data); - tfrag3::StripDraw* draw_to_add_to = nullptr; - if (existing_draws_in_tex != static_draws_by_tex.end()) { - for (auto idx : existing_draws_in_tex->second) { - if (tree.static_draws.at(idx).mode == mode) { - draw_to_add_to = &tree.static_draws[idx]; - } - } + // also add the envmap draw + if (info.uses_envmap) { + // first pass: normal draw mode, envmap bucket, normal draw list + handle_draw_for_strip(tree, static_draws_by_tex, + draws_by_category.at((int)info.category), packed_vert_indices, + mode, idx_in_lev_data, strip, inst, ifrag, proto_idx, frag_idx, + strip_idx, matrix_idx); + u32 envmap_tex_idx = + get_or_add_texture(proto.envmap_adgif.value().combo_tex, lev, tdb); + + // second pass envmap draw mode, in envmap bucket, envmap-specific draw list + handle_draw_for_strip(tree, static_draws_by_tex, + draws_by_category.at((int)info.envmap_second_draw_category), + packed_vert_indices, envmap_drawmode, envmap_tex_idx, strip, + inst, ifrag, proto_idx, frag_idx, strip_idx, matrix_idx); + } else { + handle_draw_for_strip(tree, static_draws_by_tex, + draws_by_category.at((int)info.category), packed_vert_indices, + mode, idx_in_lev_data, strip, inst, ifrag, proto_idx, frag_idx, + strip_idx, matrix_idx); } - - if (!draw_to_add_to) { - // nope, need to create a new draw - tree.static_draws.emplace_back(); - static_draws_by_tex[idx_in_lev_data].push_back(tree.static_draws.size() - 1); - draw_to_add_to = &tree.static_draws.back(); - draw_to_add_to->mode = mode; - draw_to_add_to->tree_tex_id = idx_in_lev_data; - } - - // now we have a draw, time to add vertices - tfrag3::StripDraw::VisGroup vgroup; - ASSERT(inst.vis_id < UINT16_MAX); - vgroup.vis_idx_in_pc_bvh = inst.vis_id; // associate with the instance for culling - - // only bother with tie proto idx if we use it - if (tree.has_per_proto_visibility_toggle) { - ASSERT(proto_idx < UINT16_MAX); - vgroup.tie_proto_idx = proto_idx; - } - - vgroup.num_inds = strip.verts.size() + 1; // one for the primitive restart! - vgroup.num_tris = strip.verts.size() - 2; - draw_to_add_to->num_triangles += strip.verts.size() - 2; - tfrag3::PackedTieVertices::MatrixGroup grp; - grp.matrix_idx = matrix_idx; - grp.start_vert = packed_vert_indices.at(frag_idx).at(strip_idx).first; - grp.end_vert = packed_vert_indices.at(frag_idx).at(strip_idx).second; - tree.packed_vertices.matrix_groups.push_back(grp); - tfrag3::StripDraw::VertexRun run; - run.vertex0 = tree.packed_vertices.color_indices.size(); - run.length = strip.verts.size(); - for (auto& vert : strip.verts) { - u16 color_index = 0; - if (vert.color_index_index == UINT32_MAX) { - color_index = 0; - } else { - color_index = ifrag.color_indices.at(vert.color_index_index); - ASSERT(vert.color_index_index < ifrag.color_indices.size()); - color_index += ifrag.color_index_offset_in_big_palette; - } - - tree.packed_vertices.color_indices.push_back(color_index); - // draw_to_add_to->vertex_index_stream.push_back(vert_idx); - } - draw_to_add_to->runs.push_back(run); - // draw_to_add_to->vertex_index_stream.push_back(UINT32_MAX); - draw_to_add_to->vis_groups.push_back(vgroup); } } } @@ -2274,10 +2597,20 @@ void add_vertices_and_static_draw(tfrag3::TieTree& tree, // sort draws by texture. no idea if this really matters, but will reduce the number of // times the renderer changes textures. it at least makes the rendererdoc debugging easier. - std::stable_sort(tree.static_draws.begin(), tree.static_draws.end(), - [](const tfrag3::StripDraw& a, const tfrag3::StripDraw& b) { - return a.tree_tex_id < b.tree_tex_id; - }); + for (auto& draws : draws_by_category) { + std::stable_sort(draws.begin(), draws.end(), + [](const tfrag3::StripDraw& a, const tfrag3::StripDraw& b) { + return a.tree_tex_id < b.tree_tex_id; + }); + } + + ASSERT(tree.static_draws.empty()); + tree.category_draw_indices[0] = 0; + for (int i = 0; i < tfrag3::kNumTieCategories; i++) { + tree.static_draws.insert(tree.static_draws.end(), draws_by_category[i].begin(), + draws_by_category[i].end()); + tree.category_draw_indices[i + 1] = tree.static_draws.size(); + } } /*! diff --git a/docs/progress-notes/jak2/etie.md b/docs/progress-notes/jak2/etie.md new file mode 100644 index 000000000..deb773dca --- /dev/null +++ b/docs/progress-notes/jak2/etie.md @@ -0,0 +1,2520 @@ +# ETIE +environment mapped tie. + +This document is a set of notes I took while reversing the renderer and porting to C++. + +My hope is that it can be used as a bit of a guide for OpenGOAL renderer implementation. + +# Part 0: From Jak 1 Tie +Before starting, I looked over Jak 1's TIE stuff. We know ETIE is at least kinda similar because some stuff sorta renders if we pretend it's normal TIE. + +## Terminology +- Level: this is an in-game level. Precomputed visibility is computed per level. In code, `level` generally refers to the relatively small metadata describing the current state (which level is loaded, info about the level). The actual _data_ is stored in the "BSP file". This name is a bit imprecise because the BSP is only used for precomupted visibility - stuff like level geometry/collision isn't stored in an actual BSP data structure. But, all this data is accessible through a type called `bsp-header`. This type is at the beginning of every level file, and provides pointers to all the data stored within the level. For TIE, this doesn't really matter. I'll probably use level/BSP somewhat interchangably. The actual TIE data is stored in the BSP. + +- Tree: Each level has some small number of TIE "tree"s. Trees contain "Prototypes", which are models (eg: a tree, a rock); "Instances", which are placements of a model (eg: place Rock_A at location X), and a BVH tree. The BVH tree stores the position of each instance and is used to speed up the "is this instance in the view frustum" check. The details don't really matter, but they use a ball tree with a branching factor of at most 8. Usually the number of trees is small (Sandover has 3). The reason to split into multiple trees is unknown. The limit may due to a maximum tree depth. + +- Bucket: The rendering is done partially on the EE and partially on the VU. Like all drawing, this is double buffered -- on frame 1, the engine produces DMA data that is processed by VIF1/VU1 on frame 2. This DMA data is in a linked list, divided into "buckets". The purpose of buckets is to allow them to reorder DMA data. After buckets are complete, they are stitched together in the order specified in the `bucket-it` enum. + +Here's an example of how the reordering might be useful. The renderer processes one tree at a time, possibly in this order +- data from Level 1's Tie Tree +- data from Level 2's Tie Tree + +But, the correct draw order should be: +- background stuff from Level 1 +- background stuff from Level 2 +- Transparent stuff from level 1 +- Transparent stuff from level 2 + +The solution is to have a separate background and transparent bucket. When processing each level, the renderer will put stuff in both buckets. Then, once everything is rendered, the buckets are stitched in the appropriate order. This stitching process is fast because it's just a linked list. This bucketing trick is used to insert texture uploads at the right time as well. Jak 2 has a total of 326 buckets. + +Here's an example of what might be contained in the DMA data within a bucket (simplified): + +- Instructions to upload a program to VU1 instruction memory +- Instruction to upload some per-prototype data to VU1 (vertices) +- Instructions to upload per-instance data to VU1 +- Instruction to run a VU1 microprogram +- Instruction to upload another per-instance data to VU1 +- Instruction to run a VU1 microprogram +- .... + +These VU1 programs will build a list of stuff to draw in the format of the GS, then use the XGKICK instruction to tell the GS to start rendering the list. + +From what I remember, the per-prototype data is the vertices (their position, texture coordinates). The per-instance data is the location of the instance, and the instance colors (each instance has a 256?? color palette). + +Another detail is that uploading data to the VU1 is processed by VIF1. This can be configured to do relatively simple "unpack" operations. + +And of course, because this is ND, _everything_ is double buffered. At any point in time: +- There will be an upload from DMA to VU1 data memory +- VU1 will be reading previously uploaded data, transforming vertices, and writing a list of GS data +- GS will be reading a previously generated list from VU1 memory, drawing pixels to the framebuffer + +(and yes, they double buffer both the per-prototype and per-instance data) + +## Visibility Info +There are two visibility systems: precomputed occlusion culling data, and frustum culling. + +Precomputing visibility is stored based on the camera position. This works differently in jak 2 and jak 1, but the basic idea is that a visibility string can be looked up based on camera position. This contains a bit for each drawable-id (up to 2^14 per level). It tells you "if the camera is in this area, here are all the things you could possible see". + +The frustum culling just checks if something is within the field of view. It's computed on each frame. + +Note that the actual implementation `draw-node-cull` does some cool tricks to efficient combine the precomputed data with the tree-traversal to produce the final visibility string. The final data is just a string. + +## Storage format in tree data + +There's a flat array of "instance"s. Each instance stores its position/orientation (as a compressed matrix) some parameters, and has a pointer back to "prototype". (Note that due to some clever arrangement, this flat array is also part of the BVH tree) + +Each prototype has a few different versions depending on the LOD. These versions + + +# Part 1: Reading code + +This is always the first step. The system of levels/tree/instances/prototypes and all the different lists that are built are super confusing, but we can't write any code until we understand all the details. We need to answer questions like "are the trees that output to multiple buckets"? + + +TIE drawing procedure. All TIEs are drawn from the display process + +Display process loop: +``` + (while *run* + (display-loop-main gp-1) + (with-profiler 'actors *profile-actors-color* + (suspend) + ) + ) +``` +In `display-loop-main`: +``` +(with-profiler 'draw-hook *profile-draw-hook-color* + (*draw-hook*) + ) +``` +In `drawable.gc`: +``` +(defun main-draw-hook () + (real-main-draw-hook) + (none) + ) + +(define *draw-hook* main-draw-hook) +``` +in `real-main-draw-hook`: +``` + (with-profiler 'background *profile-background-color* + ;; Run the background renderers! + + ;; first, reset the background-work + (init-background) + + ;; next, collect all levels that are registered with the engine + ;; this will call the drawable system's draw method on the levels which adds all + ;; trees known to the background system to *background-work*. + (execute-connections *background-draw-engine* (-> *display* frames (-> *display* on-screen))) + + ;; execute all background drawing + (reset! (-> *perf-stats* data (perf-stat-bucket background))) + (finish-background) + (read! (-> *perf-stats* data (perf-stat-bucket background))) + + ;; update VU stats for background draw. + (update-wait-stats (-> *perf-stats* data (perf-stat-bucket background)) + (-> *background-work* wait-to-vu0) + (the-as uint 0) + (the-as uint 0) + ) + ) +``` +in `finish-background`: +``` + (when (nonzero? (-> *background-work* tie-tree-count)) + (set! (-> *instance-tie-work* tod-env-color quad) (-> *time-of-day-context* current-env-color quad)) + (with-profiler 'instance-tie *profile-instance-tie-color* + ;; loop over trees from all level + (dotimes (s5-10 (-> *background-work* tie-tree-count)) + (let ((s4-8 (-> *background-work* tie-levels s5-10))) + (let ((a2-29 (-> s4-8 bsp))) + (when (!= s4-8 gp-6) + (set! (-> *instance-tie-work* min-dist x) 4095996000.0) + (upload-vis-bits s4-8 gp-6 a2-29) + (set! gp-6 s4-8) + ) + ) + (set! *draw-index* (-> s4-8 draw-index)) + (set! (-> *prototype-tie-work* mood) (-> s4-8 mood-context)) + (set-background-regs! s4-8) + (set-tie-quard-planes! s4-8) + (tie-scissor-make-perspective-matrix + (-> *instance-tie-work* tie-scissor-perspective-matrix) + (if (-> s4-8 info use-camera-other) + (-> *math-camera* camera-temp-other) + (-> *math-camera* camera-temp) + ) + ) + (draw-drawable-tree-instance-tie (-> *background-work* tie-trees s5-10) s4-8) + ) + (set! (-> *background-work* tie-generic s5-10) (the-as basic (-> *prototype-tie-work* generic-next))) + (set! (-> *background-work* tie-generic-trans s5-10) + (the-as basic (-> *prototype-tie-work* generic-trans-next)) + ) + ) + ) + ) +``` + +The `draw-drawable-tree-instance-tie` function does dma generation for an entire "tree". The "tree" refers to the visibility structure. At the top of `draw-drawable-tree-instance-tie`, `draw-node-cull` is used to determine visibility for all instances in the tree, and is stashed in the scratchpad. After this, there are 3 main functions: +- `draw-inline-array-instance-tie` +- `draw-inline-array-prototype-tie-asm` +- `instance-tie-patch-buckets` +and there's a much later call to `tie-vu1-init-buffers` in `display-frame-finish`. + +The `draw-inline-array-instance-tie` function builds lists per-prototype of instances. It is responsible for generating per-instance stuff, like computing distances and picking the right lod settings, and setting up DMA for per-instance colors. + +The `draw-inline-array-prototype-tie-asm` function then patches the per-proto lists into a single large list, inserting DMA stuff for uploading the proto. (actually, there's a list per renderer mode) + +The `instance-tie-patch-buckets` function links the per-renderer lists to bucket lists, optionally skipping modes disabled from the debug menu. + +The `tie-vu1-init-buffers` runs much later. It checks to see if TIE buckets are empty, and if not, adds the DMA to upload the VU1 program. There are a lot of tie buckets (13 modes x 6 levels), so it seems like a good idea to skip the program upload for unused ones. + +## Buckets/modes/levels/trees... + +There is a bucket per level and per mode. All tie bucket start with `tie`. For example `tie-esw-l2-water` is for mode "environment mapped, scissor, water" and level 2. The "water" part means a few things: +- implies something about draw order compared to other buckets (for transparency) +- possibly something about draw settings. (eg: water has alpha blending on, others don't) +- textures that are available (water tpage of level 2) + +Each level can have multiple trees. The reason for multiple trees isn't super clear, but it could be limits of the visibility system. (seems to actually be number of protos?) Unlike tfrag, a single tie tree can output to different modes. + +The `draw-drawable-tree-instance-tie` function runs per-tree. It can output to any tie bucket for the level (likely multiple). In theory, the `draw-index` stuff could be used to make it to output to another level's buckets, though I suspect this isn't used. + +The `tie-vu1-init-buffers` runs once per frame. So it can see if any of the `draw-drawable-tree-instance-tie`'s wrote to each bucket. + + +## What we need to find out: +- what is the math for envmap? +- how the envmap shader is set? +- how envmap settings are set (fade, tint) +- how normals are computed? +- what are the different TIE modes? (envmap, others?) + +### Math: +We know that: +- Jak 2's generic-merc can render emerc data identically (happens in cutscenes when character is near the border) +- Jak 2's emerc works the same as Jak 1's generic-merc +- Jak 1's generic merc uses the same code as Jak 1's generic tie for envmapping (pretty sure...). + +Unfortunately, we don't know if Jak 2's etie differs from these renderers. Jak 2 never uses generic-tie. It would make sense for etie to be the same as emerc/generic-merc, so you can place merc/tie stuff next to each other and it looks the same. But who knows - they could have done something like negate the texture coordinates because it was easier to write the VU program this way, and they don't have to worry about compatibility between generic-tie/etie. + +So it would be ideal to get the envmap math out of the ETIE vu program. Or just guess it's the same as emerc and check carefully with PCSX2. + +### Shader: +There's an "adgif-shader" for the envmap. This is the texture for the reflection. I'd guess there's a way for models to specify their own adgif-shader, or fall back to the default envmap. This will need to be added to `extract_tie.cpp`. + +### Settings: +Envmap has relatively few settings: fade/tint. The fade is used to fade out the effect as you move away. The tint is used to apply time of day lighting to envmap. Unlike the vertex colors, there's a single rgba for the entire envmap draw. Need to find this and pass it to the C++ rendering code. It changes on each frame with the time of day. + +### Normals: +Tie normally doesn't need normals. However, computing reflection directions needs normals. + +I'm not sure how these normals are generated. They could compute per-triangle face normals and use that, or they could store normals. + +In `generic-tie`, there's a `generic-tie-normal`, using 8-bit integers. It's not clear if these are included in the model data, or if they are generated. The use of only 8-bits makes me think that they are stored, though it might also be packed so they can be easily snuck through the generic pipeline. + +### Modes: +Somehow, the drawing code builds separate lists for different bucket types. The types are: +- `tie`: buckets like `tie-lX-tfrag` +- `trans`: buckets like `tie-t-lX-alpha` +- `water`: buckets like `tie-w-lX-water` +- `scissor`: buckets like `tie-s-lX-tfrag` +- `scissor-trans`: buckets like `tie-st-lX-alpha` +- `scissor-water`: buckets like `tie-sw-lX-water` +- `envmap`: buckets like `etie-lX-tfrag` +- `envmap-trans`: buckets like `etie-t-lX-alpha` +- `envmap-water`: buckets like `etie-w-lX-water` +- `envmap-scissor`: buckets like `etie-s-lX-tfrag` +- `envmap-scissor-trans`: buckets like `etie-st-lX-alpha` +- `envmap-scissor-water`: buckets like `etie-sw-lX-water` +- `generic`: (unused??) +- `vanish`: buckets like `tie-v-lX-tfrag` + +Currently, stuff is set up to do a single TIE per level, using the original game's `tie` bucket. However, this will need to be split up to render the appropriate bucket data at the appropriate time. + +Bucket order: +``` + (tex-l0-tfrag 7) ;; level 0 tex + (tfrag-l0-tfrag 8) ;; tfrag + (tie-l0-tfrag 9) ;; tie + (etie-l0-tfrag 10) ;; tie + (tfrag-s-l0-tfrag 11) ;; tfrag + (tie-s-l0-tfrag 12) ;; tie + (etie-s-l0-tfrag 13) ;; tie + (merc-l0-tfrag 14) ;; merc + (emerc-l0-tfrag 15) ;; emerc + (gmerc-l0-tfrag 16) ;; mercneric + (tie-v-l0-tfrag 17) ;; tie + ;; ... + (tex-l0-alpha 127) ;; tex + (tfrag-t-l0-alpha 128) ;; tfrag + (tie-t-l0-alpha 129) ;; tie + (etie-t-l0-alpha 130) ;; tie + (merc-l0-alpha 131) ;; merc + (emerc-l0-alpha 132) ;; emerc + (gmerc-l0-alpha 133) ;; mercneric + (tfrag-st-l0-alpha 134) ;; tfrag + (tie-st-l0-alpha 135) ;; tie + (etie-st-l0-alpha 136) ;; tie + ;; ... + (tex-l0-water 252) ;; tex + (merc-l0-water 253) ;; merc + (gmerc-l0-water 254) ;; mercneric + (tfrag-w-l0-water 255) ;; tfrag + (tie-w-l0-water 256) + (etie-w-l0-water 257) + (tie-sw-l0-water 258) + (tfrag-ws-l0-water 259) ;; tfrag + (etie-sw-l0-water 260) +``` +this looks like it's safe to have three groups per level on PC. + +- `tfrag` page: `tie`/`etie`. The vanish bucket is going to be ignored. +- `alpha` page: `tie-t`/`etie-t` +- `water` page: `tie-w`, `etie-w` + +Note that is is always safe to move things in between the scissor/normal versions of a bucket because this happens all the time at runtime and it's not noticable. + +I have no idea why they sometimes put the merc/gmerc in between. But it must be okay to swap these around because stuff jumps between normal and scissor all the time. + + +# The Envmap Shader +I can't find any envmap shader other than the `*default-envmap-shader*`. + +I looked at the textures for "mountain", and I do see an envmap texture, but it's in the `pris` folder, and tie never seems to log it in. (so I think it's for merc) + +All adgif shaders need to be "logged in" to be linked to a texture, and I can't find log-in code for envmap shaders, so I think this means that etie always envmaps with the default texture? + +(edit from after: this was wrong.) + +# ETIE stuff + +Setup is pretty reasonable, the "constants" block is: +``` +(deftype etie-consts (structure) + ((gifbufs qword :inline :offset-assert 0) + (adgif gs-gif-tag :inline :offset-assert 16) + (alpah gs-adcmd :inline) + (strgif gs-gif-tag :inline :offset-assert 48) + (envgif gs-gif-tag :inline :offset-assert 64) + (envmap adgif-shader :inline :offset-assert 80) + (pers0 vector :inline :offset-assert 160) + (pers1 vector :inline :offset-assert 176) + ) + :method-count-assert 9 + :size-assert #xc0 + :flag-assert #x9000000c0 + ) +``` +These "constants" get uploaded to VU1 memory once per frame, and are shared between all rendered objects. So it's typically stuff like the the camera perspective transformation. + + +The `gifbufs` is the usual "low bits of floats storing a memory address" nonsense: +``` + (let ((v1-3 510)) + (set! (-> arg0 gifbufs vector4w x) (gpr->fpr (+ #x4b000000 v1-3))) + ) + (let ((v1-5 249)) + (set! (-> arg0 gifbufs vector4w y) (gpr->fpr (+ #x4b000000 v1-5))) + ) + (let ((v1-7 510)) + (set! (-> arg0 gifbufs vector4w z) (gpr->fpr (+ #x4b000000 v1-7))) + ) + (let ((v1-9 249)) + (set! (-> arg0 gifbufs vector4w w) (gpr->fpr (+ #x4b000000 v1-9))) + ) +``` +The name `gifbuf` makes me think they store the memory address of the VU1 program's output GIF buffer. (GIF = the data sent to the GS). This is double buffered, and they need some way to quickly swap between two different values. The VU1 instruction set doesn't support many integer operations, so doing dumb tricks with the float registers can be worth it. These magic float contstants: +- Can be added together, and their lower bits will add, as if they were integers. (assuming you stay within the range of the VU1 memory address) +- Can be "toggled" with a single `mr32` instruction (`[a b a b] -> [b a b a]`) +- Can be converted to an integer register with a single instruction + + +The `alpha` mode is set by the call to `etie-init-engine`. We'll have to track this down later, but we know for now that the alpha mode might be different per-bucket. This alpha will be used to set the ALPHA register of the GS by the VU program. (the exact details of when this alpha is used is not known yet) +``` + (set! (-> arg0 alpah data) (the-as uint arg1)) + (set! (-> arg0 alpah cmds) (gs-reg64 alpha-1)) +``` + +the giftags for drawing: +``` + (set! (-> arg0 strgif tag) + (new 'static 'gif-tag64 + :pre #x1 + :prim (new 'static 'gs-prim :prim (gs-prim-type tri-strip) :iip #x1 :tme #x1 :fge #x1 :abe #x1) + :nreg #x3 + ) + ) + (set! (-> arg0 envgif tag) + (new 'static 'gif-tag64 + :pre #x1 + :prim (new 'static 'gs-prim :prim (gs-prim-type tri-strip) :iip #x1 :tme #x1 :abe #x1) + :nreg #x3 + ) + ) + ) +;; snip + (set! (-> arg0 strgif regs) + (new 'static 'gif-tag-regs :regs0 (gif-reg-id st) :regs1 (gif-reg-id rgbaq) :regs2 (gif-reg-id xyzf2)) + ) + (set! (-> arg0 envgif regs) + (new 'static 'gif-tag-regs :regs0 (gif-reg-id st) :regs1 (gif-reg-id rgbaq) :regs2 (gif-reg-id xyzf2)) + ) +``` +Nothing surprising. The first part tells us it uses tri-strips. The `strgif` is likely the stuff under the envmap, and `envgif` is likely the shiny part. + +The `regs` tells us that each vertex will have texture coordinates, colors, and position in that order. + + +The perspective stuff: +``` + (let* ((mc *math-camera*) + (pmat (-> mc perspective)) + (inv-fog (/ 1.0 (-> mc pfog0))) + (hvdf-off (-> mc hvdf-off)) + (xx (-> pmat vector 0 x)) + (yy (-> pmat vector 1 y)) + (zz (-> pmat vector 2 z)) + (zw (-> pmat vector 2 w)) + (wz (-> pmat trans z)) + ) + (let ((f4-1 (* zw inv-fog))) + (set! (-> arg0 pers0 x) (* f4-1 (-> hvdf-off x))) + (set! (-> arg0 pers0 y) (* f4-1 (-> hvdf-off y))) + (set! (-> arg0 pers0 z) (+ (* f4-1 (-> hvdf-off z)) zz)) + (set! (-> arg0 pers0 w) f4-1) + ) + (set! (-> arg0 pers1 x) xx) + (set! (-> arg0 pers1 y) yy) + (set! (-> arg0 pers1 z) wz) + ) + (set! (-> arg0 pers1 w) 0.0) +``` + +Interesting notes: +- they don't have anything that includes the position of the camera. This means that the per-instance data contains a `cam_T_inst` transformation, not a `world_T_inst` one. (details don't matter, but the VU1 program can be a bit more optimized if the transform is given as separate affine + perspective) +- they have some weird thing that combines hvdf/perspective. On first glance, it doens't seem the same as EMERC. This is kinda confusing because I'd guess that emerc/etie would work the same way. I am a little suspicious that they end up being the same in the end. + +The envmap stuff: +``` + (let* ((v1-44 *default-envmap-shader*) + (a0-2 (-> arg0 envmap)) + (a1-8 (-> v1-44 quad 0 quad)) + (a2-5 (-> v1-44 quad 1 quad)) + (a3-0 (-> v1-44 quad 2 quad)) + (t0-0 (-> v1-44 quad 3 quad)) + (v1-45 (-> v1-44 quad 4 quad)) + ) + (set! (-> a0-2 quad 0 quad) a1-8) + (set! (-> a0-2 quad 1 quad) a2-5) + (set! (-> a0-2 quad 2 quad) a3-0) + (set! (-> a0-2 quad 3 quad) t0-0) + (set! (-> a0-2 quad 4 quad) v1-45) + ) +``` +they're just copying an "adgif shader". An "adgif shader" is simply a chunk of data that tells the GS texturing/blending modes. The "ad" stands for "address+data", which is a mode for controlling the GS that consists of sending packets with an address and some data. So the exact format of ADGIF shaders can very slightly. + +Some more info about adgif shaders: +- they are 5 QW's long. Typically the VU program will insert a 1 QW giftag header that tells the GS that the following 5 QW's are a+d format data. +- The `texture` system updates these so they point to the correct VRAM address for textures. +- Sometimes the VU program will replace parts of an adgif shader to override their behavior. The `gs-adcmd` in the VU constants looks suspiciously like this. +- in static level data, the adgif shaders contain the texture ID for the texture they should be linked to. When the level is loaded, there is a "log in" function that must be called to adjust the adgif to point to the appropriate texture, and to add this to the linked list of adgifs for the texture. +- there are bits in the adgif shader that aren't used by the GS. They hide a pointer in here to build the linked list of adgifs per texture +- It's not super clear why they need the per-texture lists of adgifs (other than for debugging purposes) + +Here's where this adgif shader is set up +``` +;; grab the texture object from an ID. The texture object has metadata about the texture +(define *generic-envmap-texture* (lookup-texture-by-id (new 'static 'texture-id :index #x2 :page #x1f))) + +;; allocate the adgif shader +(define *default-envmap-shader* (new 'global 'adgif-shader)) + +(let ((gp-0 *default-envmap-shader*)) + (let ((a1-1 *generic-envmap-texture*)) + ;; link this adgif to the texture. The settings will be set up for the texture. + (adgif-shader<-texture! gp-0 a1-1) + ) + ;; manually set some settings, overwriting what was done above + + ;; turn on texture filtering + (set! (-> gp-0 tex1) (new 'static 'gs-tex1 :mmag #x1 :mmin #x1)) + + ;; turn on texture clamping + (set! (-> gp-0 clamp) (new 'static 'gs-clamp :wms (gs-tex-wrap-mode clamp) :wmt (gs-tex-wrap-mode clamp))) + + ;; set a specific alpha blend mode + (set! (-> gp-0 alpha) (new 'static 'gs-alpha :b #x2 :c #x1 :d #x1)) + + ;; set the addresses for the register (some of these were automatically done by the adgif-shader<-texture! call) + (set! (-> gp-0 prims 1) (gs-reg64 tex0-1)) + (set! (-> gp-0 prims 3) (gs-reg64 tex1-1)) + (set! (-> gp-0 prims 5) (gs-reg64 miptbp1-1)) + (set! (-> gp-0 clamp-reg) (gs-reg64 clamp-1)) + (set! (-> gp-0 prims 9) (gs-reg64 alpha-1)) + ) + ``` + +# Part 2: Implementation + +## Splitting up TIE3: the idea +A tie "tree" is a bunch of ties arranged in a BVH for easier culling. Generally, a level has a small number of TIE trees (somewhere between 1 and 3?). + +Previously TIE3 assumed that a single tree would be drawn all at once on PC. But now, the tree must be split up into (normal, water, trans) categories. Each category should be drawn at the appropriate time. + +The tricky part about splitting this up is that culling has to be done for the whole tree at once, and we only want to compute this once - not per category. + +So in the first TIE bucket for a given level, we'll: +- do the normal logic to see which trees the game is trying to draw +- do culling for the entire tree +- draw the first category (normal TIE) + +Then, in the other two buckets: +- Check to see if the first bucket drew on this frame +- If so, reuse culling/selection logic, but draw the other category. + +To make this change: +- each PC-format tree has a list of draws: `std::vector`. Split this into a list per-category. +- add a `Tie3AnotherCategory` renderer. + +Jak 1 doesn't do this multi-bucket-from-a-single-tree stuff, so we can just put everything in the "default" bucket and it will work the same as before. + +But before that... + +### How are TIEs sorted into categories? +Remember that TIE DMA generation is broken into three steps (executed per _tree_): +- iterate over instances. if visible, add them to a "per prototype" list +- iterate over protypes. if any visible, add them to a list for the appropriate category +- insert per-category lists into the appropriate DMA bucket. + +The category decision doesn't happen in the third step: at this point the `*prototype-tie-work*` structure contains per-category lists (eg: `(-> *prototype-tie-work* envmap-next)` will contain all the `envmap` category for this tree). + +The other two functions are asm... We need to guess where to look first, or it will take a full day to read through all this code. + +I'd guess that the category decision happens in the first function. The category decision has to be different per-instance to handle stuff like "this instance needs scissoring, but this other one doesn't", and similar for envmap fadeout. They could have done this work in either the first or the second, but I think it makes a lot more sense in the first one based on what I remember from Jak 1's code. In jak 1, stuff like picking the LOD was done in the first pass, which means they'd compute the invserse distances here, which you'd also need for envmap. + +Perhaps another clue is the `prototype-bucket-tie` type - this contains a per-catgory list of instances: +``` + (tie-next uint32 4 :offset 64) + (tie-scissor-next uint32 :offset 64) + (tie-near-next uint32 :offset 68) + (tie-mid-next uint32 :offset 72) + (tie-far-next uint32 :offset 76) + (trans-next uint32 4 :offset 64) + (trans-scissor-next uint32 4 :offset 64) + (trans-near-next uint32 :offset 68) + (trans-mid-next uint32 :offset 72) + (trans-far-next uint32 :offset 76) + (water-next uint32 4 :offset 64) + (water-scissor-next uint32 4 :offset 64) + (water-near-next uint32 :offset 68) + (water-mid-next uint32 :offset 72) + (water-far-next uint32 :offset 76) + ;; many more... +``` +Note that `near`/`mid`/`far` are LODs here. (confusing because "near" in jak 1 meant "scissor" in jak 2). + +The first function is `draw-inline-array-instance-tie` and is annoyingly large (about 1400 lines of asm), so I don't want to annotate the whole thing. + +Based on this, I'd guess the function does something like: +- skip over instances that don't have vis-bit set (unlike shrub, there's a precomputed vis string for the sphere culling, so this should be a simple "is it set" check) +- upload instance metadata to scratchpad (they love the scratchpad) +- compute the instance matrix, determine LOD and LOD interpolation coefficients. +- generate DMA on scratchpad +- download DMA from scratchpad to DMA buffer +- patch DMA appropritately + +and of course, all of this is double buffered, and very complicated. + +Arguments: +- `a0` is visibility data +- `a1` is the array of `instance-tie`s +- `a2` is the number of `instance-tie`s +- `a3` is the DMA buffer to hold the instance drawing DMA + +The beginning is the same as Jak 1's, documented in `tie_ee.asm`. It basically sets us up with some visible instances in the scratchpad. (note that these "instances" are just metadata describing the TIE). + +There's a tiny bit of difference before the following snippet, but I think it's just related to stopping DMA generation when out of DMA memory (in jak 1, they'd just overflow and crash everything). + +This is where the real differences from Jak 1 start, and I'm guessing will contain category-related stuff: + +``` +t5 = the instance-tie +t4 = our index in this group + +B24: +L284: + sll r0, r0, 0 + lw t9, 12(t5) ;; t9 = inst.bucket_ptr + and ra, t6, t7 ;; check vis bit + ld s5, 56(t5) ;; s5 = inst.origin_3 + beq ra, r0, L316 ;; skip if invisible + ld s3, 32(t5) ;; s3 = origin_0 + +B25: + sll ra, t4, 4 ;; DMA output offset + ld gp, 40(t5) ;; gp = origin_1 + pextlh s4, s5, r0 ;; s4 = s5 << 16 (128-bits version of origin 3) + ld s5, 48(t5) ;; s5 = origin_2 + psraw s4, s4, 10 ;; s4 = origin_3, signed + lq s2, 28(t9) ;; s2 = our bucket's dists (lod transitions) + pextlh s3, s3, r0 ;; origin conversion stuff, see jak 1 + lq s1, 44(t9) ;; s0 = more dist stuff + psraw s3, s3, 16 ;; origin stuff + qmtc2.ni vf14, s2 ;; origin + pextlh gp, gp, r0 ;; converion + qmtc2.ni vf15, s1 + psraw gp, gp, 16 + qmtc2.ni vf13, s4 + pextlh s5, s5, r0 + qmtc2.ni vf10, s3 + psraw s4, s5, 16 + lw s3, 4(t9) ;; DIFFERENT: s3 = flags + addu ra, ra, v1 ;; output addr calc + lhu s0, 62(t5) ;; wind garbage + andi s2, s3, 33 ;; and with (prototype-flags disable visible) + lw s1, 468(t0) ;; wind garbage + andi s5, s3, 2 ;; S5 = and with (flags flag-bit-one) + qmtc2.ni vf11, gp + dsll gp, s0, 5 + qmtc2.ni vf12, s4 + daddu s4, s1, gp + sw ra, 196(t0) + andi s1, s3, 8 ;; S1 = and with (flags vanish) + lwc1 f7, 152(t9) + andi gp, s3, 132 ;; GP = and with (flags tpage-alpha tpage-water) + lwc1 f6, 148(t9) + sll r0, r0, 0 + sw s1, 516(t0) + sll r0, r0, 0 + sw gp, 520(t0) + bne s2, r0, L316 ;; skip if invisible/disabled + cfc2.ni s3, vi1 + +B26: + vitof0.xyzw vf13, vf13 + lwc1 f9, 160(t9) + bne s3, r0, L316 + lwc1 f8, 156(t9) + +B27: + vitof12.xyzw vf10, vf10 + lhu s3, 46(t5) + sll r0, r0, 0 + lqc2 vf17, 32(t0) + andi s3, s3, 2 ;; check instance flag and skip (new for jak 2??) + lqc2 vf19, 16(t0) + bne s3, r0, L316 + qmfc2.i s2, vf6 +``` +Not too much learned here: +- Disabled/Invisible are both skipped here. +- `s5` is set if `flag-bit-one` is set (could this be envmap? just a guess). +- vanish sets `vanish-flag` in `instance-tie-work`. +- using either `tpage-alpha` or `tpage-water` will set `translucent-flag` +- there's a per-instance visibility flag now. + + +After this, there's some math likely for distance/interpolation. + +And some more reference to the `gp` (containing the `alpha`/`water` flag bits): +``` + bne gp, r0, L285 ;; check flag + lhu s2, 6(t9) + +B29: + beq r0, r0, L286 + lw s3, 544(t0) ;; if flag is 0 , s3 = itw.tfrag-dists + +B30: +L285: + andi gp, gp, 128 + sll r0, r0, 0 + bne gp, r0, L286 + lw s3, 552(t0) ;; itw.water-dists + +B31: + sll r0, r0, 0 + lw s3, 548(t0) ;; itw.alpha-dists +B32: +``` +these `dists` are part of the texture system, so it can know which MIP levels need to be in VRAM. Again, nothing surprising, but more evidence that these proto flags will be used to pick trans/water modes. + +B32 is a bunch more math, and starts writing the output. +``` +~~~~~~ + sqc2 vf5, 80(t8) + ppach s1, r0, s1 + sw s5, 80(t8) ;; kinda sus, this is flags and with (flags flag-bit-one) + or s2, s1, s2 + sqc2 vf14, 96(t0) +~~~~~~ +``` +note that this similar suspicious line appeared in jak 1, and was unused. may just be leftover. We don't seem to have a type for this output strangely. + +B33 is wind-only (ugh, different from jak 1). + +B34 is back on the main path: +``` +bne s5, r0, L299 ;; if bit-one set, go to L299 +... + +L288 +``` + +This seems very promising to be the environment map bit. I checked `bit-one` in `extract_tie.cpp` and found that it seems to the environment map bit. + +So to summarize, at this point: +- seems very likely that `tpage-alpha`/`tpage-water` bits control the category. +- seems like `bit-one` controls envmapping. +- `L299` is the envmap drawing part of `draw-inline-array-instance-tie` +- `L288` is the non-envmap path + +At this point, I printed names/flags for all protos, and the bit-one guess seems right. + +I will take a break from reverse engineering and start splitting up TIE to draw during multiple buckets. Right now we draw _all_ ties as part of the normal tie bucket. But in the real game, parts of the tree are drawn in different buckets, based on the "category". It's important that we change this, otherwise our draw order will be different from the normal games, causing issues with transparency. + +The solution is to split up the C++ Tie renderer by category. It needs to support something like "draw everything from this tree, but only if it matches this category". + +### Splitting up TIE 1 + +Detecting the category is simple: +```cpp +struct TieCategoryInfo { + tfrag3::TieCategory category; + bool uses_envmap; +}; + +TieCategoryInfo get_jak2_tie_category(u32 flags) { + constexpr int kJak2ProtoDisable = 1; + constexpr int kJak2ProtoEnvmap = 2; + constexpr int kJak2ProtoTpageAlpha = 4; + constexpr int kJak2ProtoVanish = 8; + constexpr int kJak2ProtoBitFour = 16; + constexpr int kJak2ProtoVisible = 32; + constexpr int kJak2ProtoNoCollide = 64; + constexpr int kJak2ProtoTpageWater = 128; + TieCategoryInfo result; + if (flags & kJak2ProtoTpageAlpha) { + result.category = tfrag3::TieCategory::TRANS; + ASSERT((flags & kJak2ProtoTpageWater) == 0); + } else if (flags & kJak2ProtoTpageWater) { + result.category = tfrag3::TieCategory::WATER; + ASSERT((flags & kJak2ProtoTpageAlpha) == 0); + } else { + result.category = tfrag3::TieCategory::NORMAL; + } + result.uses_envmap = flags & kJak2ProtoEnvmap; + return result; +} + +TieCategoryInfo get_jak1_tie_category(u32 flags) { + TieCategoryInfo result; + result.category = tfrag3::TieCategory::NORMAL; + result.uses_envmap = flags & 2; + return result; +} +``` + +Updating the data format: +```cpp +enum class TieCategory { + NORMAL, + TRANS, // also called alpha + WATER, +}; +constexpr int kNumTieCategories = 3; + +// A tie model +struct TieTree { + BVH bvh; + std::vector static_draws; + // Category n uses draws: static_draws[cdi[n]] to static_draws[cdi[n + 1]] + std::array category_draw_indices; +``` +This lets us keep a flat list of draws, which will work with all the existing shared tfrag/tie/shrub code. + +Updating `extract_tie.cpp` was straightforward. + +Updating `Tie3.cpp` required a pretty big rewrite (a few hours). Hopefully I won't have to do this all over again for envmap! + +It looks something like this. First, in the main `Tie3` renderer (associated with the default bucket), we do all setup for all trees, but only draw the default bucket. +```cpp +/*! + * Render method called from bucket render system. + * Does common setup for all category, but only renderers default_category. + */ +void Tie3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) { + // read dma, figure out what level to draw, camera matrix, etc. + set_up_common_data_from_dma(dma, render_state); + + // for each tree, do culling, time of day + setup_all_trees(lod(), m_common_data.settings, m_common_data.proto_vis_data, + m_common_data.proto_vis_data_size, !render_state->no_multidraw); + + // for each tree, render stuff in default category + draw_matching_draws_for_all_trees(lod(), m_common_data.settings, render_state, prof, + m_default_category); +} +``` +then, in the other buckets, we end up calling this method: +```cpp +void Tie3::render_from_another(SharedRenderState* render_state, + ScopedProfilerNode& prof, + tfrag3::TieCategory category) { + // only draw if the main bucket drew on this frame. + if (render_state->frame_idx != m_common_data.frame_idx) { + return; + } + + // draw for this category. Reuse setup from before. + draw_matching_draws_for_all_trees(lod(), m_common_data.settings, render_state, prof, category); +} +``` +This is kind of nice because we don't have to actually generate DMA for the non-default buckets - the C++ renderer will know to draw these as long as the default bucket was drawn. + +I plan to extend this for the envmap buckets, with something like `draw_matching_envmap_draws`. + +## Getting the envmap shader data into the FR3 +I am blind and missed that there's an `envmap-shader` reference in `prototype-bucket-tie`. It's just a reference to an adgif shader stored somewhere else. + +``` + (envmap-rfade float :offset-assert 160) + (envmap-fade-far float :offset-assert 164) + (envmap-shader adgif-shader :offset-assert 168) + (tint-color uint32 :offset-assert 172) +``` +I bet the `tint-color` will be important too... + +I'd exepct something to "log in" this `envmap-shader`. If it's part of the normal list of shaders, there's no need to explicitly log it in. But I'd guess it's not part of the normal list (based on what a vaguely remember for how Jak 1 built TIE stuff), so I looked for code that specifically logs it in: +``` +;; get the envmap-shader from the prototype +(let ((envmap-shader (-> proto2 envmap-shader))) + ;; check if it exists (not set on non-envmapped protos) + (when (nonzero? envmap-shader) + ;; "log in" the adgif shader to link it to a texture. + ;; in the static + (let ((envmap-tex (adgif-shader-login-no-remap envmap-shader))) + ;; that returns the texture + (when envmap-tex + ;; update texture masks + (dotimes (v1-137 3) + (dotimes (a0-74 3) + (set! (-> (the-as (pointer int32) (+ (+ (* v1-137 16) (* a0-74 4)) (the-as int *texture-masks*)))) + (logior (-> (the-as (pointer int32) (+ (* a0-74 4) (the-as int *texture-masks*) (* v1-137 16))) 0) + (-> (the-as (pointer int32) (+ (* a0-74 4) (the-as int envmap-tex) (* v1-137 16))) 15) + ) + ) + ) + (set! (-> *texture-masks* data v1-137 mask w) + (the-as int (fmax (-> *texture-masks* data v1-137 dist) (-> envmap-tex masks data v1-137 dist))) + ) + ) + ) + ) + + ;; set envmap shader stuff. + (set! (-> envmap-shader tex1) (new 'static 'gs-tex1 :mmag #x1 :mmin #x1)) + (set! (-> envmap-shader clamp) + (new 'static 'gs-clamp :wms (gs-tex-wrap-mode clamp) :wmt (gs-tex-wrap-mode clamp)) + ) + (set! (-> envmap-shader alpha) (new 'static 'gs-alpha :b #x2 :c #x1 :d #x1)) + (set! (-> envmap-shader prims 1) (gs-reg64 tex0-1)) + (set! (-> envmap-shader prims 3) (gs-reg64 tex1-1)) + (set! (-> envmap-shader prims 5) (gs-reg64 miptbp1-1)) + (set! (-> envmap-shader clamp-reg) (gs-reg64 clamp-1)) + (set! (-> envmap-shader prims 9) (gs-reg64 alpha-1)) + ) + ) +``` + +As more confirmation that this envmap shader is "special" and outside of the normal tie-proto shader lists, I looked at `mountain`'s `.asm` file, and saw that nothing else reference this adgif data other than the label in the `envmap-shader` slot. And it didn't appear to be part of the other shader list for the proto. + + +The mystery now is: how does this envmap shader work when there's also a default-envmap uploaded to VU1 for etie? My guess is that this per-proto will override the default shader. To get an idea of how often this happened, I modified `extract_tie.cpp` to also grab this shader, and added this assert +```cpp +bool using_envmap = info.uses_envmap; // set from proto flags +ASSERT(using_envmap == proto.envmap_adgif.has_value()); // set from envmap-shader being nonzero +``` +and it passed! So it seems like all protos using envmap provide their own shader. + +Looking quickly at `draw-inline-array-prototype-tie-asm` shows that it always uploads the `envmap-shader`, so I guess the default one isn't used? + +Progress up to this point took about 1 weekend day (8 hours). + +## Looking for a shortcut: I really don't want to figure out the whole program + +Figure out all of etie looks absolutely awful (3k lines of EE assembly, 1.5k lines of VU1 assembly), so let's make some guesses. + +First, add some new categories for envmap: +``` +enum class TieCategory { + NORMAL, + TRANS, // also called alpha + WATER, + NORMAL_ENVMAP, + TRANS_ENVMAP, + WATER_ENVMAP +}; +``` + +I refactored things so I could write this: + +```cpp +handle_draw_for_strip(tree, static_draws_by_tex, + draws_by_category.at((int)info.category), packed_vert_indices, + mode, idx_in_lev_data, strip, inst, ifrag, proto_idx, frag_idx, + strip_idx, matrix_idx); + +// also add the envmap draw: note useing envmap_drawmode and envmap_category this time +if (info.uses_envmap) { + handle_draw_for_strip(tree, static_draws_by_tex, + draws_by_category.at((int)info.envmap_category), + packed_vert_indices, envmap_drawmode, idx_in_lev_data, strip, + inst, ifrag, proto_idx, frag_idx, strip_idx, matrix_idx); +} +``` +But that doesn't make sense... +This code does: +- always add normal draw to normal category +- if we envmap, add a draw to envmap category + +What we really want is: +- if we envmap, add normal draw to envmap category +- add a second draw, also in the envmap category, somehow flagged to behave differently. + +Environment mapped stuff is drawn in two passes: a base pass that's very similar to normal TIE, and a second pass to draw on the shiny stuff. + +We merge and reorder draws (within a category) to reduce the number of texture switches. The number of switches for the first and second pass may be different (eg: two objects may use the same base texture, but different envmap texture), and in these cases we want to merge their second-pass draws but not their first. So it makes sense to have an entirely separate draw list for envmap drawing. + +```cpp +if (info.uses_envmap) { + // first pass: normal draw mode, envmap bucket, normal draw list + handle_draw_for_strip(tree, static_draws_by_tex, + draws_by_category.at((int)info.envmap_category), + packed_vert_indices, mode, idx_in_lev_data, strip, inst, ifrag, + proto_idx, frag_idx, strip_idx, matrix_idx); + // second pass envmap draw mode, in envmap bucket, envmap-specific draw list + handle_draw_for_strip(tree, envmap_draws_by_tex, + envmap_draws_by_category.at((int)info.envmap_category), + packed_vert_indices, envmap_drawmode, idx_in_lev_data, strip, + inst, ifrag, proto_idx, frag_idx, strip_idx, matrix_idx); +} else { + // totally normal stuff + handle_draw_for_strip(tree, static_draws_by_tex, + draws_by_category.at((int)info.category), packed_vert_indices, + mode, idx_in_lev_data, strip, inst, ifrag, proto_idx, frag_idx, + strip_idx, matrix_idx); +} +``` +At this point, I did a round of actually implementing all the bits and pieces to make this load. I just skipped doing anything with the envmap draws, and only drew the normal ones. + +And this worked! I saw the "first pass" draw for stuff like mountain temple: +(TODO picture) +So it appears, but isn't shiny. + +I realized two problems: +- this is a bad idea, because we no longer have a single flat list of draws. +- there are still bad settings (end of pipe at pumping station is messed up) + +For the first problem, I think I should still have a single draw list for the entire TIE tree, but just add more categories. For example `NORMAL_ENVMAP` should be split into `NORMAL_ENVMAP_FIRST_PASS` and `NORMAL_ENVMAP_SECOND_PASS`. The `Tie3AnotherCategory` renderer assigned to first pass will be responsible for also handling the second pass. + +For the second problem, I think I can guess what's happening. Usually the alpha channel is used for "alpha test", which is a discrete "draw/don't draw" decision per pixel. This is useful for some textures that have totally see-through parts (eg: leafy things). For envmapped stuff, I think they turn off alpha test, and this ends up getting used as an "envmap multiplier". + +This reminds me of a third problem, which is that the trans/alpha/water TIE buckets may have different renderer settings that are not properly reflected anywhere. + +This reminds me of a 4th problem, which is that the tfx/tcc settings don't seem to work right for TIE. There are places where they switch this mode to DECAL for lights, and getting this wrong makes lights look dim. I don't really know why they do this, but it was a common pattern for jak 1 lights (and eyes). + +With all these problems, I am out of time. (spent about 4 hours over an evening) + +### Looking at problem 4 +DECAL doesn't seem to work. From what I remember, decal should be set during adgif-shader login. +Sure enough, I have this in `extract_tie.cpp` +``` + // the value is overwritten by the login function. We don't care about this value, it's + // specific to the PS2's texture system. + ASSERT(ra_tex0_val == 0 || ra_tex0_val == 0x800000000); // note: decal + // the original value is a flag. this means to use decal texture mode (todo) + if (uses_magic_tex0_bit) { + *uses_magic_tex0_bit = ra_tex0_val == 0x800000000; + } +``` +and it gets set on tons of lights: +``` +magic bit: sewer-light-base-rectangular.mb +magic bit: skatea-interior-wall-light.mb +magic bit: city-slum-lamp-01.mb +``` +but nothing actually uses this info. + +Easy fix for the fragment shader: +``` + if (decal == 1) { + // output color is only from the texture. + // likely more efficient to have this branch in the vertex shader. + fragment_color = vec4(1.0, 1.0, 1.0, 1.0); + } else { + // time of day lookup + fragment_color = texelFetch(tex_T1, time_of_day_index, 0); + // color adjustment + fragment_color *= 2; + fragment_color.a *= 2; + } +``` + +set up the uniform: +```cpp +void Tie3::init_shaders(ShaderLibrary& shaders) { + m_uniforms.decal = glGetUniformLocation(shaders[ShaderId::TFRAG3].id(), "decal"); +} +``` +then in the drawing loop: +```cpp + glUniform1i(m_uniforms.decal, draw.mode.get_decal() ? 1 : 0); +``` +and it fixes the lights! + +I checked on jak 1, and found that we missed some blue things in citadel, near the robot. + +### Looking at problem 2, then getting distracted and working on problem 3 +This problem is that alpha test is removing pixels that shouldn't be removed on the first draw of envmap. + +My guess is that alpha-test should be entirely disabled for the envmap base draw. This is how it worked in jak 1. + +The alpha test is controlled by the `gs-test` register, typically set by A+D data. There's some asserts in `process_adgif`that prove that it's not being set through `adgif-shaders`. In the original TIE renderer, there was some magic to toggle on and off alpha testing (which we handle). In the EE code, they generate template data: +``` + (set! (-> arg0 atestgif tag) (new 'static 'gif-tag64 :nloop #x2 :eop #x1 :nreg #x1)) + (set! (-> arg0 atestgif regs) (new 'static 'gif-tag-regs :regs0 (gif-reg-id a+d))) + (set! (-> arg0 alpha data) (the-as uint arg1)) + (set! (-> arg0 alpha cmds) (gs-reg64 alpha-1)) + (set! (-> arg0 atest-tra cmds) (gs-reg64 test-1)) + (set! (-> arg0 atest-tra data) (the-as uint arg2)) + (set! (-> arg0 atest-def cmds) (gs-reg64 test-1)) + (set! (-> arg0 atest-def data) (the-as uint arg3)) +``` +The "tra" and "def" test modes can be controlled by the VU program. + +In Jak 1 we detected this with the `misc_x` flag +```cpp +// determine the draw mode +DrawMode mode = process_draw_mode(strip.adgif, frag.prog_info.misc_x == 0, frag.has_magic_tex0_bit); +``` +which gets set from the lower bit of some mysterious VU program register (ugh I hope we don't have to find more crap like this ever again). + +``` +frag.prog_info.misc_x = vi01; + +u16 vi01 = vf04_z; + +u16 vf04_z = frag.other_gif_data.at(10); + +// each frag also has "other" data. This is some index data that the VU program uses. +// it comes in gif_data, after tex_qwc (determined from EE program) +int tex_qwc = proto.geometry[geo].tie_fragments.at(frag_idx).tex_count; +int other_qwc = proto.geometry[geo].tie_fragments.at(frag_idx).gif_count; +frag_info.other_gif_data.resize(16 * other_qwc); +memcpy(frag_info.other_gif_data.data(), + proto.geometry[geo].tie_fragments[frag_idx].gif_data.data() + (16 * tex_qwc), + 16 * other_qwc); +``` + +Depending on the category, the `tra` and `def` are different. + +Normal: +``` +(tie-init-buf + (-> s2-0 tie-bucket) + (the-as gs-alpha gp-0) + (new 'static 'gs-test + :ate #x1 + :atst (gs-atest greater-equal) + :aref #x26 + :zte #x1 + :ztst (gs-ztest greater-equal) + ) + (new 'static 'gs-test :zte #x1 :ztst (gs-ztest greater-equal)) + ) +``` + +Trans: (same as normal for `gs-test`) +``` +(tie-init-buf + (-> s2-0 tie-trans-bucket) + (the-as gs-alpha s4-0) + (new 'static 'gs-test + :ate #x1 + :atst (gs-atest greater-equal) + :aref #x26 + :zte #x1 + :ztst (gs-ztest greater-equal) + ) + (new 'static 'gs-test :zte #x1 :ztst (gs-ztest greater-equal)) + ) +``` + +Water: +``` +(tie-init-buf + (-> s2-0 tie-water-bucket) + (the-as gs-alpha s4-0) + (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest greater-equal)) + (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest greater-equal)) + ) +``` +(note that this weird alpha test setting is effectively used to mask z-buffer writes). + +This was a good point to notice that they turned on alpha blending for TIE in jak 2. + +From here, we can modify `extract_tie` to apply the correct `gs-test` settings based on the category. + +## Going back to problem 2 + +We need to look at the code that sets up ETIE to understand the settings. +Unlike normal TIE, there doesn't seem to be a feature to toggle between `tra` and `def` mode. Here is `etie-init-engine`, which sets up DMA to set `gs-test` prior to any ETIE stuff running. (this is different from TIE, which puts the `gs-test` stuff in the constants block so the VU program can manipulate gs-test at runtime) +``` +(defun etie-init-engine ((arg0 dma-buffer) (arg1 gs-alpha) (arg2 gs-test)) + ;; set up DMA for 1 register + (let* ((v1-0 arg0) + (a0-1 (the-as dma-packet (-> v1-0 base))) + ) + (set! (-> a0-1 dma) (new 'static 'dma-tag :qwc #x2 :id (dma-tag-id cnt))) + (set! (-> a0-1 vif0) (new 'static 'vif-tag)) + (set! (-> a0-1 vif1) (new 'static 'vif-tag :imm #x2 :cmd (vif-cmd direct) :msk #x1)) + (set! (-> v1-0 base) (the-as pointer (&+ a0-1 16))) + ) + (let* ((v1-1 arg0) + (a0-3 (the-as gs-gif-tag (-> v1-1 base))) + ) + (set! (-> a0-3 tag) (new 'static 'gif-tag64 :nloop #x1 :eop #x1 :nreg #x1)) + (set! (-> a0-3 regs) GIF_REGS_ALL_AD) + (set! (-> v1-1 base) (the-as pointer (&+ a0-3 16))) + ) + (let* ((v1-2 arg0) + (a0-5 (-> v1-2 base)) + ) + (set! (-> (the-as (pointer uint64) a0-5)) arg2) ;; this is where gs-test is set + (set! (-> (the-as (pointer gs-reg64) a0-5) 1) (gs-reg64 test-1)) + (set! (-> v1-2 base) (&+ a0-5 16)) + ) + ;; set up DMA to load ETIE program + (dma-buffer-add-vu-function arg0 etie-vu1-block 1) + + ;; set up DMA to load ETIE constants + (let ((s4-0 12)) + (let* ((v1-3 arg0) + (a0-8 (the-as dma-packet (-> v1-3 base))) + ) + (set! (-> a0-8 dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc s4-0)) + (set! (-> a0-8 vif0) (new 'static 'vif-tag :cmd (vif-cmd stmod))) + (set! (-> a0-8 vif1) (new 'static 'vif-tag :imm #x382 :cmd (vif-cmd unpack-v4-32) :num s4-0)) + (set! (-> v1-3 base) (the-as pointer (&+ a0-8 16))) + ) + (etie-init-consts (the-as etie-consts (-> arg0 base)) arg1) + (&+! (-> arg0 base) (* s4-0 16)) + ) + + ;; DMA to run program at address 8 (likely some init thing), then flusha. + (let* ((v1-6 arg0) + (a0-12 (the-as dma-packet (-> v1-6 base))) + ) + (set! (-> a0-12 dma) (new 'static 'dma-tag :id (dma-tag-id cnt))) + (set! (-> a0-12 vif0) (new 'static 'vif-tag :imm #x8 :cmd (vif-cmd mscalf) :msk #x1)) + (set! (-> a0-12 vif1) (new 'static 'vif-tag :cmd (vif-cmd flusha) :msk #x1)) + (set! (-> v1-6 base) (the-as pointer (&+ a0-12 16))) + ) + + ;; dma to set the ROW register + (let* ((v1-7 arg0) + (a0-14 (the-as dma-packet (-> v1-7 base))) + ) + (set! (-> a0-14 dma) (new 'static 'dma-tag :qwc #x2 :id (dma-tag-id cnt))) + (set! (-> a0-14 vif0) (new 'static 'vif-tag)) + (set! (-> a0-14 vif1) (new 'static 'vif-tag :cmd (vif-cmd strow) :msk #x1)) + (set! (-> v1-7 base) (the-as pointer (&+ a0-14 16))) + ) + (let* ((v1-8 arg0) + (a0-16 (-> v1-8 base)) + ) + ;; common values used for their "use ROW add to convert ints to floats" trick + (set! (-> (the-as (pointer uint32) a0-16) 0) (the-as uint #x4b000000)) + (set! (-> (the-as (pointer uint32) a0-16) 1) (the-as uint #x4b000000)) + (set! (-> (the-as (pointer uint32) a0-16) 2) (the-as uint #x4b000000)) + (set! (-> (the-as (pointer uint32) a0-16) 3) (the-as uint #x4b000000)) + + ;; set up base/offset for xtop-based double-buffering + (set! (-> (the-as (pointer vif-tag) a0-16) 4) (new 'static 'vif-tag :imm #xb8 :cmd (vif-cmd base))) + (set! (-> (the-as (pointer vif-tag) a0-16) 5) (new 'static 'vif-tag :imm #x20 :cmd (vif-cmd offset))) + + ;; setup default uploading mode for ETIE, I guess. + (set! (-> (the-as (pointer vif-tag) a0-16) 6) (new 'static 'vif-tag :cmd (vif-cmd stmod))) + (set! (-> (the-as (pointer vif-tag) a0-16) 7) (new 'static 'vif-tag :imm #x404 :cmd (vif-cmd stcycl))) + (set! (-> v1-8 base) (&+ a0-16 32)) + ) + (none) + ) + ``` +There's still some mysterious stuff going on with alpha (why does that need to be sent at all? adgif shaders should be setting alpha!), but let's ignore it for now. We ignored it for Jak 1's TIE and it was ok. (I kinda suspect it's for vanish or something like that?). + +Just like TIE, this can be traced back to figure out the per-bucket settings: +```cpp + +/*! + * Get the draw mode settings that are pre-set for the entire bucket and not controlled by adgif + * shaders + */ +DrawMode get_base_draw_mode_jak2(bool use_tra, tfrag3::TieCategory category) { + DrawMode mode; + mode.enable_ab(); + switch (category) { + case tfrag3::TieCategory::NORMAL: + case tfrag3::TieCategory::TRANS: + mode.enable_zt(); + mode.set_depth_test(GsTest::ZTest::GEQUAL); + if (use_tra) { + mode.enable_at(); + mode.set_aref(0x26); + mode.set_alpha_fail(GsTest::AlphaFail::KEEP); + mode.set_alpha_test(DrawMode::AlphaTest::GEQUAL); + } else { + mode.disable_at(); + } + break; + mode.enable_zt(); + mode.set_depth_test(GsTest::ZTest::GEQUAL); + if (use_tra) { + mode.enable_at(); + mode.set_aref(0x26); + mode.set_alpha_fail(GsTest::AlphaFail::KEEP); + mode.set_alpha_test(DrawMode::AlphaTest::GEQUAL); + } else { + mode.disable_at(); + } + break; + case tfrag3::TieCategory::WATER: + case tfrag3::TieCategory::WATER_ENVMAP: + case tfrag3::TieCategory::WATER_ENVMAP_SECOND_DRAW: + // (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest greater-equal)) + mode.enable_zt(); + mode.set_depth_test(GsTest::ZTest::GEQUAL); + mode.enable_at(); + mode.set_aref(0); + mode.set_alpha_fail(GsTest::AlphaFail::FB_ONLY); + mode.set_alpha_test(DrawMode::AlphaTest::NEVER); + break; + + case tfrag3::TieCategory::NORMAL_ENVMAP: + case tfrag3::TieCategory::TRANS_ENVMAP: + case tfrag3::TieCategory::NORMAL_ENVMAP_SECOND_DRAW: + case tfrag3::TieCategory::TRANS_ENVMAP_SECOND_DRAW: + mode.enable_zt(); + mode.set_depth_test(GsTest::ZTest::GEQUAL); + mode.disable_at(); + break; + + default: + ASSERT(false); + } + return mode; +} +``` +So we can take these settings, then apply alpha/tex0/clamp from the adgif shader, then we'll get our final draw mode. Notice that I'm assuming that the second draw uses the same settings. I think this actually makes sense for `gs-test`, but I'm less sure about alpha (not that we're setting it yet...). + +Also, refactored the categories again to have separate "second draw" categories. +```cpp +TieCategoryInfo get_jak2_tie_category(u32 flags) { + constexpr int kJak2ProtoDisable = 1; + constexpr int kJak2ProtoEnvmap = 2; + constexpr int kJak2ProtoTpageAlpha = 4; + constexpr int kJak2ProtoVanish = 8; + constexpr int kJak2ProtoBitFour = 16; + constexpr int kJak2ProtoVisible = 32; + constexpr int kJak2ProtoNoCollide = 64; + constexpr int kJak2ProtoTpageWater = 128; + TieCategoryInfo result; + result.uses_envmap = flags & kJak2ProtoEnvmap; + + if (flags & kJak2ProtoTpageAlpha) { + if (result.uses_envmap) { + result.category = tfrag3::TieCategory::TRANS_ENVMAP; + result.envmap_second_draw_category = tfrag3::TieCategory::TRANS_ENVMAP_SECOND_DRAW; + } else { + result.category = tfrag3::TieCategory::TRANS; + } + ASSERT((flags & kJak2ProtoTpageWater) == 0); + } else if (flags & kJak2ProtoTpageWater) { + if (result.uses_envmap) { + result.category = tfrag3::TieCategory::WATER_ENVMAP; + result.envmap_second_draw_category = tfrag3::TieCategory::WATER_ENVMAP_SECOND_DRAW; + } else { + result.category = tfrag3::TieCategory::WATER; + } + ASSERT((flags & kJak2ProtoTpageAlpha) == 0); + } else { + if (result.uses_envmap) { + result.category = tfrag3::TieCategory::NORMAL_ENVMAP; + result.envmap_second_draw_category = tfrag3::TieCategory::NORMAL_ENVMAP_SECOND_DRAW; + } else { + result.category = tfrag3::TieCategory::NORMAL; + } + } + return result; +} +``` +The "category" now means the category for the base draw. If there is a second pass draw, it goes in `envmap_second_draw_category`. This removes the envmap-specific draw list. + +Some more refactoring later, it looks awful. Turning on alpha blending has caused the envmap base draw to make stuff partially transparent: + +PICTURE + +Some ideas: +- the alpha blend mode used is wrong +- the VU program clears the alpha blend enable bit +- the VU program overwrites alpha somehow + +Looking into the first idea first. All the adgifs go through this, so I think that all adgifs do set alpha: +```cpp + // alpha settings. we care about these, but no hidden value + u8 ra_alpha = gif_data.at(16 * (tex_idx * 5 + 4) + 8); + ASSERT(ra_alpha == (u8)GsRegisterAddress::ALPHA_1); // make sure it's setting alpha + u64 alpha; + memcpy(&alpha, &gif_data.at(16 * (tex_idx * 5 + 4)), 8); + adgif.alpha_val = alpha; + return adgif; +``` +unsupported alphas are also an error, so I don't think it's ignoring a weird alpha mode. Interestingly, almost all TIE adgifs in jak 2 have SRC_DST_SRC_DST ("normal transparency"). The only exception is some `vil1-outdoor-light` copied from jak 1. This is different from jak 1, which sometimes set other modes (despite having alpha blend turned off, causing them to do nothing). + +To investiage the others, we'll have to figure out the address of the alpha cmd, and the `abe` bit +``` + (alpah gs-adcmd :inline :offset-assert 32) + (strgif gs-gif-tag :inline :offset-assert 48) +``` +in constants. +The unpack is at +``` + (set! (-> a0-8 vif1) (new 'static 'vif-tag :imm #x382 :cmd (vif-cmd unpack-v4-32) :num s4-0)) +``` +So the address is `0x384` for the alpha and `0x385` for the "strgif" (containing `:abe 1` bit). + +The `0x384` is 900. It's used here: + +``` + lq.xyzw vf17, 903(vi00) | nop ;; load, from constants, the envmap shader + lq.xyzw vf18, 904(vi00) | nop ;; (5x A+D format data) + lq.xyzw vf19, 905(vi00) | nop + lq.xyzw vf20, 906(vi00) | nop + lq.xyzw vf21, 907(vi00) | nop + iaddi vi04, vi00, 0x0 | nop ;; vi04 = 0 + lq.xyz vf11, 899(vi00) | nop ;; vf11 = adgif, the template giftag for sending an adgif shader + ilwr.w vi05, vi04 | nop ;; loading... something... + ilw.w vi07, 1(vi04) | nop ;; loading more things + ilw.w vi13, 2(vi04) | nop ;; more things + lq.xyzw vf24, 900(vi00) | nop ;; load the alpha a+d data + lqi.xyzw vf12, vi04 | nop ;; load 5 qw in a row. Very likely to be an adgif shader + lqi.xyzw vf13, vi04 | nop + lqi.xyzw vf14, vi04 | nop + lqi.xyzw vf15, vi04 | nop + lqi.xyzw vf16, vi04 | subw.w vf11, vf11, vf11 ;; this clears the upper 32-bits of the giftag + iadd vi05, vi05, vi12 | nop ;; they like to stash random crap here. + iadd vi06, vi05, vi13 | nop + iaddi vi01, vi00, 0x6 | nop + sq.xyzw vf11, -1(vi05) | nop ;; stores the giftag, the data after vi05 should be adgif format. + isw.x vi01, -1(vi05) | nop ;; _modifies_ giftag to include 6 a+d's instead of usual 5 + sqi.xyzw vf12, vi05 | nop ;; store the 5 a+d's like normal + sqi.xyzw vf13, vi05 | nop + sqi.xyzw vf14, vi05 | nop + sqi.xyzw vf15, vi05 | nop + sqi.xyzw vf16, vi05 | nop + b L6 | nop + sqi.xyzw vf24, vi05 | nop ;; (branch delay slot), store the 6th thing, the alpha + +;; now we have some sort of loop to store more adgifs. +;; strangely, vf16 isn't reloaded, so they always store the same one +;; (which will be alpha... but not the adjusted alpha.) + L5: + iadd vi05, vi05, vi12 | nop + iadd vi06, vi05, vi13 | nop + sqi.xyzw vf11, vi05 | nop ;; store adgif giftag + sqi.xyzw vf12, vi05 | nop ;; store adgif data (5 qw) + sqi.xyzw vf13, vi05 | nop + sqi.xyzw vf14, vi05 | nop + sqi.xyzw vf15, vi05 | nop + sqi.xyzw vf16, vi05 | nop ;; they don't reload vf16 ever? +L6: + sqi.xyzw vf11, vi06 | nop ;; store adgif giftag + sqi.xyzw vf17, vi06 | nop ;; store the envmap shader's a+d + sqi.xyzw vf18, vi06 | nop + sqi.xyzw vf19, vi06 | nop + sqi.xyzw vf20, vi06 | nop + sqi.xyzw vf21, vi06 | nop + iaddi vi07, vi07, -0x1 | nop + ilwr.w vi05, vi04 | nop + lqi.xyzw vf12, vi04 | nop + lqi.xyzw vf13, vi04 | nop + lqi.xyzw vf14, vi04 | nop + lqi.xyzw vf15, vi04 | nop + ibgtz vi07, L5 | nop + ;; edit: OOPS the load of vf16 was here... +``` +which brings up a few mysteries: +- it looks like they do the second envmap draw with the shader supplied in the constants block (always set to default-envmap-shader on VU program load). Then why did we think they were giving a per-proto envmap shader earlier? +- they "extend" the first adgif to use the alpha provided in the constants block. But then future adgifs don't get to use this. +- Future adgifs inherit the alpha from the first alpha. (or whatever a+d that had. I'm pretty sure it's always alpha, but who knows if other games are being played). + +The first mystery is solved by looking at `tie-work.gc` +``` +:envmap-shader (new 'static 'dma-packet + :dma (new 'static 'dma-tag :qwc #x5 :id (dma-tag-id ref)) + :vif0 (new 'static 'vif-tag :cmd (vif-cmd stmod)) + :vif1 (new 'static 'vif-tag :imm #x387 :num #x5 :cmd (vif-cmd unpack-v4-32)) + ) +``` +this is a DMA template to load an adgif shader to address `0x387` - 5 qw into the constants block, which is where the `envmap` adgif-shader is located. So they basically upload the envmap adgif-shader on top of the default one. Good news. It means that our extraction of the envmap-specific adgif-shader was right. + + +The other two mysteries are unknown. We'll have to wait to see trans/water in use first. + +Back to the alpha-blend-enable mystery. Searching for the use of the "strgif" tag (enables alpha blending, which seems wrong): +``` + iaddi vi04, vi04, -0x2 | subw.w vf22, vf00, vf00 + ilwr.x vi08, vi04 | subw.w vf23, vf00, vf00 ;; clear w of vf22. + ilwr.y vi09, vi04 | nop + ilwr.z vi05, vi04 | nop + iaddi vi07, vi07, -0x1 | nop + iaddi vi04, vi04, 0x1 | nop + lq.xyz vf22, 901(vi09) | nop ;; load only xyz part of strgif (w stays 0) + ibeq vi00, vi07, L8 | nop + lq.xyz vf23, 902(vi09) | nop +L7: + iadd vi05, vi05, vi12 | nop + iadd vi06, vi05, vi13 | nop + iaddi vi07, vi07, -0x1 | nop + sq.xyzw vf22, 0(vi05) | nop ;; store the strgif somewhere + iswr.x vi08, vi05 | nop + sq.xyzw vf23, 0(vi06) | nop + iswr.x vi08, vi06 | nop + ilwr.x vi08, vi04 | nop + ilwr.y vi09, vi04 | nop + ilwr.z vi05, vi04 | nop + iaddi vi04, vi04, 0x1 | nop + ibne vi00, vi07, L7 | nop + lq.xyz vf22, 901(vi09) | nop +``` +zeoring out the upper 32-bits doesn't clear `:abe` (it's likely because they hid stuff in those bits, and they need to be 0 for the GS). So no answer there... + +Looking through envmapped things _most_ of them have only 1 adgif. A few have more. What if this is actually the right behavior? +```cpp + if (use_crazy_jak2_etie_alpha_thing) { + if (tex_idx == 0) { + first_adgif_alpha = adgif.alpha_val; + adgif.alpha_val = + alpha_value_for_etie_alpha_override(get_jak2_tie_category(proto.flags).category); + } else { + adgif.alpha_val = first_adgif_alpha; + } + } +``` +it kinda seems promising. The alpha mode of 0 (the override mode for normal tie) corresponds to `(cs - cs) * as + cs`, which is just like having blending off. + +So I tried this... and it still wasn't right. Setting the override is doing the right thing and making things not-transparent, but for the second/third adgifs, it is definitely wrong. + +At this point, my guess is that the final a+d _doesn't set alpha at all_. So it looks like this: +``` +first shader, special +a+d +a+d +a+d +a+d +a+d +bonus a+d: sets alpha to override + +seonc shader: +a+d +a+d +a+d +a+d +a+d, but not actually alpha. + +we still have the override alpha. +``` + +I scrolled through the entire VU1 program and didn't see any obvious place where alpha would be modified. + +So I traced back. All ADGIF shaders are "logged in", which updates the `TEX0` data to point to the right VRAM address (and also sets stuff like tcc). This is a bit complicated because there's a bunch of packed flags, and the formats are terrible (in the file, it's some packed mess read by `adgif-shader-login-remap`, which outputs a GS format tag, with a bunch of ND specific stuff packed in the unused bits - some stuff is inserted by the `login` function - like a linked list pointer - and other magic bits are just passed through and have renderer-specific meaning). + +This showed that some tie logins would actually change the register address in A+D format data: +``` + (let ((pre (-> s5-0 reg-4)) + (v1-1 (adgif-shader-login-no-remap s5-0))) + (format 0 "TIE LOGIN: ~D, ~D -> ~D~%" (-> s5-0 alpha) pre (-> s5-0 reg-4)) + ... + +;; this printed +TIE LOGIN: 34981577296, 66 -> 54 +``` +which is an alpha -> `miptbp2-1` register change. That would make perfect sense! + +The actual `adgif-shader-login-no-remap` is an asm mess, but I see: +``` + (when (< (the-as uint 4) (-> arg1 num-mips)) + (set! (-> arg0 alpha-as-miptb2) (new 'static 'gs-miptbp + :tbp1 (-> arg1 dest 4) + :tbw1 (-> arg1 width 4) + :tbp2 (-> arg1 dest 5) + :tbw2 (-> arg1 width 5) + :tbp3 (-> arg1 dest 6) + :tbw3 (-> arg1 width 6) + ) + ) + (set! (-> (&-> arg0 reg-4-u32) 0) (gs-reg32 miptbp2-1)) + ) +``` +which is replacing alpha with `miptbp2-1` if num_mips is more than 4. This is in `adgif-shader<-texture!` which I think is similar to the mip2sc version. The asm version has +``` +daddiu t0, a3, -5 +bltz t0, L44 +``` +which is a "branch if more than 5"... + +Ignoring the 4 vs. 5 thing: +```cpp + if (version == GameVersion::Jak2 && tfrag3::is_envmap_first_draw_category(category)) { + if (num_mips > 5) { + update_mode_from_alpha1(alpha_value_for_etie_alpha_override(category), mode); + } else { + // do ther normal stuff... + } + } +``` +but I hit two problems: +- there were cases where it seemed like an alpha would "sneak through" where it shouldn't. +- I've also broken normal the doors in the stadium (jetboard). Might be normal TIE that's broken here. + +So the plan for next time is: +- do my best guess at what ETIE should do, based on literal reading of the VU program. +- Identify a place where I think an alpha "sneaks through" +- Check it in game and compare to PCSX2. + +There's also the fact that enabling alpha blend for normal TIE seems to do the wrong thing sometimes (stadium doors transparent, the TIE ones). + +The good news is that some transparent TIEs look right now. So there was some benefit to all this. + +And this is all the time I have for today (spent about 4.5 hours) + +## Next. Looking into normal tie + +the drawing kicks for normal tie. The first kick is (I think) initializing alpha, then the second draws stuff +``` + ilw.x vi01, 971(vi00) | nop + ilw.y vi12, 971(vi00) | nop + ilw.z vi02, 971(vi00) | nop + lq.xyzw vf05, 972(vi00) | nop + lq.xyzw vf06, 973(vi00) | nop + lq.xyzw vf07, 974(vi00) | nop + lq.xyzw vf08, 975(vi00) | nop + sq.xyzw vf05, 976(vi00) | nop + sq.xyzw vf06, 977(vi00) | nop + isw.y vi02, 977(vi00) | nop + ibeq vi00, vi01, L41 | nop + sq.xyzw vf07, 978(vi00) | nop + sq.xyzw vf08, 978(vi00) | nop +L41: + iaddiu vi02, vi00, 0x3d0 | nop ;; vi02 = 976 + isw.y vi01, 971(vi00) | nop + nop | nop + xgkick vi02 | nop +``` + +This places the following giftag at 976: +``` +(-> consts atestgif) +alpha a+d +test a+d +``` +which sets alpha/test. + +However, my current understanding of VU code is that alpha can "sneak through" if: +- less than 4 (or 5?) mips on any adgif in normal tie +- less than 4 (or 5?) mips on any adgif after the first one in etie + +Using this "sneak through" logic does obviously wrong things for normal tie (pipe in atoll, the one you cross on the first mission). + +Doing the sensible thing, and somehow assuming that the alpha from the category always "wins": + +```cpp + if (version == GameVersion::Jak1) { + // use alpha from adgif shader, that's what we did in the past (could be wrong?) + update_mode_from_alpha1(info.alpha_val, mode); + } else { + if (tfrag3::is_envmap_second_draw_category(category)) { + // envmap shader gets to control its own alpha + update_mode_from_alpha1(info.alpha_val, mode); + } else { + // non-envmap always get overriden (both the first draw of etie, and normal tie) + update_mode_from_alpha1(alpha_value_for_jak2_tie_or_etie_alpha_override(category), mode); + } + } +``` +this seems to work everywhere I looked (included stuff that should be transparent, or drawn wrong earlier). Though it's very confusing how this actually happens. I've wasted enough time here. + + +## Vertex normals +One of the challenges of environment mapping is that the VU1 program needs a normal for each vertex. Note that these aren't "face normals" - these would cause obvious discontinuities in the environment map texture coordinates. This means that each vertex needs a normal. + +The challenge here is finding the surface normals from inside the TIE mess. + +Remembering back to TIE +- BP1, drawing vertices without interpolation that appear once +- BP2, drawing vertices without interpolation that appear twice +- IP1, drawing vertices with interpolation that appear one +- IP2, guess what it does + +Very strangely, they didn't have a case for "draw IPs, but don't do interpolation". (TFRAG had this, but it also had a totally different interpolation scheme). + +They had an absolutely wild scheme to jump between the piplined loops, but we can look at the main loop bodies for simplicity. + +ETIE seems to have the same 4 loops (L16, L24, L34, L37). + +We can look at BP1 as it's the simplest loop. Hopefully we can derive most stuff from here. +``` +L16: + mtir vi01, vf11.x | addw.xy vf20, vf20, vf03 + mfp.x vf30, P | add.xy vf21, vf21, vf14 + mtir vi04, vf16.w | mulaw.zw ACC, vf10, vf00 + esum.xyzw P, vf13 | mulz.xyz vf14, vf16, vf22 + lqi.xyz vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf20, vi11 | mul.xyz vf28, vf24, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf20, vf20, Q + lqi.xyzw vf17, vi09 | maddz.xyzw vf18, vf09, vf17 + lq.xyz vf30, 770(vi01) | mulax.xyzw ACC, vf02, vf12 + lqi.xy vf27, vi09 | madday.xyzw ACC, vf03, vf12 + iadd vi02, vi02, vi12 | maddz.xyzw vf23, vf04, vf12 + iadd vi06, vi02, vi13 | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf30, 1(vi02) | maddax.xyzw ACC, vf05, vf17 + esadd.xyz P, vf14 | madday.xyzw ACC, vf06, vf17 + mfp.x vf13, P | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | subw.z vf23, vf23, vf00 + sq.xyzw vf20, 0(vi06) | maddz.xyz vf17, vf07, vf17 + sq.xyzw vf01, 1(vi06) | addw.z vf21, vf00, vf00 + sq.xyzw vf19, 2(vi02) | mulx.xy vf22, vf22, vf13 + div Q, vf00.w, vf18.w | mul.xy vf21, vf21, Q + ibeq vi14, vi02, L18 | mula.xy ACC, vf10, vf16 + sq.xyzw vf19, 2(vi06) | mul.xyz vf13, vf17, vf23 + mtir vi01, vf11.y | addw.xy vf21, vf21, vf03 + mfp.x vf30, P | add.xy vf22, vf22, vf14 + mtir vi05, vf17.w | mulaw.zw ACC, vf10, vf00 + esum.xyzw P, vf13 | mulz.xyz vf14, vf17, vf23 + lqi.xyz vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf21, vi11 | mul.xyz vf28, vf25, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf21, vf21, Q + lq.xyz vf30, 770(vi01) | maddz.xyzw vf18, vf09, vf16 + lqi.xyzw vf16, vi09 | mulax.xyzw ACC, vf02, vf12 + lqi.xy vf24, vi09 | madday.xyzw ACC, vf03, vf12 + iadd vi03, vi03, vi12 | maddz.xyzw vf20, vf04, vf12 + iadd vi06, vi03, vi13 | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf30, 1(vi03) | maddax.xyzw ACC, vf05, vf16 + esadd.xyz P, vf14 | madday.xyzw ACC, vf06, vf16 + mfp.x vf13, P | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi03) | subw.z vf20, vf20, vf00 + sq.xyzw vf21, 0(vi06) | maddz.xyz vf16, vf07, vf16 + sq.xyzw vf01, 1(vi06) | addw.z vf22, vf00, vf00 + sq.xyzw vf19, 2(vi03) | mulx.xy vf23, vf23, vf13 + div Q, vf00.w, vf18.w | mul.xy vf22, vf22, Q + ibeq vi14, vi03, L20 | mula.xy ACC, vf10, vf17 + sq.xyzw vf19, 2(vi06) | mul.xyz vf13, vf16, vf20 + mtir vi01, vf11.z | addw.xy vf22, vf22, vf03 + mfp.x vf30, P | add.xy vf23, vf23, vf14 + mtir vi02, vf16.w | mulaw.zw ACC, vf10, vf00 + esum.xyzw P, vf13 | mulz.xyz vf14, vf16, vf20 + lqi.xyz vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf22, vi11 | mul.xyz vf28, vf26, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf22, vf22, Q + lq.xyz vf30, 770(vi01) | maddz.xyzw vf18, vf09, vf17 + lqi.xyzw vf17, vi09 | mulax.xyzw ACC, vf02, vf12 + lqi.xy vf25, vi09 | madday.xyzw ACC, vf03, vf12 + iadd vi04, vi04, vi12 | maddz.xyzw vf21, vf04, vf12 + iadd vi06, vi04, vi13 | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf30, 1(vi04) | maddax.xyzw ACC, vf05, vf17 + esadd.xyz P, vf14 | madday.xyzw ACC, vf06, vf17 + mfp.x vf13, P | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi04) | subw.z vf21, vf21, vf00 + sq.xyzw vf22, 0(vi06) | maddz.xyz vf17, vf07, vf17 + sq.xyzw vf01, 1(vi06) | addw.z vf23, vf00, vf00 + sq.xyzw vf19, 2(vi04) | mulx.xy vf20, vf20, vf13 + div Q, vf00.w, vf18.w | mul.xy vf23, vf23, Q + ibeq vi14, vi04, L22 | mula.xy ACC, vf10, vf16 + sq.xyzw vf19, 2(vi06) | mul.xyz vf13, vf17, vf21 + mtir vi01, vf11.w | addw.xy vf23, vf23, vf03 + mfp.x vf30, P | add.xy vf20, vf20, vf14 + mtir vi03, vf17.w | mulaw.zw ACC, vf10, vf00 + esum.xyzw P, vf13 | mulz.xyz vf14, vf17, vf21 + lqi.xyz vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf23, vi11 | mul.xyz vf28, vf27, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf23, vf23, Q + lqi.xyzw vf16, vi09 | maddz.xyzw vf18, vf09, vf16 + lq.xyz vf30, 770(vi01) | mulax.xyzw ACC, vf02, vf12 + lqi.xy vf26, vi09 | madday.xyzw ACC, vf03, vf12 + iadd vi05, vi05, vi12 | maddz.xyzw vf22, vf04, vf12 + iadd vi06, vi05, vi13 | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf30, 1(vi05) | maddax.xyzw ACC, vf05, vf16 + esadd.xyz P, vf14 | madday.xyzw ACC, vf06, vf16 + mfp.x vf13, P | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi05) | subw.z vf22, vf22, vf00 + sq.xyzw vf23, 0(vi06) | maddz.xyz vf16, vf07, vf16 + sq.xyzw vf01, 1(vi06) | addw.z vf20, vf00, vf00 + sq.xyzw vf19, 2(vi05) | mulx.xy vf21, vf21, vf13 + lqi.xyzw vf11, vi10 | nop + div Q, vf00.w, vf18.w | mul.xy vf20, vf20, Q + ibne vi14, vi05, L16 | mula.xy ACC, vf10, vf17 + sq.xyzw vf19, 2(vi06) | mul.xyz vf13, vf16, vf22 +``` +Dealing with a chunk of VU1 code like this is hard, especially without much context. + +The best strategy I have is to start with the store at the end, and work backward. Here's what I did to figure out the envmap math. +Note that I removed instructions related to memory addressing. +``` +;; inputs + +;; vi08 normal data +;; vi09 point/tex data + +;; - vf02, vf03, vf04 rotation +;; - vf05, vf06, vf07, vf08 affine transform +;; - vf09, vf10 perspective + +L16: + mtir vi01, vf11.x | addw.xy vf20, vf20, vf03 + mfp.x vf30, P | add.xy vf21, vf21, vf14 + mtir vi04, vf16.w | mulaw.zw ACC, vf10, vf00 + esum.xyzw P, vf13 | mulz.xyz vf14, vf16, vf22 + + lqi.xyz vf12, vi08 | ;; LOAD normal + | + | + lqi.xyzw vf17, vi09 | ;; LOAD point + | mulax.xyzw ACC, vf02, vf12 ;; rotate normal + lqi.xy vf27, vi09 | madday.xyzw ACC, vf03, vf12 ;; rotate normal + | maddz.xyzw vf23, vf04, vf12 ;; vf23 = rotated normal + | mulaw.xyzw ACC, vf08, vf00 ;; transform point + | maddax.xyzw ACC, vf05, vf17 ;; transform point + | madday.xyzw ACC, vf06, vf17 ;; transform point + | + | subw.z vf23, vf23, vf00 ;; nrm.z -= 1.0 + | maddz.xyz vf17, vf07, vf17 ;; vf17 = transformed point + | + | + | + | + | mul.xyz vf13, vf17, vf23 ;; vf13 = nrm .* ptx + | + | + | + esum.xyzw P, vf13 | mulz.xyz vf14, vf17, vf23 + | + | + | + | + | + | + | + | + | + esadd.xyz P, vf14 | + mfp.x vf13, P | + | + | + | addw.z vf22, vf00, vf00 + | mulx.xy vf23, vf23, vf13 + | + | mula.xy ACC, vf10, vf17 ;; acc build 1 + | -- 13 BAD + | addw.xy vf22, vf22, vf03 + mfp.x vf30, P | add.xy vf23, vf23, vf14 + | mulaw.zw ACC, vf10, vf00 ;; acc build 2 + | -- 14 BAD + | + | + rsqrt Q, vf02.w, vf30.x | + | maddz.xyzw vf18, vf09, vf17 ;; acc star + | + | + | + | + | + | + | + | subw.z vf21, vf21, vf00 + | + | addw.z vf23, vf00, vf00 + | + div Q, vf00.w, vf18.w | mul.xy vf23, vf23, Q + | + | + | addw.xy vf23, vf23, vf03 + | add.xy vf20, vf20, vf14 + | + | + | mul.xyz vf19, vf18, Q + sqi.xyzw vf23, vi11 | mul.xyz vf28, vf27, Q + | mul.xyz vf23, vf23, Q + | + | + | + | + | + sq.xyzw vf30, 1(vi05) | + esadd.xyz P, vf14 | + mfp.x vf13, P | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi05) | + sq.xyzw vf23, 0(vi06) | + sq.xyzw vf01, 1(vi06) | + sq.xyzw vf19, 2(vi05) | + | + | + | + sq.xyzw vf19, 2(vi06) | +``` + +These are the stores. Each vertex has 3 QW (texture coords, rgba, xyz) +``` +;; STORES +sq.xyzw vf28, 0(vi05) +sq.xyzw vf23, 0(vi06) + +sq.xyzw vf30, 1(vi05) +sq.xyzw vf01, 1(vi06) + +;; xyz of first and second draw are the same, nice +sq.xyzw vf19, 2(vi05) +sq.xyzw vf19, 2(vi06) +``` + +### Trace back `vf19` first (the vertex position): +``` +;; load the vertex position in world space +lqi.xyzw vf17, vi09 + +;; apply affine transform stored in matrix vf05, vf06, vf07, vf08 +;; (likely transforms from world to camera space, no projection) +mulaw.xyzw ACC, vf08, vf00 +maddax.xyzw ACC, vf05, vf17 +madday.xyzw ACC, vf06, vf17 +maddz.xyz vf17, vf07, vf17 + +;; at this point vf17 contains the point relative to the camera. + +;; apply perspective (using vf09/vf10 as the perspective coefficients) +mula.xy ACC, vf10, vf17 +mulaw.zw ACC, vf10, vf00 +maddz.xyzw vf18, vf09, vf17 + +;; perspective divide +div Q, vf00.w, vf18.w +mul.xyz vf19, vf18, Q + +;; convert to int for the GS format +ftoi4.xyz vf19, vf19 + +;; store +sq.xyzw vf19, 2(vi05) +sq.xyzw vf19, 2(vi06) +``` +This is relatively uninteresting - the only difference is that `hvdf-offset` is not applied in the usual way. But, in `etie-vu1`, we see that `hvdf-off` is invovled in computing the perspective coefficients (likely vf08, vf09), so it still enters in. This is different from normal, but it makes some sense - there's an intermediate result of `vf17` being the point relative to the camera, which will be useful to compute the reflection direction for environment mapping. + +### Trace back `vf01` (the color, I think of the envmap): +``` +;; this comes from outside the loop. It's a constant! +;; note that vi10 comes from the result of xtop, so it's the input from the EE asm function. +;; (which we don't have a type for, darn) +lq.xyzw vf01, 7(vi10) +``` +leave this for now, we'll have to dig through the EE asm later + +### Trace back `vf30` (the color of the base draw): +``` +;; load from input (it's the address of interpolated color) +lqi.xyzw vf11, vi10 +;; load interpolated color +mtir vi01, vf11.w +;; store interpolated color +lq.xyz vf30, 770(vi01) +``` +This is basically the same as normal TIE (there's some additional logic because the 4 vertices use the x, y, z, w components of the `vf11`, which is only reloaded once per 4). The existing `extract_tie` has the color_index_index nonsense. + +The difference is that alpha is set from earlier, and constant: +``` +;; outside loop +lq.w vf30, 6(vi10) +``` +This is held constant for an entire fragment and computed on the EE. It's likely an envmap intensity scale used to fade it out. + + + +### Trace back vf28, texture coords of first draw + +``` +lqi.xy vf27, vi09 +mul.xyz vf28, vf27, Q ;; Q is the perspective divisor +``` +Kind of as expected, this just loads and multiplies (required for GS perspective-correct tex). + +### Trace back `vf23`: the second draws texture coordinates +I'd expect this to compute: +- place a plane with normal parallel to the vertex normal, intersecting the vertex +- place a vector from the camera to the vertex +- reflect this vertex +- somehow map this direction onto the envmap texture + + +``` +;; reg names: +;; vf13 dot +;; vf14 rfl +;; vf17 pos +;; vf23 nrm + + +;; load the normal and point. +lqi.xyz vf12, vi08 +lqi.xyzw vf17, vi09 + +;; rotate the normal +mulax.xyzw ACC, vf02, vf12 ;; rotate normal +madday.xyzw ACC, vf03, vf12 ;; rotate normal +maddz.xyzw vf23, vf04, vf12 ;; vf23 = rotated normal + +;; transform the point +mulaw.xyzw ACC, vf08, vf00 ;; transform point +maddax.xyzw ACC, vf05, vf17 ;; transform point +madday.xyzw ACC, vf06, vf17 ;; transform point +maddz.xyz vf17, vf07, vf17 ;; vf17 = transformed point + +;; nrm.z -= 1 +subw.z vf23, vf23, vf00 + +;; dot = nrm.xyz * pt.xyz +mul.xyz vf13, vf17, vf23 +esum.xyzw P, vf13 +mfp.x vf13, P + +;; rfl = pt.xzy * nrm.z +mulz.xyz vf14, vf17, vf23 +;; Q_envmap = vf02.w / norm(rfl.xyz) +esadd.xyz P, vf14 +mfp.x vf30, P +rsqrt Q, vf02.w, vf30.x + +;; nrm.xy *= dot.x +mulx.xy vf23, vf23, vf13 + +;; perspective transform +mula.xy ACC, vf10, vf17 ;; acc build 1 +mulaw.zw ACC, vf10, vf00 ;; acc build 2 +maddz.xyzw vf18, vf09, vf17 ;; acc star + +;; nrm.xy += rfl.xy +add.xy vf23, vf23, vf14 + +;; nrm.z = 1.0 +addw.z vf23, vf00, vf00 + +;; nrm.xy *= Q_envmap +mul.xy vf23, vf23, Q + +;; perspective divide +div Q, vf00.w, vf18.w + +;; nrm.xy += vf03.w +addw.xy vf23, vf23, vf03 + +;; that's the non-persp_Q multiplied envmap value. +``` + +### Mysterious `sqi.xyzw vf23, vi11` +Highly observant readers will have noticed this instruction, which stashes the texture coordinates (before perspective divide) to some address. These are picked up later on in the IP drawing routines. + +### Summary + +Main results: +- transformation is like normal, but computes point in camera space (no perspect) first. +- perspective transformation is expressed differently, but same math in the end +- second draw rgba computed on EE (constant per frag) +- first draw alpha computed on EE (constant per frag) +- first draw tex coord is usual. +- we got the math for envmapping! (at least for BP1 drawing) +- the normals are uploaded to VU (at least for BP1 drawing) + +And that all for today (about 4.5 hours) + +## Where do those stupid normals come from? + +The `vi08` is the pointer to the normals, initialized to `0x85 = 133` +``` +iaddiu vi08, vi00, 0x85 ;; normals start at 0x85 +``` +the fixed address here is a super awesome clue. + +Looking in `tie-work` +``` +;; in *prototype-tie-work* +;; (upload-envmap-3 dma-packet :inline :offset-assert 256) +:upload-envmap-3 (new 'static 'dma-packet + :dma (new 'static 'dma-tag :id (dma-tag-id ref)) + :vif1 (new 'static 'vif-tag :imm #x84 :cmd (vif-cmd unpack-v4-8)) + ) +``` +which is uploading some data to an offset of `0x84`, from int8's. The `int8` is promising because 8-bits seems reasonable for normals on a ps2 game. + +There's also: +``` +(deftype generic-tie-normal (structure) + ((x int8 :offset-assert 0) + (y int8 :offset-assert 1) + (z int8 :offset-assert 2) + (dummy int8 :offset-assert 3) + ) + ) +``` +which is vaguely promising that TIE envmap has 8-bit normals. + +Uploading normals is done per-model, so likely in `draw-inline-array-prototype-tie-asm`. + +We're looking for a place where they use the `upload-envmap-3` DMA template. They will modify this template, then store it in a scratchpad buffer (later DMA'd to the DMA buffer, which is later DMA'd to VIF1 for unpack) +``` +(deftype dma-tag (uint64) + ((qwc uint16 :offset 0) ;; quadword count + (pce uint8 :offset 26 :size 2) ;; priority (source mode) + (id dma-tag-id :offset 28 :size 3) ;; ID (what the tag means) + (irq uint8 :offset 31 :size 1) ;; interrupt at the end? + (addr uint32 :offset 32 :size 31) ;; address (31 bits) + (spr uint8 :offset 63 :size 1) ;; spr or not flag. + ) + ) + +(deftype vif-unpack-imm (uint16) + ((addr uint16 :offset 0 :size 10) + (usn uint8 :offset 14 :size 1) + (flg uint8 :offset 15 :size 1) + ) + ) + +(deftype vif-tag (uint32) + ((imm uint16 :offset 0 :size 16) + (num uint8 :offset 16 :size 8) + (cmd vif-cmd :offset 24 :size 7) + (irq uint8 :offset 31 :size 1) + (msk uint8 :offset 28 :size 1) + ) + ) +``` + +Here is this part (found that `s0` is the `prototype-work-structure`, searched for 256 offset). Many unrelated instructions removed: +``` + ;; store some qwc value into the dma-tag's qwc (number of qw read from EE) + sh ra, 256(s0) + ;; multiply qwc by 4 (to get the number of qw written on VU) + dsll ra, ra, 2 + ;; store some source pointer in the addr field of the dma tag + sw a2, 260(s0) + ;; store the unpack num + sb ra, 270(s0) + ;; load the entire template + lq s5, 256(s0) + ;; store the entire template + sq s5, 48(a3) +``` + +Tracing back: +``` +lhu ra, 50(t6) +lw a2, 52(t6) +``` +which I'm guessing is: +``` +(deftype tie-fragment (drawable) + ((gif-ref (inline-array adgif-shader) :offset 4) + (point-ref uint32 :offset 8) + ... + (normal-count uint16 :offset 54) + (normal-ref uint32 :offset 56) ;; <- NORMALS! +``` +so `normal-count` looks like QW of normals on the EE. + +Let's try grabbing this data in the extraction tool: +```cpp + if (version > GameVersion::Jak1) { + u16 normals_qwc = read_plain_data_field(ref, "normal-count", dts); + if (normals_qwc) { + normals.resize(16 * normals_qwc); + auto normals_data_ref = deref_label(get_field_ref(ref, "normal-ref", dts)); + memcpy_plain_data((u8*)normals.data(), normals_data_ref, normals_qwc * 16); + // print them for debug +``` + +This looks very reasonable! +``` +Normals: +-41 102 62 0 +-47 102 58 0 +-41 102 62 0 +-47 102 58 0 +-53 102 53 0 +-53 102 53 0 +... +``` +Some promising patterns: +- `w` component is 0. I don't see it used in the VU code, and generic tie calls it "dummy". +- Normals in a fragment are similar-ish +- The length of a few normals I spot-checked is about 128 +- There's some fragments where the Y normals are all 0 (vertical surfaces), which seems like a common thing. + +Now the challenge is matching up these normals with the points in the mesh. Some observations: +- The 0x84/0x85 offset mismatch doesn't matter - 0x84 is the right number. There's a `lq.xyz vf12, 132(vi00)` (0x84) for the first point. +- I don't see any magic flags at the end of the normals. I was half expecting some confusing table of indices at the end, but it doesn't look like it. + +Passing this through the `extract_tie.cpp` maze: +```cpp +// normals +const auto& normal_data = proto.geometry[geo].tie_fragments[frag_idx].normals; +frag_info.normal_data_packed.resize(normal_data.size() / 4); +for (size_t ni = 0; ni < normal_data.size() / 4; ni++) { + for (int j = 0; j < 4; j++) { + frag_info.normal_data_packed[ni][j] = normal_data[ni * 4 + j]; + } +} +``` + +Doing some debug, we find something amazing: They just stored one normal for each unique vertex! This will be super easy to extract. + +I thought this wasn't the case because they were stashing some texture coordinates related to envmap and reusing them later. But I now realize this might be just needed for interpolating from a base point. (you want to the interp before applying perspective correct texturing multiply). + +So now I will try to get the normals into the FR3 mesh, and see if we can draw them. + +The basic idea is: + +when processing vertices (per proto), add a line like this (this goes in draw order) +```cpp +vertex_info.nrm = frag.get_normal_if_present(normal_table_offset++); +``` + +Expand the on-disk FR3 format to store a normal per tie proto vertex, and populate this from vertex info above when converting formats. +```cpp +struct PackedTieVertices { + struct Vertex { + float x, y, z; + float s, t; + s8 nx, ny, nz; // added + }; +``` + +When the TIE mesh is loaded, it is de-instanced, so we need to rotate the normals (and convert to opengl format). The speed of this operation isn't important because it happens at load time (in the loader thread too): +```cpp +math::Vector unpack_tie_normal(const std::array& mat, + s8 nx, + s8 ny, + s8 nz) { + // rotate the normal + math::Vector3f nrm = math::Vector3f::zero(); + nrm += mat[0].xyz() * nx; + nrm += mat[1].xyz() * ny; + nrm += mat[2].xyz() * nz; + // convert to s16 for OpenGL renderer + nrm.normalize(INT16_MAX - 2); + return nrm.cast(); +} +``` + +### Drawing envmap +Next I refactored TIE3 to do a second draw pass when doing an envmap category, with a separate shader. + +I passed the normals through, and used them as RGB. I'd expect to see horizontal surface (+y normal) be green, and the usual "smooth rainbow on curves" effect. Which I did! It worked on the first try. + +PICTURE + +### Envmap Math Start +The envmap math computes both the vertex location and the reflection. We can start with the vertex location because that's super easy to verify. + +I needed to add two new matrices to `add-pc-tfrag3-data` so the GOAL code sends the renderer the required information. + +These are used to compute `persp0`/`persp1`, which can then be used for perspective projection of the vertex position +```cpp + math::Vector4f perspective[2]; + float inv_fog = 1.f / render_state->camera_fog[0]; + auto& hvdf_off = render_state->camera_hvdf_off; + float pxx = render_state->camera_persp[0].x(); + float pyy = render_state->camera_persp[1].y(); + float pzz = render_state->camera_persp[2].z(); + float pzw = render_state->camera_persp[2].w(); + float pwz = render_state->camera_persp[3].z(); + float scale = pzw * inv_fog; + perspective[0].x() = scale * hvdf_off.x(); + perspective[0].y() = scale * hvdf_off.y(); + perspective[0].z() = scale * hvdf_off.z() + pzz; + perspective[0].w() = scale; + + perspective[1].x() = pxx; + perspective[1].y() = pyy; + perspective[1].z() = pwz; + perspective[1].w() = 0; + + set_uniform(m_etie_uniforms.persp0, perspective[0]); + set_uniform(m_etie_uniforms.persp1, perspective[1]); +``` + +I wrote a shader for just vertex position (no reflection) to test this: +```cpp +vec4 vf17 = cam_no_persp[3]; +vf17 += cam_no_persp[0] * position_in.x; +vf17 += cam_no_persp[1] * position_in.y; +vf17 += cam_no_persp[2] * position_in.z; + +//;; perspective transform +//mula.xy ACC, vf10, vf17 ;; acc build 1 +//mulaw.zw ACC, vf10, vf00 ;; acc build 2 +vec4 p_proj = vec4(persp1.x * vf17.x, persp1.y * vf17.y, persp1.z, persp1.w); +//maddz.xyzw vf18, vf09, vf17 ;; acc star +p_proj += persp0 * vf17.z; + +//;; perspective divide +//div Q, vf00.w, vf18.w +float pQ = 1.f / p_proj.w; + +vec4 transformed = p_proj * pQ; + +// usual rest of the stuff +``` +and it worked, but brought up a really annoying problem - the math is different by a tiny amount (floating point rounding, an impossible to see difference), so sometimes the depth test fails when drawing over something. + +So, we have to refactor TIE to use the similar projection math. + +After that, things aren't a flickerly/fighting mess. + +In theory, we could see fighting in between ETIE and TIE geometry, but I don't see that yet. + +### Envmap Math 2 + +I tried implementing the envmap math, and it seems slightly wrong. + +```cpp + // nrm.z -= 1 + //subw.z vf23, vf23, vf00 + nrm_vf23.z -= 1.f; + + // dot = nrm.xyz * pt.xyz + //mul.xyz vf13, vf17, vf23 + //esum.xyzw P, vf13 + //mfp.x vf13, P + float nrm_dot = dot(vf17.xyz, nrm_vf23); + + // rfl = pt.xzy * nrm.z + //mulz.xyz vf14, vf17, vf23 + vec3 rfl_vf14 = vf17.xyz * nrm_vf23.z; + + //;; Q_envmap = vf02.w / norm(rfl.xyz) + //esadd.xyz P, vf14 + //mfp.x vf30, P + //rsqrt Q, vf02.w, vf30.x + float Q_envmap = -0.5 / length(rfl_vf14); + + // + //;; nrm.xy *= dot.x + //mulx.xy vf23, vf23, vf13 + nrm_vf23.xy *= nrm_dot; + //;; perspective transform + //mula.xy ACC, vf10, vf17 ;; acc build 1 + //mulaw.zw ACC, vf10, vf00 ;; acc build 2 + vec4 p_proj = vec4(persp1.x * vf17.x, persp1.y * vf17.y, persp1.z, persp1.w); + //maddz.xyzw vf18, vf09, vf17 ;; acc star + p_proj += persp0 * vf17.z; + + // + //;; nrm.xy += rfl.xy + //add.xy vf23, vf23, vf14 + nrm_vf23.xy += rfl_vf14.xy; + // + //;; nrm.z = 1.0 + //addw.z vf23, vf00, vf00 + nrm_vf23.z = 1.0; + // + //;; nrm.xy *= Q_envmap + //mul.xy vf23, vf23, Q + nrm_vf23.xy *= Q_envmap; + // + //;; perspective divide + //div Q, vf00.w, vf18.w + float pQ = 1.f / p_proj.w; + // + //;; nrm.xy += vf03.w + //addw.xy vf23, vf23, vf03 + nrm_vf23.xy += 0.5; + tex_coord = nrm_vf23; +``` + +after staring at it for a while, I didn't see any bugs, so I became suspicious of my rotation matrix: +- I assume this matrix is just `cam_R_world` +- In the game, they upload separate "rotate normal" and "transform point" matrices. I know I have transform point right. I assumed that "rotate normal" is just the upper 3x3 of "transform point"... but why would they upload separate matrices if that was the case? + +Now we have to track down how this matrix is built. + +This is the VU program that uses the matrix to rotate the normal: +``` +mulax.xyzw ACC, vf02, vf12 +madday.xyzw ACC, vf03, vf12 +maddz.xyzw vf23, vf04, vf12 +``` + +This is where the matrix is loaded: +``` +lq.xyzw vf02, 4(vi10) +lq.xyzw vf03, 5(vi10) +lq.xyzw vf04, 6(vi10) +``` +where `vi10` is the register set from `xtop` (the per-instance data). At offsets 0, 1, 2, 3 are the tranformation matrix. + +Blindly guessing didn't make much progress... time to dive into `draw-inline-array-instance-tie`'s envmap path. + +At entry: +- `t0` is `instance-tie-work` +- `t8` is matrix +- `t9` is bucket + +Let's go block by block: +``` +L299: + beq s4, r0, L288 + lw s3, 532(t0) ;; (-> itw use-etie) +``` +Not sure what the branch checks. Jumps back to where we came from. + +``` +B57: + vcallms 29 +``` +Run VU0 microprogram. I think the `background-vu` block is loaded, so: +``` + lq.xyzw vf24, 4(vi00) | nop + lq.xyzw vf25, 5(vi00) | nop + lq.xyzw vf26, 6(vi00) | nop :e + lq.xyzw vf27, 7(vi00) | nop +``` +these registers are, I think, set from `set-background-regs!`: +``` + (.lvf vf24 (&-> v1-0 camera-rot quad 0)) + (.lvf vf25 (&-> v1-0 camera-rot quad 1)) + (.lvf vf26 (&-> v1-0 camera-rot quad 2)) + (.lvf vf27 (&-> v1-0 camera-rot trans quad)) +``` + + +Back to the EE program: +``` + sw s5, 512(t0) ;; set flags, who care + beq s3, s7, L308 ;; branch somewhere if use-etie if #f, who cares + lw gp, 108(t0) ;; dist-test.w, who knows? + + lw s5, 84(t8) ;; see note below + sll r0, r0, 0 + sqc2 vf5, 448(t0) ;; set fog temp, no idea. + bne s5, r0, L302 ;; if we have envmap-mid's go to L302 + lw s5, 104(t0) ;; another dist-test.z +``` +I think earlier code stashed a `guard-flag` at `84(t8)` (maybe). Either way not super important because whether we take this branch doesn't change much in the end... + + +(reordered, removed nops) +``` +L300: + ;; increment count in the bucket for envmap-mid-count + lh gp, 120(t9) + daddiu gp, gp, 1 + sh gp, 120(t9) + + ;; something to set up the dma chain + lw s5, 84(t9) ;; envmap-mid-next + addiu ra, ra, 128 + sw ra, 84(t9) + + ;; morph temp + sqc2 vf29, 432(t0) + + ;; matrix multiplies... looks like camera rot being used to rotate. + vmulax.xyzw acc, vf24, vf10 + vmadday.xyzw acc, vf25, vf10 + vmaddz.xyzw vf16, vf26, vf10 + + vmulax.xyzw acc, vf24, vf11 + vmadday.xyzw acc, vf25, vf11 + vmaddz.xyzw vf17, vf26, vf11 + + ;; count stuff + lbu s3, 134(t9) + lhu gp, 144(t9) + lbu s4, 138(t9) + + beq r0, r0, L303 + sll r0, r0, 0 +``` +so we're mid way through rotating a matrix vf10, vf11, vf12, vf13. + +``` + ;; rest of the mult... + vmulax.xyzw acc, vf24, vf12 + vmadday.xyzw acc, vf25, vf12 + vmaddz.xyzw vf18, vf26, vf12 + + ;; this one's different + vmulax.xyzw acc, vf24, vf13 + vmadday.xyzw acc, vf25, vf13 + vmaddaz.xyzw acc, vf26, vf13 + vmaddw.xyzw vf19, vf27, vf0 +``` +weird. no idea what the `vmaddw` is adding, but I am 99% sure it won't be part of the rotation matrix, so ignore for now! + +``` + lq s2, 224(t0) ;; upload-color-2 tmpl + lq ra, 240(t0) ;; upload-color-ret tmpl +``` +These DMA templates I don't care about. + +``` + dsll gp, gp, 4 + daddu s4, s4, t9 + mfc1 s1, f15 + addiu t4, t4, 8 + qmtc2.i vf14, s1 +``` +no idea, doesn't look important. + +I think, at `t8`, we're now building: +``` +(deftype etie-matrix (structure) + ((rmtx matrix :inline :offset-assert 0) + (nmtx matrix3 :inline :offset-assert 64) + (morph float :offset 76) + (fog float :offset 92) + (fade uint32 :offset 108) + (tint qword :inline :offset-assert 112) + ) + :method-count-assert 9 + :size-assert #x80 + :flag-assert #x900000080 + ) +``` +Whwere `rmtx` is the camera matrix not including perspective, and `nmtx` is the thing I need to figure out. The multiplies above seemed to compute `rmtx` in vf16, 20. + + +``` + sqc2 vf16, 0(t8) ;; first matrix store? + sqc2 vf17, 16(t8) + sqc2 vf18, 32(t8) + sqc2 vf19, 48(t8) +``` +and, as expected, they do! + +(regs at this point, it's getting confusing): +``` +vf16, vf17, vf18, vf19: rmtx (includes translation, but no perspective) +vf10, vf11, vf13, vf14: tie instance matrix +``` + +Here is the extracted part of this math. +``` + vmulx.xyz vf16, vf10, vf14 + + vopmula.xyz acc, vf11, vf16 + + vopmsub.xyz vf17, vf16, vf11 + + vopmula.xyz acc, vf16, vf17 + + vopmsub.xyz vf17, vf17, vf16 + + vmul.xyz vf14, vf17, vf17 + + vmulax.w acc, vf0, vf14 + vmadday.w acc, vf0, vf14 + vmaddz.w vf14, vf0, vf14 + vrsqrt Q, vf0.w, vf14.w + + vmulax.xyzw acc, vf24, vf16 + vmadday.xyzw acc, vf25, vf16 + vmaddz.xyzw vf10, vf26, vf16 + + vwaitq + vmulq.xyz vf17, vf17, Q + + vopmula.xyz acc, vf16, vf17 + vopmsub.xyz vf18, vf17, vf16 + + vmulax.xyzw acc, vf24, vf17 + vmadday.xyzw acc, vf25, vf17 + vmaddz.xyzw vf11, vf26, vf17 + + vmulax.xyzw acc, vf24, vf18 + vmadday.xyzw acc, vf25, vf18 + vmaddz.xyzw vf12, vf26, vf18 + + sqc2 vf10, -112(t8) + sqc2 vf11, -96(t8) + sqc2 vf12, -80(t8) +``` + +which just ended up computing the inverse transpose. I tried my own implementation, and their implementation, and same problems. + +## Frustration +At this point, I didn't take good notes for a few hours. The summary is: +- I thought things were still wrong +- I implemented emerc's math in the etie shader +- Realized we were using the wrong envmap textures. +- With the right textures, things looked better +- still some uncertainty with the length of normals. +- A few possible options: + - using emerc, which doesn't care about normal length + - use etie, normalize normals + - use etie, don't normalize, assume magic number in TIE EE asm was 1.0 + +For now I'm using `emerc`, but it's easy to switch. I think once I get everything else working properly, it will be easier to compare them. + +## Tint +The envmap has some color applied. Right now, I put some constant white, but it's clearly not right. In the VU program, it just copies the color from the input. I found a block of code that looks promising for the color: + +``` +lw t9, 168(t9) ;; tint color from bucket (4x u8's) +pextlb t9, t9, r0 ;; unpack tint from bucket to u16's +dsll s2, s2, 5 ;; ?? maybe fade or something +pextlh t9, r0, t9 ;; unpack tint from bucket to u32's +qmtc2.i vf9, s2 ;; ?? +psraw t9, t9, 3 ;; shift bucket tint +sll r0, r0, 0 +qmtc2.i vf14, t9 ;; bucket tint +sll r0, r0, 0 +lqc2 vf15, 416(t0) ;; envmap-tod from instance-tie-work +vitof12.xyzw vf9, vf9 ;; bucket to float +sll r0, r0, 0 +vitof12.xyzw vf14, vf14 ;; ?? to float +sll r0, r0, 0 +vmul.xyzw vf15, vf15, vf14 ;; multiply by envmap +sll r0, r0, 0 +vmulx.xyzw vf15, vf15, vf9 ;; scale by ?? +``` +I'm assuming the `??` bit is to fade it out (would also explain why this is done per-instance). But we need `envmap-tod`... + +Which is set in normal GOAL code for once: +``` +(set! (-> *instance-tie-work* tod-env-color quad) (-> *time-of-day-context* current-env-color quad)) +``` + +I ended up sneaking the tint color into the actual vertices (a bit wasteful, but I had a spare 4 bytes), then setting the time of day color through a uniform. + + +## Final results on the normal thing + +I'm pretty sure the unknown normal matrix scaling factor is designed to give normals the right length, even if the instance matrix isn't a pure rotation. + +EMERC (built-in normalization) and ETIE-with-add-normalization are idential. + +Cheating the normal length makes things look obviously worse/wrong. \ No newline at end of file diff --git a/game/graphics/opengl_renderer/BucketRenderer.h b/game/graphics/opengl_renderer/BucketRenderer.h index 3db73783b..6a56a0ddb 100644 --- a/game/graphics/opengl_renderer/BucketRenderer.h +++ b/game/graphics/opengl_renderer/BucketRenderer.h @@ -48,7 +48,15 @@ struct SharedRenderState { LevelVis occlusion_vis[6]; math::Vector4f camera_planes[4]; + + // including transformation, rotation, perspective math::Vector4f camera_matrix[4]; + + // including transformation, rotation + math::Vector4f camera_no_persp[4]; + + // just the perspective + math::Vector4f camera_persp[4]; math::Vector4f camera_hvdf_off; math::Vector4f camera_fog; math::Vector4f camera_pos; @@ -78,6 +86,7 @@ struct SharedRenderState { int bucket_for_vis_copy = 0; int num_vis_to_copy = 0; GameVersion version; + u64 frame_idx = 0; }; /*! diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index c0ac151c1..8ab9e0218 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -99,7 +99,7 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { init_bucket_renderer("ocean-mid-far", BucketCategory::OCEAN, BucketId::OCEAN_MID_FAR); for (int i = 0; i < 6; ++i) { -#define GET_BUCKET_ID_FOR_LIST(bkt1, bkt2, idx) ((int)bkt1 + ((int)bkt2 - (int)bkt1) * idx) +#define GET_BUCKET_ID_FOR_LIST(bkt1, bkt2, idx) ((int)(bkt1) + ((int)(bkt2) - (int)(bkt1)) * (idx)) init_bucket_renderer( fmt::format("tex-l{}-tfrag", i), BucketCategory::TEX, GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_TFRAG, BucketId::TEX_L1_TFRAG, i)); @@ -107,9 +107,13 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { fmt::format("tfrag-l{}-tfrag", i), BucketCategory::TFRAG, GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_L0_TFRAG, BucketId::TFRAG_L1_TFRAG, i), std::vector{tfrag3::TFragmentTreeKind::NORMAL}, false, i); - init_bucket_renderer( + Tie3* tie = init_bucket_renderer( fmt::format("tie-l{}-tfrag", i), BucketCategory::TIE, GET_BUCKET_ID_FOR_LIST(BucketId::TIE_L0_TFRAG, BucketId::TIE_L1_TFRAG, i), i); + init_bucket_renderer( + fmt::format("etie-l{}-tfrag", i), BucketCategory::TIE, + GET_BUCKET_ID_FOR_LIST(BucketId::ETIE_L0_TFRAG, BucketId::ETIE_L1_TFRAG, i), tie, + tfrag3::TieCategory::NORMAL_ENVMAP); init_bucket_renderer( fmt::format("merc-l{}-tfrag", i), BucketCategory::MERC, GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_TFRAG, BucketId::MERC_L1_TFRAG, i)); @@ -131,6 +135,14 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { fmt::format("tfrag-t-l{}-alpha", i), BucketCategory::TFRAG, GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_T_L0_ALPHA, BucketId::TFRAG_T_L1_ALPHA, i), std::vector{tfrag3::TFragmentTreeKind::TRANS}, false, i); + init_bucket_renderer( + fmt::format("tie-t-l{}-alpha", i), BucketCategory::TIE, + GET_BUCKET_ID_FOR_LIST(BucketId::TIE_T_L0_ALPHA, BucketId::TIE_T_L1_ALPHA, i), tie, + tfrag3::TieCategory::TRANS); + init_bucket_renderer( + fmt::format("etie-t-l{}-alpha", i), BucketCategory::TIE, + GET_BUCKET_ID_FOR_LIST(BucketId::ETIE_T_L0_ALPHA, BucketId::ETIE_T_L1_ALPHA, i), tie, + tfrag3::TieCategory::TRANS_ENVMAP); init_bucket_renderer( fmt::format("tex-l{}-pris", i), BucketCategory::TEX, @@ -156,6 +168,14 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { fmt::format("tfrag-w-l{}-alpha", i), BucketCategory::TFRAG, GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_W_L0_WATER, BucketId::TFRAG_W_L1_WATER, i), std::vector{tfrag3::TFragmentTreeKind::WATER}, false, i); + init_bucket_renderer( + fmt::format("tie-w-l{}-water", i), BucketCategory::TIE, + GET_BUCKET_ID_FOR_LIST(BucketId::TIE_W_L0_WATER, BucketId::TIE_W_L1_WATER, i), tie, + tfrag3::TieCategory::WATER); + init_bucket_renderer( + fmt::format("etie-w-l{}-water", i), BucketCategory::TIE, + GET_BUCKET_ID_FOR_LIST(BucketId::ETIE_W_L0_WATER, BucketId::ETIE_W_L1_WATER, i), tie, + tfrag3::TieCategory::WATER_ENVMAP); #undef GET_BUCKET_ID_FOR_LIST } // 180 @@ -911,6 +931,7 @@ void OpenGLRenderer::dispatch_buckets(DmaFollower dma, ScopedProfilerNode& prof, bool sync_after_buckets) { m_render_state.version = m_version; + m_render_state.frame_idx++; switch (m_version) { case GameVersion::Jak1: dispatch_buckets_jak1(dma, prof, sync_after_buckets); diff --git a/game/graphics/opengl_renderer/Shader.cpp b/game/graphics/opengl_renderer/Shader.cpp index 25df695e4..e01d086d8 100644 --- a/game/graphics/opengl_renderer/Shader.cpp +++ b/game/graphics/opengl_renderer/Shader.cpp @@ -104,6 +104,8 @@ ShaderLibrary::ShaderLibrary(GameVersion version) { at(ShaderId::GLOW_PROBE_READ_DEBUG) = {"glow_probe_read_debug", version}; at(ShaderId::GLOW_PROBE_DOWNSAMPLE) = {"glow_probe_downsample", version}; at(ShaderId::GLOW_DRAW) = {"glow_draw", version}; + at(ShaderId::ETIE_BASE) = {"etie_base", version}; + at(ShaderId::ETIE) = {"etie", version}; for (auto& shader : m_shaders) { ASSERT_MSG(shader.okay(), "error compiling shader"); diff --git a/game/graphics/opengl_renderer/Shader.h b/game/graphics/opengl_renderer/Shader.h index 011c49245..95c047f0b 100644 --- a/game/graphics/opengl_renderer/Shader.h +++ b/game/graphics/opengl_renderer/Shader.h @@ -54,6 +54,8 @@ enum class ShaderId { GLOW_PROBE_READ_DEBUG = 27, GLOW_PROBE_DOWNSAMPLE = 28, GLOW_DRAW = 29, + ETIE_BASE = 30, + ETIE = 31, MAX_SHADERS }; diff --git a/game/graphics/opengl_renderer/background/Tie3.cpp b/game/graphics/opengl_renderer/background/Tie3.cpp index 1766cf047..3ad32be41 100644 --- a/game/graphics/opengl_renderer/background/Tie3.cpp +++ b/game/graphics/opengl_renderer/background/Tie3.cpp @@ -6,8 +6,8 @@ #include "third-party/imgui/imgui.h" -Tie3::Tie3(const std::string& name, int my_id, int level_id) - : BucketRenderer(name, my_id), m_level_id(level_id) { +Tie3::Tie3(const std::string& name, int my_id, int level_id, tfrag3::TieCategory category) + : BucketRenderer(name, my_id), m_level_id(level_id), m_default_category(category) { // regardless of how many we use some fixed max // we won't actually interp or upload to gpu the unused ones, but we need a fixed maximum so // indexing works properly. @@ -29,59 +29,95 @@ Tie3::~Tie3() { discard_tree_cache(); } -void Tie3::update_load(const LevelData* loader_data) { +void Tie3::init_shaders(ShaderLibrary& shaders) { + m_uniforms.decal = glGetUniformLocation(shaders[ShaderId::TFRAG3].id(), "decal"); + + m_etie_uniforms.persp0 = glGetUniformLocation(shaders[ShaderId::ETIE].id(), "persp0"); + m_etie_uniforms.persp1 = glGetUniformLocation(shaders[ShaderId::ETIE].id(), "persp1"); + m_etie_uniforms.cam_no_persp = glGetUniformLocation(shaders[ShaderId::ETIE].id(), "cam_no_persp"); + m_etie_uniforms.envmap_tod_tint = + glGetUniformLocation(shaders[ShaderId::ETIE].id(), "envmap_tod_tint"); + + m_etie_base_uniforms.decal = glGetUniformLocation(shaders[ShaderId::ETIE_BASE].id(), "decal"); + m_etie_base_uniforms.persp0 = glGetUniformLocation(shaders[ShaderId::ETIE_BASE].id(), "persp0"); + m_etie_base_uniforms.persp1 = glGetUniformLocation(shaders[ShaderId::ETIE_BASE].id(), "persp1"); + m_etie_base_uniforms.cam_no_persp = + glGetUniformLocation(shaders[ShaderId::ETIE_BASE].id(), "cam_no_persp"); +} + +/*! + * Load a TIE tree from FR3 data. + * This often causes stutters, so as much as possible, we move stuff to the loader, + * and this function just updates things to reference loader data. + */ +void Tie3::load_from_fr3_data(const LevelData* loader_data) { auto ul = scoped_prof("update-load"); const tfrag3::Level* lev_data = loader_data->level.get(); m_wind_vectors.clear(); - // We changed level! + + // We changed level! free opengl resources allocated for the previous discard_tree_cache(); + + // resize for the number of trees in this level. for (int geo = 0; geo < 4; ++geo) { m_trees[geo].resize(lev_data->tie_trees[geo].size()); } - size_t vis_temp_len = 0; - size_t max_draws = 0; - size_t max_num_grps = 0; u16 max_wind_idx = 0; - size_t time_of_day_count = 0; - size_t max_inds = 0; + // loop over all "geos" (level of details) for (u32 l_geo = 0; l_geo < tfrag3::TIE_GEOS; l_geo++) { + // loop over all trees for (u32 l_tree = 0; l_tree < lev_data->tie_trees[l_geo].size(); l_tree++) { auto ul = scoped_prof("load-tree"); size_t wind_idx_buffer_len = 0; size_t num_grps = 0; const auto& tree = lev_data->tie_trees[l_geo][l_tree]; - max_draws = std::max(tree.static_draws.size(), max_draws); + + // compute maximum number of vis groups (leaf in the bvh) for (auto& draw : tree.static_draws) { num_grps += draw.vis_groups.size(); } - max_num_grps = std::max(max_num_grps, num_grps); + + // compute wind buffer sizes 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); - max_inds = std::max(tree.unpacked.indices.size(), max_inds); - u32 verts = tree.packed_vertices.color_indices.size(); + + // vertex buffer max auto& lod_tree = m_trees.at(l_geo); + + // set up resources: create a VAO glGenVertexArrays(1, &lod_tree[l_tree].vao); glBindVertexArray(lod_tree[l_tree].vao); + // openGL vertex buffer from loader lod_tree[l_tree].vertex_buffer = loader_data->tie_data[l_geo][l_tree].vertex_buffer; - lod_tree[l_tree].vert_count = verts; + // draw array from FR3 data lod_tree[l_tree].draws = &tree.static_draws; + // base TOD colors from FR3 lod_tree[l_tree].colors = &tree.colors; + // visibility BVH from FR3 lod_tree[l_tree].vis = &tree.bvh; + // indices from FR3 (needed on CPU for culling) lod_tree[l_tree].index_data = tree.unpacked.indices.data(); + // wind metadata 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()); + // preprocess colors for faster interpolation (TODO: move to loader) lod_tree[l_tree].tod_cache = swizzle_time_of_day(tree.colors); + // OpenGL index buffer (fixed index buffer for multidraw system) + lod_tree[l_tree].index_buffer = loader_data->tie_data[l_geo][l_tree].index_buffer; + lod_tree[l_tree].category_draw_indices = tree.category_draw_indices; + + // set up vertex attributes glBindBuffer(GL_ARRAY_BUFFER, lod_tree[l_tree].vertex_buffer); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + glEnableVertexAttribArray(4); glVertexAttribPointer(0, // location 0 in the shader 3, // 3 values per vert @@ -106,12 +142,28 @@ void Tie3::update_load(const LevelData* loader_data) { (void*)offsetof(tfrag3::PreloadedVertex, color_index) // offset (0) ); - glGenBuffers(1, &lod_tree[l_tree].single_draw_index_buffer); - lod_tree[l_tree].index_buffer = loader_data->tie_data[l_geo][l_tree].index_buffer; + glVertexAttribPointer(3, // location 1 in the shader + 3, // 3 values per vert + GL_SHORT, // floats + GL_TRUE, // normalized + sizeof(tfrag3::PreloadedVertex), // stride + (void*)offsetof(tfrag3::PreloadedVertex, nx) // offset (0) + ); + glVertexAttribPointer(4, // location 1 in the shader + 4, // 3 values per vert + GL_UNSIGNED_BYTE, // floats + GL_TRUE, // normalized + sizeof(tfrag3::PreloadedVertex), // stride + (void*)offsetof(tfrag3::PreloadedVertex, r) // offset (0) + ); + + // allocate dynamic index buffer for the fallback "not multidraw" mode. + glGenBuffers(1, &lod_tree[l_tree].single_draw_index_buffer); + + // set up wind 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; @@ -121,11 +173,13 @@ void Tie3::update_load(const LevelData* loader_data) { } } + // set up per-proto visibility. Jak 2 needs to enable/disable individual protos. lod_tree[l_tree].has_proto_visibility = tree.has_per_proto_visibility_toggle; if (tree.has_per_proto_visibility_toggle) { lod_tree[l_tree].proto_visibility.init(tree.proto_names); } + // set up time of day texture. 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); @@ -135,40 +189,49 @@ void Tie3::update_load(const LevelData* loader_data) { glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glBindVertexArray(0); + + lod_tree[l_tree].vis_temp.resize(tree.bvh.vis_nodes.size()); + + lod_tree[l_tree].draw_idx_temp.resize(tree.static_draws.size()); + lod_tree[l_tree].index_temp.resize(tree.unpacked.indices.size()); + lod_tree[l_tree].multidraw_offset_per_stripdraw.resize(tree.static_draws.size()); + lod_tree[l_tree].multidraw_count_buffer.resize(num_grps); + lod_tree[l_tree].multidraw_index_offset_buffer.resize(num_grps); } } - m_cache.vis_temp.resize(vis_temp_len); - m_cache.multidraw_offset_per_stripdraw.resize(max_draws); - m_cache.multidraw_count_buffer.resize(max_num_grps); - m_cache.multidraw_index_offset_buffer.resize(max_num_grps); - m_wind_vectors.resize(4 * max_wind_idx + 4); // 4x u32's per wind. - m_cache.draw_idx_temp.resize(max_draws); - m_cache.index_temp.resize(max_inds); + // set up temporary caches. These are just temporary, so they don't need per-tree versions. - ASSERT(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT); + 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. + * Try loading a level. Hopefully it has been preloaded and this is fast. */ -bool Tie3::setup_for_level(const std::string& level, SharedRenderState* render_state) { +bool Tie3::try_loading_level(const std::string& level, SharedRenderState* render_state) { // make sure we have the level data. 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)) { + // loader failed, or we have an old copy of a level. either way, we have nothing to draw. m_has_level = false; m_textures = nullptr; m_level_name = ""; discard_tree_cache(); return false; } + + // loading was successful. Link textures/load ID. m_textures = &lev_data->textures; m_load_id = lev_data->load_id; + // see if this is the first time we've gotten the level if (m_level_name != level) { - update_load(lev_data); + // it is! do the one time load. + load_from_fr3_data(lev_data); m_has_level = true; m_level_name = level; } else { @@ -182,6 +245,515 @@ bool Tie3::setup_for_level(const std::string& level, SharedRenderState* render_s return m_has_level; } +void Tie3::discard_tree_cache() { + for (int geo = 0; geo < 4; ++geo) { + 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.index_buffer); + glDeleteBuffers(1, &tree.single_draw_index_buffer); + glDeleteVertexArrays(1, &tree.vao); + } + + m_trees[geo].clear(); + } +} + +bool Tie3::set_up_common_data_from_dma(DmaFollower& dma, SharedRenderState* render_state) { + auto data0 = dma.read_and_advance(); + ASSERT(data0.vif1() == 0 || data0.vifcode1().kind == VifCode::Kind::NOP); + ASSERT(data0.vif0() == 0 || data0.vifcode0().kind == VifCode::Kind::NOP || + data0.vifcode0().kind == VifCode::Kind::MARK); + ASSERT(data0.size_bytes == 0); + + if (dma.current_tag().kind == DmaTag::Kind::CALL) { + // renderer didn't run, let's just get out of here. + for (int i = 0; i < 4; i++) { + dma.read_and_advance(); + } + ASSERT(dma.current_tag_offset() == render_state->next_bucket); + return false; + } + + if (dma.current_tag_offset() == render_state->next_bucket) { + return false; + } + + auto gs_test = dma.read_and_advance(); + if (gs_test.size_bytes == 160) { + } else { + ASSERT(gs_test.size_bytes == 32); + + auto tie_consts = dma.read_and_advance(); + ASSERT(tie_consts.size_bytes == 9 * 16); + } + + auto mscalf = dma.read_and_advance(); + ASSERT(mscalf.size_bytes == 0); + + auto row = dma.read_and_advance(); + ASSERT(row.size_bytes == 32); + + auto next = dma.read_and_advance(); + if (next.size_bytes == 32) { + next = dma.read_and_advance(); + } + ASSERT(next.size_bytes == 0); + + auto pc_port_data = dma.read_and_advance(); + ASSERT(pc_port_data.size_bytes == sizeof(TfragPcPortData)); + memcpy(&m_pc_port_data, pc_port_data.data, sizeof(TfragPcPortData)); + m_pc_port_data.level_name[11] = '\0'; + + if (render_state->version == GameVersion::Jak1) { + auto wind_data = dma.read_and_advance(); + ASSERT(wind_data.size_bytes == sizeof(WindWork)); + memcpy(&m_wind_data, wind_data.data, sizeof(WindWork)); + } + + if (render_state->version == GameVersion::Jak2) { + // jak 2 proto visibility + auto proto_mask_data = dma.read_and_advance(); + m_common_data.proto_vis_data = proto_mask_data.data; + m_common_data.proto_vis_data_size = proto_mask_data.size_bytes; + // jak 2 envmap color + auto envmap_color = dma.read_and_advance(); + ASSERT(envmap_color.size_bytes == 16); + memcpy(m_common_data.envmap_color.data(), envmap_color.data, 16); + m_common_data.envmap_color /= 128.f; + } + m_common_data.frame_idx = render_state->frame_idx; + + while (dma.current_tag_offset() != render_state->next_bucket) { + dma.read_and_advance(); + } + + m_common_data.settings.hvdf_offset = m_pc_port_data.hvdf_off; + m_common_data.settings.fog = m_pc_port_data.fog; + + memcpy(m_common_data.settings.math_camera.data(), m_pc_port_data.camera[0].data(), 64); + m_common_data.settings.tree_idx = 0; + + if (render_state->occlusion_vis[m_level_id].valid) { + m_common_data.settings.occlusion_culling = render_state->occlusion_vis[m_level_id].data; + } else { + m_common_data.settings.occlusion_culling = 0; + } + + update_render_state_from_pc_settings(render_state, m_pc_port_data); + + for (int i = 0; i < 4; i++) { + m_common_data.settings.planes[i] = m_pc_port_data.planes[i]; + m_common_data.settings.itimes[i] = m_pc_port_data.itimes[i]; + } + + m_has_level = try_loading_level(m_pc_port_data.level_name, render_state); + return true; +} +/*! + * Render method called from bucket render system. + * Does common setup for all category, but only renderers default_category. + */ +void Tie3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) { + if (!m_enabled) { + while (dma.current_tag_offset() != render_state->next_bucket) { + dma.read_and_advance(); + } + return; + } + + if (set_up_common_data_from_dma(dma, render_state)) { + setup_all_trees(lod(), m_common_data.settings, m_common_data.proto_vis_data, + m_common_data.proto_vis_data_size, !render_state->no_multidraw, prof); + + draw_matching_draws_for_all_trees(lod(), m_common_data.settings, render_state, prof, + m_default_category); + } +} + +void Tie3::render_from_another(SharedRenderState* render_state, + ScopedProfilerNode& prof, + tfrag3::TieCategory category) { + if (render_state->frame_idx != m_common_data.frame_idx) { + return; + } + draw_matching_draws_for_all_trees(lod(), m_common_data.settings, render_state, prof, category); +} + +void Tie3::draw_matching_draws_for_all_trees(int geom, + const TfragRenderSettings& settings, + SharedRenderState* render_state, + ScopedProfilerNode& prof, + tfrag3::TieCategory category) { + for (u32 i = 0; i < m_trees[geom].size(); i++) { + if (tfrag3::is_envmap_first_draw_category(category)) { + draw_matching_draws_for_tree(i, geom, settings, render_state, prof, category); + } else { + draw_matching_draws_for_tree(i, geom, settings, render_state, prof, category); + } + } +} + +void Tie3::setup_all_trees(int geom, + const TfragRenderSettings& settings, + const u8* proto_vis_data, + size_t proto_vis_data_size, + bool use_multidraw, + ScopedProfilerNode& prof) { + for (u32 i = 0; i < m_trees[geom].size(); i++) { + setup_tree(i, geom, settings, proto_vis_data, proto_vis_data_size, use_multidraw, prof); + } +} + +void Tie3::setup_tree(int idx, + int geom, + const TfragRenderSettings& settings, + const u8* proto_vis_data, + size_t proto_vis_data_size, + bool use_multidraw, + ScopedProfilerNode& prof) { + // reset perf + auto& tree = m_trees.at(geom).at(idx); + // don't render if we haven't loaded + if (!m_has_level) { + return; + } + + // update time of day + if (m_color_result.size() < tree.colors->size()) { + m_color_result.resize(tree.colors->size()); + } + + if (m_use_fast_time_of_day) { + interp_time_of_day_fast(settings.itimes, tree.tod_cache, m_color_result.data()); + } else { + interp_time_of_day_slow(settings.itimes, *tree.colors, m_color_result.data()); + } + + glActiveTexture(GL_TEXTURE10); + glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture); + glTexSubImage1D(GL_TEXTURE_1D, 0, 0, tree.colors->size(), GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, + m_color_result.data()); + + // update proto vis mask + if (proto_vis_data) { + tree.proto_visibility.update(proto_vis_data, proto_vis_data_size); + } + + if (!m_debug_all_visible) { + // need culling data + cull_check_all_slow(settings.planes, tree.vis->vis_nodes, settings.occlusion_culling, + tree.vis_temp.data()); + } + + u32 num_tris = 0; + if (use_multidraw) { + if (m_debug_all_visible) { + num_tris = make_all_visible_multidraws( + tree.multidraw_offset_per_stripdraw.data(), tree.multidraw_count_buffer.data(), + tree.multidraw_index_offset_buffer.data(), *tree.draws); + } else { + Timer index_timer; + if (tree.has_proto_visibility) { + num_tris = make_multidraws_from_vis_and_proto_string( + tree.multidraw_offset_per_stripdraw.data(), tree.multidraw_count_buffer.data(), + tree.multidraw_index_offset_buffer.data(), *tree.draws, tree.vis_temp, + tree.proto_visibility.vis_flags); + } else { + num_tris = make_multidraws_from_vis_string( + tree.multidraw_offset_per_stripdraw.data(), tree.multidraw_count_buffer.data(), + tree.multidraw_index_offset_buffer.data(), *tree.draws, tree.vis_temp); + } + } + } else { + u32 idx_buffer_size; + if (m_debug_all_visible) { + idx_buffer_size = + make_all_visible_index_list(tree.draw_idx_temp.data(), tree.index_temp.data(), + *tree.draws, tree.index_data, &num_tris); + } else { + if (tree.has_proto_visibility) { + idx_buffer_size = make_index_list_from_vis_and_proto_string( + tree.draw_idx_temp.data(), tree.index_temp.data(), *tree.draws, tree.vis_temp, + tree.proto_visibility.vis_flags, tree.index_data, &num_tris); + } else { + idx_buffer_size = + make_index_list_from_vis_string(tree.draw_idx_temp.data(), tree.index_temp.data(), + *tree.draws, tree.vis_temp, tree.index_data, &num_tris); + } + } + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tree.single_draw_index_buffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size * sizeof(u32), tree.index_temp.data(), + GL_STREAM_DRAW); + } + + prof.add_tri(num_tris); +} + +namespace { +void set_uniform(GLuint uniform, const math::Vector4f& val) { + glUniform4f(uniform, val.x(), val.y(), val.z(), val.w()); +} +} // namespace + +void init_etie_cam_uniforms(const EtieUniforms& uniforms, const SharedRenderState* render_state) { + glUniformMatrix4fv(uniforms.cam_no_persp, 1, GL_FALSE, render_state->camera_no_persp[0].data()); + + math::Vector4f perspective[2]; + float inv_fog = 1.f / render_state->camera_fog[0]; + auto& hvdf_off = render_state->camera_hvdf_off; + float pxx = render_state->camera_persp[0].x(); + float pyy = render_state->camera_persp[1].y(); + float pzz = render_state->camera_persp[2].z(); + float pzw = render_state->camera_persp[2].w(); + float pwz = render_state->camera_persp[3].z(); + float scale = pzw * inv_fog; + perspective[0].x() = scale * hvdf_off.x(); + perspective[0].y() = scale * hvdf_off.y(); + perspective[0].z() = scale * hvdf_off.z() + pzz; + perspective[0].w() = scale; + + perspective[1].x() = pxx; + perspective[1].y() = pyy; + perspective[1].z() = pwz; + perspective[1].w() = 0; + + set_uniform(uniforms.persp0, perspective[0]); + set_uniform(uniforms.persp1, perspective[1]); +} + +void Tie3::draw_matching_draws_for_tree(int idx, + int geom, + const TfragRenderSettings& settings, + SharedRenderState* render_state, + ScopedProfilerNode& prof, + tfrag3::TieCategory category) { + auto& tree = m_trees.at(geom).at(idx); + + // don't render if we haven't loaded + if (!m_has_level) { + return; + } + bool use_envmap = tfrag3::is_envmap_first_draw_category(category); + auto shader_id = use_envmap ? ShaderId::ETIE_BASE : ShaderId::TFRAG3; + + // setup OpenGL shader + first_tfrag_draw_setup(settings, render_state, shader_id); + + if (use_envmap) { + // if we use envmap, use the envmap-style math for the base draw to avoid rounding issue. + init_etie_cam_uniforms(m_etie_base_uniforms, render_state); + } + + glBindVertexArray(tree.vao); + glBindBuffer(GL_ARRAY_BUFFER, tree.vertex_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, + render_state->no_multidraw ? tree.single_draw_index_buffer : tree.index_buffer); + + glActiveTexture(GL_TEXTURE10); + glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture); + + glActiveTexture(GL_TEXTURE0); + glEnable(GL_PRIMITIVE_RESTART); + glPrimitiveRestartIndex(UINT32_MAX); + + int last_texture = -1; + for (size_t draw_idx = tree.category_draw_indices[(int)category]; + draw_idx < tree.category_draw_indices[(int)category + 1]; draw_idx++) { + const auto& draw = tree.draws->operator[](draw_idx); + const auto& multidraw_indices = tree.multidraw_offset_per_stripdraw[draw_idx]; + const auto& singledraw_indices = tree.draw_idx_temp[draw_idx]; + + if (render_state->no_multidraw) { + if (singledraw_indices.second == 0) { + continue; + } + } else { + if (multidraw_indices.second == 0) { + continue; + } + } + + if ((int)draw.tree_tex_id != last_texture) { + glBindTexture(GL_TEXTURE_2D, m_textures->at(draw.tree_tex_id)); + last_texture = draw.tree_tex_id; + } + + auto double_draw = setup_tfrag_shader(render_state, draw.mode, ShaderId::TFRAG3); + + glUniform1i(use_envmap ? m_etie_base_uniforms.decal : m_uniforms.decal, + draw.mode.get_decal() ? 1 : 0); + + prof.add_draw_call(); + + if (render_state->no_multidraw) { + glDrawElements(GL_TRIANGLE_STRIP, singledraw_indices.second, GL_UNSIGNED_INT, + (void*)(singledraw_indices.first * sizeof(u32))); + } else { + glMultiDrawElements( + GL_TRIANGLE_STRIP, &tree.multidraw_count_buffer[multidraw_indices.first], GL_UNSIGNED_INT, + &tree.multidraw_index_offset_buffer[multidraw_indices.first], multidraw_indices.second); + } + + switch (double_draw.kind) { + case DoubleDrawKind::NONE: + break; + case DoubleDrawKind::AFAIL_NO_DEPTH_WRITE: + ASSERT(false); + prof.add_draw_call(); + glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_min"), + -10.f); + glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_max"), + double_draw.aref_second); + glDepthMask(GL_FALSE); + if (render_state->no_multidraw) { + glDrawElements(GL_TRIANGLE_STRIP, singledraw_indices.second, GL_UNSIGNED_INT, + (void*)(singledraw_indices.first * sizeof(u32))); + } else { + glMultiDrawElements( + GL_TRIANGLE_STRIP, &tree.multidraw_count_buffer[multidraw_indices.first], + GL_UNSIGNED_INT, &tree.multidraw_index_offset_buffer[multidraw_indices.first], + multidraw_indices.second); + } + break; + default: + ASSERT(false); + } + } + + if (!m_hide_wind && category == tfrag3::TieCategory::NORMAL) { + auto wind_prof = prof.make_scoped_child("wind"); + render_tree_wind(idx, geom, settings, render_state, wind_prof); + } + + glBindVertexArray(0); + + if (use_envmap) { + envmap_second_pass_draw(tree, settings, render_state, prof, + tfrag3::get_second_draw_category(category)); + } +} + +void Tie3::envmap_second_pass_draw(const Tree& tree, + const TfragRenderSettings& settings, + SharedRenderState* render_state, + ScopedProfilerNode& prof, + tfrag3::TieCategory category) { + first_tfrag_draw_setup(settings, render_state, ShaderId::ETIE); + glBindVertexArray(tree.vao); + glBindBuffer(GL_ARRAY_BUFFER, tree.vertex_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, + render_state->no_multidraw ? tree.single_draw_index_buffer : tree.index_buffer); + + init_etie_cam_uniforms(m_etie_uniforms, render_state); + set_uniform(m_etie_uniforms.envmap_tod_tint, m_common_data.envmap_color); + + int last_texture = -1; + for (size_t draw_idx = tree.category_draw_indices[(int)category]; + draw_idx < tree.category_draw_indices[(int)category + 1]; draw_idx++) { + const auto& draw = tree.draws->operator[](draw_idx); + const auto& multidraw_indices = tree.multidraw_offset_per_stripdraw[draw_idx]; + const auto& singledraw_indices = tree.draw_idx_temp[draw_idx]; + + if (render_state->no_multidraw) { + if (singledraw_indices.second == 0) { + continue; + } + } else { + if (multidraw_indices.second == 0) { + continue; + } + } + + if ((int)draw.tree_tex_id != last_texture) { + glBindTexture(GL_TEXTURE_2D, m_textures->at(draw.tree_tex_id)); + last_texture = draw.tree_tex_id; + } + + auto double_draw = setup_tfrag_shader(render_state, draw.mode, ShaderId::ETIE); + + prof.add_draw_call(); + + if (render_state->no_multidraw) { + glDrawElements(GL_TRIANGLE_STRIP, singledraw_indices.second, GL_UNSIGNED_INT, + (void*)(singledraw_indices.first * sizeof(u32))); + } else { + glMultiDrawElements( + GL_TRIANGLE_STRIP, &tree.multidraw_count_buffer[multidraw_indices.first], GL_UNSIGNED_INT, + &tree.multidraw_index_offset_buffer[multidraw_indices.first], multidraw_indices.second); + } + + switch (double_draw.kind) { + case DoubleDrawKind::NONE: + break; + default: + ASSERT(false); + } + } +} + +void Tie3::draw_debug_window() { + ImGui::Checkbox("Fast ToD", &m_use_fast_time_of_day); + ImGui::SameLine(); + ImGui::Checkbox("All Visible", &m_debug_all_visible); + ImGui::Checkbox("Hide Wind", &m_hide_wind); + ImGui::SliderFloat("Wind Multiplier", &m_wind_multiplier, 0., 40.f); + ImGui::Separator(); +} + +void TieProtoVisibility::init(const std::vector& names) { + vis_flags.resize(names.size()); + for (auto& x : vis_flags) { + x = 1; + } + all_visible = true; + name_to_idx.clear(); + size_t i = 0; + for (auto& name : names) { + name_to_idx[name].push_back(i++); + } +} + +void TieProtoVisibility::update(const u8* data, size_t size) { + char name_buffer[256]; // ?? + + if (!all_visible) { + for (auto& x : vis_flags) { + x = 1; + } + all_visible = true; + } + + const u8* end = data + size; + + while (true) { + int name_idx = 0; + while (*data) { + name_buffer[name_idx++] = *data; + data++; + } + if (name_idx) { + ASSERT(name_idx < 254); + name_buffer[name_idx] = '\0'; + const auto& it = name_to_idx.find(name_buffer); + if (it != name_to_idx.end()) { + all_visible = false; + for (auto x : name_to_idx.at(name_buffer)) { + vis_flags[x] = 0; + } + } + } + + while (*data == 0) { + if (data >= end) { + return; + } + data++; + } + } +} + void vector_min_in_place(math::Vector4f& v, float val) { for (int i = 0; i < 4; i++) { if (v[i] > val) { @@ -292,138 +864,6 @@ void do_wind_math(u16 wind_idx, // sd s2, 0(s5) } -void Tie3::discard_tree_cache() { - for (int geo = 0; geo < 4; ++geo) { - 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.index_buffer); - glDeleteBuffers(1, &tree.single_draw_index_buffer); - glDeleteVertexArrays(1, &tree.vao); - } - - m_trees[geo].clear(); - } -} - -void Tie3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) { - if (!m_enabled) { - while (dma.current_tag_offset() != render_state->next_bucket) { - dma.read_and_advance(); - } - return; - } - - if (m_override_level && m_pending_user_level) { - m_has_level = setup_for_level(*m_pending_user_level, render_state); - m_pending_user_level = {}; - } - - auto data0 = dma.read_and_advance(); - ASSERT(data0.vif1() == 0 || data0.vifcode1().kind == VifCode::Kind::NOP); - ASSERT(data0.vif0() == 0 || data0.vifcode0().kind == VifCode::Kind::NOP || - data0.vifcode0().kind == VifCode::Kind::MARK); - ASSERT(data0.size_bytes == 0); - - if (dma.current_tag().kind == DmaTag::Kind::CALL) { - // renderer didn't run, let's just get out of here. - for (int i = 0; i < 4; i++) { - dma.read_and_advance(); - } - ASSERT(dma.current_tag_offset() == render_state->next_bucket); - return; - } - - if (dma.current_tag_offset() == render_state->next_bucket) { - return; - } - - auto gs_test = dma.read_and_advance(); - if (gs_test.size_bytes == 160) { - } else { - ASSERT(gs_test.size_bytes == 32); - - auto tie_consts = dma.read_and_advance(); - ASSERT(tie_consts.size_bytes == 9 * 16); - } - - auto mscalf = dma.read_and_advance(); - ASSERT(mscalf.size_bytes == 0); - - auto row = dma.read_and_advance(); - ASSERT(row.size_bytes == 32); - - auto next = dma.read_and_advance(); - if (next.size_bytes == 32) { - next = dma.read_and_advance(); - } - ASSERT(next.size_bytes == 0); - - auto pc_port_data = dma.read_and_advance(); - ASSERT(pc_port_data.size_bytes == sizeof(TfragPcPortData)); - memcpy(&m_pc_port_data, pc_port_data.data, sizeof(TfragPcPortData)); - m_pc_port_data.level_name[11] = '\0'; - - if (render_state->version == GameVersion::Jak1) { - auto wind_data = dma.read_and_advance(); - ASSERT(wind_data.size_bytes == sizeof(WindWork)); - memcpy(&m_wind_data, wind_data.data, sizeof(WindWork)); - } - - const u8* proto_vis_data = nullptr; - size_t proto_vis_data_size = 0; - if (render_state->version == GameVersion::Jak2) { - auto proto_mask_data = dma.read_and_advance(); - proto_vis_data = proto_mask_data.data; - proto_vis_data_size = proto_mask_data.size_bytes; - } - - while (dma.current_tag_offset() != render_state->next_bucket) { - dma.read_and_advance(); - } - - TfragRenderSettings settings; - settings.hvdf_offset = m_pc_port_data.hvdf_off; - settings.fog = m_pc_port_data.fog; - - memcpy(settings.math_camera.data(), m_pc_port_data.camera[0].data(), 64); - settings.tree_idx = 0; - - if (render_state->occlusion_vis[m_level_id].valid) { - settings.occlusion_culling = render_state->occlusion_vis[m_level_id].data; - } - - update_render_state_from_pc_settings(render_state, m_pc_port_data); - - for (int i = 0; i < 4; i++) { - settings.planes[i] = m_pc_port_data.planes[i]; - settings.itimes[i] = m_pc_port_data.itimes[i]; - } - - if (!m_override_level) { - m_has_level = setup_for_level(m_pc_port_data.level_name, render_state); - } - - render_all_trees(lod(), settings, render_state, prof, proto_vis_data, proto_vis_data_size); -} - -void Tie3::render_all_trees(int geom, - const TfragRenderSettings& settings, - SharedRenderState* render_state, - ScopedProfilerNode& prof, - const u8* proto_vis_data, - size_t proto_vis_data_size) { - Timer all_tree_timer; - if (m_override_level && m_pending_user_level) { - m_has_level = setup_for_level(*m_pending_user_level, render_state); - m_pending_user_level = {}; - } - for (u32 i = 0; i < m_trees[geom].size(); i++) { - render_tree(i, geom, settings, render_state, prof, proto_vis_data, proto_vis_data_size); - } - m_all_tree_time.add(all_tree_timer.getSeconds()); -} - void Tie3::render_tree_wind(int idx, int geom, const TfragRenderSettings& settings, @@ -491,7 +931,7 @@ void Tie3::render_tree_wind(int idx, int off = 0; for (auto& grp : draw.instance_groups) { - if (!m_debug_all_visible && !m_cache.vis_temp.at(grp.vis_idx)) { + if (!m_debug_all_visible && !tree.vis_temp.at(grp.vis_idx)) { off += grp.num; continue; // invisible, skip. } @@ -503,9 +943,6 @@ void Tie3::render_tree_wind(int idx, prof.add_draw_call(); prof.add_tri(grp.num); - tree.perf.draws++; - tree.perf.wind_draws++; - glDrawElements(GL_TRIANGLE_STRIP, grp.num, GL_UNSIGNED_INT, (void*)((off + tree.wind_vertex_index_offsets.at(draw_idx)) * sizeof(u32))); off += grp.num; @@ -514,8 +951,6 @@ void Tie3::render_tree_wind(int idx, case DoubleDrawKind::NONE: break; case DoubleDrawKind::AFAIL_NO_DEPTH_WRITE: - tree.perf.draws++; - tree.perf.wind_draws++; prof.add_draw_call(); prof.add_tri(grp.num); glUniform1f( @@ -535,297 +970,25 @@ void Tie3::render_tree_wind(int idx, } } -void Tie3::render_tree(int idx, - int geom, - const TfragRenderSettings& settings, - SharedRenderState* render_state, - ScopedProfilerNode& prof, - const u8* proto_vis_data, - size_t proto_vis_data_size) { - // reset perf - Timer tree_timer; - auto& tree = m_trees.at(geom).at(idx); - tree.perf.draws = 0; - tree.perf.wind_draws = 0; +Tie3AnotherCategory::Tie3AnotherCategory(const std::string& name, + int my_id, + Tie3* parent, + tfrag3::TieCategory category) + : BucketRenderer(name, my_id), m_parent(parent), m_category(category) {} - // don't render if we haven't loaded - if (!m_has_level) { - return; - } - - // update time of day - if (m_color_result.size() < tree.colors->size()) { - m_color_result.resize(tree.colors->size()); - } - - // update proto vis mask - if (proto_vis_data) { - tree.proto_visibility.update(proto_vis_data, proto_vis_data_size); - } - - Timer interp_timer; - if (m_use_fast_time_of_day) { - interp_time_of_day_fast(settings.itimes, tree.tod_cache, m_color_result.data()); - } else { - interp_time_of_day_slow(settings.itimes, *tree.colors, m_color_result.data()); - } - tree.perf.tod_time.add(interp_timer.getSeconds()); - - Timer setup_timer; - glActiveTexture(GL_TEXTURE10); - glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture); - glTexSubImage1D(GL_TEXTURE_1D, 0, 0, tree.colors->size(), GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, - m_color_result.data()); - - // setup OpenGL shader - first_tfrag_draw_setup(settings, render_state, ShaderId::TFRAG3); - - glBindVertexArray(tree.vao); - glBindBuffer(GL_ARRAY_BUFFER, tree.vertex_buffer); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, - render_state->no_multidraw ? tree.single_draw_index_buffer : tree.index_buffer); - glActiveTexture(GL_TEXTURE0); - glEnable(GL_PRIMITIVE_RESTART); - glPrimitiveRestartIndex(UINT32_MAX); - tree.perf.tod_time.add(setup_timer.getSeconds()); - - int last_texture = -1; - - if (!m_debug_all_visible) { - // need culling data - Timer cull_timer; - cull_check_all_slow(settings.planes, tree.vis->vis_nodes, settings.occlusion_culling, - m_cache.vis_temp.data()); - tree.perf.cull_time.add(cull_timer.getSeconds()); - } else { - // no culling. - tree.perf.cull_time.add(0); - } - - u32 num_tris; - if (render_state->no_multidraw) { - Timer index_timer; - u32 idx_buffer_size; - if (m_debug_all_visible) { - idx_buffer_size = - make_all_visible_index_list(m_cache.draw_idx_temp.data(), m_cache.index_temp.data(), - *tree.draws, tree.index_data, &num_tris); - } else { - if (tree.has_proto_visibility) { - idx_buffer_size = make_index_list_from_vis_and_proto_string( - m_cache.draw_idx_temp.data(), m_cache.index_temp.data(), *tree.draws, m_cache.vis_temp, - tree.proto_visibility.vis_flags, tree.index_data, &num_tris); - } else { - idx_buffer_size = make_index_list_from_vis_string( - m_cache.draw_idx_temp.data(), m_cache.index_temp.data(), *tree.draws, m_cache.vis_temp, - tree.index_data, &num_tris); - } - } - - glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size * sizeof(u32), m_cache.index_temp.data(), - GL_STREAM_DRAW); - tree.perf.index_time.add(index_timer.getSeconds()); - - } else { - if (m_debug_all_visible) { - Timer index_timer; - num_tris = make_all_visible_multidraws( - m_cache.multidraw_offset_per_stripdraw.data(), m_cache.multidraw_count_buffer.data(), - m_cache.multidraw_index_offset_buffer.data(), *tree.draws); - tree.perf.index_time.add(index_timer.getSeconds()); - } else { - Timer index_timer; - if (tree.has_proto_visibility) { - num_tris = make_multidraws_from_vis_and_proto_string( - m_cache.multidraw_offset_per_stripdraw.data(), m_cache.multidraw_count_buffer.data(), - m_cache.multidraw_index_offset_buffer.data(), *tree.draws, m_cache.vis_temp, - tree.proto_visibility.vis_flags); - } else { - num_tris = make_multidraws_from_vis_string( - m_cache.multidraw_offset_per_stripdraw.data(), m_cache.multidraw_count_buffer.data(), - m_cache.multidraw_index_offset_buffer.data(), *tree.draws, m_cache.vis_temp); - } - - tree.perf.index_time.add(index_timer.getSeconds()); - } - } - - Timer draw_timer; - prof.add_tri(num_tris); - - for (size_t draw_idx = 0; draw_idx < tree.draws->size(); draw_idx++) { - const auto& draw = tree.draws->operator[](draw_idx); - const auto& multidraw_indices = m_cache.multidraw_offset_per_stripdraw[draw_idx]; - const auto& singledraw_indices = m_cache.draw_idx_temp[draw_idx]; - - if (render_state->no_multidraw) { - if (singledraw_indices.second == 0) { - continue; - } - } else { - if (multidraw_indices.second == 0) { - continue; - } - } - - if ((int)draw.tree_tex_id != last_texture) { - glBindTexture(GL_TEXTURE_2D, m_textures->at(draw.tree_tex_id)); - last_texture = draw.tree_tex_id; - } - - auto double_draw = setup_tfrag_shader(render_state, draw.mode, ShaderId::TFRAG3); - - prof.add_draw_call(); - - tree.perf.draws++; - - if (render_state->no_multidraw) { - glDrawElements(GL_TRIANGLE_STRIP, singledraw_indices.second, GL_UNSIGNED_INT, - (void*)(singledraw_indices.first * sizeof(u32))); - } else { - glMultiDrawElements(GL_TRIANGLE_STRIP, - &m_cache.multidraw_count_buffer[multidraw_indices.first], GL_UNSIGNED_INT, - &m_cache.multidraw_index_offset_buffer[multidraw_indices.first], - multidraw_indices.second); - } - - switch (double_draw.kind) { - case DoubleDrawKind::NONE: - break; - case DoubleDrawKind::AFAIL_NO_DEPTH_WRITE: - tree.perf.draws++; - prof.add_draw_call(); - glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_min"), - -10.f); - glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_max"), - double_draw.aref_second); - glDepthMask(GL_FALSE); - if (render_state->no_multidraw) { - glDrawElements(GL_TRIANGLE_STRIP, singledraw_indices.second, GL_UNSIGNED_INT, - (void*)(singledraw_indices.first * sizeof(u32))); - } else { - glMultiDrawElements( - GL_TRIANGLE_STRIP, &m_cache.multidraw_count_buffer[multidraw_indices.first], - GL_UNSIGNED_INT, &m_cache.multidraw_index_offset_buffer[multidraw_indices.first], - multidraw_indices.second); - } - break; - default: - ASSERT(false); - } - - if (m_debug_wireframe && !render_state->no_multidraw) { - render_state->shaders[ShaderId::TFRAG3_NO_TEX].activate(); - glUniformMatrix4fv( - glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "camera"), 1, - GL_FALSE, settings.math_camera.data()); - glUniform4f( - glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "hvdf_offset"), - settings.hvdf_offset[0], settings.hvdf_offset[1], settings.hvdf_offset[2], - settings.hvdf_offset[3]); - glUniform1f( - glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "fog_constant"), - settings.fog.x()); - glDisable(GL_BLEND); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - glMultiDrawElements(GL_TRIANGLE_STRIP, - &m_cache.multidraw_count_buffer[multidraw_indices.first], GL_UNSIGNED_INT, - &m_cache.multidraw_index_offset_buffer[multidraw_indices.first], - multidraw_indices.second); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - prof.add_draw_call(); - render_state->shaders[ShaderId::TFRAG3].activate(); - } - } - - if (!m_hide_wind) { - auto wind_prof = prof.make_scoped_child("wind"); - render_tree_wind(idx, geom, settings, render_state, wind_prof); - } - - glBindVertexArray(0); - tree.perf.draw_time.add(draw_timer.getSeconds()); - tree.perf.tree_time.add(tree_timer.getSeconds()); +void Tie3AnotherCategory::draw_debug_window() { + ImGui::Text("Child of this renderer:"); + m_parent->draw_debug_window(); } -void Tie3::draw_debug_window() { - ImGui::InputText("Custom Level", m_user_level, sizeof(m_user_level)); - if (ImGui::Button("Go!")) { - m_pending_user_level = m_user_level; - } - ImGui::Checkbox("Override level", &m_override_level); - ImGui::Checkbox("Fast ToD", &m_use_fast_time_of_day); - ImGui::Checkbox("Wireframe", &m_debug_wireframe); - ImGui::SameLine(); - ImGui::Checkbox("All Visible", &m_debug_all_visible); - ImGui::Checkbox("Hide Wind", &m_hide_wind); - ImGui::SliderFloat("Wind Multiplier", &m_wind_multiplier, 0., 40.f); - ImGui::Separator(); - for (u32 i = 0; i < m_trees[lod()].size(); i++) { - auto& perf = m_trees[lod()][i].perf; - ImGui::Text("Tree: %d", i); - ImGui::Text("time of days: %d", (int)m_trees[lod()][i].colors->size()); - ImGui::Text("draw: %d", perf.draws); - ImGui::Text("wind draw: %d", perf.wind_draws); - ImGui::Text("total: %.2f", perf.tree_time.get()); - ImGui::Text("proto vis: %.2f", perf.proto_vis_time.get() * 1000.f); - ImGui::Text("cull: %.2f index: %.2f tod: %.2f setup: %.2f draw: %.2f", - perf.cull_time.get() * 1000.f, perf.index_time.get() * 1000.f, - perf.tod_time.get() * 1000.f, perf.setup_time.get() * 1000.f, - perf.draw_time.get() * 1000.f); - ImGui::Separator(); - } - ImGui::Text("All trees: %.2f", 1000.f * m_all_tree_time.get()); -} - -void TieProtoVisibility::init(const std::vector& names) { - vis_flags.resize(names.size()); - for (auto& x : vis_flags) { - x = 1; - } - all_visible = true; - name_to_idx.clear(); - size_t i = 0; - for (auto& name : names) { - name_to_idx[name].push_back(i++); - } -} - -void TieProtoVisibility::update(const u8* data, size_t size) { - char name_buffer[256]; // ?? - - if (!all_visible) { - for (auto& x : vis_flags) { - x = 1; - } - all_visible = true; - } - - const u8* end = data + size; - - while (true) { - int name_idx = 0; - while (*data) { - name_buffer[name_idx++] = *data; - data++; - } - if (name_idx) { - ASSERT(name_idx < 254); - name_buffer[name_idx] = '\0'; - const auto& it = name_to_idx.find(name_buffer); - if (it != name_to_idx.end()) { - all_visible = false; - for (auto x : name_to_idx.at(name_buffer)) { - vis_flags[x] = 0; - } - } - } - - while (*data == 0) { - if (data >= end) { - return; - } - data++; - } +void Tie3AnotherCategory::render(DmaFollower& dma, + SharedRenderState* render_state, + ScopedProfilerNode& prof) { + auto first_tag = dma.current_tag(); + dma.read_and_advance(); + if (first_tag.kind != DmaTag::Kind::CNT || first_tag.qwc != 0) { + fmt::print("Bucket renderer {} ({}) was supposed to be empty, but wasn't\n", m_my_id, m_name); + ASSERT(false); } + m_parent->render_from_another(render_state, prof, m_category); } \ No newline at end of file diff --git a/game/graphics/opengl_renderer/background/Tie3.h b/game/graphics/opengl_renderer/background/Tie3.h index 9cf67b4f2..899a11e06 100644 --- a/game/graphics/opengl_renderer/background/Tie3.h +++ b/game/graphics/opengl_renderer/background/Tie3.h @@ -19,27 +19,58 @@ struct TieProtoVisibility { bool all_visible = true; }; +struct EtieUniforms { + GLuint persp0, persp1, cam_no_persp, envmap_tod_tint, decal; +}; + class Tie3 : public BucketRenderer { public: - Tie3(const std::string& name, int my_id, int level_id); + // by default, only render the specified category on the call to render. + // to render the other categories, use the Tie3AnotherCategory renderer below. + Tie3(const std::string& name, + int my_id, + int level_id, + tfrag3::TieCategory category = tfrag3::TieCategory::NORMAL); void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override; void draw_debug_window() override; + void init_shaders(ShaderLibrary& shaders) override; ~Tie3(); - void render_all_trees(int geom, - const TfragRenderSettings& settings, - SharedRenderState* render_state, - ScopedProfilerNode& prof, - const u8* proto_vis_data, - size_t proto_vis_data_size); - void render_tree(int idx, - int geom, - const TfragRenderSettings& settings, - SharedRenderState* render_state, - ScopedProfilerNode& prof, - const u8* proto_vis_data, - size_t proto_vis_data_size); - bool setup_for_level(const std::string& str, SharedRenderState* render_state); + bool set_up_common_data_from_dma(DmaFollower& dma, SharedRenderState* render_state); + + void setup_all_trees(int geom, + const TfragRenderSettings& settings, + const u8* proto_vis_data, + size_t proto_vis_data_size, + bool use_multidraw, + ScopedProfilerNode& prof); + + void setup_tree(int idx, + int geom, + const TfragRenderSettings& settings, + const u8* proto_vis_data, + size_t proto_vis_data_size, + bool use_multidraw, + ScopedProfilerNode& prof); + + void draw_matching_draws_for_all_trees(int geom, + const TfragRenderSettings& settings, + SharedRenderState* render_state, + ScopedProfilerNode& prof, + tfrag3::TieCategory category); + + void draw_matching_draws_for_tree(int idx, + int geom, + const TfragRenderSettings& settings, + SharedRenderState* render_state, + ScopedProfilerNode& prof, + tfrag3::TieCategory category); + + bool try_loading_level(const std::string& str, SharedRenderState* render_state); + + void render_from_another(SharedRenderState* render_state, + ScopedProfilerNode& prof, + tfrag3::TieCategory category); struct WindWork { u32 paused; @@ -55,7 +86,7 @@ class Tie3 : public BucketRenderer { int lod() const { return Gfx::g_global_settings.lod_tie; } private: - void update_load(const LevelData* loader_data); + void load_from_fr3_data(const LevelData* loader_data); void discard_tree_cache(); void render_tree_wind(int idx, int geom, @@ -63,13 +94,22 @@ class Tie3 : public BucketRenderer { SharedRenderState* render_state, ScopedProfilerNode& prof); + struct CommonData { + // data that the AnotherCategory renderers can use. + TfragRenderSettings settings; + const u8* proto_vis_data = nullptr; + u32 proto_vis_data_size = 0; + math::Vector4f envmap_color; + u64 frame_idx = -1; + } m_common_data; + struct Tree { GLuint vertex_buffer; GLuint index_buffer; GLuint single_draw_index_buffer; GLuint time_of_day_texture; GLuint vao; - u32 vert_count; + std::array category_draw_indices; const std::vector* draws = nullptr; const std::vector* wind_draws = nullptr; const std::vector* instance_info = nullptr; @@ -77,56 +117,39 @@ class Tie3 : public BucketRenderer { const tfrag3::BVH* vis = nullptr; const u32* index_data = nullptr; SwizzledTimeOfDay tod_cache; - std::vector> wind_matrix_cache; - - bool has_wind = false; GLuint wind_vertex_index_buffer; std::vector wind_vertex_index_offsets; - bool has_proto_visibility = false; TieProtoVisibility proto_visibility; - struct { - u32 draws = 0; - u32 wind_draws = 0; - Filtered cull_time; - Filtered index_time; - Filtered tod_time; - Filtered proto_vis_time; - Filtered setup_time; - Filtered draw_time; - Filtered tree_time; - } perf; - }; - - std::array, 4> m_trees; // includes 4 lods! - std::string m_level_name; - const std::vector* m_textures; - u64 m_load_id = -1; - - struct Cache { std::vector> draw_idx_temp; std::vector index_temp; std::vector vis_temp; std::vector> multidraw_offset_per_stripdraw; std::vector multidraw_count_buffer; std::vector multidraw_index_offset_buffer; - } m_cache; + }; + + void envmap_second_pass_draw(const Tree& tree, + const TfragRenderSettings& settings, + SharedRenderState* render_state, + ScopedProfilerNode& prof, + tfrag3::TieCategory category); + + std::array, 4> m_trees; // includes 4 lods! + std::string m_level_name; + const std::vector* m_textures; + u64 m_load_id = -1; std::vector> m_color_result; static constexpr int TIME_OF_DAY_COLOR_COUNT = 8192; bool m_has_level = false; - char m_user_level[255] = "vi1"; - std::optional m_pending_user_level = std::nullopt; - bool m_override_level = false; bool m_use_fast_time_of_day = true; - bool m_debug_wireframe = false; bool m_debug_all_visible = false; bool m_hide_wind = false; - Filtered m_all_tree_time; TfragPcPortData m_pc_port_data; @@ -136,5 +159,27 @@ class Tie3 : public BucketRenderer { int m_level_id; + tfrag3::TieCategory m_default_category; + + struct { + GLuint decal; + } m_uniforms; + + EtieUniforms m_etie_uniforms, m_etie_base_uniforms; + static_assert(sizeof(WindWork) == 84 * 16); }; + +class Tie3AnotherCategory : public BucketRenderer { + public: + Tie3AnotherCategory(const std::string& name, + int my_id, + Tie3* parent, + tfrag3::TieCategory category); + void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override; + void draw_debug_window() override; + + private: + Tie3* m_parent; + tfrag3::TieCategory m_category; +}; \ No newline at end of file diff --git a/game/graphics/opengl_renderer/background/background_common.cpp b/game/graphics/opengl_renderer/background/background_common.cpp index 5b7bce94e..b24d2196e 100644 --- a/game/graphics/opengl_renderer/background/background_common.cpp +++ b/game/graphics/opengl_renderer/background/background_common.cpp @@ -36,9 +36,14 @@ DoubleDraw setup_opengl_from_draw_mode(DrawMode mode, u32 tex_unit, bool mipmap) DoubleDraw double_draw; + bool should_enable_blend = false; if (mode.get_ab_enable() && mode.get_alpha_blend() != DrawMode::AlphaBlend::DISABLED) { - glEnable(GL_BLEND); + should_enable_blend = true; switch (mode.get_alpha_blend()) { + case DrawMode::AlphaBlend::SRC_SRC_SRC_SRC: + should_enable_blend = false; + // (SRC - SRC) * alpha + SRC = SRC, no blend. + break; case DrawMode::AlphaBlend::SRC_DST_SRC_DST: glBlendEquation(GL_FUNC_ADD); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); @@ -71,6 +76,12 @@ DoubleDraw setup_opengl_from_draw_mode(DrawMode mode, u32 tex_unit, bool mipmap) default: ASSERT(false); } + } else { + should_enable_blend = false; + } + + if (should_enable_blend) { + glEnable(GL_BLEND); } else { glDisable(GL_BLEND); } @@ -159,6 +170,8 @@ void first_tfrag_draw_setup(const TfragRenderSettings& settings, sh.activate(); auto id = sh.id(); glUniform1i(glGetUniformLocation(id, "gfx_hack_no_tex"), Gfx::g_global_settings.hack_no_tex); + glUniform1i(glGetUniformLocation(id, "decal"), false); + glUniform1i(glGetUniformLocation(id, "tex_T0"), 0); glUniformMatrix4fv(glGetUniformLocation(id, "camera"), 1, GL_FALSE, settings.math_camera.data()); glUniform4f(glGetUniformLocation(id, "hvdf_offset"), settings.hvdf_offset[0], @@ -752,6 +765,8 @@ void update_render_state_from_pc_settings(SharedRenderState* state, const TfragP for (int i = 0; i < 4; i++) { state->camera_planes[i] = data.planes[i]; state->camera_matrix[i] = data.camera[i]; + state->camera_no_persp[i] = data.camera_rot[i]; + state->camera_persp[i] = data.camera_perspective[i]; } state->camera_pos = data.cam_trans; state->camera_hvdf_off = data.hvdf_off; diff --git a/game/graphics/opengl_renderer/background/background_common.h b/game/graphics/opengl_renderer/background/background_common.h index 49544a62e..3f414a650 100644 --- a/game/graphics/opengl_renderer/background/background_common.h +++ b/game/graphics/opengl_renderer/background/background_common.h @@ -13,8 +13,13 @@ struct TfragPcPortData { math::Vector4f hvdf_off; math::Vector4f fog; math::Vector4f cam_trans; + + math::Vector4f camera_rot[4]; + math::Vector4f camera_perspective[4]; + char level_name[16]; }; +static_assert(sizeof(TfragPcPortData) == 16 * 24); // inputs to background renderers. struct TfragRenderSettings { diff --git a/game/graphics/opengl_renderer/shaders/emerc.vert b/game/graphics/opengl_renderer/shaders/emerc.vert index f8585841d..4ebe59803 100644 --- a/game/graphics/opengl_renderer/shaders/emerc.vert +++ b/game/graphics/opengl_renderer/shaders/emerc.vert @@ -104,9 +104,7 @@ void main() { // this is required to make jak 1's envmapping look right // otherwise it behaves like the envmap texture is mirrored. - // TODO: see if this is right for jak 2 or not. - // It _might_ make sense that this exists because we skip the multiply by Q - // below, and Q is negative (no idea how that works out with clamp). + // this is because we flip vtx_pos above with a negative sign. st_mod.x = 1 - vf10.x; st_mod.y = 1 - vf10.y; diff --git a/game/graphics/opengl_renderer/shaders/etie.frag b/game/graphics/opengl_renderer/shaders/etie.frag new file mode 100644 index 000000000..094a0f14c --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/etie.frag @@ -0,0 +1,31 @@ +#version 430 core + +out vec4 color; + +in vec4 fragment_color; +in vec3 tex_coord; +in float fogginess; +uniform sampler2D tex_T0; + +uniform float alpha_min; +uniform float alpha_max; +uniform vec4 fog_color; + +uniform int gfx_hack_no_tex; + + +void main() { + if (gfx_hack_no_tex == 0) { + //vec4 T0 = texture(tex_T0, tex_coord); + vec4 T0 = texture(tex_T0, tex_coord.xy); + color = fragment_color * T0; + } else { + color = fragment_color/2; + } + + if (color.a < alpha_min || color.a > alpha_max) { + discard; + } + + color.rgb = mix(color.rgb, fog_color.rgb, clamp(fogginess * fog_color.a, 0, 1)); +} diff --git a/game/graphics/opengl_renderer/shaders/etie.vert b/game/graphics/opengl_renderer/shaders/etie.vert new file mode 100644 index 000000000..56e28b2c0 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/etie.vert @@ -0,0 +1,165 @@ +#version 430 core + +layout (location = 0) in vec3 position_in; +layout (location = 1) in vec3 tex_coord_in; +layout (location = 2) in int time_of_day_index; +layout (location = 3) in vec3 normal; +layout (location = 4) in vec4 proto_tint; + +uniform vec4 hvdf_offset; +uniform mat4 camera; +uniform float fog_constant; +uniform float fog_min; +uniform float fog_max; +uniform vec4 envmap_tod_tint; +layout (binding = 10) uniform sampler1D tex_T1; // note, sampled in the vertex shader on purpose. +uniform int decal; + +out vec4 fragment_color; +out vec3 tex_coord; +out float fogginess; + +// etie stuff +uniform vec4 persp0; +uniform vec4 persp1; +uniform mat4 cam_no_persp; + +void main() { + + // maybe we could do fog faster using intermediate results from below, but it doesn't seem significant. + float fog1 = camera[3].w + camera[0].w * position_in.x + camera[1].w * position_in.y + camera[2].w * position_in.z; + fogginess = 255 - clamp(fog1 + hvdf_offset.w, fog_min, fog_max); + + // rotate the normal + vec3 nrm_vf23 = cam_no_persp[0].xyz * normal.x + + cam_no_persp[1].xyz * normal.y + + cam_no_persp[2].xyz * normal.z; + vec3 r_nrm = nrm_vf23; + + // transform the point + vec4 vf17 = cam_no_persp[3]; + vf17 += cam_no_persp[0] * position_in.x; + vf17 += cam_no_persp[1] * position_in.y; + vf17 += cam_no_persp[2] * position_in.z; + + /* + // This is the ETIE math. + // It seems right only if the nrm_vf23 is normalized first + // (and in this case it's identical the emerc math below) + // There is no reason to prefer the emerc version over etie - they are identical. + // likely on PS2, their normal transformation matrix was scaled correctly. + // ours isn't. (yes, the camera matrix is a pure rotation and that doesn't matter, but the + // instance matrix used to de-instance the mesh needs the correction, and we don't have it) + if (use_emerc_math == 0) { + nrm_vf23 = nrm_vf23; + // nrm.z -= 1 + //subw.z vf23, vf23, vf00 + nrm_vf23.z -= 1.f; + + // dot = nrm.xyz * pt.xyz + //mul.xyz vf13, vf17, vf23 + //esum.xyzw P, vf13 + //mfp.x vf13, P + float nrm_dot = dot(vf17.xyz, nrm_vf23); + + // rfl = pt.xzy * nrm.z + //mulz.xyz vf14, vf17, vf23 + vec3 rfl_vf14 = vf17.xyz * nrm_vf23.z; + + //;; Q_envmap = vf02.w / norm(rfl.xyz) + //esadd.xyz P, vf14 + //mfp.x vf30, P + //rsqrt Q, vf02.w, vf30.x + float Q_envmap = -0.5 / length(rfl_vf14); + + // + //;; nrm.xy *= dot.x + //mulx.xy vf23, vf23, vf13 + nrm_vf23.xy *= nrm_dot; + + // + //;; nrm.xy += rfl.xy + //add.xy vf23, vf23, vf14 + nrm_vf23.xy += rfl_vf14.xy; + // + //;; nrm.z = 1.0 + //addw.z vf23, vf00, vf00 + nrm_vf23.z = 1.0; + // + //;; nrm.xy *= Q_envmap + //mul.xy vf23, vf23, Q + nrm_vf23.xy *= Q_envmap; + // + + // + //;; nrm.xy += vf03.w + //addw.xy vf23, vf23, vf03 + nrm_vf23.xy += 0.5; + tex_coord = nrm_vf23; + }*/ + + //;; perspective transform + //mula.xy ACC, vf10, vf17 ;; acc build 1 + //mulaw.zw ACC, vf10, vf00 ;; acc build 2 + vec4 p_proj = vec4(persp1.x * vf17.x, persp1.y * vf17.y, persp1.z, persp1.w); + + //maddz.xyzw vf18, vf09, vf17 ;; acc star + p_proj += persp0 * vf17.z; + + //;; perspective divide + //div Q, vf00.w, vf18.w + float pQ = 1.f / p_proj.w; + + // EMERC version of the math + { + // emerc hack + vec3 vf10 = normalize(r_nrm); + //subw.z vf10, vf10, vf00 ;; subtract 1 from z + vf10.z -= 1; + //addw.z vf09, vf00, vf09 ;; xyww the unperspected thing + vec4 vf09 = vf17; + //mul.xyz vf15, vf09, vf10 ;; + vec3 vf15 = vf09.xyz * vf10.xyz; + //adday.xyzw vf15, vf15 + //maddz.x vf15, vf21, vf15 + float vf15_x = vf15.x + vf15.y + vf15.z; + //div Q, vf15.x, vf10.z + float qq = vf15_x / vf10.z; + //mulaw.xyzw ACC, vf09, vf00 + vec3 ACC = vf09.xyz; + //madd.xyzw vf10, vf10, Q + vf10 = ACC + vf10 * qq; + //eleng.xyz P, vf10 + float P = length(vf10.xyz); + //mfp.w vf10, P + //div Q, vf23.z, vf10.w + float qqq = 0.5 / P; + //addaz.xyzw vf00, vf23 + ACC = vec3(0.5, 0.5, 0.5); + //madd.xyzw vf10, vf10, Q + vf10 = ACC + vf10 * qqq; + tex_coord = vf10.xyz; + } + + + vec4 transformed = p_proj * pQ; + transformed.w = p_proj.w; + + // correct xy offset + transformed.xy -= (2048.); + // correct z scale + transformed.z /= (8388608); + transformed.z -= 1; + // correct xy scale + transformed.x /= (256); + transformed.y /= -(128); + // hack + transformed.xyz *= transformed.w; + // scissoring area adjust + transformed.y *= SCISSOR_ADJUST * HEIGHT_SCALE; + gl_Position = transformed; + + + fragment_color = proto_tint * envmap_tod_tint; + +} diff --git a/game/graphics/opengl_renderer/shaders/etie_base.frag b/game/graphics/opengl_renderer/shaders/etie_base.frag new file mode 100644 index 000000000..ea900d8db --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/etie_base.frag @@ -0,0 +1,32 @@ +#version 430 core + +out vec4 color; + +in vec4 fragment_color; +in vec3 tex_coord; +in float fogginess; +uniform sampler2D tex_T0; + +uniform float alpha_min; +uniform float alpha_max; +uniform vec4 fog_color; + +uniform int gfx_hack_no_tex; + + +void main() { + if (gfx_hack_no_tex == 0) { + //vec4 T0 = texture(tex_T0, tex_coord); + vec4 T0 = texture(tex_T0, tex_coord.xy); + color = fragment_color * T0; + } else { + color = fragment_color/2; + } + + if (color.a < alpha_min || color.a > alpha_max) { + discard; + } + + color.rgb = mix(color.rgb, fog_color.rgb, clamp(fogginess * fog_color.a, 0, 1)); + +} diff --git a/game/graphics/opengl_renderer/shaders/etie_base.vert b/game/graphics/opengl_renderer/shaders/etie_base.vert new file mode 100644 index 000000000..c9fcc44fc --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/etie_base.vert @@ -0,0 +1,70 @@ +#version 430 core + +layout (location = 0) in vec3 position_in; +layout (location = 1) in vec3 tex_coord_in; +layout (location = 2) in int time_of_day_index; + +uniform vec4 hvdf_offset; +uniform mat4 camera; +uniform float fog_constant; +uniform float fog_min; +uniform float fog_max; +layout (binding = 10) uniform sampler1D tex_T1; // note, sampled in the vertex shader on purpose. +uniform int decal; + +out vec4 fragment_color; +out vec3 tex_coord; +out float fogginess; + +// etie stuff +uniform vec4 persp0; +uniform vec4 persp1; +uniform mat4 cam_no_persp; + +void main() { + float fog1 = camera[3].w + camera[0].w * position_in.x + camera[1].w * position_in.y + camera[2].w * position_in.z; + fogginess = 255 - clamp(fog1 + hvdf_offset.w, fog_min, fog_max); + vec4 vf17 = cam_no_persp[3]; + vf17 += cam_no_persp[0] * position_in.x; + vf17 += cam_no_persp[1] * position_in.y; + vf17 += cam_no_persp[2] * position_in.z; + vec4 p_proj = vec4(persp1.x * vf17.x, persp1.y * vf17.y, persp1.z, persp1.w); + p_proj += persp0 * vf17.z; + + float pQ = 1.f / p_proj.w; + vec4 transformed = p_proj * pQ; + transformed.w = p_proj.w; + + // correct xy offset + transformed.xy -= (2048.); + // correct z scale + transformed.z /= (8388608); + transformed.z -= 1; + // correct xy scale + transformed.x /= (256); + transformed.y /= -(128); + // hack + transformed.xyz *= transformed.w; + // scissoring area adjust + transformed.y *= SCISSOR_ADJUST * HEIGHT_SCALE; + gl_Position = transformed; + + + + if (decal == 1) { + fragment_color = vec4(1.0, 1.0, 1.0, 1.0); + } else { + // time of day lookup + fragment_color = texelFetch(tex_T1, time_of_day_index, 0); + // color adjustment + fragment_color *= 2; + fragment_color.a *= 2; + } + + // fog hack + if (fragment_color.r < 0.005 && fragment_color.g < 0.005 && fragment_color.b < 0.005) { + fogginess = 0; + } + + tex_coord = tex_coord_in; +} diff --git a/game/graphics/opengl_renderer/shaders/tfrag3.frag b/game/graphics/opengl_renderer/shaders/tfrag3.frag index a0e25d8c5..094a0f14c 100644 --- a/game/graphics/opengl_renderer/shaders/tfrag3.frag +++ b/game/graphics/opengl_renderer/shaders/tfrag3.frag @@ -13,6 +13,7 @@ uniform vec4 fog_color; uniform int gfx_hack_no_tex; + void main() { if (gfx_hack_no_tex == 0) { //vec4 T0 = texture(tex_T0, tex_coord); diff --git a/game/graphics/opengl_renderer/shaders/tfrag3.vert b/game/graphics/opengl_renderer/shaders/tfrag3.vert index a31ee481f..a04fcdba1 100644 --- a/game/graphics/opengl_renderer/shaders/tfrag3.vert +++ b/game/graphics/opengl_renderer/shaders/tfrag3.vert @@ -10,6 +10,7 @@ uniform float fog_constant; uniform float fog_min; uniform float fog_max; layout (binding = 10) uniform sampler1D tex_T1; // note, sampled in the vertex shader on purpose. +uniform int decal; out vec4 fragment_color; out vec3 tex_coord; @@ -62,12 +63,15 @@ void main() { transformed.y *= SCISSOR_ADJUST * HEIGHT_SCALE; gl_Position = transformed; - // time of day lookup - fragment_color = texelFetch(tex_T1, time_of_day_index, 0); - - // color adjustment - fragment_color *= 2; - fragment_color.a *= 2; + if (decal == 1) { + fragment_color = vec4(1.0, 1.0, 1.0, 1.0); + } else { + // time of day lookup + fragment_color = texelFetch(tex_T1, time_of_day_index, 0); + // color adjustment + fragment_color *= 2; + fragment_color.a *= 2; + } // fog hack if (fragment_color.r < 0.005 && fragment_color.g < 0.005 && fragment_color.b < 0.005) { diff --git a/game/main.cpp b/game/main.cpp index 08e45eaf2..6e256d34d 100644 --- a/game/main.cpp +++ b/game/main.cpp @@ -91,8 +91,8 @@ int main(int argc, char** argv) { // // Only handling args the launcher provides, all others can be changed // in this repo at the time of merge. - std::vector adjusted_argv_vals; - std::vector adjusted_argv_vals_passthru; + std::vector adjusted_argv_vals; + std::vector adjusted_argv_vals_passthru; for (int i = 0; i < argc; i++) { const auto& val = std::string(argv[i]); // Handle all args that aren't passed through @@ -117,7 +117,7 @@ int main(int argc, char** argv) { } } - std::vector new_argv; + std::vector new_argv; if (!adjusted_argv_vals.empty() || !adjusted_argv_vals_passthru.empty()) { new_argv.push_back(argv[0]); for (const auto& arg : adjusted_argv_vals) { @@ -129,7 +129,7 @@ int main(int argc, char** argv) { new_argv.push_back(arg); } } - argv = new_argv.data(); + argv = (char**)new_argv.data(); argc = new_argv.size(); } // --- END temporary shim @@ -209,7 +209,7 @@ int main(int argc, char** argv) { bool force_debug_next_time = false; // always start with an empty arg, as internally kmachine starts at `1` not `0` - std::vector arg_ptrs = {""}; + std::vector arg_ptrs = {""}; for (auto& str : game_args) { arg_ptrs.push_back(str.data()); } diff --git a/game/runtime.cpp b/game/runtime.cpp index 49cdf3df7..07dd2843f 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -75,7 +75,7 @@ GameVersion g_game_version = GameVersion::Jak1; namespace { int g_argc = 0; -char** g_argv = nullptr; +const char** g_argv = nullptr; /*! * SystemThread function for running the DECI2 communication with the GOAL compiler. @@ -310,7 +310,7 @@ void dmac_runner(SystemThreadInterface& iface) { * Main function to launch the runtime. * GOAL kernel arguments are currently ignored. */ -RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, char** argv) { +RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const char** argv) { g_argc = argc; g_argv = argv; g_main_thread_id = std::this_thread::get_id(); diff --git a/game/runtime.h b/game/runtime.h index f554e40ce..b545c9d71 100644 --- a/game/runtime.h +++ b/game/runtime.h @@ -16,6 +16,6 @@ extern u8* g_ee_main_mem; extern GameVersion g_game_version; -RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, char** argv); +RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const char** argv); extern std::thread::id g_main_thread_id; diff --git a/goal_src/jak1/engine/gfx/tfrag/tfrag.gc b/goal_src/jak1/engine/gfx/tfrag/tfrag.gc index e44fbdea9..72c51a258 100644 --- a/goal_src/jak1/engine/gfx/tfrag/tfrag.gc +++ b/goal_src/jak1/engine/gfx/tfrag/tfrag.gc @@ -37,7 +37,7 @@ (defmethod mem-usage tfragment ((obj tfragment) (arg0 memory-usage-block) (arg1 int)) "Compute the memory usage of a tfragment" - + ;; seems like this flag does colors? (when (logtest? arg1 2) (+! (-> arg0 data 19 count) 1) @@ -47,18 +47,18 @@ ) (return obj) ) - + (let ((s4-0 1)) (set! (-> arg0 length) (max (-> arg0 length) (+ s4-0 8))) (set! (-> arg0 data s4-0 name) (symbol->string 'tfragment)) (+! (-> arg0 data s4-0 count) 1) - + ;; the size of the actual tfragment (let ((v1-22 (asize-of obj))) (+! (-> arg0 data s4-0 used) v1-22) (+! (-> arg0 data s4-0 total) (logand -16 (+ v1-22 15))) ) - + ;; the size of the "base" DMA (set! (-> arg0 data (+ s4-0 1) name) "tfragment-base") (+! (-> arg0 data (+ s4-0 1) count) 1) @@ -66,7 +66,7 @@ (+! (-> arg0 data (+ s4-0 1) used) v1-33) (+! (-> arg0 data (+ s4-0 1) total) v1-33) ) - + ;; the size of the "common" DMA (set! (-> arg0 data (+ s4-0 2) name) "tfragment-common") (+! (-> arg0 data (+ s4-0 2) count) 1) @@ -74,7 +74,7 @@ (+! (-> arg0 data (+ s4-0 2) used) v1-43) (+! (-> arg0 data (+ s4-0 2) total) v1-43) ) - + ;; the size of the "level0" DMA (set! (-> arg0 data (+ s4-0 3) name) "tfragment-level0") (when (nonzero? (-> obj num-level0-colors)) @@ -84,7 +84,7 @@ (+! (-> arg0 data (+ s4-0 3) total) v1-55) ) ) - + ;; The size of the "level0" DMA. Note that the dma chains can overlap, so this is a bit weird. (set! (-> arg0 data (+ s4-0 4) name) "tfragment-level1") (when (not (or (= (-> obj dma-level-1) (-> obj dma-common)) @@ -104,7 +104,7 @@ (+! (-> arg0 data (+ s4-0 4) total) v1-70) ) ) - + ;; colors (set! (-> arg0 data (+ s4-0 5) name) "tfragment-color") (+! (-> arg0 data (+ s4-0 5) count) 1) @@ -117,11 +117,11 @@ (+! (-> arg0 data (+ s4-0 5) used) v1-79) (+! (-> arg0 data (+ s4-0 5) total) (logand -16 (+ v1-79 15))) ) - + ;; debug (unused) (set! (-> arg0 data (+ s4-0 6) name) "tfragment-debug") ) - + obj ) @@ -322,12 +322,12 @@ (defun add-pc-tfrag3-data ((dma-buf dma-buffer) (lev level)) "Add PC-port specific tfrag data" (let ((packet (the-as dma-packet (-> dma-buf base)))) - (set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 16)) + (set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 24)) (set! (-> packet vif0) (new 'static 'vif-tag)) (set! (-> packet vif1) (new 'static 'vif-tag :cmd (vif-cmd pc-port))) (set! (-> dma-buf base) (the pointer (&+ packet 16))) ) - + ;; first 4 quadwords are planes, then itimes (let ((data-ptr (the-as (pointer uint128) (-> dma-buf base)))) (set! (-> data-ptr 0) (-> *math-camera* plane 0 quad)) @@ -349,9 +349,18 @@ (set! (-> vec z) (-> *math-camera* fog-max)) ) (set! (-> data-ptr 14) (-> (math-camera-pos) quad)) - (charp<-string (the (pointer uint8) (&-> data-ptr 15)) (symbol->string (-> lev nickname))) + (set! (-> data-ptr 15) (-> *math-camera* camera-rot vector 0 quad)) + (set! (-> data-ptr 16) (-> *math-camera* camera-rot vector 1 quad)) + (set! (-> data-ptr 17) (-> *math-camera* camera-rot vector 2 quad)) + (set! (-> data-ptr 18) (-> *math-camera* camera-rot vector 3 quad)) + + (set! (-> data-ptr 19) (-> *math-camera* perspective vector 0 quad)) + (set! (-> data-ptr 20) (-> *math-camera* perspective vector 1 quad)) + (set! (-> data-ptr 21) (-> *math-camera* perspective vector 2 quad)) + (set! (-> data-ptr 22) (-> *math-camera* perspective vector 3 quad)) + (charp<-string (the (pointer uint8) (&-> data-ptr 23)) (symbol->string (-> lev nickname))) ) - (&+! (-> dma-buf base) (* 16 16)) + (&+! (-> dma-buf base) (* 16 24)) ) ;;;;;;;;;;;;;;;;;;;;; diff --git a/goal_src/jak2/engine/gfx/background/background.gc b/goal_src/jak2/engine/gfx/background/background.gc index fb3cfe2f2..099020930 100644 --- a/goal_src/jak2/engine/gfx/background/background.gc +++ b/goal_src/jak2/engine/gfx/background/background.gc @@ -607,7 +607,7 @@ (defun add-pc-tfrag3-data ((dma-buf dma-buffer) (lev level)) "Add PC-port specific tfrag data" (let ((packet (the-as dma-packet (-> dma-buf base)))) - (set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 16)) + (set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 24)) (set! (-> packet vif0) (new 'static 'vif-tag)) (set! (-> packet vif1) (new 'static 'vif-tag :cmd (vif-cmd pc-port))) (set! (-> dma-buf base) (the pointer (&+ packet 16))) @@ -638,6 +638,17 @@ (set! (-> vec z) (-> *math-camera* fog-max)) ) (set! (-> data-ptr 14) (-> *math-camera* trans-other quad)) + + (set! (-> data-ptr 15) (-> *math-camera* camera-rot-other vector 0 quad)) + (set! (-> data-ptr 16) (-> *math-camera* camera-rot-other vector 1 quad)) + (set! (-> data-ptr 17) (-> *math-camera* camera-rot-other vector 2 quad)) + (set! (-> data-ptr 18) (-> *math-camera* camera-rot-other vector 3 quad)) + + (set! (-> data-ptr 19) (-> *math-camera* perspective vector 0 quad)) + (set! (-> data-ptr 20) (-> *math-camera* perspective vector 1 quad)) + (set! (-> data-ptr 21) (-> *math-camera* perspective vector 2 quad)) + (set! (-> data-ptr 22) (-> *math-camera* perspective vector 3 quad)) + ) (else (set! (-> data-ptr 0) (-> *math-camera* plane 0 quad)) @@ -659,10 +670,20 @@ (set! (-> vec z) (-> *math-camera* fog-max)) ) (set! (-> data-ptr 14) (-> *math-camera* trans quad)) + + (set! (-> data-ptr 15) (-> *math-camera* camera-rot vector 0 quad)) + (set! (-> data-ptr 16) (-> *math-camera* camera-rot vector 1 quad)) + (set! (-> data-ptr 17) (-> *math-camera* camera-rot vector 2 quad)) + (set! (-> data-ptr 18) (-> *math-camera* camera-rot vector 3 quad)) + + (set! (-> data-ptr 19) (-> *math-camera* perspective vector 0 quad)) + (set! (-> data-ptr 20) (-> *math-camera* perspective vector 1 quad)) + (set! (-> data-ptr 21) (-> *math-camera* perspective vector 2 quad)) + (set! (-> data-ptr 22) (-> *math-camera* perspective vector 3 quad)) ) ) - (charp<-string (the (pointer uint8) (&-> data-ptr 15)) (symbol->string (-> lev nickname))) + (charp<-string (the (pointer uint8) (&-> data-ptr 23)) (symbol->string (-> lev nickname))) ) - (&+! (-> dma-buf base) (* 16 16)) + (&+! (-> dma-buf base) (* 16 24)) ) diff --git a/goal_src/jak2/engine/gfx/tie/tie-methods.gc b/goal_src/jak2/engine/gfx/tie/tie-methods.gc index 2887240c6..8287296e8 100644 --- a/goal_src/jak2/engine/gfx/tie/tie-methods.gc +++ b/goal_src/jak2/engine/gfx/tie/tie-methods.gc @@ -110,6 +110,23 @@ ) ) +(defun pc-add-tie-envmap-info ((dma-buf dma-buffer)) + (let ((packet (the-as dma-packet (-> dma-buf base)))) + (set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 1)) + (set! (-> packet vif0) (new 'static 'vif-tag)) + (set! (-> packet vif1) (new 'static 'vif-tag :cmd (vif-cmd pc-port))) + (set! (-> dma-buf base) (the pointer (&+ packet 16))) + (set! (-> (the (pointer uint128) (-> dma-buf base))) + (if (and *time-of-day-context* + (nonzero? *time-of-day-context*)) + (-> *time-of-day-context* current-env-color quad) + (the uint128 0) + ) + ) + (set! (-> dma-buf base) (the pointer (&+ packet 32))) + ) + ) + (defun instance-tie-patch-buckets ((arg0 dma-buffer) (arg1 level)) ; (when (logtest? (-> *display* vu1-enable-user) (vu1-renderer-mask tie)) ; (when (nonzero? (-> *prototype-tie-work* scissor-count)) @@ -220,6 +237,7 @@ ; (&+! (-> v1-42 base) 16) (add-pc-tfrag3-data v1-42 arg1) (pc-add-tie-vis-mask arg1 v1-42) + (pc-add-tie-envmap-info v1-42) (let ((a3-16 (-> v1-42 base))) (let ((a0-20 (the-as dma-packet (-> v1-42 base)))) (set! (-> a0-20 dma) (new 'static 'dma-tag :id (dma-tag-id next))) @@ -1442,14 +1460,14 @@ (none) ) -(defun tie-init-envmap-buf ((arg0 bucket-id) (arg1 gs-alpha) (arg2 int) (arg3 int)) - ;; nothing yet +(defun tie-init-envmap-buf ((arg0 bucket-id) (arg1 gs-alpha) (arg2 gs-test)) + ;; PC doesn't need this, skip it. ; (let ((s5-0 (-> *display* frames (-> *display* on-screen) bucket-group arg0))) ; (when (!= s5-0 (-> s5-0 last)) ; (let* ((s4-0 (-> *display* frames (-> *display* on-screen) global-buf)) ; (s3-1 (-> s4-0 base)) ; ) - ; (etie-init-engine s4-0) + ; (etie-init-engine s4-0 arg1 arg2) ; (let ((v1-12 (the-as dma-packet (-> s4-0 base)))) ; (set! (-> v1-12 dma) (new 'static 'dma-tag :id (dma-tag-id next) :addr (-> s5-0 next))) ; (set! (-> v1-12 vif0) (new 'static 'vif-tag)) @@ -1632,7 +1650,11 @@ (a3-1 #x50000) ) (t9-1 a0-3 (the-as gs-alpha a1-2) (the-as gs-test a2-1) (the-as gs-test a3-1)) - (tie-init-envmap-buf (-> s2-0 tie-envmap-bucket) (the-as gs-alpha gp-0) #x50000 a3-1) + (tie-init-envmap-buf + (-> s2-0 tie-envmap-bucket) + (the-as gs-alpha gp-0) + (new 'static 'gs-test :zte #x1 :ztst (gs-ztest greater-equal)) + ) (tie-init-envmap-scissor-buf (-> s2-0 tie-envmap-scissor-bucket) (the-as gs-alpha gp-0) #x50000 a3-1) ) (tie-init-buf @@ -1666,7 +1688,11 @@ (a3-4 #x50000) ) (t9-6 a0-8 (the-as gs-alpha a1-7) (the-as gs-test a2-6) (the-as gs-test a3-4)) - (tie-init-envmap-buf (-> s2-0 tie-envmap-trans-bucket) (the-as gs-alpha s4-0) #x50000 a3-4) + (tie-init-envmap-buf + (-> s2-0 tie-envmap-trans-bucket) + (the-as gs-alpha s4-0) + (new 'static 'gs-test :zte #x1 :ztst (gs-ztest greater-equal)) + ) (tie-init-envmap-scissor-buf (-> s2-0 tie-envmap-scissor-trans-bucket) (the-as gs-alpha s4-0) #x50000 a3-4) ) (tie-init-buf @@ -1682,7 +1708,11 @@ (a3-6 #x51001) ) (t9-10 a0-12 (the-as gs-alpha a1-11) (the-as gs-test a2-10) (the-as gs-test a3-6)) - (tie-init-envmap-buf (-> s2-0 tie-envmap-water-bucket) (the-as gs-alpha s4-0) #x51001 a3-6) + (tie-init-envmap-buf + (-> s2-0 tie-envmap-water-bucket) + (the-as gs-alpha s4-0) + (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest greater-equal)) + ) (tie-init-envmap-scissor-buf (-> s2-0 tie-envmap-scissor-water-bucket) (the-as gs-alpha s4-0) #x51001 a3-6) ) ) diff --git a/goalc/build_level/build_level.cpp b/goalc/build_level/build_level.cpp index 647521533..431ad65fe 100644 --- a/goalc/build_level/build_level.cpp +++ b/goalc/build_level/build_level.cpp @@ -77,6 +77,7 @@ bool run_build_level(const std::string& input_file, add_ambients_from_json(level_json.at("ambients"), ambients, level_json.value("base_id", 12345)); file.ambients = std::move(ambients); auto& ambient_drawable_tree = file.drawable_trees.ambients.emplace_back(); + (void)ambient_drawable_tree; // cameras // nodes // boxes diff --git a/goalc/build_level/gltf_mesh_extract.cpp b/goalc/build_level/gltf_mesh_extract.cpp index 5c1914f3e..70893b6fd 100644 --- a/goalc/build_level/gltf_mesh_extract.cpp +++ b/goalc/build_level/gltf_mesh_extract.cpp @@ -241,11 +241,6 @@ ExtractedVertices gltf_vertices(const tinygltf::Model& model, v.s = 0; v.t = 0; } - - v.q_unused = 0; - v.pad[0] = 0; - v.pad[1] = 0; - v.pad[2] = 0; } // TODO: other properties return {result, vtx_colors, normals}; diff --git a/test/decompiler/test_VuDisasm.cpp b/test/decompiler/test_VuDisasm.cpp index cddfa1a57..3e2bba2ef 100644 --- a/test/decompiler/test_VuDisasm.cpp +++ b/test/decompiler/test_VuDisasm.cpp @@ -107,6 +107,13 @@ TEST(VuDisasm, Tie_Jak2) { EXPECT_EQ(disasm.to_string(prog), get_expected("jak2/tie")); } +TEST(VuDisasm, etie_Jak2) { + auto data = get_test_data("jak2/etie-vu1"); + VuDisassembler disasm(VuDisassembler::VuKind::VU1); + auto prog = disasm.disassemble(data.data(), data.size() * 4, false); + EXPECT_EQ(disasm.to_string(prog), get_expected("jak2/etie-vu1")); +} + TEST(VuDisasm, SpriteDistort) { auto data = get_test_data("sprite-distort"); VuDisassembler disasm(VuDisassembler::VuKind::VU1); @@ -146,7 +153,6 @@ TEST(VuDisasm, ForegroundVu0_Jak2) { auto data = get_test_data("jak2/foreground-vu0"); VuDisassembler disasm(VuDisassembler::VuKind::VU0); auto prog = disasm.disassemble(data.data(), data.size() * 4, false); - // fmt::print("{}\n", disasm.to_string(prog)); EXPECT_EQ(disasm.to_string(prog), get_expected("jak2/foreground-vu0")); } diff --git a/test/decompiler/vu_reference/jak2/etie-vu1-result.txt b/test/decompiler/vu_reference/jak2/etie-vu1-result.txt new file mode 100644 index 000000000..ac60c7bd5 --- /dev/null +++ b/test/decompiler/vu_reference/jak2/etie-vu1-result.txt @@ -0,0 +1,1535 @@ + b L14 | nop + nop | nop + b L2 | nop + nop | nop + b L2 | nop + nop | nop + b L3 | nop + isw.z vi00, 914(vi00) | nop + b L1 | nop + nop | nop + b L9 | nop + nop | nop +L1: + isw.w vi00, 914(vi00) | nop :e + isw.z vi00, 915(vi00) | nop +L2: + nop | nop :e + nop | nop +L3: + bal vi15, L4 | nop + nop | nop + ilw.w vi01, 914(vi00) | nop + nop | nop + nop | nop + nop | nop + iaddi vi01, vi01, 0x1 | nop + isw.w vi01, 914(vi00) | nop + nop | nop :e + nop | nop +L4: + ilw.w vi12, 898(vi00) | nop + lq.xyzw vf17, 903(vi00) | nop + lq.xyzw vf18, 904(vi00) | nop + lq.xyzw vf19, 905(vi00) | nop + lq.xyzw vf20, 906(vi00) | nop + lq.xyzw vf21, 907(vi00) | nop + iaddi vi04, vi00, 0x0 | nop + lq.xyz vf11, 899(vi00) | nop + ilwr.w vi05, vi04 | nop + ilw.w vi07, 1(vi04) | nop + ilw.w vi13, 2(vi04) | nop + lq.xyzw vf24, 900(vi00) | nop + lqi.xyzw vf12, vi04 | nop + lqi.xyzw vf13, vi04 | nop + lqi.xyzw vf14, vi04 | nop + lqi.xyzw vf15, vi04 | nop + lqi.xyzw vf16, vi04 | subw.w vf11, vf11, vf11 + iadd vi05, vi05, vi12 | nop + iadd vi06, vi05, vi13 | nop + iaddi vi01, vi00, 0x6 | nop + sq.xyzw vf11, -1(vi05) | nop + isw.x vi01, -1(vi05) | nop + sqi.xyzw vf12, vi05 | nop + sqi.xyzw vf13, vi05 | nop + sqi.xyzw vf14, vi05 | nop + sqi.xyzw vf15, vi05 | nop + sqi.xyzw vf16, vi05 | nop + b L6 | nop + sqi.xyzw vf24, vi05 | nop +L5: + iadd vi05, vi05, vi12 | nop + iadd vi06, vi05, vi13 | nop + sqi.xyzw vf11, vi05 | nop + sqi.xyzw vf12, vi05 | nop + sqi.xyzw vf13, vi05 | nop + sqi.xyzw vf14, vi05 | nop + sqi.xyzw vf15, vi05 | nop + sqi.xyzw vf16, vi05 | nop +L6: + sqi.xyzw vf11, vi06 | nop + sqi.xyzw vf17, vi06 | nop + sqi.xyzw vf18, vi06 | nop + sqi.xyzw vf19, vi06 | nop + sqi.xyzw vf20, vi06 | nop + sqi.xyzw vf21, vi06 | nop + iaddi vi07, vi07, -0x1 | nop + ilwr.w vi05, vi04 | nop + lqi.xyzw vf12, vi04 | nop + lqi.xyzw vf13, vi04 | nop + lqi.xyzw vf14, vi04 | nop + lqi.xyzw vf15, vi04 | nop + ibgtz vi07, L5 | nop + lqi.xyzw vf16, vi04 | nop + mtir vi07, vf12.w | nop + sq.xy vf12, 914(vi00) | nop + sq.xyzw vf13, 913(vi00) | nop + sq.xy vf14, 915(vi00) | nop + iaddi vi04, vi04, -0x2 | subw.w vf22, vf00, vf00 + ilwr.x vi08, vi04 | subw.w vf23, vf00, vf00 + ilwr.y vi09, vi04 | nop + ilwr.z vi05, vi04 | nop + iaddi vi07, vi07, -0x1 | nop + iaddi vi04, vi04, 0x1 | nop + lq.xyz vf22, 901(vi09) | nop + ibeq vi00, vi07, L8 | nop + lq.xyz vf23, 902(vi09) | nop +L7: + iadd vi05, vi05, vi12 | nop + iadd vi06, vi05, vi13 | nop + iaddi vi07, vi07, -0x1 | nop + sq.xyzw vf22, 0(vi05) | nop + iswr.x vi08, vi05 | nop + sq.xyzw vf23, 0(vi06) | nop + iswr.x vi08, vi06 | nop + ilwr.x vi08, vi04 | nop + ilwr.y vi09, vi04 | nop + ilwr.z vi05, vi04 | nop + iaddi vi04, vi04, 0x1 | nop + ibne vi00, vi07, L7 | nop + lq.xyz vf22, 901(vi09) | nop +L8: + iadd vi05, vi05, vi12 | nop + iadd vi06, vi05, vi13 | nop + sq.xyzw vf22, 0(vi05) | nop + iswr.x vi08, vi05 | nop + iaddiu vi08, vi08, 0x4000 | nop + iaddiu vi08, vi08, 0x4000 | nop + sq.xyzw vf23, 0(vi06) | nop + jr vi15 | nop + iswr.x vi08, vi06 | nop +L9: + iaddiu vi05, vi00, 0x84 | nop + 0.0078125 | nop :i + lqi.xyzw vf20, vi05 | addi.x vf23, vf00, I + ilw.x vi01, 915(vi00) | addw.z vf17, vf00, vf00 + lq.xyzw vf14, 32(vi00) | addw.z vf18, vf00, vf00 + lq.xyw vf17, 33(vi00) | addw.z vf19, vf00, vf00 + iaddiu vi03, vi00, 0x22 | itof0.xyzw vf20, vf20 + iaddiu vi01, vi01, 0x20 | itof0.xyz vf14, vf14 + lqi.xyzw vf15, vi03 | itof12.xy vf17, vf17 + lqi.xyw vf18, vi03 | nop + lqi.xyzw vf21, vi05 | nop + 64.0 | mulx.xyzw vf20, vf20, vf23 :i + ibeq vi01, vi03, L11 | muli.xyz vf14, vf14, I + ilw.y vi02, 915(vi00) | itof0.xyz vf15, vf15 +L10: + lqi.xyzw vf22, vi05 | itof0.xyzw vf21, vf21 + lqi.xyzw vf16, vi03 | itof12.xy vf18, vf18 + lqi.xyw vf19, vi03 | nop + sq.xyzw vf17, -5(vi03) | nop + sq.xyzw vf20, -3(vi05) | mulx.xyzw vf21, vf21, vf23 + ibeq vi01, vi03, L11 | muli.xyz vf15, vf15, I + sq.xyzw vf14, -6(vi03) | itof0.xyz vf16, vf16 + lqi.xyzw vf20, vi05 | itof0.xyzw vf22, vf22 + lqi.xyzw vf14, vi03 | itof12.xy vf19, vf19 + lqi.xyw vf17, vi03 | nop + sq.xyzw vf18, -5(vi03) | nop + sq.xyzw vf21, -3(vi05) | mulx.xyzw vf22, vf22, vf23 + ibeq vi01, vi03, L11 | muli.xyz vf16, vf16, I + sq.xyzw vf15, -6(vi03) | itof0.xyz vf14, vf14 + lqi.xyzw vf21, vi05 | itof0.xyzw vf20, vf20 + lqi.xyzw vf15, vi03 | itof12.xy vf17, vf17 + lqi.xyw vf18, vi03 | nop + sq.xyzw vf19, -5(vi03) | nop + sq.xyzw vf22, -3(vi05) | mulx.xyzw vf20, vf20, vf23 + ibne vi01, vi03, L10 | muli.xyz vf14, vf14, I + sq.xyzw vf16, -6(vi03) | itof0.xyz vf15, vf15 +L11: + iaddi vi05, vi05, -0x2 | nop + lq.xyzw vf11, -4(vi03) | nop + lq.xyzw vf14, -3(vi03) | nop + lq.xyzw vf17, -2(vi03) | nop + lqi.xyzw vf20, vi05 | nop + iadd vi02, vi02, vi03 | nop + iaddi vi02, vi02, -0x4 | nop + iaddi vi03, vi03, -0x1 | nop + iaddi vi04, vi03, -0x3 | nop + ibeq vi02, vi03, L13 | itof0.xyzw vf20, vf20 + nop | itof0.xyzw vf11, vf11 + lqi.xyzw vf12, vi03 | itof0.xyz vf14, vf14 + lqi.xyzw vf15, vi03 | nop + lqi.xyzw vf18, vi03 | itof12.xy vf17, vf17 + lqi.xyzw vf21, vi05 | mulx.xyzw vf20, vf20, vf23 + nop | muli.xyz vf11, vf11, I + ibeq vi02, vi03, L13 | muli.xyz vf14, vf14, I + nop | itof0.xyzw vf12, vf12 + nop | itof0.xyzw vf21, vf21 +L12: + lqi.xyzw vf13, vi03 | itof0.xyz vf15, vf15 + lqi.xyzw vf16, vi03 | nop + lqi.xyzw vf19, vi03 | itof12.xy vf18, vf18 + lqi.xyzw vf22, vi05 | mulx.xyzw vf21, vf21, vf23 + sq.w vf17, 1(vi04) | nop + sq.w vf14, 2(vi04) | nop + sqi.xyzw vf11, vi04 | nop + sqi.xyz vf14, vi04 | muli.xyz vf12, vf12, I + sq.xyzw vf20, -3(vi05) | itof0.xyzw vf22, vf22 + ibeq vi02, vi03, L13 | muli.xyz vf15, vf15, I + sqi.xyz vf17, vi04 | itof0.xyzw vf13, vf13 + lqi.xyzw vf11, vi03 | itof0.xyz vf16, vf16 + lqi.xyzw vf14, vi03 | nop + lqi.xyzw vf17, vi03 | itof12.xy vf19, vf19 + lqi.xyzw vf20, vi05 | mulx.xyzw vf22, vf22, vf23 + sq.w vf18, 1(vi04) | nop + sq.w vf15, 2(vi04) | nop + sqi.xyzw vf12, vi04 | nop + sqi.xyz vf15, vi04 | muli.xyz vf13, vf13, I + sq.xyzw vf21, -3(vi05) | itof0.xyzw vf20, vf20 + ibeq vi02, vi03, L13 | muli.xyz vf16, vf16, I + sqi.xyz vf18, vi04 | itof0.xyzw vf11, vf11 + lqi.xyzw vf12, vi03 | itof0.xyz vf14, vf14 + lqi.xyzw vf15, vi03 | nop + lqi.xyzw vf18, vi03 | itof12.xy vf17, vf17 + lqi.xyzw vf21, vi05 | mulx.xyzw vf20, vf20, vf23 + sq.w vf19, 1(vi04) | nop + sq.w vf16, 2(vi04) | nop + sqi.xyzw vf13, vi04 | nop + sqi.xyz vf16, vi04 | muli.xyz vf11, vf11, I + sq.xyzw vf22, -3(vi05) | itof0.xyzw vf21, vf21 + ibne vi02, vi03, L12 | muli.xyz vf14, vf14, I + sqi.xyz vf19, vi04 | itof0.xyzw vf12, vf12 +L13: + nop | nop :e + nop | nop +L14: + ilw.z vi01, 914(vi00) | nop + xtop vi10 | nop + lq.xyzw vf05, 0(vi10) | nop + lq.xyzw vf06, 1(vi10) | nop + iaddi vi01, vi01, -0x1 | nop + lq.xyzw vf07, 2(vi10) | nop + ibne vi00, vi01, L15 | nop + lq.xyzw vf08, 3(vi10) | nop + bal vi15, L4 | nop + nop | nop +L15: + lq.xyz vf12, 132(vi00) | nop + lq.xyzw vf02, 4(vi10) | nop + lq.xyzw vf03, 5(vi10) | nop + lq.xyzw vf04, 6(vi10) | nop + lq.xyzw vf16, 32(vi00) | subw.w vf13, vf00, vf00 + iaddiu vi08, vi00, 0x85 | mulax.xyzw ACC, vf02, vf12 + lq.xy vf24, 33(vi00) | madday.xyzw ACC, vf03, vf12 + iaddiu vi09, vi00, 0x22 | maddz.xyzw vf20, vf04, vf12 + nop | mulaw.xyzw ACC, vf08, vf00 + nop | maddax.xyzw ACC, vf05, vf16 + nop | madday.xyzw ACC, vf06, vf16 + lq.w vf29, 4(vi10) | subw.z vf20, vf20, vf00 + nop | maddz.xyz vf16, vf07, vf16 + lq.w vf19, 5(vi10) | subw.w vf02, vf00, vf00 + nop | subw.w vf03, vf00, vf00 + nop | addw.y vf31, vf00, vf29 + nop | mul.xyz vf13, vf16, vf20 + -0.5 | ftoi4.w vf19, vf19 :i + 0.5 | addi.w vf02, vf02, I :i + mtir vi02, vf16.w | addi.w vf03, vf03, I + esum.xyzw P, vf13 | mulz.xyz vf14, vf16, vf20 + lqi.xyz vf12, vi08 | addw.x vf31, vf00, vf00 + nop | addw.z vf24, vf00, vf00 + nop | addw.z vf25, vf00, vf00 + lqi.xyzw vf17, vi09 | addw.z vf26, vf00, vf00 + nop | mulax.xyzw ACC, vf02, vf12 + lqi.xy vf25, vi09 | madday.xyzw ACC, vf03, vf12 + nop | maddz.xyzw vf21, vf04, vf12 + nop | mulaw.xyzw ACC, vf08, vf00 + 128.0 | maddax.xyzw ACC, vf05, vf17 :i + esadd.xyz P, vf14 | madday.xyzw ACC, vf06, vf17 + mfp.x vf13, P | muli.w vf31, vf29, I + lq.xyzw vf09, 908(vi00) | subw.z vf21, vf21, vf00 + lq.xyzw vf10, 909(vi00) | maddz.xyz vf17, vf07, vf17 + nop | subw.x vf31, vf31, vf29 + nop | mulx.xy vf20, vf20, vf13 + nop | ftoi0.w vf31, vf31 + nop | mula.xy ACC, vf10, vf16 + 0.5 | mul.xyz vf13, vf17, vf21 :i + nop | muli.y vf31, vf31, I + 256.0 | itof0.w vf31, vf31 :i + mfp.x vf30, P | add.xy vf20, vf20, vf14 + mtir vi03, vf17.w | mulaw.zw ACC, vf10, vf00 + esum.xyzw P, vf13 | mulz.xyz vf14, vf17, vf21 + lqi.xyz vf12, vi08 | addw.z vf27, vf00, vf00 + nop | addi.z vf31, vf00, I + rsqrt Q, vf02.w, vf30.x | subw.z vf31, vf31, vf31 + nop | maddz.xyzw vf18, vf09, vf16 + lqi.xyzw vf16, vi09 | mulax.xyzw ACC, vf02, vf12 + lqi.xy vf26, vi09 | madday.xyzw ACC, vf03, vf12 + nop | maddz.xyzw vf22, vf04, vf12 + ilw.w vi12, 898(vi00) | mulaw.xyzw ACC, vf08, vf00 + lq.w vf30, 6(vi10) | maddax.xyzw ACC, vf05, vf16 + esadd.xyz P, vf14 | madday.xyzw ACC, vf06, vf16 + mfp.x vf13, P | subw.z vf31, vf31, vf31 + lq.xyzw vf01, 7(vi10) | subw.z vf22, vf22, vf00 + iaddi vi10, vi10, 0x8 | maddz.xyz vf16, vf07, vf16 + ilw.x vi14, 913(vi00) | addw.z vf20, vf00, vf00 + lqi.xyzw vf11, vi10 | mulx.xy vf21, vf21, vf13 + div Q, vf00.w, vf18.w | mul.xy vf20, vf20, Q + iaddiu vi11, vi00, 0x397 | mula.xy ACC, vf10, vf17 + iadd vi14, vi14, vi12 | mul.xyz vf13, vf16, vf22 +L16: + mtir vi01, vf11.x | addw.xy vf20, vf20, vf03 + mfp.x vf30, P | add.xy vf21, vf21, vf14 + mtir vi04, vf16.w | mulaw.zw ACC, vf10, vf00 + esum.xyzw P, vf13 | mulz.xyz vf14, vf16, vf22 + lqi.xyz vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf20, vi11 | mul.xyz vf28, vf24, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf20, vf20, Q + lqi.xyzw vf17, vi09 | maddz.xyzw vf18, vf09, vf17 + lq.xyz vf30, 770(vi01) | mulax.xyzw ACC, vf02, vf12 + lqi.xy vf27, vi09 | madday.xyzw ACC, vf03, vf12 + iadd vi02, vi02, vi12 | maddz.xyzw vf23, vf04, vf12 + iadd vi06, vi02, vi13 | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf30, 1(vi02) | maddax.xyzw ACC, vf05, vf17 + esadd.xyz P, vf14 | madday.xyzw ACC, vf06, vf17 + mfp.x vf13, P | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | subw.z vf23, vf23, vf00 + sq.xyzw vf20, 0(vi06) | maddz.xyz vf17, vf07, vf17 + sq.xyzw vf01, 1(vi06) | addw.z vf21, vf00, vf00 + sq.xyzw vf19, 2(vi02) | mulx.xy vf22, vf22, vf13 + div Q, vf00.w, vf18.w | mul.xy vf21, vf21, Q + ibeq vi14, vi02, L18 | mula.xy ACC, vf10, vf16 + sq.xyzw vf19, 2(vi06) | mul.xyz vf13, vf17, vf23 + mtir vi01, vf11.y | addw.xy vf21, vf21, vf03 + mfp.x vf30, P | add.xy vf22, vf22, vf14 + mtir vi05, vf17.w | mulaw.zw ACC, vf10, vf00 + esum.xyzw P, vf13 | mulz.xyz vf14, vf17, vf23 + lqi.xyz vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf21, vi11 | mul.xyz vf28, vf25, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf21, vf21, Q + lq.xyz vf30, 770(vi01) | maddz.xyzw vf18, vf09, vf16 + lqi.xyzw vf16, vi09 | mulax.xyzw ACC, vf02, vf12 + lqi.xy vf24, vi09 | madday.xyzw ACC, vf03, vf12 + iadd vi03, vi03, vi12 | maddz.xyzw vf20, vf04, vf12 + iadd vi06, vi03, vi13 | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf30, 1(vi03) | maddax.xyzw ACC, vf05, vf16 + esadd.xyz P, vf14 | madday.xyzw ACC, vf06, vf16 + mfp.x vf13, P | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi03) | subw.z vf20, vf20, vf00 + sq.xyzw vf21, 0(vi06) | maddz.xyz vf16, vf07, vf16 + sq.xyzw vf01, 1(vi06) | addw.z vf22, vf00, vf00 + sq.xyzw vf19, 2(vi03) | mulx.xy vf23, vf23, vf13 + div Q, vf00.w, vf18.w | mul.xy vf22, vf22, Q + ibeq vi14, vi03, L20 | mula.xy ACC, vf10, vf17 + sq.xyzw vf19, 2(vi06) | mul.xyz vf13, vf16, vf20 + mtir vi01, vf11.z | addw.xy vf22, vf22, vf03 + mfp.x vf30, P | add.xy vf23, vf23, vf14 + mtir vi02, vf16.w | mulaw.zw ACC, vf10, vf00 + esum.xyzw P, vf13 | mulz.xyz vf14, vf16, vf20 + lqi.xyz vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf22, vi11 | mul.xyz vf28, vf26, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf22, vf22, Q + lq.xyz vf30, 770(vi01) | maddz.xyzw vf18, vf09, vf17 + lqi.xyzw vf17, vi09 | mulax.xyzw ACC, vf02, vf12 + lqi.xy vf25, vi09 | madday.xyzw ACC, vf03, vf12 + iadd vi04, vi04, vi12 | maddz.xyzw vf21, vf04, vf12 + iadd vi06, vi04, vi13 | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf30, 1(vi04) | maddax.xyzw ACC, vf05, vf17 + esadd.xyz P, vf14 | madday.xyzw ACC, vf06, vf17 + mfp.x vf13, P | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi04) | subw.z vf21, vf21, vf00 + sq.xyzw vf22, 0(vi06) | maddz.xyz vf17, vf07, vf17 + sq.xyzw vf01, 1(vi06) | addw.z vf23, vf00, vf00 + sq.xyzw vf19, 2(vi04) | mulx.xy vf20, vf20, vf13 + div Q, vf00.w, vf18.w | mul.xy vf23, vf23, Q + ibeq vi14, vi04, L22 | mula.xy ACC, vf10, vf16 + sq.xyzw vf19, 2(vi06) | mul.xyz vf13, vf17, vf21 + mtir vi01, vf11.w | addw.xy vf23, vf23, vf03 + mfp.x vf30, P | add.xy vf20, vf20, vf14 + mtir vi03, vf17.w | mulaw.zw ACC, vf10, vf00 + esum.xyzw P, vf13 | mulz.xyz vf14, vf17, vf21 + lqi.xyz vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf23, vi11 | mul.xyz vf28, vf27, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf23, vf23, Q + lqi.xyzw vf16, vi09 | maddz.xyzw vf18, vf09, vf16 + lq.xyz vf30, 770(vi01) | mulax.xyzw ACC, vf02, vf12 + lqi.xy vf26, vi09 | madday.xyzw ACC, vf03, vf12 + iadd vi05, vi05, vi12 | maddz.xyzw vf22, vf04, vf12 + iadd vi06, vi05, vi13 | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf30, 1(vi05) | maddax.xyzw ACC, vf05, vf16 + esadd.xyz P, vf14 | madday.xyzw ACC, vf06, vf16 + mfp.x vf13, P | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi05) | subw.z vf22, vf22, vf00 + sq.xyzw vf23, 0(vi06) | maddz.xyz vf16, vf07, vf16 + sq.xyzw vf01, 1(vi06) | addw.z vf20, vf00, vf00 + sq.xyzw vf19, 2(vi05) | mulx.xy vf21, vf21, vf13 + lqi.xyzw vf11, vi10 | nop + div Q, vf00.w, vf18.w | mul.xy vf20, vf20, Q + ibne vi14, vi05, L16 | mula.xy ACC, vf10, vf17 + sq.xyzw vf19, 2(vi06) | mul.xyz vf13, vf16, vf22 + nop | addw.xy vf20, vf20, vf03 + mfp.x vf30, P | add.xy vf21, vf21, vf14 + mtir vi04, vf16.w | mulaw.zw ACC, vf10, vf00 + esum.xyzw P, vf13 | mulz.xyz vf14, vf16, vf22 + lqi.xyz vf12, vi08 | nop + mtir vi01, vf11.x | mul.xyz vf19, vf18, Q + sqi.xyzw vf20, vi11 | mul.xyz vf28, vf24, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf20, vf20, Q + lq.xyz vf30, 770(vi01) | maddz.xyzw vf18, vf09, vf17 + lqi.xyzw vf17, vi09 | mulax.xyzw ACC, vf02, vf12 + lqi.xyw vf27, vi09 | madday.xyzw ACC, vf03, vf12 + iadd vi02, vi02, vi12 | maddz.xyz vf23, vf04, vf12 + iadd vi06, vi02, vi13 | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf30, 1(vi02) | maddax.xyzw ACC, vf05, vf17 + esadd.xyz P, vf14 | madday.xyzw ACC, vf06, vf17 + mfp.x vf13, P | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | maddz.xyz vf17, vf07, vf17 + sq.xyzw vf20, 0(vi06) | subw.z vf23, vf23, vf00 + sq.xyzw vf01, 1(vi06) | addw.z vf21, vf00, vf00 + sq.xyzw vf19, 2(vi02) | mulx.xy vf22, vf22, vf13 + div Q, vf00.w, vf18.w | mul.xy vf21, vf21, Q + nop | mula.xy ACC, vf10, vf16 + sq.xyzw vf19, 2(vi06) | nop + mtir vi01, vf11.y | addw.xy vf21, vf21, vf03 + mfp.x vf30, P | add.xy vf22, vf22, vf14 + nop | mulaw.zw ACC, vf10, vf00 + nop | mulz.xyz vf14, vf17, vf23 + lqi.xyz vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf21, vi11 | mul.xyz vf28, vf25, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf21, vf21, Q + lq.xyz vf30, 770(vi01) | maddz.xyzw vf18, vf09, vf16 + lqi.xyzw vf16, vi09 | mul.xyz vf15, vf14, vf14 + nop | mulax.xyzw ACC, vf02, vf12 + iadd vi03, vi03, vi12 | madday.xyzw ACC, vf03, vf12 + iadd vi06, vi03, vi13 | maddz.xyz vf20, vf04, vf12 + sq.xyzw vf30, 1(vi03) | addy.x vf15, vf15, vf15 + nop | mul.xyz vf13, vf17, vf23 + nop | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi03) | nop + sq.xyzw vf21, 0(vi06) | addz.x vf15, vf15, vf15 + sq.xyzw vf01, 1(vi06) | addw.z vf22, vf00, vf00 + sq.xyzw vf19, 2(vi03) | addy.x vf13, vf13, vf13 + div Q, vf00.w, vf18.w | mul.xy vf22, vf22, Q + nop | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf19, 2(vi06) | maddax.xyzw ACC, vf05, vf16 + mtir vi01, vf11.z | addw.xy vf22, vf22, vf03 + nop | addz.x vf13, vf13, vf13 + nop | madday.xyzw ACC, vf06, vf16 + nop | maddz.xyz vf16, vf07, vf16 + nop | mul.xyz vf19, vf18, Q + sqi.xyzw vf22, vi11 | mul.xyz vf28, vf26, Q + nop | mul.xyz vf22, vf22, Q + lq.xyz vf30, 770(vi01) | nop + ilw.x vi14, 914(vi00) | nop + rsqrt Q, vf02.w, vf15.x | mulx.xy vf23, vf23, vf13 + iadd vi04, vi04, vi12 | nop + iadd vi06, vi04, vi13 | mula.xy ACC, vf10, vf17 + sq.xyzw vf30, 1(vi04) | mulaw.zw ACC, vf10, vf00 + ibne vi00, vi14, L17 | add.xy vf23, vf23, vf14 + lqi.xyw vf24, vi09 | ftoi4.xyz vf19, vf19 + ilw.y vi14, 913(vi00) | nop + lqi.xyz vf12, vi08 | subw.z vf20, vf20, vf00 + sq.xyzw vf28, 0(vi04) | nop + sq.xyzw vf22, 0(vi06) | maddz.xyzw vf18, vf09, vf17 + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf19, 2(vi04) | mulax.xyzw ACC, vf02, vf12 + iadd vi14, vi14, vi12 | madday.xyzw ACC, vf03, vf12 + b L27 | mulz.xyz vf14, vf16, vf20 + sq.xyzw vf19, 2(vi06) | maddz.xyz vf21, vf04, vf12 +L17: + ilw.y vi14, 914(vi00) | nop + sq.xyzw vf28, 0(vi04) | nop + sq.xyzw vf22, 0(vi06) | nop + sq.xyzw vf01, 0(vi06) | nop + sq.xyzw vf19, 2(vi04) | nop + ibne vi00, vi14, L39 | nop + sq.xyzw vf19, 2(vi06) | nop + b L32 | nop + nop | nop +L18: + nop | nop + mtir vi01, vf11.y | addw.xy vf21, vf21, vf03 + mfp.x vf30, P | add.xy vf22, vf22, vf14 + mtir vi05, vf17.w | mulaw.zw ACC, vf10, vf00 + esum.xyzw P, vf13 | mulz.xyz vf14, vf17, vf23 + lqi.xyzw vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf21, vi11 | mul.xyz vf28, vf25, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf21, vf21, Q + lq.xyz vf30, 770(vi01) | maddz.xyzw vf18, vf09, vf16 + lqi.xyzw vf16, vi09 | mulax.xyzw ACC, vf02, vf12 + lqi.xyw vf24, vi09 | madday.xyzw ACC, vf03, vf12 + iadd vi03, vi03, vi12 | maddz.xyz vf20, vf04, vf12 + iadd vi06, vi03, vi13 | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf30, 1(vi03) | maddax.xyzw ACC, vf05, vf16 + nop | nop + nop | nop + esadd.xyz P, vf14 | madday.xyzw ACC, vf06, vf16 + mfp.x vf13, P | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi03) | maddz.xyz vf16, vf07, vf16 + sq.xyzw vf21, 0(vi06) | subw.z vf20, vf20, vf00 + sq.xyzw vf01, 1(vi06) | addw.z vf22, vf00, vf00 + sq.xyzw vf19, 2(vi03) | mulx.xy vf23, vf23, vf13 + div Q, vf00.w, vf18.w | mul.xy vf22, vf22, Q + nop | mula.xy ACC, vf10, vf17 + sq.xyzw vf19, 2(vi06) | nop + mtir vi01, vf11.z | addw.xy vf22, vf22, vf03 + nop | nop + nop | nop + mfp.x vf30, P | add.xy vf23, vf23, vf14 + nop | mulaw.zw ACC, vf10, vf00 + nop | mulz.xyz vf14, vf16, vf20 + lqi.xyzw vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf22, vi11 | mul.xyz vf28, vf26, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf22, vf22, Q + lq.xyz vf30, 770(vi01) | maddz.xyzw vf18, vf09, vf17 + lqi.xyzw vf17, vi09 | mul.xyz vf15, vf14, vf14 + nop | mulax.xyzw ACC, vf02, vf12 + iadd vi04, vi04, vi12 | madday.xyzw ACC, vf03, vf12 + iadd vi06, vi04, vi13 | maddz.xyz vf21, vf04, vf12 + sq.xyzw vf30, 1(vi04) | addy.x vf15, vf15, vf15 + nop | mul.xyz vf13, vf16, vf20 + nop | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi04) | nop + sq.xyzw vf22, 0(vi06) | addz.x vf15, vf15, vf15 + sq.xyzw vf01, 1(vi06) | addw.z vf23, vf00, vf00 + sq.xyzw vf19, 2(vi04) | addy.x vf13, vf13, vf13 + div Q, vf00.w, vf18.w | mul.xy vf23, vf23, Q + nop | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf19, 2(vi06) | maddax.xyzw ACC, vf05, vf17 + nop | nop + mtir vi01, vf11.w | addw.xy vf23, vf23, vf03 + nop | addz.x vf13, vf13, vf13 + nop | madday.xyzw ACC, vf06, vf17 + nop | maddz.xyz vf17, vf07, vf17 + nop | mul.xyz vf19, vf18, Q + sqi.xyzw vf23, vi11 | mul.xyz vf28, vf27, Q + nop | mul.xyz vf23, vf23, Q + lq.xyz vf30, 770(vi01) | nop + ilw.x vi14, 914(vi00) | nop + rsqrt Q, vf02.w, vf15.x | mulx.xy vf20, vf20, vf13 + iadd vi05, vi05, vi12 | nop + iadd vi06, vi05, vi13 | mula.xy ACC, vf10, vf16 + sq.xyzw vf30, 1(vi05) | mulaw.zw ACC, vf10, vf00 + ibne vi00, vi14, L19 | add.xy vf20, vf20, vf14 + lqi.xyw vf25, vi09 | ftoi4.xyz vf19, vf19 + ilw.y vi14, 913(vi00) | nop + lqi.xyzw vf12, vi08 | subw.z vf21, vf21, vf00 + sq.xyzw vf28, 0(vi05) | nop + sq.xyzw vf23, 0(vi06) | maddz.xyzw vf18, vf09, vf16 + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf19, 2(vi05) | mulax.xyzw ACC, vf02, vf12 + iadd vi14, vi14, vi12 | madday.xyzw ACC, vf03, vf12 + b L24 | mulz.xyz vf14, vf17, vf21 + sq.xyzw vf19, 2(vi06) | maddz.xyz vf22, vf04, vf12 +L19: + ilw.y vi14, 914(vi00) | nop + sq.xyzw vf28, 0(vi05) | nop + sq.xyzw vf23, 0(vi06) | nop + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf19, 2(vi05) | nop + ibne vi00, vi14, L39 | nop + sq.xyzw vf19, 2(vi06) | nop + b L32 | nop + nop | nop +L20: + mtir vi01, vf11.z | addw.xy vf22, vf22, vf03 + mfp.x vf30, P | add.xy vf23, vf23, vf14 + mtir vi02, vf16.w | mulaw.zw ACC, vf10, vf00 + esum.xyzw P, vf13 | mulz.xyz vf14, vf16, vf20 + lqi.xyzw vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf22, vi11 | mul.xyz vf28, vf26, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf22, vf22, Q + lq.xyz vf30, 770(vi01) | maddz.xyzw vf18, vf09, vf17 + lqi.xyzw vf17, vi09 | mulax.xyzw ACC, vf02, vf12 + lqi.xyw vf25, vi09 | madday.xyzw ACC, vf03, vf12 + iadd vi04, vi04, vi12 | maddz.xyz vf21, vf04, vf12 + iadd vi06, vi04, vi13 | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf30, 1(vi04) | maddax.xyzw ACC, vf05, vf17 + esadd.xyz P, vf14 | madday.xyzw ACC, vf06, vf17 + mfp.x vf13, P | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi04) | maddz.xyz vf17, vf07, vf17 + sq.xyzw vf22, 0(vi06) | subw.z vf21, vf21, vf00 + sq.xyzw vf01, 1(vi06) | addw.z vf23, vf00, vf00 + sq.xyzw vf19, 2(vi04) | mulx.xy vf20, vf20, vf13 + div Q, vf00.w, vf18.w | mul.xy vf23, vf23, Q + nop | mula.xy ACC, vf10, vf16 + sq.xyzw vf19, 2(vi06) | nop + mtir vi01, vf11.w | addw.xy vf23, vf23, vf03 + mfp.x vf30, P | add.xy vf20, vf20, vf14 + nop | mulaw.zw ACC, vf10, vf00 + nop | mulz.xyz vf14, vf17, vf21 + lqi.xyzw vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf23, vi11 | mul.xyz vf28, vf27, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf23, vf23, Q + lq.xyz vf30, 770(vi01) | maddz.xyzw vf18, vf09, vf16 + lqi.xyzw vf16, vi09 | mul.xyz vf15, vf14, vf14 + nop | mulax.xyzw ACC, vf02, vf12 + iadd vi05, vi05, vi12 | madday.xyzw ACC, vf03, vf12 + iadd vi06, vi05, vi13 | maddz.xyz vf22, vf04, vf12 + sq.xyzw vf30, 1(vi05) | addy.x vf15, vf15, vf15 + nop | mul.xyz vf13, vf17, vf21 + lqi.xyzw vf11, vi10 | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi05) | nop + sq.xyzw vf23, 0(vi06) | addz.x vf15, vf15, vf15 + sq.xyzw vf01, 1(vi06) | addw.z vf20, vf00, vf00 + sq.xyzw vf19, 2(vi05) | addy.x vf13, vf13, vf13 + div Q, vf00.w, vf18.w | mul.xy vf20, vf20, Q + nop | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf19, 2(vi06) | maddax.xyzw ACC, vf05, vf16 + mtir vi01, vf11.x | addw.xy vf20, vf20, vf03 + nop | addz.x vf13, vf13, vf13 + nop | madday.xyzw ACC, vf06, vf16 + nop | maddz.xyz vf16, vf07, vf16 + nop | mul.xyz vf19, vf18, Q + sqi.xyzw vf20, vi11 | mul.xyz vf28, vf24, Q + nop | mul.xyz vf20, vf20, Q + lq.xyz vf30, 770(vi01) | nop + ilw.x vi14, 914(vi00) | nop + rsqrt Q, vf02.w, vf15.x | mulx.xy vf21, vf21, vf13 + iadd vi02, vi02, vi12 | nop + iadd vi06, vi02, vi13 | mula.xy ACC, vf10, vf17 + sq.xyzw vf30, 1(vi02) | mulaw.zw ACC, vf10, vf00 + ibne vi00, vi14, L21 | add.xy vf21, vf21, vf14 + lqi.xyw vf26, vi09 | ftoi4.xyz vf19, vf19 + ilw.y vi14, 913(vi00) | subw.z vf22, vf22, vf00 + sq.xyzw vf28, 0(vi02) | nop + sq.xyzw vf20, 0(vi06) | maddz.xyzw vf18, vf09, vf17 + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf19, 2(vi02) | mulax.xyzw ACC, vf02, vf12 + iadd vi14, vi14, vi12 | madday.xyzw ACC, vf03, vf12 + b L25 | mulz.xyz vf14, vf16, vf22 + sq.xyzw vf19, 2(vi06) | maddz.xyz vf23, vf04, vf12 +L21: + ilw.y vi14, 914(vi00) | nop + sq.xyzw vf28, 0(vi02) | nop + sq.xyzw vf20, 0(vi06) | nop + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf19, 2(vi02) | nop + ibne vi00, vi14, L39 | nop + sq.xyzw vf19, 2(vi06) | nop + b L32 | nop + nop | nop +L22: + mtir vi01, vf11.w | addw.xy vf23, vf23, vf03 + mfp.x vf30, P | add.xy vf20, vf20, vf14 + mtir vi03, vf17.w | mulaw.zw ACC, vf10, vf00 + esum.xyzw P, vf13 | mulz.xyz vf14, vf17, vf21 + lqi.xyzw vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf23, vi11 | mul.xyz vf28, vf27, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf23, vf23, Q + lq.xyz vf30, 770(vi01) | maddz.xyzw vf18, vf09, vf16 + lqi.xyzw vf16, vi09 | mulax.xyzw ACC, vf02, vf12 + lqi.xyw vf26, vi09 | madday.xyzw ACC, vf03, vf12 + iadd vi05, vi05, vi12 | maddz.xyz vf22, vf04, vf12 + iadd vi06, vi05, vi13 | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf30, 1(vi05) | maddax.xyzw ACC, vf05, vf16 + esadd.xyz P, vf14 | madday.xyzw ACC, vf06, vf16 + mfp.x vf13, P | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi05) | maddz.xyz vf16, vf07, vf16 + sq.xyzw vf23, 0(vi06) | subw.z vf22, vf22, vf00 + sq.xyzw vf01, 1(vi06) | addw.z vf20, vf00, vf00 + sq.xyzw vf19, 2(vi05) | mulx.xy vf21, vf21, vf13 + div Q, vf00.w, vf18.w | mul.xy vf20, vf20, Q + lqi.xyzw vf11, vi10 | mula.xy ACC, vf10, vf17 + sq.xyzw vf19, 2(vi06) | nop + nop | addw.xy vf20, vf20, vf03 + mfp.x vf30, P | add.xy vf21, vf21, vf14 + nop | mulaw.zw ACC, vf10, vf00 + mtir vi01, vf11.x | mulz.xyz vf14, vf16, vf22 + lqi.xyzw vf12, vi08 | mul.xyz vf19, vf18, Q + sqi.xyzw vf20, vi11 | mul.xyz vf28, vf24, Q + rsqrt Q, vf02.w, vf30.x | mul.xyz vf20, vf20, Q + lq.xyz vf30, 770(vi01) | maddz.xyzw vf18, vf09, vf17 + lqi.xyzw vf17, vi09 | mul.xyz vf15, vf14, vf14 + nop | mulax.xyzw ACC, vf02, vf12 + iadd vi02, vi02, vi12 | madday.xyzw ACC, vf03, vf12 + iadd vi06, vi02, vi13 | maddz.xyz vf23, vf04, vf12 + sq.xyzw vf30, 1(vi02) | addy.x vf15, vf15, vf15 + nop | mul.xyz vf13, vf16, vf22 + nop | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | nop + sq.xyzw vf20, 0(vi06) | addz.x vf15, vf15, vf15 + sq.xyzw vf01, 1(vi06) | addw.z vf21, vf00, vf00 + sq.xyzw vf19, 2(vi02) | addy.x vf13, vf13, vf13 + div Q, vf00.w, vf18.w | mul.xy vf21, vf21, Q + nop | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf19, 2(vi06) | maddax.xyzw ACC, vf05, vf17 + mtir vi01, vf11.y | addw.xy vf21, vf21, vf03 + nop | addz.x vf13, vf13, vf13 + nop | madday.xyzw ACC, vf06, vf17 + nop | maddz.xyz vf17, vf07, vf17 + nop | mul.xyz vf19, vf18, Q + sqi.xyzw vf21, vi11 | mul.xyz vf28, vf25, Q + nop | mul.xyz vf21, vf21, Q + lq.xyz vf30, 770(vi01) | nop + ilw.x vi14, 914(vi00) | nop + rsqrt Q, vf02.w, vf15.x | mulx.xy vf22, vf22, vf13 + iadd vi03, vi03, vi12 | nop + iadd vi06, vi03, vi13 | mula.xy ACC, vf10, vf16 + sq.xyzw vf30, 1(vi03) | mulaw.zw ACC, vf10, vf00 + ibne vi00, vi14, L23 | add.xy vf22, vf22, vf14 + lqi.xyw vf27, vi09 | ftoi4.xyz vf19, vf19 + ilw.y vi14, 913(vi00) | nop + lqi.xyzw vf12, vi08 | subw.z vf23, vf23, vf00 + sq.xyzw vf28, 0(vi03) | nop + sq.xyzw vf21, 0(vi06) | maddz.xyzw vf18, vf09, vf16 + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf19, 2(vi03) | mulax.xyzw ACC, vf02, vf12 + iadd vi14, vi14, vi12 | madday.xyzw ACC, vf03, vf12 + b L26 | mulz.xyz vf14, vf17, vf23 + sq.xyzw vf19, 2(vi06) | maddz.xyz vf20, vf04, vf12 +L23: + ilw.y vi14, 914(vi00) | nop + sq.xyzw vf28, 0(vi03) | nop + sq.xyzw vf21, 0(vi06) | nop + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf19, 2(vi03) | nop + ibne vi00, vi14, L39 | nop + sq.xyzw vf19, 2(vi06) | nop + b L32 | nop + nop | nop +L24: + lqi.xyzw vf11, vi10 | nop + div Q, vf00.w, vf18.w | mul.xy vf20, vf20, Q + mtir vi02, vf16.w | addw.z vf20, vf00, vf00 + mtir vi07, vf24.w | mul.xyz vf15, vf14, vf14 + lqi.xyzw vf16, vi09 | mul.xyz vf13, vf17, vf21 + mtir vi01, vf11.x | addw.xy vf20, vf20, vf03 + iadd vi02, vi02, vi12 | mulaw.xyzw ACC, vf08, vf00 + iadd vi06, vi02, vi13 | addy.x vf15, vf15, vf15 + iadd vi07, vi07, vi12 | maddax.xyzw ACC, vf05, vf16 + sqi.xyzw vf20, vi11 | addy.x vf13, vf13, vf13 + lq.xyz vf30, 770(vi01) | madday.xyzw ACC, vf06, vf16 + iadd vi15, vi07, vi13 | addz.x vf15, vf15, vf15 + sq.xyzw vf01, 1(vi06) | maddz.xyz vf16, vf07, vf16 + sq.xyzw vf01, 1(vi15) | addz.x vf13, vf13, vf13 + sq.xyzw vf30, 1(vi02) | mul.xyz vf19, vf18, Q + rsqrt Q, vf02.w, vf15.x | mul.xyz vf28, vf24, Q + sq.xyzw vf30, 1(vi07) | mul.xyz vf20, vf20, Q + lqi.xyw vf26, vi09 | mulx.xy vf21, vf21, vf13 + lqi.xyzw vf12, vi08 | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | mula.xy ACC, vf10, vf17 + sq.xyzw vf28, 0(vi07) | mulaw.zw ACC, vf10, vf00 + sq.xyzw vf20, 0(vi06) | add.xy vf21, vf21, vf14 + sq.xyzw vf20, 0(vi15) | subw.z vf22, vf22, vf00 + sq.xyzw vf19, 2(vi02) | maddz.xyzw vf18, vf09, vf17 + sq.xyzw vf19, 2(vi06) | mulax.xyzw ACC, vf02, vf12 + sq.xyzw vf19, 2(vi07) | madday.xyzw ACC, vf03, vf12 + ibeq vi14, vi02, L28 | mulz.xyz vf14, vf16, vf22 + sq.xyzw vf19, 2(vi15) | maddz.xyz vf23, vf04, vf12 +L25: + div Q, vf00.w, vf18.w | mul.xy vf21, vf21, Q + mtir vi02, vf17.w | addw.z vf21, vf00, vf00 + mtir vi07, vf25.w | mul.xyz vf15, vf14, vf14 + lqi.xyzw vf17, vi09 | mul.xyz vf13, vf16, vf22 + mtir vi01, vf11.y | addw.xy vf21, vf21, vf03 + iadd vi02, vi02, vi12 | mulaw.xyzw ACC, vf08, vf00 + iadd vi06, vi02, vi13 | addy.x vf15, vf15, vf15 + iadd vi07, vi07, vi12 | maddax.xyzw ACC, vf05, vf17 + sqi.xyzw vf21, vi11 | addy.x vf13, vf13, vf13 + lq.xyz vf30, 770(vi01) | madday.xyzw ACC, vf06, vf17 + iadd vi15, vi07, vi13 | addz.x vf15, vf15, vf15 + sq.xyzw vf01, 1(vi06) | maddz.xyz vf17, vf07, vf17 + sq.xyzw vf01, 1(vi15) | addz.x vf13, vf13, vf13 + sq.xyzw vf30, 1(vi02) | mul.xyz vf19, vf18, Q + rsqrt Q, vf02.w, vf15.x | mul.xyz vf28, vf25, Q + sq.xyzw vf30, 1(vi07) | mul.xyz vf21, vf21, Q + lqi.xyw vf27, vi09 | mulx.xy vf22, vf22, vf13 + lqi.xyzw vf12, vi08 | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | mula.xy ACC, vf10, vf16 + sq.xyzw vf28, 0(vi07) | mulaw.zw ACC, vf10, vf00 + sq.xyzw vf21, 0(vi06) | add.xy vf22, vf22, vf14 + sq.xyzw vf21, 0(vi15) | subw.z vf23, vf23, vf00 + sq.xyzw vf19, 2(vi02) | maddz.xyzw vf18, vf09, vf16 + sq.xyzw vf19, 2(vi06) | mulax.xyzw ACC, vf02, vf12 + sq.xyzw vf19, 2(vi07) | madday.xyzw ACC, vf03, vf12 + ibeq vi14, vi02, L29 | mulz.xyz vf14, vf17, vf23 + sq.xyzw vf19, 2(vi15) | maddz.xyz vf20, vf04, vf12 +L26: + div Q, vf00.w, vf18.w | mul.xy vf22, vf22, Q + mtir vi02, vf16.w | addw.z vf22, vf00, vf00 + mtir vi07, vf26.w | mul.xyz vf15, vf14, vf14 + lqi.xyzw vf16, vi09 | mul.xyz vf13, vf17, vf23 + mtir vi01, vf11.z | addw.xy vf22, vf22, vf03 + iadd vi02, vi02, vi12 | mulaw.xyzw ACC, vf08, vf00 + iadd vi06, vi02, vi13 | addy.x vf15, vf15, vf15 + iadd vi07, vi07, vi12 | maddax.xyzw ACC, vf05, vf16 + sqi.xyzw vf22, vi11 | addy.x vf13, vf13, vf13 + lq.xyz vf30, 770(vi01) | madday.xyzw ACC, vf06, vf16 + iadd vi15, vi07, vi13 | addz.x vf15, vf15, vf15 + sq.xyzw vf01, 1(vi06) | maddz.xyz vf16, vf07, vf16 + sq.xyzw vf01, 1(vi15) | addz.x vf13, vf13, vf13 + sq.xyzw vf30, 1(vi02) | mul.xyz vf19, vf18, Q + rsqrt Q, vf02.w, vf15.x | mul.xyz vf28, vf26, Q + sq.xyzw vf30, 1(vi07) | mul.xyz vf22, vf22, Q + lqi.xyw vf24, vi09 | mulx.xy vf23, vf23, vf13 + lqi.xyzw vf12, vi08 | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | mula.xy ACC, vf10, vf17 + sq.xyzw vf28, 0(vi07) | mulaw.zw ACC, vf10, vf00 + sq.xyzw vf22, 0(vi06) | add.xy vf23, vf23, vf14 + sq.xyzw vf22, 0(vi15) | subw.z vf20, vf20, vf00 + sq.xyzw vf19, 2(vi02) | maddz.xyzw vf18, vf09, vf17 + sq.xyzw vf19, 2(vi06) | mulax.xyzw ACC, vf02, vf12 + sq.xyzw vf19, 2(vi07) | madday.xyzw ACC, vf03, vf12 + ibeq vi14, vi02, L30 | mulz.xyz vf14, vf16, vf20 + sq.xyzw vf19, 2(vi15) | maddz.xyz vf21, vf04, vf12 +L27: + div Q, vf00.w, vf18.w | mul.xy vf23, vf23, Q + mtir vi02, vf17.w | addw.z vf23, vf00, vf00 + mtir vi07, vf27.w | mul.xyz vf15, vf14, vf14 + lqi.xyzw vf17, vi09 | mul.xyz vf13, vf16, vf20 + mtir vi01, vf11.w | addw.xy vf23, vf23, vf03 + iadd vi02, vi02, vi12 | mulaw.xyzw ACC, vf08, vf00 + iadd vi06, vi02, vi13 | addy.x vf15, vf15, vf15 + iadd vi07, vi07, vi12 | maddax.xyzw ACC, vf05, vf17 + sqi.xyzw vf23, vi11 | addy.x vf13, vf13, vf13 + lq.xyz vf30, 770(vi01) | madday.xyzw ACC, vf06, vf17 + iadd vi15, vi07, vi13 | addz.x vf15, vf15, vf15 + sq.xyzw vf01, 1(vi06) | maddz.xyz vf17, vf07, vf17 + sq.xyzw vf01, 1(vi15) | addz.x vf13, vf13, vf13 + sq.xyzw vf30, 1(vi02) | mul.xyz vf19, vf18, Q + rsqrt Q, vf02.w, vf15.x | mul.xyz vf28, vf27, Q + sq.xyzw vf30, 1(vi07) | mul.xyz vf23, vf23, Q + lqi.xyw vf25, vi09 | mulx.xy vf20, vf20, vf13 + lqi.xyzw vf12, vi08 | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | mula.xy ACC, vf10, vf16 + sq.xyzw vf28, 0(vi07) | mulaw.zw ACC, vf10, vf00 + sq.xyzw vf23, 0(vi06) | add.xy vf20, vf20, vf14 + sq.xyzw vf23, 0(vi15) | subw.z vf21, vf21, vf00 + sq.xyzw vf19, 2(vi02) | maddz.xyzw vf18, vf09, vf16 + sq.xyzw vf19, 2(vi06) | mulax.xyzw ACC, vf02, vf12 + sq.xyzw vf19, 2(vi07) | madday.xyzw ACC, vf03, vf12 + ibne vi14, vi02, L24 | mulz.xyz vf14, vf17, vf21 + sq.xyzw vf19, 2(vi15) | maddz.xyz vf22, vf04, vf12 + lqi.xyzw vf11, vi10 | nop + div Q, vf00.w, vf18.w | mul.xy vf20, vf20, Q + mtir vi02, vf16.w | addw.z vf20, vf00, vf00 + mtir vi07, vf24.w | mul.xyz vf15, vf14, vf14 + nop | mul.xyz vf13, vf17, vf21 + mtir vi01, vf11.x | addw.xy vf20, vf20, vf03 + iadd vi02, vi02, vi12 | nop + iadd vi06, vi02, vi13 | addy.x vf15, vf15, vf15 + iadd vi07, vi07, vi12 | nop + sqi.xyzw vf20, vi11 | addy.x vf13, vf13, vf13 + lq.xyz vf30, 770(vi01) | nop + iadd vi15, vi07, vi13 | addz.x vf15, vf15, vf15 + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf01, 1(vi15) | addz.x vf13, vf13, vf13 + sq.xyzw vf30, 1(vi02) | mul.xyz vf19, vf18, Q + rsqrt Q, vf02.w, vf15.x | mul.xyz vf28, vf24, Q + sq.xyzw vf30, 1(vi07) | mul.xyz vf20, vf20, Q + nop | mulx.xy vf21, vf21, vf13 + nop | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | mula.xy ACC, vf10, vf17 + sq.xyzw vf28, 0(vi07) | mulaw.zw ACC, vf10, vf00 + sq.xyzw vf20, 0(vi06) | add.xy vf21, vf21, vf14 + sq.xyzw vf20, 0(vi15) | nop + sq.xyzw vf19, 2(vi02) | nop + sq.xyzw vf19, 2(vi06) | maddz.xyzw vf18, vf09, vf17 + sq.xyzw vf19, 2(vi07) | nop + nop | nop + sq.xyzw vf19, 2(vi15) | nop + div Q, vf00.w, vf18.w | mul.xy vf21, vf21, Q + mtir vi02, vf17.w | addw.z vf21, vf00, vf00 + mtir vi07, vf25.w | nop + nop | nop + mtir vi01, vf11.y | addw.xy vf21, vf21, vf03 + iadd vi02, vi02, vi12 | nop + iadd vi06, vi02, vi13 | nop + iadd vi07, vi07, vi12 | nop + sqi.xyzw vf21, vi11 | nop + lq.xyz vf30, 770(vi01) | nop + iadd vi15, vi07, vi13 | nop + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf01, 1(vi15) | nop + sq.xyzw vf30, 1(vi02) | mul.xyz vf19, vf18, Q + nop | mul.xyz vf28, vf25, Q + sq.xyzw vf30, 1(vi07) | mul.xyz vf21, vf21, Q + nop | nop + ilw.y vi14, 914(vi00) | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | nop + sq.xyzw vf28, 0(vi07) | nop + sq.xyzw vf21, 0(vi06) | nop + sq.xyzw vf21, 0(vi15) | nop + sq.xyzw vf19, 2(vi02) | nop + sq.xyzw vf19, 2(vi06) | nop + sq.xyzw vf19, 2(vi07) | nop + ibne vi00, vi14, L39 | nop + sq.xyzw vf19, 2(vi15) | nop + b L31 | nop + nop | nop +L28: + div Q, vf00.w, vf18.w | mul.xy vf21, vf21, Q + mtir vi02, vf17.w | addw.z vf21, vf00, vf00 + mtir vi07, vf25.w | mul.xyz vf15, vf14, vf14 + nop | mul.xyz vf13, vf16, vf22 + mtir vi01, vf11.y | addw.xy vf21, vf21, vf03 + iadd vi02, vi02, vi12 | nop + iadd vi06, vi02, vi13 | addy.x vf15, vf15, vf15 + iadd vi07, vi07, vi12 | nop + sqi.xyzw vf21, vi11 | addy.x vf13, vf13, vf13 + lq.xyz vf30, 770(vi01) | nop + iadd vi15, vi07, vi13 | addz.x vf15, vf15, vf15 + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf01, 1(vi15) | addz.x vf13, vf13, vf13 + sq.xyzw vf30, 1(vi02) | mul.xyz vf19, vf18, Q + rsqrt Q, vf02.w, vf15.x | mul.xyz vf28, vf25, Q + sq.xyzw vf30, 1(vi07) | mul.xyz vf21, vf21, Q + nop | mulx.xy vf22, vf22, vf13 + nop | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | mula.xy ACC, vf10, vf16 + sq.xyzw vf28, 0(vi07) | mulaw.zw ACC, vf10, vf00 + sq.xyzw vf21, 0(vi06) | add.xy vf22, vf22, vf14 + sq.xyzw vf21, 0(vi15) | nop + sq.xyzw vf19, 2(vi02) | nop + sq.xyzw vf19, 2(vi06) | maddz.xyzw vf18, vf09, vf16 + sq.xyzw vf19, 2(vi07) | nop + nop | nop + sq.xyzw vf19, 2(vi15) | nop + div Q, vf00.w, vf18.w | mul.xy vf22, vf22, Q + mtir vi02, vf16.w | addw.z vf22, vf00, vf00 + mtir vi07, vf26.w | nop + nop | nop + mtir vi01, vf11.z | addw.xy vf22, vf22, vf03 + iadd vi02, vi02, vi12 | nop + iadd vi06, vi02, vi13 | nop + iadd vi07, vi07, vi12 | nop + sqi.xyzw vf22, vi11 | nop + lq.xyz vf30, 770(vi01) | nop + iadd vi15, vi07, vi13 | nop + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf01, 1(vi15) | nop + sq.xyzw vf30, 1(vi02) | mul.xyz vf19, vf18, Q + nop | mul.xyz vf28, vf26, Q + sq.xyzw vf30, 1(vi07) | mul.xyz vf22, vf22, Q + nop | nop + ilw.y vi14, 914(vi00) | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | nop + sq.xyzw vf28, 0(vi07) | nop + sq.xyzw vf22, 0(vi06) | nop + sq.xyzw vf22, 0(vi15) | nop + sq.xyzw vf19, 2(vi02) | nop + sq.xyzw vf19, 2(vi06) | nop + sq.xyzw vf19, 2(vi07) | nop + ibne vi00, vi14, L39 | nop + sq.xyzw vf19, 2(vi15) | nop + b L31 | nop + nop | nop +L29: + div Q, vf00.w, vf18.w | mul.xy vf22, vf22, Q + mtir vi02, vf16.w | addw.z vf22, vf00, vf00 + mtir vi07, vf26.w | mul.xyz vf15, vf14, vf14 + nop | mul.xyz vf13, vf17, vf23 + mtir vi01, vf11.z | addw.xy vf22, vf22, vf03 + iadd vi02, vi02, vi12 | nop + iadd vi06, vi02, vi13 | addy.x vf15, vf15, vf15 + iadd vi07, vi07, vi12 | nop + sqi.xyzw vf22, vi11 | addy.x vf13, vf13, vf13 + lq.xyz vf30, 770(vi01) | nop + iadd vi15, vi07, vi13 | addz.x vf15, vf15, vf15 + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf01, 1(vi15) | addz.x vf13, vf13, vf13 + sq.xyzw vf30, 1(vi02) | mul.xyz vf19, vf18, Q + rsqrt Q, vf02.w, vf15.x | mul.xyz vf28, vf26, Q + sq.xyzw vf30, 1(vi07) | mul.xyz vf22, vf22, Q + nop | mulx.xy vf23, vf23, vf13 + nop | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | mula.xy ACC, vf10, vf17 + sq.xyzw vf28, 0(vi07) | mulaw.zw ACC, vf10, vf00 + sq.xyzw vf22, 0(vi06) | add.xy vf23, vf23, vf14 + sq.xyzw vf22, 0(vi15) | nop + sq.xyzw vf19, 2(vi02) | nop + sq.xyzw vf19, 2(vi06) | maddz.xyzw vf18, vf09, vf17 + sq.xyzw vf19, 2(vi07) | nop + nop | nop + sq.xyzw vf19, 2(vi15) | nop + div Q, vf00.w, vf18.w | mul.xy vf23, vf23, Q + mtir vi02, vf17.w | addw.z vf23, vf00, vf00 + mtir vi07, vf27.w | nop + nop | nop + mtir vi01, vf11.w | addw.xy vf23, vf23, vf03 + iadd vi02, vi02, vi12 | nop + iadd vi06, vi02, vi13 | nop + iadd vi07, vi07, vi12 | nop + sqi.xyzw vf23, vi11 | nop + lq.xyz vf30, 770(vi01) | nop + iadd vi15, vi07, vi13 | nop + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf01, 1(vi15) | nop + sq.xyzw vf30, 1(vi02) | mul.xyz vf19, vf18, Q + nop | mul.xyz vf28, vf27, Q + sq.xyzw vf30, 1(vi07) | mul.xyz vf23, vf23, Q + nop | nop + ilw.y vi14, 914(vi00) | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | nop + sq.xyzw vf28, 0(vi07) | nop + sq.xyzw vf23, 0(vi06) | nop + sq.xyzw vf23, 0(vi15) | nop + sq.xyzw vf19, 2(vi02) | nop + sq.xyzw vf19, 2(vi06) | nop + sq.xyzw vf19, 2(vi07) | nop + ibne vi00, vi14, L39 | nop + sq.xyzw vf19, 2(vi15) | nop + b L31 | nop + nop | nop +L30: + nop | nop + div Q, vf00.w, vf18.w | mul.xy vf23, vf23, Q + mtir vi02, vf17.w | addw.z vf23, vf00, vf00 + mtir vi07, vf27.w | mul.xyz vf15, vf14, vf14 + nop | mul.xyz vf13, vf16, vf20 + mtir vi01, vf11.w | addw.xy vf23, vf23, vf03 + iadd vi02, vi02, vi12 | nop + iadd vi06, vi02, vi13 | addy.x vf15, vf15, vf15 + iadd vi07, vi07, vi12 | nop + sqi.xyzw vf23, vi11 | addy.x vf13, vf13, vf13 + lq.xyz vf30, 770(vi01) | nop + iadd vi15, vi07, vi13 | addz.x vf15, vf15, vf15 + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf01, 1(vi15) | addz.x vf13, vf13, vf13 + sq.xyzw vf30, 1(vi02) | mul.xyz vf19, vf18, Q + rsqrt Q, vf02.w, vf15.x | mul.xyz vf28, vf27, Q + sq.xyzw vf30, 1(vi07) | mul.xyz vf23, vf23, Q + nop | mulx.xy vf20, vf20, vf13 + nop | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | mula.xy ACC, vf10, vf16 + sq.xyzw vf28, 0(vi07) | mulaw.zw ACC, vf10, vf00 + sq.xyzw vf23, 0(vi06) | add.xy vf20, vf20, vf14 + sq.xyzw vf23, 0(vi15) | nop + sq.xyzw vf19, 2(vi02) | nop + sq.xyzw vf19, 2(vi06) | maddz.xyzw vf18, vf09, vf16 + sq.xyzw vf19, 2(vi07) | nop + lqi.xyzw vf11, vi10 | nop + sq.xyzw vf19, 2(vi15) | nop + div Q, vf00.w, vf18.w | mul.xy vf20, vf20, Q + mtir vi02, vf16.w | addw.z vf20, vf00, vf00 + mtir vi07, vf24.w | nop + nop | nop + mtir vi01, vf11.x | addw.xy vf20, vf20, vf03 + iadd vi02, vi02, vi12 | nop + iadd vi06, vi02, vi13 | nop + iadd vi07, vi07, vi12 | nop + sqi.xyzw vf20, vi11 | nop + lq.xyz vf30, 770(vi01) | nop + iadd vi15, vi07, vi13 | nop + sq.xyzw vf01, 1(vi06) | nop + sq.xyzw vf01, 1(vi15) | nop + sq.xyzw vf30, 1(vi02) | mul.xyz vf19, vf18, Q + nop | mul.xyz vf28, vf24, Q + sq.xyzw vf30, 1(vi07) | mul.xyz vf20, vf20, Q + nop | nop + ilw.y vi14, 914(vi00) | ftoi4.xyz vf19, vf19 + sq.xyzw vf28, 0(vi02) | nop + sq.xyzw vf28, 0(vi07) | nop + sq.xyzw vf20, 0(vi06) | nop + sq.xyzw vf20, 0(vi15) | nop + sq.xyzw vf19, 2(vi02) | nop + sq.xyzw vf19, 2(vi06) | nop + sq.xyzw vf19, 2(vi07) | nop + ibne vi00, vi14, L39 | nop + sq.xyzw vf19, 2(vi15) | nop + b L31 | nop + nop | nop +L31: + ilw.z vi14, 913(vi00) | nop + iaddi vi08, vi08, -0x1 | nop + b L33 | nop + nop | nop +L32: + ilw.z vi14, 913(vi00) | nop + iaddi vi08, vi08, -0x2 | nop + iaddi vi09, vi09, -0x4 | nop + nop | nop +L33: + ibeq vi00, vi14, L36 | nop + nop | nop + lq.w vf14, 898(vi00) | subw.w vf13, vf13, vf13 + iadd vi14, vi14, vi12 | addw.z vf28, vf00, vf00 + lqi.xyz vf29, vi08 | nop + lqi.xyzw vf20, vi09 | nop + lqi.xyz vf14, vi09 | nop + lqi.xyzw vf24, vi09 | nop + nop | nop + nop | mulw.xyz vf20, vf20, vf29 + nop | mulax.xyz ACC, vf02, vf29 + nop | madday.xyz ACC, vf03, vf29 + nop | maddz.xyz vf17, vf04, vf29 + nop | add.xyzw vf20, vf20, vf14 + nop | nop + nop | nop + nop | nop + nop | mulaw.xyzw ACC, vf08, vf00 + nop | maddax.xyzw ACC, vf05, vf20 + nop | madday.xyzw ACC, vf06, vf20 + nop | maddz.xyz vf20, vf07, vf20 + nop | subw.z vf17, vf17, vf00 + nop | nop + nop | nop + nop | nop + nop | nop + nop | nop + nop | mul.xyz vf13, vf17, vf20 + lqi.xyz vf29, vi08 | nop + nop | nop + nop | nop + esum.xyzw P, vf13 | nop + lqi.xyzw vf21, vi09 | nop + lqi.xyz vf14, vi09 | nop + lqi.xyzw vf25, vi09 | nop + nop | nop + nop | nop + nop | mulw.xyz vf21, vf21, vf29 + nop | nop + nop | mulax.xyz ACC, vf02, vf29 + nop | mulz.xyz vf11, vf20, vf17 + nop | add.xyzw vf21, vf21, vf14 + nop | madday.xyz ACC, vf03, vf29 + nop | maddz.xyz vf18, vf04, vf29 + nop | nop + esadd.xyz P, vf11 | nop + mfp.w vf12, P | nop + nop | mulaw.xyzw ACC, vf08, vf00 + nop | maddax.xyzw ACC, vf05, vf21 + nop | madday.xyzw ACC, vf06, vf21 + nop | maddz.xyz vf21, vf07, vf21 + nop | subw.z vf18, vf18, vf00 + nop | nop + nop | nop + nop | nop + nop | nop + nop | nop + mfp.w vf04, P | mul.xyz vf13, vf18, vf21 + lqi.xyz vf29, vi08 | nop + nop | nop + lqi.xyz vf11, vi10 | nop + esum.xyzw P, vf13 | nop + lqi.xyzw vf22, vi09 | mulaz.xy ACC, vf20, vf17 + lqi.xyz vf14, vi09 | nop + lqi.xyzw vf26, vi09 | nop + mtir vi01, vf11.x | nop + mtir vi02, vf11.y | mulw.xyz vf22, vf22, vf29 + mtir vi03, vf11.z | maddw.xy vf28, vf17, vf12 + rsqrt Q, vf02.w, vf04.w | nop + nop | mulax.xyz ACC, vf02, vf29 + nop | add.xyzw vf22, vf22, vf14 + nop | mulz.xyz vf11, vf21, vf18 + nop | madday.xyz ACC, vf03, vf29 + nop | maddz.xyz vf17, vf04, vf29 +L34: + mtir vi04, vf20.w | mulaw.zw ACC, vf10, vf00 + esadd.xyz P, vf11 | mula.xy ACC, vf10, vf20 + mfp.w vf12, P | maddz.xyzw vf20, vf09, vf20 + lq.xyz vf30, 770(vi01) | mulaw.xyzw ACC, vf08, vf00 + lq.xyz vf15, 770(vi02) | maddax.xyzw ACC, vf05, vf22 + lq.xyz vf16, 770(vi03) | madday.xyzw ACC, vf06, vf22 + mtir vi01, vf24.z | maddz.xyz vf22, vf07, vf22 + mtir vi02, vf24.w | subw.z vf17, vf17, vf00 + iadd vi05, vi04, vi13 | mul.xy vf28, vf28, Q + sq.xyzw vf01, 1(vi05) | mulaw.xyzw ACC, vf15, vf31 + lq.xy vf12, 919(vi01) | maddaw.xyzw ACC, vf16, vf31 + lq.xy vf14, 919(vi02) | maddz.xyz vf30, vf30, vf31 + div Q, vf00.w, vf20.w | addw.xy vf28, vf28, vf03 + mfp.w vf04, P | mul.xyz vf13, vf17, vf22 + lqi.xyz vf29, vi08 | mulay.xy ACC, vf12, vf31 + sq.xyzw vf30, 1(vi04) | madday.xy ACC, vf14, vf31 + lqi.xyz vf11, vi10 | maddx.xy vf28, vf28, vf31 + esum.xyzw P, vf13 | addw.z vf24, vf00, vf00 + lqi.xyzw vf23, vi09 | mulaz.xy ACC, vf21, vf18 + lqi.xyz vf14, vi09 | mul.xyz vf20, vf20, Q + lqi.xyzw vf27, vi09 | mul.xyz vf12, vf28, Q + mtir vi01, vf11.x | mul.xyz vf24, vf24, Q + mtir vi02, vf11.y | mulw.xyz vf23, vf23, vf29 + mtir vi03, vf11.z | ftoi4.xyz vf19, vf20 + rsqrt Q, vf02.w, vf04.w | maddw.xy vf28, vf18, vf12 + sq.xyzw vf12, 0(vi05) | mulax.xyz ACC, vf02, vf29 + sq.xyzw vf24, 0(vi04) | add.xyzw vf23, vf23, vf14 + sq.xyzw vf19, 2(vi04) | mulz.xyz vf11, vf22, vf17 + ibeq vi14, vi04, L35 | madday.xyz ACC, vf03, vf29 + sq.xyzw vf19, 2(vi05) | maddz.xyz vf18, vf04, vf29 + mtir vi04, vf21.w | mulaw.zw ACC, vf10, vf00 + esadd.xyz P, vf11 | mula.xy ACC, vf10, vf21 + mfp.w vf12, P | maddz.xyzw vf21, vf09, vf21 + lq.xyz vf30, 770(vi01) | mulaw.xyzw ACC, vf08, vf00 + lq.xyz vf15, 770(vi02) | maddax.xyzw ACC, vf05, vf23 + lq.xyz vf16, 770(vi03) | madday.xyzw ACC, vf06, vf23 + mtir vi01, vf25.z | maddz.xyz vf23, vf07, vf23 + mtir vi02, vf25.w | subw.z vf18, vf18, vf00 + iadd vi05, vi04, vi13 | mul.xy vf28, vf28, Q + sq.xyzw vf01, 1(vi05) | mulaw.xyzw ACC, vf15, vf31 + lq.xy vf12, 919(vi01) | maddaw.xyzw ACC, vf16, vf31 + lq.xy vf14, 919(vi02) | maddz.xyz vf30, vf30, vf31 + div Q, vf00.w, vf21.w | addw.xy vf28, vf28, vf03 + mfp.w vf04, P | mul.xyz vf13, vf18, vf23 + lqi.xyz vf29, vi08 | mulay.xy ACC, vf12, vf31 + sq.xyzw vf30, 1(vi04) | madday.xy ACC, vf14, vf31 + lqi.xyz vf11, vi10 | maddx.xy vf28, vf28, vf31 + esum.xyzw P, vf13 | addw.z vf25, vf00, vf00 + lqi.xyzw vf20, vi09 | mulaz.xy ACC, vf22, vf17 + lqi.xyz vf14, vi09 | mul.xyz vf21, vf21, Q + lqi.xyzw vf24, vi09 | mul.xyz vf12, vf28, Q + mtir vi01, vf11.x | mul.xyz vf25, vf25, Q + mtir vi02, vf11.y | mulw.xyz vf20, vf20, vf29 + mtir vi03, vf11.z | ftoi4.xyz vf19, vf21 + rsqrt Q, vf02.w, vf04.w | maddw.xy vf28, vf17, vf12 + sq.xyzw vf12, 0(vi05) | mulax.xyz ACC, vf02, vf29 + sq.xyzw vf25, 0(vi04) | add.xyzw vf20, vf20, vf14 + sq.xyzw vf19, 2(vi04) | mulz.xyz vf11, vf23, vf18 + ibeq vi14, vi04, L35 | madday.xyz ACC, vf03, vf29 + sq.xyzw vf19, 2(vi05) | maddz.xyz vf17, vf04, vf29 + mtir vi04, vf22.w | mulaw.zw ACC, vf10, vf00 + esadd.xyz P, vf11 | mula.xy ACC, vf10, vf22 + mfp.w vf12, P | maddz.xyzw vf22, vf09, vf22 + lq.xyz vf30, 770(vi01) | mulaw.xyzw ACC, vf08, vf00 + lq.xyz vf15, 770(vi02) | maddax.xyzw ACC, vf05, vf20 + lq.xyz vf16, 770(vi03) | madday.xyzw ACC, vf06, vf20 + mtir vi01, vf26.z | maddz.xyz vf20, vf07, vf20 + mtir vi02, vf26.w | subw.z vf17, vf17, vf00 + iadd vi05, vi04, vi13 | mul.xy vf28, vf28, Q + sq.xyzw vf01, 1(vi05) | mulaw.xyzw ACC, vf15, vf31 + lq.xy vf12, 919(vi01) | maddaw.xyzw ACC, vf16, vf31 + lq.xy vf14, 919(vi02) | maddz.xyz vf30, vf30, vf31 + div Q, vf00.w, vf22.w | addw.xy vf28, vf28, vf03 + mfp.w vf04, P | mul.xyz vf13, vf17, vf20 + lqi.xyz vf29, vi08 | mulay.xy ACC, vf12, vf31 + sq.xyzw vf30, 1(vi04) | madday.xy ACC, vf14, vf31 + lqi.xyz vf11, vi10 | maddx.xy vf28, vf28, vf31 + esum.xyzw P, vf13 | addw.z vf26, vf00, vf00 + lqi.xyzw vf21, vi09 | mulaz.xy ACC, vf23, vf18 + lqi.xyz vf14, vi09 | mul.xyz vf22, vf22, Q + lqi.xyzw vf25, vi09 | mul.xyz vf12, vf28, Q + mtir vi01, vf11.x | mul.xyz vf26, vf26, Q + mtir vi02, vf11.y | mulw.xyz vf21, vf21, vf29 + mtir vi03, vf11.z | ftoi4.xyz vf19, vf22 + rsqrt Q, vf02.w, vf04.w | maddw.xy vf28, vf18, vf12 + sq.xyzw vf12, 0(vi05) | mulax.xyz ACC, vf02, vf29 + sq.xyzw vf26, 0(vi04) | add.xyzw vf21, vf21, vf14 + sq.xyzw vf19, 2(vi04) | mulz.xyz vf11, vf20, vf17 + ibeq vi14, vi04, L35 | madday.xyz ACC, vf03, vf29 + sq.xyzw vf19, 2(vi05) | maddz.xyz vf18, vf04, vf29 + mtir vi04, vf23.w | mulaw.zw ACC, vf10, vf00 + esadd.xyz P, vf11 | mula.xy ACC, vf10, vf23 + mfp.w vf12, P | maddz.xyzw vf23, vf09, vf23 + lq.xyz vf30, 770(vi01) | mulaw.xyzw ACC, vf08, vf00 + lq.xyz vf15, 770(vi02) | maddax.xyzw ACC, vf05, vf21 + lq.xyz vf16, 770(vi03) | madday.xyzw ACC, vf06, vf21 + mtir vi01, vf27.z | maddz.xyz vf21, vf07, vf21 + mtir vi02, vf27.w | subw.z vf18, vf18, vf00 + iadd vi05, vi04, vi13 | mul.xy vf28, vf28, Q + sq.xyzw vf01, 1(vi05) | mulaw.xyzw ACC, vf15, vf31 + lq.xy vf12, 919(vi01) | maddaw.xyzw ACC, vf16, vf31 + lq.xy vf14, 919(vi02) | maddz.xyz vf30, vf30, vf31 + div Q, vf00.w, vf23.w | addw.xy vf28, vf28, vf03 + mfp.w vf04, P | mul.xyz vf13, vf18, vf21 + lqi.xyz vf29, vi08 | mulay.xy ACC, vf12, vf31 + sq.xyzw vf30, 1(vi04) | madday.xy ACC, vf14, vf31 + lqi.xyz vf11, vi10 | maddx.xy vf28, vf28, vf31 + esum.xyzw P, vf13 | addw.z vf27, vf00, vf00 + lqi.xyzw vf22, vi09 | mulaz.xy ACC, vf20, vf17 + lqi.xyz vf14, vi09 | mul.xyz vf23, vf23, Q + lqi.xyzw vf26, vi09 | mul.xyz vf12, vf28, Q + mtir vi01, vf11.x | mul.xyz vf27, vf27, Q + mtir vi02, vf11.y | mulw.xyz vf22, vf22, vf29 + mtir vi03, vf11.z | ftoi4.xyz vf19, vf23 + rsqrt Q, vf02.w, vf04.w | maddw.xy vf28, vf17, vf12 + sq.xyzw vf12, 0(vi05) | mulax.xyz ACC, vf02, vf29 + sq.xyzw vf27, 0(vi04) | add.xyzw vf22, vf22, vf14 + sq.xyzw vf19, 2(vi04) | mulz.xyz vf11, vf21, vf18 + ibne vi14, vi04, L34 | madday.xyz ACC, vf03, vf29 + sq.xyzw vf19, 2(vi05) | maddz.xyz vf17, vf04, vf29 +L35: + ilw.w vi14, 913(vi00) | nop + nop | nop + nop | nop + nop | nop + ibeq vi00, vi14, L39 | nop + iaddi vi10, vi10, -0x1 | nop + iaddi vi08, vi08, -0x3 | nop + b L36 | nop + iaddi vi09, vi09, -0x9 | nop + isw.x vi00, 976(vi00) | nop + isw.x vi01, 977(vi00) | nop + isw.x vi02, 978(vi00) | nop + isw.x vi03, 979(vi00) | nop + isw.x vi04, 980(vi00) | nop + isw.x vi05, 981(vi00) | nop + isw.x vi06, 982(vi00) | nop + isw.x vi07, 983(vi00) | nop + isw.x vi08, 984(vi00) | nop + isw.x vi09, 985(vi00) | nop + isw.x vi10, 986(vi00) | nop + isw.x vi11, 987(vi00) | nop + isw.x vi12, 988(vi00) | nop + isw.x vi13, 989(vi00) | nop + isw.x vi14, 990(vi00) | nop + isw.x vi15, 991(vi00) | nop + sq.xyzw vf00, 992(vi00) | nop + sq.xyzw vf01, 993(vi00) | nop + sq.xyzw vf02, 994(vi00) | nop + sq.xyzw vf03, 995(vi00) | nop + sq.xyzw vf04, 996(vi00) | nop + sq.xyzw vf05, 997(vi00) | nop + sq.xyzw vf06, 998(vi00) | nop + sq.xyzw vf07, 999(vi00) | nop + sq.xyzw vf08, 1000(vi00) | nop + sq.xyzw vf09, 1001(vi00) | nop + sq.xyzw vf10, 1002(vi00) | nop + sq.xyzw vf11, 1003(vi00) | nop + sq.xyzw vf12, 1004(vi00) | nop + sq.xyzw vf13, 1005(vi00) | nop + sq.xyzw vf14, 1006(vi00) | nop + sq.xyzw vf15, 1007(vi00) | nop + sq.xyzw vf16, 1008(vi00) | nop + sq.xyzw vf17, 1009(vi00) | nop + sq.xyzw vf18, 1010(vi00) | nop + sq.xyzw vf19, 1011(vi00) | nop + sq.xyzw vf20, 1012(vi00) | nop + sq.xyzw vf21, 1013(vi00) | nop + sq.xyzw vf22, 1014(vi00) | nop + sq.xyzw vf23, 1015(vi00) | nop + sq.xyzw vf24, 1016(vi00) | nop + sq.xyzw vf25, 1017(vi00) | nop + sq.xyzw vf26, 1018(vi00) | nop + sq.xyzw vf27, 1019(vi00) | nop + sq.xyzw vf28, 1020(vi00) | nop + sq.xyzw vf29, 1021(vi00) | nop + sq.xyzw vf30, 1022(vi00) | nop :e + sq.xyzw vf31, 1023(vi00) | nop +L36: + ilw.w vi14, 913(vi00) | nop + lq.w vf04, 898(vi00) | nop + nop | nop + nop | addw.z vf18, vf00, vf00 + iadd vi14, vi14, vi12 | nop + nop | nop + lqi.xyzw vf23, vi09 | nop + lqi.xyz vf29, vi08 | nop + nop | nop + lqi.xyzw vf21, vi09 | nop + lqi.xyzw vf26, vi09 | mulw.xyz vf23, vf23, vf29 + nop | mulax.xyz ACC, vf02, vf29 + nop | madday.xyzw ACC, vf03, vf29 + nop | maddz.xyzw vf17, vf04, vf29 + nop | add.xyz vf23, vf23, vf21 + nop | addw.w vf23, vf23, vf04 + nop | mulaw.xyzw ACC, vf08, vf00 + nop | subw.z vf17, vf17, vf00 + nop | maddax.xyzw ACC, vf05, vf23 + nop | madday.xyzw ACC, vf06, vf23 + nop | maddz.xyz vf23, vf07, vf23 + nop | nop + nop | nop + nop | nop + nop | nop + nop | mul.xyz vf13, vf17, vf23 + nop | mulz.xyz vf14, vf23, vf17 + nop | nop + nop | nop + nop | addy.x vf13, vf13, vf13 + nop | mul.xyz vf14, vf14, vf14 + nop | nop + nop | nop + nop | addz.x vf13, vf13, vf13 + nop | addy.x vf14, vf14, vf14 + nop | nop + nop | nop + nop | nop + nop | mulax.xy ACC, vf17, vf13 + nop | addz.x vf14, vf14, vf14 + nop | nop + lqi.xyzw vf24, vi09 | maddz.xy vf18, vf23, vf17 + lqi.xyz vf29, vi08 | nop + rsqrt Q, vf02.w, vf14.x | nop + lqi.xyzw vf22, vi09 | nop + lqi.xyzw vf27, vi09 | mulw.xyz vf24, vf24, vf29 + nop | mulax.xyz ACC, vf02, vf29 + lqi.xyzw vf11, vi10 | madday.xyzw ACC, vf03, vf29 + nop | maddz.xyzw vf17, vf04, vf29 + nop | add.xyz vf24, vf24, vf22 + nop | addw.w vf24, vf24, vf04 + nop | mulaw.xyzw ACC, vf08, vf00 + nop | subw.z vf17, vf17, vf00 + nop | maddax.xyzw ACC, vf05, vf24 + nop | madday.xyzw ACC, vf06, vf24 + mtir vi01, vf26.z | maddz.xyz vf24, vf07, vf24 + nop | mula.xy ACC, vf10, vf23 + mtir vi02, vf26.w | mulaw.zw ACC, vf10, vf00 + nop | mul.xy vf18, vf18, Q + lq.xy vf12, 919(vi01) | maddz.xyzw vf25, vf09, vf23 + nop | mul.xyz vf13, vf17, vf24 + lq.xy vf15, 919(vi02) | mulz.xyz vf14, vf24, vf17 + nop | addw.xy vf18, vf18, vf03 + div Q, vf00.w, vf25.w | mulay.xy ACC, vf12, vf31 + nop | addy.x vf13, vf13, vf13 + nop | mul.xyz vf14, vf14, vf14 + nop | madday.xy ACC, vf15, vf31 +L37: + nop | addw.z vf26, vf00, vf00 + mtir vi02, vf11.y | maddx.xy vf18, vf18, vf31 + mtir vi01, vf11.x | addz.x vf13, vf13, vf13 + mtir vi03, vf11.z | addy.x vf14, vf14, vf14 + mtir vi04, vf23.w | mul.xyz vf25, vf25, Q + lq.xyz vf11, 770(vi02) | mul.xyz vf16, vf18, Q + lq.xyz vf30, 770(vi01) | mul.xyz vf12, vf26, Q + lq.xyz vf20, 770(vi03) | mulax.xy ACC, vf17, vf13 + mtir vi06, vf21.w | addz.x vf14, vf14, vf14 + iadd vi05, vi04, vi13 | ftoi4.xyz vf19, vf25 + lqi.xyzw vf23, vi09 | maddz.xy vf18, vf24, vf17 + lqi.xyz vf29, vi08 | mulaw.xyzw ACC, vf11, vf31 + rsqrt Q, vf02.w, vf14.x | maddaw.xyzw ACC, vf20, vf31 + lqi.xyzw vf21, vi09 | maddz.xyz vf30, vf30, vf31 + lqi.xyzw vf26, vi09 | mulw.xyz vf23, vf23, vf29 + iadd vi06, vi06, vi12 | mulax.xyz ACC, vf02, vf29 + lqi.xyzw vf11, vi10 | madday.xyzw ACC, vf03, vf29 + iadd vi07, vi06, vi13 | maddz.xyzw vf17, vf04, vf29 + sq.xyzw vf12, 0(vi04) | add.xyz vf23, vf23, vf21 + sq.xyzw vf30, 1(vi04) | addw.w vf23, vf23, vf04 + sq.xyzw vf19, 2(vi04) | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf16, 0(vi05) | subw.z vf17, vf17, vf00 + sq.xyzw vf01, 1(vi05) | maddax.xyzw ACC, vf05, vf23 + sq.xyzw vf19, 2(vi05) | madday.xyzw ACC, vf06, vf23 + mtir vi01, vf27.z | maddz.xyz vf23, vf07, vf23 + sq.xyzw vf12, 0(vi06) | mula.xy ACC, vf10, vf24 + mtir vi02, vf27.w | mulaw.zw ACC, vf10, vf00 + sq.xyzw vf30, 1(vi06) | mul.xy vf18, vf18, Q + lq.xy vf12, 919(vi01) | maddz.xyzw vf25, vf09, vf24 + sq.xyzw vf19, 2(vi06) | mul.xyz vf13, vf17, vf23 + lq.xy vf15, 919(vi02) | mulz.xyz vf14, vf23, vf17 + sq.xyzw vf16, 0(vi07) | addw.xy vf18, vf18, vf03 + div Q, vf00.w, vf25.w | mulay.xy ACC, vf12, vf31 + sq.xyzw vf01, 1(vi07) | addy.x vf13, vf13, vf13 + ibeq vi14, vi04, L38 | mul.xyz vf14, vf14, vf14 + sq.xyzw vf19, 2(vi07) | madday.xy ACC, vf15, vf31 + nop | addw.z vf27, vf00, vf00 + mtir vi02, vf11.y | maddx.xy vf18, vf18, vf31 + mtir vi01, vf11.x | addz.x vf13, vf13, vf13 + mtir vi03, vf11.z | addy.x vf14, vf14, vf14 + mtir vi04, vf24.w | mul.xyz vf25, vf25, Q + lq.xyz vf11, 770(vi02) | mul.xyz vf16, vf18, Q + lq.xyz vf30, 770(vi01) | mul.xyz vf12, vf27, Q + lq.xyz vf20, 770(vi03) | mulax.xy ACC, vf17, vf13 + mtir vi06, vf22.w | addz.x vf14, vf14, vf14 + iadd vi05, vi04, vi13 | ftoi4.xyz vf19, vf25 + lqi.xyzw vf24, vi09 | maddz.xy vf18, vf23, vf17 + lqi.xyz vf29, vi08 | mulaw.xyzw ACC, vf11, vf31 + rsqrt Q, vf02.w, vf14.x | maddaw.xyzw ACC, vf20, vf31 + lqi.xyzw vf22, vi09 | maddz.xyz vf30, vf30, vf31 + lqi.xyzw vf27, vi09 | mulw.xyz vf24, vf24, vf29 + iadd vi06, vi06, vi12 | mulax.xyz ACC, vf02, vf29 + lqi.xyzw vf11, vi10 | madday.xyzw ACC, vf03, vf29 + iadd vi07, vi06, vi13 | maddz.xyzw vf17, vf04, vf29 + sq.xyzw vf12, 0(vi04) | add.xyz vf24, vf24, vf22 + sq.xyzw vf30, 1(vi04) | addw.w vf24, vf24, vf04 + sq.xyzw vf19, 2(vi04) | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf16, 0(vi05) | subw.z vf17, vf17, vf00 + sq.xyzw vf01, 1(vi05) | maddax.xyzw ACC, vf05, vf24 + sq.xyzw vf19, 2(vi05) | madday.xyzw ACC, vf06, vf24 + mtir vi01, vf26.z | maddz.xyz vf24, vf07, vf24 + sq.xyzw vf12, 0(vi06) | mula.xy ACC, vf10, vf23 + mtir vi02, vf26.w | mulaw.zw ACC, vf10, vf00 + sq.xyzw vf30, 1(vi06) | mul.xy vf18, vf18, Q + lq.xy vf12, 919(vi01) | maddz.xyzw vf25, vf09, vf23 + sq.xyzw vf19, 2(vi06) | mul.xyz vf13, vf17, vf24 + lq.xy vf15, 919(vi02) | mulz.xyz vf14, vf24, vf17 + sq.xyzw vf16, 0(vi07) | addw.xy vf18, vf18, vf03 + div Q, vf00.w, vf25.w | mulay.xy ACC, vf12, vf31 + sq.xyzw vf01, 1(vi07) | addy.x vf13, vf13, vf13 + ibeq vi14, vi04, L38 | mul.xyz vf14, vf14, vf14 + sq.xyzw vf19, 2(vi07) | madday.xy ACC, vf15, vf31 + nop | addw.z vf26, vf00, vf00 + mtir vi02, vf11.y | maddx.xy vf18, vf18, vf31 + mtir vi01, vf11.x | addz.x vf13, vf13, vf13 + mtir vi03, vf11.z | addy.x vf14, vf14, vf14 + mtir vi04, vf23.w | mul.xyz vf25, vf25, Q + lq.xyz vf11, 770(vi02) | mul.xyz vf16, vf18, Q + lq.xyz vf30, 770(vi01) | mul.xyz vf12, vf26, Q + lq.xyz vf20, 770(vi03) | mulax.xy ACC, vf17, vf13 + mtir vi06, vf21.w | addz.x vf14, vf14, vf14 + iadd vi05, vi04, vi13 | ftoi4.xyz vf19, vf25 + lqi.xyzw vf23, vi09 | maddz.xy vf18, vf24, vf17 + lqi.xyz vf29, vi08 | mulaw.xyzw ACC, vf11, vf31 + rsqrt Q, vf02.w, vf14.x | maddaw.xyzw ACC, vf20, vf31 + lqi.xyzw vf21, vi09 | maddz.xyz vf30, vf30, vf31 + lqi.xyzw vf26, vi09 | mulw.xyz vf23, vf23, vf29 + iadd vi06, vi06, vi12 | mulax.xyz ACC, vf02, vf29 + lqi.xyzw vf11, vi10 | madday.xyzw ACC, vf03, vf29 + iadd vi07, vi06, vi13 | maddz.xyzw vf17, vf04, vf29 + sq.xyzw vf12, 0(vi04) | add.xyz vf23, vf23, vf21 + sq.xyzw vf30, 1(vi04) | addw.w vf23, vf23, vf04 + sq.xyzw vf19, 2(vi04) | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf16, 0(vi05) | subw.z vf17, vf17, vf00 + sq.xyzw vf01, 1(vi05) | maddax.xyzw ACC, vf05, vf23 + sq.xyzw vf19, 2(vi05) | madday.xyzw ACC, vf06, vf23 + mtir vi01, vf27.z | maddz.xyz vf23, vf07, vf23 + sq.xyzw vf12, 0(vi06) | mula.xy ACC, vf10, vf24 + mtir vi02, vf27.w | mulaw.zw ACC, vf10, vf00 + sq.xyzw vf30, 1(vi06) | mul.xy vf18, vf18, Q + lq.xy vf12, 919(vi01) | maddz.xyzw vf25, vf09, vf24 + sq.xyzw vf19, 2(vi06) | mul.xyz vf13, vf17, vf23 + lq.xy vf15, 919(vi02) | mulz.xyz vf14, vf23, vf17 + sq.xyzw vf16, 0(vi07) | addw.xy vf18, vf18, vf03 + div Q, vf00.w, vf25.w | mulay.xy ACC, vf12, vf31 + sq.xyzw vf01, 1(vi07) | addy.x vf13, vf13, vf13 + ibeq vi14, vi04, L38 | mul.xyz vf14, vf14, vf14 + sq.xyzw vf19, 2(vi07) | madday.xy ACC, vf15, vf31 + nop | addw.z vf27, vf00, vf00 + mtir vi02, vf11.y | maddx.xy vf18, vf18, vf31 + mtir vi01, vf11.x | addz.x vf13, vf13, vf13 + mtir vi03, vf11.z | addy.x vf14, vf14, vf14 + mtir vi04, vf24.w | mul.xyz vf25, vf25, Q + lq.xyz vf11, 770(vi02) | mul.xyz vf16, vf18, Q + lq.xyz vf30, 770(vi01) | mul.xyz vf12, vf27, Q + lq.xyz vf20, 770(vi03) | mulax.xy ACC, vf17, vf13 + mtir vi06, vf22.w | addz.x vf14, vf14, vf14 + iadd vi05, vi04, vi13 | ftoi4.xyz vf19, vf25 + lqi.xyzw vf24, vi09 | maddz.xy vf18, vf23, vf17 + lqi.xyz vf29, vi08 | mulaw.xyzw ACC, vf11, vf31 + rsqrt Q, vf02.w, vf14.x | maddaw.xyzw ACC, vf20, vf31 + lqi.xyzw vf22, vi09 | maddz.xyz vf30, vf30, vf31 + lqi.xyzw vf27, vi09 | mulw.xyz vf24, vf24, vf29 + iadd vi06, vi06, vi12 | mulax.xyz ACC, vf02, vf29 + lqi.xyzw vf11, vi10 | madday.xyzw ACC, vf03, vf29 + iadd vi07, vi06, vi13 | maddz.xyzw vf17, vf04, vf29 + sq.xyzw vf12, 0(vi04) | add.xyz vf24, vf24, vf22 + sq.xyzw vf30, 1(vi04) | addw.w vf24, vf24, vf04 + sq.xyzw vf19, 2(vi04) | mulaw.xyzw ACC, vf08, vf00 + sq.xyzw vf16, 0(vi05) | subw.z vf17, vf17, vf00 + sq.xyzw vf01, 1(vi05) | maddax.xyzw ACC, vf05, vf24 + sq.xyzw vf19, 2(vi05) | madday.xyzw ACC, vf06, vf24 + mtir vi01, vf26.z | maddz.xyz vf24, vf07, vf24 + sq.xyzw vf12, 0(vi06) | mula.xy ACC, vf10, vf23 + mtir vi02, vf26.w | mulaw.zw ACC, vf10, vf00 + sq.xyzw vf30, 1(vi06) | mul.xy vf18, vf18, Q + lq.xy vf12, 919(vi01) | maddz.xyzw vf25, vf09, vf23 + sq.xyzw vf19, 2(vi06) | mul.xyz vf13, vf17, vf24 + lq.xy vf15, 919(vi02) | mulz.xyz vf14, vf24, vf17 + sq.xyzw vf16, 0(vi07) | addw.xy vf18, vf18, vf03 + div Q, vf00.w, vf25.w | mulay.xy ACC, vf12, vf31 + sq.xyzw vf01, 1(vi07) | addy.x vf13, vf13, vf13 + ibne vi14, vi04, L37 | mul.xyz vf14, vf14, vf14 + sq.xyzw vf19, 2(vi07) | madday.xy ACC, vf15, vf31 +L38: + b L39 | nop + nop | nop + nop | nop :e + nop | nop +L39: + lq.xyzw vf01, 898(vi00) | nop + ilw.z vi02, 914(vi00) | nop + ilw.z vi03, 915(vi00) | nop + iaddi vi01, vi12, -0x1 | nop + xgkick vi01 | nop + mr32.xyzw vf01, vf01 | nop + iaddi vi02, vi02, 0x1 | nop + iaddi vi03, vi03, 0x1 | nop + isw.z vi02, 914(vi00) | nop + isw.z vi03, 915(vi00) | nop :e + sq.xyzw vf01, 898(vi00) | nop diff --git a/test/decompiler/vu_reference/jak2/etie-vu1.txt b/test/decompiler/vu_reference/jak2/etie-vu1.txt new file mode 100644 index 000000000..8fff7b71d --- /dev/null +++ b/test/decompiler/vu_reference/jak2/etie-vu1.txt @@ -0,0 +1,2992 @@ + .word 0x400000c8 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x4000000b + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x40000009 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x40000009 + .word 0x2ff + .word 0xa400392 + .word 0x2ff + .word 0x40000003 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x40000065 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0xa200392 + .word 0x400002ff + .word 0xa400393 + .word 0x2ff + .word 0x80000030 + .word 0x400002ff + .word 0x80000030 + .word 0x2ff + .word 0x420f0009 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x8210392 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80010872 + .word 0x2ff + .word 0xa210392 + .word 0x2ff + .word 0x80000030 + .word 0x400002ff + .word 0x80000030 + .word 0x2ff + .word 0x82c0382 + .word 0x2ff + .word 0x1f10387 + .word 0x2ff + .word 0x1f20388 + .word 0x2ff + .word 0x1f30389 + .word 0x2ff + .word 0x1f4038a + .word 0x2ff + .word 0x1f5038b + .word 0x2ff + .word 0x80040032 + .word 0x2ff + .word 0x1cb0383 + .word 0x2ff + .word 0x802523fe + .word 0x2ff + .word 0x8272001 + .word 0x2ff + .word 0x82d2002 + .word 0x2ff + .word 0x1f80384 + .word 0x2ff + .word 0x81ec237c + .word 0x2ff + .word 0x81ed237c + .word 0x2ff + .word 0x81ee237c + .word 0x2ff + .word 0x81ef237c + .word 0x2ff + .word 0x81f0237c + .word 0x2b5ac7 + .word 0x800c2970 + .word 0x2ff + .word 0x800d29b0 + .word 0x2ff + .word 0x800101b2 + .word 0x2ff + .word 0x3e55fff + .word 0x2ff + .word 0xb012fff + .word 0x2ff + .word 0x81e5637d + .word 0x2ff + .word 0x81e56b7d + .word 0x2ff + .word 0x81e5737d + .word 0x2ff + .word 0x81e57b7d + .word 0x2ff + .word 0x81e5837d + .word 0x2ff + .word 0x40000009 + .word 0x2ff + .word 0x81e5c37d + .word 0x2ff + .word 0x800c2970 + .word 0x2ff + .word 0x800d29b0 + .word 0x2ff + .word 0x81e55b7d + .word 0x2ff + .word 0x81e5637d + .word 0x2ff + .word 0x81e56b7d + .word 0x2ff + .word 0x81e5737d + .word 0x2ff + .word 0x81e57b7d + .word 0x2ff + .word 0x81e5837d + .word 0x2ff + .word 0x81e65b7d + .word 0x2ff + .word 0x81e68b7d + .word 0x2ff + .word 0x81e6937d + .word 0x2ff + .word 0x81e69b7d + .word 0x2ff + .word 0x81e6a37d + .word 0x2ff + .word 0x81e6ab7d + .word 0x2ff + .word 0x80073ff2 + .word 0x2ff + .word 0x802523fe + .word 0x2ff + .word 0x81ec237c + .word 0x2ff + .word 0x81ed237c + .word 0x2ff + .word 0x81ee237c + .word 0x2ff + .word 0x81ef237c + .word 0x2ff + .word 0x5a003feb + .word 0x2ff + .word 0x81f0237c + .word 0x2ff + .word 0x806763fc + .word 0x2ff + .word 0x3806392 + .word 0x2ff + .word 0x3e06b91 + .word 0x2ff + .word 0x3807393 + .word 0x2ff + .word 0x800427b2 + .word 0x200587 + .word 0x810823fe + .word 0x2005c7 + .word 0x808923fe + .word 0x2ff + .word 0x804523fe + .word 0x2ff + .word 0x80073ff2 + .word 0x2ff + .word 0x80042072 + .word 0x2ff + .word 0x1d64b85 + .word 0x2ff + .word 0x5000380e + .word 0x2ff + .word 0x1d74b86 + .word 0x2ff + .word 0x800c2970 + .word 0x2ff + .word 0x800d29b0 + .word 0x2ff + .word 0x80073ff2 + .word 0x2ff + .word 0x3e5b000 + .word 0x2ff + .word 0x81082bff + .word 0x2ff + .word 0x3e6b800 + .word 0x2ff + .word 0x810833ff + .word 0x2ff + .word 0x810823fe + .word 0x2ff + .word 0x808923fe + .word 0x2ff + .word 0x804523fe + .word 0x2ff + .word 0x80042072 + .word 0x2ff + .word 0x52003ff4 + .word 0x2ff + .word 0x1d64b85 + .word 0x2ff + .word 0x800c2970 + .word 0x2ff + .word 0x800d29b0 + .word 0x2ff + .word 0x3e5b000 + .word 0x2ff + .word 0x81082bff + .word 0x2ff + .word 0x11084000 + .word 0x2ff + .word 0x11084000 + .word 0x2ff + .word 0x3e6b800 + .word 0x2ff + .word 0x48007800 + .word 0x2ff + .word 0x810833ff + .word 0x2ff + .word 0x10050084 + .word 0x2ff + .word 0x3c000000 + .word 0x800002ff + .word 0x81f42b7c + .word 0x10005e2 + .word 0x9010393 + .word 0x400443 + .word 0x1ee0020 + .word 0x400483 + .word 0x1b10021 + .word 0x4004c3 + .word 0x10030022 + .word 0x1f4a13c + .word 0x10010820 + .word 0x1ce713c + .word 0x81ef1b7c + .word 0x191893e + .word 0x81b21b7c + .word 0x2ff + .word 0x81f52b7c + .word 0x2ff + .word 0x42800000 + .word 0x81f7a518 + .word 0x50011816 + .word 0x1c0739e + .word 0x8820393 + .word 0x1cf793c + .word 0x81f62b7c + .word 0x1f5a93c + .word 0x81f01b7c + .word 0x192913e + .word 0x81b31b7c + .word 0x2ff + .word 0x3e38ffb + .word 0x2ff + .word 0x3e5a7fd + .word 0x1f7ad58 + .word 0x5001180f + .word 0x1c07bde + .word 0x3e377fa + .word 0x1d0813c + .word 0x81f42b7c + .word 0x1f6b13c + .word 0x81ee1b7c + .word 0x193993e + .word 0x81b11b7c + .word 0x2ff + .word 0x3e397fb + .word 0x2ff + .word 0x3e5affd + .word 0x1f7b598 + .word 0x50011808 + .word 0x1c0841e + .word 0x3e37ffa + .word 0x1ce713c + .word 0x81f52b7c + .word 0x1f4a13c + .word 0x81ef1b7c + .word 0x191893e + .word 0x81b21b7c + .word 0x2ff + .word 0x3e39ffb + .word 0x2ff + .word 0x3e5b7fd + .word 0x1f7a518 + .word 0x52011fec + .word 0x1c0739e + .word 0x3e387fa + .word 0x1cf793c + .word 0x80052fb2 + .word 0x2ff + .word 0x1eb1ffc + .word 0x2ff + .word 0x1ee1ffd + .word 0x2ff + .word 0x1f11ffe + .word 0x2ff + .word 0x81f42b7c + .word 0x2ff + .word 0x800310b0 + .word 0x2ff + .word 0x80021732 + .word 0x2ff + .word 0x80031ff2 + .word 0x2ff + .word 0x80041f72 + .word 0x2ff + .word 0x5002182a + .word 0x1f4a13c + .word 0x80000030 + .word 0x1eb593c + .word 0x81ec1b7c + .word 0x1ce713c + .word 0x81ef1b7c + .word 0x2ff + .word 0x81f21b7c + .word 0x191893e + .word 0x81f52b7c + .word 0x1f7a518 + .word 0x80000030 + .word 0x1c05ade + .word 0x50021823 + .word 0x1c0739e + .word 0x80000030 + .word 0x1ec613c + .word 0x80000030 + .word 0x1f5a93c + .word 0x81ed1b7c + .word 0x1cf793c + .word 0x81f01b7c + .word 0x2ff + .word 0x81f31b7c + .word 0x192913e + .word 0x81f62b7c + .word 0x1f7ad58 + .word 0x2248801 + .word 0x2ff + .word 0x2247002 + .word 0x2ff + .word 0x81e45b7d + .word 0x2ff + .word 0x81c4737d + .word 0x1c0631e + .word 0x3e5a7fd + .word 0x1f6b13c + .word 0x50021817 + .word 0x1c07bde + .word 0x81c48b7d + .word 0x1ed693c + .word 0x81eb1b7c + .word 0x1d0813c + .word 0x81ee1b7c + .word 0x2ff + .word 0x81f11b7c + .word 0x193993e + .word 0x81f42b7c + .word 0x1f7b598 + .word 0x2249001 + .word 0x2ff + .word 0x2247802 + .word 0x2ff + .word 0x81e4637d + .word 0x2ff + .word 0x81c47b7d + .word 0x1c06b5e + .word 0x3e5affd + .word 0x1f4a13c + .word 0x5002180c + .word 0x1c0841e + .word 0x81c4937d + .word 0x1eb593c + .word 0x81ec1b7c + .word 0x1ce713c + .word 0x81ef1b7c + .word 0x2ff + .word 0x81f21b7c + .word 0x191893e + .word 0x81f52b7c + .word 0x1f7a518 + .word 0x2249801 + .word 0x2ff + .word 0x2248002 + .word 0x2ff + .word 0x81e46b7d + .word 0x2ff + .word 0x81c4837d + .word 0x1c05ade + .word 0x3e5b7fd + .word 0x1f5a93c + .word 0x52021fe0 + .word 0x1c0739e + .word 0x81c49b7d + .word 0x1ec613c + .word 0x80000030 + .word 0x400002ff + .word 0x80000030 + .word 0x2ff + .word 0x8410392 + .word 0x2ff + .word 0x800a06bc + .word 0x2ff + .word 0x1e55000 + .word 0x2ff + .word 0x1e65001 + .word 0x2ff + .word 0x80010ff2 + .word 0x2ff + .word 0x1e75002 + .word 0x2ff + .word 0x52000803 + .word 0x2ff + .word 0x1e85003 + .word 0x2ff + .word 0x420f0748 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x1cc0084 + .word 0x2ff + .word 0x1e25004 + .word 0x2ff + .word 0x1e35005 + .word 0x2ff + .word 0x1e45006 + .word 0x2ff + .word 0x1f00020 + .word 0x200347 + .word 0x10080085 + .word 0x1ec11bc + .word 0x1980021 + .word 0x1ec18bd + .word 0x10090022 + .word 0x1ec250a + .word 0x80000030 + .word 0x1e041bf + .word 0x80000030 + .word 0x1f028bc + .word 0x80000030 + .word 0x1f030bd + .word 0x3d5004 + .word 0x40a507 + .word 0x80000030 + .word 0x1d03c0a + .word 0x335005 + .word 0x200087 + .word 0x80000030 + .word 0x2000c7 + .word 0x80000030 + .word 0x9d07c3 + .word 0x80000030 + .word 0x1d4836a + .word 0xbf000000 + .word 0x8033997d + .word 0x3f000000 + .word 0x802010a2 + .word 0x806283fc + .word 0x2018e2 + .word 0x81e06f7e + .word 0x1d4839a + .word 0x81cc437c + .word 0x10007c3 + .word 0x80000030 + .word 0x400603 + .word 0x80000030 + .word 0x400643 + .word 0x81f14b7c + .word 0x400683 + .word 0x80000030 + .word 0x1ec11bc + .word 0x81994b7c + .word 0x1ec18bd + .word 0x80000030 + .word 0x1ec254a + .word 0x80000030 + .word 0x1e041bf + .word 0x43000000 + .word 0x81f128bc + .word 0x81c0773c + .word 0x1f130bd + .word 0x810d067c + .word 0x20efde + .word 0x1e9038c + .word 0x40ad47 + .word 0x1ea038d + .word 0x1d13c4a + .word 0x80000030 + .word 0x11dffc7 + .word 0x80000030 + .word 0x18da518 + .word 0x80000030 + .word 0x3ff97c + .word 0x80000030 + .word 0x19052be + .word 0x3f000000 + .word 0x81d58b6a + .word 0x80000030 + .word 0x80ffde + .word 0x43800000 + .word 0x803ff93c + .word 0x811e067c + .word 0x18ea528 + .word 0x80638bfc + .word 0x6051bf + .word 0x81e06f7e + .word 0x1d58b9a + .word 0x81cc437c + .word 0x4006c3 + .word 0x80000030 + .word 0x4007e2 + .word 0x807e13be + .word 0x5fffc7 + .word 0x80000030 + .word 0x1f04c8a + .word 0x81f04b7c + .word 0x1ec11bc + .word 0x819a4b7c + .word 0x1ec18bd + .word 0x80000030 + .word 0x1ec258a + .word 0x82c0382 + .word 0x1e041bf + .word 0x3e5006 + .word 0x1f028bc + .word 0x81c0773c + .word 0x1f030bd + .word 0x810d067c + .word 0x5fffc7 + .word 0x1e15007 + .word 0x40b587 + .word 0x800a5232 + .word 0x1d03c0a + .word 0x90e0391 + .word 0x400503 + .word 0x81eb537c + .word 0x18dad58 + .word 0x81f203bc + .word 0x180a51c + .word 0x100b0397 + .word 0x19152be + .word 0x800c73b0 + .word 0x1d6836a + .word 0x80015bfc + .word 0x183a503 + .word 0x811e067c + .word 0x18ead68 + .word 0x806483fc + .word 0x6051bf + .word 0x81e06f7e + .word 0x1d6839a + .word 0x81cc437c + .word 0x1c094dc + .word 0x81eba37d + .word 0x1c0c71c + .word 0x807e13be + .word 0x1c0a51c + .word 0x81f14b7c + .word 0x1f14c8a + .word 0x1de0b02 + .word 0x1ec11bc + .word 0x819b4b7c + .word 0x1ec18bd + .word 0x800c10b0 + .word 0x1ec25ca + .word 0x800d11b0 + .word 0x1e041bf + .word 0x3e2f001 + .word 0x1f128bc + .word 0x81c0773c + .word 0x1f130bd + .word 0x810d067c + .word 0x1d3997d + .word 0x3e2e000 + .word 0x40bdc7 + .word 0x3e6a000 + .word 0x1d13c4a + .word 0x3e60801 + .word 0x400543 + .word 0x3e29802 + .word 0x18db598 + .word 0x81f203bc + .word 0x180ad5c + .word 0x500e1092 + .word 0x19052be + .word 0x3e69802 + .word 0x1d78b6a + .word 0x80215bfc + .word 0x183ad43 + .word 0x811e067c + .word 0x18eb5a8 + .word 0x80658bfc + .word 0x6051bf + .word 0x81e06f7e + .word 0x1d78b9a + .word 0x81cc437c + .word 0x1c094dc + .word 0x81ebab7d + .word 0x1c0cf1c + .word 0x807e13be + .word 0x1c0ad5c + .word 0x1de0b02 + .word 0x1f04c8a + .word 0x81f04b7c + .word 0x1ec11bc + .word 0x81984b7c + .word 0x1ec18bd + .word 0x800c18f0 + .word 0x1ec250a + .word 0x800d19b0 + .word 0x1e041bf + .word 0x3e3f001 + .word 0x1f028bc + .word 0x81c0773c + .word 0x1f030bd + .word 0x810d067c + .word 0x1d3997d + .word 0x3e3e000 + .word 0x40a507 + .word 0x3e6a800 + .word 0x1d03c0a + .word 0x3e60801 + .word 0x400583 + .word 0x3e39802 + .word 0x18dbdd8 + .word 0x81f203bc + .word 0x180b59c + .word 0x500e18cf + .word 0x19152be + .word 0x3e69802 + .word 0x1d4836a + .word 0x80415bfc + .word 0x183b583 + .word 0x811e067c + .word 0x18ebde8 + .word 0x806283fc + .word 0x6051bf + .word 0x81e06f7e + .word 0x1d4839a + .word 0x81cc437c + .word 0x1c094dc + .word 0x81ebb37d + .word 0x1c0d71c + .word 0x807e13be + .word 0x1c0b59c + .word 0x1de0b02 + .word 0x1f14c8a + .word 0x81f14b7c + .word 0x1ec11bc + .word 0x81994b7c + .word 0x1ec18bd + .word 0x800c2130 + .word 0x1ec254a + .word 0x800d21b0 + .word 0x1e041bf + .word 0x3e4f001 + .word 0x1f128bc + .word 0x81c0773c + .word 0x1f130bd + .word 0x810d067c + .word 0x1d3997d + .word 0x3e4e000 + .word 0x40ad47 + .word 0x3e6b000 + .word 0x1d13c4a + .word 0x3e60801 + .word 0x4005c3 + .word 0x3e49802 + .word 0x18da518 + .word 0x81f203bc + .word 0x180bddc + .word 0x500e2105 + .word 0x19052be + .word 0x3e69802 + .word 0x1d58b6a + .word 0x80615bfc + .word 0x183bdc3 + .word 0x811e067c + .word 0x18ea528 + .word 0x80638bfc + .word 0x6051bf + .word 0x81e06f7e + .word 0x1d58b9a + .word 0x81cc437c + .word 0x1c094dc + .word 0x81ebbb7d + .word 0x1c0df1c + .word 0x807e13be + .word 0x1c0bddc + .word 0x81f04b7c + .word 0x1f04c8a + .word 0x1de0b02 + .word 0x1ec11bc + .word 0x819a4b7c + .word 0x1ec18bd + .word 0x800c2970 + .word 0x1ec258a + .word 0x800d29b0 + .word 0x1e041bf + .word 0x3e5f001 + .word 0x1f028bc + .word 0x81c0773c + .word 0x1f030bd + .word 0x810d067c + .word 0x1d3997d + .word 0x3e5e000 + .word 0x40b587 + .word 0x3e6b800 + .word 0x1d03c0a + .word 0x3e60801 + .word 0x400503 + .word 0x3e59802 + .word 0x18dad58 + .word 0x81eb537c + .word 0x2ff + .word 0x81f203bc + .word 0x180a51c + .word 0x520e2fa8 + .word 0x19152be + .word 0x3e69802 + .word 0x1d6836a + .word 0x80000030 + .word 0x183a503 + .word 0x811e067c + .word 0x18ead68 + .word 0x806483fc + .word 0x6051bf + .word 0x81e06f7e + .word 0x1d6839a + .word 0x81cc437c + .word 0x2ff + .word 0x80015bfc + .word 0x1c094dc + .word 0x81eba37d + .word 0x1c0c71c + .word 0x807e13be + .word 0x1c0a51c + .word 0x1de0b02 + .word 0x1f14c8a + .word 0x81f14b7c + .word 0x1ec11bc + .word 0x81bb4b7c + .word 0x1ec18bd + .word 0x800c10b0 + .word 0x1cc25ca + .word 0x800d11b0 + .word 0x1e041bf + .word 0x3e2f001 + .word 0x1f128bc + .word 0x81c0773c + .word 0x1f130bd + .word 0x810d067c + .word 0x1d3997d + .word 0x3e2e000 + .word 0x1d13c4a + .word 0x3e6a000 + .word 0x40bdc7 + .word 0x3e60801 + .word 0x400543 + .word 0x3e29802 + .word 0x18db598 + .word 0x81f203bc + .word 0x180ad5c + .word 0x80000030 + .word 0x19052be + .word 0x3e69802 + .word 0x2ff + .word 0x80215bfc + .word 0x183ad43 + .word 0x811e067c + .word 0x18eb5a8 + .word 0x80000030 + .word 0x6051bf + .word 0x80000030 + .word 0x1d78b9a + .word 0x81cc437c + .word 0x1c094dc + .word 0x81ebab7d + .word 0x1c0cf1c + .word 0x807e13be + .word 0x1c0ad5c + .word 0x1de0b02 + .word 0x1f04c8a + .word 0x81f04b7c + .word 0x1ce73ea + .word 0x80000030 + .word 0x1ec11bc + .word 0x800c18f0 + .word 0x1ec18bd + .word 0x800d19b0 + .word 0x1cc250a + .word 0x3e3f001 + .word 0x10f7bc1 + .word 0x80000030 + .word 0x1d78b6a + .word 0x80000030 + .word 0x1d3997d + .word 0x3e3e000 + .word 0x2ff + .word 0x3e6a800 + .word 0x10f7bc2 + .word 0x3e60801 + .word 0x400583 + .word 0x3e39802 + .word 0x10d6b41 + .word 0x81f203bc + .word 0x180b59c + .word 0x80000030 + .word 0x1e041bf + .word 0x3e69802 + .word 0x1f028bc + .word 0x80415bfc + .word 0x183b583 + .word 0x80000030 + .word 0x10d6b42 + .word 0x80000030 + .word 0x1f030bd + .word 0x80000030 + .word 0x1d03c0a + .word 0x80000030 + .word 0x1c094dc + .word 0x81ebb37d + .word 0x1c0d71c + .word 0x80000030 + .word 0x1c0b59c + .word 0x1de0b02 + .word 0x2ff + .word 0x90e0392 + .word 0x2ff + .word 0x806f13be + .word 0x18dbdd8 + .word 0x800c2130 + .word 0x2ff + .word 0x800d21b0 + .word 0x19152be + .word 0x3e4f001 + .word 0x6051bf + .word 0x5200700a + .word 0x18ebde8 + .word 0x81b84b7c + .word 0x1d3997d + .word 0x88e0391 + .word 0x2ff + .word 0x81cc437c + .word 0x40a507 + .word 0x3e4e000 + .word 0x2ff + .word 0x3e6b000 + .word 0x1f14c8a + .word 0x3e60801 + .word 0x2ff + .word 0x3e49802 + .word 0x1ec11bc + .word 0x800c73b0 + .word 0x1ec18bd + .word 0x40000148 + .word 0x1d4839a + .word 0x3e69802 + .word 0x1cc254a + .word 0x88e0392 + .word 0x2ff + .word 0x3e4e000 + .word 0x2ff + .word 0x3e6b000 + .word 0x2ff + .word 0x3e60800 + .word 0x2ff + .word 0x3e49802 + .word 0x2ff + .word 0x52007418 + .word 0x2ff + .word 0x3e69802 + .word 0x2ff + .word 0x40000240 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80215bfc + .word 0x183ad43 + .word 0x811e067c + .word 0x18eb5a8 + .word 0x80658bfc + .word 0x6051bf + .word 0x81e06f7e + .word 0x1d78b9a + .word 0x81ec437c + .word 0x1c094dc + .word 0x81ebab7d + .word 0x1c0cf1c + .word 0x807e13be + .word 0x1c0ad5c + .word 0x1de0b02 + .word 0x1f04c8a + .word 0x81f04b7c + .word 0x1ec11bc + .word 0x81b84b7c + .word 0x1ec18bd + .word 0x800c18f0 + .word 0x1cc250a + .word 0x800d19b0 + .word 0x1e041bf + .word 0x3e3f001 + .word 0x1f028bc + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x81c0773c + .word 0x1f030bd + .word 0x810d067c + .word 0x1d3997d + .word 0x3e3e000 + .word 0x1d03c0a + .word 0x3e6a800 + .word 0x40a507 + .word 0x3e60801 + .word 0x400583 + .word 0x3e39802 + .word 0x18dbdd8 + .word 0x81f203bc + .word 0x180b59c + .word 0x80000030 + .word 0x19152be + .word 0x3e69802 + .word 0x2ff + .word 0x80415bfc + .word 0x183b583 + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x811e067c + .word 0x18ebde8 + .word 0x80000030 + .word 0x6051bf + .word 0x80000030 + .word 0x1d4839a + .word 0x81ec437c + .word 0x1c094dc + .word 0x81ebb37d + .word 0x1c0d71c + .word 0x807e13be + .word 0x1c0b59c + .word 0x1de0b02 + .word 0x1f14c8a + .word 0x81f14b7c + .word 0x1ce73ea + .word 0x80000030 + .word 0x1ec11bc + .word 0x800c2130 + .word 0x1ec18bd + .word 0x800d21b0 + .word 0x1cc254a + .word 0x3e4f001 + .word 0x10f7bc1 + .word 0x80000030 + .word 0x1d4836a + .word 0x80000030 + .word 0x1d3997d + .word 0x3e4e000 + .word 0x2ff + .word 0x3e6b000 + .word 0x10f7bc2 + .word 0x3e60801 + .word 0x4005c3 + .word 0x3e49802 + .word 0x10d6b41 + .word 0x81f203bc + .word 0x180bddc + .word 0x80000030 + .word 0x1e041bf + .word 0x3e69802 + .word 0x1f128bc + .word 0x80000030 + .word 0x2ff + .word 0x80615bfc + .word 0x183bdc3 + .word 0x80000030 + .word 0x10d6b42 + .word 0x80000030 + .word 0x1f130bd + .word 0x80000030 + .word 0x1d13c4a + .word 0x80000030 + .word 0x1c094dc + .word 0x81ebbb7d + .word 0x1c0df1c + .word 0x80000030 + .word 0x1c0bddc + .word 0x1de0b02 + .word 0x2ff + .word 0x90e0392 + .word 0x2ff + .word 0x806f13be + .word 0x18da518 + .word 0x800c2970 + .word 0x2ff + .word 0x800d29b0 + .word 0x19052be + .word 0x3e5f001 + .word 0x6051bf + .word 0x5200700a + .word 0x18ea528 + .word 0x81b94b7c + .word 0x1d3997d + .word 0x88e0391 + .word 0x2ff + .word 0x81ec437c + .word 0x40ad47 + .word 0x3e5e000 + .word 0x2ff + .word 0x3e6b800 + .word 0x1f04c8a + .word 0x3e60801 + .word 0x2ff + .word 0x3e59802 + .word 0x1ec11bc + .word 0x800c73b0 + .word 0x1ec18bd + .word 0x400000a3 + .word 0x1d58b9a + .word 0x3e69802 + .word 0x1cc258a + .word 0x88e0392 + .word 0x2ff + .word 0x3e5e000 + .word 0x2ff + .word 0x3e6b800 + .word 0x2ff + .word 0x3e60801 + .word 0x2ff + .word 0x3e59802 + .word 0x2ff + .word 0x520073c5 + .word 0x2ff + .word 0x3e69802 + .word 0x2ff + .word 0x400001ed + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80415bfc + .word 0x183b583 + .word 0x811e067c + .word 0x18ebde8 + .word 0x806283fc + .word 0x6051bf + .word 0x81e06f7e + .word 0x1d4839a + .word 0x81ec437c + .word 0x1c094dc + .word 0x81ebb37d + .word 0x1c0d71c + .word 0x807e13be + .word 0x1c0b59c + .word 0x1de0b02 + .word 0x1f14c8a + .word 0x81f14b7c + .word 0x1ec11bc + .word 0x81b94b7c + .word 0x1ec18bd + .word 0x800c2130 + .word 0x1cc254a + .word 0x800d21b0 + .word 0x1e041bf + .word 0x3e4f001 + .word 0x1f128bc + .word 0x81c0773c + .word 0x1f130bd + .word 0x810d067c + .word 0x1d3997d + .word 0x3e4e000 + .word 0x1d13c4a + .word 0x3e6b000 + .word 0x40ad47 + .word 0x3e60801 + .word 0x4005c3 + .word 0x3e49802 + .word 0x18da518 + .word 0x81f203bc + .word 0x180bddc + .word 0x80000030 + .word 0x19052be + .word 0x3e69802 + .word 0x2ff + .word 0x80615bfc + .word 0x183bdc3 + .word 0x811e067c + .word 0x18ea528 + .word 0x80000030 + .word 0x6051bf + .word 0x80000030 + .word 0x1d58b9a + .word 0x81ec437c + .word 0x1c094dc + .word 0x81ebbb7d + .word 0x1c0df1c + .word 0x807e13be + .word 0x1c0bddc + .word 0x1de0b02 + .word 0x1f04c8a + .word 0x81f04b7c + .word 0x1ce73ea + .word 0x80000030 + .word 0x1ec11bc + .word 0x800c2970 + .word 0x1ec18bd + .word 0x800d29b0 + .word 0x1cc258a + .word 0x3e5f001 + .word 0x10f7bc1 + .word 0x80000030 + .word 0x1d58b6a + .word 0x81eb537c + .word 0x1d3997d + .word 0x3e5e000 + .word 0x2ff + .word 0x3e6b800 + .word 0x10f7bc2 + .word 0x3e60801 + .word 0x400503 + .word 0x3e59802 + .word 0x10d6b41 + .word 0x81f203bc + .word 0x180a51c + .word 0x80000030 + .word 0x1e041bf + .word 0x3e69802 + .word 0x1f028bc + .word 0x80015bfc + .word 0x183a503 + .word 0x80000030 + .word 0x10d6b42 + .word 0x80000030 + .word 0x1f030bd + .word 0x80000030 + .word 0x1d03c0a + .word 0x80000030 + .word 0x1c094dc + .word 0x81eba37d + .word 0x1c0c71c + .word 0x80000030 + .word 0x1c0a51c + .word 0x1de0b02 + .word 0x2ff + .word 0x90e0392 + .word 0x2ff + .word 0x806f13be + .word 0x18dad58 + .word 0x800c10b0 + .word 0x2ff + .word 0x800d11b0 + .word 0x19152be + .word 0x3e2f001 + .word 0x6051bf + .word 0x52007009 + .word 0x18ead68 + .word 0x81ba4b7c + .word 0x1d3997d + .word 0x88e0391 + .word 0x40b587 + .word 0x3e2e000 + .word 0x2ff + .word 0x3e6a000 + .word 0x1f14c8a + .word 0x3e60801 + .word 0x2ff + .word 0x3e29802 + .word 0x1ec11bc + .word 0x800c73b0 + .word 0x1ec18bd + .word 0x40000073 + .word 0x1d6839a + .word 0x3e69802 + .word 0x1cc25ca + .word 0x88e0392 + .word 0x2ff + .word 0x3e2e000 + .word 0x2ff + .word 0x3e6a000 + .word 0x2ff + .word 0x3e60801 + .word 0x2ff + .word 0x3e29802 + .word 0x2ff + .word 0x52007379 + .word 0x2ff + .word 0x3e69802 + .word 0x2ff + .word 0x400001a1 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80615bfc + .word 0x183bdc3 + .word 0x811e067c + .word 0x18ea528 + .word 0x80638bfc + .word 0x6051bf + .word 0x81e06f7e + .word 0x1d58b9a + .word 0x81ec437c + .word 0x1c094dc + .word 0x81ebbb7d + .word 0x1c0df1c + .word 0x807e13be + .word 0x1c0bddc + .word 0x1de0b02 + .word 0x1f04c8a + .word 0x81f04b7c + .word 0x1ec11bc + .word 0x81ba4b7c + .word 0x1ec18bd + .word 0x800c2970 + .word 0x1cc258a + .word 0x800d29b0 + .word 0x1e041bf + .word 0x3e5f001 + .word 0x1f028bc + .word 0x81c0773c + .word 0x1f030bd + .word 0x810d067c + .word 0x1d3997d + .word 0x3e5e000 + .word 0x1d03c0a + .word 0x3e6b800 + .word 0x40b587 + .word 0x3e60801 + .word 0x400503 + .word 0x3e59802 + .word 0x18dad58 + .word 0x81f203bc + .word 0x180a51c + .word 0x81eb537c + .word 0x19152be + .word 0x3e69802 + .word 0x2ff + .word 0x80000030 + .word 0x183a503 + .word 0x811e067c + .word 0x18ead68 + .word 0x80000030 + .word 0x6051bf + .word 0x80015bfc + .word 0x1d6839a + .word 0x81ec437c + .word 0x1c094dc + .word 0x81eba37d + .word 0x1c0c71c + .word 0x807e13be + .word 0x1c0a51c + .word 0x1de0b02 + .word 0x1f14c8a + .word 0x81f14b7c + .word 0x1ce73ea + .word 0x80000030 + .word 0x1ec11bc + .word 0x800c10b0 + .word 0x1ec18bd + .word 0x800d11b0 + .word 0x1cc25ca + .word 0x3e2f001 + .word 0x10f7bc1 + .word 0x80000030 + .word 0x1d6836a + .word 0x80000030 + .word 0x1d3997d + .word 0x3e2e000 + .word 0x2ff + .word 0x3e6a000 + .word 0x10f7bc2 + .word 0x3e60801 + .word 0x400543 + .word 0x3e29802 + .word 0x10d6b41 + .word 0x81f203bc + .word 0x180ad5c + .word 0x80000030 + .word 0x1e041bf + .word 0x3e69802 + .word 0x1f128bc + .word 0x80215bfc + .word 0x183ad43 + .word 0x80000030 + .word 0x10d6b42 + .word 0x80000030 + .word 0x1f130bd + .word 0x80000030 + .word 0x1d13c4a + .word 0x80000030 + .word 0x1c094dc + .word 0x81ebab7d + .word 0x1c0cf1c + .word 0x80000030 + .word 0x1c0ad5c + .word 0x1de0b02 + .word 0x2ff + .word 0x90e0392 + .word 0x2ff + .word 0x806f13be + .word 0x18db598 + .word 0x800c18f0 + .word 0x2ff + .word 0x800d19b0 + .word 0x19052be + .word 0x3e3f001 + .word 0x6051bf + .word 0x5200700a + .word 0x18eb5a8 + .word 0x81bb4b7c + .word 0x1d3997d + .word 0x88e0391 + .word 0x2ff + .word 0x81ec437c + .word 0x40bdc7 + .word 0x3e3e000 + .word 0x2ff + .word 0x3e6a800 + .word 0x1f04c8a + .word 0x3e60801 + .word 0x2ff + .word 0x3e39802 + .word 0x1ec11bc + .word 0x800c73b0 + .word 0x1ec18bd + .word 0x40000041 + .word 0x1d78b9a + .word 0x3e69802 + .word 0x1cc250a + .word 0x88e0392 + .word 0x2ff + .word 0x3e3e000 + .word 0x2ff + .word 0x3e6a800 + .word 0x2ff + .word 0x3e60801 + .word 0x2ff + .word 0x3e39802 + .word 0x2ff + .word 0x5200732c + .word 0x2ff + .word 0x3e69802 + .word 0x2ff + .word 0x40000154 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x81eb537c + .word 0x2ff + .word 0x81f203bc + .word 0x180a51c + .word 0x806283fc + .word 0x400503 + .word 0x8067c3fc + .word 0x1ce73ea + .word 0x81f04b7c + .word 0x1d58b6a + .word 0x80015bfc + .word 0x183a503 + .word 0x800c10b0 + .word 0x1e041bf + .word 0x800d11b0 + .word 0x10f7bc1 + .word 0x800c39f0 + .word 0x1f028bc + .word 0x81eba37d + .word 0x10d6b41 + .word 0x1de0b02 + .word 0x1f030bd + .word 0x800d3bf0 + .word 0x10f7bc2 + .word 0x3e60801 + .word 0x1d03c0a + .word 0x3ef0801 + .word 0x10d6b42 + .word 0x3e2f001 + .word 0x1c094dc + .word 0x806f13be + .word 0x1c0c71c + .word 0x3e7f001 + .word 0x1c0a51c + .word 0x81ba4b7c + .word 0x18dad58 + .word 0x81ec437c + .word 0x1d3997d + .word 0x3e2e000 + .word 0x19152be + .word 0x3e7e000 + .word 0x6051bf + .word 0x3e6a000 + .word 0x18ead68 + .word 0x3efa000 + .word 0x40b587 + .word 0x3e29802 + .word 0x1f14c8a + .word 0x3e69802 + .word 0x1ec11bc + .word 0x3e79802 + .word 0x1ec18bd + .word 0x500e108b + .word 0x1d6839a + .word 0x3ef9802 + .word 0x1cc25ca + .word 0x81f203bc + .word 0x180ad5c + .word 0x80628bfc + .word 0x400543 + .word 0x8067cbfc + .word 0x1ce73ea + .word 0x81f14b7c + .word 0x1d6836a + .word 0x80215bfc + .word 0x183ad43 + .word 0x800c10b0 + .word 0x1e041bf + .word 0x800d11b0 + .word 0x10f7bc1 + .word 0x800c39f0 + .word 0x1f128bc + .word 0x81ebab7d + .word 0x10d6b41 + .word 0x1de0b02 + .word 0x1f130bd + .word 0x800d3bf0 + .word 0x10f7bc2 + .word 0x3e60801 + .word 0x1d13c4a + .word 0x3ef0801 + .word 0x10d6b42 + .word 0x3e2f001 + .word 0x1c094dc + .word 0x806f13be + .word 0x1c0cf1c + .word 0x3e7f001 + .word 0x1c0ad5c + .word 0x81bb4b7c + .word 0x18db598 + .word 0x81ec437c + .word 0x1d3997d + .word 0x3e2e000 + .word 0x19052be + .word 0x3e7e000 + .word 0x6051bf + .word 0x3e6a800 + .word 0x18eb5a8 + .word 0x3efa800 + .word 0x40bdc7 + .word 0x3e29802 + .word 0x1f04c8a + .word 0x3e69802 + .word 0x1ec11bc + .word 0x3e79802 + .word 0x1ec18bd + .word 0x500e10a8 + .word 0x1d78b9a + .word 0x3ef9802 + .word 0x1cc250a + .word 0x81f203bc + .word 0x180b59c + .word 0x806283fc + .word 0x400583 + .word 0x8067d3fc + .word 0x1ce73ea + .word 0x81f04b7c + .word 0x1d78b6a + .word 0x80415bfc + .word 0x183b583 + .word 0x800c10b0 + .word 0x1e041bf + .word 0x800d11b0 + .word 0x10f7bc1 + .word 0x800c39f0 + .word 0x1f028bc + .word 0x81ebb37d + .word 0x10d6b41 + .word 0x1de0b02 + .word 0x1f030bd + .word 0x800d3bf0 + .word 0x10f7bc2 + .word 0x3e60801 + .word 0x1d03c0a + .word 0x3ef0801 + .word 0x10d6b42 + .word 0x3e2f001 + .word 0x1c094dc + .word 0x806f13be + .word 0x1c0d71c + .word 0x3e7f001 + .word 0x1c0b59c + .word 0x81b84b7c + .word 0x18dbdd8 + .word 0x81ec437c + .word 0x1d3997d + .word 0x3e2e000 + .word 0x19152be + .word 0x3e7e000 + .word 0x6051bf + .word 0x3e6b000 + .word 0x18ebde8 + .word 0x3efb000 + .word 0x40a507 + .word 0x3e29802 + .word 0x1f14c8a + .word 0x3e69802 + .word 0x1ec11bc + .word 0x3e79802 + .word 0x1ec18bd + .word 0x500e10c5 + .word 0x1d4839a + .word 0x3ef9802 + .word 0x1cc254a + .word 0x81f203bc + .word 0x180bddc + .word 0x80628bfc + .word 0x4005c3 + .word 0x8067dbfc + .word 0x1ce73ea + .word 0x81f14b7c + .word 0x1d4836a + .word 0x80615bfc + .word 0x183bdc3 + .word 0x800c10b0 + .word 0x1e041bf + .word 0x800d11b0 + .word 0x10f7bc1 + .word 0x800c39f0 + .word 0x1f128bc + .word 0x81ebbb7d + .word 0x10d6b41 + .word 0x1de0b02 + .word 0x1f130bd + .word 0x800d3bf0 + .word 0x10f7bc2 + .word 0x3e60801 + .word 0x1d13c4a + .word 0x3ef0801 + .word 0x10d6b42 + .word 0x3e2f001 + .word 0x1c094dc + .word 0x806f13be + .word 0x1c0df1c + .word 0x3e7f001 + .word 0x1c0bddc + .word 0x81b94b7c + .word 0x18da518 + .word 0x81ec437c + .word 0x1d3997d + .word 0x3e2e000 + .word 0x19052be + .word 0x3e7e000 + .word 0x6051bf + .word 0x3e6b800 + .word 0x18ea528 + .word 0x3efb800 + .word 0x40ad47 + .word 0x3e29802 + .word 0x1f04c8a + .word 0x3e69802 + .word 0x1ec11bc + .word 0x3e79802 + .word 0x1ec18bd + .word 0x520e1794 + .word 0x1d58b9a + .word 0x3ef9802 + .word 0x1cc258a + .word 0x81eb537c + .word 0x2ff + .word 0x81f203bc + .word 0x180a51c + .word 0x806283fc + .word 0x400503 + .word 0x8067c3fc + .word 0x1ce73ea + .word 0x80000030 + .word 0x1d58b6a + .word 0x80015bfc + .word 0x183a503 + .word 0x800c10b0 + .word 0x2ff + .word 0x800d11b0 + .word 0x10f7bc1 + .word 0x800c39f0 + .word 0x2ff + .word 0x81eba37d + .word 0x10d6b41 + .word 0x1de0b02 + .word 0x2ff + .word 0x800d3bf0 + .word 0x10f7bc2 + .word 0x3e60801 + .word 0x2ff + .word 0x3ef0801 + .word 0x10d6b42 + .word 0x3e2f001 + .word 0x1c094dc + .word 0x806f13be + .word 0x1c0c71c + .word 0x3e7f001 + .word 0x1c0a51c + .word 0x80000030 + .word 0x18dad58 + .word 0x80000030 + .word 0x1d3997d + .word 0x3e2e000 + .word 0x19152be + .word 0x3e7e000 + .word 0x6051bf + .word 0x3e6a000 + .word 0x18ead68 + .word 0x3efa000 + .word 0x2ff + .word 0x3e29802 + .word 0x2ff + .word 0x3e69802 + .word 0x1f14c8a + .word 0x3e79802 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x3ef9802 + .word 0x2ff + .word 0x81f203bc + .word 0x180ad5c + .word 0x80628bfc + .word 0x400543 + .word 0x8067cbfc + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80215bfc + .word 0x183ad43 + .word 0x800c10b0 + .word 0x2ff + .word 0x800d11b0 + .word 0x2ff + .word 0x800c39f0 + .word 0x2ff + .word 0x81ebab7d + .word 0x2ff + .word 0x1de0b02 + .word 0x2ff + .word 0x800d3bf0 + .word 0x2ff + .word 0x3e60801 + .word 0x2ff + .word 0x3ef0801 + .word 0x2ff + .word 0x3e2f001 + .word 0x1c094dc + .word 0x80000030 + .word 0x1c0cf1c + .word 0x3e7f001 + .word 0x1c0ad5c + .word 0x80000030 + .word 0x2ff + .word 0x88e0392 + .word 0x1d3997d + .word 0x3e2e000 + .word 0x2ff + .word 0x3e7e000 + .word 0x2ff + .word 0x3e6a800 + .word 0x2ff + .word 0x3efa800 + .word 0x2ff + .word 0x3e29802 + .word 0x2ff + .word 0x3e69802 + .word 0x2ff + .word 0x3e79802 + .word 0x2ff + .word 0x52007286 + .word 0x2ff + .word 0x3ef9802 + .word 0x2ff + .word 0x400000aa + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x81f203bc + .word 0x180ad5c + .word 0x80628bfc + .word 0x400543 + .word 0x8067cbfc + .word 0x1ce73ea + .word 0x80000030 + .word 0x1d6836a + .word 0x80215bfc + .word 0x183ad43 + .word 0x800c10b0 + .word 0x2ff + .word 0x800d11b0 + .word 0x10f7bc1 + .word 0x800c39f0 + .word 0x2ff + .word 0x81ebab7d + .word 0x10d6b41 + .word 0x1de0b02 + .word 0x2ff + .word 0x800d3bf0 + .word 0x10f7bc2 + .word 0x3e60801 + .word 0x2ff + .word 0x3ef0801 + .word 0x10d6b42 + .word 0x3e2f001 + .word 0x1c094dc + .word 0x806f13be + .word 0x1c0cf1c + .word 0x3e7f001 + .word 0x1c0ad5c + .word 0x80000030 + .word 0x18db598 + .word 0x80000030 + .word 0x1d3997d + .word 0x3e2e000 + .word 0x19052be + .word 0x3e7e000 + .word 0x6051bf + .word 0x3e6a800 + .word 0x18eb5a8 + .word 0x3efa800 + .word 0x2ff + .word 0x3e29802 + .word 0x2ff + .word 0x3e69802 + .word 0x1f04c8a + .word 0x3e79802 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x3ef9802 + .word 0x2ff + .word 0x81f203bc + .word 0x180b59c + .word 0x806283fc + .word 0x400583 + .word 0x8067d3fc + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80415bfc + .word 0x183b583 + .word 0x800c10b0 + .word 0x2ff + .word 0x800d11b0 + .word 0x2ff + .word 0x800c39f0 + .word 0x2ff + .word 0x81ebb37d + .word 0x2ff + .word 0x1de0b02 + .word 0x2ff + .word 0x800d3bf0 + .word 0x2ff + .word 0x3e60801 + .word 0x2ff + .word 0x3ef0801 + .word 0x2ff + .word 0x3e2f001 + .word 0x1c094dc + .word 0x80000030 + .word 0x1c0d71c + .word 0x3e7f001 + .word 0x1c0b59c + .word 0x80000030 + .word 0x2ff + .word 0x88e0392 + .word 0x1d3997d + .word 0x3e2e000 + .word 0x2ff + .word 0x3e7e000 + .word 0x2ff + .word 0x3e6b000 + .word 0x2ff + .word 0x3efb000 + .word 0x2ff + .word 0x3e29802 + .word 0x2ff + .word 0x3e69802 + .word 0x2ff + .word 0x3e79802 + .word 0x2ff + .word 0x5200724e + .word 0x2ff + .word 0x3ef9802 + .word 0x2ff + .word 0x40000072 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x81f203bc + .word 0x180b59c + .word 0x806283fc + .word 0x400583 + .word 0x8067d3fc + .word 0x1ce73ea + .word 0x80000030 + .word 0x1d78b6a + .word 0x80415bfc + .word 0x183b583 + .word 0x800c10b0 + .word 0x2ff + .word 0x800d11b0 + .word 0x10f7bc1 + .word 0x800c39f0 + .word 0x2ff + .word 0x81ebb37d + .word 0x10d6b41 + .word 0x1de0b02 + .word 0x2ff + .word 0x800d3bf0 + .word 0x10f7bc2 + .word 0x3e60801 + .word 0x2ff + .word 0x3ef0801 + .word 0x10d6b42 + .word 0x3e2f001 + .word 0x1c094dc + .word 0x806f13be + .word 0x1c0d71c + .word 0x3e7f001 + .word 0x1c0b59c + .word 0x80000030 + .word 0x18dbdd8 + .word 0x80000030 + .word 0x1d3997d + .word 0x3e2e000 + .word 0x19152be + .word 0x3e7e000 + .word 0x6051bf + .word 0x3e6b000 + .word 0x18ebde8 + .word 0x3efb000 + .word 0x2ff + .word 0x3e29802 + .word 0x2ff + .word 0x3e69802 + .word 0x1f14c8a + .word 0x3e79802 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x3ef9802 + .word 0x2ff + .word 0x81f203bc + .word 0x180bddc + .word 0x80628bfc + .word 0x4005c3 + .word 0x8067dbfc + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80615bfc + .word 0x183bdc3 + .word 0x800c10b0 + .word 0x2ff + .word 0x800d11b0 + .word 0x2ff + .word 0x800c39f0 + .word 0x2ff + .word 0x81ebbb7d + .word 0x2ff + .word 0x1de0b02 + .word 0x2ff + .word 0x800d3bf0 + .word 0x2ff + .word 0x3e60801 + .word 0x2ff + .word 0x3ef0801 + .word 0x2ff + .word 0x3e2f001 + .word 0x1c094dc + .word 0x80000030 + .word 0x1c0df1c + .word 0x3e7f001 + .word 0x1c0bddc + .word 0x80000030 + .word 0x2ff + .word 0x88e0392 + .word 0x1d3997d + .word 0x3e2e000 + .word 0x2ff + .word 0x3e7e000 + .word 0x2ff + .word 0x3e6b800 + .word 0x2ff + .word 0x3efb800 + .word 0x2ff + .word 0x3e29802 + .word 0x2ff + .word 0x3e69802 + .word 0x2ff + .word 0x3e79802 + .word 0x2ff + .word 0x52007216 + .word 0x2ff + .word 0x3ef9802 + .word 0x2ff + .word 0x4000003a + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x81f203bc + .word 0x180bddc + .word 0x80628bfc + .word 0x4005c3 + .word 0x8067dbfc + .word 0x1ce73ea + .word 0x80000030 + .word 0x1d4836a + .word 0x80615bfc + .word 0x183bdc3 + .word 0x800c10b0 + .word 0x2ff + .word 0x800d11b0 + .word 0x10f7bc1 + .word 0x800c39f0 + .word 0x2ff + .word 0x81ebbb7d + .word 0x10d6b41 + .word 0x1de0b02 + .word 0x2ff + .word 0x800d3bf0 + .word 0x10f7bc2 + .word 0x3e60801 + .word 0x2ff + .word 0x3ef0801 + .word 0x10d6b42 + .word 0x3e2f001 + .word 0x1c094dc + .word 0x806f13be + .word 0x1c0df1c + .word 0x3e7f001 + .word 0x1c0bddc + .word 0x80000030 + .word 0x18da518 + .word 0x80000030 + .word 0x1d3997d + .word 0x3e2e000 + .word 0x19052be + .word 0x3e7e000 + .word 0x6051bf + .word 0x3e6b800 + .word 0x18ea528 + .word 0x3efb800 + .word 0x2ff + .word 0x3e29802 + .word 0x2ff + .word 0x3e69802 + .word 0x1f04c8a + .word 0x3e79802 + .word 0x2ff + .word 0x81eb537c + .word 0x2ff + .word 0x3ef9802 + .word 0x2ff + .word 0x81f203bc + .word 0x180a51c + .word 0x806283fc + .word 0x400503 + .word 0x8067c3fc + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80015bfc + .word 0x183a503 + .word 0x800c10b0 + .word 0x2ff + .word 0x800d11b0 + .word 0x2ff + .word 0x800c39f0 + .word 0x2ff + .word 0x81eba37d + .word 0x2ff + .word 0x1de0b02 + .word 0x2ff + .word 0x800d3bf0 + .word 0x2ff + .word 0x3e60801 + .word 0x2ff + .word 0x3ef0801 + .word 0x2ff + .word 0x3e2f001 + .word 0x1c094dc + .word 0x80000030 + .word 0x1c0c71c + .word 0x3e7f001 + .word 0x1c0a51c + .word 0x80000030 + .word 0x2ff + .word 0x88e0392 + .word 0x1d3997d + .word 0x3e2e000 + .word 0x2ff + .word 0x3e7e000 + .word 0x2ff + .word 0x3e6a000 + .word 0x2ff + .word 0x3efa000 + .word 0x2ff + .word 0x3e29802 + .word 0x2ff + .word 0x3e69802 + .word 0x2ff + .word 0x3e79802 + .word 0x2ff + .word 0x520071dd + .word 0x2ff + .word 0x3ef9802 + .word 0x2ff + .word 0x40000001 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x84e0391 + .word 0x2ff + .word 0x800847f2 + .word 0x2ff + .word 0x40000005 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x84e0391 + .word 0x2ff + .word 0x800847b2 + .word 0x2ff + .word 0x80094f32 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x500070fa + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x2e0382 + .word 0x2d6b47 + .word 0x800c73b0 + .word 0x400703 + .word 0x81dd437c + .word 0x2ff + .word 0x81f44b7c + .word 0x2ff + .word 0x81ce4b7c + .word 0x2ff + .word 0x81f84b7c + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x1dda51b + .word 0x80000030 + .word 0x1dd11bc + .word 0x80000030 + .word 0x1dd18bd + .word 0x80000030 + .word 0x1dd244a + .word 0x80000030 + .word 0x1eea528 + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x1e041bf + .word 0x80000030 + .word 0x1f428bc + .word 0x80000030 + .word 0x1f430bd + .word 0x80000030 + .word 0x1d43d0a + .word 0x80000030 + .word 0x408c47 + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x1d48b6a + .word 0x81dd437c + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x81e06f7e + .word 0x2ff + .word 0x81f54b7c + .word 0x2ff + .word 0x81ce4b7c + .word 0x2ff + .word 0x81f94b7c + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x1ddad5b + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x1dd11bc + .word 0x80000030 + .word 0x1d1a2da + .word 0x80000030 + .word 0x1eead68 + .word 0x80000030 + .word 0x1dd18bd + .word 0x80000030 + .word 0x1dd248a + .word 0x80000030 + .word 0x2ff + .word 0x81c05f3c + .word 0x2ff + .word 0x802c067c + .word 0x2ff + .word 0x80000030 + .word 0x1e041bf + .word 0x80000030 + .word 0x1f528bc + .word 0x80000030 + .word 0x1f530bd + .word 0x80000030 + .word 0x1d53d4a + .word 0x80000030 + .word 0x409487 + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x8024067c + .word 0x1d5936a + .word 0x81dd437c + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x81cb537c + .word 0x2ff + .word 0x81e06f7e + .word 0x2ff + .word 0x81f64b7c + .word 0x191a1be + .word 0x81ce4b7c + .word 0x2ff + .word 0x81fa4b7c + .word 0x2ff + .word 0x80015bfc + .word 0x2ff + .word 0x80225bfc + .word 0x1ddb59b + .word 0x80435bfc + .word 0x18c8f0b + .word 0x81e413be + .word 0x2ff + .word 0x80000030 + .word 0x1dd11bc + .word 0x80000030 + .word 0x1eeb5a8 + .word 0x80000030 + .word 0x1d2aada + .word 0x80000030 + .word 0x1dd18bd + .word 0x80000030 + .word 0x1dd244a + .word 0x8064a3fc + .word 0x6051bf + .word 0x81c05f3c + .word 0x19452be + .word 0x802c067c + .word 0x1f44d0a + .word 0x1de0b02 + .word 0x1e041bf + .word 0x1cf1302 + .word 0x1f628bc + .word 0x1d01b02 + .word 0x1f630bd + .word 0x8041c3fc + .word 0x1d63d8a + .word 0x8062c3fc + .word 0x408c47 + .word 0x800d2170 + .word 0x180e71c + .word 0x3e50801 + .word 0x1ff79bf + .word 0x18c0b97 + .word 0x1ff80bf + .word 0x18e1397 + .word 0x1dff78a + .word 0x81f403bc + .word 0x183e703 + .word 0x8024067c + .word 0x1d68b6a + .word 0x81dd437c + .word 0x19f61bd + .word 0x3e4f001 + .word 0x19f70bd + .word 0x81cb537c + .word 0x19fe708 + .word 0x81e06f7e + .word 0x400603 + .word 0x81f74b7c + .word 0x192a9be + .word 0x81ce4b7c + .word 0x1c0a51c + .word 0x81fb4b7c + .word 0x1c0e31c + .word 0x80015bfc + .word 0x1c0c61c + .word 0x80225bfc + .word 0x1ddbddb + .word 0x80435bfc + .word 0x1d3a17d + .word 0x81e413be + .word 0x18c970b + .word 0x3e56000 + .word 0x1dd11bc + .word 0x3e4c000 + .word 0x1eebde8 + .word 0x3e49802 + .word 0x1d1b2da + .word 0x500e205b + .word 0x1dd18bd + .word 0x3e59802 + .word 0x1dd248a + .word 0x8064abfc + .word 0x6051bf + .word 0x81c05f3c + .word 0x19552be + .word 0x802c067c + .word 0x1f54d4a + .word 0x1de0b02 + .word 0x1e041bf + .word 0x1cf1302 + .word 0x1f728bc + .word 0x1d01b02 + .word 0x1f730bd + .word 0x8041cbfc + .word 0x1d73dca + .word 0x8062cbfc + .word 0x409487 + .word 0x800d2170 + .word 0x180e71c + .word 0x3e50801 + .word 0x1ff79bf + .word 0x18c0b97 + .word 0x1ff80bf + .word 0x18e1397 + .word 0x1dff78a + .word 0x81f503bc + .word 0x183e703 + .word 0x8024067c + .word 0x1d7936a + .word 0x81dd437c + .word 0x19f61bd + .word 0x3e4f001 + .word 0x19f70bd + .word 0x81cb537c + .word 0x19fe708 + .word 0x81e06f7e + .word 0x400643 + .word 0x81f44b7c + .word 0x191b1be + .word 0x81ce4b7c + .word 0x1c0ad5c + .word 0x81f84b7c + .word 0x1c0e31c + .word 0x80015bfc + .word 0x1c0ce5c + .word 0x80225bfc + .word 0x1dda51b + .word 0x80435bfc + .word 0x1d3a97d + .word 0x81e413be + .word 0x18c8f0b + .word 0x3e56000 + .word 0x1dd11bc + .word 0x3e4c800 + .word 0x1eea528 + .word 0x3e49802 + .word 0x1d2bada + .word 0x500e203d + .word 0x1dd18bd + .word 0x3e59802 + .word 0x1dd244a + .word 0x8064b3fc + .word 0x6051bf + .word 0x81c05f3c + .word 0x19652be + .word 0x802c067c + .word 0x1f64d8a + .word 0x1de0b02 + .word 0x1e041bf + .word 0x1cf1302 + .word 0x1f428bc + .word 0x1d01b02 + .word 0x1f430bd + .word 0x8041d3fc + .word 0x1d43d0a + .word 0x8062d3fc + .word 0x408c47 + .word 0x800d2170 + .word 0x180e71c + .word 0x3e50801 + .word 0x1ff79bf + .word 0x18c0b97 + .word 0x1ff80bf + .word 0x18e1397 + .word 0x1dff78a + .word 0x81f603bc + .word 0x183e703 + .word 0x8024067c + .word 0x1d48b6a + .word 0x81dd437c + .word 0x19f61bd + .word 0x3e4f001 + .word 0x19f70bd + .word 0x81cb537c + .word 0x19fe708 + .word 0x81e06f7e + .word 0x400683 + .word 0x81f54b7c + .word 0x192b9be + .word 0x81ce4b7c + .word 0x1c0b59c + .word 0x81f94b7c + .word 0x1c0e31c + .word 0x80015bfc + .word 0x1c0d69c + .word 0x80225bfc + .word 0x1ddad5b + .word 0x80435bfc + .word 0x1d3b17d + .word 0x81e413be + .word 0x18c970b + .word 0x3e56000 + .word 0x1dd11bc + .word 0x3e4d000 + .word 0x1eead68 + .word 0x3e49802 + .word 0x1d1a2da + .word 0x500e201f + .word 0x1dd18bd + .word 0x3e59802 + .word 0x1dd248a + .word 0x8064bbfc + .word 0x6051bf + .word 0x81c05f3c + .word 0x19752be + .word 0x802c067c + .word 0x1f74dca + .word 0x1de0b02 + .word 0x1e041bf + .word 0x1cf1302 + .word 0x1f528bc + .word 0x1d01b02 + .word 0x1f530bd + .word 0x8041dbfc + .word 0x1d53d4a + .word 0x8062dbfc + .word 0x409487 + .word 0x800d2170 + .word 0x180e71c + .word 0x3e50801 + .word 0x1ff79bf + .word 0x18c0b97 + .word 0x1ff80bf + .word 0x18e1397 + .word 0x1dff78a + .word 0x81f703bc + .word 0x183e703 + .word 0x8024067c + .word 0x1d5936a + .word 0x81dd437c + .word 0x19f61bd + .word 0x3e4f001 + .word 0x19f70bd + .word 0x81cb537c + .word 0x19fe708 + .word 0x81e06f7e + .word 0x4006c3 + .word 0x81f64b7c + .word 0x191a1be + .word 0x81ce4b7c + .word 0x1c0bddc + .word 0x81fa4b7c + .word 0x1c0e31c + .word 0x80015bfc + .word 0x1c0dedc + .word 0x80225bfc + .word 0x1ddb59b + .word 0x80435bfc + .word 0x1d3b97d + .word 0x81e413be + .word 0x18c8f0b + .word 0x3e56000 + .word 0x1dd11bc + .word 0x3e4d800 + .word 0x1eeb5a8 + .word 0x3e49802 + .word 0x1d2aada + .word 0x520e2789 + .word 0x1dd18bd + .word 0x3e59802 + .word 0x1dd244a + .word 0x82e0391 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x5000710b + .word 0x2ff + .word 0x800a57f2 + .word 0x2ff + .word 0x80084772 + .word 0x2ff + .word 0x40000031 + .word 0x2ff + .word 0x80094df2 + .word 0x2ff + .word 0xb0003d0 + .word 0x2ff + .word 0xb0103d1 + .word 0x2ff + .word 0xb0203d2 + .word 0x2ff + .word 0xb0303d3 + .word 0x2ff + .word 0xb0403d4 + .word 0x2ff + .word 0xb0503d5 + .word 0x2ff + .word 0xb0603d6 + .word 0x2ff + .word 0xb0703d7 + .word 0x2ff + .word 0xb0803d8 + .word 0x2ff + .word 0xb0903d9 + .word 0x2ff + .word 0xb0a03da + .word 0x2ff + .word 0xb0b03db + .word 0x2ff + .word 0xb0c03dc + .word 0x2ff + .word 0xb0d03dd + .word 0x2ff + .word 0xb0e03de + .word 0x2ff + .word 0xb0f03df + .word 0x2ff + .word 0x3e003e0 + .word 0x2ff + .word 0x3e00be1 + .word 0x2ff + .word 0x3e013e2 + .word 0x2ff + .word 0x3e01be3 + .word 0x2ff + .word 0x3e023e4 + .word 0x2ff + .word 0x3e02be5 + .word 0x2ff + .word 0x3e033e6 + .word 0x2ff + .word 0x3e03be7 + .word 0x2ff + .word 0x3e043e8 + .word 0x2ff + .word 0x3e04be9 + .word 0x2ff + .word 0x3e053ea + .word 0x2ff + .word 0x3e05beb + .word 0x2ff + .word 0x3e063ec + .word 0x2ff + .word 0x3e06bed + .word 0x2ff + .word 0x3e073ee + .word 0x2ff + .word 0x3e07bef + .word 0x2ff + .word 0x3e083f0 + .word 0x2ff + .word 0x3e08bf1 + .word 0x2ff + .word 0x3e093f2 + .word 0x2ff + .word 0x3e09bf3 + .word 0x2ff + .word 0x3e0a3f4 + .word 0x2ff + .word 0x3e0abf5 + .word 0x2ff + .word 0x3e0b3f6 + .word 0x2ff + .word 0x3e0bbf7 + .word 0x2ff + .word 0x3e0c3f8 + .word 0x2ff + .word 0x3e0cbf9 + .word 0x2ff + .word 0x3e0d3fa + .word 0x2ff + .word 0x3e0dbfb + .word 0x2ff + .word 0x3e0e3fc + .word 0x2ff + .word 0x3e0ebfd + .word 0x2ff + .word 0x3e0f3fe + .word 0x400002ff + .word 0x3e0fbff + .word 0x2ff + .word 0x82e0391 + .word 0x2ff + .word 0x240382 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x400483 + .word 0x800c73b0 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x81f74b7c + .word 0x2ff + .word 0x81dd437c + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x81f54b7c + .word 0x2ff + .word 0x81fa4b7c + .word 0x1ddbddb + .word 0x80000030 + .word 0x1dd11bc + .word 0x80000030 + .word 0x1fd18bd + .word 0x80000030 + .word 0x1fd244a + .word 0x80000030 + .word 0x1d5bde8 + .word 0x80000030 + .word 0x24bdc3 + .word 0x80000030 + .word 0x1e041bf + .word 0x80000030 + .word 0x408c47 + .word 0x80000030 + .word 0x1f728bc + .word 0x80000030 + .word 0x1f730bd + .word 0x80000030 + .word 0x1d73dca + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x1d78b6a + .word 0x80000030 + .word 0x1d1bb9a + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x10d6b41 + .word 0x80000030 + .word 0x1ce73aa + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x10d6b42 + .word 0x80000030 + .word 0x10e7381 + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x18d89bc + .word 0x80000030 + .word 0x10e7382 + .word 0x80000030 + .word 0x2ff + .word 0x81f84b7c + .word 0x191bc8a + .word 0x81dd437c + .word 0x2ff + .word 0x806e13be + .word 0x2ff + .word 0x81f64b7c + .word 0x2ff + .word 0x81fb4b7c + .word 0x1ddc61b + .word 0x80000030 + .word 0x1dd11bc + .word 0x81eb537c + .word 0x1fd18bd + .word 0x80000030 + .word 0x1fd244a + .word 0x80000030 + .word 0x1d6c628 + .word 0x80000030 + .word 0x24c603 + .word 0x80000030 + .word 0x1e041bf + .word 0x80000030 + .word 0x408c47 + .word 0x80000030 + .word 0x1f828bc + .word 0x80000030 + .word 0x1f830bd + .word 0x8041d3fc + .word 0x1d83e0a + .word 0x80000030 + .word 0x19752be + .word 0x8062d3fc + .word 0x6051bf + .word 0x80000030 + .word 0x180949c + .word 0x18c0b97 + .word 0x1f74e4a + .word 0x80000030 + .word 0x1d88b6a + .word 0x18f1397 + .word 0x1d1c39a + .word 0x80000030 + .word 0x1839483 + .word 0x81f903bc + .word 0x19f61bd + .word 0x80000030 + .word 0x10d6b41 + .word 0x80000030 + .word 0x1ce73aa + .word 0x80000030 + .word 0x19f78bd + .word 0x80000030 + .word 0x400683 + .word 0x80225bfc + .word 0x19f9488 + .word 0x80015bfc + .word 0x10d6b42 + .word 0x80435bfc + .word 0x10e7381 + .word 0x8064bbfc + .word 0x1c0ce5c + .word 0x1cb1302 + .word 0x1c0941c + .word 0x1de0b02 + .word 0x1c0d31c + .word 0x1d41b02 + .word 0x18d89bc + .word 0x8066abfc + .word 0x10e7382 + .word 0x800d2170 + .word 0x1d3c97d + .word 0x81f74b7c + .word 0x191c48a + .word 0x81dd437c + .word 0x1ff59bf + .word 0x806e13be + .word 0x1ffa0bf + .word 0x81f54b7c + .word 0x1dff78a + .word 0x81fa4b7c + .word 0x1ddbddb + .word 0x800c31b0 + .word 0x1dd11bc + .word 0x81eb537c + .word 0x1fd18bd + .word 0x800d31f0 + .word 0x1fd244a + .word 0x3e46000 + .word 0x1d5bde8 + .word 0x3e4f001 + .word 0x24bdc3 + .word 0x3e49802 + .word 0x1e041bf + .word 0x3e58000 + .word 0x408c47 + .word 0x3e50801 + .word 0x1f728bc + .word 0x3e59802 + .word 0x1f730bd + .word 0x8041dbfc + .word 0x1d73dca + .word 0x3e66000 + .word 0x19852be + .word 0x8062dbfc + .word 0x6051bf + .word 0x3e6f001 + .word 0x180949c + .word 0x18c0b97 + .word 0x1f84e4a + .word 0x3e69802 + .word 0x1d78b6a + .word 0x18f1397 + .word 0x1d1bb9a + .word 0x3e78000 + .word 0x1839483 + .word 0x81f903bc + .word 0x19f61bd + .word 0x3e70801 + .word 0x10d6b41 + .word 0x500e206d + .word 0x1ce73aa + .word 0x3e79802 + .word 0x19f78bd + .word 0x80000030 + .word 0x4006c3 + .word 0x80225bfc + .word 0x19f9488 + .word 0x80015bfc + .word 0x10d6b42 + .word 0x80435bfc + .word 0x10e7381 + .word 0x8064c3fc + .word 0x1c0ce5c + .word 0x1cb1302 + .word 0x1c0941c + .word 0x1de0b02 + .word 0x1c0db1c + .word 0x1d41b02 + .word 0x18d89bc + .word 0x8066b3fc + .word 0x10e7382 + .word 0x800d2170 + .word 0x1d3c97d + .word 0x81f84b7c + .word 0x191bc8a + .word 0x81dd437c + .word 0x1ff59bf + .word 0x806e13be + .word 0x1ffa0bf + .word 0x81f64b7c + .word 0x1dff78a + .word 0x81fb4b7c + .word 0x1ddc61b + .word 0x800c31b0 + .word 0x1dd11bc + .word 0x81eb537c + .word 0x1fd18bd + .word 0x800d31f0 + .word 0x1fd244a + .word 0x3e46000 + .word 0x1d6c628 + .word 0x3e4f001 + .word 0x24c603 + .word 0x3e49802 + .word 0x1e041bf + .word 0x3e58000 + .word 0x408c47 + .word 0x3e50801 + .word 0x1f828bc + .word 0x3e59802 + .word 0x1f830bd + .word 0x8041d3fc + .word 0x1d83e0a + .word 0x3e66000 + .word 0x19752be + .word 0x8062d3fc + .word 0x6051bf + .word 0x3e6f001 + .word 0x180949c + .word 0x18c0b97 + .word 0x1f74e4a + .word 0x3e69802 + .word 0x1d88b6a + .word 0x18f1397 + .word 0x1d1c39a + .word 0x3e78000 + .word 0x1839483 + .word 0x81f903bc + .word 0x19f61bd + .word 0x3e70801 + .word 0x10d6b41 + .word 0x500e2049 + .word 0x1ce73aa + .word 0x3e79802 + .word 0x19f78bd + .word 0x80000030 + .word 0x400683 + .word 0x80225bfc + .word 0x19f9488 + .word 0x80015bfc + .word 0x10d6b42 + .word 0x80435bfc + .word 0x10e7381 + .word 0x8064bbfc + .word 0x1c0ce5c + .word 0x1cb1302 + .word 0x1c0941c + .word 0x1de0b02 + .word 0x1c0d31c + .word 0x1d41b02 + .word 0x18d89bc + .word 0x8066abfc + .word 0x10e7382 + .word 0x800d2170 + .word 0x1d3c97d + .word 0x81f74b7c + .word 0x191c48a + .word 0x81dd437c + .word 0x1ff59bf + .word 0x806e13be + .word 0x1ffa0bf + .word 0x81f54b7c + .word 0x1dff78a + .word 0x81fa4b7c + .word 0x1ddbddb + .word 0x800c31b0 + .word 0x1dd11bc + .word 0x81eb537c + .word 0x1fd18bd + .word 0x800d31f0 + .word 0x1fd244a + .word 0x3e46000 + .word 0x1d5bde8 + .word 0x3e4f001 + .word 0x24bdc3 + .word 0x3e49802 + .word 0x1e041bf + .word 0x3e58000 + .word 0x408c47 + .word 0x3e50801 + .word 0x1f728bc + .word 0x3e59802 + .word 0x1f730bd + .word 0x8041dbfc + .word 0x1d73dca + .word 0x3e66000 + .word 0x19852be + .word 0x8062dbfc + .word 0x6051bf + .word 0x3e6f001 + .word 0x180949c + .word 0x18c0b97 + .word 0x1f84e4a + .word 0x3e69802 + .word 0x1d78b6a + .word 0x18f1397 + .word 0x1d1bb9a + .word 0x3e78000 + .word 0x1839483 + .word 0x81f903bc + .word 0x19f61bd + .word 0x3e70801 + .word 0x10d6b41 + .word 0x500e2025 + .word 0x1ce73aa + .word 0x3e79802 + .word 0x19f78bd + .word 0x80000030 + .word 0x4006c3 + .word 0x80225bfc + .word 0x19f9488 + .word 0x80015bfc + .word 0x10d6b42 + .word 0x80435bfc + .word 0x10e7381 + .word 0x8064c3fc + .word 0x1c0ce5c + .word 0x1cb1302 + .word 0x1c0941c + .word 0x1de0b02 + .word 0x1c0db1c + .word 0x1d41b02 + .word 0x18d89bc + .word 0x8066b3fc + .word 0x10e7382 + .word 0x800d2170 + .word 0x1d3c97d + .word 0x81f84b7c + .word 0x191bc8a + .word 0x81dd437c + .word 0x1ff59bf + .word 0x806e13be + .word 0x1ffa0bf + .word 0x81f64b7c + .word 0x1dff78a + .word 0x81fb4b7c + .word 0x1ddc61b + .word 0x800c31b0 + .word 0x1dd11bc + .word 0x81eb537c + .word 0x1fd18bd + .word 0x800d31f0 + .word 0x1fd244a + .word 0x3e46000 + .word 0x1d6c628 + .word 0x3e4f001 + .word 0x24c603 + .word 0x3e49802 + .word 0x1e041bf + .word 0x3e58000 + .word 0x408c47 + .word 0x3e50801 + .word 0x1f828bc + .word 0x3e59802 + .word 0x1f830bd + .word 0x8041d3fc + .word 0x1d83e0a + .word 0x3e66000 + .word 0x19752be + .word 0x8062d3fc + .word 0x6051bf + .word 0x3e6f001 + .word 0x180949c + .word 0x18c0b97 + .word 0x1f74e4a + .word 0x3e69802 + .word 0x1d88b6a + .word 0x18f1397 + .word 0x1d1c39a + .word 0x3e78000 + .word 0x1839483 + .word 0x81f903bc + .word 0x19f61bd + .word 0x3e70801 + .word 0x10d6b41 + .word 0x520e2771 + .word 0x1ce73aa + .word 0x3e79802 + .word 0x19f78bd + .word 0x40000003 + .word 0x2ff + .word 0x80000030 + .word 0x2ff + .word 0x80000030 + .word 0x400002ff + .word 0x80000030 + .word 0x2ff + .word 0x1e10382 + .word 0x2ff + .word 0x8420392 + .word 0x2ff + .word 0x8430393 + .word 0x2ff + .word 0x800167f2 + .word 0x2ff + .word 0x80000efc + .word 0x2ff + .word 0x81e10b3d + .word 0x2ff + .word 0x80021072 + .word 0x2ff + .word 0x80031872 + .word 0x2ff + .word 0xa420392 + .word 0x2ff + .word 0xa430393 + .word 0x400002ff + .word 0x3e00b82 + .word 0x2ff \ No newline at end of file diff --git a/test/goalc/framework/test_runner.cpp b/test/goalc/framework/test_runner.cpp index 2d749c63c..8bfc91c53 100644 --- a/test/goalc/framework/test_runner.cpp +++ b/test/goalc/framework/test_runner.cpp @@ -111,7 +111,7 @@ void runtime_no_kernel_jak1() { const char* argv[argc] = {"", "-fakeiso", "-debug", "-nokernel", "-nosound"}; GameLaunchOptions game_options; game_options.disable_display = true; - exec_runtime(game_options, argc, const_cast(argv)); + exec_runtime(game_options, argc, argv); } void runtime_no_kernel_jak2() { @@ -120,7 +120,7 @@ void runtime_no_kernel_jak2() { GameLaunchOptions game_options; game_options.disable_display = true; game_options.game_version = GameVersion::Jak2; - exec_runtime(game_options, argc, const_cast(argv)); + exec_runtime(game_options, argc, argv); } void runtime_with_kernel_jak1() { @@ -128,7 +128,7 @@ void runtime_with_kernel_jak1() { const char* argv[argc] = {"", "-fakeiso", "-debug", "-nosound"}; GameLaunchOptions game_options; game_options.disable_display = true; - exec_runtime(game_options, argc, const_cast(argv)); + exec_runtime(game_options, argc, argv); } void runtime_with_kernel_jak2() { @@ -137,7 +137,7 @@ void runtime_with_kernel_jak2() { GameLaunchOptions game_options; game_options.disable_display = true; game_options.game_version = GameVersion::Jak2; - exec_runtime(game_options, argc, const_cast(argv)); + exec_runtime(game_options, argc, argv); } void runtime_with_kernel_no_debug_segment() { @@ -145,7 +145,7 @@ void runtime_with_kernel_no_debug_segment() { const char* argv[argc] = {"", "-fakeiso", "-debug-mem", "-nosound"}; GameLaunchOptions game_options; game_options.disable_display = true; - exec_runtime(game_options, argc, const_cast(argv)); + exec_runtime(game_options, argc, argv); } void createDirIfAbsent(const std::string& path) {