[game] clean up texture stuff (#1120)

* better texture loading

* remove old and bad tfrag renderers
This commit is contained in:
water111
2022-01-28 23:44:03 -05:00
committed by GitHub
parent 41888aac8f
commit 8f4dd59b7a
14 changed files with 165 additions and 2914 deletions
-3
View File
@@ -106,11 +106,8 @@ set(RUNTIME_SOURCE
graphics/opengl_renderer/Sprite3.cpp
graphics/opengl_renderer/SpriteRenderer.cpp
graphics/opengl_renderer/TextureUploadHandler.cpp
graphics/opengl_renderer/tfrag/BufferedRenderer.cpp
graphics/opengl_renderer/tfrag/program6_cpu.cpp
graphics/opengl_renderer/tfrag/Tfrag3.cpp
graphics/opengl_renderer/tfrag/tfrag_common.cpp
graphics/opengl_renderer/tfrag/tfrag_unpack.cpp
graphics/opengl_renderer/tfrag/TFragment.cpp
graphics/opengl_renderer/tfrag/Tie3.cpp
graphics/texture/TextureConverter.cpp
+90 -6
View File
@@ -13,11 +13,11 @@ std::string uppercase_string(const std::string& s) {
}
} // namespace
tfrag3::Level* Loader::get_tfrag3_level(const std::string& level_name) {
const Loader::LevelData* Loader::get_tfrag3_level(const std::string& level_name) {
std::unique_lock<std::mutex> lk(m_loader_mutex);
const auto& existing = m_tfrag3_levels.find(level_name);
if (existing == m_tfrag3_levels.end()) {
if (m_level_to_load.empty()) {
const auto& existing = m_loaded_tfrag3_levels.find(level_name);
if (existing == m_loaded_tfrag3_levels.end()) {
if (m_level_to_load.empty() && m_initializing_tfrag3_levels.count(level_name) == 0) {
fmt::print("[pc loader] starting load for {}\n", level_name);
m_level_to_load = level_name;
lk.unlock();
@@ -28,7 +28,7 @@ tfrag3::Level* Loader::get_tfrag3_level(const std::string& level_name) {
}
} else {
existing->second.frames_since_last_used = 0;
return existing->second.level.get();
return &existing->second.data;
}
}
@@ -60,8 +60,8 @@ void Loader::loader_thread() {
disk_load_time, import_time, decomp_time);
lk.lock();
m_initializing_tfrag3_levels[lev].data.level = std::move(result);
m_level_to_load = "";
m_tfrag3_levels[lev].level = std::move(result);
}
}
@@ -69,6 +69,79 @@ Loader::Loader() {
m_loader_thread = std::thread(&Loader::loader_thread, this);
}
void Loader::update() {
Timer loader_timer;
// only main thread can touch this.
for (auto& lev : m_loaded_tfrag3_levels) {
lev.second.frames_since_last_used++;
}
bool did_gpu_stuff = false;
{
// try to move level from initializing to initialized:
std::unique_lock<std::mutex> lk(m_loader_mutex);
const auto& it = m_initializing_tfrag3_levels.begin();
if (it != m_initializing_tfrag3_levels.end()) {
did_gpu_stuff = true;
constexpr int MAX_TEX_BYTES_PER_FRAME = 1024 * 1024;
auto& data = it->second.data;
int bytes_this_run = 0;
while (data.textures.size() < data.level->textures.size()) {
auto& tex = data.level->textures[data.textures.size()];
GLuint gl_tex;
glGenTextures(1, &gl_tex);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.w, tex.h, 0, GL_RGBA,
GL_UNSIGNED_INT_8_8_8_8_REV, tex.data.data());
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glGenerateMipmap(GL_TEXTURE_2D);
float aniso = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, aniso);
it->second.data.textures.push_back(gl_tex);
bytes_this_run += tex.w * tex.h * 4;
if (bytes_this_run > MAX_TEX_BYTES_PER_FRAME) {
break;
}
}
if (data.textures.size() == data.level->textures.size()) {
fmt::print("Loader texture complete: {}\n", it->first);
it->second.data.load_id = m_id++;
m_loaded_tfrag3_levels[it->first] = std::move(it->second);
m_initializing_tfrag3_levels.erase(it);
}
}
}
if (!did_gpu_stuff) {
// try to remove levels.
if (m_loaded_tfrag3_levels.size() >= 3) {
for (const auto& lev : m_loaded_tfrag3_levels) {
if (lev.second.frames_since_last_used > 180) {
fmt::print("------------------------- PC unloading {}\n", lev.first);
for (auto tex : lev.second.data.textures) {
glBindTexture(GL_TEXTURE_2D, tex);
glDeleteTextures(1, &tex);
}
m_loaded_tfrag3_levels.erase(lev.first);
break;
}
}
}
}
if (loader_timer.getMs() > 5) {
fmt::print("Loader::update slow setup: {:.1f}ms\n", loader_timer.getMs());
}
}
Loader::~Loader() {
{
std::lock_guard<std::mutex> lk(m_loader_mutex);
@@ -76,4 +149,15 @@ Loader::~Loader() {
m_loader_cv.notify_all();
}
m_loader_thread.join();
}
void Loader::hack_scramble_textures() {
for (auto& it : m_loaded_tfrag3_levels) {
int n = it.second.data.textures.size();
for (int i = 0; i < n; i++) {
int a = rand() % n;
int b = rand() % n;
std::swap(it.second.data.textures[a], it.second.data.textures[b]);
}
}
}
+17 -3
View File
@@ -4,27 +4,41 @@
#include <mutex>
#include <condition_variable>
#include "game/graphics/pipelines/opengl.h"
#include "common/custom_data/Tfrag3Data.h"
class Loader {
public:
Loader();
~Loader();
tfrag3::Level* get_tfrag3_level(const std::string& level_name);
void update();
struct LevelData {
std::unique_ptr<tfrag3::Level> level;
std::vector<GLuint> textures;
u64 load_id = 0;
};
const LevelData* get_tfrag3_level(const std::string& level_name);
void hack_scramble_textures();
private:
struct Level {
std::unique_ptr<tfrag3::Level> level;
LevelData data;
int frames_since_last_used = 0;
};
void loader_thread();
std::unordered_map<std::string, Level> m_tfrag3_levels;
std::unordered_map<std::string, Level> m_loaded_tfrag3_levels;
std::unordered_map<std::string, Level> m_initializing_tfrag3_levels;
std::string m_level_to_load;
std::thread m_loader_thread;
std::mutex m_loader_mutex;
std::condition_variable m_loader_cv;
bool m_want_shutdown = false;
uint64_t m_id = 0;
};
@@ -165,6 +165,8 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) {
finish_screenshot(settings.screenshot_path, settings.window_width_px, settings.window_height_px,
settings.lbox_width_px, settings.lbox_height_px);
}
m_render_state.loader.update();
}
void OpenGLRenderer::serialize(Serializer& ser) {
@@ -1,664 +0,0 @@
#include "BufferedRenderer.h"
#include "common/dma/gs.h"
#include "third-party/imgui/imgui.h"
#include "game/graphics/pipelines/opengl.h"
namespace BufferedRenderer {
Renderer::Renderer(BucketId my_id) : m_my_id(my_id) {
glGenBuffers(1, &m_ogl.vertex_buffer);
glGenBuffers(1, &m_ogl.index_buffer);
glGenVertexArrays(1, &m_ogl.vao);
// these are some big buffers
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
m_ogl.vertex_buffer_size = MAX_VERTS;
glBufferData(GL_ARRAY_BUFFER, m_ogl.vertex_buffer_size * sizeof(Vertex), nullptr,
GL_DYNAMIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ogl.index_buffer);
m_ogl.index_buffer_size = MAX_VERTS * 3;
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_ogl.index_buffer_size * sizeof(u32), nullptr,
GL_DYNAMIC_DRAW);
}
Renderer::~Renderer() {
glDeleteBuffers(1, &m_ogl.vertex_buffer);
glDeleteBuffers(1, &m_ogl.index_buffer);
glDeleteVertexArrays(1, &m_ogl.vao);
}
void Renderer::render_list(const DrawList& list,
SharedRenderState* render_state,
ScopedProfilerNode& prof,
const std::vector<Vertex>& vertices) {
// first, load primitive buffer
glBindVertexArray(m_ogl.vao);
u32 vert_count = std::min((int)vertices.size(), (int)m_ogl.vertex_buffer_size);
if (vertices.size() > m_ogl.vertex_buffer_size) {
fmt::print("TOO MANY VERTICES: {} / {}\n", vertices.size(), m_ogl.vertex_buffer_size);
assert(false);
}
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
glBufferSubData(GL_ARRAY_BUFFER, 0, vert_count * sizeof(Vertex), vertices.data());
glVertexAttribPointer(0, // location 0 in the shader
3, // 3 values per vert
GL_UNSIGNED_INT, // u32's
GL_TRUE, // normalized
sizeof(Vertex), // stride
(void*)offsetof(Vertex, xyz) // offset (0)
);
glVertexAttribPointer(1, // location 1 in the shader
4, // 4 values per vert
GL_UNSIGNED_BYTE, // u8's
GL_TRUE, // normalized
sizeof(Vertex), // stride
(void*)offsetof(Vertex, rgba) // offset (0)
);
glVertexAttribPointer(2, // location 2 in the shader
3, // 3 values per vert
GL_FLOAT, // u32's
GL_FALSE, // normalized
sizeof(Vertex), // stride
(void*)offsetof(Vertex, st) // offset (0)
);
glActiveTexture(GL_TEXTURE0);
for (auto& group : list.groups) {
if (group.tbp != UINT16_MAX) {
render_group(group, render_state, prof, vertices);
}
}
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glBindVertexArray(0);
}
void Renderer::setup_opengl_excluding_textures(SharedRenderState* render_state, DrawMode mode) {
if (mode.get_depth_write_enable()) {
glDepthMask(GL_TRUE);
} else {
glDepthMask(GL_FALSE);
}
if (mode.get_zt_enable()) {
glEnable(GL_DEPTH_TEST);
switch (mode.get_depth_test()) {
case GsTest::ZTest::NEVER:
glDepthFunc(GL_NEVER);
break;
case GsTest::ZTest::ALWAYS:
glDepthFunc(GL_ALWAYS);
break;
case GsTest::ZTest::GEQUAL:
glDepthFunc(GL_GEQUAL);
break;
case GsTest::ZTest::GREATER:
glDepthFunc(GL_GREATER);
break;
default:
assert(false);
}
} else {
glDisable(GL_DEPTH_TEST);
}
if (mode.get_ab_enable() && mode.get_alpha_blend() != DrawMode::AlphaBlend::DISABLED) {
glEnable(GL_BLEND);
switch (mode.get_alpha_blend()) {
case DrawMode::AlphaBlend::SRC_DST_SRC_DST:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
break;
case DrawMode::AlphaBlend::SRC_0_SRC_DST:
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
break;
default:
assert(false);
}
} else {
glDisable(GL_BLEND);
}
if (mode.get_clamp_s_enable()) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
}
if (mode.get_clamp_t_enable()) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
if (mode.get_filt_enable()) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_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);
}
float alpha_reject = 0.;
if (mode.get_at_enable()) {
switch (mode.get_alpha_test()) {
case DrawMode::AlphaTest::ALWAYS:
break;
case DrawMode::AlphaTest::GEQUAL:
alpha_reject = mode.get_aref() / 127.f;
break;
case DrawMode::AlphaTest::NEVER:
break;
default:
assert(false);
}
}
// todo check afail
if (mode.get_tcc_enable()) {
render_state->shaders[ShaderId::BUFFERED_TCC1].activate();
glUniform1f(
glGetUniformLocation(render_state->shaders[ShaderId::BUFFERED_TCC1].id(), "alpha_reject"),
alpha_reject);
glUniform1i(glGetUniformLocation(render_state->shaders[ShaderId::BUFFERED_TCC1].id(), "T0"), 0);
} else {
fmt::print("tcc off!\n");
render_state->shaders[ShaderId::BUFFERED_TCC0].activate();
glUniform1f(
glGetUniformLocation(render_state->shaders[ShaderId::BUFFERED_TCC0].id(), "alpha_reject"),
alpha_reject);
glUniform1i(glGetUniformLocation(render_state->shaders[ShaderId::BUFFERED_TCC0].id(), "T0"), 0);
}
}
void Renderer::render_group(const DrawGroup& group,
SharedRenderState* render_state,
ScopedProfilerNode& prof,
const std::vector<Vertex>& /*vertices*/) {
TextureRecord* tex = nullptr;
tex = render_state->texture_pool->lookup(group.tbp);
if (!tex) {
fmt::print("Failed to find texture at {}, using random\n", group.tbp);
tex = render_state->texture_pool->get_random_texture();
}
assert(tex);
// first: do we need to load the texture?
if (!tex->on_gpu) {
render_state->texture_pool->upload_to_gpu(tex);
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex->gpu_texture);
for (auto& draw : group.draws) {
if (draw.mode.is_valid()) {
m_stats.draw_calls++;
prof.add_draw_call();
prof.add_tri(draw.triangles.size());
render_state->shaders[ShaderId::DEBUG_BUFFERED].activate();
setup_opengl_excluding_textures(render_state, draw.mode);
// check for overflows.
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ogl.index_buffer);
if (draw.triangles.size() * 3 > m_ogl.index_buffer_size) {
fmt::format("TOO MANY TRIS: {}/{}\n", draw.triangles.size() * 3, m_ogl.index_buffer_size);
assert(false);
}
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, draw.triangles.size() * sizeof(u32) * 3,
draw.triangles.data());
glDrawElements(GL_TRIANGLES, draw.triangles.size() * 3, GL_UNSIGNED_INT, (void*)0);
}
}
}
void Renderer::draw_debug_window() {
// todo
ImGui::Text("draws: %d", m_stats.draw_calls);
}
void Renderer::clear_stats() {
m_stats = {};
}
Builder::Builder(BucketId my_id) : m_my_id(my_id), m_renderer(my_id) {}
void Builder::add_gif_data_sized(const void* data, u32 expected_size) {
if (expected_size != add_gif_data(data)) {
assert(false); // todo, might be too strict due to alignment crap
}
}
void Builder::flush(SharedRenderState* render_state, ScopedProfilerNode& prof) {
m_renderer.render_list(m_list, render_state, prof, m_vertices);
m_list.clear();
m_vertices.clear();
m_cache = {};
}
void Builder::draw_debug_window() {
ImGui::Text("Builder: %d tri, %d vert", m_stats.m_tri, m_stats.m_dvert);
ImGui::Text("Renderer:");
ImGui::Separator();
m_renderer.draw_debug_window();
}
void Builder::reset_state() {
m_current_mode.as_int() = 0;
m_stats = {};
m_renderer.clear_stats();
m_vertex_queue = {};
m_current_mode.enable_depth_write();
m_current_mode.enable_ab();
m_current_mode.enable_at();
m_current_mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST);
}
u32 Builder::add_gif_data(const void* data_in) {
bool eop = false;
auto* data = (const u8*)data_in;
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.pre()) {
handle_prim(tag.prim());
}
for (u32 loop = 0; loop < tag.nloop(); loop++) {
for (u32 reg = 0; reg < nreg; reg++) {
switch (reg_desc[reg]) {
case GifTag::RegisterDescriptor::AD:
handle_ad(data + offset);
break;
case GifTag::RegisterDescriptor::ST:
handle_st_packed(data + offset);
break;
case GifTag::RegisterDescriptor::RGBAQ:
handle_rgbaq_packed(data + offset);
break;
case GifTag::RegisterDescriptor::XYZF2:
handle_xyzf2_packed(data + offset);
break;
// case GifTag::RegisterDescriptor::PRIM:
// handle_prim_packed(data + offset, render_state, prof);
// break;
// case GifTag::RegisterDescriptor::TEX0_1:
// handle_tex0_1_packed(data + offset, render_state, prof);
// break;
default:
fmt::print("Register {} is not supported in packed mode of BufferedRenderer\n",
reg_descriptor_name(reg_desc[reg]));
assert(false);
}
offset += 16; // PACKED = quadwords
}
}
} else if (format == GifTag::Format::REGLIST) {
for (u32 loop = 0; loop < tag.nloop(); loop++) {
for (u32 reg = 0; reg < nreg; reg++) {
u64 register_data;
memcpy(&register_data, data + offset, 8);
// fmt::print("loop: {} reg: {} {}\n", loop, reg,
// reg_descriptor_name(reg_desc[reg]));
switch (reg_desc[reg]) {
// case GifTag::RegisterDescriptor::PRIM:
// handle_prim(register_data, render_state, prof);
// break;
// case GifTag::RegisterDescriptor::RGBAQ:
// handle_rgbaq(register_data);
// break;
// case GifTag::RegisterDescriptor::XYZF2:
// handle_xyzf2(register_data, render_state, prof);
// break;
default:
fmt::print("Register {} is not supported in reglist mode of BufferedRenderer\n",
reg_descriptor_name(reg_desc[reg]));
assert(false);
}
offset += 8; // PACKED = quadwords
}
}
} else {
assert(false); // format not packed or reglist.
}
eop = tag.eop();
}
return offset;
}
void Builder::handle_ad(const u8* data) {
u64 value;
GsRegisterAddress addr;
memcpy(&value, data, sizeof(u64));
memcpy(&addr, data + 8, sizeof(GsRegisterAddress));
switch (addr) {
// case GsRegisterAddress::ZBUF_1:
// handle_zbuf1(value, render_state, prof);
// break;
case GsRegisterAddress::TEST_1:
handle_test1(value);
break;
case GsRegisterAddress::ALPHA_1:
handle_alpha1(value);
break;
// case GsRegisterAddress::PABE:
// handle_pabe(value);
break;
case GsRegisterAddress::CLAMP_1:
handle_clamp1(value);
break;
// case GsRegisterAddress::PRIM:
// handle_prim(value, render_state, prof);
// break;
//
case GsRegisterAddress::TEX1_1:
handle_tex1_1(value);
break;
// case GsRegisterAddress::TEXA:
// handle_texa(value);
// break;
// case GsRegisterAddress::TEXCLUT:
// // TODO
// // the only thing the direct renderer does with texture is font, which does no tricks
// with
// // CLUT. The texture upload process will do all of the lookups with the default CLUT.
// // So we'll just assume that the TEXCLUT is set properly and ignore this.
// break;
// case GsRegisterAddress::FOGCOL:
// // TODO
// break;
case GsRegisterAddress::TEX0_1:
handle_tex0_1(value);
break;
case GsRegisterAddress::MIPTBP1_1:
case GsRegisterAddress::MIPTBP2_1:
// this has the address of different mip levels, we can just ignore it.
break;
// case GsRegisterAddress::TEXFLUSH:
// break;
default:
fmt::print("Address {} is not supported in ad of BufferedRenderer\n",
register_address_name(addr));
assert(false);
}
}
void Builder::handle_test1(u64 val) {
// ate, atst, aref, afail, date, datm, zte, ztest
GsTest test(val);
// ATE
m_current_mode.set_at(test.alpha_test_enable());
// ATST
switch (test.alpha_test()) {
case GsTest::AlphaTest::ALWAYS:
m_current_mode.set_alpha_test(DrawMode::AlphaTest::ALWAYS);
break;
case GsTest::AlphaTest::GEQUAL:
m_current_mode.set_alpha_test(DrawMode::AlphaTest::GEQUAL);
break;
case GsTest::AlphaTest::NEVER:
m_current_mode.set_alpha_test(DrawMode::AlphaTest::NEVER);
break;
default:
fmt::print("Alpha test: {} not supported\n", (int)test.alpha_test());
assert(false);
}
// AREF
m_current_mode.set_aref(test.aref());
// AFAIL
m_current_mode.set_alpha_fail(test.afail());
// DATE
assert(test.date() == false);
// DATM
// who cares, if date is off
// ZTE
m_current_mode.set_zt(test.zte());
// ZTST
m_current_mode.set_depth_test(test.ztest());
}
void Builder::handle_tex0_1(u64 val) {
GsTex0 reg(val);
// TBP0
m_current_tbp = reg.tbp0();
// TBW
// assume it's right
// PSM
assert(reg.psm() != GsTex0::PSM::PSMT4HH); // not supported in buffered yet.
// TW, TH
// assume it's right
// TCC
m_current_mode.set_tcc(reg.tcc());
// TFX
assert(reg.tfx() == GsTex0::TextureFunction::MODULATE);
// CBP, CPSM, CSM
// assume it's right
}
void Builder::handle_tex1_1(u64 val) {
GsTex1 reg(val);
// ignoring these and doing our own thing!
// just need to pick between filtering and not filtering.
m_current_mode.set_filt_enable(reg.mmag());
}
void Builder::handle_clamp1(u64 val) {
// check that we got one of the expected ones
if (!(val == 0b101 || val == 0 || val == 1 || val == 0b100)) {
fmt::print("clamp: 0x{:x}\n", val);
assert(false);
}
m_current_mode.set_clamp_s_enable(val & 0b1);
m_current_mode.set_clamp_t_enable(val & 0b100);
}
void Builder::handle_alpha1(u64 val) {
GsAlpha reg(val);
if (reg.a_mode() == GsAlpha::BlendMode::SOURCE && reg.b_mode() == GsAlpha::BlendMode::DEST &&
reg.c_mode() == GsAlpha::BlendMode::SOURCE && reg.d_mode() == GsAlpha::BlendMode::DEST) {
// (Cs - Cd) * As + Cd
// Cs * As + (1 - As) * Cd
m_current_mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST);
} else if (reg.a_mode() == GsAlpha::BlendMode::SOURCE &&
reg.b_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
reg.c_mode() == GsAlpha::BlendMode::SOURCE &&
reg.d_mode() == GsAlpha::BlendMode::DEST) {
// (Cs - 0) * As + Cd
// Cs * As + (1) * CD
m_current_mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_SRC_DST);
} else {
// unsupported blend: a 0 b 2 c 2 d 1
fmt::print("unsupported blend: a {} b {} c {} d {}\n", (int)reg.a_mode(), (int)reg.b_mode(),
(int)reg.c_mode(), (int)reg.d_mode());
assert(false);
}
}
void Builder::handle_prim(u64 val) {
GsPrim prim(val);
// PRIM
m_prim_kind = prim.kind();
// IIP
assert(prim.gouraud());
// TME
assert(prim.tme());
// FGE
// TODO fog
// ABE
m_current_mode.set_ab(prim.abe());
// AA1
assert(!prim.aa1());
// FST
assert(!prim.fst());
// CTXT
assert(!prim.ctxt());
// FIX
assert(!prim.fix());
if (m_vertex_queue.startup) {
m_vertex_queue.startup = 0;
m_vertex_queue.idx = 0;
} else {
// assert(false);
}
}
void Builder::handle_st_packed(const u8* data) {
memcpy(m_st_pending_q, data, 3 * sizeof(float));
}
void Builder::handle_rgbaq_packed(const u8* data) {
m_q = m_st_pending_q[2];
// memcpy(m_rgba.data(), data, 4);
m_rgba[0] = data[0];
m_rgba[1] = data[4];
m_rgba[2] = data[8];
m_rgba[3] = data[12];
}
void Builder::handle_xyzf2_packed(const u8* data) {
u32 x, y;
memcpy(&x, data, 4);
memcpy(&y, data + 4, 4);
u64 upper;
memcpy(&upper, data + 8, 8);
u32 z = (upper >> 4) & 0xffffff;
u8 f = (upper >> 36);
bool adc = upper & (1ull << 47);
handle_xyzf2_common(x, y, z, f, !adc);
}
void Builder::handle_xyzf2_common(u32 x, u32 y, u32 z, u8 /*f*/, bool advance) {
// first, create a vertex:
u32 new_vertex = create_vertex_now(x, y, z);
// next, add that vertex. This will inform us if we actually draw a prim or not
bool new_prim = false;
switch (m_prim_kind) {
case GsPrim::Kind::TRI_STRIP:
new_prim = handle_tri_strip_add(new_vertex, advance);
break;
default:
assert(false);
}
if (new_prim) {
// todo, winding order?
add_prim_now({m_vertex_queue.verts[0], m_vertex_queue.verts[1], m_vertex_queue.verts[2]});
}
}
void Builder::add_prim_now(Triangle tri) {
if (m_cache.last_tbp == m_current_tbp && m_cache.last_mode == m_current_mode.as_int()) {
m_cache.draw->triangles.push_back(tri);
} else {
auto group = m_list.get_group_for_tbp(m_current_tbp);
assert(group);
// todo flush and stats
auto draw = group->get_draw_for_mode(m_current_mode);
assert(draw);
// todo flush and stats
draw->triangles.push_back(tri);
m_cache.last_mode = m_current_mode.as_int();
m_cache.last_tbp = m_current_tbp;
m_cache.draw = draw;
}
m_stats.m_tri++;
}
u32 Builder::create_vertex_now(u32 x, u32 y, u32 z) {
m_stats.m_dvert++;
u32 idx = m_vertices.size();
m_vertices.emplace_back();
auto& vert = m_vertices.back();
vert.xyz = math::Vector<u32, 3>(x << 16, y << 16, z << 8);
vert.rgba = m_rgba;
vert.st = math::Vector<float, 2>(m_st_pending_q[0], m_st_pending_q[1]);
vert.q = m_q;
return idx;
}
bool Builder::handle_tri_strip_add(u32 new_vertex, bool advance) {
m_vertex_queue.verts[m_vertex_queue.idx] = new_vertex;
m_vertex_queue.idx++;
// wrap the index
if (m_vertex_queue.idx == 3) {
m_vertex_queue.idx = 0;
}
// bump the startup
if (m_vertex_queue.startup < 3) {
m_vertex_queue.startup++;
}
if (m_vertex_queue.startup >= 3) {
if (advance) {
return true;
}
}
return false;
}
} // namespace BufferedRenderer
@@ -1,219 +0,0 @@
#pragma once
#include "common/math/Vector.h"
#include "common/common_types.h"
#include "game/graphics/opengl_renderer/BucketRenderer.h"
#include "common/dma/gs.h"
#include "game/graphics/pipelines/opengl.h"
namespace BufferedRenderer {
// The buffered renderer performs efficient sorting of primitives to reduce draw calls.
// the settings:
// this is the maximum number of draw "kinds" we can have per textures.
// a draw is a different "kind" if any opengl state changes happen (like z test, etc)
// if this is exceeded, this will return an error code.
// you must flush the group, then try adding it again.
// making this too large will slow down insertion and increase memory usage
constexpr int MAX_DRAW_KINDS_PER_TEX = 4;
// this is the maximum number of textures. If this is exceeded, there will be an error like above.
constexpr int MAX_TEXTURES = 256;
// this is the PS2 maximum TBP value.
constexpr int MAX_TBP = 16384;
// 32-byte vertex.
// the xyz, rgba, and stq are aligned. we have a free 4-bytes at the end.
// there is a single big pool of vertices.
struct Vertex {
math::Vector<u32, 3> xyz; // ps2 coords (0)
math::Vector<u8, 4> rgba; // 0, 4 (1)
math::Vector<float, 2> st; // (2)
float q; // (3)
u32 pad;
};
static_assert(sizeof(Vertex) == 32);
// a triangle grabs three vertices from the pools.
struct Triangle {
u32 verts[3];
};
struct Draw {
DrawMode mode;
std::vector<Triangle> triangles; // just indices
void clear() {
mode.set_invalid();
triangles.clear();
}
};
struct DrawGroup {
Draw draws[MAX_DRAW_KINDS_PER_TEX];
u16 tbp = UINT16_MAX;
// can fail, in which case you should flush the DrawGroup.
Draw* get_draw_for_mode(DrawMode mode) {
for (auto& draw : draws) {
if (draw.mode.is_invalid()) {
draw.mode = mode;
return &draw;
} else if (draw.mode == mode) {
return &draw;
}
}
return nullptr;
}
void clear() {
for (auto& draw : draws) {
draw.clear();
tbp = UINT16_MAX;
}
}
};
struct DrawList {
DrawGroup groups[MAX_TEXTURES];
u16 tbp_to_tex_id[MAX_TBP];
u16 current_tex_id = 0;
DrawList() {
for (auto& x : tbp_to_tex_id) {
x = UINT16_MAX;
}
}
void clear() {
for (auto& group : groups) {
group.clear();
}
for (auto& x : tbp_to_tex_id) {
x = UINT16_MAX;
}
current_tex_id = 0;
}
DrawGroup* get_group_for_tbp(u16 tbp) {
if (tbp_to_tex_id[tbp] != UINT16_MAX) {
// already have it
return &groups[tbp_to_tex_id[tbp]];
} else {
if (current_tex_id == MAX_TEXTURES) {
// don't have it, and out of room
return nullptr;
} else {
// don't have it, but we can add it.
auto group = &groups[current_tex_id];
group->tbp = tbp;
tbp_to_tex_id[tbp] = current_tex_id;
current_tex_id++;
return group;
}
}
}
};
class Renderer {
public:
Renderer(BucketId my_id);
~Renderer();
void render_list(const DrawList& list,
SharedRenderState* render_state,
ScopedProfilerNode& prof,
const std::vector<Vertex>& vertices);
void render_group(const DrawGroup& group,
SharedRenderState* render_state,
ScopedProfilerNode& prof,
const std::vector<Vertex>& vertices);
void setup_opengl_excluding_textures(SharedRenderState* render_state, DrawMode mode);
void draw_debug_window();
void clear_stats();
BucketId my_id() const { return m_my_id; }
private:
static constexpr int MAX_VERTS = 400000;
BucketId m_my_id;
struct {
int triangles = 0;
int draw_calls = 0;
int groups = 0;
} m_stats;
struct {
GLuint vertex_buffer = -1;
u32 vertex_buffer_size = 0;
GLuint index_buffer = -1;
u32 index_buffer_size = 0;
GLuint vao = -1;
} m_ogl;
};
class Builder {
public:
Builder(BucketId my_id);
u32 add_gif_data(const void* data);
void add_gif_data_sized(const void* data, u32 expected_size);
void flush(SharedRenderState* render_state, ScopedProfilerNode& prof);
void draw_debug_window();
void reset_state();
private:
void handle_ad(const u8* data);
void handle_test1(u64 val);
void handle_tex0_1(u64 val);
void handle_tex1_1(u64 val);
void handle_clamp1(u64 val);
void handle_prim(u64 val);
void handle_alpha1(u64 val);
void handle_st_packed(const u8* data);
void handle_rgbaq_packed(const u8* data);
void handle_xyzf2_packed(const u8* data);
void handle_xyzf2_common(u32 x, u32 y, u32 z, u8 f, bool advance);
bool handle_tri_strip_add(u32 new_vertex, bool advance);
void add_prim_now(Triangle tri);
u32 create_vertex_now(u32 x, u32 y, u32 z);
BucketId my_id() const { return m_my_id; }
BucketId m_my_id;
DrawList m_list;
Renderer m_renderer;
DrawMode m_current_mode;
u16 m_current_tbp = 0;
GsPrim::Kind m_prim_kind = GsPrim::Kind::PRIM_7;
std::vector<Vertex> m_vertices;
float m_st_pending_q[3] = {0}; // q goes to real q on rgbaq packed
float m_q = 0;
math::Vector<u8, 4> m_rgba;
// todo maybe add a mode cache?
struct {
u32 idx = 0;
u32 startup = 0;
u32 verts[3] = {0, 0, 0};
} m_vertex_queue;
struct {
u32 m_dvert = 0;
u32 m_tri = 0;
} m_stats;
struct {
u32 last_tbp = UINT32_MAX;
u32 last_mode = UINT32_MAX;
Draw* draw = nullptr;
} m_cache;
};
} // namespace BufferedRenderer
+24 -346
View File
@@ -22,8 +22,6 @@ TFragment::TFragment(const std::string& name,
int level_id)
: BucketRenderer(name, my_id),
m_child_mode(child_mode),
m_direct_renderer(fmt::format("{}.direct", name), my_id, 1024, DirectRenderer::Mode::NORMAL),
m_buffered_renderer(my_id),
m_tree_kinds(trees),
m_level_id(level_id) {
for (auto& buf : m_buffered_data) {
@@ -31,10 +29,6 @@ TFragment::TFragment(const std::string& name,
x = 0xff;
}
}
for (auto& x : m_kick_data.pad) {
x = 0;
}
}
constexpr const char* level_names[] = {"bea", "cit", "dar", "fin", "int", "jub", "jun", "fic",
@@ -45,14 +39,6 @@ void TFragment::render(DmaFollower& dma,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
m_debug_string.clear();
m_frag_debug.clear();
if (m_use_buffered_renderer) {
m_buffered_renderer.reset_state();
} else {
m_direct_renderer.reset_state();
}
m_stats = {};
if (!m_enabled) {
while (dma.current_tag_offset() != render_state->next_bucket) {
@@ -110,30 +96,26 @@ void TFragment::render(DmaFollower& dma,
return;
}
if (m_extra_debug) {
ImGui::Begin(fmt::format("{} extra", m_name).c_str());
}
if (m_use_tfrag3) {
std::string level_name;
while (looks_like_tfrag_init(dma)) {
handle_initialization(dma, render_state, prof);
if (level_name.empty()) {
level_name = m_pc_port_data.level_name;
} else if (level_name != m_pc_port_data.level_name) {
assert(false);
}
while (looks_like_tfragment_dma(dma)) {
dma.read_and_advance();
}
std::string level_name;
while (looks_like_tfrag_init(dma)) {
handle_initialization(dma);
if (level_name.empty()) {
level_name = m_pc_port_data.level_name;
} else if (level_name != m_pc_port_data.level_name) {
assert(false);
}
while (dma.current_tag_offset() != render_state->next_bucket) {
while (looks_like_tfragment_dma(dma)) {
dma.read_and_advance();
}
}
assert(!level_name.empty());
while (dma.current_tag_offset() != render_state->next_bucket) {
dma.read_and_advance();
}
assert(!level_name.empty());
{
m_tfrag3.setup_for_level(m_tree_kinds, level_name, render_state);
TfragRenderSettings settings;
settings.hvdf_offset = m_tfrag_data.hvdf_offset;
@@ -164,50 +146,10 @@ void TFragment::render(DmaFollower& dma,
auto t3prof = prof.make_scoped_child("t3");
m_tfrag3.render_matching_trees(m_tree_kinds, settings, render_state, t3prof);
} else {
while (looks_like_tfrag_init(dma)) {
m_debug_string += "------------- START!\n";
handle_initialization(dma, render_state, prof);
int count = 0;
// fmt::print("---------------------------------------START\n");
while (looks_like_tfragment_dma(dma)) {
m_stats.tfrag_dma_packets++;
auto frag = dma.read_and_advance();
m_stats.tfrag_bytes += frag.size_bytes;
if (m_extra_debug) {
handle_tfrag<true>(frag, render_state, prof);
} else {
handle_tfrag<false>(frag, render_state, prof);
}
if (m_max_draw >= 0 && count++ > m_max_draw) {
break;
}
}
if (dma.current_tag().qwc == 3) {
dma.read_and_advance();
}
if (dma.current_tag().qwc == 0) {
dma.read_and_advance();
}
}
}
if (m_extra_debug) {
ImGui::End();
}
m_debug_string += fmt::format("fail: {}\n", dma.current_tag().print());
if (m_use_buffered_renderer) {
m_buffered_renderer.flush(render_state, prof);
} else {
m_direct_renderer.flush_pending(render_state, prof);
}
while (dma.current_tag_offset() != render_state->next_bucket) {
auto tag = dma.current_tag().print();
auto data = dma.read_and_advance();
@@ -250,15 +192,16 @@ void TFragment::render(DmaFollower& dma,
}
}
}
if (m_hack_scrambler) {
render_state->loader.hack_scramble_textures();
m_hack_scrambler = false;
}
}
void TFragment::draw_debug_window() {
ImGui::Separator();
ImGui::Checkbox("Extra Debug", &m_extra_debug);
ImGui::InputInt("Max Draw", &m_max_draw);
ImGui::SameLine();
if (ImGui::Button("All")) {
m_max_draw = -1;
if (ImGui::Button("Scrambler")) {
m_hack_scrambler = true;
}
ImGui::Checkbox("Manual Time of Day", &m_override_time_of_day);
if (m_override_time_of_day) {
@@ -274,41 +217,12 @@ void TFragment::draw_debug_window() {
}
}
ImGui::Checkbox("Use TFRAG3", &m_use_tfrag3);
if (!m_use_tfrag3) {
ImGui::Checkbox("Use Buffered Renderer", &m_use_buffered_renderer);
ImGui::Checkbox("Skip MSCAL", &m_skip_mscals);
ImGui::Checkbox("Skip XGKICK", &m_skip_xgkick);
ImGui::Checkbox("Prog8 hack", &m_prog8_with_prog6);
ImGui::Checkbox("Prog10 hack", &m_prog10_with_prog6);
ImGui::Checkbox("Prog18 hack", &m_prog18_with_prog6);
ImGui::Checkbox("Others with prog6", &m_all_with_prog6);
ImGui::Text("packets: %d", m_stats.tfrag_dma_packets);
ImGui::Text("frag bytes: %d", m_stats.tfrag_bytes);
ImGui::Text("errors: %d", m_stats.error_packets);
for (int prog = 0; prog < 12; prog++) {
ImGui::Text(" prog %d: %d calls\n", prog, m_stats.per_program[prog].calls);
}
if (!m_use_buffered_renderer && ImGui::TreeNode("direct")) {
m_direct_renderer.draw_debug_window();
ImGui::TreePop();
}
if (m_use_buffered_renderer && ImGui::TreeNode("buffered")) {
m_buffered_renderer.draw_debug_window();
ImGui::TreePop();
}
} else {
m_tfrag3.draw_debug_window();
}
m_tfrag3.draw_debug_window();
ImGui::TextUnformatted(m_debug_string.data());
}
void TFragment::handle_initialization(DmaFollower& dma,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
void TFragment::handle_initialization(DmaFollower& dma) {
// Set up test (different between different renderers)
auto setup_test = dma.read_and_advance();
assert(setup_test.vif0() == 0);
@@ -316,23 +230,16 @@ void TFragment::handle_initialization(DmaFollower& dma,
assert(setup_test.vifcode1().immediate == 2);
assert(setup_test.size_bytes == 32);
memcpy(m_test_setup, setup_test.data, 32);
if (m_use_buffered_renderer) {
m_buffered_renderer.add_gif_data_sized(m_test_setup, 32);
} else {
m_direct_renderer.render_gif(m_test_setup, 32, render_state, prof);
}
// matrix 0
auto mat0_upload = dma.read_and_advance();
unpack_to_stcycl(&m_buffered_data[0].pad[TFragDataMem::TFragMatrix0 * 16], mat0_upload,
VifCode::Kind::UNPACK_V4_32, 4, 4, 64, TFragDataMem::TFragMatrix0, false, false);
m_debug_string += fmt::format("Matrix 0:\n {}\n", m_matrix_0.to_string_aligned());
// matrix 1
auto mat1_upload = dma.read_and_advance();
unpack_to_stcycl(&m_buffered_data[1].pad[TFragDataMem::TFragMatrix0 * 16], mat1_upload,
VifCode::Kind::UNPACK_V4_32, 4, 4, 64, TFragDataMem::TFragMatrix1, false, false);
m_debug_string += fmt::format("Matrix 1:\n {}\n", m_matrix_1.to_string_aligned());
// data
auto data_upload = dma.read_and_advance();
@@ -344,21 +251,6 @@ void TFragment::handle_initialization(DmaFollower& dma,
auto mscal_setup = dma.read_and_advance();
verify_mscal(mscal_setup, TFragProgMem::TFragSetup);
// iaddiu vi14, vi00, 0x2a0 | nop
m_ptrs.vi14 = 0x2a0; // todo constant
// iaddiu vi01, vi00, 0x350 | nop
m_ptrs.vi01 = 0x350; // todo constant
// mfir.x vf03, vi14 | nop
m_ptrs.vf03_x = m_ptrs.vi14;
// mfir.y vf03, vi01 | nop
m_ptrs.vf03_y = m_ptrs.vi01;
// mfir.z vf03, vi14 | nop
m_ptrs.vf03_z = m_ptrs.vi14;
// mfir.w vf03, vi01 | nop :e
m_ptrs.vf03_w = m_ptrs.vi01;
// lq.xyzw vf04, 664(vi00) | nop
m_globals.vf04_ambient = m_tfrag_data.ambient; // TODO get rid?
auto pc_port_data = dma.read_and_advance();
assert(pc_port_data.size_bytes == sizeof(TfragPcPortData));
memcpy(&m_pc_port_data, pc_port_data.data, sizeof(TfragPcPortData));
@@ -386,220 +278,6 @@ void TFragment::handle_initialization(DmaFollower& dma,
db_setup.vifcode1().immediate == (Buffer1_Start - Buffer0_Start));
}
template <bool DEBUG>
void TFragment::handle_tfrag(const DmaTransfer& dma,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
auto first_vif = dma.vifcode0();
auto second_vif = dma.vifcode1();
if (DEBUG) {
ImGui::Separator();
ImGui::Text("tf: %d sz %d", m_stats.tfrag_dma_packets, dma.size_bytes);
ImGui::Text(" vif: %s", first_vif.print().c_str());
ImGui::Text(" vif: %s", second_vif.print().c_str());
}
// first VIF should be a STCYCL
assert(first_vif.kind == VifCode::Kind::STCYCL);
VifCodeStcycl stcycl(first_vif.immediate);
// this is our state for running through the DMA data
int cl = stcycl.cl;
int wl = stcycl.wl;
int offset_into_data = 0;
bool row_init = false;
u32 row[4];
u8 stmod = 0;
// next can be one of:
// - NOP, UNPACK, MSCAL
// fmt::print("START vif -> {} (mod {})\n", second_vif.print(), stmod);
switch (second_vif.kind) {
case VifCode::Kind::NOP:
// do nothing!
break;
case VifCode::Kind::UNPACK_V4_8:
offset_into_data = handle_unpack_v4_8_mode0(second_vif, dma, offset_into_data, cl, wl);
break;
case VifCode::Kind::MSCAL:
if (!m_skip_mscals) {
handle_mscal<DEBUG>(second_vif, render_state, prof);
}
break;
default:
fmt::print("unknown second vif in tfragment: {}\n", second_vif.print());
assert(false);
}
bool ok = true;
while (ok && offset_into_data < (int)dma.size_bytes) {
assert((offset_into_data % 4) == 0);
auto vif = dma.read_val<u32>(offset_into_data);
offset_into_data += 4;
auto code = VifCode(vif);
// fmt::print("vif -> {} (mod {}) {}/{} #x{:x}\n", code.print(), stmod, offset_into_data,
// dma.size_bytes, dma.data_offset);
switch (code.kind) {
case VifCode::Kind::UNPACK_V4_16:
if (DEBUG) {
ImGui::Text(" vif: %s (m %d)", code.print().c_str(), stmod);
}
if (stmod == 0) {
offset_into_data = handle_unpack_v4_16_mode0(code, dma, offset_into_data, cl, wl);
} else if (stmod == 1) {
assert(row_init);
offset_into_data = handle_unpack_v4_16_mode1(code, dma, offset_into_data, cl, wl, row);
} else {
assert(false);
}
break;
case VifCode::Kind::UNPACK_V4_32:
if (DEBUG) {
ImGui::Text(" vif: %s", code.print().c_str());
}
assert(stmod == 0);
offset_into_data = handle_unpack_v4_32(code, dma, offset_into_data, cl, wl);
break;
case VifCode::Kind::UNPACK_V4_8:
if (DEBUG) {
ImGui::Text(" vif: %s", code.print().c_str());
}
if (stmod == 0) {
offset_into_data = handle_unpack_v4_8_mode0(code, dma, offset_into_data, cl, wl);
} else if (stmod == 1) {
assert(row_init);
offset_into_data = handle_unpack_v4_8_mode1(code, dma, offset_into_data, cl, wl, row);
} else {
assert(false);
}
break;
case VifCode::Kind::UNPACK_V3_32:
if (DEBUG) {
ImGui::Text(" vif: %s", code.print().c_str());
}
assert(stmod == 0);
offset_into_data = handle_unpack_v3_32(code, dma, offset_into_data, cl, wl);
break;
case VifCode::Kind::STROW:
row_init = true;
memcpy(row, dma.data + offset_into_data, 16);
offset_into_data += 16;
if (DEBUG) {
Vector4f vec;
memcpy(&vec, row, 16);
ImGui::Text(" row: %s", vec.to_string_aligned().c_str());
// fmt::print(" row: {}\n", vec.to_string_aligned().c_str());
}
break;
case VifCode::Kind::STMOD:
if (DEBUG) {
ImGui::Text(" stmod %d\n", code.immediate);
}
if (stmod == 0) {
assert(code.immediate == 1);
} else {
assert(stmod == 1);
assert(code.immediate == 0 || code.immediate == 1); // kinda weird.
}
stmod = code.immediate;
break;
case VifCode::Kind::STCYCL:
if (DEBUG) {
ImGui::Text(" vif: %s", code.print().c_str());
}
{
VifCodeStcycl ss(code.immediate);
cl = ss.cl;
wl = ss.wl;
}
break;
case VifCode::Kind::NOP:
if (DEBUG) {
ImGui::Text(" NOP");
}
break;
default:
ok = false;
if (DEBUG) {
ImGui::TextColored(ImVec4(0.8, 0.3, 0.3, 1.0), "unhandled vif: %s", code.print().c_str());
}
break;
}
}
if (!ok) {
m_stats.error_packets++;
} else {
if (DEBUG) {
ImGui::Text("END");
}
assert(stmod == 0);
}
}
template <bool DEBUG>
void TFragment::handle_mscal(const VifCode& code,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
if (DEBUG) {
ImGui::TextColored(ImVec4(0.3, 0.8, 0.3, 1.0), "MSCAL: %d", code.immediate);
}
int prog_id = code.immediate / 2;
if (prog_id >= NUM_PROGRAMS) {
fmt::print("bad program: {}\n", prog_id);
assert(false);
}
m_stats.per_program[prog_id].calls++;
switch (code.immediate) {
case 12:
case 6:
exec_program_6<DEBUG>(render_state, prof);
break;
case 8:
if (m_prog8_with_prog6) {
exec_program_6<DEBUG>(render_state, prof);
} else {
m_stats.error_mscals++;
}
break;
case 10:
if (m_prog10_with_prog6) {
exec_program_6<DEBUG>(render_state, prof);
} else {
m_stats.error_mscals++;
}
break;
case 18:
if (m_prog18_with_prog6) {
exec_program_6<DEBUG>(render_state, prof);
} else {
m_stats.error_mscals++;
}
break;
default:
if (m_all_with_prog6) {
exec_program_6<DEBUG>(render_state, prof);
} else {
m_stats.error_mscals++;
if (DEBUG) {
ImGui::TextColored(ImVec4(0.8, 0.8, 0.3, 1.0), " UNHANDLED");
}
}
break;
}
}
void TFragment::flip_buffers() {
m_uploading_buffer ^= 1;
}
std::string TFragData::print() const {
std::string result;
result += fmt::format("fog: {}\n", fog.to_string_aligned());
+3 -217
View File
@@ -2,7 +2,6 @@
#include "game/graphics/opengl_renderer/BucketRenderer.h"
#include "game/graphics/opengl_renderer/DirectRenderer.h"
#include "game/graphics/opengl_renderer/tfrag/BufferedRenderer.h"
#include "game/graphics/opengl_renderer/tfrag/Tfrag3.h"
#include "common/dma/gs.h"
#include "common/math/Vector.h"
@@ -10,8 +9,6 @@
using math::Matrix4f;
using math::Vector4f;
constexpr int KICK_ZONE_END = 1024;
struct TFragData {
Vector4f fog; // 0 656 (vf01)
Vector4f val; // 1 657 (vf02)
@@ -35,10 +32,6 @@ struct TFragBufferedData {
};
static_assert(sizeof(TFragBufferedData) == 328 * 16);
struct TFragKickZone {
u8 pad[(KICK_ZONE_END - 670) * 16];
};
class TFragment : public BucketRenderer {
public:
TFragment(const std::string& name,
@@ -50,186 +43,25 @@ class TFragment : public BucketRenderer {
void draw_debug_window() override;
private:
void handle_initialization(DmaFollower& dma,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
template <bool DEBUG>
void handle_tfrag(const DmaTransfer& dma,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
int handle_unpack_v4_8_mode0(const VifCode& code,
const DmaTransfer& dma,
int offset,
int cl,
int wl);
int handle_unpack_v4_8_mode1(const VifCode& code,
const DmaTransfer& dma,
int offset,
int cl,
int wl,
const u32 row[4]);
int handle_unpack_v4_16_mode0(const VifCode& code,
const DmaTransfer& dma,
int offset,
int cl,
int wl);
int handle_unpack_v4_16_mode1(const VifCode& code,
const DmaTransfer& dma,
int offset,
int cl,
int wl,
const u32 row[4]);
int handle_unpack_v4_32(const VifCode& code, const DmaTransfer& dma, int offset, int cl, int wl);
int handle_unpack_v3_32(const VifCode& code, const DmaTransfer& dma, int offset, int cl, int wl);
template <bool DEBUG>
void handle_mscal(const VifCode& code, SharedRenderState* render_state, ScopedProfilerNode& prof);
template <bool DEBUG>
void exec_program_6(SharedRenderState* render_state, ScopedProfilerNode& prof);
template <bool DEBUG>
void XGKICK(u32 addr, SharedRenderState* render_state, ScopedProfilerNode& prof);
struct Prog6Inputs {
Vector4f vf04_cam_mat_x;
Vector4f vf07_cam_mat_y;
Vector4f vf08_cam_mat_z;
};
struct Prog6Vars {
// pre-set
u16 vi03;
u16 vi07;
u16 vi08;
u16 vi09;
u16 vi14;
Vector4f vf16_scaled_pos_0;
Vector4f vf17_scaled_pos_1;
Vector4f vf18_scaled_pos_2;
Vector4f vf19_scaled_pos_3;
// uninit
u16 vi02;
u16 vi04;
u16 vi05;
u16 vi06_kick_zone_ptr;
u16 vi10;
u16 vi11;
u16 vi12; // seems to be gs loop count (dverts) - 0x80.
u16 vi13;
Vector4f vf09_cam_trans;
Vector4f vf12_root_pos_0; // position a, 0
Vector4f vf13_root_pos_1;
Vector4f vf14_loop_pos_0;
Vector4f vf15_loop_pos_1;
Vector4f vf20;
Vector4f vf21;
Vector4f vf22;
Vector4f vf23;
Vector4f vf24;
Vector4f vf25; // position b, 0
Vector4f vf26;
Vector4f vf27;
Vector4f vf28;
Vector4f vf29;
Vector4f vf30;
Vector4f vf31;
};
template <bool DEBUG>
void exec_program_6_process_first(const Prog6Inputs& in,
Prog6Vars& vars,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
template <bool DEBUG>
void exec_jumper_L128(const Prog6Inputs& in, Prog6Vars& vars);
template <bool DEBUG>
bool exec_jumper_L129(const Prog6Inputs& in,
Prog6Vars& vars,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
template <bool DEBUG>
void exec_jumper_L6A1(const Prog6Inputs& in, Prog6Vars& vars);
template <bool DEBUG>
bool exec_jumper_L130(const Prog6Inputs& in,
Prog6Vars& vars,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
template <bool DEBUG>
void exec_jumper_L6B0(const Prog6Inputs& in, Prog6Vars& vars);
template <bool DEBUG>
bool exec_jumper_L131(const Prog6Inputs& in,
Prog6Vars& vars,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
template <bool DEBUG>
void exec_jumper_L6BF(const Prog6Inputs& in, Prog6Vars& vars);
template <bool DEBUG>
bool exec_jumper_L132(const Prog6Inputs& in,
Prog6Vars& vars,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
template <bool DEBUG>
bool exec_jumper_L122(const Prog6Inputs& in,
Prog6Vars& vars,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
void handle_initialization(DmaFollower& dma);
std::string m_debug_string;
bool m_hack_scrambler = false;
bool m_child_mode = false;
bool m_extra_debug = false;
int m_max_draw = -1;
bool m_skip_mscals = false;
bool m_skip_xgkick = false;
bool m_prog8_with_prog6 = true;
bool m_prog10_with_prog6 = true;
bool m_prog18_with_prog6 = true;
bool m_all_with_prog6 = false;
bool m_use_buffered_renderer = true;
bool m_use_tfrag3 = true;
bool m_hack_test_many_levels = false;
bool m_override_time_of_day = false;
float m_time_of_days[8] = {0};
std::string m_frag_debug;
float m_time_of_days[8] = {1, 0, 0, 0, 0, 0, 0, 0};
// GS setup data
u8 m_test_setup[32];
// VU data
Matrix4f m_matrix_0;
Matrix4f m_matrix_1;
TFragData m_tfrag_data;
TFragKickZone m_kick_data;
TfragPcPortData m_pc_port_data;
// buffers
TFragBufferedData m_buffered_data[2];
int m_uploading_buffer = 0;
u8* get_upload_buffer() { return (u8*)&m_buffered_data[m_uploading_buffer].pad[0]; }
u8* get_processing_buffer() { return (u8*)&m_buffered_data[1 - m_uploading_buffer].pad[0]; }
void flip_buffers();
u16 ilw_data(int offset, int xyzw);
u16 ilw_kick_zone(int offset, int xyzw);
Vector4f load_vector_data(int offset);
void store_vector_kick_zone(int offset, const Vector4f& vec);
void store_gif_kick_zone(int offset, const GifTag& tag);
void store_u32_kick_zone(u32 value, int qw, int xyzw);
enum TFragDataMem {
Buffer0_Start = 0,
@@ -242,56 +74,10 @@ class TFragment : public BucketRenderer {
TFragKickZoneData = 670,
};
enum TFragJumper {
L128_PART0_X = 0,
L129_PART1_X = 1,
L0x6A1_PART0_Y = 2,
L130_PART1_Y = 3,
L0x6B0_PART0_Z = 4,
L131_PART1_Z = 5,
L0x6BF_PART0_W = 6,
L132_PART1_W = 7,
L122_KICK = 8,
END_PROGRAM = 9,
INVALID = 10
};
TFragJumper m_next_block = TFragJumper::INVALID;
TFragJumper m_ret_block = TFragJumper::INVALID;
bool m_clip_and_3ffff = false;
Vector4f m_acc; // todo, probably rearrange this so acc stays entirely in part0 or part1?
float m_q; // todo, probably regroup
enum TFragProgMem {
TFragSetup = 0,
};
struct Ptrs {
int vi01;
int vi14;
int vf03_x, vf03_y, vf03_z, vf03_w;
} m_ptrs;
struct Globals {
Vector4f vf04_ambient;
} m_globals;
static constexpr int NUM_PROGRAMS = 13;
struct Stats {
int tfrag_dma_packets = 0;
int tfrag_bytes = 0;
int error_packets = 0;
int error_mscals = 0;
struct PerProgram {
int calls = 0;
};
PerProgram per_program[NUM_PROGRAMS]; // addr / 2
} m_stats;
DirectRenderer m_direct_renderer;
BufferedRenderer::Builder m_buffered_renderer;
Tfrag3 m_tfrag3;
std::vector<tfrag3::TFragmentTreeKind> m_tree_kinds;
int m_level_id;
+13 -35
View File
@@ -160,33 +160,9 @@ bool Tfrag3::update_load(const std::vector<tfrag3::TFragmentTreeKind>& tree_kind
}
m_load_state.vert++;
if (!remaining) {
m_load_state.state = INIT_TEX;
m_load_state.tex_id = 0;
return true;
}
} break;
case State::INIT_TEX:
for (size_t max_tex =
std::min((size_t)m_load_state.tex_id + MAX_TEX_PER_FRAME, lev_data->textures.size());
m_load_state.tex_id < max_tex; m_load_state.tex_id++) {
auto& tex = lev_data->textures[m_load_state.tex_id];
GLuint gl_tex;
glGenTextures(1, &gl_tex);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.w, tex.h, 0, GL_RGBA,
GL_UNSIGNED_INT_8_8_8_8_REV, tex.data.data());
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glGenerateMipmap(GL_TEXTURE_2D);
float aniso = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, aniso);
m_textures.push_back(gl_tex);
}
return m_load_state.tex_id == lev_data->textures.size();
break;
default:
assert(false);
}
@@ -200,12 +176,18 @@ bool Tfrag3::setup_for_level(const std::vector<tfrag3::TFragmentTreeKind>& tree_
// first, get the level in memory
Timer tfrag3_setup_timer;
auto lev_data = render_state->loader.get_tfrag3_level(level);
if (!lev_data) {
const auto* lev_data = render_state->loader.get_tfrag3_level(level);
if (!lev_data || (m_has_level && lev_data->load_id != m_load_id)) {
m_has_level = false;
m_textures = nullptr;
m_level_name = "";
discard_tree_cache();
return false;
}
int init_load_state = m_load_state.state;
m_textures = &lev_data->textures;
m_load_id = lev_data->load_id;
if (m_level_name != level) {
m_has_level = false;
@@ -213,7 +195,7 @@ bool Tfrag3::setup_for_level(const std::vector<tfrag3::TFragmentTreeKind>& tree_
m_load_state.loading = true;
m_load_state.state = State::FIRST;
}
if (update_load(tree_kinds, lev_data)) {
if (update_load(tree_kinds, lev_data->level.get())) {
m_has_level = true;
m_level_name = level;
m_load_state.loading = false;
@@ -277,7 +259,7 @@ void Tfrag3::render_tree(const TfragRenderSettings& settings,
continue;
}
glBindTexture(GL_TEXTURE_2D, m_textures.at(draw.tree_tex_id));
glBindTexture(GL_TEXTURE_2D, m_textures->at(draw.tree_tex_id));
auto double_draw = setup_tfrag_shader(render_state, draw.mode);
tree.tris_this_frame += draw.num_triangles;
tree.draws_this_frame++;
@@ -393,12 +375,7 @@ void Tfrag3::draw_debug_window() {
}
void Tfrag3::discard_tree_cache() {
for (auto tex : m_textures) {
glBindTexture(GL_TEXTURE_2D, tex);
glDeleteTextures(1, &tex);
}
m_textures.clear();
m_textures = nullptr;
for (auto& tree : m_cached_trees) {
if (tree.kind != tfrag3::TFragmentTreeKind::INVALID) {
glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture);
@@ -408,6 +385,7 @@ void Tfrag3::discard_tree_cache() {
glDeleteVertexArrays(1, &tree.vao);
}
}
m_cached_trees.clear();
}
namespace {
+3 -3
View File
@@ -81,7 +81,7 @@ class Tfrag3 {
std::string m_level_name;
std::vector<GLuint> m_textures;
const std::vector<GLuint>* m_textures = nullptr;
std::vector<TreeCache> m_cached_trees;
std::vector<math::Vector<u8, 4>> m_color_result;
@@ -89,6 +89,8 @@ class Tfrag3 {
GLuint m_debug_vao = -1;
GLuint m_debug_verts = -1;
u64 m_load_id = -1;
// in theory could be up to 4096, I think, but we don't see that many...
// should be easy to increase (will require a shader change too for indexing)
static constexpr int TIME_OF_DAY_COLOR_COUNT = 8192;
@@ -105,13 +107,11 @@ class Tfrag3 {
FREE_OLD_TREES = 1,
INIT_NEW_TREES = 2,
UPLOAD_VERTS = 3,
INIT_TEX = 4,
};
struct {
bool loading = false;
State state;
u32 tex_id = 0;
u32 vert = 0;
} m_load_state;
static constexpr int MAX_TEX_PER_FRAME = 4;
+11 -33
View File
@@ -167,32 +167,10 @@ bool Tie3::update_load(const tfrag3::Level* lev_data) {
}
m_load_state.vert++;
if (!remaining) {
m_load_state.state = INIT_TEX;
m_load_state.tex = 0;
return true;
}
} break;
case State::INIT_TEX:
for (size_t max_tex = std::min((size_t)m_load_state.tex + 3, lev_data->textures.size());
m_load_state.tex < max_tex; m_load_state.tex++) {
auto& tex = lev_data->textures[m_load_state.tex];
GLuint gl_tex;
glGenTextures(1, &gl_tex);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.w, tex.h, 0, GL_RGBA,
GL_UNSIGNED_INT_8_8_8_8_REV, tex.data.data());
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glGenerateMipmap(GL_TEXTURE_2D);
float aniso = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, aniso);
m_textures.push_back(gl_tex);
}
return m_load_state.tex == lev_data->textures.size();
break;
default:
assert(false);
}
@@ -208,9 +186,15 @@ bool Tie3::setup_for_level(const std::string& level, SharedRenderState* render_s
// TODO: right now this will wait to load from disk and unpack it.
Timer tfrag3_setup_timer;
auto lev_data = render_state->loader.get_tfrag3_level(level);
if (!lev_data) {
if (!lev_data || (m_has_level && lev_data->load_id != m_load_id)) {
m_has_level = false;
m_textures = nullptr;
m_level_name = "";
discard_tree_cache();
return false;
}
m_textures = &lev_data->textures;
m_load_id = lev_data->load_id;
int init_load_state = m_load_state.state;
if (m_level_name != level) {
@@ -219,7 +203,7 @@ bool Tie3::setup_for_level(const std::string& level, SharedRenderState* render_s
m_load_state.loading = true;
m_load_state.state = State::FIRST;
}
if (update_load(lev_data)) {
if (update_load(lev_data->level.get())) {
m_has_level = true;
m_level_name = level;
m_load_state.loading = false;
@@ -346,12 +330,6 @@ void do_wind_math(u16 wind_idx,
}
void Tie3::discard_tree_cache() {
for (auto tex : m_textures) {
glBindTexture(GL_TEXTURE_2D, tex);
glDeleteTextures(1, &tex);
}
m_textures.clear();
for (auto& tree : m_trees) {
glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture);
glDeleteTextures(1, &tree.time_of_day_texture);
@@ -528,7 +506,7 @@ void Tie3::render_tree_wind(int idx,
const auto& draw = tree.wind_draws->operator[](draw_idx);
if ((int)draw.tree_tex_id != last_texture) {
glBindTexture(GL_TEXTURE_2D, m_textures.at(draw.tree_tex_id));
glBindTexture(GL_TEXTURE_2D, m_textures->at(draw.tree_tex_id));
last_texture = draw.tree_tex_id;
}
auto double_draw = setup_tfrag_shader(render_state, draw.mode);
@@ -659,7 +637,7 @@ void Tie3::render_tree(int idx,
}
if ((int)draw.tree_tex_id != last_texture) {
glBindTexture(GL_TEXTURE_2D, m_textures.at(draw.tree_tex_id));
glBindTexture(GL_TEXTURE_2D, m_textures->at(draw.tree_tex_id));
last_texture = draw.tree_tex_id;
}
+2 -1
View File
@@ -78,7 +78,8 @@ class Tie3 : public BucketRenderer {
std::vector<Tree> m_trees;
std::string m_level_name;
std::vector<GLuint> m_textures; // todo, can we share with tfrag in some cases?
const std::vector<GLuint>* m_textures;
u64 m_load_id = -1;
struct Cache {
std::vector<u8> vis_temp;
File diff suppressed because it is too large Load Diff
@@ -1,240 +0,0 @@
#include "game/graphics/opengl_renderer/tfrag/TFragment.h"
// TFragment VIF unpack implementation
int TFragment::handle_unpack_v4_8_mode0(const VifCode& code,
const DmaTransfer& dma,
int offset,
int cl,
int wl) {
VifCodeUnpack unpack(code);
assert(unpack.use_tops_flag);
// CL x (num/WL)+(num%WL)
u8* write_base = get_upload_buffer();
if (unpack.is_unsigned) {
// note: formulas below assume this!
assert(cl == 2);
assert(wl == 1);
assert(code.num);
for (int i = 0; i < code.num; i++) {
// write every other qw
int dest_qw = unpack.addr_qw + 2 * i;
assert(dest_qw <= 328);
u32 qw[4];
qw[0] = dma.read_val<u8>(offset++);
qw[1] = dma.read_val<u8>(offset++);
qw[2] = dma.read_val<u8>(offset++);
qw[3] = dma.read_val<u8>(offset++);
memcpy(write_base + (dest_qw * 16), qw, 16);
}
} else {
// note: formulas below assume this!
assert(cl == 4);
assert(wl == 4);
assert(code.num);
for (int i = 0; i < code.num; i++) {
// write every other qw
int dest_qw = unpack.addr_qw + i;
assert(dest_qw <= 328);
s32 qw[4];
qw[0] = dma.read_val<s8>(offset++);
qw[1] = dma.read_val<s8>(offset++);
qw[2] = dma.read_val<s8>(offset++);
qw[3] = dma.read_val<s8>(offset++);
memcpy(write_base + (dest_qw * 16), qw, 16);
}
}
return offset;
}
int TFragment::handle_unpack_v4_8_mode1(const VifCode& code,
const DmaTransfer& dma,
int offset,
int cl,
int wl,
const u32 row[4]) {
VifCodeUnpack unpack(code);
assert(unpack.use_tops_flag);
// CL x (num/WL)+(num%WL)
u8* write_base = get_upload_buffer();
if (unpack.is_unsigned) {
// note: formulas below assume this!
assert(cl == 4);
assert(wl == 4);
assert(code.num);
for (int i = 0; i < code.num; i++) {
// write every other qw
int dest_qw = unpack.addr_qw + i;
assert(dest_qw <= 328);
u32 qw[4];
qw[0] = row[0] + dma.read_val<u8>(offset++);
qw[1] = row[1] + dma.read_val<u8>(offset++);
qw[2] = row[2] + dma.read_val<u8>(offset++);
qw[3] = row[3] + dma.read_val<u8>(offset++);
memcpy(write_base + (dest_qw * 16), qw, 16);
}
} else {
// note: formulas below assume this!
assert(cl == 4);
assert(wl == 4);
assert(code.num);
for (int i = 0; i < code.num; i++) {
// write every other qw
int dest_qw = unpack.addr_qw + i;
assert(dest_qw <= 328);
s32 qw[4];
qw[0] = row[0] + dma.read_val<s8>(offset++);
qw[1] = row[1] + dma.read_val<s8>(offset++);
qw[2] = row[2] + dma.read_val<s8>(offset++);
qw[3] = row[3] + dma.read_val<s8>(offset++);
memcpy(write_base + (dest_qw * 16), qw, 16);
}
}
return offset;
}
int TFragment::handle_unpack_v4_16_mode0(const VifCode& code,
const DmaTransfer& dma,
int offset,
int cl,
int wl) {
VifCodeUnpack unpack(code);
assert(unpack.use_tops_flag);
assert(unpack.is_unsigned);
// note: formulas below assume this!
assert(cl == 4);
assert(wl == 4);
u8* write_base = get_upload_buffer();
assert(code.num);
for (int i = 0; i < code.num; i++) {
// write every other qw
int dest_qw = unpack.addr_qw + i;
assert(dest_qw <= 328);
u32 qw[4];
qw[0] = dma.read_val<u16>(offset);
offset += 2;
qw[1] = dma.read_val<u16>(offset);
offset += 2;
qw[2] = dma.read_val<u16>(offset);
offset += 2;
qw[3] = dma.read_val<u16>(offset);
offset += 2;
memcpy(write_base + (dest_qw * 16), qw, 16);
}
return offset;
}
int TFragment::handle_unpack_v4_16_mode1(const VifCode& code,
const DmaTransfer& dma,
int offset,
int cl,
int wl,
const u32 row[4]) {
VifCodeUnpack unpack(code);
assert(unpack.use_tops_flag);
assert(unpack.is_unsigned);
// note: formulas below assume this!
assert(cl == 4);
assert(wl == 4);
assert(code.num);
u8* write_base = get_upload_buffer();
for (int i = 0; i < code.num; i++) {
// write every other qw
int dest_qw = unpack.addr_qw + i;
assert(dest_qw <= 328);
u32 qw[4];
qw[0] = row[0] + (u32)dma.read_val<u16>(offset);
offset += 2;
qw[1] = row[1] + (u32)dma.read_val<u16>(offset);
offset += 2;
qw[2] = row[2] + (u32)dma.read_val<u16>(offset);
offset += 2;
qw[3] = row[3] + (u32)dma.read_val<u16>(offset);
offset += 2;
// fmt::print(" unpack rgba?: {:x} {:x} {:x} {:x}\n", qw[0], qw[1], qw[2], qw[3]);
memcpy(write_base + (dest_qw * 16), qw, 16);
}
return offset;
}
int TFragment::handle_unpack_v3_32(const VifCode& code,
const DmaTransfer& dma,
int offset,
int cl,
int wl) {
VifCodeUnpack unpack(code);
assert(unpack.use_tops_flag);
assert(!unpack.is_unsigned);
// note: formulas below assume this!
assert(cl == 2);
assert(wl == 1);
assert(code.num);
u8* write_base = get_upload_buffer();
for (int i = 0; i < code.num; i++) {
// write every other qw
int dest_qw = unpack.addr_qw + i * 2;
assert(dest_qw <= 328);
u32 qw[4];
qw[0] = dma.read_val<u32>(offset);
offset += 4;
qw[1] = dma.read_val<u32>(offset);
offset += 4;
qw[2] = dma.read_val<u32>(offset);
offset += 4;
qw[3] = 0x80; // this can be anything... but it seems like it tries to load from it sometimes?
memcpy(write_base + (dest_qw * 16), qw, 16);
}
return offset;
}
int TFragment::handle_unpack_v4_32(const VifCode& code,
const DmaTransfer& dma,
int offset,
int cl,
int wl) {
VifCodeUnpack unpack(code);
assert(unpack.use_tops_flag);
assert(!unpack.is_unsigned);
// note: formulas below assume this!
assert(cl == 4);
assert(wl == 4);
u8* write_base = get_upload_buffer();
assert(code.num);
for (int i = 0; i < code.num; i++) {
// write every other qw
int dest_qw = unpack.addr_qw + i;
assert(dest_qw <= 328);
u32 qw[4];
qw[0] = dma.read_val<u32>(offset);
offset += 4;
qw[1] = dma.read_val<u32>(offset);
offset += 4;
qw[2] = dma.read_val<u32>(offset);
offset += 4;
qw[3] = dma.read_val<u32>(offset);
offset += 4;
memcpy(write_base + (dest_qw * 16), qw, 16);
}
return offset;
// u8* write_base = get_upload_buffer();
// assert(code.num + unpack.addr_qw <= 328);
// memcpy(write_base + (unpack.addr_qw * 16), dma.data + offset, code.num * 16);
// return offset + code.num * 16;
}