mirror of
https://github.com/open-goal/jak-project
synced 2026-06-05 19:28:31 -04:00
9d80ada016
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.
615 lines
20 KiB
C++
615 lines
20 KiB
C++
#include "EyeRenderer.h"
|
|
|
|
#include "common/util/FileUtil.h"
|
|
|
|
#include "game/graphics/opengl_renderer/AdgifHandler.h"
|
|
|
|
#include "third-party/imgui/imgui.h"
|
|
|
|
/////////////////////////
|
|
// Bucket Renderer
|
|
/////////////////////////
|
|
EyeRenderer::EyeRenderer(const std::string& name, int id) : BucketRenderer(name, id) {}
|
|
|
|
void EyeRenderer::init_textures(TexturePool& texture_pool, GameVersion version) {
|
|
// set up eyes
|
|
for (int pair_idx = 0; pair_idx < NUM_EYE_PAIRS; pair_idx++) {
|
|
for (int lr = 0; lr < 2; lr++) {
|
|
u32 tidx = pair_idx * 2 + lr;
|
|
|
|
u32 tbp = pair_idx * 2 + lr;
|
|
switch (version) {
|
|
case GameVersion::Jak1:
|
|
tbp += EYE_BASE_BLOCK_JAK1;
|
|
break;
|
|
case GameVersion::Jak2:
|
|
// NOTE: using jak 1's address because jak 2's breaks some ocean stuff.
|
|
// this is a little suspicious, I think we're possibly just getting lucky here.
|
|
tbp += EYE_BASE_BLOCK_JAK1;
|
|
break;
|
|
case GameVersion::Jak3:
|
|
// for jak 3, go back to using the right TBP.
|
|
tbp += EYE_BASE_BLOCK_JAK3;
|
|
break;
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
TextureInput in;
|
|
in.gpu_texture = m_gpu_eye_textures[tidx].fb.texture();
|
|
in.w = 32;
|
|
in.h = 32;
|
|
in.debug_page_name = "PC-EYES";
|
|
in.debug_name = fmt::format("{}-eye-gpu-{}", lr ? "left" : "right", pair_idx);
|
|
in.id = texture_pool.allocate_pc_port_texture(version);
|
|
m_gpu_eye_textures[tidx].gpu_tex = texture_pool.give_texture_and_load_to_vram(in, tbp);
|
|
m_gpu_eye_textures[tidx].tbp = tbp;
|
|
}
|
|
}
|
|
|
|
// set up vertices for GPU mode
|
|
glGenVertexArrays(1, &m_vao);
|
|
glBindVertexArray(m_vao);
|
|
glGenBuffers(1, &m_gl_vertex_buffer);
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_gl_vertex_buffer);
|
|
glBufferData(GL_ARRAY_BUFFER, VTX_BUFFER_FLOATS * sizeof(float), nullptr, GL_STREAM_DRAW);
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, // location 0 in the shader
|
|
4, // 2 floats per vert
|
|
GL_FLOAT, // floats
|
|
GL_TRUE, // normalized, ignored,
|
|
sizeof(float) * 4, //
|
|
(void*)0 // offset in array
|
|
);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindVertexArray(0);
|
|
}
|
|
|
|
EyeRenderer::~EyeRenderer() {
|
|
glDeleteVertexArrays(1, &m_vao);
|
|
glDeleteBuffers(1, &m_gl_vertex_buffer);
|
|
}
|
|
|
|
void EyeRenderer::render(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
m_debug.clear();
|
|
|
|
// skip if disabled
|
|
if (!m_enabled) {
|
|
while (dma.current_tag_offset() != render_state->next_bucket) {
|
|
dma.read_and_advance();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// jump to bucket
|
|
auto data0 = dma.read_and_advance();
|
|
ASSERT(data0.vif1() == 0);
|
|
ASSERT(data0.vif0() == 0);
|
|
ASSERT(data0.size_bytes == 0);
|
|
|
|
// see if bucket is empty or not
|
|
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;
|
|
}
|
|
|
|
handle_eye_dma2(dma, render_state, prof);
|
|
|
|
while (dma.current_tag_offset() != render_state->next_bucket) {
|
|
auto data = dma.read_and_advance();
|
|
m_debug += fmt::format("dma: {}\n", data.size_bytes);
|
|
}
|
|
}
|
|
|
|
void EyeRenderer::draw_debug_window() {
|
|
ImGui::Text("Time: %.3f ms\n", m_average_time_ms);
|
|
ImGui::Text("Debug:\n%s", m_debug.c_str());
|
|
}
|
|
|
|
//////////////////////
|
|
// DMA Decode
|
|
//////////////////////
|
|
|
|
EyeRenderer::ScissorInfo decode_scissor(const DmaTransfer& dma) {
|
|
ASSERT(dma.vif0() == 0);
|
|
ASSERT(dma.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
ASSERT(dma.size_bytes == 32);
|
|
|
|
GifTag gifTag(dma.data);
|
|
ASSERT(gifTag.nloop() == 1);
|
|
ASSERT(gifTag.eop());
|
|
ASSERT(!gifTag.pre());
|
|
ASSERT(gifTag.flg() == GifTag::Format::PACKED);
|
|
ASSERT(gifTag.nreg() == 1);
|
|
|
|
u8 reg_addr;
|
|
memcpy(®_addr, dma.data + 24, 1);
|
|
ASSERT((GsRegisterAddress)reg_addr == GsRegisterAddress::SCISSOR_1);
|
|
EyeRenderer::ScissorInfo result;
|
|
u64 val;
|
|
memcpy(&val, dma.data + 16, 8);
|
|
GsScissor reg(val);
|
|
result.x0 = reg.x0();
|
|
result.x1 = reg.x1();
|
|
result.y0 = reg.y0();
|
|
result.y1 = reg.y1();
|
|
return result;
|
|
}
|
|
|
|
EyeRenderer::SpriteInfo decode_sprite(const DmaTransfer& dma) {
|
|
/*
|
|
(new 'static 'dma-gif-packet
|
|
:dma-vif (new 'static 'dma-packet
|
|
:dma (new 'static 'dma-tag :qwc #x6 :id (dma-tag-id cnt))
|
|
:vif1 (new 'static 'vif-tag :imm #x6 :cmd (vif-cmd direct) :msk #x1)
|
|
)
|
|
:gif0 (new 'static 'gif-tag64
|
|
:nloop #x1
|
|
:eop #x1
|
|
:pre #x1
|
|
:prim (new 'static 'gs-prim :prim (gs-prim-type sprite) :tme #x1 :fst #x1)
|
|
:nreg #x5
|
|
)
|
|
:gif1 (new 'static 'gif-tag-regs
|
|
:regs0 (gif-reg-id rgbaq)
|
|
:regs1 (gif-reg-id uv)
|
|
:regs2 (gif-reg-id xyz2)
|
|
:regs3 (gif-reg-id uv)
|
|
:regs4 (gif-reg-id xyz2)
|
|
)
|
|
)
|
|
*/
|
|
|
|
ASSERT(dma.vif0() == 0);
|
|
ASSERT(dma.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
ASSERT(dma.size_bytes == 6 * 16);
|
|
|
|
// note: not checking everything here.
|
|
GifTag gifTag(dma.data);
|
|
ASSERT(gifTag.nloop() == 1);
|
|
ASSERT(gifTag.eop());
|
|
ASSERT(gifTag.pre());
|
|
ASSERT(gifTag.flg() == GifTag::Format::PACKED);
|
|
ASSERT(gifTag.nreg() == 5);
|
|
|
|
EyeRenderer::SpriteInfo result;
|
|
|
|
// rgba
|
|
ASSERT(dma.data[16] == 128); // r
|
|
ASSERT(dma.data[16 + 4] == 128); // r
|
|
ASSERT(dma.data[16 + 8] == 128); // r
|
|
memcpy(&result.a, dma.data + 16 + 12, 1); // a
|
|
|
|
// uv0
|
|
memcpy(&result.uv0, &dma.data[32], 8);
|
|
|
|
// xyz0
|
|
memcpy(&result.xyz0[0], &dma.data[48], 12);
|
|
result.xyz0[2] >>= 4;
|
|
|
|
// uv1
|
|
memcpy(&result.uv1[0], &dma.data[64], 8);
|
|
|
|
// xyz1
|
|
memcpy(&result.xyz1[0], &dma.data[80], 12);
|
|
result.xyz1[2] >>= 4;
|
|
|
|
return result;
|
|
}
|
|
|
|
EyeRenderer::EyeDraw read_eye_draw(DmaFollower& dma) {
|
|
auto scissor = decode_scissor(dma.read_and_advance());
|
|
auto sprite = decode_sprite(dma.read_and_advance());
|
|
return {sprite, scissor};
|
|
}
|
|
|
|
std::vector<EyeRenderer::SingleEyeDraws> EyeRenderer::get_draws(DmaFollower& dma,
|
|
SharedRenderState* render_state) {
|
|
std::vector<SingleEyeDraws> draws;
|
|
// now, loop over eyes. end condition is a 8 qw transfer to restore gs.
|
|
while (dma.current_tag().qwc != 8) {
|
|
draws.emplace_back();
|
|
draws.emplace_back();
|
|
|
|
auto& l_draw = draws[draws.size() - 2];
|
|
auto& r_draw = draws[draws.size() - 1];
|
|
|
|
l_draw.lr = 0;
|
|
r_draw.lr = 1;
|
|
|
|
// eye background setup
|
|
auto adgif0_dma = dma.read_and_advance();
|
|
ASSERT(adgif0_dma.size_bytes == 96); // 5 adgifs a+d's plus tag
|
|
ASSERT(adgif0_dma.vif0() == 0);
|
|
ASSERT(adgif0_dma.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
AdgifHelper adgif0(adgif0_dma.data + 16);
|
|
auto tex0 = render_state->texture_pool->lookup_gpu_texture(adgif0.tex0().tbp0());
|
|
|
|
u32 pair_idx = -1;
|
|
// first draw. this is the background. It reads 0,0 of the texture uses that color everywhere.
|
|
// we'll also figure out the eye index here.
|
|
bool using_64 = false;
|
|
{
|
|
auto draw0 = read_eye_draw(dma);
|
|
// 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) {
|
|
using_64 = true;
|
|
l_draw.using_64 = true;
|
|
r_draw.using_64 = true;
|
|
}
|
|
u32 y0 = (draw0.sprite.xyz0[1] - 512) >> 4;
|
|
if (using_64) {
|
|
y0 = (draw0.sprite.xyz0[1] - 1024) >> 5;
|
|
y0 *= 4;
|
|
}
|
|
pair_idx = y0 / SINGLE_EYE_SIZE;
|
|
l_draw.pair = pair_idx;
|
|
r_draw.pair = pair_idx;
|
|
if (tex0 && tex0->get_data_ptr()) {
|
|
u32 tex_val;
|
|
memcpy(&tex_val, tex0->get_data_ptr(), 4);
|
|
l_draw.clear_color = tex_val;
|
|
r_draw.clear_color = tex_val;
|
|
} else {
|
|
fmt::print("clear lookup failed\n");
|
|
l_draw.clear_color = 0;
|
|
r_draw.clear_color = 0;
|
|
}
|
|
}
|
|
|
|
// up next is the pupil background
|
|
{
|
|
l_draw.iris = read_eye_draw(dma);
|
|
l_draw.iris_tex = tex0;
|
|
l_draw.iris_gl_tex = *render_state->texture_pool->lookup(adgif0.tex0().tbp0());
|
|
|
|
if (dma.current_tag().qwc == 6) {
|
|
// change adgif!
|
|
auto r_iris_adgif = dma.read_and_advance();
|
|
ASSERT(r_iris_adgif.size_bytes == 96); // 5 adgifs a+d's plus tag
|
|
ASSERT(r_iris_adgif.vif0() == 0);
|
|
ASSERT(r_iris_adgif.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
AdgifHelper r_iris_helper(r_iris_adgif.data + 16);
|
|
r_draw.iris = read_eye_draw(dma);
|
|
r_draw.iris_tex =
|
|
render_state->texture_pool->lookup_gpu_texture(r_iris_helper.tex0().tbp0());
|
|
r_draw.iris_gl_tex = *render_state->texture_pool->lookup(r_iris_helper.tex0().tbp0());
|
|
} else {
|
|
// same adgif
|
|
r_draw.iris = read_eye_draw(dma);
|
|
r_draw.iris_tex = tex0;
|
|
r_draw.iris_gl_tex = l_draw.iris_gl_tex;
|
|
}
|
|
}
|
|
|
|
// now we'll draw the pupil on top of that
|
|
auto test1 = dma.read_and_advance();
|
|
(void)test1;
|
|
auto adgif1_dma = dma.read_and_advance();
|
|
ASSERT(adgif1_dma.size_bytes == 96); // 5 adgifs a+d's plus tag
|
|
ASSERT(adgif1_dma.vif0() == 0);
|
|
ASSERT(adgif1_dma.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
AdgifHelper adgif1(adgif1_dma.data + 16);
|
|
auto tex1 = render_state->texture_pool->lookup_gpu_texture(adgif1.tex0().tbp0());
|
|
|
|
if (tex1 && tex1->get_data_ptr()) {
|
|
l_draw.pupil = read_eye_draw(dma);
|
|
l_draw.pupil_tex = tex1;
|
|
l_draw.pupil_gl_tex = *render_state->texture_pool->lookup(adgif1.tex0().tbp0());
|
|
}
|
|
|
|
if (dma.current_tag().qwc == 6) {
|
|
auto r_pupil_adgif = dma.read_and_advance();
|
|
ASSERT(r_pupil_adgif.size_bytes == 96); // 5 adgifs a+d's plus tag
|
|
ASSERT(r_pupil_adgif.vif0() == 0);
|
|
ASSERT(r_pupil_adgif.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
AdgifHelper r_pupil_helper(r_pupil_adgif.data + 16);
|
|
r_draw.pupil = read_eye_draw(dma);
|
|
r_draw.pupil_tex =
|
|
render_state->texture_pool->lookup_gpu_texture(r_pupil_helper.tex0().tbp0());
|
|
r_draw.pupil_gl_tex = *render_state->texture_pool->lookup(r_pupil_helper.tex0().tbp0());
|
|
} else {
|
|
if (tex1 && tex1->get_data_ptr()) {
|
|
r_draw.pupil = read_eye_draw(dma);
|
|
r_draw.pupil_tex = tex1;
|
|
r_draw.pupil_gl_tex = l_draw.pupil_gl_tex;
|
|
}
|
|
}
|
|
|
|
// and finally the eyelid
|
|
auto test2 = dma.read_and_advance();
|
|
(void)test2;
|
|
auto adgif2_dma = dma.read_and_advance();
|
|
ASSERT(adgif2_dma.size_bytes == 96); // 5 adgifs a+d's plus tag
|
|
ASSERT(adgif2_dma.vif0() == 0);
|
|
ASSERT(adgif2_dma.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
AdgifHelper adgif2(adgif2_dma.data + 16);
|
|
auto tex2 = render_state->texture_pool->lookup_gpu_texture(adgif2.tex0().tbp0());
|
|
|
|
{
|
|
l_draw.lid = read_eye_draw(dma);
|
|
l_draw.lid_tex = tex2;
|
|
l_draw.lid_gl_tex = *render_state->texture_pool->lookup(adgif2.tex0().tbp0());
|
|
}
|
|
|
|
if (dma.current_tag().qwc == 6) {
|
|
auto r_lid_adgif = dma.read_and_advance();
|
|
ASSERT(r_lid_adgif.size_bytes == 96); // 5 adgifs a+d's plus tag
|
|
ASSERT(r_lid_adgif.vif0() == 0);
|
|
ASSERT(r_lid_adgif.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
AdgifHelper r_lid_helper(r_lid_adgif.data + 16);
|
|
r_draw.lid = read_eye_draw(dma);
|
|
r_draw.lid_tex = render_state->texture_pool->lookup_gpu_texture(r_lid_helper.tex0().tbp0());
|
|
r_draw.lid_gl_tex = *render_state->texture_pool->lookup(r_lid_helper.tex0().tbp0());
|
|
} else {
|
|
r_draw.lid = read_eye_draw(dma);
|
|
r_draw.lid_tex = tex2;
|
|
r_draw.lid_gl_tex = l_draw.lid_gl_tex;
|
|
}
|
|
|
|
if (render_state->version == GameVersion::Jak1) {
|
|
auto end = dma.read_and_advance();
|
|
ASSERT(end.size_bytes == 0);
|
|
ASSERT(end.vif0() == 0);
|
|
ASSERT(end.vif1() == 0);
|
|
}
|
|
}
|
|
return draws;
|
|
}
|
|
|
|
void EyeRenderer::handle_eye_dma2(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode&) {
|
|
Timer timer;
|
|
m_debug.clear();
|
|
|
|
// first should be the gs setup for render to texture
|
|
auto offset_setup = dma.read_and_advance();
|
|
ASSERT(offset_setup.size_bytes == 128);
|
|
ASSERT(offset_setup.vifcode0().kind == VifCode::Kind::FLUSHA);
|
|
ASSERT(offset_setup.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
|
|
// next should be alpha setup
|
|
auto alpha_setup = dma.read_and_advance();
|
|
ASSERT(alpha_setup.size_bytes == 32);
|
|
ASSERT(alpha_setup.vifcode0().kind == VifCode::Kind::NOP);
|
|
ASSERT(alpha_setup.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
|
|
if (render_state->version == GameVersion::Jak1) {
|
|
// from the add to bucket
|
|
ASSERT(dma.current_tag().kind == DmaTag::Kind::NEXT);
|
|
ASSERT(dma.current_tag().qwc == 0);
|
|
ASSERT(dma.current_tag_vif0() == 0);
|
|
ASSERT(dma.current_tag_vif1() == 0);
|
|
dma.read_and_advance();
|
|
}
|
|
|
|
auto draws = get_draws(dma, render_state);
|
|
run_gpu(draws, render_state);
|
|
|
|
float time_ms = timer.getMs();
|
|
m_average_time_ms = m_average_time_ms * 0.95 + time_ms * 0.05;
|
|
}
|
|
|
|
int add_draw_to_buffer_32(int idx,
|
|
const EyeRenderer::EyeDraw& draw,
|
|
float* data,
|
|
int pair,
|
|
int lr) {
|
|
int x_off = lr * SINGLE_EYE_SIZE * 16;
|
|
int y_off = pair * SINGLE_EYE_SIZE * 16;
|
|
|
|
data[idx++] = draw.sprite.xyz0[0] - x_off;
|
|
data[idx++] = draw.sprite.xyz0[1] - y_off;
|
|
data[idx++] = 0;
|
|
data[idx++] = 0;
|
|
|
|
data[idx++] = draw.sprite.xyz1[0] - x_off;
|
|
data[idx++] = draw.sprite.xyz0[1] - y_off;
|
|
data[idx++] = 1;
|
|
data[idx++] = 0;
|
|
|
|
data[idx++] = draw.sprite.xyz0[0] - x_off;
|
|
data[idx++] = draw.sprite.xyz1[1] - y_off;
|
|
data[idx++] = 0;
|
|
data[idx++] = 1;
|
|
|
|
data[idx++] = draw.sprite.xyz1[0] - x_off;
|
|
data[idx++] = draw.sprite.xyz1[1] - y_off;
|
|
data[idx++] = 1;
|
|
data[idx++] = 1;
|
|
return idx;
|
|
}
|
|
|
|
int add_draw_to_buffer_64(int idx,
|
|
const EyeRenderer::EyeDraw& draw,
|
|
float* data,
|
|
int pair,
|
|
int lr) {
|
|
int x_off = lr * SINGLE_EYE_SIZE * 32;
|
|
int y_off = (pair / 4) * SINGLE_EYE_SIZE * 32;
|
|
|
|
data[idx++] = (draw.sprite.xyz0[0] - x_off) / 2;
|
|
data[idx++] = (draw.sprite.xyz0[1] - y_off) / 2;
|
|
data[idx++] = 0;
|
|
data[idx++] = 0;
|
|
|
|
data[idx++] = (draw.sprite.xyz1[0] - x_off) / 2;
|
|
data[idx++] = (draw.sprite.xyz0[1] - y_off) / 2;
|
|
data[idx++] = 1;
|
|
data[idx++] = 0;
|
|
|
|
data[idx++] = (draw.sprite.xyz0[0] - x_off) / 2;
|
|
data[idx++] = (draw.sprite.xyz1[1] - y_off) / 2;
|
|
data[idx++] = 0;
|
|
data[idx++] = 1;
|
|
|
|
data[idx++] = (draw.sprite.xyz1[0] - x_off) / 2;
|
|
data[idx++] = (draw.sprite.xyz1[1] - y_off) / 2;
|
|
data[idx++] = 1;
|
|
data[idx++] = 1;
|
|
return idx;
|
|
}
|
|
|
|
void EyeRenderer::run_gpu(const std::vector<SingleEyeDraws>& draws,
|
|
SharedRenderState* render_state) {
|
|
if (draws.empty()) {
|
|
return;
|
|
}
|
|
|
|
glBindVertexArray(m_vao);
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_gl_vertex_buffer);
|
|
|
|
// the first thing we'll do is prepare the vertices
|
|
int buffer_idx = 0;
|
|
for (const auto& draw : draws) {
|
|
if (draw.using_64) {
|
|
buffer_idx =
|
|
add_draw_to_buffer_64(buffer_idx, draw.iris, m_gpu_vertex_buffer, draw.pair, draw.lr);
|
|
buffer_idx =
|
|
add_draw_to_buffer_64(buffer_idx, draw.pupil, m_gpu_vertex_buffer, draw.pair, draw.lr);
|
|
buffer_idx =
|
|
add_draw_to_buffer_64(buffer_idx, draw.lid, m_gpu_vertex_buffer, draw.pair, draw.lr);
|
|
} else {
|
|
buffer_idx =
|
|
add_draw_to_buffer_32(buffer_idx, draw.iris, m_gpu_vertex_buffer, draw.pair, draw.lr);
|
|
buffer_idx =
|
|
add_draw_to_buffer_32(buffer_idx, draw.pupil, m_gpu_vertex_buffer, draw.pair, draw.lr);
|
|
buffer_idx =
|
|
add_draw_to_buffer_32(buffer_idx, draw.lid, m_gpu_vertex_buffer, draw.pair, draw.lr);
|
|
}
|
|
}
|
|
ASSERT(buffer_idx <= VTX_BUFFER_FLOATS);
|
|
int check = buffer_idx;
|
|
|
|
// maybe buffer sub data.
|
|
glBufferData(GL_ARRAY_BUFFER, buffer_idx * sizeof(float), m_gpu_vertex_buffer, GL_STREAM_DRAW);
|
|
|
|
FramebufferTexturePairContext ctxt(m_gpu_eye_textures[draws.front().tex_slot()].fb);
|
|
|
|
// set up common opengl state
|
|
glDisable(GL_DEPTH_TEST);
|
|
render_state->shaders[ShaderId::EYE].activate();
|
|
glUniform1i(glGetUniformLocation(render_state->shaders[ShaderId::EYE].id(), "tex_T0"), 0);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
buffer_idx = 0;
|
|
for (size_t draw_idx = 0; draw_idx < draws.size(); draw_idx++) {
|
|
const auto& draw = draws[draw_idx];
|
|
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};
|
|
for (int i = 0; i < 4; i++) {
|
|
clear[i] = ((draw.clear_color >> (8 * i)) & 0xff) / 255.f;
|
|
}
|
|
glClearBufferfv(GL_COLOR, 0, clear);
|
|
|
|
// iris
|
|
if (draw.iris_tex) {
|
|
// set alpha
|
|
// set Z
|
|
// set texture
|
|
glDisable(GL_BLEND);
|
|
glBindTexture(GL_TEXTURE_2D, draw.iris_gl_tex);
|
|
glDrawArrays(GL_TRIANGLE_STRIP, buffer_idx / 4, 4);
|
|
}
|
|
buffer_idx += 4 * 4;
|
|
|
|
if (draw.pupil_tex) {
|
|
glEnable(GL_BLEND);
|
|
glBlendEquation(GL_FUNC_ADD);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glBindTexture(GL_TEXTURE_2D, draw.pupil_gl_tex);
|
|
glDrawArrays(GL_TRIANGLE_STRIP, buffer_idx / 4, 4);
|
|
}
|
|
buffer_idx += 4 * 4;
|
|
|
|
if (draw.lid_tex) {
|
|
glDisable(GL_BLEND);
|
|
glBindTexture(GL_TEXTURE_2D, draw.lid_gl_tex);
|
|
glDrawArrays(GL_TRIANGLE_STRIP, buffer_idx / 4, 4);
|
|
}
|
|
buffer_idx += 4 * 4;
|
|
|
|
// finally, give to "vram"
|
|
render_state->texture_pool->move_existing_to_vram(out_tex.gpu_tex, out_tex.tbp);
|
|
|
|
if (draw_idx != draws.size() - 1) {
|
|
ctxt.switch_to(m_gpu_eye_textures[draws[draw_idx + 1].tex_slot()].fb);
|
|
}
|
|
}
|
|
|
|
ASSERT(check == buffer_idx);
|
|
|
|
glBindVertexArray(0);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
}
|
|
|
|
std::optional<u64> EyeRenderer::lookup_eye_texture(u8 eye_id) {
|
|
eye_id = (eye_id % 40);
|
|
if ((s32)eye_id >= NUM_EYE_PAIRS * 2) {
|
|
fmt::print("lookup eye failed for {} (1)\n", eye_id);
|
|
return {};
|
|
}
|
|
auto* gpu_tex = m_gpu_eye_textures[eye_id].gpu_tex;
|
|
if (gpu_tex) {
|
|
return gpu_tex->gpu_textures.at(0).gl;
|
|
} else {
|
|
fmt::print("lookup eye failed for {}\n", eye_id);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
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,
|
|
uv1[0], uv1[1], xyz0[0], xyz0[1], xyz0[2], xyz1[0], xyz1[1], xyz1[2]);
|
|
return result;
|
|
}
|
|
|
|
std::string EyeRenderer::ScissorInfo::print() const {
|
|
return fmt::format("x : [{}, {}], y : [{}, {}]", x0, x1, y0, y1);
|
|
}
|
|
|
|
std::string EyeRenderer::EyeDraw::print() const {
|
|
return fmt::format("{}\n{}\n", sprite.print(), scissor.print());
|
|
} |