mirror of
https://github.com/open-goal/jak-project
synced 2026-07-07 06:05:15 -04:00
[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:
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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?))
|
||||
|
||||
Reference in New Issue
Block a user