Files
jak-project/game/graphics/opengl_renderer/SpriteRenderer.cpp
T
ManDude 67bb1193a8 [runtime] GPU sprite renderer (#1075)
* sprite_3d first attempt

* fixs

* fix Q usage

* ??

* attempt 2

* Update sprite_3d.vert

* works-ish

* works properly!

* scissor fix

* simplify shader

* texture support except everything is broken for some reason

* stuff

* Update SpriteRenderer.cpp

* meltdown

* Revert "simplify shader"

This reverts commit 97bd9b77be.

* Revert "Revert "simplify shader""

This reverts commit 32fb46ce90.

* fix blend

* fix blend and prim

* fix depth write and texture rendering

* fix bugs

* remove noperspective

* fix and finalize sprite 3D vert shader

* optimize 3D program

* 2D sprites! almost first try

* fixes + merge shader code

* HUD sprite support and clean up code

* oopsie

* fix 3d sprites

* minor cleanup and increase sprite buffer to 8k sprites

* clang

* replace some uses of `glBufferSubData` with `glBufferData`
2022-01-15 12:31:38 -05:00

717 lines
27 KiB
C++

#include "third-party/fmt/core.h"
#include "third-party/imgui/imgui.h"
#include "SpriteRenderer.h"
#include "game/graphics/opengl_renderer/dma_helpers.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] <= SpriteRenderer::SPRITES_PER_CHUNK);
return header[0];
}
} // namespace
constexpr int SPRITE_RENDERER_MAX_SPRITES = 8000;
SpriteRenderer::SpriteRenderer(const std::string& name, BucketId my_id)
: BucketRenderer(name, my_id) {
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 * 3 * 2;
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 0 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 0 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 0 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 0 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);
glBindVertexArray(0);
m_vertices_3d.resize(verts);
}
/*!
* Run the sprite distorter. Currently nothing uses sprite-distorter so this just skips through
* the table upload stuff that runs every frame, even if there are no sprites.
*/
void SpriteRenderer::render_distorter(DmaFollower& dma,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
// Next thing should be the sprite-distorter setup
// m_direct_renderer.reset_state();
while (dma.current_tag().qwc != 7) {
auto direct_data = dma.read_and_advance();
// m_direct_renderer.render_vif(direct_data.vif0(), direct_data.vif1(), direct_data.data,
// direct_data.size_bytes, render_state, prof);
}
// m_direct_renderer.flush_pending(render_state, prof);
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);
// Next thing should be the sprite-distorter tables
auto sprite_distorter_tables = dma.read_and_advance();
assert(sprite_distorter_tables.size_bytes == 0x8b * 16);
assert(sprite_distorter_tables.vifcode0().kind == VifCode::Kind::STCYCL);
VifCodeStcycl distorter_table_transfer(sprite_distorter_tables.vifcode0());
assert(distorter_table_transfer.cl == 4);
assert(distorter_table_transfer.wl == 4);
// TODO: check unpack cmd (vif1)
// TODO: do something with the table
// next would be the program, but we don't have it.
// TODO: next is the sprite-distorter (currently not used)
}
/*!
* 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 SpriteRenderer::handle_sprite_frame_setup(DmaFollower& dma) {
// 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);
// next would be the program, but it's 0 size on the PC and isn't sent.
// next is the "frame data"
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));
// 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 SpriteRenderer::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 SpriteRenderer::render_2d_group0(DmaFollower& dma,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
// opengl sprite frame setup
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "hvdf_offset"), 1,
m_3d_matrix_data.hvdf_offset.data());
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "pfog0"),
m_frame_data.pfog0);
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "fog_min"),
m_frame_data.fog_min);
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "fog_max"),
m_frame_data.fog_max);
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "min_scale"),
m_frame_data.min_scale);
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "max_scale"),
m_frame_data.max_scale);
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "bonus"),
m_frame_data.bonus);
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "hmge_scale"), 1,
m_frame_data.hmge_scale.data());
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "deg_to_rad"),
m_frame_data.deg_to_rad);
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "inv_area"),
m_frame_data.inv_area);
glUniformMatrix4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "camera"),
1, GL_FALSE, m_3d_matrix_data.camera.data());
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "xy_array"), 8,
m_frame_data.xy_array[0].data());
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "xyz_array"), 4,
m_frame_data.xyz_array[0].data());
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "st_array"), 4,
m_frame_data.st_array[0].data());
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "basis_x"), 1,
m_frame_data.basis_x.data());
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "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);
if (run.vifcode1().immediate == SpriteProgMem::Sprites2dGrp0 &&
m_prim_gl_state.current_register != m_frame_data.sprite_2d_giftag.prim()) {
m_prim_gl_state.from_register(m_frame_data.sprite_2d_giftag.prim());
} else if (m_prim_gl_state.current_register != m_frame_data.sprite_3d_giftag.prim()) {
m_prim_gl_state.from_register(m_frame_data.sprite_3d_giftag.prim());
}
}
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;
}
}
}
void SpriteRenderer::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 SpriteRenderer::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::SPRITE].id(), "hud_hvdf_offset"), 1,
m_hud_matrix_data.hvdf_offset.data());
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "hud_hvdf_user"),
75, m_hud_matrix_data.user_hvdf[0].data());
glUniformMatrix4fv(
glGetUniformLocation(render_state->shaders[ShaderId::SPRITE].id(), "hud_matrix"), 1, GL_FALSE,
m_hud_matrix_data.matrix.data());
m_prim_gl_state.from_register(m_frame_data.sprite_2d_giftag2.prim());
// 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);
assert(run.vifcode1().immediate == SpriteProgMem::Sprites2dHud);
if (m_enabled && m_2d_enable) {
do_block_common(SpriteMode::ModeHUD, sprite_count, render_state, prof);
}
}
}
void SpriteRenderer::render(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;
}
render_state->shaders[ShaderId::SPRITE].activate();
// 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);
// 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);
}
// 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);
}
// 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(" vif: {}\n", code.print());
if (code.kind == VifCode::Kind::NOP) {
// fmt::print(" vif: {}\n", VifCode(data.vif1()).print());
}
}
}
void SpriteRenderer::draw_debug_window() {
ImGui::Separator();
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("Extra Debug", &m_extra_debug);
ImGui::Checkbox("2d", &m_2d_enable);
ImGui::SameLine();
ImGui::Checkbox("3d", &m_3d_enable);
ImGui::SameLine();
ImGui::Checkbox("3d-debug", &m_3d_debug);
if (ImGui::TreeNode("direct")) {
// m_sprite_renderer.draw_debug_window();
ImGui::TreePop();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Render (for real)
void SpriteRenderer::flush_sprites(SharedRenderState* render_state, ScopedProfilerNode& prof) {
for (int i = 0; i <= m_adgif_index; ++i) {
update_gl_texture(render_state, i);
}
if (m_sprite_offset == 0) {
// nothing to render
m_adgif_index = 0;
return;
}
update_gl_blend(m_adgif_state_stack[m_adgif_index]);
if (m_adgif_state_stack[m_adgif_index].z_write) {
glDepthMask(GL_TRUE);
} else {
glDepthMask(GL_FALSE);
}
glBindVertexArray(m_ogl.vao);
// render!
// fmt::print("drawing {} sprites\n", m_sprite_offset);
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, m_sprite_offset * sizeof(SpriteVertex3D) * 6, m_vertices_3d.data(),
GL_STREAM_DRAW);
glDrawArrays(GL_TRIANGLES, 0, m_sprite_offset * 6);
glBindVertexArray(0);
int n_tris = m_sprite_offset * 6 / 3;
prof.add_tri(n_tris);
prof.add_draw_call(1);
m_sprite_offset = 0;
m_adgif_index = 0;
}
void SpriteRenderer::handle_tex0(u64 val,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
GsTex0 reg(val);
// update tbp
m_adgif_state.reg_tex0 = reg;
m_adgif_state.texture_base_ptr = reg.tbp0();
m_adgif_state.using_mt4hh = reg.psm() == GsTex0::PSM::PSMT4HH;
m_adgif_state.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);
// cbp: assume they got it right
// cpsm: assume they got it right
// csm: assume they got it right
}
void SpriteRenderer::handle_tex1(u64 val,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
GsTex1 reg(val);
// for now, we aren't going to handle mipmapping. I don't think it's used with direct.
// assert(reg.mxl() == 0);
// if that's true, we can ignore LCM, MTBA, L, K
m_adgif_state.enable_tex_filt = reg.mmag();
// MMAG/MMIN specify texture filtering. For now, assume always linear
// assert(reg.mmag() == true);
// if (!(reg.mmin() == 1 || reg.mmin() == 4)) { // with mipmap off, both of these are linear
// // lg::error("unsupported mmin");
// }
}
void SpriteRenderer::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);
m_adgif_state.z_write = !x.zmsk();
}
void SpriteRenderer::handle_clamp(u64 val,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
if (!(val == 0b101 || val == 0 || val == 1 || val == 0b100)) {
fmt::print("clamp: 0x{:x}\n", val);
assert(false);
}
m_adgif_state.reg_clamp = val;
m_adgif_state.clamp_s = val & 0b001;
m_adgif_state.clamp_t = val & 0b100;
}
void SpriteRenderer::update_gl_blend(AdGifState& state) {
if (!m_prim_gl_state.alpha_blend_enable) {
glDisable(GL_BLEND);
} else {
if (state.a == GsAlpha::BlendMode::SOURCE && state.b == GsAlpha::BlendMode::DEST &&
state.c == GsAlpha::BlendMode::SOURCE && state.d == GsAlpha::BlendMode::DEST) {
// (Cs - Cd) * As + Cd
// Cs * As + (1 - As) * Cd
glEnable(GL_BLEND);
// s, d
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
} else if (state.a == GsAlpha::BlendMode::SOURCE &&
state.b == GsAlpha::BlendMode::ZERO_OR_FIXED &&
state.c == GsAlpha::BlendMode::SOURCE && state.d == GsAlpha::BlendMode::DEST) {
// (Cs - 0) * As + Cd
// Cs * As + (1) * CD
glEnable(GL_BLEND);
// s, d
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
} else {
// unsupported blend: a 0 b 2 c 2 d 1
lg::error("unsupported blend: a {} b {} c {} d {}\n", (int)state.a, (int)state.b,
(int)state.c, (int)state.d);
assert(false);
}
}
}
void SpriteRenderer::handle_alpha(u64 val,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
GsAlpha reg(val);
m_adgif_state.from_register(reg);
}
void SpriteRenderer::update_gl_prim(SharedRenderState* render_state) {
// currently gouraud is handled in setup.
const auto& state = m_prim_gl_state;
if (state.fogging_enable) {
// assert(false);
}
if (state.aa_enable) {
assert(false);
}
if (state.use_uv) {
assert(false);
}
if (state.ctxt) {
assert(false);
}
if (state.fix) {
assert(false);
}
}
void SpriteRenderer::update_gl_texture(SharedRenderState* render_state, int unit) {
TextureRecord* tex = nullptr;
auto& state = m_adgif_state_stack[unit];
if (!state.used) {
// nothing used this state, don't bother binding the texture.
return;
}
if (state.using_mt4hh) {
tex = render_state->texture_pool->lookup_mt4hh(state.texture_base_ptr);
} else {
tex = render_state->texture_pool->lookup(state.texture_base_ptr);
}
if (!tex) {
// TODO Add back
fmt::print("Failed to find texture at {}, using random\n", state.texture_base_ptr);
tex = render_state->texture_pool->get_random_texture();
if (tex) {
// fmt::print("Successful texture lookup! {} {}\n", tex->page_name, tex->name);
}
}
assert(tex);
// first: do we need to load the texture?
if (!tex->on_gpu) {
render_state->texture_pool->upload_to_gpu(tex);
}
glActiveTexture(GL_TEXTURE20 + unit);
glBindTexture(GL_TEXTURE_2D, tex->gpu_texture);
// Note: CLAMP and CLAMP_TO_EDGE are different...
if (state.clamp_s) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
}
if (state.clamp_t) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
if (state.enable_tex_filt) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
state.used = false;
}
void SpriteRenderer::do_block_common(SpriteMode mode,
u32 count,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
for (u32 sprite_idx = 0; sprite_idx < count; sprite_idx++) {
if (m_sprite_offset == SPRITE_RENDERER_MAX_SPRITES) {
flush_sprites(render_state, prof);
}
auto& adgif = m_adgif[sprite_idx];
// fmt::print("adgif: {:X} {:X} {:X} {:X}\n", adgif.tex0_data, adgif.tex1_data,
// adgif.clamp_data, adgif.alpha_data); fmt::print("adgif regs: {} {} {} {} {}\n",
// register_address_name(adgif.tex0_addr), register_address_name(adgif.tex1_addr),
// register_address_name(adgif.mip_addr), register_address_name(adgif.clamp_addr),
// register_address_name(adgif.alpha_addr));
handle_tex0(adgif.tex0_data, render_state, prof);
handle_tex1(adgif.tex1_data, render_state, prof);
// handle_mip(adgif.mip_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);
if (!m_adgif_state_stack[m_adgif_index].used) {
m_adgif_state_stack[m_adgif_index] = m_adgif_state;
m_adgif_state_stack[m_adgif_index].used = true;
} else if (m_adgif_state != m_adgif_state_stack[m_adgif_index]) {
if (m_adgif_index + 1 == ADGIF_STATE_COUNT ||
!m_adgif_state.nontexture_equal(m_adgif_state_stack[m_adgif_index])) {
flush_sprites(render_state, prof);
} else {
m_adgif_index++;
}
m_adgif_state_stack[m_adgif_index] = m_adgif_state;
m_adgif_state_stack[m_adgif_index].used = true;
}
int vert_idx = 6 * m_sprite_offset;
auto& vert1 = m_vertices_3d.at(vert_idx + 0);
vert1.xyz_sx = m_vec_data_2d[sprite_idx].xyz_sx;
vert1.quat_sy = m_vec_data_2d[sprite_idx].flag_rot_sy;
vert1.rgba = m_vec_data_2d[sprite_idx].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] = m_adgif_index;
vert1.info[1] = m_adgif_state_stack[m_adgif_index].tcc;
vert1.info[2] = 0;
vert1.info[3] = mode;
m_vertices_3d.at(vert_idx + 1) = vert1;
m_vertices_3d.at(vert_idx + 2) = vert1;
m_vertices_3d.at(vert_idx + 3) = vert1;
m_vertices_3d.at(vert_idx + 4) = vert1;
m_vertices_3d.at(vert_idx + 5) = vert1;
m_vertices_3d.at(vert_idx + 1).info[2] = 1;
m_vertices_3d.at(vert_idx + 2).info[2] = 2;
m_vertices_3d.at(vert_idx + 3).info[2] = 2;
m_vertices_3d.at(vert_idx + 4).info[2] = 3;
m_vertices_3d.at(vert_idx + 5).info[2] = 0;
++m_sprite_offset;
}
}