#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/core.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: { 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: 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: 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 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) { 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; ++m_sprite_idx; } }