Files
jak-project/game/graphics/opengl_renderer/sprite/Sprite3_Distort.cpp
T
Hat Kid 93afb02cf4 decomp3: spawn target, add merc and particle buckets and some temporary hacks (#3445)
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>
2024-04-05 00:07:39 -04:00

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);
}
}