[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.
This commit is contained in:
water111 2024-07-26 11:42:52 -04:00 committed by GitHub
parent e81431bd21
commit 9d80ada016
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 121 additions and 18 deletions

19
common/util/fnv.h Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include <string>
#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());
}

View file

@ -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::SingleEyeDraws> 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<SingleEyeDraws>& 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<u64> EyeRenderer::lookup_eye_texture(u8 eye_id) {
}
}
std::optional<u64> 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;
}

View file

@ -22,10 +22,11 @@ class EyeRenderer : public BucketRenderer {
void handle_eye_dma2(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof);
std::optional<u64> lookup_eye_texture(u8 eye_id);
std::optional<u64> 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;

View file

@ -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<GLuint>* 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);

View file

@ -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,

View file

@ -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;
}
/*!

View file

@ -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

View file

@ -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