From 9d80ada0161f089b13a0cb7581eb66bf44bed77f Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Fri, 26 Jul 2024 11:42:52 -0400 Subject: [PATCH] [jak3] Fix eye slot assignment and textures (#3603) I found two issues with Jak 3 eyes. The first was simple - we were missing a `-pc` texture upload in `texture.gc` for `pris2` textures, which has eye textures for a few characters, like torn or damas. The second was a little more annoying. Unlike jak 2 and jak 1, jak 3 can dynamically assign eye slots when merc models are loaded. This involves modifying eye data to tell the eye renderer where to render, and modifying the merc model's adgif shaders to point to the correct eye texture. The modification to the merc adgif shader is problematic since our PC port of merc assumes this slot is constant. My solution here was to bypass this whole slot system entirely for jak 3. I modified the GOAL eye renderer to tell the c++ eye renderer the name of the merc-ctrl containing the eye. Then, the PC C++ Merc renderer can just look up the merc-ctrl by name. To make this fit nicely in the existing memory layout, I used a 64-bit fnv hash of the name. (which honestly is how we should have handled a lot of other texture/model names stuff...) Unrelated fix to Overlord2 so it handles the case where file size changes after the game starts, I had this in jak2/jak1 and forgot it for jak 3. --- common/util/fnv.h | 19 +++++++++++ game/graphics/opengl_renderer/EyeRenderer.cpp | 34 +++++++++++++++---- game/graphics/opengl_renderer/EyeRenderer.h | 6 +++- .../opengl_renderer/foreground/Merc2.cpp | 31 +++++++++++++---- .../opengl_renderer/foreground/Merc2.h | 4 ++- game/overlord/jak3/iso_cd.cpp | 10 +++++- goal_src/jak3/engine/gfx/foreground/eye.gc | 33 ++++++++++++++++++ goal_src/jak3/engine/gfx/texture/texture.gc | 2 +- 8 files changed, 121 insertions(+), 18 deletions(-) create mode 100644 common/util/fnv.h diff --git a/common/util/fnv.h b/common/util/fnv.h new file mode 100644 index 0000000000..7ec0320db0 --- /dev/null +++ b/common/util/fnv.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "common/common_types.h" + +inline u64 fnv64(const void* data, u64 len) { + u64 ret = 0xcbf29ce484222325; + const auto* ptr = (const u8*)data; + for (u64 i = 0; i < len; i++) { + ret = 1099511628211 * (((u64)*ptr) ^ ret); + ptr++; + } + return ret; +} + +inline u64 fnv64(const std::string& str) { + return fnv64(str.data(), str.length()); +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/EyeRenderer.cpp b/game/graphics/opengl_renderer/EyeRenderer.cpp index 10d74cd9ab..c4cca5c2e7 100644 --- a/game/graphics/opengl_renderer/EyeRenderer.cpp +++ b/game/graphics/opengl_renderer/EyeRenderer.cpp @@ -186,7 +186,7 @@ EyeRenderer::SpriteInfo decode_sprite(const DmaTransfer& dma) { memcpy(&result.a, dma.data + 16 + 12, 1); // a // uv0 - memcpy(&result.uv0[0], &dma.data[32], 8); + memcpy(&result.uv0, &dma.data[32], 8); // xyz0 memcpy(&result.xyz0[0], &dma.data[48], 12); @@ -236,8 +236,11 @@ std::vector EyeRenderer::get_draws(DmaFollower& dma bool using_64 = false; { auto draw0 = read_eye_draw(dma); - ASSERT(draw0.sprite.uv0[0] == 0); - ASSERT(draw0.sprite.uv0[1] == 0); + // ASSERT(draw0.sprite.uv0[0] == 0); + // ASSERT(draw0.sprite.uv0[1] == 0); + // printf("hashed name is 0x%x 0x%x\n", draw0.sprite.uv0[0], draw0.sprite.uv0[1]); + l_draw.fnv_name_hash = draw0.sprite.uv0; + r_draw.fnv_name_hash = draw0.sprite.uv0; ASSERT(draw0.sprite.uv1[0] == 0); ASSERT(draw0.sprite.uv1[1] == 0); if (draw0.scissor.y1 - draw0.scissor.y0 == 63) { @@ -509,7 +512,9 @@ void EyeRenderer::run_gpu(const std::vector& draws, buffer_idx = 0; for (size_t draw_idx = 0; draw_idx < draws.size(); draw_idx++) { const auto& draw = draws[draw_idx]; - const auto& out_tex = m_gpu_eye_textures[draw.tex_slot()]; + auto& out_tex = m_gpu_eye_textures[draw.tex_slot()]; + out_tex.fnv_name_hash = draw.fnv_name_hash; + out_tex.lr = draw.lr; // first, the clear float clear[4] = {0, 0, 0, 0}; @@ -574,15 +579,30 @@ std::optional EyeRenderer::lookup_eye_texture(u8 eye_id) { } } +std::optional EyeRenderer::lookup_eye_texture_hash(u64 hash, bool lr) { + for (auto& slot : m_gpu_eye_textures) { + if (slot.fnv_name_hash == hash && slot.lr == lr) { + auto* gpu_tex = slot.gpu_tex; + if (gpu_tex) { + return gpu_tex->gpu_textures.at(0).gl; + } else { + fmt::print("lookup eye failed for {} (1)\n", hash); + return {}; + } + } + } + fmt::print("lookup eye failed for {} (2)\n", hash); + return {}; +} + ////////////////////// // DMA Decode ////////////////////// std::string EyeRenderer::SpriteInfo::print() const { std::string result; - result += - fmt::format("a: {:x} uv: ({}, {}), ({}, {}) xyz: ({}, {}, {}), ({}, {}, {})", a, uv0[0], - uv0[1], uv1[0], uv1[1], xyz0[0], xyz0[1], xyz0[2], xyz1[0], xyz1[1], xyz1[2]); + result += fmt::format("a: {:x} uv: ({}), ({}, {}) xyz: ({}, {}, {}), ({}, {}, {})", a, uv0, + uv1[0], uv1[1], xyz0[0], xyz0[1], xyz0[2], xyz1[0], xyz1[1], xyz1[2]); return result; } diff --git a/game/graphics/opengl_renderer/EyeRenderer.h b/game/graphics/opengl_renderer/EyeRenderer.h index b9c24c504d..9a1d151826 100644 --- a/game/graphics/opengl_renderer/EyeRenderer.h +++ b/game/graphics/opengl_renderer/EyeRenderer.h @@ -22,10 +22,11 @@ class EyeRenderer : public BucketRenderer { void handle_eye_dma2(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof); std::optional lookup_eye_texture(u8 eye_id); + std::optional lookup_eye_texture_hash(u64 hash, bool lr); struct SpriteInfo { u8 a; - u32 uv0[2]; + u64 uv0; // stores hashed name of merc-ctrl that reads this eye. u32 uv1[2]; u32 xyz0[3]; u32 xyz1[3]; @@ -53,6 +54,8 @@ class EyeRenderer : public BucketRenderer { GpuTexture* gpu_tex = nullptr; u32 tbp; FramebufferTexturePair fb; + u64 fnv_name_hash = 0; + bool lr = false; // note: eye texture increased to 128x128 (originally 32x32) here. GpuEyeTex() : fb(128, 128, GL_UNSIGNED_INT_8_8_8_8_REV) {} @@ -65,6 +68,7 @@ class EyeRenderer : public BucketRenderer { GLuint m_gl_vertex_buffer; struct SingleEyeDraws { + u64 fnv_name_hash = 0; int lr; int pair; bool using_64 = false; diff --git a/game/graphics/opengl_renderer/foreground/Merc2.cpp b/game/graphics/opengl_renderer/foreground/Merc2.cpp index 896c3203f6..b359f78707 100644 --- a/game/graphics/opengl_renderer/foreground/Merc2.cpp +++ b/game/graphics/opengl_renderer/foreground/Merc2.cpp @@ -7,6 +7,7 @@ #endif #include "common/global_profiler/GlobalProfiler.h" +#include "common/util/fnv.h" #include "game/graphics/opengl_renderer/EyeRenderer.h" #include "game/graphics/opengl_renderer/background/background_common.h" @@ -54,6 +55,8 @@ std::mutex g_merc_data_mutex; Merc2::Merc2(ShaderLibrary& shaders, const std::vector* anim_slot_array) : m_anim_slot_array(anim_slot_array) { + ASSERT(fnv64("the quick brown fox jumps over the lazy dog") == 0x7404cea13ff89bb0); + // Set up main vertex array. This will point to the data stored in the .FR3 level file, and will // be uploaded to the GPU by the Loader. glGenVertexArrays(1, &m_vao); @@ -612,6 +615,8 @@ void Merc2::handle_pc_model(const DmaTransfer& setup, u32 lights = alloc_lights(current_lights); stats->num_lights++; + u64 hash = fnv64(model->name); + // loop over effects, creating draws for each for (size_t ei = 0; ei < model->effects.size(); ei++) { // game has disabled it? @@ -636,7 +641,7 @@ void Merc2::handle_pc_model(const DmaTransfer& setup, // do fixed draws: for (auto& fdraw : effect.mod.fix_draw) { alloc_normal_draw(fdraw, ignore_alpha, lev_bucket, first_bone, lights, uses_water, - model_disables_fog); + model_disables_fog, hash); if (should_envmap) { try_alloc_envmap_draw(fdraw, effect.envmap_mode, effect.envmap_texture, lev_bucket, fade_buffer + 4 * ei, first_bone, lights, uses_water); @@ -646,7 +651,7 @@ void Merc2::handle_pc_model(const DmaTransfer& setup, // do mod draws for (auto& mdraw : effect.mod.mod_draw) { auto n = alloc_normal_draw(mdraw, ignore_alpha, lev_bucket, first_bone, lights, uses_water, - model_disables_fog); + model_disables_fog, hash); // modify the draw, set the mod flag and point it to the opengl buffer n->flags |= MOD_VTX; n->mod_vtx_buffer = mod_opengl_buffers[ei]; @@ -668,7 +673,7 @@ void Merc2::handle_pc_model(const DmaTransfer& setup, fade_buffer + 4 * ei, first_bone, lights, uses_water); } alloc_normal_draw(draw, ignore_alpha, lev_bucket, first_bone, lights, uses_water, - model_disables_fog); + model_disables_fog, hash); } } } @@ -1056,6 +1061,7 @@ Merc2::Draw* Merc2::try_alloc_envmap_draw(const tfrag3::MercDraw& mdraw, draw->first_index = mdraw.first_index; draw->index_count = mdraw.index_count; draw->mode = envmap_mode; + draw->hash = 0; if (jak1_water_mode) { draw->mode.enable_ab(); draw->mode.disable_depth_write(); @@ -1076,12 +1082,14 @@ Merc2::Draw* Merc2::alloc_normal_draw(const tfrag3::MercDraw& mdraw, u32 first_bone, u32 lights, bool jak1_water_mode, - bool disable_fog) { + bool disable_fog, + u64 hash) { Draw* draw = &lev_bucket->draws[lev_bucket->next_free_draw++]; draw->flags = 0; draw->first_index = mdraw.first_index; draw->index_count = mdraw.index_count; draw->mode = mdraw.mode; + draw->hash = hash; if (jak1_water_mode) { draw->mode.set_ab(true); draw->mode.disable_depth_write(); @@ -1247,10 +1255,19 @@ void Merc2::do_draws(const Draw* draw_array, if (draw.texture < (int)lev->textures.size() && draw.texture >= 0) { glBindTexture(GL_TEXTURE_2D, lev->textures.at(draw.texture)); } else if ((draw.texture & 0xffffff00) == 0xefffff00) { - auto maybe_eye = render_state->eye_renderer->lookup_eye_texture(draw.texture & 0xff); - if (maybe_eye) { - glBindTexture(GL_TEXTURE_2D, *maybe_eye); + if (render_state->version >= GameVersion::Jak3) { + auto maybe_eye = + render_state->eye_renderer->lookup_eye_texture_hash(draw.hash, (draw.texture & 1)); + if (maybe_eye) { + glBindTexture(GL_TEXTURE_2D, *maybe_eye); + } + } else { + auto maybe_eye = render_state->eye_renderer->lookup_eye_texture(draw.texture & 0xff); + if (maybe_eye) { + glBindTexture(GL_TEXTURE_2D, *maybe_eye); + } } + use_mipmaps_for_filtering = false; } else if (draw.texture < 0) { int slot = -(draw.texture + 1); diff --git a/game/graphics/opengl_renderer/foreground/Merc2.h b/game/graphics/opengl_renderer/foreground/Merc2.h index 166d7d05af..9705907b66 100644 --- a/game/graphics/opengl_renderer/foreground/Merc2.h +++ b/game/graphics/opengl_renderer/foreground/Merc2.h @@ -192,6 +192,7 @@ class Merc2 { u8 fade[4]; // no strip hack for custom models u8 no_strip; + u64 hash; }; struct LevelDrawBucket { @@ -213,7 +214,8 @@ class Merc2 { u32 first_bone, u32 lights, bool jak1_water_mode, - bool disable_fog); + bool disable_fog, + u64 hash); Draw* try_alloc_envmap_draw(const tfrag3::MercDraw& mdraw, const DrawMode& envmap_mode, diff --git a/game/overlord/jak3/iso_cd.cpp b/game/overlord/jak3/iso_cd.cpp index a10467fb86..4d731df8ab 100644 --- a/game/overlord/jak3/iso_cd.cpp +++ b/game/overlord/jak3/iso_cd.cpp @@ -381,7 +381,15 @@ ISOFileDef* CISOCDFileSystem::FindIN(const jak3::ISOName* name) { * Get the length of a file, in bytes. */ int CISOCDFileSystem::GetLength(const jak3::ISOFileDef* file) { - return file->length; + // return file->length; + lg::info("getlength"); + file_util::assert_file_exists(file->full_path.c_str(), "CISOCDFileSystem GetLength"); + FILE* fp = file_util::open_file(file->full_path.c_str(), "rb"); + ASSERT(fp); + fseek(fp, 0, SEEK_END); + uint32_t len = ftell(fp); + fclose(fp); + return len; } /*! diff --git a/goal_src/jak3/engine/gfx/foreground/eye.gc b/goal_src/jak3/engine/gfx/foreground/eye.gc index 7c8458ec31..fd92bd5bc3 100644 --- a/goal_src/jak3/engine/gfx/foreground/eye.gc +++ b/goal_src/jak3/engine/gfx/foreground/eye.gc @@ -7,6 +7,28 @@ ;; DECOMP BEGINS +;; og:preserve-this +(defun fnv64 ((data pointer) (length int)) + "64-bit hash for strings. This is extremely unlikely to have collisions for all strings + in the game. (modern ND games rely on this and use this hash function as unique ID's for + strings." + (let* ((ret (the uint #xcbf29ce484222325)) + (ptr (the (pointer uint8) data)) + (end (&+ ptr length))) + (while (!= ptr end) + (set! ret (imul64 (logxor (-> ptr) ret) 1099511628211)) + (&+! ptr 1) + ) + ret + ) + ) + +(defun fnv64-string ((str string)) + (fnv64 (-> str data) (length str)) + ) + +(define *current-eye-merc-ctrl-name* (the string #f)) + (define *eye-work* (new 'static 'eye-work :sprite-tmpl (new 'static 'dma-gif-packet :dma-vif (new 'static 'dma-packet @@ -137,6 +159,10 @@ (set! (-> (the-as (inline-array vector4w) v1-25) 1 quad) (-> *eye-work* sprite-tmpl quad 1)) (set-vector! (-> (the-as (inline-array vector4w) v1-25) 2) 128 128 128 128) (set-vector! (-> (the-as (inline-array vector4w) v1-25) 3) 0 0 0 0) + ;; og:preserve-this: stash the hashed name of the merc-ctrl that will read these eyes: + (set! (-> (the (pointer uint64) v1-25) 6) + (fnv64-string *current-eye-merc-ctrl-name*) + ) (set-vector! (-> (the-as (inline-array vector4w) v1-25) 4) (* s4-0 16) (the-as int (* s3-0 16)) #xffffff 0) (set-vector! (-> (the-as (inline-array vector4w) v1-25) 5) 0 0 0 0) (set-vector! @@ -425,6 +451,12 @@ (set! (-> (the-as (inline-array vector4w) v1-25) 1 quad) (-> *eye-work* sprite-tmpl quad 1)) (set-vector! (-> (the-as (inline-array vector4w) v1-25) 2) 128 128 128 128) (set-vector! (-> (the-as (inline-array vector4w) v1-25) 3) 0 0 0 0) + + ;; og:preserve-this: stash the hashed name of the merc-ctrl that will read these eyes: + (set! (-> (the (pointer uint64) v1-25) 6) + (fnv64-string *current-eye-merc-ctrl-name*) + ) + (set-vector! (-> (the-as (inline-array vector4w) v1-25) 4) (* s4-0 16) (the-as int (* s3-0 16)) #xffffff 0) (set-vector! (-> (the-as (inline-array vector4w) v1-25) 5) 0 0 0 0) (set-vector! @@ -680,6 +712,7 @@ (logtest? (-> (the-as process-drawable v1-7) skel status) (joint-control-status eye-anim)) (logtest? (-> (the-as process-drawable v1-7) draw status) (draw-control-status on-screen)) ) + (set! *current-eye-merc-ctrl-name* (-> (the process-drawable v1-7) draw mgeo name)) (when (-> s5-0 shaders) (when (not (paused?)) (cond diff --git a/goal_src/jak3/engine/gfx/texture/texture.gc b/goal_src/jak3/engine/gfx/texture/texture.gc index b94275c493..42b0e92c5e 100644 --- a/goal_src/jak3/engine/gfx/texture/texture.gc +++ b/goal_src/jak3/engine/gfx/texture/texture.gc @@ -1383,7 +1383,7 @@ (+! (-> lev upload-size 8) (upload-vram-pages pool (-> pool segment-common) a2-1 (tex-upload-mode seg0-1-2) bucket) ) - (set! (-> lev upload-size 6) (upload-vram-pages-pris + (set! (-> lev upload-size 6) (upload-vram-pages-pris-pc pool (-> pool segment-common) a2-1