mirror of
https://github.com/open-goal/jak-project
synced 2026-08-15 04:51:00 -04:00
71cb1aef6f
This PR adds a feature to merc2 to update vertices. This will be needed to efficient do effects like blerc/ripple/texture scroll. It's enabled for blerc in jak 1 and jak 2, but with a few disclaimers: - currently we still use the mips2c blerc implementation, which is slow and has some "jittering" because of integer precision. When porting to PC, there was an additional synchronization problem because blerc overwrites the merc data as its being read by the renderers. I _think_ this wasn't an issue on PS2 because the blerc dma is higher priority than the VIF1 DMA, but I'm not certain. Either way, I had to add a mutex for this on PC to avoid very slight flickering/gaps. This isn't ideal for performance, but still beats generic by a significant amount in every place I tested. If you see merc taking 2ms to draw, it is likely because it is stuck waiting on blerc to finish. This will go away once blerc itself is ported to C++. - in jak 1, we end up using generic in some cases where we could use merc. In particular maia in village3 hut. This will be fixed later once we can use merc in more places. I don't want to mess with the merc/generic selection logic when we're hopefully going to get rid of it soon. - There is no support for ripple or texture scroll. These use generic on jak 1, and remain broken on jak 2. - Like with `emerc`, jak 1 has a toggle to go back to the old behavior `*blerc-hack*`. - In most cases, toggling this causes no visual differences. One exception is Gol's teeth. I believe this is caused by texture coordinate rounding issues, where generic has an additional float -> int -> float compared to PC merc. It is very hard to notice so I'm not going to worry about it.
200 lines
6.8 KiB
C++
200 lines
6.8 KiB
C++
#include "OceanMidAndFar.h"
|
|
|
|
#include "third-party/imgui/imgui.h"
|
|
|
|
OceanMidAndFar::OceanMidAndFar(const std::string& name, int my_id)
|
|
: BucketRenderer(name, my_id), m_direct(name, my_id, 4096), m_texture_renderer(true) {}
|
|
|
|
void OceanMidAndFar::draw_debug_window() {
|
|
m_texture_renderer.draw_debug_window();
|
|
m_direct.draw_debug_window();
|
|
}
|
|
|
|
void OceanMidAndFar::init_textures(TexturePool& pool, GameVersion version) {
|
|
m_texture_renderer.init_textures(pool, version);
|
|
}
|
|
|
|
void OceanMidAndFar::render(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
// skip if disabled
|
|
if (!m_enabled) {
|
|
while (dma.current_tag_offset() != render_state->next_bucket) {
|
|
dma.read_and_advance();
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch (render_state->version) {
|
|
case GameVersion::Jak1:
|
|
render_jak1(dma, render_state, prof);
|
|
break;
|
|
case GameVersion::Jak2:
|
|
render_jak2(dma, render_state, prof);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void OceanMidAndFar::render_jak1(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
// 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;
|
|
}
|
|
m_direct.reset_state();
|
|
|
|
{
|
|
auto p = prof.make_scoped_child("texture");
|
|
m_texture_renderer.handle_ocean_texture_jak1(dma, render_state, p);
|
|
}
|
|
|
|
handle_ocean_far(dma, render_state, prof);
|
|
m_direct.flush_pending(render_state, prof);
|
|
|
|
m_direct.set_mipmap(true);
|
|
handle_ocean_mid(dma, render_state, prof);
|
|
|
|
auto final_next = dma.read_and_advance();
|
|
ASSERT(final_next.vifcode0().kind == VifCode::Kind::NOP &&
|
|
final_next.vifcode1().kind == VifCode::Kind::NOP && final_next.size_bytes == 0);
|
|
for (int i = 0; i < 4; i++) {
|
|
dma.read_and_advance();
|
|
}
|
|
ASSERT(dma.current_tag_offset() == render_state->next_bucket);
|
|
|
|
m_direct.flush_pending(render_state, prof);
|
|
m_direct.set_mipmap(false);
|
|
}
|
|
|
|
void advance_and_print_dma(DmaFollower& dma) {
|
|
auto data = dma.read_and_advance();
|
|
printf(
|
|
"dma transfer:\n%ssize: %d\nvif0: %s, data: %d\nvif1: %s, data: %d, imm: "
|
|
"%d\n\n",
|
|
dma.current_tag().print().c_str(), data.size_bytes, data.vifcode0().print().c_str(),
|
|
data.vif0(), data.vifcode1().print().c_str(), data.vifcode1().num, data.vifcode1().immediate);
|
|
}
|
|
|
|
void OceanMidAndFar::render_jak2(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
// jump to bucket
|
|
auto data0 = dma.read_and_advance();
|
|
ASSERT(data0.vif1() == 0 || data0.vifcode1().kind == VifCode::Kind::NOP);
|
|
ASSERT(data0.vif0() == 0 || data0.vifcode0().kind == VifCode::Kind::MARK);
|
|
ASSERT(data0.size_bytes == 0);
|
|
|
|
// see if bucket is empty or not
|
|
if (dma.current_tag_offset() == render_state->next_bucket) {
|
|
// fmt::print("ocean-mid-far: early exit!\n");
|
|
return;
|
|
}
|
|
m_direct.reset_state();
|
|
|
|
// TODO handle ocean::89 and ocean::79
|
|
// handle_ocean_89_jak2(dma, render_state, prof);
|
|
|
|
{
|
|
auto p = prof.make_scoped_child("texture");
|
|
m_texture_renderer.handle_ocean_texture_jak2(dma, render_state, p);
|
|
}
|
|
|
|
// handle_ocean_79_jak2(dma, render_state, prof);
|
|
handle_ocean_far(dma, render_state, prof);
|
|
m_direct.flush_pending(render_state, prof);
|
|
|
|
m_direct.set_mipmap(true);
|
|
handle_ocean_mid(dma, render_state, prof);
|
|
|
|
auto final_next = dma.read_and_advance();
|
|
ASSERT(final_next.vifcode0().kind == VifCode::Kind::NOP &&
|
|
final_next.vifcode1().kind == VifCode::Kind::NOP && final_next.size_bytes == 0);
|
|
for (int i = 0; i < 2; i++) {
|
|
dma.read_and_advance();
|
|
}
|
|
ASSERT(dma.current_tag_offset() == render_state->next_bucket);
|
|
|
|
// auto transfers = 0;
|
|
// // print the entire chain
|
|
// printf("START OCEAN MID FAR DMA!!!!!!!\n");
|
|
// while (dma.current_tag_offset() != render_state->next_bucket) {
|
|
// auto data = dma.read_and_advance();
|
|
// printf(
|
|
// "dma transfer %d:\n%ssize: %d\nvif0: %s, data: %d\nvif1: %s, data: %d, imm: "
|
|
// "%d\n\n",
|
|
// transfers, dma.current_tag().print().c_str(), data.size_bytes,
|
|
// data.vifcode0().print().c_str(), data.vif0(), data.vifcode1().print().c_str(),
|
|
// data.vifcode1().num, data.vifcode1().immediate);
|
|
// transfers++;
|
|
// }
|
|
// printf("transfers: %d\n\n", transfers);
|
|
|
|
m_direct.flush_pending(render_state, prof);
|
|
m_direct.set_mipmap(false);
|
|
}
|
|
|
|
void OceanMidAndFar::handle_ocean_far(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
auto init_data = dma.read_and_advance();
|
|
ASSERT(init_data.size_bytes == 160);
|
|
u8 init_data_buffer[160];
|
|
memcpy(init_data_buffer, init_data.data, 160);
|
|
|
|
// this is a bit of a hack, but it patches the ta0 to 0 in
|
|
// (set! (-> (the-as (pointer gs-texa) s4-0) 8) (new 'static 'gs-texa :ta0 #x80 :ta1 #x80))
|
|
// TODO figure out if we actually have do something here.
|
|
u8 val = 0;
|
|
memcpy(init_data_buffer + 80, &val, 1);
|
|
m_direct.render_gif(init_data_buffer, 160, render_state, prof);
|
|
|
|
while (dma.current_tag().kind == DmaTag::Kind::CNT &&
|
|
dma.current_tag_vifcode0().kind == VifCode::Kind::NOP) {
|
|
auto data = dma.read_and_advance();
|
|
ASSERT(data.vifcode0().kind == VifCode::Kind::NOP);
|
|
ASSERT(data.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
ASSERT(data.size_bytes / 16 == data.vifcode1().immediate);
|
|
m_direct.render_gif(data.data, data.size_bytes, render_state, prof);
|
|
}
|
|
}
|
|
|
|
bool is_end_tag(const DmaTag& tag, const VifCode& v0, const VifCode& v1) {
|
|
return tag.qwc == 0 && tag.kind == DmaTag::Kind::NEXT && v0.kind == VifCode::Kind::NOP &&
|
|
v1.kind == VifCode::Kind::NOP;
|
|
}
|
|
void OceanMidAndFar::handle_ocean_mid(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
if (dma.current_tag_vifcode0().kind == VifCode::Kind::BASE) {
|
|
switch (render_state->version) {
|
|
case GameVersion::Jak1:
|
|
m_mid_renderer.run(dma, render_state, prof);
|
|
break;
|
|
case GameVersion::Jak2:
|
|
m_mid_renderer.run_jak2(dma, render_state, prof);
|
|
}
|
|
} else {
|
|
// not drawing
|
|
return;
|
|
}
|
|
|
|
while (!is_end_tag(dma.current_tag(), dma.current_tag_vifcode0(), dma.current_tag_vifcode1())) {
|
|
dma.read_and_advance();
|
|
}
|
|
}
|
|
|
|
void handle_ocean_89_jak2(DmaFollower&, SharedRenderState*, ScopedProfilerNode&) {}
|
|
|
|
void handle_ocean_79_jak2(DmaFollower&, SharedRenderState*, ScopedProfilerNode&) {} |