Files
jak-project/game/graphics/opengl_renderer/ocean/CommonOceanRenderer.cpp
T
Hat Kid 3d08d079c9 jak2: ocean renderer (#2142)
Initial implementation of the `ocean-mid`, `ocean-far` and `ocean-near`
renderers for Jak 2.

There's still a few things to sort out, mainly:

- [x] ~Backwards compatibility with Jak 1. The only thing that currently
stands in the way of that is figuring out a clean way to "un-hardcode"
the texture base pointer in C++ without creating a completely separate
`OceanTexture` class for Jak 2. One thing I thought of would be
modifying `BucketRenderer`'s virtual `init_textures` method to also pass
the `GameVersion`, but I'm not sure if there's a better way.~
- [x] ~The sudden transition from `ocean-near` to `ocean-mid`. Not sure
why it's happening or how to fix it.~
- [x] The ocean has two new methods in Jak 2, `ocean::89` and
`ocean::79`, one of which seems to be related to time of day sky colors.
~Even without them implemented, the end result looks quite close, so we
may be able to skip them?~ `ocean::89` generates `ocean-mid` envmap
textures, so it will likely be required, but will not be handled right
now.

Reverted the VU prologue removals because it made the tests fail.
2023-01-22 18:07:46 -05:00

540 lines
18 KiB
C++

#include "CommonOceanRenderer.h"
#include "common/log/log.h"
constexpr int OCEAN_TEX_TBP_JAK1 = 8160; // todo
constexpr int OCEAN_TEX_TBP_JAK2 = 672;
CommonOceanRenderer::CommonOceanRenderer() {
m_vertices.resize(4096 * 10); // todo decrease
for (auto& buf : m_indices) {
buf.resize(4096 * 10);
}
// create OpenGL objects
glGenBuffers(1, &m_ogl.vertex_buffer);
glGenBuffers(NUM_BUCKETS, m_ogl.index_buffer);
glGenVertexArrays(1, &m_ogl.vao);
// set up the vertex array
glBindVertexArray(m_ogl.vao);
for (int i = 0; i < NUM_BUCKETS; i++) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ogl.index_buffer[i]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indices[i].size() * sizeof(u32), nullptr,
GL_STREAM_DRAW);
}
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, m_vertices.size() * sizeof(Vertex), nullptr, GL_STREAM_DRAW);
// xyz
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, // location 0 in the shader
3, // 3 floats per vert
GL_FLOAT, // floats
GL_TRUE, // normalized, ignored,
sizeof(Vertex), //
(void*)offsetof(Vertex, xyz) // offset in array
);
// rgba
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, // location 1 in the shader
4, // 4 color components
GL_UNSIGNED_BYTE, // u8
GL_TRUE, // normalized (255 becomes 1)
sizeof(Vertex), //
(void*)offsetof(Vertex, rgba) //
);
// stq
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, // location 2 in the shader
3, // 2 floats per vert
GL_FLOAT, // floats
GL_FALSE, // normalized, ignored
sizeof(Vertex), //
(void*)offsetof(Vertex, stq) // offset in array
);
// byte data
glEnableVertexAttribArray(3);
glVertexAttribIPointer(3, // location 3 in the shader
4, //
GL_UNSIGNED_BYTE, // u8's
sizeof(Vertex), //
(void*)offsetof(Vertex, fog) // offset in array
);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
CommonOceanRenderer::~CommonOceanRenderer() {
glDeleteBuffers(1, &m_ogl.vertex_buffer);
glDeleteBuffers(3, m_ogl.index_buffer);
glDeleteVertexArrays(1, &m_ogl.vao);
}
void CommonOceanRenderer::init_for_near() {
m_next_free_vertex = 0;
for (auto& x : m_next_free_index) {
x = 0;
}
}
void CommonOceanRenderer::kick_from_near(const u8* data) {
bool eop = false;
u32 offset = 0;
while (!eop) {
GifTag tag(data + offset);
offset += 16;
if (tag.nreg() == 3) {
ASSERT(tag.pre());
if (GsPrim(tag.prim()).kind() == GsPrim::Kind::TRI_STRIP) {
handle_near_vertex_gif_data_strip(data, offset, tag.nloop());
} else {
handle_near_vertex_gif_data_fan(data, offset, tag.nloop());
}
offset += 16 * 3 * tag.nloop();
} else if (tag.nreg() == 1) {
handle_near_adgif(data, offset, tag.nloop());
offset += 16 * 1 * tag.nloop();
} else {
ASSERT(false);
}
eop = tag.eop();
}
}
void CommonOceanRenderer::handle_near_vertex_gif_data_strip(const u8* data, u32 offset, u32 loop) {
m_indices[m_current_bucket][m_next_free_index[m_current_bucket]++] = UINT32_MAX;
bool reset_last = false;
for (u32 i = 0; i < loop; i++) {
auto& dest_vert = m_vertices[m_next_free_vertex++];
// stq
memcpy(dest_vert.stq.data(), data + offset, 12);
offset += 16;
// rgba
dest_vert.rgba[0] = data[offset];
dest_vert.rgba[1] = data[offset + 4];
dest_vert.rgba[2] = data[offset + 8];
dest_vert.rgba[3] = data[offset + 12];
offset += 16;
// xyz
u32 x = 0, y = 0;
memcpy(&x, data + offset, 4);
memcpy(&y, data + offset + 4, 4);
u64 upper;
memcpy(&upper, data + offset + 8, 8);
u32 z = (upper >> 4) & 0xffffff;
offset += 16;
dest_vert.xyz[0] = (float)(x << 16) / (float)UINT32_MAX;
dest_vert.xyz[1] = (float)(y << 16) / (float)UINT32_MAX;
dest_vert.xyz[2] = (float)(z << 8) / (float)UINT32_MAX;
u8 f = (upper >> 36);
dest_vert.fog = f;
auto vidx = m_next_free_vertex - 1;
bool adc = upper & (1ull << 47);
if (!adc) {
m_indices[m_current_bucket][m_next_free_index[m_current_bucket]++] = vidx;
reset_last = false;
} else {
if (reset_last) {
m_next_free_index[m_current_bucket] -= 3;
}
m_indices[m_current_bucket][m_next_free_index[m_current_bucket]++] = UINT32_MAX;
m_indices[m_current_bucket][m_next_free_index[m_current_bucket]++] = vidx - 1;
m_indices[m_current_bucket][m_next_free_index[m_current_bucket]++] = vidx;
reset_last = true;
}
}
}
void CommonOceanRenderer::handle_near_vertex_gif_data_fan(const u8* data, u32 offset, u32 loop) {
u32 ind_of_fan_start = UINT32_MAX;
bool fan_running = false;
// :regs0 (gif-reg-id st) :regs1 (gif-reg-id rgbaq) :regs2 (gif-reg-id xyzf2)
for (u32 i = 0; i < loop; i++) {
auto& dest_vert = m_vertices[m_next_free_vertex++];
// stq
memcpy(dest_vert.stq.data(), data + offset, 12);
offset += 16;
// rgba
dest_vert.rgba[0] = data[offset];
dest_vert.rgba[1] = data[offset + 4];
dest_vert.rgba[2] = data[offset + 8];
dest_vert.rgba[3] = data[offset + 12];
offset += 16;
// xyz
u32 x = 0, y = 0;
memcpy(&x, data + offset, 4);
memcpy(&y, data + offset + 4, 4);
u64 upper;
memcpy(&upper, data + offset + 8, 8);
u32 z = (upper >> 4) & 0xffffff;
offset += 16;
dest_vert.xyz[0] = (float)(x << 16) / (float)UINT32_MAX;
dest_vert.xyz[1] = (float)(y << 16) / (float)UINT32_MAX;
dest_vert.xyz[2] = (float)(z << 8) / (float)UINT32_MAX;
u8 f = (upper >> 36);
dest_vert.fog = f;
auto vidx = m_next_free_vertex - 1;
if (ind_of_fan_start == UINT32_MAX) {
ind_of_fan_start = vidx;
} else {
if (fan_running) {
// hack to draw fans with strips. this isn't efficient, but fans happen extremely rarely
// (you basically have to put the camera intersecting the ocean and looking fwd)
m_indices[m_current_bucket][m_next_free_index[m_current_bucket]++] = UINT32_MAX;
m_indices[m_current_bucket][m_next_free_index[m_current_bucket]++] = vidx;
m_indices[m_current_bucket][m_next_free_index[m_current_bucket]++] = vidx - 1;
m_indices[m_current_bucket][m_next_free_index[m_current_bucket]++] = ind_of_fan_start;
} else {
fan_running = true;
}
}
}
}
void CommonOceanRenderer::handle_near_adgif(const u8* data, u32 offset, u32 count) {
u32 most_recent_tbp = 0;
for (u32 i = 0; i < count; i++) {
u64 value;
GsRegisterAddress addr;
memcpy(&value, data + offset + 16 * i, sizeof(u64));
memcpy(&addr, data + offset + 16 * i + 8, sizeof(GsRegisterAddress));
switch (addr) {
case GsRegisterAddress::MIPTBP1_1:
// ignore this, it's just mipmapping settings
break;
case GsRegisterAddress::TEX1_1: {
GsTex1 reg(value);
ASSERT(reg.mmag());
} break;
case GsRegisterAddress::CLAMP_1: {
bool s = value & 0b001;
bool t = value & 0b100;
ASSERT(s == t);
if (s) {
m_current_bucket = VertexBucket::ENV_MAP;
}
} break;
case GsRegisterAddress::TEX0_1: {
GsTex0 reg(value);
ASSERT(reg.tfx() == GsTex0::TextureFunction::MODULATE);
if (!reg.tcc()) {
m_current_bucket = VertexBucket::RGB_TEXTURE;
}
most_recent_tbp = reg.tbp0();
} break;
case GsRegisterAddress::ALPHA_1: {
GsAlpha reg(value);
if (reg.a_mode() == GsAlpha::BlendMode::SOURCE &&
reg.b_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
reg.c_mode() == GsAlpha::BlendMode::DEST && reg.d_mode() == GsAlpha::BlendMode::DEST) {
m_current_bucket = VertexBucket::ENV_MAP;
}
} break;
case GsRegisterAddress::FRAME_1: {
u32 mask = value >> 32;
if (mask) {
m_current_bucket = VertexBucket::ALPHA;
}
} break;
default:
lg::debug("reg: {}", register_address_name(addr));
break;
}
}
if (m_current_bucket == VertexBucket::ENV_MAP) {
m_envmap_tex = most_recent_tbp;
}
if (m_vertices.size() - 128 < m_next_free_vertex) {
ASSERT(false); // add more vertices.
}
}
void CommonOceanRenderer::flush_near(SharedRenderState* render_state, ScopedProfilerNode& prof) {
glBindVertexArray(m_ogl.vao);
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
glEnable(GL_PRIMITIVE_RESTART);
glPrimitiveRestartIndex(UINT32_MAX);
glBufferData(GL_ARRAY_BUFFER, m_next_free_vertex * sizeof(Vertex), m_vertices.data(),
GL_STREAM_DRAW);
render_state->shaders[ShaderId::OCEAN_COMMON].activate();
glUniform4f(glGetUniformLocation(render_state->shaders[ShaderId::OCEAN_COMMON].id(), "fog_color"),
render_state->fog_color[0] / 255.f, render_state->fog_color[1] / 255.f,
render_state->fog_color[2] / 255.f, render_state->fog_intensity / 255);
glDepthMask(GL_FALSE);
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glDepthFunc(GL_GEQUAL);
for (int bucket = 0; bucket < 3; bucket++) {
switch (bucket) {
case 0: {
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
glBlendEquation(GL_FUNC_ADD);
auto tbp =
render_state->version == GameVersion::Jak1 ? OCEAN_TEX_TBP_JAK1 : OCEAN_TEX_TBP_JAK2;
auto tex = render_state->texture_pool->lookup(tbp);
if (!tex) {
tex = render_state->texture_pool->get_placeholder_texture();
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, *tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glUniform1i(
glGetUniformLocation(render_state->shaders[ShaderId::OCEAN_COMMON].id(), "tex_T0"), 0);
glUniform1i(
glGetUniformLocation(render_state->shaders[ShaderId::OCEAN_COMMON].id(), "bucket"), 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
break;
case 1:
glBlendFuncSeparate(GL_ZERO, GL_ONE, GL_ONE, GL_ZERO);
glUniform1f(
glGetUniformLocation(render_state->shaders[ShaderId::OCEAN_COMMON].id(), "alpha_mult"),
1.f);
glUniform1i(
glGetUniformLocation(render_state->shaders[ShaderId::OCEAN_COMMON].id(), "bucket"), 1);
break;
case 2:
auto tex = render_state->texture_pool->lookup(m_envmap_tex);
if (!tex) {
tex = render_state->texture_pool->get_placeholder_texture();
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, *tex);
glBlendFuncSeparate(GL_DST_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
glBlendEquation(GL_FUNC_ADD);
glUniform1i(
glGetUniformLocation(render_state->shaders[ShaderId::OCEAN_COMMON].id(), "bucket"), 2);
break;
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ogl.index_buffer[bucket]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_next_free_index[bucket] * sizeof(u32),
m_indices[bucket].data(), GL_STREAM_DRAW);
glDrawElements(GL_TRIANGLE_STRIP, m_next_free_index[bucket], GL_UNSIGNED_INT, nullptr);
prof.add_draw_call();
prof.add_tri(m_next_free_index[bucket]);
}
}
void CommonOceanRenderer::kick_from_mid(const u8* data) {
bool eop = false;
u32 offset = 0;
while (!eop) {
GifTag tag(data + offset);
offset += 16;
// unpack registers.
// faster to do it once outside of the nloop loop.
GifTag::RegisterDescriptor reg_desc[16];
u32 nreg = tag.nreg();
for (u32 i = 0; i < nreg; i++) {
reg_desc[i] = tag.reg(i);
}
auto format = tag.flg();
if (format == GifTag::Format::PACKED) {
if (tag.nreg() == 1) {
ASSERT(!tag.pre());
ASSERT(tag.nloop() == 5);
handle_mid_adgif(data, offset);
offset += 5 * 16;
} else {
ASSERT(tag.nreg() == 3);
ASSERT(tag.pre());
m_current_bucket = GsPrim(tag.prim()).abe() ? 1 : 0;
int count = tag.nloop();
if (GsPrim(tag.prim()).kind() == GsPrim::Kind::TRI_STRIP) {
handle_near_vertex_gif_data_strip(data, offset, tag.nloop());
} else {
handle_near_vertex_gif_data_fan(data, offset, tag.nloop());
}
offset += 3 * 16 * count;
// todo handle.
}
} else {
ASSERT(false); // format not packed or reglist.
}
eop = tag.eop();
}
}
void CommonOceanRenderer::handle_mid_adgif(const u8* data, u32 offset) {
u32 most_recent_tbp = 0;
for (u32 i = 0; i < 5; i++) {
u64 value;
GsRegisterAddress addr;
memcpy(&value, data + offset + 16 * i, sizeof(u64));
memcpy(&addr, data + offset + 16 * i + 8, sizeof(GsRegisterAddress));
switch (addr) {
case GsRegisterAddress::MIPTBP1_1:
case GsRegisterAddress::MIPTBP2_1:
// ignore this, it's just mipmapping settings
break;
case GsRegisterAddress::TEX1_1: {
GsTex1 reg(value);
ASSERT(reg.mmag());
} break;
case GsRegisterAddress::CLAMP_1: {
bool s = value & 0b001;
bool t = value & 0b100;
ASSERT(s == t);
} break;
case GsRegisterAddress::TEX0_1: {
GsTex0 reg(value);
ASSERT(reg.tfx() == GsTex0::TextureFunction::MODULATE);
most_recent_tbp = reg.tbp0();
} break;
case GsRegisterAddress::ALPHA_1: {
} break;
default:
lg::debug("reg: {}", register_address_name(addr));
break;
}
}
if (most_recent_tbp != OCEAN_TEX_TBP_JAK2) {
m_envmap_tex = most_recent_tbp;
}
if (m_vertices.size() - 128 < m_next_free_vertex) {
ASSERT(false); // add more vertices.
}
}
void CommonOceanRenderer::init_for_mid() {
m_next_free_vertex = 0;
for (auto& x : m_next_free_index) {
x = 0;
}
}
void reverse_indices(u32* indices, u32 count) {
if (count) {
for (u32 a = 0, b = count - 1; a < b; a++, b--) {
std::swap(indices[a], indices[b]);
}
}
}
void CommonOceanRenderer::flush_mid(SharedRenderState* render_state, ScopedProfilerNode& prof) {
glBindVertexArray(m_ogl.vao);
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
glEnable(GL_PRIMITIVE_RESTART);
glPrimitiveRestartIndex(UINT32_MAX);
glBufferData(GL_ARRAY_BUFFER, m_next_free_vertex * sizeof(Vertex), m_vertices.data(),
GL_STREAM_DRAW);
render_state->shaders[ShaderId::OCEAN_COMMON].activate();
glUniform4f(glGetUniformLocation(render_state->shaders[ShaderId::OCEAN_COMMON].id(), "fog_color"),
render_state->fog_color[0] / 255.f, render_state->fog_color[1] / 255.f,
render_state->fog_color[2] / 255.f, render_state->fog_intensity / 255);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_ALWAYS);
glDisable(GL_BLEND);
// note:
// there are some places where the game draws the same section of ocean twice, in this order:
// - low poly mesh with ocean texture
// - low poly mesh with envmap texture
// - high poly mesh with ocean texture (overwrites previous draw)
// - high poly mesh with envmap texture (overwrites previous draw)
// we draw all ocean textures together and all envmap textures togther. luckily, there's a trick
// we can use to get the same result.
// first, we'll draw all ocean textures. The high poly mesh is drawn second, so it wins.
// then, we'll draw all envmaps, but with two changes:
// - first, we draw it in reverse, so the high poly versions are drawn first
// - second, we'll modify the shader to set alpha = 0 of the destination. when the low poly
// version is drawn on top, it won't draw at all because of the blending mode
// (s_factor = DST_ALPHA, d_factor = 1)
// draw it in reverse
reverse_indices(m_indices[1].data(), m_next_free_index[1]);
for (int bucket = 0; bucket < 2; bucket++) {
switch (bucket) {
case 0: {
auto tbp =
render_state->version == GameVersion::Jak1 ? OCEAN_TEX_TBP_JAK1 : OCEAN_TEX_TBP_JAK2;
auto tex = render_state->texture_pool->lookup(tbp);
if (!tex) {
tex = render_state->texture_pool->get_placeholder_texture();
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, *tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glUniform1i(
glGetUniformLocation(render_state->shaders[ShaderId::OCEAN_COMMON].id(), "tex_T0"), 0);
glUniform1i(
glGetUniformLocation(render_state->shaders[ShaderId::OCEAN_COMMON].id(), "bucket"), 3);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
break;
case 1:
glEnable(GL_BLEND);
auto tex = render_state->texture_pool->lookup(m_envmap_tex);
if (!tex) {
tex = render_state->texture_pool->get_placeholder_texture();
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, *tex);
glBlendFuncSeparate(GL_DST_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
glBlendEquation(GL_FUNC_ADD);
glUniform1i(
glGetUniformLocation(render_state->shaders[ShaderId::OCEAN_COMMON].id(), "bucket"), 4);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
break;
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ogl.index_buffer[bucket]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_next_free_index[bucket] * sizeof(u32),
m_indices[bucket].data(), GL_STREAM_DRAW);
glDrawElements(GL_TRIANGLE_STRIP, m_next_free_index[bucket], GL_UNSIGNED_INT, nullptr);
prof.add_draw_call();
prof.add_tri(m_next_free_index[bucket]);
}
}