mirror of
https://github.com/open-goal/jak-project
synced 2026-07-27 06:45:56 -04:00
[game] clean up texture stuff (#1120)
* better texture loading * remove old and bad tfrag renderers
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(®ister_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
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user