[graphics] merc2 renderer (#1374)

* tfrag3 data for merc2

* dma hooks for merc2

* start designing merc2 opengl, seems like the simple approach will be the best here

* before bone packing experiment

* fix up bones.gc

* use uniform buffer

* speedup, fix faces and eyes

* final fixes
This commit is contained in:
water111
2022-05-28 19:28:19 -04:00
committed by GitHub
parent 9449078a9b
commit e56b2e8d56
32 changed files with 1639 additions and 317 deletions
+53
View File
@@ -237,6 +237,54 @@ void CollisionMesh::serialize(Serializer& ser) {
ser.from_pod_vector(&vertices);
}
void MercDraw::serialize(Serializer& ser) {
ser.from_ptr(&mode);
ser.from_ptr(&tree_tex_id);
ser.from_ptr(&first_index);
ser.from_ptr(&index_count);
ser.from_ptr(&num_triangles);
}
void MercEffect::serialize(Serializer& ser) {
if (ser.is_saving()) {
ser.save<size_t>(draws.size());
} else {
draws.resize(ser.load<size_t>());
}
for (auto& draw : draws) {
draw.serialize(ser);
}
}
void MercModel::serialize(Serializer& ser) {
ser.from_str(&name);
if (ser.is_saving()) {
ser.save<size_t>(effects.size());
} else {
effects.resize(ser.load<size_t>());
}
for (auto& effect : effects) {
effect.serialize(ser);
}
ser.from_ptr(&scale_xyz);
ser.from_ptr(&max_draws);
ser.from_ptr(&max_bones);
}
void MercModelGroup::serialize(Serializer& ser) {
if (ser.is_saving()) {
ser.save<size_t>(models.size());
} else {
models.resize(ser.load<size_t>());
}
for (auto& model : models) {
model.serialize(ser);
}
ser.from_pod_vector(&indices);
ser.from_pod_vector(&vertices);
}
void Level::serialize(Serializer& ser) {
ser.from_ptr(&version);
if (ser.is_loading() && version != TFRAG3_VERSION) {
@@ -287,6 +335,7 @@ void Level::serialize(Serializer& ser) {
}
collision.serialize(ser);
merc_data.serialize(ser);
ser.from_ptr(&version2);
if (ser.is_loading() && version2 != TFRAG3_VERSION) {
@@ -358,6 +407,10 @@ std::array<int, MemoryUsageCategory::NUM_CATEGORIES> Level::get_memory_usage() c
result[SHRUB_IND] += sizeof(u32) * shrub_tree.indices.size();
}
// merc
result[MERC_INDEX] += merc_data.indices.size() * sizeof(u32);
result[MERC_VERT] += merc_data.vertices.size() * sizeof(MercVertex);
// collision
result[COLLISION] += sizeof(CollisionMesh::Vertex) * collision.vertices.size();
+58 -1
View File
@@ -45,12 +45,15 @@ enum MemoryUsageCategory {
SHRUB_VERT,
SHRUB_IND,
MERC_VERT,
MERC_INDEX,
COLLISION,
NUM_CATEGORIES
};
constexpr int TFRAG3_VERSION = 17;
constexpr int TFRAG3_VERSION = 19;
// These vertices should be uploaded to the GPU at load time and don't change
struct PreloadedVertex {
@@ -323,6 +326,59 @@ struct CollisionMesh {
void serialize(Serializer& ser);
};
// MERC
struct MercVertex {
float pos[3];
float pad0;
float normal[3];
float pad1;
float weights[3];
float pad2;
float st[2];
u8 rgba[4];
u8 mats[3];
u8 pad3;
};
static_assert(sizeof(MercVertex) == 64);
struct MercDraw {
DrawMode mode;
u32 tree_tex_id = 0; // the texture that should be bound for the draw
u32 first_index;
u32 index_count;
u32 num_triangles;
void serialize(Serializer& ser);
};
struct MercEffect {
std::vector<MercDraw> draws;
void serialize(Serializer& ser);
};
struct MercModel {
std::string name;
std::vector<MercEffect> effects;
float scale_xyz;
u32 max_draws;
u32 max_bones;
void serialize(Serializer& ser);
};
struct MercModelGroup {
std::vector<MercVertex> vertices;
std::vector<u32> indices;
std::vector<MercModel> models;
void serialize(Serializer& ser);
};
//
constexpr int TFRAG_GEOS = 3;
constexpr int TIE_GEOS = 4;
@@ -334,6 +390,7 @@ struct Level {
std::array<std::vector<TieTree>, TIE_GEOS> tie_trees;
std::vector<ShrubTree> shrub_trees;
CollisionMesh collision;
MercModelGroup merc_data;
u16 version2 = TFRAG3_VERSION;
void serialize(Serializer& ser);
+3
View File
@@ -79,6 +79,9 @@ std::string VifCode::print() {
case Kind::DIRECTHL:
result = "DIRECTHL";
break;
case Kind::PC_PORT:
result = "PC_PORT";
break;
case Kind::UNPACK_V4_8: {
VifCodeUnpack up(*this);
result = fmt::format("UNPACK-V4-8: {} addr: {} us: {} tops: {}", num, up.addr_qw,
+1
View File
@@ -53,6 +53,7 @@ add_library(
level_extractor/BspHeader.cpp
level_extractor/extract_collide_frags.cpp
level_extractor/extract_common.cpp
level_extractor/extract_level.cpp
level_extractor/extract_merc.cpp
level_extractor/extract_tfrag.cpp
+1
View File
@@ -5,6 +5,7 @@
namespace decompiler {
void MercCtrlHeader::from_ref(TypedRef tr, const DecompilerTypeSystem& dts) {
st_magic = read_plain_data_field<u32>(tr, "st-magic", dts);
xyz_scale = read_plain_data_field<float>(tr, "xyz-scale", dts);
st_out_a = read_plain_data_field<u32>(tr, "st-out-a", dts);
st_out_b = read_plain_data_field<u32>(tr, "st-out-b", dts);
@@ -0,0 +1,44 @@
#include "extract_common.h"
u32 clean_up_vertex_indices(std::vector<u32>& idx) {
std::vector<u32> fixed;
u32 num_tris = 0;
bool looking_for_start = true;
size_t i_of_start;
for (size_t i = 0; i < idx.size(); i++) {
if (looking_for_start) {
if (idx[i] != UINT32_MAX) {
looking_for_start = false;
i_of_start = i;
}
} else {
if (idx[i] == UINT32_MAX) {
looking_for_start = true;
size_t num_verts = i - i_of_start;
if (num_verts >= 3) {
if (!fixed.empty()) {
fixed.push_back(UINT32_MAX);
}
fixed.insert(fixed.end(), idx.begin() + i_of_start, idx.begin() + i);
num_tris += (num_verts - 2);
}
}
}
}
if (!looking_for_start) {
size_t num_verts = idx.size() - i_of_start;
if (num_verts >= 3) {
if (!fixed.empty()) {
fixed.push_back(UINT32_MAX);
}
fixed.insert(fixed.end(), idx.begin() + i_of_start, idx.begin() + idx.size());
num_tris += (num_verts - 2);
}
}
idx = std::move(fixed);
return num_tris;
}
@@ -0,0 +1,5 @@
#pragma once
#include <vector>
#include "common/common_types.h"
u32 clean_up_vertex_indices(std::vector<u32>& idx);
+6 -1
View File
@@ -80,7 +80,9 @@ void print_memory_usage(const tfrag3::Level& lev, int uncompressed_data_size) {
{"shrub-colors", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_TIME_OF_DAY]},
{"shrub-vert", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_VERT]},
{"shrub-ind", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_IND]},
{"collision", memory_use_by_category[tfrag3::MemoryUsageCategory::COLLISION]}};
{"collision", memory_use_by_category[tfrag3::MemoryUsageCategory::COLLISION]},
{"merc-vert", memory_use_by_category[tfrag3::MemoryUsageCategory::MERC_VERT]},
{"merc-idx", memory_use_by_category[tfrag3::MemoryUsageCategory::MERC_INDEX]}};
for (auto& known : known_categories) {
total_accounted += known.second;
}
@@ -263,6 +265,8 @@ void extract_common(const ObjectFileDB& db,
tfrag_level.serialize(ser);
auto compressed =
compression::compress_zstd(ser.get_save_result().first, ser.get_save_result().second);
fmt::print("stats for {}\n", dgo_name);
print_memory_usage(tfrag_level, ser.get_save_result().second);
fmt::print("compressed: {} -> {} ({:.2f}%)\n", ser.get_save_result().second, compressed.size(),
100.f * compressed.size() / ser.get_save_result().second);
@@ -293,6 +297,7 @@ void extract_from_level(const ObjectFileDB& db,
level_data.serialize(ser);
auto compressed =
compression::compress_zstd(ser.get_save_result().first, ser.get_save_result().second);
fmt::print("stats for {}\n", dgo_name);
print_memory_usage(level_data, ser.get_save_result().second);
fmt::print("compressed: {} -> {} ({:.2f}%)\n", ser.get_save_result().second, compressed.size(),
100.f * compressed.size() / ser.get_save_result().second);
+149 -7
View File
@@ -1,6 +1,7 @@
#include "extract_merc.h"
#include "decompiler/util/goal_data_reader.h"
#include "decompiler/level_extractor/MercData.h"
#include "decompiler/level_extractor/extract_common.h"
#include "common/util/FileUtil.h"
#include "common/util/colors.h"
@@ -228,6 +229,9 @@ DrawMode process_draw_mode(const MercShader& info) {
// check these
mode.disable_ab();
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST);
mode.set_tcc(info.tex0.tcc());
mode.set_decal(info.tex0.tfx() == GsTex0::TextureFunction::DECAL);
mode.set_filt_enable(info.tex1.mmag());
// the alpha matters (maybe?)
update_mode_from_alpha1(info.alpha, mode);
@@ -334,8 +338,8 @@ void handle_frag(const std::string& debug_name,
if (vtx.kind == 1) {
vtx.skel_mats[0] = state.vu1_matrix_slots.at(vu1_addr_to_matrix_slot(mat0_addr));
vtx.skel_mats[1] = -1;
vtx.skel_mats[2] = -1;
vtx.skel_mats[1] = 0;
vtx.skel_mats[2] = 0;
vtx.mat_weights[0] = 1.f;
vtx.mat_weights[1] = 0.f;
vtx.mat_weights[2] = 0.f;
@@ -351,7 +355,7 @@ void handle_frag(const std::string& debug_name,
}
vtx.skel_mats[0] = state.vu1_matrix_slots.at(prev_mat0);
vtx.skel_mats[1] = state.vu1_matrix_slots.at(prev_mat1);
vtx.skel_mats[2] = -1;
vtx.skel_mats[2] = 0;
if (m0 != 0x7f && i != mat1_cnt) {
if (perc_toggle) {
@@ -436,6 +440,7 @@ void handle_frag(const std::string& debug_name,
vtx.dst0 = float_as_u32(v1.x()) - 371; // xtop to output buffer offset
vtx.dst1 = float_as_u32(v1.y()) - 371;
vtx.rgba = frag.unsigned_four_including_header.at(rgba_ptr);
vtx.st = math::Vector2f(v2.x(), v2.y());
// crazy flag logic to set adc
s16 mat1_flag = mat1;
@@ -812,6 +817,41 @@ ConvertedMercEffect convert_merc_effect(const MercEffect& input_effect,
return result;
}
u8 convert_mat(int in) {
if (in >= 0) {
return in;
} else {
return 0;
}
}
tfrag3::MercVertex convert_vertex(const MercUnpackedVtx& vtx) {
tfrag3::MercVertex out;
out.pos[0] = vtx.pos[0];
out.pos[1] = vtx.pos[1];
out.pos[2] = vtx.pos[2];
out.pad0 = 0;
out.normal[0] = vtx.nrm[0];
out.normal[1] = vtx.nrm[1];
out.normal[2] = vtx.nrm[2];
out.pad1 = 0;
out.weights[0] = vtx.mat_weights[0];
out.weights[1] = vtx.mat_weights[1];
out.weights[2] = vtx.mat_weights[2];
out.pad2 = 0;
out.st[0] = vtx.st[0];
out.st[1] = vtx.st[1];
out.rgba[0] = vtx.rgba[0];
out.rgba[1] = vtx.rgba[1];
out.rgba[2] = vtx.rgba[2];
out.rgba[3] = vtx.rgba[3];
out.mats[0] = convert_mat(vtx.skel_mats[0]);
out.mats[1] = convert_mat(vtx.skel_mats[1]);
out.mats[2] = convert_mat(vtx.skel_mats[2]);
out.pad3 = 0;
return out;
}
/*!
* Top-level merc extraction
*/
@@ -819,7 +859,7 @@ void extract_merc(const ObjectFileData& ag_data,
const TextureDB& tex_db,
const DecompilerTypeSystem& dts,
const std::vector<level_tools::TextureRemap>& map,
tfrag3::Level& /*out*/,
tfrag3::Level& out,
bool dump_level) {
if (dump_level) {
file_util::create_dir_if_needed(file_util::get_file_path({"debug_out/merc"}));
@@ -835,11 +875,113 @@ void extract_merc(const ObjectFileData& ag_data,
}
// extract draws. this does no regrouping yet.
std::vector<ConvertedMercEffect> all_effects;
std::vector<std::vector<ConvertedMercEffect>> all_effects;
for (size_t ci = 0; ci < ctrls.size(); ci++) {
auto& effects_in_ctrl = all_effects.emplace_back();
for (size_t ei = 0; ei < ctrls[ci].effects.size(); ei++) {
all_effects.push_back(convert_merc_effect(ctrls[ci].effects[ei], ctrls[ci].header, tex_db,
map, ctrls[ci].name, ci, ei, dump_level));
effects_in_ctrl.push_back(convert_merc_effect(ctrls[ci].effects[ei], ctrls[ci].header, tex_db,
map, ctrls[ci].name, ci, ei, dump_level));
}
}
// convert to PC format
// first pass, before merging indices
u32 first_model = out.merc_data.models.size();
std::vector<std::vector<std::vector<std::vector<u32>>>> indices_temp; // ctrl, effect, draw, vtx
for (size_t ci = 0; ci < ctrls.size(); ci++) {
indices_temp.emplace_back();
auto& pc_ctrl = out.merc_data.models.emplace_back();
auto& ctrl = ctrls[ci];
pc_ctrl.name = ctrl.name;
pc_ctrl.scale_xyz = ctrl.header.xyz_scale;
pc_ctrl.max_draws = 0;
pc_ctrl.max_bones = 0;
for (size_t ei = 0; ei < ctrls[ci].effects.size(); ei++) {
indices_temp[ci].emplace_back();
auto& pc_effect = pc_ctrl.effects.emplace_back();
auto& effect = all_effects[ci][ei];
u32 first_vertex = out.merc_data.vertices.size();
for (auto& vtx : effect.vertices) {
auto cvtx = convert_vertex(vtx);
out.merc_data.vertices.push_back(cvtx);
for (int i = 0; i < 3; i++) {
pc_ctrl.max_bones = std::max(pc_ctrl.max_bones, (u32)cvtx.mats[i]);
}
}
// can do two types of de-duplication: toggling back and forth shaders and matrices
std::map<u64, u64> draw_mode_dedup;
for (auto& draw : effect.draws) {
pc_ctrl.max_draws++;
indices_temp[ci][ei].emplace_back();
// find draw to add to, or create a new one
const auto& existing = draw_mode_dedup.find(draw.state.merc_draw_mode.as_u64());
tfrag3::MercDraw* pc_draw = nullptr;
u64 pc_draw_idx = -1;
if (existing == draw_mode_dedup.end()) {
pc_draw_idx = pc_effect.draws.size();
draw_mode_dedup[draw.state.merc_draw_mode.as_u64()] = pc_draw_idx;
pc_draw = &pc_effect.draws.emplace_back();
pc_draw->mode = draw.state.merc_draw_mode.mode;
u32 idx_in_level_texture = UINT32_MAX;
for (u32 i = 0; i < out.textures.size(); i++) {
if (out.textures[i].combo_id == draw.state.merc_draw_mode.pc_combo_tex_id) {
idx_in_level_texture = i;
break;
}
}
if (idx_in_level_texture == UINT32_MAX) {
// not added to level, add it
auto tex_it = tex_db.textures.find(draw.state.merc_draw_mode.pc_combo_tex_id);
if (tex_it == tex_db.textures.end()) {
ASSERT(false);
} else {
idx_in_level_texture = out.textures.size();
auto& new_tex = out.textures.emplace_back();
new_tex.combo_id = draw.state.merc_draw_mode.pc_combo_tex_id;
new_tex.w = tex_it->second.w;
new_tex.h = tex_it->second.h;
new_tex.debug_name = tex_it->second.name;
new_tex.debug_tpage_name = tex_db.tpage_names.at(tex_it->second.page);
new_tex.data = tex_it->second.rgba_bytes;
}
}
pc_draw->tree_tex_id = idx_in_level_texture;
} else {
pc_draw_idx = existing->second;
pc_draw = &pc_effect.draws.at(pc_draw_idx);
}
for (auto idx : draw.indices) {
if (idx == UINT32_MAX) {
indices_temp[ci][ei][pc_draw_idx].push_back(idx);
} else {
indices_temp[ci][ei][pc_draw_idx].push_back(idx + first_vertex);
}
}
}
}
}
// merge indices
for (size_t ci = 0; ci < ctrls.size(); ci++) {
auto& pc_ctrl = out.merc_data.models.at(ci + first_model);
for (size_t ei = 0; ei < ctrls[ci].effects.size(); ei++) {
auto& pc_effect = pc_ctrl.effects.at(ei);
for (size_t di = 0; di < pc_effect.draws.size(); di++) {
auto& pc_draw = pc_effect.draws.at(di);
auto& inds = indices_temp[ci][ei][di];
pc_draw.num_triangles = clean_up_vertex_indices(inds);
pc_draw.first_index = out.merc_data.indices.size();
pc_draw.index_count = inds.size();
out.merc_data.indices.insert(out.merc_data.indices.end(), inds.begin(), inds.end());
}
}
}
}
+1 -44
View File
@@ -1,7 +1,7 @@
#include <array>
#include "extract_shrub.h"
#include "decompiler/level_extractor/extract_common.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
#include "common/util/FileUtil.h"
@@ -395,49 +395,6 @@ std::string dump_full_to_obj(const std::vector<ShrubProtoInfo>& protos) {
return result;
}
u32 clean_up_vertex_indices(std::vector<u32>& idx) {
std::vector<u32> fixed;
u32 num_tris = 0;
bool looking_for_start = true;
size_t i_of_start;
for (size_t i = 0; i < idx.size(); i++) {
if (looking_for_start) {
if (idx[i] != UINT32_MAX) {
looking_for_start = false;
i_of_start = i;
}
} else {
if (idx[i] == UINT32_MAX) {
looking_for_start = true;
size_t num_verts = i - i_of_start;
if (num_verts >= 3) {
if (!fixed.empty()) {
fixed.push_back(UINT32_MAX);
}
fixed.insert(fixed.end(), idx.begin() + i_of_start, idx.begin() + i);
num_tris += (num_verts - 2);
}
}
}
}
if (!looking_for_start) {
size_t num_verts = idx.size() - i_of_start;
if (num_verts >= 3) {
if (!fixed.empty()) {
fixed.push_back(UINT32_MAX);
}
fixed.insert(fixed.end(), idx.begin() + i_of_start, idx.begin() + idx.size());
num_tris += (num_verts - 2);
}
}
idx = std::move(fixed);
return num_tris;
}
void make_draws(tfrag3::Level& lev,
tfrag3::ShrubTree& tree_out,
const std::vector<ShrubProtoInfo>& protos,
+1
View File
@@ -85,6 +85,7 @@ set(RUNTIME_SOURCE
graphics/opengl_renderer/background/Tfrag3.cpp
graphics/opengl_renderer/background/TFragment.cpp
graphics/opengl_renderer/background/Tie3.cpp
graphics/opengl_renderer/foreground/Merc2.cpp
graphics/opengl_renderer/foreground/Generic2.cpp
graphics/opengl_renderer/foreground/Generic2_DMA.cpp
graphics/opengl_renderer/foreground/Generic2_Build.cpp
@@ -89,4 +89,16 @@ void RenderMux::draw_debug_window() {
ImGui::ListBox("Pick", &m_render_idx, m_name_str_ptrs.data(), m_renderers.size());
ImGui::Separator();
m_renderers[m_render_idx]->draw_debug_window();
}
void RenderMux::init_textures(TexturePool& tp) {
for (auto& rend : m_renderers) {
rend->init_textures(tp);
}
}
void RenderMux::init_shaders(ShaderLibrary& sl) {
for (auto& rend : m_renderers) {
rend->init_shaders(sl);
}
}
@@ -40,7 +40,8 @@ struct SharedRenderState {
bool use_direct2 = true;
math::Vector<u8, 4> fog_color;
float fog_intensity = 1.f;
bool no_multidraw = true;
bool no_multidraw = false;
bool merc2 = true;
void reset();
bool has_pc_data = false;
@@ -87,6 +88,9 @@ class RenderMux : public BucketRenderer {
std::vector<std::unique_ptr<BucketRenderer>> renderers);
void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override;
void draw_debug_window() override;
void init_shaders(ShaderLibrary&) override;
void init_textures(TexturePool&) override;
void set_idx(u32 i) { m_render_idx = i; };
private:
std::vector<std::unique_ptr<BucketRenderer>> m_renderers;
+80 -30
View File
@@ -39,8 +39,8 @@ const Loader::LevelData* Loader::get_tfrag3_level(const std::string& level_name)
if (existing == m_loaded_tfrag3_levels.end()) {
return nullptr;
} else {
existing->second.frames_since_last_used = 0;
return &existing->second.data;
existing->second->frames_since_last_used = 0;
return existing->second.get();
}
}
@@ -83,8 +83,8 @@ std::vector<Loader::LevelData*> Loader::get_in_use_levels() {
std::unique_lock<std::mutex> lk(m_loader_mutex);
for (auto& lev : m_loaded_tfrag3_levels) {
if (lev.second.frames_since_last_used < 5) {
result.push_back(&lev.second.data);
if (lev.second->frames_since_last_used < 5) {
result.push_back(lev.second.get());
}
}
return result;
@@ -151,8 +151,8 @@ void Loader::loader_thread() {
// grab the lock again
lk.lock();
// move this level to "initializing" state.
m_initializing_tfrag3_levels[lev].data = {}; // reset load state
m_initializing_tfrag3_levels[lev].data.level = std::move(result);
m_initializing_tfrag3_levels[lev] = std::make_unique<LevelData>(); // reset load state
m_initializing_tfrag3_levels[lev]->level = std::move(result);
m_level_to_load = "";
m_file_load_done_cv.notify_all();
}
@@ -168,10 +168,14 @@ void Loader::load_common(TexturePool& tex_pool, const std::string& name) {
auto decomp_data = compression::decompress_zstd(data.data(), data.size());
Serializer ser(decomp_data.data(), decomp_data.size());
m_common_level.serialize(ser);
for (auto& tex : m_common_level.textures) {
add_texture(tex_pool, tex, true);
m_common_level.level = std::make_unique<tfrag3::Level>();
m_common_level.level->serialize(ser);
for (auto& tex : m_common_level.level->textures) {
m_common_level.textures.push_back(add_texture(tex_pool, tex, true));
}
Timer tim;
init_merc(tim, m_common_level);
}
/*!
@@ -490,11 +494,33 @@ bool Loader::init_tie(Timer& timer, LevelData& data) {
}
bool Loader::init_collide(Timer& /*timer*/, LevelData& data) {
Timer t;
glGenBuffers(1, &data.collide_vertices);
glBindBuffer(GL_ARRAY_BUFFER, data.collide_vertices);
glBufferData(GL_ARRAY_BUFFER,
data.level->collision.vertices.size() * sizeof(tfrag3::CollisionMesh::Vertex),
data.level->collision.vertices.data(), GL_STATIC_DRAW);
fmt::print("init_collide took {:.2f} ms\n", t.getMs());
return true;
}
bool Loader::init_merc(Timer& /*timer*/, LevelData& data) {
Timer t;
glGenBuffers(1, &data.merc_indices);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.merc_indices);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.level->merc_data.indices.size() * sizeof(u32),
data.level->merc_data.indices.data(), GL_STATIC_DRAW);
glGenBuffers(1, &data.merc_vertices);
glBindBuffer(GL_ARRAY_BUFFER, data.merc_vertices);
glBufferData(GL_ARRAY_BUFFER, data.level->merc_data.vertices.size() * sizeof(tfrag3::MercVertex),
data.level->merc_data.vertices.data(), GL_STATIC_DRAW);
for (auto& model : data.level->merc_data.models) {
data.merc_model_lookup[model.name] = &model;
m_all_merc_models[model.name].push_back({&model, data.load_id, &data});
}
fmt::print("init_merc took {:.2f} ms\n", t.getMs());
return true;
}
@@ -584,7 +610,7 @@ void Loader::update(TexturePool& texture_pool) {
// only main thread can touch this.
for (auto& lev : m_loaded_tfrag3_levels) {
lev.second.frames_since_last_used++;
lev.second->frames_since_last_used++;
}
bool did_gpu_stuff = false;
@@ -598,22 +624,25 @@ void Loader::update(TexturePool& texture_pool) {
if (it != m_initializing_tfrag3_levels.end()) {
std::string name = it->first;
auto& lev = it->second;
it->second->load_id = m_id++;
// we're the only place that erases, so it's okay to unlock and hold a reference
lk.unlock();
if (!lev.data.tfrag_load_done) {
if (!lev->tfrag_load_done) {
did_gpu_stuff = true;
}
if (upload_textures(loader_timer, lev.data, texture_pool)) {
if (init_tie(loader_timer, lev.data)) {
if (init_tfrag(loader_timer, lev.data)) {
if (init_shrub(loader_timer, lev.data)) {
if (init_collide(loader_timer, lev.data)) {
// we're done! lock before removing from loaded.
lk.lock();
it->second.data.load_id = m_id++;
if (upload_textures(loader_timer, *lev, texture_pool)) {
if (init_tie(loader_timer, *lev)) {
if (init_tfrag(loader_timer, *lev)) {
if (init_shrub(loader_timer, *lev)) {
if (init_collide(loader_timer, *lev)) {
if (init_merc(loader_timer, *lev)) {
// we're done! lock before removing from loaded.
lk.lock();
m_loaded_tfrag3_levels[name] = std::move(lev);
m_initializing_tfrag3_levels.erase(it);
m_loaded_tfrag3_levels[name] = std::move(lev);
m_initializing_tfrag3_levels.erase(it);
}
}
}
}
@@ -625,19 +654,19 @@ void Loader::update(TexturePool& texture_pool) {
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) {
for (auto& lev : m_loaded_tfrag3_levels) {
if (lev.second->frames_since_last_used > 180) {
std::unique_lock<std::mutex> lk(texture_pool.mutex());
fmt::print("------------------------- PC unloading {}\n", lev.first);
for (size_t i = 0; i < lev.second.data.level->textures.size(); i++) {
auto& tex = lev.second.data.level->textures[i];
for (size_t i = 0; i < lev.second->level->textures.size(); i++) {
auto& tex = lev.second->level->textures[i];
if (tex.load_to_pool) {
texture_pool.unload_texture(PcTextureId::from_combo_id(tex.combo_id),
lev.second.data.textures.at(i));
lev.second->textures.at(i));
}
}
lk.unlock();
for (auto tex : lev.second.data.textures) {
for (auto tex : lev.second->textures) {
if (EXTRA_TEX_DEBUG) {
for (auto& slot : texture_pool.all_textures()) {
if (slot.source) {
@@ -652,7 +681,7 @@ void Loader::update(TexturePool& texture_pool) {
glDeleteTextures(1, &tex);
}
for (auto& tie_geo : lev.second.data.tie_data) {
for (auto& tie_geo : lev.second->tie_data) {
for (auto& tie_tree : tie_geo) {
glDeleteBuffers(1, &tie_tree.vertex_buffer);
if (tie_tree.has_wind) {
@@ -661,13 +690,23 @@ void Loader::update(TexturePool& texture_pool) {
}
}
for (auto& tfrag_geo : lev.second.data.tfrag_vertex_data) {
for (auto& tfrag_geo : lev.second->tfrag_vertex_data) {
for (auto& tfrag_buff : tfrag_geo) {
glDeleteBuffers(1, &tfrag_buff);
}
}
glDeleteBuffers(1, &lev.second.data.collide_vertices);
glDeleteBuffers(1, &lev.second->collide_vertices);
glDeleteBuffers(1, &lev.second->merc_vertices);
glDeleteBuffers(1, &lev.second->merc_indices);
for (auto& model : lev.second->level->merc_data.models) {
auto& mercs = m_all_merc_models.at(model.name);
MercRef ref{&model, lev.second->load_id};
auto it = std::find(mercs.begin(), mercs.end(), ref);
ASSERT_MSG(it != mercs.end(), fmt::format("missing merc: {}\n", model.name));
mercs.erase(it);
}
m_loaded_tfrag3_levels.erase(lev.first);
break;
@@ -680,3 +719,14 @@ void Loader::update(TexturePool& texture_pool) {
fmt::print("Loader::update slow setup: {:.1f}ms\n", loader_timer.getMs());
}
}
std::optional<Loader::MercRef> Loader::get_merc_model(const char* model_name) {
// don't think we need to lock here...
const auto& it = m_all_merc_models.find(model_name);
if (it != m_all_merc_models.end() && !it->second.empty()) {
// it->second.front().parent_level->frames_since_last_used = 0;
return it->second.front();
} else {
return std::nullopt;
}
}
+22 -8
View File
@@ -33,6 +33,10 @@ class Loader {
std::vector<GLuint> shrub_vertex_data;
GLuint collide_vertices;
GLuint merc_vertices;
GLuint merc_indices;
std::unordered_map<std::string, const tfrag3::MercModel*> merc_model_lookup;
// internal load state
bool tie_opengl_created = false;
bool tie_verts_done = false;
@@ -52,19 +56,26 @@ class Loader {
bool shrub_load_done = false;
u32 shrub_next_tree = 0;
u32 shrub_next_vert = 0;
int frames_since_last_used = 0;
};
struct MercRef {
const tfrag3::MercModel* model = nullptr;
u64 load_id = 0;
const LevelData* level = nullptr;
bool operator==(const MercRef& other) const {
return model == other.model && load_id == other.load_id;
}
};
const LevelData* get_tfrag3_level(const std::string& level_name);
std::optional<MercRef> get_merc_model(const char* model_name);
void load_common(TexturePool& tex_pool, const std::string& name);
void set_want_levels(const std::vector<std::string>& levels);
std::vector<LevelData*> get_in_use_levels();
private:
struct Level {
LevelData data;
int frames_since_last_used = 0;
};
void loader_thread();
u64 add_texture(TexturePool& pool, const tfrag3::Texture& tex, bool is_common);
@@ -73,11 +84,12 @@ class Loader {
bool init_tfrag(Timer& timer, LevelData& data);
bool init_shrub(Timer& timer, LevelData& data);
bool init_collide(Timer& timer, LevelData& data);
bool init_merc(Timer& timer, LevelData& data);
// used by game and loader thread
std::unordered_map<std::string, Level> m_initializing_tfrag3_levels;
std::unordered_map<std::string, std::unique_ptr<LevelData>> m_initializing_tfrag3_levels;
tfrag3::Level m_common_level;
LevelData m_common_level;
std::string m_level_to_load;
@@ -89,7 +101,9 @@ class Loader {
uint64_t m_id = 0;
// used only by game thread
std::unordered_map<std::string, Level> m_loaded_tfrag3_levels;
std::unordered_map<std::string, std::unique_ptr<LevelData>> m_loaded_tfrag3_levels;
std::unordered_map<std::string, std::vector<MercRef>> m_all_merc_models;
std::vector<std::string> m_desired_levels;
};
+11 -3
View File
@@ -59,10 +59,12 @@ void MercRenderer::render(DmaFollower& dma,
m_direct.flush_pending(render_state, prof);
}
namespace {
bool tag_is_nothing_next(const DmaFollower& dma) {
return dma.current_tag().kind == DmaTag::Kind::NEXT && dma.current_tag().qwc == 0 &&
dma.current_tag_vif0() == 0 && dma.current_tag_vif1() == 0;
}
} // namespace
void MercRenderer::unpack32(const VifCodeUnpack& up, const u8* data, u32 imm) {
ASSERT(!up.is_unsigned);
@@ -150,13 +152,19 @@ void MercRenderer::handle_merc_chain(DmaFollower& dma,
auto init = dma.read_and_advance();
// skip pc port stuff
if (init.vifcode1().kind == VifCode::Kind::PC_PORT) {
dma.read_and_advance();
init = dma.read_and_advance();
}
ASSERT(init.vifcode0().kind == VifCode::Kind::STROW);
ASSERT(init.size_bytes == 16);
m_vif.row[0] = init.vif1();
memcpy(m_vif.row + 1, init.data, 12);
u32 extra;
memcpy(&extra, init.data + 12, 4);
ASSERT(extra == 0);
// now used in pc renderer.
// u32 extra;
// memcpy(&extra, init.data + 12, 4);
// ASSERT(extra == 0);
DmaTransfer next;
bool setting_up = true;
@@ -16,6 +16,7 @@
#include "game/graphics/opengl_renderer/EyeRenderer.h"
#include "game/graphics/opengl_renderer/ShadowRenderer.h"
#include "game/graphics/opengl_renderer/foreground/Generic2.h"
#include "game/graphics/opengl_renderer/foreground/Merc2.h"
#include "game/graphics/opengl_renderer/ocean/OceanMidAndFar.h"
#include "game/graphics/opengl_renderer/ocean/OceanNear.h"
@@ -68,6 +69,15 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr<TexturePool> texture_pool,
init_bucket_renderers();
}
void OpenGLRenderer::init_merc_renderer(const std::string& name, BucketId id) {
std::vector<std::unique_ptr<BucketRenderer>> merc_renderers;
merc_renderers.push_back(std::make_unique<MercRenderer>(name, id));
merc_renderers.push_back(std::make_unique<Merc2>(name, id));
m_mercs.push_back(
init_bucket_renderer<RenderMux>(name, BucketCategory::MERC, id, std::move(merc_renderers)));
}
/*!
* Construct bucket renderers. We can specify different renderers for different buckets
*/
@@ -106,8 +116,7 @@ void OpenGLRenderer::init_bucket_renderers() {
// 9 : TIE_LEVEL0
init_bucket_renderer<Tie3>("l0-tfrag-tie", BucketCategory::TIE, BucketId::TIE_LEVEL0, 0);
// 10 : MERC_TFRAG_TEX_LEVEL0
init_bucket_renderer<MercRenderer>("l0-tfrag-merc", BucketCategory::MERC,
BucketId::MERC_TFRAG_TEX_LEVEL0);
init_merc_renderer("l0-tfrag-merc", BucketId::MERC_TFRAG_TEX_LEVEL0);
// 11 : GMERC_TFRAG_TEX_LEVEL0
init_bucket_renderer<Generic2>("l0-tfrag-generic", BucketCategory::GENERIC,
BucketId::GENERIC_TFRAG_TEX_LEVEL0, 1500000, 10000, 10000, 800);
@@ -126,8 +135,7 @@ void OpenGLRenderer::init_bucket_renderers() {
// 16 : TIE_LEVEL1
init_bucket_renderer<Tie3>("l1-tfrag-tie", BucketCategory::TIE, BucketId::TIE_LEVEL1, 1);
// 17 : MERC_TFRAG_TEX_LEVEL1
init_bucket_renderer<MercRenderer>("l1-tfrag-merc", BucketCategory::MERC,
BucketId::MERC_TFRAG_TEX_LEVEL1);
init_merc_renderer("l1-tfrag-merc", BucketId::MERC_TFRAG_TEX_LEVEL1);
// 18 : GMERC_TFRAG_TEX_LEVEL1
init_bucket_renderer<Generic2>("l1-tfrag-generic", BucketCategory::GENERIC,
BucketId::GENERIC_TFRAG_TEX_LEVEL1, 1500000, 10000, 10000, 800);
@@ -194,8 +202,7 @@ void OpenGLRenderer::init_bucket_renderers() {
BucketId::TFRAG_ICE_LEVEL1, ice_tfrags, false, 1);
// 44
init_bucket_renderer<MercRenderer>("common-alpha-merc", BucketCategory::MERC,
BucketId::MERC_AFTER_ALPHA);
init_merc_renderer("common-alpha-merc", BucketId::MERC_AFTER_ALPHA);
init_bucket_renderer<Generic2>("common-alpha-generic", BucketCategory::GENERIC,
BucketId::GENERIC_ALPHA); // 46
@@ -206,8 +213,7 @@ void OpenGLRenderer::init_bucket_renderers() {
//-----------------------
init_bucket_renderer<TextureUploadHandler>("l0-pris-tex", BucketCategory::TEX,
BucketId::PRIS_TEX_LEVEL0); // 48
init_bucket_renderer<MercRenderer>("l0-pris-merc", BucketCategory::MERC,
BucketId::MERC_PRIS_LEVEL0); // 49
init_merc_renderer("l0-pris-merc", BucketId::MERC_PRIS_LEVEL0); // 49
init_bucket_renderer<Generic2>("l0-pris-generic", BucketCategory::GENERIC,
BucketId::GENERIC_PRIS_LEVEL0); // 50
@@ -216,16 +222,16 @@ void OpenGLRenderer::init_bucket_renderers() {
//-----------------------
init_bucket_renderer<TextureUploadHandler>("l1-pris-tex", BucketCategory::TEX,
BucketId::PRIS_TEX_LEVEL1); // 51
init_bucket_renderer<MercRenderer>("l1-pris-merc", BucketCategory::MERC,
BucketId::MERC_PRIS_LEVEL1); // 52
init_merc_renderer("l1-pris-merc", BucketId::MERC_PRIS_LEVEL1); // 52
init_bucket_renderer<Generic2>("l1-pris-generic", BucketCategory::GENERIC,
BucketId::GENERIC_PRIS_LEVEL1); // 53
// other renderers may output to the eye renderer
m_render_state.eye_renderer = init_bucket_renderer<EyeRenderer>(
"common-pris-eyes", BucketCategory::OTHER, BucketId::MERC_EYES_AFTER_PRIS); // 54
init_bucket_renderer<MercRenderer>("common-pris-merc", BucketCategory::MERC,
BucketId::MERC_AFTER_PRIS); // 55
// hack: set to merc2 for debugging
init_merc_renderer("common-pris-merc", BucketId::MERC_AFTER_PRIS); // 55
init_bucket_renderer<Generic2>("common-pris-generic", BucketCategory::GENERIC,
BucketId::GENERIC_PRIS); // 56
@@ -234,8 +240,7 @@ void OpenGLRenderer::init_bucket_renderers() {
//-----------------------
init_bucket_renderer<TextureUploadHandler>("l0-water-tex", BucketCategory::TEX,
BucketId::WATER_TEX_LEVEL0); // 57
init_bucket_renderer<MercRenderer>("l0-water-merc", BucketCategory::MERC,
BucketId::MERC_WATER_LEVEL0); // 58
init_merc_renderer("l0-water-merc", BucketId::MERC_WATER_LEVEL0); // 58
init_bucket_renderer<Generic2>("l0-water-generic", BucketCategory::GENERIC,
BucketId::GENERIC_WATER_LEVEL0); // 59
@@ -244,8 +249,7 @@ void OpenGLRenderer::init_bucket_renderers() {
//-----------------------
init_bucket_renderer<TextureUploadHandler>("l1-water-tex", BucketCategory::TEX,
BucketId::WATER_TEX_LEVEL1); // 60
init_bucket_renderer<MercRenderer>("l1-water-merc", BucketCategory::MERC,
BucketId::MERC_WATER_LEVEL1); // 61
init_merc_renderer("l1-water-merc", BucketId::MERC_WATER_LEVEL1); // 61
init_bucket_renderer<Generic2>("l1-water-generic", BucketCategory::GENERIC,
BucketId::GENERIC_WATER_LEVEL1); // 62
@@ -295,6 +299,11 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) {
m_render_state.ee_main_memory = g_ee_main_mem;
m_render_state.offset_of_s7 = offset_of_s7();
// hack to toggle all mercs, todo remove once we finish merc2
for (auto merc : m_mercs) {
merc->set_idx(m_render_state.merc2 ? 1 : 0);
}
{
auto prof = m_profiler.root()->make_scoped_child("frame-setup");
setup_frame(settings.window_width_px, settings.window_height_px, settings.lbox_width_px,
@@ -365,6 +374,7 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) {
void OpenGLRenderer::draw_renderer_selection_window() {
ImGui::Begin("Renderer Debug");
ImGui::Checkbox("Merc2", &m_render_state.merc2);
ImGui::Checkbox("Use old single-draw", &m_render_state.no_multidraw);
ImGui::SliderFloat("Fog Adjust", &m_render_state.fog_intensity, 0, 10);
ImGui::Checkbox("Sky CPU", &m_render_state.use_sky_cpu);
@@ -48,6 +48,8 @@ class OpenGLRenderer {
void draw_renderer_selection_window();
void finish_screenshot(const std::string& output_name, int px, int py, int x, int y);
void init_merc_renderer(const std::string& name, BucketId id);
template <typename T, class... Args>
T* init_bucket_renderer(const std::string& name,
BucketCategory cat,
@@ -67,6 +69,8 @@ class OpenGLRenderer {
std::array<std::unique_ptr<BucketRenderer>, (int)BucketId::MAX_BUCKETS> m_bucket_renderers;
std::array<BucketCategory, (int)BucketId::MAX_BUCKETS> m_bucket_categories;
std::vector<RenderMux*> m_mercs;
std::array<float, (int)BucketCategory::MAX_CATEGORIES> m_category_times;
FullScreenDraw m_blackout_renderer;
CollideMeshRenderer m_collide_renderer;
+1
View File
@@ -84,6 +84,7 @@ ShaderLibrary::ShaderLibrary() {
at(ShaderId::SHRUB) = {"shrub"};
at(ShaderId::SHADOW) = {"shadow"};
at(ShaderId::COLLISION) = {"collision"};
at(ShaderId::MERC2) = {"merc2"};
for (auto& shader : m_shaders) {
ASSERT_MSG(shader.okay(), "Shader compiled");
+1
View File
@@ -41,6 +41,7 @@ enum class ShaderId {
SHADOW = 16,
SHRUB = 17,
COLLISION = 18,
MERC2 = 19,
MAX_SHADERS
};
@@ -31,10 +31,6 @@ TFragment::TFragment(const std::string& name,
}
}
constexpr const char* level_names[] = {"bea", "cit", "dar", "fin", "int", "jub", "jun", "fic",
"lav", "mai", "mis", "ogr", "rob", "rol", "sno", "sub",
"sun", "swa", "tit", "tra", "vi1", "vi2", "vi3"};
void TFragment::render(DmaFollower& dma,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
@@ -150,43 +146,6 @@ void TFragment::render(DmaFollower& dma,
auto tag = dma.current_tag().print();
dma.read_and_advance();
}
if (m_hack_test_many_levels) {
std::vector<tfrag3::TFragmentTreeKind> all_kinds = {
tfrag3::TFragmentTreeKind::NORMAL, tfrag3::TFragmentTreeKind::TRANS,
tfrag3::TFragmentTreeKind::DIRT, tfrag3::TFragmentTreeKind::ICE,
tfrag3::TFragmentTreeKind::LOWRES, tfrag3::TFragmentTreeKind::LOWRES_TRANS};
for (int i = 0; i < HackManyLevels::NUM_LEVELS; i++) {
if (m_many_level_render.level_enables[i]) {
if (!m_many_level_render.tfrag_level_renderers[i]) {
m_many_level_render.tfrag_level_renderers[i] = std::make_unique<Tfrag3>();
}
if (!m_many_level_render.tie_level_renderers[i]) {
m_many_level_render.tie_level_renderers[i] = std::make_unique<Tie3>("tie", m_my_id, 0);
}
m_many_level_render.tfrag_level_renderers[i]->setup_for_level(all_kinds, level_names[i],
render_state);
m_many_level_render.tie_level_renderers[i]->setup_for_level(level_names[i], render_state);
TfragRenderSettings settings;
settings.hvdf_offset = m_tfrag_data.hvdf_offset;
settings.fog = m_tfrag_data.fog;
memcpy(settings.math_camera.data(),
&m_buffered_data[0].pad[TFragDataMem::TFragMatrix0 * 16], 64);
settings.tree_idx = 0;
for (int j = 0; j < 8; j++) {
settings.time_of_day_weights[j] = m_time_of_days[j];
}
auto t3prof = prof.make_scoped_child(level_names[i]);
m_many_level_render.tfrag_level_renderers[i]->debug_render_all_trees_nolores(
0, settings, render_state, t3prof);
// always renders max lod
m_many_level_render.tie_level_renderers[i]->render_all_trees(0, settings, render_state,
t3prof);
}
}
}
}
void TFragment::draw_debug_window() {
@@ -197,13 +156,6 @@ void TFragment::draw_debug_window() {
}
}
ImGui::Checkbox("Hack Test Many (danger)", &m_hack_test_many_levels);
if (m_hack_test_many_levels) {
for (int i = 0; i < HackManyLevels::NUM_LEVELS; i++) {
ImGui::Checkbox(level_names[i], &m_many_level_render.level_enables[i]);
}
}
m_tfrag3.draw_debug_window();
}
@@ -47,7 +47,6 @@ class TFragment : public BucketRenderer {
void handle_initialization(DmaFollower& dma);
bool m_child_mode = false;
bool m_hack_test_many_levels = false;
bool m_override_time_of_day = false;
float m_time_of_days[8] = {1, 0, 0, 0, 0, 0, 0, 0};
@@ -80,11 +79,4 @@ class TFragment : public BucketRenderer {
Tfrag3 m_tfrag3;
std::vector<tfrag3::TFragmentTreeKind> m_tree_kinds;
int m_level_id;
struct HackManyLevels {
static constexpr int NUM_LEVELS = 23;
std::unique_ptr<Tfrag3> tfrag_level_renderers[NUM_LEVELS];
std::unique_ptr<Tie3> tie_level_renderers[NUM_LEVELS];
bool level_enables[NUM_LEVELS] = {0};
} m_many_level_render;
};
@@ -317,33 +317,9 @@ void Tfrag3::render_matching_trees(int geom,
}
}
void Tfrag3::debug_render_all_trees_nolores(int geom,
const TfragRenderSettings& settings,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
TfragRenderSettings settings_copy = settings;
for (size_t i = 0; i < m_cached_trees.size(); i++) {
if (m_cached_trees[geom][i].kind != tfrag3::TFragmentTreeKind::INVALID &&
m_cached_trees[geom][i].kind != tfrag3::TFragmentTreeKind::LOWRES_TRANS &&
m_cached_trees[geom][i].kind != tfrag3::TFragmentTreeKind::LOWRES) {
settings_copy.tree_idx = i;
render_tree(geom, settings_copy, render_state, prof);
}
}
// for (size_t i = 0; i < m_cached_trees.size(); i++) {
// if (m_cached_trees[i].kind != tfrag3::TFragmentTreeKind::INVALID &&
// m_cached_trees[i].kind != tfrag3::TFragmentTreeKind::LOWRES_TRANS &&
// m_cached_trees[i].kind != tfrag3::TFragmentTreeKind::LOWRES) {
// settings_copy.tree_idx = i;
// render_tree_cull_debug(settings_copy, render_state, prof);
// }
// }
}
void Tfrag3::draw_debug_window() {
for (int i = 0; i < (int)m_cached_trees.size(); i++) {
auto& tree = m_cached_trees[lod()][i];
for (int i = 0; i < (int)m_cached_trees.at(lod()).size(); i++) {
auto& tree = m_cached_trees.at(lod()).at(i);
if (tree.kind == tfrag3::TFragmentTreeKind::INVALID) {
continue;
}
@@ -12,11 +12,6 @@ class Tfrag3 {
Tfrag3();
~Tfrag3();
void debug_render_all_trees_nolores(int geom,
const TfragRenderSettings& settings,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
void render_all_trees(int geom,
const TfragRenderSettings& settings,
SharedRenderState* render_state,
@@ -57,7 +52,7 @@ class Tfrag3 {
static constexpr int GEOM_MAX = 3;
struct TreeCache {
tfrag3::TFragmentTreeKind kind;
tfrag3::TFragmentTreeKind kind = tfrag3::TFragmentTreeKind::INVALID;
GLuint vertex_buffer = -1;
GLuint index_buffer = -1;
GLuint single_draw_index_buffer = -1;
@@ -14,7 +14,6 @@ class Generic2 : public BucketRenderer {
void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override;
void draw_debug_window() override;
void init_shaders(ShaderLibrary& shaders) override;
// void init_shaders(ShaderLibrary& shaders) override;
struct Vertex {
math::Vector<float, 3> xyz;
@@ -0,0 +1,604 @@
#include "Merc2.h"
#include "third-party/imgui/imgui.h"
#include "game/graphics/opengl_renderer/background/background_common.h"
Merc2::Merc2(const std::string& name, BucketId my_id) : BucketRenderer(name, my_id) {
glGenVertexArrays(1, &m_vao);
glBindVertexArray(m_vao);
glGenBuffers(1, &m_bones_buffer);
glBindBuffer(GL_UNIFORM_BUFFER, m_bones_buffer);
glBufferData(GL_UNIFORM_BUFFER, MAX_SHADER_BONES * sizeof(MercMat), nullptr, GL_DYNAMIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
for (int i = 0; i < MAX_LEVELS; i++) {
auto& draws = m_level_draw_buckets.emplace_back();
draws.draws.resize(MAX_DRAWS_PER_LEVEL);
}
}
/*!
* Handle the merc renderer switching to a different model.
*/
void Merc2::init_pc_model(const DmaTransfer& setup, SharedRenderState* render_state) {
// determine the name. We've packed this in a separate PC-port specific packet.
char name[128];
strcpy(name, (const char*)setup.data);
// get the model from the loader
m_current_model = render_state->loader->get_merc_model(name);
// update stats
m_stats.num_models++;
if (m_current_model) {
for (const auto& effect : m_current_model->model->effects) {
m_stats.num_effects++;
m_stats.num_predicted_draws += effect.draws.size();
for (const auto& draw : effect.draws) {
m_stats.num_predicted_tris += draw.num_triangles;
}
}
}
}
/*!
* Once-per-frame initialization
*/
void Merc2::init_for_frame(SharedRenderState* render_state) {
// reset state
m_current_model = std::nullopt;
m_stats = {};
// activate the merc shader used for all draws
render_state->shaders[ShaderId::MERC2].activate();
// set uniforms that we know from render_state
glUniform4f(m_uniforms.fog_color, render_state->fog_color[0], render_state->fog_color[1],
render_state->fog_color[2], render_state->fog_intensity);
}
void Merc2::draw_debug_window() {
ImGui::Text("Models : %d", m_stats.num_models);
ImGui::Text("Effects : %d", m_stats.num_effects);
ImGui::Text("Draws (p): %d", m_stats.num_predicted_draws);
ImGui::Text("Tris (p): %d", m_stats.num_predicted_tris);
ImGui::Text("Bones : %d", m_stats.num_bones_uploaded);
ImGui::Text("Lights : %d", m_stats.num_lights);
ImGui::Text("Dflush : %d", m_stats.num_draw_flush);
}
void Merc2::init_shaders(ShaderLibrary& shaders) {
shaders[ShaderId::MERC2].activate();
m_uniforms.light_direction[0] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_dir0");
m_uniforms.light_direction[1] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_dir1");
m_uniforms.light_direction[2] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_dir2");
m_uniforms.light_color[0] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_col0");
m_uniforms.light_color[1] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_col1");
m_uniforms.light_color[2] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_col2");
m_uniforms.light_ambient = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_ambient");
m_uniforms.hvdf_offset = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "hvdf_offset");
m_uniforms.perspective[0] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective0");
m_uniforms.perspective[1] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective1");
m_uniforms.perspective[2] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective2");
m_uniforms.perspective[3] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective3");
m_uniforms.fog = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "fog_constants");
m_uniforms.decal = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "decal_enable");
m_uniforms.fog_color = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "fog_color");
m_uniforms.perspective_matrix =
glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective_matrix");
m_uniforms.ignore_alpha = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "ignore_alpha");
}
/*!
* Main merc2 rendering.
*/
void Merc2::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) {
// skip if disabled
if (!m_enabled) {
while (dma.current_tag_offset() != render_state->next_bucket) {
dma.read_and_advance();
}
return;
}
init_for_frame(render_state);
// iterate through the dma chain, filling buckets
handle_all_dma(dma, render_state, prof);
// flush model data to buckets
flush_pending_model(render_state, prof);
// flush buckets to draws
flush_draw_buckets(render_state, prof);
}
/*!
* Queue up some bones to be included in the bone buffer.
* Returns the index of the first bone.
*/
u32 Merc2::alloc_bones(int count, float scale) {
ASSERT(count + m_next_free_bone <= MAX_SHADER_BONES);
u32 first_bone = m_next_free_bone;
// model should have under 128 bones.
ASSERT(count <= MAX_SKEL_BONES);
// iterate over each bone we need
for (int i = 0; i < count; i++) {
auto& skel_mat = m_skel_matrix_buffer[i];
auto& shader_mat = m_shader_matrix_buffer[m_next_free_bone++];
// scale the transformation matrix (todo: can we move this to the extraction)
// and copy to the large bone buffer.
for (int j = 0; j < 3; j++) {
shader_mat.tmat[j] = skel_mat.tmat[j] * scale;
}
shader_mat.tmat[3] = skel_mat.tmat[3];
for (int j = 0; j < 3; j++) {
shader_mat.nmat[j] = skel_mat.nmat[j];
}
// we could include the effect of the perspective matrix here.
// for (int j = 0; j < 3; j++) {
// tbone_buffer[i][j] = vf15.elementwise_multiply(bone_mat[j]);
// tbone_buffer[i][j].w() += p.w() * bone_mat[j].z();
// tbone_buffer[i][j] *= scale;
// }
//
// tbone_buffer[i][3] = vf15.elementwise_multiply(bone_mat[3]) +
// m_low_memory.perspective[3]; tbone_buffer[i][3].w() += p.w() * bone_mat[3].z();
}
return first_bone;
}
u32 Merc2::alloc_lights(const VuLights& lights) {
ASSERT(m_next_free_light < MAX_LIGHTS);
m_stats.num_lights++;
u32 light_idx = m_next_free_light;
m_lights_buffer[m_next_free_light++] = lights;
static_assert(sizeof(VuLights) == 7 * 16);
return light_idx;
}
/*!
* Store light values
*/
void Merc2::set_lights(const DmaTransfer& dma) {
memcpy(&m_current_lights, dma.data, sizeof(VuLights));
}
void Merc2::handle_matrix_dma(const DmaTransfer& dma) {
int slot = dma.vif0() & 0xff;
ASSERT(slot < MAX_SKEL_BONES);
memcpy(&m_skel_matrix_buffer[slot], dma.data, sizeof(MercMat));
}
/*!
* Main MERC2 function to handle DMA
*/
void Merc2::handle_all_dma(DmaFollower& dma,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
// process the first tag. this is just jumping to the merc-specific dma.
auto data0 = dma.read_and_advance();
ASSERT(data0.vif1() == 0);
ASSERT(data0.vif0() == 0);
ASSERT(data0.size_bytes == 0);
if (dma.current_tag().kind == DmaTag::Kind::CALL) {
// renderer didn't run, let's just get out of here.
for (int i = 0; i < 4; i++) {
dma.read_and_advance();
}
ASSERT(dma.current_tag_offset() == render_state->next_bucket);
return;
}
ASSERT(data0.size_bytes == 0);
ASSERT(data0.vif0() == 0);
ASSERT(data0.vif1() == 0);
// if we reach here, there's stuff to draw
// this handles merc-specific setup DMA
handle_setup_dma(dma);
// handle each merc transfer
while (dma.current_tag_offset() != render_state->next_bucket) {
handle_merc_chain(dma, render_state, prof);
}
ASSERT(dma.current_tag_offset() == render_state->next_bucket);
}
namespace {
void set_uniform(GLuint uniform, const math::Vector3f& val) {
glUniform3f(uniform, val.x(), val.y(), val.z());
}
void set_uniform(GLuint uniform, const math::Vector4f& val) {
glUniform4f(uniform, val.x(), val.y(), val.z(), val.w());
}
} // namespace
void Merc2::handle_setup_dma(DmaFollower& dma) {
auto first = dma.read_and_advance();
// 10 quadword setup packet
ASSERT(first.size_bytes == 10 * 16);
// m_stats.str += fmt::format("Setup 0: {} {} {}", first.size_bytes / 16,
// first.vifcode0().print(), first.vifcode1().print());
// transferred vifcodes
{
auto vif0 = first.vifcode0();
auto vif1 = first.vifcode1();
// STCYCL 4, 4
ASSERT(vif0.kind == VifCode::Kind::STCYCL);
auto vif0_st = VifCodeStcycl(vif0);
ASSERT(vif0_st.cl == 4 && vif0_st.wl == 4);
// STMOD
ASSERT(vif1.kind == VifCode::Kind::STMOD);
ASSERT(vif1.immediate == 0);
}
// 1 qw with 4 vifcodes.
u32 vifcode_data[4];
memcpy(vifcode_data, first.data, 16);
{
auto vif0 = VifCode(vifcode_data[0]);
ASSERT(vif0.kind == VifCode::Kind::BASE);
ASSERT(vif0.immediate == MercDataMemory::BUFFER_BASE);
auto vif1 = VifCode(vifcode_data[1]);
ASSERT(vif1.kind == VifCode::Kind::OFFSET);
ASSERT((s16)vif1.immediate == MercDataMemory::BUFFER_OFFSET);
auto vif2 = VifCode(vifcode_data[2]);
ASSERT(vif2.kind == VifCode::Kind::NOP);
auto vif3 = VifCode(vifcode_data[3]);
ASSERT(vif3.kind == VifCode::Kind::UNPACK_V4_32);
VifCodeUnpack up(vif3);
ASSERT(up.addr_qw == MercDataMemory::LOW_MEMORY);
ASSERT(!up.use_tops_flag);
ASSERT(vif3.num == 8);
}
// 8 qw's of low memory data
memcpy(&m_low_memory, first.data + 16, sizeof(LowMemory));
set_uniform(m_uniforms.hvdf_offset, m_low_memory.hvdf_offset);
set_uniform(m_uniforms.fog, m_low_memory.fog);
for (int i = 0; i < 4; i++) {
set_uniform(m_uniforms.perspective[i], m_low_memory.perspective[i]);
}
// todo rm.
glUniformMatrix4fv(m_uniforms.perspective_matrix, 1, GL_FALSE, &m_low_memory.perspective[0].x());
// 1 qw with another 4 vifcodes.
u32 vifcode_final_data[4];
memcpy(vifcode_final_data, first.data + 16 + sizeof(LowMemory), 16);
{
ASSERT(VifCode(vifcode_final_data[0]).kind == VifCode::Kind::FLUSHE);
ASSERT(vifcode_final_data[1] == 0);
ASSERT(vifcode_final_data[2] == 0);
VifCode mscal(vifcode_final_data[3]);
ASSERT(mscal.kind == VifCode::Kind::MSCAL);
ASSERT(mscal.immediate == 0);
}
// TODO: process low memory initialization
auto second = dma.read_and_advance();
ASSERT(second.size_bytes == 32); // setting up test register.
auto nothing = dma.read_and_advance();
ASSERT(nothing.size_bytes == 0);
ASSERT(nothing.vif0() == 0);
ASSERT(nothing.vif1() == 0);
}
namespace {
bool tag_is_nothing_next(const DmaFollower& dma) {
return dma.current_tag().kind == DmaTag::Kind::NEXT && dma.current_tag().qwc == 0 &&
dma.current_tag_vif0() == 0 && dma.current_tag_vif1() == 0;
}
bool tag_is_nothing_cnt(const DmaFollower& dma) {
return dma.current_tag().kind == DmaTag::Kind::CNT && dma.current_tag().qwc == 0 &&
dma.current_tag_vif0() == 0 && dma.current_tag_vif1() == 0;
}
} // namespace
void Merc2::handle_merc_chain(DmaFollower& dma,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
while (tag_is_nothing_next(dma)) {
auto nothing = dma.read_and_advance();
ASSERT(nothing.size_bytes == 0);
}
if (dma.current_tag().kind == DmaTag::Kind::CALL) {
for (int i = 0; i < 4; i++) {
dma.read_and_advance();
}
return;
}
auto init = dma.read_and_advance();
if (init.vifcode1().kind == VifCode::Kind::PC_PORT) {
// we got a PC PORT packet. this contains some extra data to set up the model
flush_pending_model(render_state, prof);
init_pc_model(init, render_state);
ASSERT(tag_is_nothing_cnt(dma));
init = dma.read_and_advance(); // dummy tag in pc port
init = dma.read_and_advance();
}
// row stuff.
ASSERT(init.vifcode0().kind == VifCode::Kind::STROW);
ASSERT(init.size_bytes == 16);
// m_vif.row[0] = init.vif1();
// memcpy(m_vif.row + 1, init.data, 12);
u32 extra;
memcpy(&extra, init.data + 12, 4);
// ASSERT(extra == 0);
m_current_effect_enable_bits = extra;
m_current_ignore_alpha_bits = extra >> 16;
DmaTransfer next;
bool setting_up = true;
u32 mscal_addr = -1;
while (setting_up) {
next = dma.read_and_advance();
// fmt::print("next: {}", dma.current_tag().print());
u32 offset_in_data = 0;
// fmt::print("START {} : {} {}\n", next.size_bytes, next.vifcode0().print(),
// next.vifcode1().print());
auto vif0 = next.vifcode0();
switch (vif0.kind) {
case VifCode::Kind::NOP:
case VifCode::Kind::FLUSHE:
break;
case VifCode::Kind::STMOD:
ASSERT(vif0.immediate == 0 || vif0.immediate == 1);
// m_vif.stmod = vif0.immediate;
break;
default:
ASSERT(false);
}
auto vif1 = next.vifcode1();
switch (vif1.kind) {
case VifCode::Kind::UNPACK_V4_8: {
VifCodeUnpack up(vif1);
offset_in_data += 4 * vif1.num;
} break;
case VifCode::Kind::UNPACK_V4_32: {
VifCodeUnpack up(vif1);
if (up.addr_qw == 132 && vif1.num == 8) {
set_lights(next);
} else if (vif1.num == 7) {
handle_matrix_dma(next);
}
offset_in_data += 16 * vif1.num;
} break;
case VifCode::Kind::MSCAL:
// fmt::print("cal\n");
mscal_addr = vif1.immediate;
ASSERT(next.size_bytes == 0);
setting_up = false;
break;
default:
ASSERT(false);
}
ASSERT(offset_in_data <= next.size_bytes);
if (offset_in_data < next.size_bytes) {
ASSERT((offset_in_data % 4) == 0);
u32 leftover = next.size_bytes - offset_in_data;
if (leftover < 16) {
for (u32 i = 0; i < leftover; i++) {
ASSERT(next.data[offset_in_data + i] == 0);
}
} else {
ASSERT(false);
}
}
}
}
/*!
* Flush a model to draw buckets
*/
void Merc2::flush_pending_model(SharedRenderState* render_state, ScopedProfilerNode& prof) {
if (!m_current_model) {
return;
}
const Loader::LevelData* lev = m_current_model->level;
const tfrag3::MercModel* model = m_current_model->model;
int bone_count = (model->max_bones + 31) & (~31); // todo
// int bone_count = 128;
if (m_next_free_light >= MAX_LIGHTS) {
fmt::print("MERC2 out of lights, consider increasing MAX_LIGHTS\n");
flush_draw_buckets(render_state, prof);
}
if (m_next_free_bone + bone_count > MAX_SHADER_BONES) {
fmt::print("MERC2 out of bones, consider increasing MAX_SHADER_BONES\n");
flush_draw_buckets(render_state, prof);
}
// find a level bucket
LevelDrawBucket* lev_bucket = nullptr;
for (u32 i = 0; i < m_next_free_level_bucket; i++) {
if (m_level_draw_buckets[i].level == lev) {
lev_bucket = &m_level_draw_buckets[i];
break;
}
}
if (!lev_bucket) {
// no existing bucket
if (m_next_free_level_bucket >= m_level_draw_buckets.size()) {
// out of room, flush
fmt::print("MERC2 out of levels, consider increasing MAX_LEVELS\n");
flush_draw_buckets(render_state, prof);
// and retry the whole thing.
flush_pending_model(render_state, prof);
return;
}
// alloc a new one
lev_bucket = &m_level_draw_buckets[m_next_free_level_bucket++];
lev_bucket->reset();
lev_bucket->level = lev;
}
if (lev_bucket->next_free_draw + model->max_draws >= lev_bucket->draws.size()) {
// out of room, flush
fmt::print("MERC2 out of draws, consider increasing MAX_DRAWS_PER_LEVEL\n");
flush_draw_buckets(render_state, prof);
// and retry the whole thing.
flush_pending_model(render_state, prof);
return;
}
u32 first_bone = alloc_bones(bone_count, model->scale_xyz);
// allocate lights
u32 lights = alloc_lights(m_current_lights);
//
for (size_t ei = 0; ei < model->effects.size(); ei++) {
if (!(m_current_effect_enable_bits & (1 << ei))) {
continue;
}
u8 ignore_alpha = (m_current_ignore_alpha_bits & (1 << ei));
auto& effect = model->effects[ei];
for (auto& mdraw : effect.draws) {
Draw* draw = &lev_bucket->draws[lev_bucket->next_free_draw++];
draw->first_index = mdraw.first_index;
draw->index_count = mdraw.index_count;
draw->mode = mdraw.mode;
draw->texture = mdraw.tree_tex_id;
draw->first_bone = first_bone;
draw->light_idx = lights;
draw->num_triangles = mdraw.num_triangles;
draw->ignore_alpha = ignore_alpha;
}
}
m_current_model = std::nullopt;
}
void Merc2::flush_draw_buckets(SharedRenderState* /*render_state*/, ScopedProfilerNode& prof) {
m_stats.num_draw_flush++;
for (u32 li = 0; li < m_next_free_level_bucket; li++) {
const auto& lev_bucket = m_level_draw_buckets[li];
const auto* lev = lev_bucket.level;
glBindVertexArray(m_vao);
glBindBuffer(GL_ARRAY_BUFFER, lev->merc_vertices);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lev->merc_indices);
glEnable(GL_PRIMITIVE_RESTART);
glPrimitiveRestartIndex(UINT32_MAX);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glEnableVertexAttribArray(4);
glEnableVertexAttribArray(5);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_GEQUAL);
glVertexAttribPointer(0, // location 0 in the shader
3, // 3 values per vert
GL_FLOAT, // floats
GL_FALSE, // normalized
sizeof(tfrag3::MercVertex), // stride
(void*)offsetof(tfrag3::MercVertex, pos) // offset (0)
);
glVertexAttribPointer(1, // location 1 in the
3, // 3 values per vert
GL_FLOAT, // floats
GL_FALSE, // normalized
sizeof(tfrag3::MercVertex), // stride
(void*)offsetof(tfrag3::MercVertex, normal[0]) // offset (0)
);
glVertexAttribPointer(2, // location 1 in the
3, // 3 values per vert
GL_FLOAT, // floats
GL_FALSE, // normalized
sizeof(tfrag3::MercVertex), // stride
(void*)offsetof(tfrag3::MercVertex, weights[0]) // offset (0)
);
glVertexAttribPointer(3, // location 1 in the shader
2, // 3 values per vert
GL_FLOAT, // floats
GL_FALSE, // normalized
sizeof(tfrag3::MercVertex), // stride
(void*)offsetof(tfrag3::MercVertex, st[0]) // offset (0)
);
glVertexAttribPointer(4, // location 1 in the shader
3, // 3 values per vert
GL_UNSIGNED_BYTE, // floats
GL_TRUE, // normalized
sizeof(tfrag3::MercVertex), // stride
(void*)offsetof(tfrag3::MercVertex, rgba[0]) // offset (0)
);
glVertexAttribIPointer(5, // location 0 in the
3, // 3 floats per vert
GL_UNSIGNED_BYTE, // u8's
sizeof(tfrag3::MercVertex), //
(void*)offsetof(tfrag3::MercVertex, mats[0]) // offset in array
);
int last_tex = -1;
int last_light = -1;
m_stats.num_bones_uploaded += m_next_free_bone;
glBindBuffer(GL_UNIFORM_BUFFER, m_bones_buffer);
glBufferSubData(GL_UNIFORM_BUFFER, 0, m_next_free_bone * sizeof(MercMat),
m_shader_matrix_buffer);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
for (u32 di = 0; di < lev_bucket.next_free_draw; di++) {
auto& draw = lev_bucket.draws[di];
glUniform1i(m_uniforms.ignore_alpha, draw.ignore_alpha);
if ((int)draw.texture != last_tex) {
glBindTexture(GL_TEXTURE_2D, lev->textures.at(draw.texture));
last_tex = draw.texture;
}
if ((int)draw.light_idx != last_light) {
set_uniform(m_uniforms.light_direction[0], m_lights_buffer[draw.light_idx].direction0);
set_uniform(m_uniforms.light_direction[1], m_lights_buffer[draw.light_idx].direction1);
set_uniform(m_uniforms.light_direction[2], m_lights_buffer[draw.light_idx].direction2);
set_uniform(m_uniforms.light_color[0], m_lights_buffer[draw.light_idx].color0);
set_uniform(m_uniforms.light_color[1], m_lights_buffer[draw.light_idx].color1);
set_uniform(m_uniforms.light_color[2], m_lights_buffer[draw.light_idx].color2);
set_uniform(m_uniforms.light_ambient, m_lights_buffer[draw.light_idx].ambient);
last_light = draw.light_idx;
}
setup_opengl_from_draw_mode(draw.mode, GL_TEXTURE0, true);
glUniform1i(m_uniforms.decal, draw.mode.get_decal());
prof.add_draw_call();
prof.add_tri(draw.num_triangles);
glBindBufferRange(GL_UNIFORM_BUFFER, 1, m_bones_buffer, sizeof(MercMat) * draw.first_bone,
128 * sizeof(MercMat));
glDrawElements(GL_TRIANGLE_STRIP, draw.index_count, GL_UNSIGNED_INT,
(void*)(sizeof(u32) * draw.first_index));
}
}
m_next_free_light = 0;
m_next_free_bone = 0;
m_next_free_level_bucket = 0;
}
@@ -0,0 +1,143 @@
#pragma once
#include "game/graphics/opengl_renderer/BucketRenderer.h"
class Merc2 : public BucketRenderer {
public:
Merc2(const std::string& name, BucketId my_id);
void draw_debug_window() override;
void init_shaders(ShaderLibrary& shaders) override;
void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override;
void handle_merc_chain(DmaFollower& dma,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
private:
enum MercDataMemory {
LOW_MEMORY = 0,
BUFFER_BASE = 442,
// this negative offset is what broke jak graphics in Dobiestation for a long time.
BUFFER_OFFSET = -442
};
struct LowMemory {
u8 tri_strip_tag[16];
u8 ad_gif_tag[16];
math::Vector4f hvdf_offset;
math::Vector4f perspective[4];
math::Vector4f fog;
} m_low_memory;
static_assert(sizeof(LowMemory) == 0x80);
struct VuLights {
math::Vector3f direction0;
u32 w0;
math::Vector3f direction1;
u32 w1;
math::Vector3f direction2;
u32 w2;
math::Vector3f color0;
u32 w3;
math::Vector3f color1;
u32 w4;
math::Vector3f color2;
u32 w5;
math::Vector3f ambient;
u32 w6;
};
void init_for_frame(SharedRenderState* render_state);
void init_pc_model(const DmaTransfer& setup, SharedRenderState* render_state);
void handle_all_dma(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof);
void handle_setup_dma(DmaFollower& dma);
u32 alloc_lights(const VuLights& lights);
void set_lights(const DmaTransfer& dma);
void handle_matrix_dma(const DmaTransfer& dma);
void flush_pending_model(SharedRenderState* render_state, ScopedProfilerNode& prof);
u32 alloc_bones(int count, float scale);
std::optional<Loader::MercRef> m_current_model = std::nullopt;
u16 m_current_effect_enable_bits = 0;
u16 m_current_ignore_alpha_bits = 0;
struct MercMat {
math::Vector4f tmat[4];
math::Vector4f nmat[3];
};
static constexpr int MAX_SKEL_BONES = 128;
static constexpr int MAX_SHADER_BONES = 8192;
static constexpr int MAX_LEVELS = 3;
static constexpr int MAX_DRAWS_PER_LEVEL = 1024;
MercMat m_shader_matrix_buffer[MAX_SHADER_BONES];
MercMat m_skel_matrix_buffer[MAX_SKEL_BONES];
struct {
GLuint light_direction[3];
GLuint light_color[3];
GLuint light_ambient;
GLuint hvdf_offset;
GLuint perspective[4];
GLuint fog;
GLuint tbone;
GLuint nbone;
GLuint fog_color;
GLuint perspective_matrix;
GLuint ignore_alpha;
GLuint decal;
} m_uniforms;
GLuint m_vao;
GLuint m_bones_buffer;
struct Stats {
int num_models = 0;
int num_chains = 0;
int num_effects = 0;
int num_predicted_draws = 0;
int num_predicted_tris = 0;
int num_bones_uploaded = 0;
int num_lights = 0;
int num_draw_flush = 0;
} m_stats;
struct Draw {
u32 first_index;
u32 index_count;
DrawMode mode;
u32 texture;
u32 num_triangles;
u16 first_bone;
u16 light_idx;
u8 ignore_alpha;
};
struct LevelDrawBucket {
const Loader::LevelData* level = nullptr;
std::vector<Draw> draws;
u32 next_free_draw = 0;
void reset() {
level = nullptr;
next_free_draw = 0;
}
};
static constexpr int MAX_LIGHTS = 256;
VuLights m_lights_buffer[MAX_LIGHTS];
u32 m_next_free_light = 0;
VuLights m_current_lights;
std::vector<LevelDrawBucket> m_level_draw_buckets;
u32 m_next_free_level_bucket = 0;
u32 m_next_free_bone = 0;
void flush_draw_buckets(SharedRenderState* render_state, ScopedProfilerNode& prof);
};
@@ -0,0 +1,34 @@
#version 430 core
out vec4 color;
in vec3 vtx_color;
in vec2 vtx_st;
in float fog;
uniform sampler2D tex_T0;
uniform vec4 fog_color;
uniform int ignore_alpha;
uniform int decal_enable;
void main() {
vec4 T0 = texture(tex_T0, vtx_st);
if (decal_enable == 0) {
color.xyz = vtx_color * T0.xyz;
} else {
color.xyz = T0.xyz * 0.5;
}
color.w = T0.w;
color *= 2;
if (ignore_alpha == 0 && color.w < 0.128) {
discard;
}
color.xyz = mix(color.xyz, fog_color.xyz / 255., clamp(fog, 0, 1));
}
@@ -0,0 +1,115 @@
#version 430 core
// merc vertex definition
layout (location = 0) in vec3 position_in;
layout (location = 1) in vec3 normal_in;
layout (location = 2) in vec3 weights_in;
layout (location = 3) in vec2 st_in;
layout (location = 4) in vec3 rgba;
layout (location = 5) in uvec3 mats;
// light control
uniform vec3 light_dir0;
uniform vec3 light_dir1;
uniform vec3 light_dir2;
uniform vec3 light_col0;
uniform vec3 light_col1;
uniform vec3 light_col2;
uniform vec3 light_ambient;
// camera control
uniform vec4 hvdf_offset;
uniform vec4 perspective0;
uniform vec4 perspective1;
uniform vec4 perspective2;
uniform vec4 perspective3;
uniform vec4 fog_constants;
uniform mat4 perspective_matrix;
// output
out vec3 vtx_color;
out vec2 vtx_st;
out float fog;
struct MercMatrixData {
mat4 X;
mat3 R;
};
layout (std140, binding = 1) uniform ub_bones {
MercMatrixData bones[128];
};
/*
The inputs are in registers 8, 10, 12, 25, and the outputs are 9, 11, 13, 26. The output is written over the input.
```
mula.xyzw ACC, vf15, vf08
maddz.xyzw vf09, vf16, vf08
mula.xyzw ACC, vf15, vf10
maddz.xyzw vf11, vf16, vf10
mula.xyzw ACC, vf15, vf12
maddz.xyzw vf13, vf16, vf12
addax.xyzw vf20, vf00
madda.xyzw ACC, vf27, vf25
maddz.xyzw vf26, vf28, vf25
```
*/
void main() {
// vec4 transformed = -perspective3.xyzw;
// transformed += -perspective0 * position_in.x;
// transformed += -perspective1 * position_in.y;
// transformed += -perspective2 * position_in.z;
// vec4 transformed = -hmat3.xyzw;
// transformed += -hmat0 * position_in.x;
// transformed += -hmat1 * position_in.y;
// transformed += -hmat2 * position_in.z;
vec4 p = vec4(position_in, 1);
vec4 vtx_pos = -bones[mats[0]].X * p * weights_in[0]
-bones[mats[1]].X * p * weights_in[1]
-bones[mats[2]].X * p * weights_in[2];
vec4 transformed = perspective_matrix * vtx_pos;
vec3 rotated_nrm = bones[mats[0]].R * normal_in * weights_in[0]
+ bones[mats[1]].R * normal_in * weights_in[1]
+ bones[mats[2]].R * normal_in * weights_in[2];
rotated_nrm = normalize(rotated_nrm);
vec3 light_intensity = light_dir0 * rotated_nrm.x + light_dir1 * rotated_nrm.y + light_dir2 * rotated_nrm.z;
light_intensity = max(light_intensity, vec3(0, 0, 0));
vec3 light_color = light_ambient
+ light_intensity.x * light_col0
+ light_intensity.y * light_col1
+ light_intensity.z * light_col2;
float Q = fog_constants.x / transformed[3];
float fog1 = -transformed.w + hvdf_offset.w;
float fog2 = min(fog1, fog_constants.z);
float fog3 = max(fog2, fog_constants.y);
fog = 1 - (fog3/256);
transformed.xyz *= Q;
transformed.xyz += hvdf_offset.xyz;
transformed.xy -= (2048.);
transformed.z /= (8388608);
transformed.z -= 1;
transformed.x /= (256);
transformed.y /= -(128);
transformed.xyz *= transformed.w;
gl_Position = transformed;
gl_Position.y *= 512.0/448.0;
vtx_color = rgba * light_color;
vtx_st = st_in;
}
+2
View File
@@ -9,6 +9,7 @@
#include <cstring>
#include <cstdio>
#include <common/versions.h>
#include "common/log/log.h"
#include "klink.h"
#include "fileio.h"
#include "kscheme.h"
@@ -778,6 +779,7 @@ void link_control::finish(bool jump_from_c_to_goal) {
*EnableMethodSet = *EnableMethodSet + m_keep_debug;
ObjectFileHeader* ofh = m_link_block_ptr.cast<ObjectFileHeader>().c();
lg::info("link finish: {}", m_object_name);
if (ofh->object_file_version == 3) {
// todo check function type of entry
+67
View File
@@ -700,6 +700,51 @@ struct Cache {
void* fake_scratchpad_data;
} cache;
/*
(deftype merc-effect-bucket-info (structure)
((color-fade rgba :offset-assert 0)
(use-mercneric uint8 :offset-assert 4)
(ignore-alpha uint8 :offset-assert 5)
(pad0 uint8 :offset-assert 6)
(pad1 uint8 :offset-assert 7)
)
:pack-me
:method-count-assert 9
:size-assert #x8
:flag-assert #x900000008
)
;; information for everything being submitted.
(deftype merc-bucket-info (structure)
((light vu-lights :inline :offset-assert 0)
(needs-clip int32 :offset-assert 112)
(need-mercprime-if-merc int32 :offset-assert 116)
(must-use-mercneric-for-clip int32 :offset-assert 120)
(effect merc-effect-bucket-info 16 :inline :offset-assert 124)
)
:method-count-assert 9
:size-assert #xfc
:flag-assert #x9000000fc
)
*/
struct MercEffectBucketInfo {
u8 color_fade[4];
u8 use_mercneric;
u8 ignore_alpha;
u8 pad0;
u8 pad1;
};
struct MercBucketInfo {
u8 lights[0x70];
u32 needs_clip;
u32 mercprime;
u32 mercneric;
MercEffectBucketInfo effects[16];
};
static_assert(sizeof(MercBucketInfo) == 0xfc);
u64 execute(void* ctxt) {
auto* c = (ExecutionContext*)ctxt;
bool bc = false;
@@ -752,6 +797,20 @@ u64 execute(void* ctxt) {
c->pcpyld(t6, t9, t6); // pcpyld t6, t9, t6
c->daddiu(t9, t7, 108); // daddiu t9, t7, 108 // the actual merc-effect
c->load_symbol(a3, cache.merc_bucket_info); // lw a3, *merc-bucket-info*(s7)
// PC HACK: built a bitmask of which effects end up using mercneric.
const MercBucketInfo* mbi = (const MercBucketInfo*)(g_ee_main_mem + c->sgpr64(a3));
u16 use_pc_merc_bits = 0;
u16 ignore_alpha_bits = 0;
for (int i = 0; i < 16; i++) {
if (!mbi->effects[i].use_mercneric) {
use_pc_merc_bits |= (1 << i);
}
if (mbi->effects[i].ignore_alpha) {
ignore_alpha_bits |= (1 << i);
}
}
c->daddiu(ra, a3, 124); // daddiu ra, a3, 124 // effect bucket infos
// effect loop!
@@ -821,6 +880,10 @@ block_3:
c->sw(a3, 12, a2); // sw a3, 12(a2) // st-vif-add's x.
c->srl(s0, s0, 2); // srl s0, s0, 2 // lump fours / 4
c->sq(t1, 16, a2); // sq t1, 16(a2) // row y (will be overwritten) z w (nop).
// PC HACK: sneak in the bits here:
memcpy(g_ee_main_mem + c->sgpr64(a2) + 28, &use_pc_merc_bits, 2);
memcpy(g_ee_main_mem + c->sgpr64(a2) + 30, &ignore_alpha_bits, 2);
// store the dma tag for the lump fours
c->xor_(t3, t3, s0); // xor t3, t3, s0
@@ -898,6 +961,10 @@ block_3:
c->sq(t5, 0, a2); // sq t5, 0(a2) // dma template
c->lbu(s0, 1, gp); // lbu s0, 1(gp) // s0 = mat-dest
c->daddiu(gp, gp, 2); // daddiu gp, gp, 2 // inc mat-dest-data pr
// HACK for PC PORT: stash the source matrix number in the unused bits of nop viftag.
c->sb(a3, 8, a2);
c->lbu(a3, 0, gp); // lbu a3, 0(gp) // load for next iter (ugh)
c->daddiu(s3, s3, -1); // daddiu s3, s3, -1 // dec count
c->sb(s0, 12, a2); // sb s0, 12(a2) // store matrix destination.
+187 -117
View File
@@ -15,7 +15,7 @@
;; It's more than just bones in here - submitting to merc is done from here.
(defglobalconstant USE_GENERIC #t)
(defglobalconstant BACKWARD_COMPAT_MERC_CLIP #f)
;;;;;;;;;;;;;;;;;;
;; calc list
@@ -330,7 +330,7 @@
(let ((a2-1 (the-as bone-memory (+ 16 (scratchpad-object int))))
(v1-2 (the-as bone-memory (+ 16 (scratchpad-object int))))
)
;; layout joints, bones, and outputs
(set! (-> a2-1 work layout joint 0) (the-as joint (+ 256 (scratchpad-object int))))
(set! (-> a2-1 work layout joint 1) (the-as joint (+ 4864 (scratchpad-object int))))
@@ -338,7 +338,7 @@
(set! (-> a2-1 work layout bone 1) (the-as bone (+ 5888 (scratchpad-object int))))
(set! (-> a2-1 work layout output 0) (the-as uint (+ 2816 (scratchpad-object int))))
(set! (-> a2-1 work layout output 1) (the-as uint (+ 7424 (scratchpad-object int))))
;; set up work
(set! (-> v1-2 work next-tag dma) (new 'static 'dma-tag :id (dma-tag-id next)))
(set! (-> v1-2 work next-tag vif0) (new 'static 'vif-tag :imm #x404 :cmd (vif-cmd stcycl)))
@@ -347,13 +347,13 @@
(set! (-> v1-2 work sink-group) arg1)
(set! (-> v1-2 work next-merc) (the-as dma-packet 0))
)
;; reset globals
(let ((v1-3 *merc-globals*))
(set! (-> v1-3 first) (the-as uint 0))
(set! (-> v1-3 next) (the-as (pointer uint32) 0))
)
;; upload bones program.
(#unless PC_PORT
(let ((gp-0 *vu0-dma-list*))
@@ -373,7 +373,7 @@
(dma-buffer-send-chain (the-as dma-bank-source #x10008000) gp-0)
)
)
;; we will use "run" in the shadow queue. Reset that (but don't increment yet, just in case we don't draw shadows)
(let ((gp-1 *shadow-queue*))
(if (>= (-> gp-1 cur-run) (the-as uint 15))
@@ -476,11 +476,11 @@
(.lvf vf25 (&-> v1-13 vector 0 quad))
(.lvf vf26 (&-> v1-13 vector 1 quad))
(.lvf vf27 (&-> v1-13 vector 2 quad))
(.mov v1-14 vf27)
;; hack??
(bones-mtx-calc
(the-as int (-> s4-0 matrix-area))
(the-as pointer (-> s4-0 joints))
@@ -889,7 +889,7 @@
; (let ((vec (the vector (+ (the-as int a3-2) 16))))
; (format 0 "#x~X #x~X mat ~D: ~f ~f ~f~%" a0-55 a3-2 a1-17 (-> vec x) (-> vec y) (-> vec z))
; )
)
(set! (-> (the-as dma-packet a0-55) vif0) (new 'static 'vif-tag))
(set! (-> (the-as dma-packet a0-55) vif1) (new 'static 'vif-tag))
@@ -936,6 +936,35 @@
`(set! (-> (the-as (pointer uint32) ,addr)) ,val)
)
(defun pc-draw-bones ((dc draw-control) (dma-buf pointer))
"Add a dma packet to tell the PC renderer which model we are renderering."
(let ((packet (the-as dma-packet dma-buf)))
;; merc draw asm will check this.
(when (zero? (-> (scratchpad-object terrain-context) work foreground bone-mem work next-merc))
(set! (-> (scratchpad-object terrain-context) work foreground bone-mem work next-merc) packet)
)
(set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 10))
(set! (-> packet vif0) (new 'static 'vif-tag))
(set! (-> packet vif1) (new 'static 'vif-tag :cmd (vif-cmd pc-port)))
(set! dma-buf (the pointer (&+ packet 16)))
)
(let ((data-ptr (the-as (pointer uint128) dma-buf)))
(charp<-string (the (pointer uint8) (&-> data-ptr 0)) (-> dc mgeo name))
)
(&+! dma-buf (* 16 10))
;; merc linking needs this.
(let ((packet (the-as dma-packet dma-buf)))
(set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 0))
(set! (-> packet vif0) (new 'static 'vif-tag))
(set! (-> packet vif1) (new 'static 'vif-tag))
(set! dma-buf (the pointer (&+ packet 16)))
)
dma-buf
)
(defun draw-bones ((arg0 draw-control) (dma-buf dma-buffer) (arg2 float))
"Main draw function for all bone-related renderers. Will set up merc, generic and shadow.
and also add the bones to the calculation list."
@@ -943,7 +972,7 @@
(used-merc int) (used-mercneric int) (effect-idx int) (sv-144 ripple-control))
(rlet ((vf1 :class vf) (vf2 :class vf) (vf3 :class vf) (vf4 :class vf) (vf5 :class vf) (vf6 :class vf)
(vf7 :class vf) (vf8 :class vf) (vf9 :class vf) (acc :class vf))
;; compute the number of bones. I believe this num-joints doesn't count align/prejoint/main, so each needs a bone
(let* ((num-bones (+ (-> arg0 mgeo num-joints) 3))
;; we'll use 128-bytes/bone.
@@ -951,11 +980,11 @@
;; temp work
(spr-work (scratchpad-object terrain-context))
)
;;;;;;;;;;;;;;;;;;;;;;;;;
;; BONE CALC SETUP
;;;;;;;;;;;;;;;;;;;;;;;;;
;; next, grab some DMA data.
;; we'll also use this for storing bone calculation entries.
(let* ((dma-data-start (-> dma-buf base))
@@ -965,14 +994,14 @@
;; we won't calculate them now, but instead we'll tell the *bone-calculation-list* to put them here later.
(matrix-data (the-as object (&+ dma-data-start 64)))
)
;; align the matrix data to 64 bytes (we know it is at least 16 byte aligned)
(let ((a2-1 (logand (the-as int matrix-data) 48)))
(b! (zero? a2-1) cfg-2 :delay (nop!))
(set! matrix-data (&- (&+ (the-as pointer matrix-data) 64) (the-as uint a2-1)))
)
(label cfg-2)
;; start recording perf stats.
;; this just counts the stats for adding the calculation to the linked list.
;; which is really fast and not worth profiling.
@@ -989,7 +1018,7 @@
(set! (-> a2-6 bone-ptr) (-> arg0 skeleton bones))
(set! (-> a2-6 num-bones) (the-as uint num-bones))
)
;; Add the bone calculation to the list, to be done later.
;; this will write to both the "matrix data" and the bones array.
(let ((t0-2 matrix-data)
@@ -1021,7 +1050,7 @@
(&+ bone-calc-entry 48)
;; end stat collection
(read! (-> *perf-stats* data (perf-stat-bucket bones)))
;; set up the dma buffer
(let ((s2-0 (the-as object (+ (the-as uint matrix-data) bone-data-size))))
(let ((a0-2 (shl (the-as int s2-0) 32)))
@@ -1030,15 +1059,25 @@
)
;; NOTE: does this work correctly for the upper 64 bits??
)
;; only data-format 1 is supported.
(when (= (-> arg0 data-format) 1)
;; we'll be filling out the *merc-bucket-info*, then calling the draw asm functions
;; the asm functions read *merc-bucket-info* and generate DMA data as needed.
;;;;;;;;;;;;;;;;;;;;;;;;;
;; LIGHT SETUP
;;;;;;;;;;;;;;;;;;;;;;;;;
;; lights should be converted to a single vu-lights already, by the drawing code.
;; it stashes lights on the scratchpad. We'll load these here and transform them.
;; the result goes in the merc-bucke-info.
;; the result goes in the merc-bucket-info.
(let ((v1-6 (the-as vu-lights (+ 64 (scratchpad-object int))))
(a0-7 (-> *merc-bucket-info* light))
)
;; merc does drawing in a camera rotated frame, so multiply by inv-rot to have the right directions
;; relative to the camera.
(let ((a1-8 (-> *math-camera* inv-camera-rot)))
(.lvf vf4 (&-> a1-8 vector 0 quad))
(.lvf vf5 (&-> a1-8 vector 1 quad))
@@ -1047,30 +1086,29 @@
(.lvf vf1 (&-> v1-6 direction 0 quad))
(.lvf vf2 (&-> v1-6 direction 1 quad))
(.lvf vf3 (&-> v1-6 direction 2 quad))
;;(.vcallms 54)
;; TODO!
;mulax.xyzw ACC, vf01, vf04
(.mul.x.vf acc vf1 vf4)
;madday.xyzw ACC, vf02, vf04
(.add.mul.y.vf acc vf2 vf4 acc)
;maddz.xyzw vf07, vf03, vf04
(.add.mul.z.vf vf7 vf3 vf4 acc)
;mulax.xyzw ACC, vf01, vf05
(.mul.x.vf acc vf1 vf5)
;madday.xyzw ACC, vf02, vf05
(.add.mul.y.vf acc vf2 vf5 acc)
;maddz.xyzw vf08, vf03, vf05
(.add.mul.z.vf vf8 vf3 vf5 acc)
;mulax.xyzw ACC, vf01, vf06
(.mul.x.vf acc vf1 vf6)
;madday.xyzw ACC, vf02, vf06 :e
(.add.mul.y.vf acc vf2 vf6 acc)
;maddz.xyzw vf09, vf03, vf06
(.add.mul.z.vf vf9 vf3 vf6 acc)
;;(.vcallms 54) ;; manually ported to goal here.
;mulax.xyzw ACC, vf01, vf04
(.mul.x.vf acc vf1 vf4)
;madday.xyzw ACC, vf02, vf04
(.add.mul.y.vf acc vf2 vf4 acc)
;maddz.xyzw vf07, vf03, vf04
(.add.mul.z.vf vf7 vf3 vf4 acc)
;mulax.xyzw ACC, vf01, vf05
(.mul.x.vf acc vf1 vf5)
;madday.xyzw ACC, vf02, vf05
(.add.mul.y.vf acc vf2 vf5 acc)
;maddz.xyzw vf08, vf03, vf05
(.add.mul.z.vf vf8 vf3 vf5 acc)
;mulax.xyzw ACC, vf01, vf06
(.mul.x.vf acc vf1 vf6)
;madday.xyzw ACC, vf02, vf06 :e
(.add.mul.y.vf acc vf2 vf6 acc)
;maddz.xyzw vf09, vf03, vf06
(.add.mul.z.vf vf9 vf3 vf6 acc)
(let ((a1-9 (-> v1-6 color 0 quad)))
(let ((a2-14 (-> v1-6 color 1 quad)))
(let ((a3-11 (-> v1-6 color 2 quad)))
@@ -1088,62 +1126,85 @@
(.svf (&-> a0-7 direction 2 quad) vf9)
(set! (-> a0-7 direction 1 w) (the-as float 0))
)
;; the drawable system's culling already knows if we need clipping or not,
;; we just trust it.
(set! (-> *merc-bucket-info* needs-clip)
;; we just trust it. Anything needing clip will have this set. It might set it for
;; meshes that actually don't need clipping.
(set! (-> *merc-bucket-info* needs-clip)
(if (logtest? (-> arg0 status) (draw-status needs-clip))
1
0
)
)
;; Now, we need to decide on merc settings. Default to nothing.
(set! used-merc 0)
(set! used-mercneric 0)
;; clipping is really slow because merc vertex interpolation is slow
;; so we have an option to just let the GS scissor, called mercprime.
;; it requires a special codepath in merc called mercprime.
(set! (-> *merc-bucket-info* need-mercprime-if-merc) 0)
;; if we're really out of bounds, mercprime will fail, and we need to fall back to mercneric
;; in order to do clipping.
(set! (-> *merc-bucket-info* must-use-mercneric-for-clip) 0)
(when (logtest? (-> arg0 status) (draw-status needs-clip))
;; we can either clip with "mercprime" (part of merc), or fall back to generic.
;; the generic approach always works, but is slower, so we try to use mercprime.
;; the user must provide a longest edge length, and we must pass an edge check (fails if close to camera)
(cond
((nonzero? (-> arg0 longest-edge))
;; TODO this makes close up things bad...
(if (draw-bones-check-longest-edge-asm arg0 arg2)
(set! (-> *merc-bucket-info* need-mercprime-if-merc) 1) ;; clip with mercprime!
(set! (-> *merc-bucket-info* must-use-mercneric-for-clip) 1) ;; failed, use generic
;;;;;;;;;;;;;;;;;;;
;; CLIPPING TEST
;; in the PC port merc2 renderer, we handle clipping perfectly without any special flags, and it's the fastest.
;; so we only need to worry about mercneric/mercprime if we want to maintain backward compatibility with original merc
(#when BACKWARD_COMPAT_MERC_CLIP
(when (logtest? (-> arg0 status) (draw-status needs-clip))
;; we can either clip with "mercprime" (part of merc), or fall back to generic.
;; the generic approach always works, but is slower, so we try to use mercprime.
;; the user must provide a longest edge length, and we must pass an edge check (fails if close to camera)
(cond
((nonzero? (-> arg0 longest-edge))
;; check to see if the edge will be too long:
(if (draw-bones-check-longest-edge-asm arg0 arg2)
(set! (-> *merc-bucket-info* need-mercprime-if-merc) 1) ;; use mercprime!
(set! (-> *merc-bucket-info* must-use-mercneric-for-clip) 1) ;; failed, use generic and actually scissor
)
)
(else
;; in cases where no longest edge is set, fall back to mercneric for clipping always
(set! (-> *merc-bucket-info* must-use-mercneric-for-clip) 1)
)
)
)
)
)
(else
(set! (-> *merc-bucket-info* must-use-mercneric-for-clip) 1) ;; no longest edge info, use generic.
)
)
)
;; grab the geometry at the appropriate level of detail.
(let ((geom (-> arg0 lod-set lod (-> arg0 cur-lod) geo)))
(let ((geom (-> arg0 lod-set lod (-> arg0 cur-lod) geo))
;; merc2 can't handle all cases of the original merc, so we add this fallback on PC.
(pc-force-mercneric #f)
)
;; loop over effects, and set them up/pick renderers.
(set! effect-idx 0)
(while (< effect-idx (the-as int (-> geom header effect-count)))
;; try texture scroll
;; check if this "effect" uses texture scrolling
(when (logtest? (-> geom effect effect-idx effect-bits) 1)
;; grab the extra info for it:
(let* ((v1-35 (-> geom effect effect-idx extra-info))
(v1-36 (the-as mei-texture-scroll (+ (the-as uint v1-35) (* (-> v1-35 texture-scroll-offset) 16))))
)
(if (< arg2 (-> v1-36 max-dist))
;; just add to texscroll list.
(when (< arg2 (-> v1-36 max-dist)) ;; in range to draw?
;; do scrolling. in PC, always use generic here because merc2 can't update texture coordinates
(#when PC_PORT
(set! pc-force-mercneric #t)
)
(texscroll-make-request (-> geom effect effect-idx))
)
)
)
;; try ripple
;; check if this effect uses "ripple"
(when (logtest? (-> geom effect effect-idx effect-bits) 4)
;; TODO.
(when (-> arg0 ripple)
;; merc2 can't handle ripple, even if it's off.
(#when PC_PORT
(set! pc-force-mercneric #t)
)
(set! sv-144 (-> arg0 ripple))
(let* ((f1-4 (/ (- (-> sv-144 far-fade-dist) arg2) (- (-> sv-144 far-fade-dist) (-> sv-144 close-fade-dist))))
(f1-6 (fmax 0.0 (fmin 1.0 f1-4)))
@@ -1182,17 +1243,45 @@
)
)
)
;; additional check on PC to force mercneric for blend shapes
(#when PC_PORT
(let* ((pd (the process-drawable (-> arg0 process)))
(jc (-> pd skel)))
(when (nonzero? jc)
(when (logtest? (-> jc status) (janim-status blerc))
(set! pc-force-mercneric #t)
)
)
)
)
;; final mercneric checks
(cond
((nonzero? (-> arg0 death-timer))
;; if using hte death effect, use mercneric always!
((nonzero? (-> arg0 death-timer)) ;; death effect?
;; use mercneric always!
(set! (-> *merc-bucket-info* effect effect-idx use-mercneric) (the-as uint 1))
(set! used-mercneric 1)
used-mercneric
)
((nonzero? (-> geom effect effect-idx envmap-usage))
;; if we need envmap, set it up.
;; hack:
;; the game switches from mercneric to merc after the envmap is faded out.
;; this would normally be fine in the PC port too, but we want the eyes to remain
;; using mercneric (dynamic texture updates) in PC.
;; so we force mercneric on models with envmap and very few tris.
;; it seems to work so far...
(#when PC_PORT
(when (and
(< (-> geom effect effect-idx tri-count) 100)
(-> geom header eye-ctrl)
)
(set! pc-force-mercneric #t)
)
)
(let* ((v1-83 (-> geom effect effect-idx extra-info))
;; pointer to envmap tint data
(v1-84 (the-as structure (+ (the-as uint v1-83) (* (-> v1-83 envmap-tint-offset) 16))))
@@ -1208,10 +1297,11 @@
(if (< f0-9 0.0)
(set! f0-9 0.0)
)
(cond
;; do envmap stuff if we are using generic anyway, or our strength is > 0.
((or (nonzero? (-> *merc-bucket-info* must-use-mercneric-for-clip)) (< 0.0 f0-9))
((or (nonzero? (-> *merc-bucket-info* must-use-mercneric-for-clip)) (< 0.0 f0-9) pc-force-mercneric)
;; multiply colors by sun tint.
(let ((v1-85 (&-> (the-as merc-extra-info v1-84) dummy 4))
(a0-36 (the-as object (-> *merc-bucket-info* effect effect-idx)))
@@ -1249,8 +1339,7 @@
)
)
;; final two cases are for all other effects that don't matter generic/normal
;; here is a good place to force mercneric.
((nonzero? (-> *merc-bucket-info* must-use-mercneric-for-clip))
((or (nonzero? (-> *merc-bucket-info* must-use-mercneric-for-clip)) pc-force-mercneric)
(set! (-> *merc-bucket-info* effect effect-idx use-mercneric) (the-as uint 1))
(set! used-mercneric 1)
used-mercneric
@@ -1262,54 +1351,34 @@
used-merc
)
)
;; HACK just use merc if we aren't using generic.
(#unless USE_GENERIC
(when (logtest? *vu1-enable-user* (vu1-renderer-mask generic))
(unless (logtest? (-> arg0 status) (draw-status needs-clip))
;; no clip, but wants generic. Just use merc for now.
(set! (-> *merc-bucket-info* effect effect-idx use-mercneric) (the-as uint 0))
(set! used-merc 1)
)
(when (and (logtest? (-> arg0 status) (draw-status needs-clip)) ;; we need to clip
;;(nonzero? (-> arg0 longest-edge)) ;; we known the longest edge
;;(draw-bones-check-longest-edge-asm arg0 arg2) ;; it's ok to use percprime.
)
;; wants clip. in all cases, give to merc. this might give it to merc even if it fails the
;; long edge check, but it does ok
(set! (-> *merc-bucket-info* effect effect-idx use-mercneric) (the-as uint 0))
(set! (-> *merc-bucket-info* need-mercprime-if-merc) 1)
(set! used-merc 1)
)
)
)
;; final force to mercneric for ps
(when pc-force-mercneric
(set! (-> *merc-bucket-info* effect effect-idx use-mercneric) (the-as uint 1))
(set! used-mercneric 1)
)
(set! effect-idx (+ effect-idx 1))
) ;; end effect loop
;; draw generic!
;; note: this doesn't do the fully draw, there's some other stuff in process-drawable.gc to actually execute.
(#when USE_GENERIC
(when (nonzero? used-mercneric)
(when (logtest? *vu1-enable-user* (vu1-renderer-mask generic))
(set! (-> dma-buf base) (draw-bones-generic-merc arg0 (the pointer matrix-data) (the pointer s2-0) 0))
(set! s2-0 (-> dma-buf base))
)
;; note: this doesn't do the fully draw, there's some other stuff in process-drawable.gc to actually execute.
(when (nonzero? used-mercneric)
(when (logtest? *vu1-enable-user* (vu1-renderer-mask generic))
(set! (-> dma-buf base) (draw-bones-generic-merc arg0 (the pointer matrix-data) (the pointer s2-0) 0))
(set! s2-0 (-> dma-buf base))
)
)
;; draw shadow!
;; TODO
(when (-> arg0 shadow)
(when #t
(set! s2-0 (draw-bones-shadow arg0 (the pointer matrix-data) (the pointer s2-0)))
(set! (-> dma-buf base) (the-as pointer s2-0))
)
)
;; draw merc!
;;
(when (nonzero? used-merc)
(when (logtest? *vu1-enable-user* (vu1-renderer-mask merc))
(when (= (-> arg0 cur-lod) (-> arg0 lod-set max-lod))
@@ -1328,6 +1397,7 @@
)
)
)
(set! s2-0 (pc-draw-bones arg0 (the pointer s2-0)))
(if (nonzero? (-> *merc-bucket-info* need-mercprime-if-merc))
(set! (-> dma-buf base) (draw-bones-merc arg0 matrix-data s2-0 32 17))
(set! (-> dma-buf base) (draw-bones-merc arg0 matrix-data s2-0 35 20))
@@ -1336,7 +1406,7 @@
)
)
) ;; end geom processing
;; advance death timer.
(when (nonzero? (-> arg0 death-timer))
(when (not (paused?))