mirror of
https://github.com/open-goal/jak-project
synced 2026-06-29 19:42:41 -04:00
93afb02cf4
This includes all the collision stuff needed to spawn `target`, decompiles the sparticle code and adds some of the PC hacks needed for merc to run (it doesn't work quite right and looks bad, likely due to a combination of code copied from Jak 2 and the time of day hacks). There are a bunch of temporary hacks (see commits) in place to prevent the game from crashing quite as much, but it is still extremely prone to doing so due to lots of missing functions/potentially bad decomp. --------- Co-authored-by: water <awaterford111445@gmail.com>
640 lines
26 KiB
C++
640 lines
26 KiB
C++
#include "Sprite3.h"
|
|
#include "game/graphics/opengl_renderer/dma_helpers.h"
|
|
|
|
namespace {
|
|
/*!
|
|
* Does the next DMA transfer look like the frame data for sprite distort?
|
|
*/
|
|
bool looks_like_distort_frame_data(const DmaFollower& dma) {
|
|
return dma.current_tag().kind == DmaTag::Kind::CNT &&
|
|
dma.current_tag_vifcode0().kind == VifCode::Kind::NOP &&
|
|
dma.current_tag_vifcode1().kind == VifCode::Kind::UNPACK_V4_32;
|
|
}
|
|
|
|
constexpr int SPRITE_RENDERER_MAX_DISTORT_SPRITES =
|
|
256 * 12; // size of sprite-aux-list in GOAL code * SPRITE_MAX_AMOUNT_MULT
|
|
} // namespace
|
|
|
|
void Sprite3::opengl_setup_distort() {
|
|
// Create framebuffer to snapshot current render to a texture that can be bound for the distort
|
|
// shader This will represent tex0 from the original GS data
|
|
glGenFramebuffers(1, &m_distort_ogl.fbo);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, m_distort_ogl.fbo);
|
|
|
|
glGenTextures(1, &m_distort_ogl.fbo_texture);
|
|
glBindTexture(GL_TEXTURE_2D, m_distort_ogl.fbo_texture);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_distort_ogl.fbo_width, m_distort_ogl.fbo_height, 0,
|
|
GL_RGB, GL_UNSIGNED_BYTE, NULL);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
// Texture clamping here matches the GS init data for distort
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
|
m_distort_ogl.fbo_texture, 0);
|
|
|
|
ASSERT(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
|
|
// Non-instancing
|
|
// ----------------------
|
|
glGenBuffers(1, &m_distort_ogl.vertex_buffer);
|
|
glGenVertexArrays(1, &m_distort_ogl.vao);
|
|
glBindVertexArray(m_distort_ogl.vao);
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_distort_ogl.vertex_buffer);
|
|
// note: each sprite shares a single vertex per slice, account for that here
|
|
int distort_vert_buffer_len =
|
|
SPRITE_RENDERER_MAX_DISTORT_SPRITES *
|
|
((5 - 1) * 11 + 1); // max * ((verts_per_slice - 1) * max_slices + 1)
|
|
glBufferData(GL_ARRAY_BUFFER, distort_vert_buffer_len * sizeof(SpriteDistortVertex), nullptr,
|
|
GL_DYNAMIC_DRAW);
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, // location 0 in the shader
|
|
3, // 3 floats per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // don't normalize, ignored
|
|
sizeof(SpriteDistortVertex), //
|
|
(void*)offsetof(SpriteDistortVertex, xyz) // offset in array
|
|
);
|
|
glEnableVertexAttribArray(1);
|
|
glVertexAttribPointer(1, // location 1 in the shader
|
|
2, // 2 floats per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // don't normalize, ignored
|
|
sizeof(SpriteDistortVertex), //
|
|
(void*)offsetof(SpriteDistortVertex, st) // offset in array
|
|
);
|
|
|
|
// note: add one extra element per sprite that marks the end of a triangle strip
|
|
int distort_idx_buffer_len = SPRITE_RENDERER_MAX_DISTORT_SPRITES *
|
|
((5 * 11) + 1); // max * ((verts_per_slice * max_slices) + 1)
|
|
glGenBuffers(1, &m_distort_ogl.index_buffer);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_distort_ogl.index_buffer);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, distort_idx_buffer_len * sizeof(u32), nullptr,
|
|
GL_DYNAMIC_DRAW);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
glBindVertexArray(0);
|
|
|
|
m_sprite_distorter_vertices.resize(distort_vert_buffer_len);
|
|
m_sprite_distorter_indices.resize(distort_idx_buffer_len);
|
|
m_sprite_distorter_frame_data.resize(SPRITE_RENDERER_MAX_DISTORT_SPRITES);
|
|
|
|
// Instancing
|
|
// ----------------------
|
|
glGenVertexArrays(1, &m_distort_instanced_ogl.vao);
|
|
glBindVertexArray(m_distort_instanced_ogl.vao);
|
|
|
|
int distort_max_sprite_slices = 0;
|
|
for (int i = 3; i < 12; i++) {
|
|
// For each 'resolution', there can be that many slices
|
|
distort_max_sprite_slices += i;
|
|
}
|
|
|
|
glGenBuffers(1, &m_distort_instanced_ogl.vertex_buffer);
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_distort_instanced_ogl.vertex_buffer);
|
|
|
|
int distort_instanced_vert_buffer_len = distort_max_sprite_slices * 5; // 5 vertices per slice
|
|
glBufferData(GL_ARRAY_BUFFER, distort_instanced_vert_buffer_len * sizeof(SpriteDistortVertex),
|
|
nullptr, GL_STREAM_DRAW);
|
|
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, // location 0 in the shader
|
|
3, // 3 floats per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // don't normalize, ignored
|
|
sizeof(SpriteDistortVertex), //
|
|
(void*)offsetof(SpriteDistortVertex, xyz) // offset in array
|
|
);
|
|
glEnableVertexAttribArray(1);
|
|
glVertexAttribPointer(1, // location 1 in the shader
|
|
2, // 2 floats per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // don't normalize, ignored
|
|
sizeof(SpriteDistortVertex), //
|
|
(void*)offsetof(SpriteDistortVertex, st) // offset in array
|
|
);
|
|
|
|
glGenBuffers(1, &m_distort_instanced_ogl.instance_buffer);
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_distort_instanced_ogl.instance_buffer);
|
|
|
|
int distort_instance_buffer_len = SPRITE_RENDERER_MAX_DISTORT_SPRITES;
|
|
glBufferData(GL_ARRAY_BUFFER, distort_instance_buffer_len * sizeof(SpriteDistortInstanceData),
|
|
nullptr, GL_DYNAMIC_DRAW);
|
|
|
|
glEnableVertexAttribArray(2);
|
|
glVertexAttribPointer(2, // location 2 in the shader
|
|
4, // 4 floats per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // normalized, ignored,
|
|
sizeof(SpriteDistortInstanceData), //
|
|
(void*)offsetof(SpriteDistortInstanceData, x_y_z_s) // offset in array
|
|
);
|
|
glEnableVertexAttribArray(3);
|
|
glVertexAttribPointer(3, // location 3 in the shader
|
|
4, // 4 floats per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // normalized, ignored,
|
|
sizeof(SpriteDistortInstanceData), //
|
|
(void*)offsetof(SpriteDistortInstanceData, sx_sy_sz_t) // offset in array
|
|
);
|
|
|
|
glVertexAttribDivisor(2, 1);
|
|
glVertexAttribDivisor(3, 1);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
glBindVertexArray(0);
|
|
|
|
m_sprite_distorter_vertices_instanced.resize(distort_instanced_vert_buffer_len);
|
|
|
|
for (int i = 3; i < 12; i++) {
|
|
auto vec = std::vector<SpriteDistortInstanceData>();
|
|
vec.resize(distort_instance_buffer_len);
|
|
|
|
m_sprite_distorter_instances_by_res[i] = vec;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Run the sprite distorter.
|
|
*/
|
|
void Sprite3::render_distorter(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
// Read DMA
|
|
{
|
|
auto prof_node = prof.make_scoped_child("dma");
|
|
distort_dma(render_state->version, dma, prof_node);
|
|
}
|
|
|
|
if (!m_enabled || !m_distort_enable) {
|
|
// Distort disabled, we can stop here since all the DMA has been read
|
|
return;
|
|
}
|
|
|
|
// Set up vertex data
|
|
{
|
|
auto prof_node = prof.make_scoped_child("setup");
|
|
if (m_enable_distort_instancing) {
|
|
distort_setup_instanced(prof_node);
|
|
} else {
|
|
distort_setup(prof_node);
|
|
}
|
|
}
|
|
|
|
// Draw
|
|
{
|
|
auto prof_node = prof.make_scoped_child("drawing");
|
|
if (m_enable_distort_instancing) {
|
|
distort_draw_instanced(render_state, prof_node);
|
|
} else {
|
|
distort_draw(render_state, prof_node);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Reads all sprite distort related DMA packets.
|
|
*/
|
|
void Sprite3::distort_dma(GameVersion version, DmaFollower& dma, ScopedProfilerNode& /*prof*/) {
|
|
// set the expected values per game version first
|
|
u32 expect_zbp, expect_th;
|
|
switch (version) {
|
|
case GameVersion::Jak1:
|
|
expect_zbp = 0x1c0;
|
|
expect_th = 8;
|
|
break;
|
|
case GameVersion::Jak2:
|
|
case GameVersion::Jak3:
|
|
expect_zbp = 0x130;
|
|
expect_th = 9;
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
return;
|
|
}
|
|
|
|
// First should be the GS setup
|
|
auto sprite_distorter_direct_setup = dma.read_and_advance();
|
|
ASSERT(sprite_distorter_direct_setup.vifcode0().kind == VifCode::Kind::NOP);
|
|
ASSERT(sprite_distorter_direct_setup.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
ASSERT(sprite_distorter_direct_setup.vifcode1().immediate == 7);
|
|
memcpy(&m_sprite_distorter_setup, sprite_distorter_direct_setup.data, 7 * 16);
|
|
|
|
auto gif_tag = m_sprite_distorter_setup.gif_tag;
|
|
ASSERT(gif_tag.nloop() == 1);
|
|
ASSERT(gif_tag.eop() == true);
|
|
ASSERT(gif_tag.nreg() == 6);
|
|
ASSERT(gif_tag.reg(0) == GifTag::RegisterDescriptor::AD);
|
|
|
|
auto zbuf1 = m_sprite_distorter_setup.zbuf;
|
|
ASSERT(zbuf1.zbp() == expect_zbp);
|
|
ASSERT(zbuf1.zmsk() == true);
|
|
ASSERT(zbuf1.psm() == TextureFormat::PSMZ24);
|
|
|
|
auto tex0 = m_sprite_distorter_setup.tex0;
|
|
ASSERT(tex0.tbw() == 8);
|
|
ASSERT(tex0.tw() == 9);
|
|
ASSERT(tex0.th() == expect_th);
|
|
|
|
auto tex1 = m_sprite_distorter_setup.tex1;
|
|
ASSERT(tex1.mmag() == 1);
|
|
ASSERT(tex1.mmin() == 1);
|
|
|
|
auto alpha = m_sprite_distorter_setup.alpha;
|
|
ASSERT(alpha.a_mode() == GsAlpha::BlendMode::SOURCE);
|
|
ASSERT(alpha.b_mode() == GsAlpha::BlendMode::DEST);
|
|
ASSERT(alpha.c_mode() == GsAlpha::BlendMode::SOURCE);
|
|
ASSERT(alpha.d_mode() == GsAlpha::BlendMode::DEST);
|
|
|
|
// Next is the aspect used by the sine tables (PC only)
|
|
//
|
|
// This was added to let the renderer reliably detect when the sine tables changed,
|
|
// which is whenever the aspect ratio changed. However, the tables aren't always
|
|
// updated on the same frame that the aspect changed, so this just lets the game
|
|
// easily notify the renderer when it finally does get updated.
|
|
auto sprite_distort_tables_aspect = dma.read_and_advance();
|
|
ASSERT(sprite_distort_tables_aspect.size_bytes == 16);
|
|
ASSERT(sprite_distort_tables_aspect.vifcode1().kind == VifCode::Kind::PC_PORT);
|
|
memcpy(&m_sprite_distorter_sine_tables_aspect, sprite_distort_tables_aspect.data,
|
|
sizeof(math::Vector4f));
|
|
|
|
// Next thing should be the sine tables
|
|
auto sprite_distorter_tables = dma.read_and_advance();
|
|
unpack_to_stcycl(&m_sprite_distorter_sine_tables, sprite_distorter_tables,
|
|
VifCode::Kind::UNPACK_V4_32, 4, 4, 0x8b * 16, 0x160, false, false);
|
|
|
|
ASSERT(GsPrim(m_sprite_distorter_sine_tables.gs_gif_tag.prim()).kind() ==
|
|
GsPrim::Kind::TRI_STRIP);
|
|
|
|
// Finally, should be frame data packets (containing sprites)
|
|
// Up to 170 sprites will be DMA'd at a time followed by a mscalf,
|
|
// and this process can happen twice up to a maximum of 256 sprites DMA'd
|
|
// (256 is the size of sprite-aux-list which drives this).
|
|
int sprite_idx = 0;
|
|
m_distort_stats.total_sprites = 0;
|
|
|
|
while (looks_like_distort_frame_data(dma)) {
|
|
math::Vector<u32, 4> num_sprites_vec;
|
|
|
|
// Read sprite packets
|
|
do {
|
|
int qwc = dma.current_tag().qwc;
|
|
int dest = dma.current_tag_vifcode1().immediate;
|
|
auto distort_data = dma.read_and_advance();
|
|
|
|
if (dest == 511) {
|
|
// VU address 511 specifies the number of sprites
|
|
unpack_to_no_stcycl(&num_sprites_vec, distort_data, VifCode::Kind::UNPACK_V4_32, 16, dest,
|
|
false, false);
|
|
} else {
|
|
// VU address >= 512 is the actual vertex data
|
|
ASSERT(dest >= 512);
|
|
ASSERT(sprite_idx + (qwc / 3) <= (int)m_sprite_distorter_frame_data.capacity());
|
|
|
|
unpack_to_no_stcycl(&m_sprite_distorter_frame_data.at(sprite_idx), distort_data,
|
|
VifCode::Kind::UNPACK_V4_32, qwc * 16, dest, false, false);
|
|
|
|
sprite_idx += qwc / 3;
|
|
}
|
|
} while (looks_like_distort_frame_data(dma));
|
|
|
|
// Sprite packets should always end with a mscalf flush
|
|
ASSERT(dma.current_tag().kind == DmaTag::Kind::CNT);
|
|
ASSERT(dma.current_tag_vifcode0().kind == VifCode::Kind::MSCALF);
|
|
ASSERT(dma.current_tag_vifcode1().kind == VifCode::Kind::FLUSH);
|
|
dma.read_and_advance();
|
|
|
|
m_distort_stats.total_sprites += num_sprites_vec.x();
|
|
}
|
|
|
|
// Done
|
|
ASSERT(m_distort_stats.total_sprites <= SPRITE_RENDERER_MAX_DISTORT_SPRITES);
|
|
}
|
|
|
|
/*!
|
|
* Sets up OpenGL data for each distort sprite.
|
|
*/
|
|
void Sprite3::distort_setup(ScopedProfilerNode& /*prof*/) {
|
|
m_distort_stats.total_tris = 0;
|
|
|
|
m_sprite_distorter_vertices.clear();
|
|
m_sprite_distorter_indices.clear();
|
|
|
|
int sprite_idx = 0;
|
|
int sprites_left = m_distort_stats.total_sprites;
|
|
|
|
// This part is mostly ripped from the VU program
|
|
while (sprites_left != 0) {
|
|
// flag seems to represent the 'resolution' of the circle sprite used to create the distortion
|
|
// effect For example, a flag value of 3 will create a circle using 3 "pie-slice" shapes
|
|
u32 flag = m_sprite_distorter_frame_data.at(sprite_idx).flag;
|
|
u32 slices_left = flag;
|
|
|
|
// flag has a minimum value of 3 which represents the first ientry
|
|
// Additionally, the ientry index has 352 added to it (which is the start of the entry array
|
|
// in VU memory), so we need to subtract that as well
|
|
int entry_index = m_sprite_distorter_sine_tables.ientry[flag - 3].x() - 352;
|
|
|
|
// Here would be adding the giftag, but we don't need that
|
|
|
|
// Get the frame data for the next distort sprite
|
|
SpriteDistortFrameData frame_data = m_sprite_distorter_frame_data.at(sprite_idx);
|
|
sprite_idx++;
|
|
|
|
// Build the OpenGL data for the sprite
|
|
math::Vector2f vf03 = frame_data.st;
|
|
math::Vector3f vf14 = frame_data.xyz;
|
|
|
|
// Each slice shares a center vertex, we can use this fact and cut out duplicate vertices
|
|
u32 center_vert_idx = m_sprite_distorter_vertices.size();
|
|
m_sprite_distorter_vertices.push_back({vf14, vf03});
|
|
|
|
do {
|
|
math::Vector3f vf06 = m_sprite_distorter_sine_tables.entry[entry_index++].xyz();
|
|
math::Vector2f vf07 = m_sprite_distorter_sine_tables.entry[entry_index++].xy();
|
|
math::Vector3f vf08 = m_sprite_distorter_sine_tables.entry[entry_index + 0].xyz();
|
|
math::Vector2f vf09 = m_sprite_distorter_sine_tables.entry[entry_index + 1].xy();
|
|
|
|
slices_left--;
|
|
|
|
math::Vector2f vf11 = (vf07 * frame_data.rgba.z()) + frame_data.st;
|
|
math::Vector2f vf13 = (vf09 * frame_data.rgba.z()) + frame_data.st;
|
|
math::Vector3f vf06_2 = (vf06 * frame_data.rgba.x()) + frame_data.xyz;
|
|
math::Vector2f vf07_2 = (vf07 * frame_data.rgba.x()) + frame_data.st;
|
|
math::Vector3f vf08_2 = (vf08 * frame_data.rgba.x()) + frame_data.xyz;
|
|
math::Vector2f vf09_2 = (vf09 * frame_data.rgba.x()) + frame_data.st;
|
|
math::Vector3f vf10 = (vf06 * frame_data.rgba.y()) + frame_data.xyz;
|
|
math::Vector3f vf12 = (vf08 * frame_data.rgba.y()) + frame_data.xyz;
|
|
math::Vector3f vf06_3 = vf06_2;
|
|
math::Vector3f vf08_3 = vf08_2;
|
|
|
|
m_sprite_distorter_indices.push_back(m_sprite_distorter_vertices.size());
|
|
m_sprite_distorter_vertices.push_back({vf06_3, vf07_2});
|
|
|
|
m_sprite_distorter_indices.push_back(m_sprite_distorter_vertices.size());
|
|
m_sprite_distorter_vertices.push_back({vf08_3, vf09_2});
|
|
|
|
m_sprite_distorter_indices.push_back(m_sprite_distorter_vertices.size());
|
|
m_sprite_distorter_vertices.push_back({vf10, vf11});
|
|
|
|
m_sprite_distorter_indices.push_back(m_sprite_distorter_vertices.size());
|
|
m_sprite_distorter_vertices.push_back({vf12, vf13});
|
|
|
|
// Originally, would add the shared center vertex, but in our case we can just add the index
|
|
m_sprite_distorter_indices.push_back(center_vert_idx);
|
|
// m_sprite_distorter_vertices.push_back({vf14, vf03});
|
|
|
|
m_distort_stats.total_tris += 2;
|
|
} while (slices_left != 0);
|
|
|
|
// Mark the end of the triangle strip
|
|
m_sprite_distorter_indices.push_back(UINT32_MAX);
|
|
|
|
sprites_left--;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Sets up OpenGL data for rendering distort sprites using instanced rendering.
|
|
*
|
|
* A mesh is built once for each possible sprite resolution and is only re-built
|
|
* when the dimensions of the window are changed. These meshes are built just like
|
|
* the triangle strips in the VU program, but with the sprite-specific data removed.
|
|
*
|
|
* Required sprite-specific frame data is kept as is and is grouped by resolution.
|
|
*/
|
|
void Sprite3::distort_setup_instanced(ScopedProfilerNode& /*prof*/) {
|
|
if (m_distort_instanced_ogl.last_aspect_x != m_sprite_distorter_sine_tables_aspect.x() ||
|
|
m_distort_instanced_ogl.last_aspect_y != m_sprite_distorter_sine_tables_aspect.y()) {
|
|
m_distort_instanced_ogl.last_aspect_x = m_sprite_distorter_sine_tables_aspect.x();
|
|
m_distort_instanced_ogl.last_aspect_y = m_sprite_distorter_sine_tables_aspect.y();
|
|
// Aspect ratio changed, which means we have a new sine table
|
|
m_sprite_distorter_vertices_instanced.clear();
|
|
|
|
// Build a mesh for every possible distort sprite resolution
|
|
auto vf03 = math::Vector2f(0, 0);
|
|
auto vf14 = math::Vector3f(0, 0, 0);
|
|
|
|
for (int res = 3; res < 12; res++) {
|
|
int entry_index = m_sprite_distorter_sine_tables.ientry[res - 3].x() - 352;
|
|
// ASSERT_MSG(entry_index >= 0, "weird sprite_distort startup crash happened again!");
|
|
|
|
for (int i = 0; i < res; i++) {
|
|
math::Vector3f vf06 = m_sprite_distorter_sine_tables.entry[entry_index++].xyz();
|
|
math::Vector2f vf07 = m_sprite_distorter_sine_tables.entry[entry_index++].xy();
|
|
math::Vector3f vf08 = m_sprite_distorter_sine_tables.entry[entry_index + 0].xyz();
|
|
math::Vector2f vf09 = m_sprite_distorter_sine_tables.entry[entry_index + 1].xy();
|
|
|
|
// Normally, there would be a bunch of transformations here against the sprite data.
|
|
// Instead, we'll let the shader do it and just store the sine table specific parts here.
|
|
|
|
m_sprite_distorter_vertices_instanced.push_back({vf06, vf07});
|
|
m_sprite_distorter_vertices_instanced.push_back({vf08, vf09});
|
|
m_sprite_distorter_vertices_instanced.push_back({vf06, vf07});
|
|
m_sprite_distorter_vertices_instanced.push_back({vf08, vf09});
|
|
m_sprite_distorter_vertices_instanced.push_back({vf14, vf03});
|
|
}
|
|
}
|
|
|
|
m_distort_instanced_ogl.vertex_data_changed = true;
|
|
}
|
|
|
|
// Set up instance data for each sprite
|
|
m_distort_stats.total_tris = 0;
|
|
|
|
for (auto& [res, vec] : m_sprite_distorter_instances_by_res) {
|
|
vec.clear();
|
|
}
|
|
|
|
for (int i = 0; i < m_distort_stats.total_sprites; i++) {
|
|
SpriteDistortFrameData frame_data = m_sprite_distorter_frame_data.at(i);
|
|
|
|
// Shader just needs the position, tex coords, and scale
|
|
auto x_y_z_s = math::Vector4f(frame_data.xyz.x(), frame_data.xyz.y(), frame_data.xyz.z(),
|
|
frame_data.st.x());
|
|
auto sx_sy_sz_t = math::Vector4f(frame_data.rgba.x(), frame_data.rgba.y(), frame_data.rgba.z(),
|
|
frame_data.st.y());
|
|
|
|
int res = frame_data.flag;
|
|
|
|
m_sprite_distorter_instances_by_res[res].push_back({x_y_z_s, sx_sy_sz_t});
|
|
|
|
m_distort_stats.total_tris += res * 2;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Draws each distort sprite.
|
|
*/
|
|
void Sprite3::distort_draw(SharedRenderState* render_state, ScopedProfilerNode& prof) {
|
|
// First, make sure the distort framebuffer is the correct size
|
|
distort_setup_framebuffer_dims(render_state);
|
|
|
|
if (m_distort_stats.total_tris == 0) {
|
|
// No distort sprites to draw, we can end early
|
|
return;
|
|
}
|
|
|
|
// Do common distort drawing logic
|
|
distort_draw_common(render_state, prof);
|
|
|
|
// Set up shader
|
|
auto shader = &render_state->shaders[ShaderId::SPRITE_DISTORT];
|
|
shader->activate();
|
|
|
|
Vector4f colorf = Vector4f(m_sprite_distorter_sine_tables.color.x() / 255.0f,
|
|
m_sprite_distorter_sine_tables.color.y() / 255.0f,
|
|
m_sprite_distorter_sine_tables.color.z() / 255.0f,
|
|
m_sprite_distorter_sine_tables.color.w() / 255.0f);
|
|
glUniform4fv(glGetUniformLocation(shader->id(), "u_color"), 1, colorf.data());
|
|
|
|
// Bind vertex array
|
|
glBindVertexArray(m_distort_ogl.vao);
|
|
|
|
// Enable prim restart, we need this to break up the triangle strips
|
|
glEnable(GL_PRIMITIVE_RESTART);
|
|
glPrimitiveRestartIndex(UINT32_MAX);
|
|
|
|
// Upload vertex data
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_distort_ogl.vertex_buffer);
|
|
glBufferData(GL_ARRAY_BUFFER, m_sprite_distorter_vertices.size() * sizeof(SpriteDistortVertex),
|
|
m_sprite_distorter_vertices.data(), GL_DYNAMIC_DRAW);
|
|
|
|
// Upload element data
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_distort_ogl.index_buffer);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_sprite_distorter_indices.size() * sizeof(u32),
|
|
m_sprite_distorter_indices.data(), GL_DYNAMIC_DRAW);
|
|
|
|
// Draw
|
|
prof.add_draw_call();
|
|
prof.add_tri(m_distort_stats.total_tris);
|
|
|
|
glDrawElements(GL_TRIANGLE_STRIP, m_sprite_distorter_indices.size(), GL_UNSIGNED_INT, (void*)0);
|
|
|
|
// Done
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
glBindVertexArray(0);
|
|
}
|
|
|
|
/*!
|
|
* Draws each distort sprite using instanced rendering.
|
|
*/
|
|
void Sprite3::distort_draw_instanced(SharedRenderState* render_state, ScopedProfilerNode& prof) {
|
|
// First, make sure the distort framebuffer is the correct size
|
|
distort_setup_framebuffer_dims(render_state);
|
|
|
|
if (m_distort_stats.total_tris == 0) {
|
|
// No distort sprites to draw, we can end early
|
|
return;
|
|
}
|
|
|
|
// Do common distort drawing logic
|
|
distort_draw_common(render_state, prof);
|
|
|
|
// Set up shader
|
|
auto shader = &render_state->shaders[ShaderId::SPRITE_DISTORT_INSTANCED];
|
|
shader->activate();
|
|
|
|
Vector4f colorf = Vector4f(m_sprite_distorter_sine_tables.color.x() / 255.0f,
|
|
m_sprite_distorter_sine_tables.color.y() / 255.0f,
|
|
m_sprite_distorter_sine_tables.color.z() / 255.0f,
|
|
m_sprite_distorter_sine_tables.color.w() / 255.0f);
|
|
glUniform4fv(glGetUniformLocation(shader->id(), "u_color"), 1, colorf.data());
|
|
|
|
// Bind vertex array
|
|
glBindVertexArray(m_distort_instanced_ogl.vao);
|
|
|
|
// Upload vertex data (if it changed)
|
|
if (m_distort_instanced_ogl.vertex_data_changed) {
|
|
m_distort_instanced_ogl.vertex_data_changed = false;
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_distort_instanced_ogl.vertex_buffer);
|
|
glBufferData(GL_ARRAY_BUFFER,
|
|
m_sprite_distorter_vertices_instanced.size() * sizeof(SpriteDistortVertex),
|
|
m_sprite_distorter_vertices_instanced.data(), GL_STREAM_DRAW);
|
|
}
|
|
|
|
// Draw each resolution group
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_distort_instanced_ogl.instance_buffer);
|
|
prof.add_tri(m_distort_stats.total_tris);
|
|
|
|
int vert_offset = 0;
|
|
for (int res = 3; res < 12; res++) {
|
|
auto& instances = m_sprite_distorter_instances_by_res[res];
|
|
int num_verts = res * 5;
|
|
|
|
if (instances.size() > 0) {
|
|
// Upload instance data
|
|
glBufferData(GL_ARRAY_BUFFER, instances.size() * sizeof(SpriteDistortInstanceData),
|
|
instances.data(), GL_DYNAMIC_DRAW);
|
|
|
|
// Draw
|
|
prof.add_draw_call();
|
|
|
|
glDrawArraysInstanced(GL_TRIANGLE_STRIP, vert_offset, num_verts, instances.size());
|
|
}
|
|
|
|
vert_offset += num_verts;
|
|
}
|
|
|
|
// Done
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindVertexArray(0);
|
|
}
|
|
|
|
void Sprite3::distort_draw_common(SharedRenderState* render_state, ScopedProfilerNode& /*prof*/) {
|
|
// The distort effect needs to read the current framebuffer, so copy what's been rendered so far
|
|
// to a texture that we can then pass to the shader
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, render_state->render_fb);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_distort_ogl.fbo);
|
|
|
|
glBlitFramebuffer(0, // srcX0
|
|
0, // srcY0
|
|
render_state->render_fb_w, // srcX1
|
|
render_state->render_fb_h, // srcY1
|
|
0, // dstX0
|
|
0, // dstY0
|
|
m_distort_ogl.fbo_width, // dstX1
|
|
m_distort_ogl.fbo_height, // dstY1
|
|
GL_COLOR_BUFFER_BIT, // mask
|
|
GL_NEAREST // filter
|
|
);
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, render_state->render_fb);
|
|
|
|
// Set up OpenGL state
|
|
m_current_mode.set_depth_write_enable(!m_sprite_distorter_setup.zbuf.zmsk()); // zbuf
|
|
glBindTexture(GL_TEXTURE_2D, m_distort_ogl.fbo_texture); // tex0
|
|
m_current_mode.set_filt_enable(m_sprite_distorter_setup.tex1.mmag()); // tex1
|
|
update_mode_from_alpha1(m_sprite_distorter_setup.alpha.data, m_current_mode); // alpha1
|
|
// note: clamp and miptbp are skipped since that is set up ahead of time with the distort
|
|
// framebuffer texture
|
|
|
|
setup_opengl_from_draw_mode(m_current_mode, GL_TEXTURE0, false);
|
|
}
|
|
|
|
void Sprite3::distort_setup_framebuffer_dims(SharedRenderState* render_state) {
|
|
// Distort framebuffer must be the same dimensions as the default window framebuffer
|
|
if (m_distort_ogl.fbo_width != render_state->render_fb_w ||
|
|
m_distort_ogl.fbo_height != render_state->render_fb_h) {
|
|
m_distort_ogl.fbo_width = render_state->render_fb_w;
|
|
m_distort_ogl.fbo_height = render_state->render_fb_h;
|
|
|
|
glBindTexture(GL_TEXTURE_2D, m_distort_ogl.fbo_texture);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_distort_ogl.fbo_width, m_distort_ogl.fbo_height, 0,
|
|
GL_RGB, GL_UNSIGNED_BYTE, NULL);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
}
|