mirror of
https://github.com/open-goal/jak-project
synced 2026-07-04 05:20:56 -04:00
d3cc739e43
This attempts to get into master whatever work was done in this PR / it's earlier PR https://github.com/open-goal/jak-project/pull/3965 I don't want this work to be lost / floating around in massive PRs. However the changes are: - switch to ntsc_v1 instead of PAL as the development target, as we have done for all other games - remove most of the copied-from-jak2/3 changes as they need to be confirmed during the decompilation process not just assumed - avoids committing any changes to `game/kernel/common` as it was not clear to me if these were changes made in jak x's kernel that were not properly broken out into it's own functions. We don't want to accidentally introduce bugs into jak1-3's kernel code. - in other words, if the change in the kernel only happens in jak x...it should likely be specific to jak x's kernel, not common. --------- Co-authored-by: VodBox <dillon@vodbox.io> Co-authored-by: yodah <greenboyyodah@gmail.com>
906 lines
33 KiB
C++
906 lines
33 KiB
C++
#include "Sprite3.h"
|
|
|
|
#include "common/log/log.h"
|
|
|
|
#include "game/graphics/opengl_renderer/background/background_common.h"
|
|
#include "game/graphics/opengl_renderer/dma_helpers.h"
|
|
|
|
#include "fmt/format.h"
|
|
#include "third-party/imgui/imgui.h"
|
|
|
|
namespace {
|
|
|
|
/*!
|
|
* Does the next DMA transfer look like it could be the start of a 2D group?
|
|
*/
|
|
bool looks_like_2d_chunk_start(const DmaFollower& dma) {
|
|
return dma.current_tag().qwc == 1 && dma.current_tag().kind == DmaTag::Kind::CNT;
|
|
}
|
|
|
|
/*!
|
|
* Read the header. Asserts if it's bad.
|
|
* Returns the number of sprites.
|
|
* Advances 1 dma transfer
|
|
*/
|
|
u32 process_sprite_chunk_header(DmaFollower& dma) {
|
|
auto transfer = dma.read_and_advance();
|
|
// note that flg = true, this should use double buffering
|
|
bool ok = verify_unpack_with_stcycl(transfer, VifCode::Kind::UNPACK_V4_32, 4, 4, 1,
|
|
SpriteDataMem::Header, false, true);
|
|
ASSERT(ok);
|
|
u32 header[4];
|
|
memcpy(header, transfer.data, 16);
|
|
ASSERT(header[0] <= Sprite3::SPRITES_PER_CHUNK);
|
|
return header[0];
|
|
}
|
|
|
|
constexpr int SPRITE_RENDERER_MAX_SPRITES = 1920 * 12;
|
|
} // namespace
|
|
|
|
Sprite3::Sprite3(const std::string& name, int my_id)
|
|
: BucketRenderer(name, my_id), m_direct(name, my_id, 1024) {
|
|
opengl_setup();
|
|
}
|
|
|
|
void Sprite3::opengl_setup() {
|
|
// Set up OpenGL for 'normal' sprites
|
|
opengl_setup_normal();
|
|
|
|
// Set up OpenGL for distort sprites
|
|
opengl_setup_distort();
|
|
}
|
|
|
|
void Sprite3::opengl_setup_normal() {
|
|
glGenBuffers(1, &m_ogl.vertex_buffer);
|
|
glGenVertexArrays(1, &m_ogl.vao);
|
|
glBindVertexArray(m_ogl.vao);
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
|
|
auto verts = SPRITE_RENDERER_MAX_SPRITES * 4;
|
|
auto bytes = verts * sizeof(SpriteVertex3D);
|
|
glBufferData(GL_ARRAY_BUFFER, bytes, nullptr, GL_STREAM_DRAW);
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(
|
|
0, // location 0 in the shader
|
|
4, // 4 floats per vert (w unused)
|
|
GL_FLOAT, // floats
|
|
GL_TRUE, // normalized, ignored,
|
|
sizeof(SpriteVertex3D), //
|
|
(void*)offsetof(SpriteVertex3D, xyz_sx) // offset in array (why is this a pointer...)
|
|
);
|
|
|
|
glEnableVertexAttribArray(1);
|
|
glVertexAttribPointer(
|
|
1, // location 1 in the shader
|
|
4, // 4 color components
|
|
GL_FLOAT, // floats
|
|
GL_TRUE, // normalized, ignored,
|
|
sizeof(SpriteVertex3D), //
|
|
(void*)offsetof(SpriteVertex3D, quat_sy) // offset in array (why is this a pointer...)
|
|
);
|
|
|
|
glEnableVertexAttribArray(2);
|
|
glVertexAttribPointer(
|
|
2, // location 2 in the shader
|
|
4, // 4 color components
|
|
GL_FLOAT, // floats
|
|
GL_TRUE, // normalized, ignored,
|
|
sizeof(SpriteVertex3D), //
|
|
(void*)offsetof(SpriteVertex3D, rgba) // offset in array (why is this a pointer...)
|
|
);
|
|
|
|
glEnableVertexAttribArray(3);
|
|
glVertexAttribIPointer(
|
|
3, // location 3 in the shader
|
|
2, // 4 color components
|
|
GL_UNSIGNED_SHORT, // floats
|
|
sizeof(SpriteVertex3D), //
|
|
(void*)offsetof(SpriteVertex3D, flags_matrix) // offset in array (why is this a pointer...)
|
|
);
|
|
|
|
glEnableVertexAttribArray(4);
|
|
glVertexAttribIPointer(
|
|
4, // location 4 in the shader
|
|
4, // 3 floats per vert
|
|
GL_UNSIGNED_SHORT, // floats
|
|
sizeof(SpriteVertex3D), //
|
|
(void*)offsetof(SpriteVertex3D, info) // offset in array (why is this a pointer...)
|
|
);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
u32 idx_buffer_len = SPRITE_RENDERER_MAX_SPRITES * 5;
|
|
glGenBuffers(1, &m_ogl.index_buffer);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ogl.index_buffer);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr, GL_STREAM_DRAW);
|
|
|
|
glBindVertexArray(0);
|
|
|
|
m_vertices_3d.resize(verts);
|
|
m_index_buffer_data.resize(idx_buffer_len);
|
|
|
|
m_default_mode.disable_depth_write();
|
|
m_default_mode.set_depth_test(GsTest::ZTest::GEQUAL);
|
|
m_default_mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST);
|
|
m_default_mode.set_aref(38);
|
|
m_default_mode.set_alpha_test(DrawMode::AlphaTest::GEQUAL);
|
|
m_default_mode.set_alpha_fail(GsTest::AlphaFail::FB_ONLY);
|
|
m_default_mode.set_at(true);
|
|
m_default_mode.set_zt(true);
|
|
m_default_mode.set_ab(true);
|
|
|
|
m_current_mode = m_default_mode;
|
|
}
|
|
|
|
/*!
|
|
* Handle DMA data that does the per-frame setup.
|
|
* This should get the dma chain immediately after the call to sprite-draw-distorters.
|
|
* It ends right before the sprite-add-matrix-data for the 3d's
|
|
*/
|
|
void Sprite3::handle_sprite_frame_setup(DmaFollower& dma,
|
|
GameVersion version,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& /*prof*/) {
|
|
// first is some direct data
|
|
auto direct_data = dma.read_and_advance();
|
|
ASSERT(direct_data.size_bytes == 3 * 16);
|
|
memcpy(m_sprite_direct_setup, direct_data.data, 3 * 16);
|
|
ASSERT(m_sprite_direct_setup[0] == 0x2000000000008001);
|
|
ASSERT(m_sprite_direct_setup[1] == 0xEEEEEEEEEEEEEEEE);
|
|
ASSERT(m_sprite_direct_setup[2] == 0x000000000005126B);
|
|
ASSERT(m_sprite_direct_setup[3] == 0x0000000000000047);
|
|
ASSERT(m_sprite_direct_setup[4] == 0x0000000000000005);
|
|
ASSERT(m_sprite_direct_setup[5] == 0x0000000000000008);
|
|
|
|
// next would be the program, but it's 0 size on the PC and isn't sent.
|
|
|
|
// next is the "frame data"
|
|
switch (version) {
|
|
case GameVersion::Jak1: {
|
|
render_state->shaders[ShaderId::SPRITE3].activate();
|
|
auto frame_data = dma.read_and_advance();
|
|
ASSERT(frame_data.size_bytes == (int)sizeof(SpriteFrameDataJak1)); // very cool
|
|
ASSERT(frame_data.vifcode0().kind == VifCode::Kind::STCYCL);
|
|
VifCodeStcycl frame_data_stcycl(frame_data.vifcode0());
|
|
ASSERT(frame_data_stcycl.cl == 4);
|
|
ASSERT(frame_data_stcycl.wl == 4);
|
|
ASSERT(frame_data.vifcode1().kind == VifCode::Kind::UNPACK_V4_32);
|
|
VifCodeUnpack frame_data_unpack(frame_data.vifcode1());
|
|
ASSERT(frame_data_unpack.addr_qw == SpriteDataMem::FrameData);
|
|
ASSERT(frame_data_unpack.use_tops_flag == false);
|
|
SpriteFrameDataJak1 jak1_data;
|
|
memcpy(&jak1_data, frame_data.data, sizeof(SpriteFrameDataJak1));
|
|
m_frame_data.from_jak1(jak1_data);
|
|
} break;
|
|
case GameVersion::Jak2:
|
|
case GameVersion::Jak3:
|
|
case GameVersion::JakX: {
|
|
render_state->shaders[ShaderId::SPRITE3].activate();
|
|
auto frame_data = dma.read_and_advance();
|
|
ASSERT(frame_data.size_bytes == (int)sizeof(SpriteFrameData)); // very cool
|
|
ASSERT(frame_data.vifcode0().kind == VifCode::Kind::STCYCL);
|
|
VifCodeStcycl frame_data_stcycl(frame_data.vifcode0());
|
|
ASSERT(frame_data_stcycl.cl == 4);
|
|
ASSERT(frame_data_stcycl.wl == 4);
|
|
ASSERT(frame_data.vifcode1().kind == VifCode::Kind::UNPACK_V4_32);
|
|
VifCodeUnpack frame_data_unpack(frame_data.vifcode1());
|
|
ASSERT(frame_data_unpack.addr_qw == SpriteDataMem::FrameData);
|
|
ASSERT(frame_data_unpack.use_tops_flag == false);
|
|
memcpy(&m_frame_data, frame_data.data, sizeof(SpriteFrameData));
|
|
} break;
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
// next, a MSCALF.
|
|
auto mscalf = dma.read_and_advance();
|
|
ASSERT(mscalf.size_bytes == 0);
|
|
ASSERT(mscalf.vifcode0().kind == VifCode::Kind::MSCALF);
|
|
ASSERT(mscalf.vifcode0().immediate == SpriteProgMem::Init);
|
|
ASSERT(mscalf.vifcode1().kind == VifCode::Kind::FLUSHE);
|
|
|
|
// next base and offset
|
|
auto base_offset = dma.read_and_advance();
|
|
ASSERT(base_offset.size_bytes == 0);
|
|
ASSERT(base_offset.vifcode0().kind == VifCode::Kind::BASE);
|
|
ASSERT(base_offset.vifcode0().immediate == SpriteDataMem::Buffer0);
|
|
ASSERT(base_offset.vifcode1().kind == VifCode::Kind::OFFSET);
|
|
ASSERT(base_offset.vifcode1().immediate == SpriteDataMem::Buffer1);
|
|
}
|
|
|
|
void Sprite3::render_3d(DmaFollower& dma) {
|
|
// one time matrix data
|
|
auto matrix_data = dma.read_and_advance();
|
|
ASSERT(matrix_data.size_bytes == sizeof(Sprite3DMatrixData));
|
|
|
|
bool unpack_ok = verify_unpack_with_stcycl(matrix_data, VifCode::Kind::UNPACK_V4_32, 4, 4, 5,
|
|
SpriteDataMem::Matrix, false, false);
|
|
ASSERT(unpack_ok);
|
|
static_assert(sizeof(m_3d_matrix_data) == 5 * 16);
|
|
memcpy(&m_3d_matrix_data, matrix_data.data, sizeof(m_3d_matrix_data));
|
|
// TODO
|
|
}
|
|
|
|
void Sprite3::render_2d_group0(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
// opengl sprite frame setup
|
|
auto shid = render_state->shaders[ShaderId::SPRITE3].id();
|
|
glUniform4fv(glGetUniformLocation(shid, "hvdf_offset"), 1, m_3d_matrix_data.hvdf_offset.data());
|
|
glUniform1f(glGetUniformLocation(shid, "pfog0"), m_frame_data.pfog0);
|
|
glUniform1f(glGetUniformLocation(shid, "min_scale"), m_frame_data.min_scale);
|
|
glUniform1f(glGetUniformLocation(shid, "max_scale"), m_frame_data.max_scale);
|
|
glUniform1f(glGetUniformLocation(shid, "fog_min"), m_frame_data.fog_min);
|
|
glUniform1f(glGetUniformLocation(shid, "fog_max"), m_frame_data.fog_max);
|
|
// glUniform1f(glGetUniformLocation(shid, "bonus"), m_frame_data.bonus);
|
|
// glUniform4fv(glGetUniformLocation(shid, "hmge_scale"), 1, m_frame_data.hmge_scale.data());
|
|
glUniform1f(glGetUniformLocation(shid, "deg_to_rad"), m_frame_data.deg_to_rad);
|
|
glUniform1f(glGetUniformLocation(shid, "inv_area"), m_frame_data.inv_area);
|
|
glUniformMatrix4fv(glGetUniformLocation(shid, "camera"), 1, GL_FALSE,
|
|
m_3d_matrix_data.camera.data());
|
|
glUniform4fv(glGetUniformLocation(shid, "xy_array"), 8, m_frame_data.xy_array[0].data());
|
|
glUniform4fv(glGetUniformLocation(shid, "xyz_array"), 4, m_frame_data.xyz_array[0].data());
|
|
glUniform4fv(glGetUniformLocation(shid, "st_array"), 4, m_frame_data.st_array[0].data());
|
|
glUniform4fv(glGetUniformLocation(shid, "basis_x"), 1, m_frame_data.basis_x.data());
|
|
glUniform4fv(glGetUniformLocation(shid, "basis_y"), 1, m_frame_data.basis_y.data());
|
|
|
|
u16 last_prog = -1;
|
|
|
|
while (looks_like_2d_chunk_start(dma)) {
|
|
m_debug_stats.blocks_2d_grp0++;
|
|
// 4 packets per chunk
|
|
|
|
// first is the header
|
|
u32 sprite_count = process_sprite_chunk_header(dma);
|
|
m_debug_stats.count_2d_grp0 += sprite_count;
|
|
|
|
// second is the vector data
|
|
u32 expected_vec_size = sizeof(SpriteVecData2d) * sprite_count;
|
|
auto vec_data = dma.read_and_advance();
|
|
ASSERT(expected_vec_size <= sizeof(m_vec_data_2d));
|
|
unpack_to_no_stcycl(&m_vec_data_2d, vec_data, VifCode::Kind::UNPACK_V4_32, expected_vec_size,
|
|
SpriteDataMem::Vector, false, true);
|
|
|
|
// third is the adgif data
|
|
u32 expected_adgif_size = sizeof(AdGifData) * sprite_count;
|
|
auto adgif_data = dma.read_and_advance();
|
|
ASSERT(expected_adgif_size <= sizeof(m_adgif));
|
|
unpack_to_no_stcycl(&m_adgif, adgif_data, VifCode::Kind::UNPACK_V4_32, expected_adgif_size,
|
|
SpriteDataMem::Adgif, false, true);
|
|
|
|
// fourth is the actual run!!!!!
|
|
auto run = dma.read_and_advance();
|
|
ASSERT(run.vifcode0().kind == VifCode::Kind::NOP);
|
|
ASSERT(run.vifcode1().kind == VifCode::Kind::MSCAL);
|
|
|
|
if (m_enabled) {
|
|
if (run.vifcode1().immediate != last_prog) {
|
|
// one-time setups and flushing
|
|
flush_sprites(render_state, prof, false);
|
|
}
|
|
|
|
if (run.vifcode1().immediate == SpriteProgMem::Sprites2dGrp0) {
|
|
if (m_2d_enable) {
|
|
do_block_common(SpriteMode::Mode2D, sprite_count, render_state, prof);
|
|
}
|
|
} else {
|
|
if (m_3d_enable) {
|
|
do_block_common(SpriteMode::Mode3D, sprite_count, render_state, prof);
|
|
}
|
|
}
|
|
last_prog = run.vifcode1().immediate;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Run the pre-sprite directrenderer.
|
|
*/
|
|
bool Sprite3::render_direct(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
m_direct.reset_state();
|
|
while (dma.current_tag().qwc != 7 && dma.current_tag_offset() != render_state->next_bucket) {
|
|
auto direct_data = dma.read_and_advance();
|
|
m_direct.render_vif(direct_data.vif0(), direct_data.vif1(), direct_data.data,
|
|
direct_data.size_bytes, render_state, prof);
|
|
}
|
|
m_direct.flush_pending(render_state, prof);
|
|
|
|
// if sprites are off, after all the directrenderer dma, there is nothing left and we must exit
|
|
if (dma.current_tag_offset() == render_state->next_bucket) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Sprite3::render_fake_shadow(DmaFollower& dma) {
|
|
// TODO
|
|
// nop + flushe
|
|
auto nop_flushe = dma.read_and_advance();
|
|
ASSERT(nop_flushe.vifcode0().kind == VifCode::Kind::NOP);
|
|
ASSERT(nop_flushe.vifcode1().kind == VifCode::Kind::FLUSHE);
|
|
}
|
|
|
|
/*!
|
|
* Handle DMA data for group1 2d's (HUD)
|
|
*/
|
|
void Sprite3::render_2d_group1(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
// one time matrix data upload
|
|
auto mat_upload = dma.read_and_advance();
|
|
bool mat_ok = verify_unpack_with_stcycl(mat_upload, VifCode::Kind::UNPACK_V4_32, 4, 4, 80,
|
|
SpriteDataMem::Matrix, false, false);
|
|
ASSERT(mat_ok);
|
|
ASSERT(mat_upload.size_bytes == sizeof(m_hud_matrix_data));
|
|
memcpy(&m_hud_matrix_data, mat_upload.data, sizeof(m_hud_matrix_data));
|
|
|
|
// opengl sprite frame setup
|
|
glUniform4fv(
|
|
glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "hud_hvdf_offset"), 1,
|
|
m_hud_matrix_data.hvdf_offset.data());
|
|
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "hud_hvdf_user"),
|
|
75, m_hud_matrix_data.user_hvdf[0].data());
|
|
glUniformMatrix4fv(
|
|
glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "hud_matrix"), 1,
|
|
GL_FALSE, m_hud_matrix_data.matrix.data());
|
|
|
|
// loop through chunks.
|
|
while (looks_like_2d_chunk_start(dma)) {
|
|
m_debug_stats.blocks_2d_grp1++;
|
|
// 4 packets per chunk
|
|
|
|
// first is the header
|
|
u32 sprite_count = process_sprite_chunk_header(dma);
|
|
m_debug_stats.count_2d_grp1 += sprite_count;
|
|
|
|
// second is the vector data
|
|
u32 expected_vec_size = sizeof(SpriteVecData2d) * sprite_count;
|
|
auto vec_data = dma.read_and_advance();
|
|
ASSERT(expected_vec_size <= sizeof(m_vec_data_2d));
|
|
unpack_to_no_stcycl(&m_vec_data_2d, vec_data, VifCode::Kind::UNPACK_V4_32, expected_vec_size,
|
|
SpriteDataMem::Vector, false, true);
|
|
|
|
// third is the adgif data
|
|
u32 expected_adgif_size = sizeof(AdGifData) * sprite_count;
|
|
auto adgif_data = dma.read_and_advance();
|
|
ASSERT(expected_adgif_size <= sizeof(m_adgif));
|
|
unpack_to_no_stcycl(&m_adgif, adgif_data, VifCode::Kind::UNPACK_V4_32, expected_adgif_size,
|
|
SpriteDataMem::Adgif, false, true);
|
|
|
|
// fourth is the actual run!!!!!
|
|
auto run = dma.read_and_advance();
|
|
ASSERT(run.vifcode0().kind == VifCode::Kind::NOP);
|
|
ASSERT(run.vifcode1().kind == VifCode::Kind::MSCAL);
|
|
|
|
switch (render_state->version) {
|
|
case GameVersion::Jak1:
|
|
ASSERT(run.vifcode1().immediate == SpriteProgMem::Sprites2dHud_Jak1);
|
|
break;
|
|
case GameVersion::Jak2:
|
|
ASSERT(run.vifcode1().immediate == SpriteProgMem::Sprites2dHud_Jak2);
|
|
break;
|
|
case GameVersion::Jak3:
|
|
case GameVersion::JakX:
|
|
ASSERT_EQ_IMM(run.vifcode1().immediate, (int)SpriteProgMem::Sprites2dHud_Jak3);
|
|
break;
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
if (m_enabled && m_2d_enable) {
|
|
do_block_common(SpriteMode::ModeHUD, sprite_count, render_state, prof);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sprite3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) {
|
|
switch (render_state->version) {
|
|
case GameVersion::Jak1:
|
|
render_jak1(dma, render_state, prof);
|
|
break;
|
|
case GameVersion::Jak2:
|
|
case GameVersion::Jak3:
|
|
case GameVersion::JakX:
|
|
render_jak2(dma, render_state, prof);
|
|
break;
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
void Sprite3::render_jak2(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
m_debug_stats = {};
|
|
auto data0 = dma.read_and_advance();
|
|
ASSERT(data0.vif0() == 0 || data0.vifcode0().kind == VifCode::Kind::MARK);
|
|
ASSERT(data0.vif1() == 0 || data0.vifcode1().kind == VifCode::Kind::NOP);
|
|
ASSERT(data0.size_bytes == 0);
|
|
|
|
if (dma.current_tag_offset() == render_state->next_bucket) {
|
|
return;
|
|
}
|
|
|
|
// Before anything else, some directrenderer DMA might have been sent
|
|
{
|
|
auto child = prof.make_scoped_child("direct");
|
|
if (render_direct(dma, render_state, child)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// First is the distorter
|
|
{
|
|
auto child = prof.make_scoped_child("distorter");
|
|
render_distorter(dma, render_state, child);
|
|
}
|
|
|
|
// next, the normal sprite stuff
|
|
handle_sprite_frame_setup(dma, render_state->version, render_state, prof);
|
|
|
|
// 3d sprites
|
|
render_3d(dma);
|
|
|
|
// 2d draw
|
|
// m_sprite_renderer.reset_state();
|
|
{
|
|
auto child = prof.make_scoped_child("2d-group0");
|
|
render_2d_group0(dma, render_state, child);
|
|
flush_sprites(render_state, prof, false);
|
|
}
|
|
|
|
// shadow draw
|
|
render_fake_shadow(dma);
|
|
|
|
// 2d draw (HUD)
|
|
{
|
|
auto child = prof.make_scoped_child("2d-group1");
|
|
render_2d_group1(dma, render_state, child);
|
|
flush_sprites(render_state, prof, true);
|
|
auto nop_flushe = dma.read_and_advance();
|
|
ASSERT(nop_flushe.vifcode0().kind == VifCode::Kind::NOP);
|
|
ASSERT(nop_flushe.vifcode1().kind == VifCode::Kind::FLUSHE);
|
|
}
|
|
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glBlendEquation(GL_FUNC_ADD);
|
|
|
|
{
|
|
auto p = prof.make_scoped_child("glow");
|
|
glow_dma_and_draw(dma, render_state, p);
|
|
}
|
|
|
|
// fmt::print("next bucket is 0x{}\n", render_state->next_bucket);
|
|
while (dma.current_tag_offset() != render_state->next_bucket) {
|
|
// auto tag = dma.current_tag();
|
|
auto data = dma.read_and_advance();
|
|
(void)data;
|
|
// VifCode code(data.vif0());
|
|
// fmt::print("@ 0x{:x} tag: {}", dma.current_tag_offset(), tag.print());
|
|
// fmt::print(" vif0: {}\n", code.print());
|
|
// fmt::print(" vif1: {}\n", VifCode(data.vif1()).print());
|
|
}
|
|
}
|
|
|
|
void Sprite3::render_jak1(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
m_debug_stats = {};
|
|
// First thing should be a NEXT with two nops. this is a jump from buckets to sprite data
|
|
auto data0 = dma.read_and_advance();
|
|
ASSERT(data0.vif1() == 0);
|
|
ASSERT(data0.vif0() == 0);
|
|
ASSERT(data0.size_bytes == 0);
|
|
|
|
if (dma.current_tag().kind == DmaTag::Kind::CALL) {
|
|
// sprite 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;
|
|
}
|
|
|
|
// Before anything else, some directrenderer DMA might have been sent
|
|
{
|
|
auto child = prof.make_scoped_child("direct");
|
|
if (render_direct(dma, render_state, child)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// First is the distorter
|
|
{
|
|
auto child = prof.make_scoped_child("distorter");
|
|
render_distorter(dma, render_state, child);
|
|
}
|
|
|
|
// next, sprite frame setup.
|
|
handle_sprite_frame_setup(dma, render_state->version, render_state, prof);
|
|
|
|
// 3d sprites
|
|
render_3d(dma);
|
|
|
|
// 2d draw
|
|
// m_sprite_renderer.reset_state();
|
|
{
|
|
auto child = prof.make_scoped_child("2d-group0");
|
|
render_2d_group0(dma, render_state, child);
|
|
flush_sprites(render_state, prof, false);
|
|
}
|
|
|
|
// shadow draw
|
|
render_fake_shadow(dma);
|
|
|
|
// 2d draw (HUD)
|
|
{
|
|
auto child = prof.make_scoped_child("2d-group1");
|
|
render_2d_group1(dma, render_state, child);
|
|
flush_sprites(render_state, prof, true);
|
|
}
|
|
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glBlendEquation(GL_FUNC_ADD);
|
|
|
|
// TODO finish this up.
|
|
// fmt::print("next bucket is 0x{}\n", render_state->next_bucket);
|
|
while (dma.current_tag_offset() != render_state->next_bucket) {
|
|
// auto tag = dma.current_tag();
|
|
// fmt::print("@ 0x{:x} tag: {}", dma.current_tag_offset(), tag.print());
|
|
auto data = dma.read_and_advance();
|
|
VifCode code(data.vif0());
|
|
// fmt::print(" vif0: {}\n", code.print());
|
|
if (code.kind == VifCode::Kind::NOP) {
|
|
// fmt::print(" vif1: {}\n", VifCode(data.vif1()).print());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sprite3::draw_debug_window() {
|
|
ImGui::Checkbox("Glow", &m_enable_glow);
|
|
ImGui::Checkbox("new glow", &m_glow_renderer.new_mode);
|
|
ImGui::Separator();
|
|
ImGui::Text("Distort sprites: %d", m_distort_stats.total_sprites);
|
|
ImGui::Text("2D Group 0 (World) blocks: %d sprites: %d", m_debug_stats.blocks_2d_grp0,
|
|
m_debug_stats.count_2d_grp0);
|
|
ImGui::Text("2D Group 1 (HUD) blocks: %d sprites: %d", m_debug_stats.blocks_2d_grp1,
|
|
m_debug_stats.count_2d_grp1);
|
|
ImGui::Checkbox("Culling", &m_enable_culling);
|
|
ImGui::Checkbox("2d", &m_2d_enable);
|
|
ImGui::SameLine();
|
|
ImGui::Checkbox("3d", &m_3d_enable);
|
|
ImGui::Checkbox("Distort", &m_distort_enable);
|
|
ImGui::Checkbox("Distort instancing", &m_enable_distort_instancing);
|
|
ImGui::Separator();
|
|
m_glow_renderer.draw_debug_window();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Render (for real)
|
|
|
|
void Sprite3::flush_sprites(SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof,
|
|
bool double_draw) {
|
|
glBindVertexArray(m_ogl.vao);
|
|
|
|
glEnable(GL_PRIMITIVE_RESTART);
|
|
glPrimitiveRestartIndex(UINT32_MAX);
|
|
|
|
// upload vertex buffer
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
|
|
glBufferData(GL_ARRAY_BUFFER, m_sprite_idx * sizeof(SpriteVertex3D) * 4, m_vertices_3d.data(),
|
|
GL_STREAM_DRAW);
|
|
|
|
// two passes through the buckets. first to build the index buffer
|
|
u32 idx_offset = 0;
|
|
for (const auto bucket : m_bucket_list) {
|
|
memcpy(&m_index_buffer_data[idx_offset], bucket->ids.data(), bucket->ids.size() * sizeof(u32));
|
|
bucket->offset_in_idx_buffer = idx_offset;
|
|
idx_offset += bucket->ids.size();
|
|
}
|
|
|
|
// now upload it
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ogl.index_buffer);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_offset * sizeof(u32), m_index_buffer_data.data(),
|
|
GL_STREAM_DRAW);
|
|
|
|
// now do draws!
|
|
for (const auto bucket : m_bucket_list) {
|
|
u32 tbp = bucket->key >> 32;
|
|
DrawMode mode;
|
|
mode.as_int() = bucket->key & 0xffffffff;
|
|
|
|
std::optional<u64> tex;
|
|
tex = render_state->texture_pool->lookup(tbp);
|
|
|
|
if (!tex) {
|
|
lg::warn("Failed to find texture at {}, using random (sprite)", tbp);
|
|
tex = render_state->texture_pool->get_placeholder_texture();
|
|
}
|
|
ASSERT(tex);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, *tex);
|
|
|
|
auto settings = setup_opengl_from_draw_mode(mode, GL_TEXTURE0, false);
|
|
|
|
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "alpha_min"),
|
|
double_draw ? settings.aref_first : 0.016);
|
|
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "alpha_max"),
|
|
10.f);
|
|
glUniform1i(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "tex_T0"), 0);
|
|
|
|
prof.add_draw_call();
|
|
prof.add_tri(2 * (bucket->ids.size() / 5));
|
|
|
|
glDrawElements(GL_TRIANGLE_STRIP, bucket->ids.size(), GL_UNSIGNED_INT,
|
|
(void*)(bucket->offset_in_idx_buffer * sizeof(u32)));
|
|
|
|
if (double_draw) {
|
|
switch (settings.kind) {
|
|
case DoubleDrawKind::NONE:
|
|
break;
|
|
case DoubleDrawKind::AFAIL_NO_DEPTH_WRITE:
|
|
prof.add_draw_call();
|
|
prof.add_tri(2 * (bucket->ids.size() / 5));
|
|
glUniform1f(
|
|
glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "alpha_min"),
|
|
-10.f);
|
|
glUniform1f(
|
|
glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "alpha_max"),
|
|
settings.aref_second);
|
|
glDepthMask(GL_FALSE);
|
|
glDrawElements(GL_TRIANGLE_STRIP, bucket->ids.size(), GL_UNSIGNED_INT,
|
|
(void*)(bucket->offset_in_idx_buffer * sizeof(u32)));
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_sprite_buckets.clear();
|
|
m_bucket_list.clear();
|
|
m_last_bucket_key = UINT64_MAX;
|
|
m_last_bucket = nullptr;
|
|
m_sprite_idx = 0;
|
|
glBindVertexArray(0);
|
|
}
|
|
|
|
void Sprite3::handle_tex0(u64 val,
|
|
SharedRenderState* /*render_state*/,
|
|
ScopedProfilerNode& /*prof*/) {
|
|
GsTex0 reg(val);
|
|
|
|
// update tbp
|
|
m_current_tbp = reg.tbp0();
|
|
m_current_mode.set_tcc(reg.tcc());
|
|
|
|
// tbw: assume they got it right
|
|
// psm: assume they got it right
|
|
// tw: assume they got it right
|
|
// th: assume they got it right
|
|
|
|
ASSERT(reg.tfx() == GsTex0::TextureFunction::MODULATE);
|
|
ASSERT(reg.psm() != GsTex0::PSM::PSMT4HH);
|
|
|
|
// cbp: assume they got it right
|
|
// cpsm: assume they got it right
|
|
// csm: assume they got it right
|
|
}
|
|
|
|
void Sprite3::handle_tex1(u64 val,
|
|
SharedRenderState* /*render_state*/,
|
|
ScopedProfilerNode& /*prof*/) {
|
|
GsTex1 reg(val);
|
|
m_current_mode.set_filt_enable(reg.mmag());
|
|
}
|
|
|
|
void Sprite3::handle_zbuf(u64 val,
|
|
SharedRenderState* /*render_state*/,
|
|
ScopedProfilerNode& /*prof*/) {
|
|
// note: we can basically ignore this. There's a single z buffer that's always configured the same
|
|
// way - 24-bit, at offset 448.
|
|
GsZbuf x(val);
|
|
ASSERT(x.psm() == TextureFormat::PSMZ24);
|
|
ASSERT(x.zbp() == 448 || x.zbp() == 304); // 304 for jak 2.
|
|
|
|
m_current_mode.set_depth_write_enable(!x.zmsk());
|
|
}
|
|
|
|
void Sprite3::handle_clamp(u64 val,
|
|
SharedRenderState* /*render_state*/,
|
|
ScopedProfilerNode& /*prof*/) {
|
|
if (!(val == 0b101 || val == 0 || val == 1 || val == 0b100)) {
|
|
ASSERT_MSG(false, fmt::format("clamp: 0x{:x}", val));
|
|
}
|
|
|
|
m_current_mode.set_clamp_s_enable(val & 0b001);
|
|
m_current_mode.set_clamp_t_enable(val & 0b100);
|
|
}
|
|
|
|
void Sprite3::update_mode_from_alpha1(u64 val, DrawMode& mode) {
|
|
GsAlpha reg(val);
|
|
if (reg.a_mode() == GsAlpha::BlendMode::SOURCE && reg.b_mode() == GsAlpha::BlendMode::DEST &&
|
|
reg.c_mode() == GsAlpha::BlendMode::SOURCE && reg.d_mode() == GsAlpha::BlendMode::DEST) {
|
|
// (Cs - Cd) * As + Cd
|
|
// Cs * As + (1 - As) * Cd
|
|
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST);
|
|
|
|
} else if (reg.a_mode() == GsAlpha::BlendMode::SOURCE &&
|
|
reg.b_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
|
reg.c_mode() == GsAlpha::BlendMode::SOURCE &&
|
|
reg.d_mode() == GsAlpha::BlendMode::DEST) {
|
|
// (Cs - 0) * As + Cd
|
|
// Cs * As + (1) * CD
|
|
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_SRC_DST);
|
|
} else if (reg.a_mode() == GsAlpha::BlendMode::SOURCE &&
|
|
reg.b_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
|
reg.c_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
|
reg.d_mode() == GsAlpha::BlendMode::DEST) {
|
|
ASSERT(reg.fix() == 128);
|
|
// Cv = (Cs - 0) * FIX + Cd
|
|
// if fix = 128, it works out to 1.0
|
|
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_FIX_DST);
|
|
// src plus dest
|
|
} else if (reg.a_mode() == GsAlpha::BlendMode::SOURCE &&
|
|
reg.b_mode() == GsAlpha::BlendMode::DEST &&
|
|
reg.c_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
|
reg.d_mode() == GsAlpha::BlendMode::DEST) {
|
|
// 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::ZERO_OR_FIXED &&
|
|
reg.b_mode() == GsAlpha::BlendMode::SOURCE &&
|
|
reg.c_mode() == GsAlpha::BlendMode::SOURCE &&
|
|
reg.d_mode() == GsAlpha::BlendMode::DEST) {
|
|
// (0 - Cs) * As + Cd
|
|
// Cd - Cs * As
|
|
// s, d
|
|
mode.set_alpha_blend(DrawMode::AlphaBlend::ZERO_SRC_SRC_DST);
|
|
}
|
|
|
|
else {
|
|
lg::error("unsupported blend: a {} b {} c {} d {}", (int)reg.a_mode(), (int)reg.b_mode(),
|
|
(int)reg.c_mode(), (int)reg.d_mode());
|
|
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST);
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
|
|
void Sprite3::handle_alpha(u64 val,
|
|
SharedRenderState* /*render_state*/,
|
|
ScopedProfilerNode& /*prof*/) {
|
|
update_mode_from_alpha1(val, m_current_mode);
|
|
}
|
|
|
|
void Sprite3::do_block_common(SpriteMode mode,
|
|
u32 count,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
m_current_mode = m_default_mode;
|
|
for (u32 sprite_idx = 0; sprite_idx < count; sprite_idx++) {
|
|
if (m_sprite_idx == SPRITE_RENDERER_MAX_SPRITES) {
|
|
flush_sprites(render_state, prof, mode == ModeHUD);
|
|
}
|
|
|
|
if (mode == Mode2D && render_state->has_pc_data && m_enable_culling) {
|
|
// we can skip sprites that are out of view
|
|
// it's probably possible to do this for 3D as well.
|
|
auto bsphere = m_vec_data_2d[sprite_idx].xyz_sx;
|
|
bsphere.w() = std::max(bsphere.w(), m_vec_data_2d[sprite_idx].sy());
|
|
if (bsphere.w() == 0 || !sphere_in_view_ref(bsphere, render_state->camera_planes)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (render_state->version > GameVersion::Jak1) {
|
|
// glow code sets the matrix to -1,
|
|
// jak 2 adds:
|
|
// ibltz vi08, L4
|
|
// which is set from ilw.y vi08, 1(vi02)
|
|
if (m_vec_data_2d[sprite_idx].matrix() == -1) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
auto& adgif = m_adgif[sprite_idx];
|
|
handle_tex0(adgif.tex0_data, render_state, prof);
|
|
handle_tex1(adgif.tex1_data, render_state, prof);
|
|
if (GsRegisterAddress(adgif.clamp_addr) == GsRegisterAddress::ZBUF_1) {
|
|
handle_zbuf(adgif.clamp_data, render_state, prof);
|
|
} else {
|
|
handle_clamp(adgif.clamp_data, render_state, prof);
|
|
}
|
|
handle_alpha(adgif.alpha_data, render_state, prof);
|
|
|
|
u64 key = (((u64)m_current_tbp) << 32) | m_current_mode.as_int();
|
|
Bucket* bucket;
|
|
if (key == m_last_bucket_key) {
|
|
bucket = m_last_bucket;
|
|
} else {
|
|
auto it = m_sprite_buckets.find(key);
|
|
if (it == m_sprite_buckets.end()) {
|
|
bucket = &m_sprite_buckets[key];
|
|
bucket->key = key;
|
|
m_bucket_list.push_back(bucket);
|
|
} else {
|
|
bucket = &it->second;
|
|
}
|
|
}
|
|
u32 start_vtx_id = m_sprite_idx * 4;
|
|
bucket->ids.push_back(start_vtx_id);
|
|
bucket->ids.push_back(start_vtx_id + 1);
|
|
bucket->ids.push_back(start_vtx_id + 2);
|
|
bucket->ids.push_back(start_vtx_id + 3);
|
|
bucket->ids.push_back(UINT32_MAX);
|
|
|
|
auto& vert1 = m_vertices_3d.at(start_vtx_id + 0);
|
|
|
|
if (render_state->version == GameVersion::Jak3 || render_state->version == GameVersion::JakX) {
|
|
auto flag = m_vec_data_2d[sprite_idx].flag();
|
|
|
|
if ((flag & 0x10) || (flag & 0x20)) {
|
|
// these flags mean we need to swap vertex order around - not yet implemented since it's too
|
|
// hard to get right without this code running.
|
|
// ASSERT_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
vert1.xyz_sx = m_vec_data_2d[sprite_idx].xyz_sx;
|
|
vert1.quat_sy = m_vec_data_2d[sprite_idx].flag_rot_sy;
|
|
// ftoi'd in the original game, and I believe the VIF would discard the upper bits on pack
|
|
vert1.rgba = m_vec_data_2d[sprite_idx].rgba;
|
|
vert1.rgba.x() = (int)vert1.rgba.x() & 0xff;
|
|
vert1.rgba.y() = (int)vert1.rgba.y() & 0xff;
|
|
vert1.rgba.z() = (int)vert1.rgba.z() & 0xff;
|
|
vert1.rgba.w() = (int)vert1.rgba.w() & 0xff;
|
|
vert1.rgba /= 255;
|
|
vert1.flags_matrix[0] = m_vec_data_2d[sprite_idx].flag();
|
|
vert1.flags_matrix[1] = m_vec_data_2d[sprite_idx].matrix();
|
|
vert1.info[0] = 0; // hack
|
|
vert1.info[1] = m_current_mode.get_tcc_enable();
|
|
vert1.info[2] = 0;
|
|
vert1.info[3] = mode;
|
|
|
|
m_vertices_3d.at(start_vtx_id + 1) = vert1;
|
|
m_vertices_3d.at(start_vtx_id + 2) = vert1;
|
|
m_vertices_3d.at(start_vtx_id + 3) = vert1;
|
|
|
|
m_vertices_3d.at(start_vtx_id + 1).info[2] = 1;
|
|
m_vertices_3d.at(start_vtx_id + 2).info[2] = 3;
|
|
m_vertices_3d.at(start_vtx_id + 3).info[2] = 2;
|
|
|
|
// note that PC swaps the last two vertices
|
|
if (render_state->version == GameVersion::Jak3) {
|
|
auto flag = m_vec_data_2d[sprite_idx].flag();
|
|
switch (flag & 0x30) {
|
|
case 0x10:
|
|
// FLAG 16: 1, 0, 3, 2
|
|
m_vertices_3d.at(start_vtx_id + 0).info[2] = 0;
|
|
m_vertices_3d.at(start_vtx_id + 1).info[2] = 1;
|
|
m_vertices_3d.at(start_vtx_id + 2).info[2] = 3;
|
|
m_vertices_3d.at(start_vtx_id + 3).info[2] = 2;
|
|
break;
|
|
case 0x20:
|
|
// FLAG 32: 3, 2, 1, 0
|
|
m_vertices_3d.at(start_vtx_id + 0).info[2] = 3;
|
|
m_vertices_3d.at(start_vtx_id + 1).info[2] = 2;
|
|
m_vertices_3d.at(start_vtx_id + 2).info[2] = 0;
|
|
m_vertices_3d.at(start_vtx_id + 3).info[2] = 1;
|
|
break;
|
|
case 0x30:
|
|
// 2, 3, 0, 1
|
|
m_vertices_3d.at(start_vtx_id + 0).info[2] = 2;
|
|
m_vertices_3d.at(start_vtx_id + 1).info[2] = 3;
|
|
m_vertices_3d.at(start_vtx_id + 2).info[2] = 1;
|
|
m_vertices_3d.at(start_vtx_id + 3).info[2] = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
++m_sprite_idx;
|
|
}
|
|
}
|