[jak2] Work-in-progress texture animations (#2819)

This commit is contained in:
water111 2023-07-14 18:17:54 -04:00 committed by GitHub
parent e546fce370
commit 6f244b11ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 2650 additions and 223 deletions

View file

@ -56,6 +56,7 @@ add_library(common
serialization/subtitles/subtitles.cpp
serialization/text/text_ser.cpp
sqlite/sqlite.cpp
texture/texture_slots.cpp
type_system/defenum.cpp
type_system/deftype.cpp
type_system/state.cpp

View file

@ -433,6 +433,17 @@ void Texture::serialize(Serializer& ser) {
ser.from_ptr(&load_to_pool);
}
void IndexTexture::serialize(Serializer& ser) {
ser.from_ptr(&w);
ser.from_ptr(&h);
ser.from_ptr(&combo_id);
ser.from_pod_vector(&index_data);
ser.from_ptr(&color_table);
ser.from_str(&name);
ser.from_str(&tpage_name);
ser.from_string_vector(&level_names);
}
void CollisionMesh::serialize(Serializer& ser) {
ser.from_pod_vector(&vertices);
}
@ -545,6 +556,15 @@ void Level::serialize(Serializer& ser) {
tex.serialize(ser);
}
if (ser.is_saving()) {
ser.save<size_t>(index_textures.size());
} else {
index_textures.resize(ser.load<size_t>());
}
for (auto& tex : index_textures) {
tex.serialize(ser);
}
for (int geom = 0; geom < 3; ++geom) {
if (ser.is_saving()) {
ser.save<size_t>(tfrag_trees[geom].size());
@ -687,6 +707,11 @@ void Texture::memory_usage(MemoryUsageTracker* tracker) const {
tracker->add(MemoryUsageCategory::TEXTURE, data.size() * sizeof(u32));
}
void IndexTexture::memory_usage(MemoryUsageTracker* tracker) const {
tracker->add(MemoryUsageCategory::SPECIAL_TEXTURE, index_data.size());
tracker->add(MemoryUsageCategory::SPECIAL_TEXTURE, 256 * 4); // clut
}
void Level::memory_usage(MemoryUsageTracker* tracker) const {
for (const auto& texture : textures) {
texture.memory_usage(tracker);
@ -715,6 +740,7 @@ void print_memory_usage(const tfrag3::Level& lev, int uncompressed_data_size) {
std::vector<std::pair<std::string, int>> known_categories = {
{"texture", mem_use.data[tfrag3::MemoryUsageCategory::TEXTURE]},
{"special-texture", mem_use.data[tfrag3::MemoryUsageCategory::SPECIAL_TEXTURE]},
{"tie-deinst-vis", mem_use.data[tfrag3::MemoryUsageCategory::TIE_DEINST_VIS]},
{"tie-deinst-idx", mem_use.data[tfrag3::MemoryUsageCategory::TIE_DEINST_INDEX]},
{"tie-inst-vis", mem_use.data[tfrag3::MemoryUsageCategory::TIE_INST_VIS]},

View file

@ -18,11 +18,13 @@ namespace tfrag3 {
// - if changing any large things (vertices, vis, bvh, colors, textures) update get_memory_usage
// - if adding a new category to the memory usage, update extract_level to print it.
constexpr int TFRAG3_VERSION = 37;
constexpr int TFRAG3_VERSION = 38;
enum MemoryUsageCategory {
TEXTURE,
SPECIAL_TEXTURE,
TIE_DEINST_VIS,
TIE_DEINST_INDEX,
TIE_INST_VIS,
@ -292,6 +294,18 @@ struct Texture {
void memory_usage(MemoryUsageTracker* tracker) const;
};
struct IndexTexture {
u16 w, h;
u32 combo_id = 0;
std::vector<u8> index_data;
std::vector<std::string> level_names;
std::string name;
std::string tpage_name;
std::array<math::Vector4<u8>, 256> color_table;
void serialize(Serializer& ser);
void memory_usage(MemoryUsageTracker* tracker) const;
};
// Tfrag trees have several kinds:
enum class TFragmentTreeKind { NORMAL, TRANS, DIRT, ICE, LOWRES, LOWRES_TRANS, WATER, INVALID };
@ -458,7 +472,7 @@ static_assert(sizeof(MercVertex) == 64);
struct MercDraw {
DrawMode mode;
u32 tree_tex_id = 0; // the texture that should be bound for the draw
s32 tree_tex_id = 0; // the texture that should be bound for the draw (negative for anim slot)
u8 eye_id = 0xff; // 0xff if not eyes, (slot << 1) | (is_r)
u32 first_index;
u32 index_count;
@ -542,6 +556,7 @@ struct Level {
u16 version = TFRAG3_VERSION;
std::string level_name;
std::vector<Texture> textures;
std::vector<IndexTexture> index_textures;
std::array<std::vector<TfragTree>, TFRAG_GEOS> tfrag_trees;
std::array<std::vector<TieTree>, TIE_GEOS> tie_trees;
std::vector<ShrubTree> shrub_trees;

View file

@ -276,6 +276,7 @@ std::string GsTest::print() const {
ASSERT(false);
}
}
result += '\n';
return result;
}

View file

@ -65,7 +65,7 @@ const std::unordered_map<GameVersion, std::vector<std::string>> locale_lookup =
std::string lookup_locale_code(const GameVersion game_version, const int language_id) {
if (locale_lookup.find(game_version) == locale_lookup.end() ||
locale_lookup.at(game_version).size() < language_id) {
(int)locale_lookup.at(game_version).size() < language_id) {
return "";
}
return locale_lookup.at(game_version).at(language_id);

View file

@ -132,7 +132,7 @@ std::pair<SubtitleMetadataFile, SubtitleFile> convert_v1_to_v2(
} else {
if (v1_lines_file.speakers.find(line_meta.speaker) == v1_lines_file.speakers.end() ||
v1_lines_file.hints.find(hint_name) == v1_lines_file.hints.end() ||
line_idx >= v1_lines_file.hints.at(hint_name).size()) {
line_idx >= (int)v1_lines_file.hints.at(hint_name).size()) {
lg::warn(
"{} Couldn't find {} in line file, or line list is too small, or speaker could not "
"be resolved {}!",
@ -145,7 +145,7 @@ std::pair<SubtitleMetadataFile, SubtitleFile> convert_v1_to_v2(
}
}
// Verify we added the amount of lines we expected to
if (lines_added != hint_info.lines.size()) {
if (lines_added != (int)hint_info.lines.size()) {
throw std::runtime_error(
fmt::format("Hint: '{}' has a mismatch in metadata lines vs text lines. Expected {} "
"only added {} lines",
@ -222,7 +222,7 @@ GameSubtitlePackage read_json_files_v1(const GameSubtitleDefinitionFile& file_in
}
}
SubtitleMetadataFileV1 dump_bank_meta_v1(const GameVersion game_version,
SubtitleMetadataFileV1 dump_bank_meta_v1(const GameVersion /*game_version*/,
std::shared_ptr<GameSubtitleBank> bank) {
auto meta_file = SubtitleMetadataFileV1();
for (const auto& [scene_name, scene_info] : bank->m_scenes) {

View file

@ -196,13 +196,13 @@ GameSubtitleSceneInfo GameSubtitleBank::new_scene_from_meta(
// In either case, we acknowledge that there is a line, but there is no text to retrieve at that
// index.
if (line_meta.merge || (relevant_lines.find(scene_name) != relevant_lines.end() &&
relevant_lines.at(scene_name).size() > line_idx &&
(int)relevant_lines.at(scene_name).size() > line_idx &&
relevant_lines.at(scene_name).at(line_idx).empty())) {
new_scene.m_lines.push_back({"", line_meta});
lines_added++;
} else if (m_speakers.find(line_meta.speaker) == m_speakers.end() ||
relevant_lines.find(scene_name) == relevant_lines.end() ||
line_idx >= relevant_lines.at(scene_name).size()) {
line_idx >= (int)relevant_lines.at(scene_name).size()) {
lg::warn(
"{} Couldn't find {} in line file, or line list is too small, or speaker could not "
"be resolved {}!",
@ -290,6 +290,7 @@ SubtitleMetadataFile dump_bank_meta_v2(const GameVersion game_version,
std::shared_ptr<GameSubtitleBank> bank) {
const auto dump_with_duplicates =
dump_language_with_duplicates_from_base(game_version, bank->m_lang_id);
(void)dump_with_duplicates;
auto meta_file = SubtitleMetadataFile();
for (const auto& [scene_name, scene_info] : bank->m_scenes) {
// Avoid dumping duplicates

View file

@ -89,7 +89,7 @@ struct GameSubtitleSceneInfo {
return false;
}
// Check each line
for (int i = 0; i < m_lines.size(); i++) {
for (size_t i = 0; i < m_lines.size(); i++) {
if (m_lines.at(i).text != other.m_lines.at(i).text) {
return false;
}
@ -102,7 +102,7 @@ struct GameSubtitleSceneInfo {
return false;
}
// Check each line's metadata
for (int i = 0; i < m_lines.size(); i++) {
for (size_t i = 0; i < m_lines.size(); i++) {
if (m_lines.at(i).metadata != other.m_lines.at(i).metadata) {
return false;
}

View file

@ -34,7 +34,7 @@ sqlite::GenericResponse sqlite::SQLiteDatabase::run_query(const std::string& sql
const auto rc = sqlite3_exec(
m_db.value().get(), sql.data(),
[](void* data, int argc, char** argv, char** azColName) {
[](void* data, int argc, char** argv, char** /*azColName*/) {
GenericResponse* resp = static_cast<GenericResponse*>(data);
std::vector<std::string> row = {};
for (int i = 0; i < argc; i++) {

View file

@ -0,0 +1,31 @@
#include "texture_slots.h"
namespace {
std::vector<std::string> jak2_slots = {
"jakbsmall-eyebrow",
"jakbsmall-face",
"jakbsmall-finger",
"jakbsmall-hair",
"jak-orig-arm-formorph",
"jak-orig-eyebrow-formorph",
"jak-orig-eyelid-formorph",
"jak-orig-finger-formorph",
"jakb-facelft",
"jakb-facert",
"jakb-hairtrans",
"jakb-eyelid",
"jakb-finger",
"jakb-eyebrow",
//"kor-eyeeffect-formorph",
//"kor-hair-formorph",
//"kor-head-formorph",
//"kor-head-formorph-noreflect",
//"kor-lowercaps-formorph",
//"kor-uppercaps-formorph",
};
}
const std::vector<std::string>& jak2_animated_texture_slots() {
return jak2_slots;
}

View file

@ -0,0 +1,6 @@
#pragma once
#include <string>
#include <vector>
const std::vector<std::string>& jak2_animated_texture_slots();

View file

@ -16,6 +16,7 @@
#include "common/link_types.h"
#include "common/log/log.h"
#include "common/texture/texture_slots.h"
#include "common/util/BinaryReader.h"
#include "common/util/BitUtils.h"
#include "common/util/FileUtil.h"
@ -698,7 +699,9 @@ void ObjectFileDB::find_and_write_scripts(const fs::path& output_dir) {
lg::info(" Total {:.3f} ms", timer.getMs());
}
std::string ObjectFileDB::process_tpages(TextureDB& tex_db, const fs::path& output_path) {
std::string ObjectFileDB::process_tpages(TextureDB& tex_db,
const fs::path& output_path,
const Config& cfg) {
lg::info("- Finding textures in tpages...");
std::string tpage_string = "tpage-";
int total = 0, success = 0;
@ -706,10 +709,25 @@ std::string ObjectFileDB::process_tpages(TextureDB& tex_db, const fs::path& outp
u64 total_px = 0;
Timer timer;
std::vector<std::string> animated_slots;
switch (m_version) {
case GameVersion::Jak1: // no animated
break;
case GameVersion::Jak2:
animated_slots = jak2_animated_texture_slots();
break;
default:
ASSERT_NOT_REACHED();
}
for (size_t i = 0; i < animated_slots.size(); i++) {
tex_db.animated_tex_output_to_anim_slot[animated_slots[i]] = i;
}
std::string result;
for_each_obj([&](ObjectFileData& data) {
if (data.name_in_dgo.substr(0, tpage_string.length()) == tpage_string) {
auto statistics = process_tpage(data, tex_db, output_path);
auto statistics = process_tpage(data, tex_db, output_path, cfg.animated_textures);
total += statistics.total_textures;
success += statistics.successful_textures;
total_px += statistics.num_px;

View file

@ -247,7 +247,7 @@ class ObjectFileDB {
const std::vector<std::string>& imports,
const std::unordered_set<std::string>& skip_functions);
std::string process_tpages(TextureDB& tex_db, const fs::path& output_path);
std::string process_tpages(TextureDB& tex_db, const fs::path& output_path, const Config& cfg);
std::string process_game_count_file();
std::string process_game_text_files(const Config& cfg);
std::string process_all_spool_subtitles(const Config& cfg, const fs::path& image_out);

View file

@ -267,6 +267,11 @@ Config make_config_via_json(nlohmann::json& json) {
config.levels_to_extract = inputs_json.at("levels_to_extract").get<std::vector<std::string>>();
config.levels_extract = json.at("levels_extract").get<bool>();
if (inputs_json.contains("animated_textures")) {
config.animated_textures =
inputs_json.at("animated_textures").get<std::unordered_set<std::string>>();
}
auto art_info_json = read_json_file_from_config(json, "art_info_file");
config.art_groups_by_file =
art_info_json.at("files").get<std::unordered_map<std::string, std::string>>();

View file

@ -159,6 +159,8 @@ struct Config {
std::unordered_map<std::string, int> bad_format_strings;
std::unordered_set<std::string> animated_textures;
std::vector<std::string> levels_to_extract;
bool levels_extract;

View file

@ -443,6 +443,76 @@
"streamed_audio_file_names": [],
// Textures that should be extracted in a special way for use in animated textures.
"animated_textures": [
// Dark Jak (small gameplay model)
"jakbsmall-eyebrow",
"jakbsmall-eyebrow-norm",
"jakbsmall-eyebrow-dark",
"jakbsmall-face",
"jakbsmall-face-norm",
"jakbsmall-face-dark",
"jakbsmall-finger",
"jakbsmall-finger-norm",
"jakbsmall-finger-dark",
"jakbsmall-hair",
"jakbsmall-hair-norm",
"jakbsmall-hair-dark",
// Prison Jak
"jak-orig-arm-formorph",
"jak-orig-arm-formorph-start",
"jak-orig-arm-formorph-end",
"jak-orig-eyebrow-formorph",
"jak-orig-eyebrow-formorph-start",
"jak-orig-eyebrow-formorph-end",
"jak-orig-eyelid-formorph",
"jak-orig-eyelid-formorph-start",
"jak-orig-eyelid-formorph-end",
"jak-orig-finger-formorph",
"jak-orig-finger-formorph-start",
"jak-orig-finger-formorph-end",
"jakb-facelft",
"jakb-facelft-norm",
"jakb-facelft-dark",
"jakb-facert",
"jakb-facert-norm",
"jakb-facert-dark",
"jakb-hairtrans",
"jakb-hairtrans-norm",
"jakb-hairtrans-dark",
// Oracle Jak (and nest jak)
"jakb-eyebrow",
"jakb-eyebrow-norm",
"jakb-eyebrow-dark",
"jakb-eyelid",
"jakb-eyelid-norm",
"jakb-eyelid-dark",
"jakb-finger",
"jakb-finger-norm",
"jakb-finger-dark"
// "kor-eyeeffect-formorph",
// "kor-eyeeffect-formorph-start",
// "kor-eyeeffect-formorph-end",
// "kor-hair-formorph",
// "kor-hair-formorph-start",
// "kor-hair-formorph-end",
// "kor-head-formorph",
// "kor-head-formorph-start",
// "kor-head-formorph-end",
// "kor-head-formorph-noreflect",
// "kor-head-formorph-noreflect-start",
// "kor-head-formorph-noreflect-end",
// "kor-lowercaps-formorph",
// "kor-lowercaps-formorph-start",
// "kor-lowercaps-formorph-end",
// "kor-uppercaps-formorph",
// "kor-uppercaps-formorph-start",
// "kor-uppercaps-formorph-end"
],
"levels_to_extract": [
"ATE.DGO",
"ATO.DGO",

View file

@ -51,6 +51,48 @@ void TextureDB::add_texture(u32 tpage,
}
}
void TextureDB::add_index_texture(u32 tpage,
u32 texid,
const std::vector<u8>& index_data,
const std::array<math::Vector4<u8>, 256>& clut,
u16 w,
u16 h,
const std::string& tex_name,
const std::string& tpage_name,
const std::vector<std::string>& level_names) {
auto existing_tpage_name = tpage_names.find(tpage);
if (existing_tpage_name == tpage_names.end()) {
tpage_names[tpage] = tpage_name;
} else {
ASSERT(existing_tpage_name->second == tpage_name);
}
u32 combo_id = (tpage << 16) | texid;
auto existing_tex = index_textures_by_combo_id.find(combo_id);
if (existing_tex != index_textures_by_combo_id.end()) {
ASSERT(existing_tex->second.name == tex_name);
ASSERT(existing_tex->second.w == w);
ASSERT(existing_tex->second.h == h);
ASSERT(existing_tex->second.index_data == index_data);
ASSERT(existing_tex->second.combo_id == combo_id);
ASSERT(existing_tex->second.color_table == clut);
ASSERT(existing_tex->second.tpage_name == tpage_name);
for (auto& ln : level_names) {
existing_tex->second.level_names.push_back(ln);
}
} else {
auto& new_tex = index_textures_by_combo_id[combo_id];
new_tex.index_data = index_data;
new_tex.color_table = clut;
new_tex.name = tex_name;
new_tex.w = w;
new_tex.h = h;
new_tex.tpage_name = tpage_name;
new_tex.combo_id = combo_id;
new_tex.level_names = level_names;
}
}
void TextureDB::replace_textures(const fs::path& path) {
fs::path base_path(path);
for (auto& tex : textures) {

View file

@ -7,6 +7,7 @@
#include <vector>
#include "common/common_types.h"
#include "common/custom_data/Tfrag3Data.h"
#include "common/util/FileUtil.h"
namespace decompiler {
@ -24,6 +25,11 @@ struct TextureDB {
std::unordered_map<u32, std::string> tpage_names;
std::unordered_map<std::string, std::set<u32>> texture_ids_per_level;
// special textures for animation.
std::map<u32, tfrag3::IndexTexture> index_textures_by_combo_id;
std::unordered_map<std::string, u32> animated_tex_output_to_anim_slot;
void add_texture(u32 tpage,
u32 texid,
const std::vector<u32>& data,
@ -35,6 +41,16 @@ struct TextureDB {
u32 num_mips,
u32 dest);
void add_index_texture(u32 tpage,
u32 texid,
const std::vector<u8>& index_data,
const std::array<math::Vector4<u8>, 256>& clut,
u16 w,
u16 h,
const std::string& tex_name,
const std::string& tpage_name,
const std::vector<std::string>& level_names);
void replace_textures(const fs::path& path);
std::string generate_texture_dest_adjustment_table() const;

View file

@ -16,6 +16,7 @@
#include "tpage.h"
#include "common/log/log.h"
#include "common/texture/texture_conversion.h"
#include "common/util/FileUtil.h"
#include "common/versions/versions.h"
@ -431,7 +432,8 @@ TexturePage read_texture_page(ObjectFileData& data,
*/
TPageResultStats process_tpage(ObjectFileData& data,
TextureDB& texture_db,
const fs::path& output_path) {
const fs::path& output_path,
const std::unordered_set<std::string>& animated_textures) {
TPageResultStats stats;
auto& words = data.linked_data.words_by_seg.at(0);
const auto& level_names = data.dgo_names;
@ -451,6 +453,12 @@ TPageResultStats process_tpage(ObjectFileData& data,
// Read the texture_page struct
TexturePage texture_page = read_texture_page(data, words, 0, end_of_texture_page);
bool ignore_animated = texture_page.name == "sewesc-vis-pris";
if (ignore_animated) {
lg::warn(
"Ignoring animated textures from this tpage ({}) because of weird jakbsmall-finger issue",
texture_page.name);
}
auto texture_dump_dir = output_path / texture_page.name;
file_util::create_dir_if_needed(texture_dump_dir);
@ -494,9 +502,57 @@ TPageResultStats process_tpage(ObjectFileData& data,
stats.total_textures++;
stats.num_px += tex.w * tex.h;
if (tex.psm == int(PSM::PSMT8) && tex.clutpsm == int(CPSM::PSMCT32)) {
// this is the only supported texture format for now.
if (animated_textures.count(tex.name) && !ignore_animated) {
switch (tex.psm) {
case int(PSM::PSMT8):
ASSERT(tex.clutpsm == int(CPSM::PSMCT32));
{
// will store output pixels, index (u8)
std::vector<u8> index_out;
// width is like the TEX0 register, in 64 texel units.
// not sure what the other widths are yet.
int read_width = 64 * tex.width[0];
// loop over pixels in output texture image
for (int y = 0; y < tex.h; y++) {
for (int x = 0; x < tex.w; x++) {
// read as the PSMT8 type. The dest field tells us a block offset.
auto addr8 = psmt8_addr(x, y, read_width) + tex.dest[0] * 256;
u8 value = vram[addr8];
index_out.push_back(value);
}
}
std::array<math::Vector4<u8>, 256> unscrambled_clut;
for (int i = 0; i < 256; i++) {
u32 clut_chunk = i / 16;
u32 off_in_chunk = i % 16;
u8 clx = 0, cly = 0;
if (clut_chunk & 1) {
clx = 8;
}
cly = (clut_chunk >> 1) * 2;
if (off_in_chunk >= 8) {
off_in_chunk -= 8;
cly++;
}
clx += off_in_chunk;
u32 clut_addr = psmct32_addr(clx, cly, 64) + tex.clutdest * 256;
memcpy(&unscrambled_clut[i], vram.data() + clut_addr, 4);
}
// lg::warn("Adding index texture {} from {}\n", texture_page.name, tex.name);
texture_db.add_index_texture(texture_page.id, tex_id, index_out, unscrambled_clut,
tex.w, tex.h, tex.name, texture_page.name, level_names);
stats.successful_textures++;
}
break;
default:
lg::die("Animated texture {} format {}\n", tex.name, tex.psm);
}
}
if (tex.psm == int(PSM::PSMT8) && tex.clutpsm == int(CPSM::PSMCT32)) {
// will store output pixels, rgba (8888)
std::vector<u32> out;

View file

@ -1,4 +1,6 @@
#pragma once
#include <string>
#include <unordered_set>
#include "decompiler/data/TextureDB.h"
@ -13,5 +15,6 @@ struct TPageResultStats {
TPageResultStats process_tpage(ObjectFileData& data,
TextureDB& texture_db,
const fs::path& output_path);
const fs::path& output_path,
const std::unordered_set<std::string>& animated_textures);
} // namespace decompiler

View file

@ -163,7 +163,7 @@ void decompile(const fs::path& iso_data_path, const std::string& data_subfolder)
auto textures_out = out_folder / "textures";
file_util::create_dir_if_needed(textures_out);
file_util::write_text_file(textures_out / "tpage-dir.txt",
db.process_tpages(tex_db, textures_out));
db.process_tpages(tex_db, textures_out, config));
// texture replacements
auto replacements_path = file_util::get_jak_project_dir() / "texture_replacements";
if (fs::exists(replacements_path)) {

View file

@ -85,7 +85,7 @@ void add_all_textures_from_level(tfrag3::Level& lev,
new_tex.w = tex.w;
new_tex.h = tex.h;
new_tex.debug_tpage_name = tex_db.tpage_names.at(tex.page);
new_tex.debug_name = new_tex.debug_tpage_name + tex.name;
new_tex.debug_name = tex.name;
new_tex.data = tex.rgba_bytes;
new_tex.combo_id = id;
new_tex.load_to_pool = true;
@ -241,6 +241,11 @@ void extract_common(const ObjectFileDB& db,
add_all_textures_from_level(tfrag_level, dgo_name, tex_db);
extract_art_groups_from_level(db, tex_db, {}, dgo_name, tfrag_level);
// put _all_ index textures in common.
for (const auto& [id, tex] : tex_db.index_textures_by_combo_id) {
tfrag_level.index_textures.push_back(tex);
}
Serializer ser;
tfrag_level.serialize(ser);
auto compressed =

View file

@ -726,22 +726,22 @@ std::string debug_dump_to_ply(const std::vector<MercDraw>& draws,
return result;
}
int find_or_add_texture_to_level(tfrag3::Level& out,
s32 find_or_add_texture_to_level(tfrag3::Level& out,
const TextureDB& tex_db,
const std::string& debug_name,
u32 pc_combo_tex_id,
const MercCtrlHeader& hdr,
u8* eye_out,
GameVersion version) {
u32 idx_in_level_texture = UINT32_MAX;
for (u32 i = 0; i < out.textures.size(); i++) {
s32 idx_in_level_texture = INT32_MAX;
for (s32 i = 0; i < (int)out.textures.size(); i++) {
if (out.textures[i].combo_id == pc_combo_tex_id) {
idx_in_level_texture = i;
break;
}
}
if (idx_in_level_texture == UINT32_MAX) {
if (idx_in_level_texture == INT32_MAX) {
// not added to level, add it
auto tex_it = tex_db.textures.find(pc_combo_tex_id);
if (tex_it == tex_db.textures.end()) {
@ -792,6 +792,16 @@ int find_or_add_texture_to_level(tfrag3::Level& out,
}
}
// check anim output
const auto& level_tex = out.textures.at(idx_in_level_texture);
const auto& it = tex_db.animated_tex_output_to_anim_slot.find(level_tex.debug_name);
if (it != tex_db.animated_tex_output_to_anim_slot.end()) {
lg::error("Animated slot {} -> {}", level_tex.debug_name, it->second);
return -int(it->second) - 1;
} else {
// lg::warn("no anim: {}", level_tex.debug_name);
}
return idx_in_level_texture;
}

View file

@ -268,7 +268,7 @@ int main(int argc, char** argv) {
if (config.process_tpages || config.levels_extract) {
auto textures_out = out_folder / "textures";
file_util::create_dir_if_needed(textures_out);
auto result = db.process_tpages(tex_db, textures_out);
auto result = db.process_tpages(tex_db, textures_out, config);
if (!result.empty() && config.process_tpages) {
file_util::write_text_file(textures_out / "tpage-dir.txt", result);
file_util::write_text_file(textures_out / "tex-remap.txt",

View file

@ -2,8 +2,7 @@
#include "common/log/log.h"
#include "game/graphics/opengl_renderer/OpenGLRenderer.h"
#include "game/graphics/pipelines/opengl.h"
#include "game/graphics/opengl_renderer/Fbo.h"
BlitDisplays::BlitDisplays(const std::string& name, int my_id) : BucketRenderer(name, my_id) {}

View file

@ -7,7 +7,6 @@
#include "game/graphics/opengl_renderer/Profiler.h"
#include "game/graphics/opengl_renderer/Shader.h"
#include "game/graphics/opengl_renderer/TextureAnimator.h"
#include "game/graphics/opengl_renderer/buckets.h"
#include "game/graphics/opengl_renderer/loader/Loader.h"
#include "game/graphics/texture/TexturePool.h"
@ -32,7 +31,6 @@ struct SharedRenderState {
ShaderLibrary shaders;
std::shared_ptr<TexturePool> texture_pool;
std::shared_ptr<Loader> loader;
std::shared_ptr<TextureAnimator> texture_animator;
u32 buckets_base = 0; // address of buckets array.
u32 next_bucket = 0; // address of next bucket that we haven't started rendering in buckets

View file

@ -140,6 +140,13 @@ void DirectRenderer::reset_state() {
m_stats = {};
}
void DirectRenderer::init_shaders(ShaderLibrary& sl) {
auto id = sl[ShaderId::DIRECT_BASIC_TEXTURED].id();
m_uniforms.alpha_min = glGetUniformLocation(id, "alpha_min");
m_uniforms.alpha_max = glGetUniformLocation(id, "alpha_max");
m_uniforms.normal_shader_id = id;
}
void DirectRenderer::draw_debug_window() {
ImGui::Checkbox("Wireframe", &m_debug_state.wireframe);
ImGui::SameLine();
@ -252,8 +259,40 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state, ScopedProfil
game_height[render_state->version], viewport_size[2], viewport_size[3]);
int draw_count = 0;
glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count);
draw_count++;
int num_tris = 0;
if (m_test_state_needs_double_draw && current_shader == m_uniforms.normal_shader_id) {
// this batch thing is a hack to make the sky in jak 2 draw correctly.
// This is the usual atest with FB_ONLY issue.
// we should check what pcsx2 does
int n_batch = m_prim_buffer.vert_count;
if (n_batch > 50 && n_batch < 700 && (n_batch % 2) == 0) {
n_batch = n_batch / 2;
} else {
// printf("not splitting batch %d\n", n_batch);
}
int offset = 0;
while (offset < m_prim_buffer.vert_count) {
glDepthMask(GL_TRUE);
glUniform1f(m_uniforms.alpha_min, m_double_draw_aref);
glUniform1f(m_uniforms.alpha_max, 10);
glDrawArrays(GL_TRIANGLES, offset, n_batch);
glDepthMask(GL_FALSE);
glUniform1f(m_uniforms.alpha_min, -10);
glUniform1f(m_uniforms.alpha_max, m_double_draw_aref);
glDrawArrays(GL_TRIANGLES, offset, n_batch);
offset += n_batch;
draw_count += 2;
num_tris += n_batch / 3;
}
m_test_state_needs_double_draw = false;
m_test_state_needs_gl_update = true;
m_prim_gl_state_needs_gl_update = true;
} else {
glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count);
num_tris += m_prim_buffer.vert_count / 3;
draw_count++;
}
if (m_debug_state.wireframe) {
render_state->shaders[ShaderId::DEBUG_RED].activate();
@ -268,10 +307,9 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state, ScopedProfil
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(0);
int n_tris = draw_count * (m_prim_buffer.vert_count / 3);
prof.add_tri(n_tris);
prof.add_tri(num_tris);
prof.add_draw_call(draw_count);
m_stats.triangles += n_tris;
m_stats.triangles += num_tris;
m_stats.draw_calls += draw_count;
m_prim_buffer.vert_count = 0;
}
@ -280,14 +318,16 @@ void DirectRenderer::update_gl_prim(SharedRenderState* render_state) {
// currently gouraud is handled in setup.
const auto& state = m_prim_gl_state;
if (state.texture_enable) {
float alpha_reject = 0.0;
float alpha_min = 0.0;
float alpha_max = 10;
if (m_test_state.alpha_test_enable) {
switch (m_test_state.alpha_test) {
case GsTest::AlphaTest::ALWAYS:
break;
case GsTest::AlphaTest::GEQUAL:
case GsTest::AlphaTest::GREATER: // todo
alpha_reject = m_test_state.aref / 128.f;
alpha_min = m_test_state.aref / 128.f;
m_double_draw_aref = alpha_min;
break;
case GsTest::AlphaTest::NEVER:
break;
@ -298,8 +338,11 @@ void DirectRenderer::update_gl_prim(SharedRenderState* render_state) {
render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].activate();
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(),
"alpha_reject"),
alpha_reject);
"alpha_min"),
alpha_min);
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(),
"alpha_max"),
alpha_max);
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(),
"color_mult"),
m_ogl.color_mult);
@ -475,6 +518,14 @@ void DirectRenderer::update_gl_test() {
bool alpha_trick_to_disable = m_test_state.alpha_test_enable &&
m_test_state.alpha_test == GsTest::AlphaTest::NEVER &&
m_test_state.afail == GsTest::AlphaFail::FB_ONLY;
if (m_test_state.afail == GsTest::AlphaFail::FB_ONLY ||
m_test_state.afail == GsTest::AlphaFail::RGB_ONLY) {
m_test_state_needs_double_draw = true;
} else {
m_test_state_needs_double_draw = false;
}
if (state.depth_writes && !alpha_trick_to_disable) {
glDepthMask(GL_TRUE);
} else {

View file

@ -21,6 +21,7 @@
class DirectRenderer : public BucketRenderer {
public:
DirectRenderer(const std::string& name, int my_id, int batch_size);
void init_shaders(ShaderLibrary& sl) override;
~DirectRenderer();
void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override;
virtual void pre_render() {}
@ -289,6 +290,11 @@ class DirectRenderer : public BucketRenderer {
float alpha_mult = 1.0;
} m_ogl;
struct {
GLint alpha_min, alpha_max;
GLint normal_shader_id = -1;
} m_uniforms;
struct {
bool disable_texture = false;
bool wireframe = false;
@ -314,6 +320,8 @@ class DirectRenderer : public BucketRenderer {
bool m_prim_gl_state_needs_gl_update = true;
bool m_test_state_needs_gl_update = true;
bool m_test_state_needs_double_draw = false;
float m_double_draw_aref = 0;
bool m_blend_state_needs_gl_update = true;
struct SpriteMode {

View file

@ -0,0 +1,51 @@
#pragma once
#include <optional>
#include "game/graphics/pipelines/opengl.h"
struct Fbo {
bool valid = false; // do we have an OpenGL fbo_id?
GLuint fbo_id = -1;
// optional rgba/zbuffer/stencil data.
std::optional<GLuint> tex_id;
std::optional<GLuint> zbuf_stencil_id;
bool multisampled = false;
int multisample_count = 0; // Should be 1 if multisampled is disabled
bool is_window = false;
int width = 640;
int height = 480;
// Does this fbo match the given format? MSAA = 1 will accept a normal buffer, or a multisample 1x
bool matches(int w, int h, int msaa) const {
int effective_msaa = multisampled ? multisample_count : 1;
return valid && width == w && height == h && effective_msaa == msaa;
}
bool matches(const Fbo& other) const {
return matches(other.width, other.height, other.multisample_count);
}
// Free opengl resources, if we have any.
void clear() {
if (valid) {
glDeleteFramebuffers(1, &fbo_id);
fbo_id = -1;
if (tex_id) {
glDeleteTextures(1, &tex_id.value());
tex_id.reset();
}
if (zbuf_stencil_id) {
glDeleteRenderbuffers(1, &zbuf_stencil_id.value());
zbuf_stencil_id.reset();
}
valid = false;
}
}
};

View file

@ -85,7 +85,25 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr<TexturePool> texture_pool,
lg::debug("OpenGL context information: {}", (const char*)glGetString(GL_VERSION));
m_merc2 = std::make_shared<Merc2>(m_render_state.shaders);
const tfrag3::Level* common_level = nullptr;
{
auto p = scoped_prof("load-common");
common_level = &m_render_state.loader->load_common(*m_render_state.texture_pool, "GAME");
}
// initialize all renderers
switch (m_version) {
case GameVersion::Jak1:
break;
case GameVersion::Jak2:
m_texture_animator = std::make_shared<TextureAnimator>(m_render_state.shaders, common_level);
break;
default:
ASSERT(false);
}
m_merc2 = std::make_shared<Merc2>(m_render_state.shaders,
m_texture_animator ? m_texture_animator->slots() : nullptr);
m_generic2 = std::make_shared<Generic2>(m_render_state.shaders);
// initialize all renderers
@ -95,7 +113,6 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr<TexturePool> texture_pool,
init_bucket_renderers_jak1();
break;
case GameVersion::Jak2:
m_render_state.texture_animator = std::make_shared<TextureAnimator>();
init_bucket_renderers_jak2();
break;
default:
@ -112,7 +129,7 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
init_bucket_renderer<VisDataHandler>("vis", BucketCategory::OTHER, BucketId::BUCKET_2);
init_bucket_renderer<BlitDisplays>("blit", BucketCategory::OTHER, BucketId::BUCKET_3);
init_bucket_renderer<TextureUploadHandler>("tex-lcom-sky-pre", BucketCategory::TEX,
BucketId::TEX_LCOM_SKY_PRE);
BucketId::TEX_LCOM_SKY_PRE, m_texture_animator);
init_bucket_renderer<DirectRenderer>("sky-draw", BucketCategory::OTHER, BucketId::SKY_DRAW, 1024);
init_bucket_renderer<OceanMidAndFar>("ocean-mid-far", BucketCategory::OCEAN,
BucketId::OCEAN_MID_FAR);
@ -121,7 +138,8 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
#define GET_BUCKET_ID_FOR_LIST(bkt1, bkt2, idx) ((int)(bkt1) + ((int)(bkt2) - (int)(bkt1)) * (idx))
init_bucket_renderer<TextureUploadHandler>(
fmt::format("tex-l{}-tfrag", i), BucketCategory::TEX,
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_TFRAG, BucketId::TEX_L1_TFRAG, i));
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_TFRAG, BucketId::TEX_L1_TFRAG, i),
m_texture_animator);
init_bucket_renderer<TFragment>(
fmt::format("tfrag-l{}-tfrag", i), BucketCategory::TFRAG,
GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_L0_TFRAG, BucketId::TFRAG_L1_TFRAG, i),
@ -143,7 +161,8 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
init_bucket_renderer<TextureUploadHandler>(
fmt::format("tex-l{}-shrub", i), BucketCategory::TEX,
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_SHRUB, BucketId::TEX_L1_SHRUB, i));
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_SHRUB, BucketId::TEX_L1_SHRUB, i),
m_texture_animator);
init_bucket_renderer<Shrub>(
fmt::format("shrub-l{}-shrub", i), BucketCategory::SHRUB,
GET_BUCKET_ID_FOR_LIST(BucketId::SHRUB_L0_SHRUB, BucketId::SHRUB_L1_SHRUB, i));
@ -157,7 +176,8 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
init_bucket_renderer<TextureUploadHandler>(
fmt::format("tex-l{}-alpha", i), BucketCategory::TEX,
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_ALPHA, BucketId::TEX_L1_ALPHA, i));
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_ALPHA, BucketId::TEX_L1_ALPHA, i),
m_texture_animator);
init_bucket_renderer<TFragment>(
fmt::format("tfrag-t-l{}-alpha", i), BucketCategory::TFRAG,
GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_T_L0_ALPHA, BucketId::TFRAG_T_L1_ALPHA, i),
@ -180,7 +200,8 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
init_bucket_renderer<TextureUploadHandler>(
fmt::format("tex-l{}-pris", i), BucketCategory::TEX,
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_PRIS, BucketId::TEX_L1_PRIS, i));
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_PRIS, BucketId::TEX_L1_PRIS, i),
m_texture_animator);
init_bucket_renderer<Merc2BucketRenderer>(
fmt::format("merc-l{}-pris", i), BucketCategory::MERC,
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_PRIS, BucketId::MERC_L1_PRIS, i), m_merc2);
@ -191,7 +212,8 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
init_bucket_renderer<TextureUploadHandler>(
fmt::format("tex-l{}-pris2", i), BucketCategory::TEX,
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_PRIS2, BucketId::TEX_L1_PRIS2, i));
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_PRIS2, BucketId::TEX_L1_PRIS2, i),
m_texture_animator);
init_bucket_renderer<Merc2BucketRenderer>(
fmt::format("merc-l{}-pris2", i), BucketCategory::MERC,
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_PRIS2, BucketId::MERC_L1_PRIS2, i), m_merc2);
@ -202,7 +224,8 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
init_bucket_renderer<TextureUploadHandler>(
fmt::format("tex-l{}-water", i), BucketCategory::TEX,
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_WATER, BucketId::TEX_L1_WATER, i));
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_WATER, BucketId::TEX_L1_WATER, i),
m_texture_animator);
init_bucket_renderer<Merc2BucketRenderer>(
fmt::format("merc-l{}-water", i), BucketCategory::MERC,
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_WATER, BucketId::MERC_L1_WATER, i), m_merc2);
@ -226,12 +249,12 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
}
// 180
init_bucket_renderer<TextureUploadHandler>("tex-lcom-tfrag", BucketCategory::TEX,
BucketId::TEX_LCOM_TFRAG);
BucketId::TEX_LCOM_TFRAG, m_texture_animator);
init_bucket_renderer<Merc2BucketRenderer>("merc-lcom-tfrag", BucketCategory::MERC,
BucketId::MERC_LCOM_TFRAG, m_merc2);
// 190
init_bucket_renderer<TextureUploadHandler>("tex-lcom-shrub", BucketCategory::TEX,
BucketId::TEX_LCOM_SHRUB);
BucketId::TEX_LCOM_SHRUB, m_texture_animator);
init_bucket_renderer<Merc2BucketRenderer>("merc-lcom-shrub", BucketCategory::MERC,
BucketId::MERC_LCOM_SHRUB, m_merc2);
init_bucket_renderer<Generic2BucketRenderer>("gmerc-lcom-tfrag", BucketCategory::GENERIC,
@ -240,30 +263,30 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
init_bucket_renderer<Shadow2>("shadow", BucketCategory::OTHER, BucketId::SHADOW);
// 220
init_bucket_renderer<TextureUploadHandler>("tex-lcom-pris", BucketCategory::TEX,
BucketId::TEX_LCOM_PRIS);
BucketId::TEX_LCOM_PRIS, m_texture_animator);
init_bucket_renderer<Merc2BucketRenderer>("merc-lcom-pris", BucketCategory::MERC,
BucketId::MERC_LCOM_PRIS, m_merc2);
init_bucket_renderer<TextureUploadHandler>("tex-lcom-water", BucketCategory::TEX,
BucketId::TEX_LCOM_WATER);
BucketId::TEX_LCOM_WATER, m_texture_animator);
init_bucket_renderer<Merc2BucketRenderer>("merc-lcom-water", BucketCategory::MERC,
BucketId::MERC_LCOM_WATER, m_merc2);
init_bucket_renderer<TextureUploadHandler>("tex-lcom-sky-post", BucketCategory::TEX,
BucketId::TEX_LCOM_SKY_POST);
BucketId::TEX_LCOM_SKY_POST, m_texture_animator);
// 310
init_bucket_renderer<OceanNear>("ocean-near", BucketCategory::OCEAN, BucketId::OCEAN_NEAR);
init_bucket_renderer<TextureUploadHandler>("tex-all-sprite", BucketCategory::TEX,
BucketId::TEX_ALL_SPRITE);
BucketId::TEX_ALL_SPRITE, m_texture_animator);
init_bucket_renderer<Sprite3>("particles", BucketCategory::SPRITE, BucketId::PARTICLES);
init_bucket_renderer<Shadow2>("shadow2", BucketCategory::OTHER, BucketId::SHADOW2);
init_bucket_renderer<Generic2BucketRenderer>("effects", BucketCategory::OTHER, BucketId::EFFECTS,
m_generic2, Generic2::Mode::LIGHTNING);
init_bucket_renderer<TextureUploadHandler>("tex-all-warp", BucketCategory::TEX,
BucketId::TEX_ALL_WARP);
BucketId::TEX_ALL_WARP, m_texture_animator);
init_bucket_renderer<Warp>("warp", BucketCategory::GENERIC, BucketId::GMERC_WARP, m_generic2);
init_bucket_renderer<DirectRenderer>("debug-no-zbuf1", BucketCategory::OTHER,
BucketId::DEBUG_NO_ZBUF1, 0x8000);
init_bucket_renderer<TextureUploadHandler>("tex-all-map", BucketCategory::TEX,
BucketId::TEX_ALL_MAP);
BucketId::TEX_ALL_MAP, m_texture_animator);
// 320
init_bucket_renderer<ProgressRenderer>("progress", BucketCategory::OTHER, BucketId::PROGRESS,
0x1000);
@ -295,10 +318,8 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
m_jak2_eye_renderer->init_shaders(m_render_state.shaders);
m_jak2_eye_renderer->init_textures(*m_render_state.texture_pool, GameVersion::Jak2);
}
auto p = scoped_prof("load-common");
m_render_state.loader->load_common(*m_render_state.texture_pool, "GAME");
}
/*!
* Construct bucket renderers. We can specify different renderers for different buckets
*/
@ -331,7 +352,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
//-----------------------
// 5 : TFRAG_TEX_LEVEL0
init_bucket_renderer<TextureUploadHandler>("l0-tfrag-tex", BucketCategory::TEX,
BucketId::TFRAG_TEX_LEVEL0);
BucketId::TFRAG_TEX_LEVEL0, m_texture_animator);
// 6 : TFRAG_LEVEL0
init_bucket_renderer<TFragment>("l0-tfrag-tfrag", BucketCategory::TFRAG, BucketId::TFRAG_LEVEL0,
normal_tfrags, false, 0);
@ -353,7 +374,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
//-----------------------
// 12 : TFRAG_TEX_LEVEL1
init_bucket_renderer<TextureUploadHandler>("l1-tfrag-tex", BucketCategory::TEX,
BucketId::TFRAG_TEX_LEVEL1);
BucketId::TFRAG_TEX_LEVEL1, m_texture_animator);
// 13 : TFRAG_LEVEL1
init_bucket_renderer<TFragment>("l1-tfrag-tfrag", BucketCategory::TFRAG, BucketId::TFRAG_LEVEL1,
normal_tfrags, false, 1);
@ -375,7 +396,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
//-----------------------
// 19 : SHRUB_TEX_LEVEL0
init_bucket_renderer<TextureUploadHandler>("l0-shrub-tex", BucketCategory::TEX,
BucketId::SHRUB_TEX_LEVEL0);
BucketId::SHRUB_TEX_LEVEL0, m_texture_animator);
// 20 : SHRUB_NORMAL_LEVEL0
init_bucket_renderer<Shrub>("l0-shrub", BucketCategory::SHRUB, BucketId::SHRUB_NORMAL_LEVEL0);
// 21 : ???
@ -391,7 +412,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
//-----------------------
// 25 : SHRUB_TEX_LEVEL1
init_bucket_renderer<TextureUploadHandler>("l1-shrub-tex", BucketCategory::TEX,
BucketId::SHRUB_TEX_LEVEL1);
BucketId::SHRUB_TEX_LEVEL1, m_texture_animator);
// 26 : SHRUB_NORMAL_LEVEL1
init_bucket_renderer<Shrub>("l1-shrub", BucketCategory::SHRUB, BucketId::SHRUB_NORMAL_LEVEL1);
// 27 : ???
@ -406,7 +427,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
// LEVEL 0 alpha texture
//-----------------------
init_bucket_renderer<TextureUploadHandler>("l0-alpha-tex", BucketCategory::TEX,
BucketId::ALPHA_TEX_LEVEL0); // 31
BucketId::ALPHA_TEX_LEVEL0, m_texture_animator); // 31
init_bucket_renderer<SkyBlendHandler>("l0-alpha-sky-blend-and-tfrag-trans", BucketCategory::OTHER,
BucketId::TFRAG_TRANS0_AND_SKY_BLEND_LEVEL0, 0,
sky_gpu_blender, sky_cpu_blender); // 32
@ -423,7 +444,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
// LEVEL 1 alpha texture
//-----------------------
init_bucket_renderer<TextureUploadHandler>("l1-alpha-tex", BucketCategory::TEX,
BucketId::ALPHA_TEX_LEVEL1); // 38
BucketId::ALPHA_TEX_LEVEL1, m_texture_animator); // 38
init_bucket_renderer<SkyBlendHandler>("l1-alpha-sky-blend-and-tfrag-trans", BucketCategory::OTHER,
BucketId::TFRAG_TRANS1_AND_SKY_BLEND_LEVEL1, 1,
sky_gpu_blender, sky_cpu_blender); // 39
@ -448,7 +469,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
// LEVEL 0 pris texture
//-----------------------
init_bucket_renderer<TextureUploadHandler>("l0-pris-tex", BucketCategory::TEX,
BucketId::PRIS_TEX_LEVEL0); // 48
BucketId::PRIS_TEX_LEVEL0, m_texture_animator); // 48
init_bucket_renderer<Merc2BucketRenderer>("l0-pris-merc", BucketCategory::MERC,
BucketId::MERC_PRIS_LEVEL0, m_merc2); // 49
init_bucket_renderer<Generic2BucketRenderer>("l0-pris-generic", BucketCategory::GENERIC,
@ -459,7 +480,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
// LEVEL 1 pris texture
//-----------------------
init_bucket_renderer<TextureUploadHandler>("l1-pris-tex", BucketCategory::TEX,
BucketId::PRIS_TEX_LEVEL1); // 51
BucketId::PRIS_TEX_LEVEL1, m_texture_animator); // 51
init_bucket_renderer<Merc2BucketRenderer>("l1-pris-merc", BucketCategory::MERC,
BucketId::MERC_PRIS_LEVEL1, m_merc2); // 52
init_bucket_renderer<Generic2BucketRenderer>("l1-pris-generic", BucketCategory::GENERIC,
@ -481,7 +502,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
// LEVEL 0 water texture
//-----------------------
init_bucket_renderer<TextureUploadHandler>("l0-water-tex", BucketCategory::TEX,
BucketId::WATER_TEX_LEVEL0); // 57
BucketId::WATER_TEX_LEVEL0, m_texture_animator); // 57
init_bucket_renderer<Merc2BucketRenderer>("l0-water-merc", BucketCategory::MERC,
BucketId::MERC_WATER_LEVEL0, m_merc2); // 58
init_bucket_renderer<Generic2BucketRenderer>("l0-water-generic", BucketCategory::GENERIC,
@ -492,7 +513,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
// LEVEL 1 water texture
//-----------------------
init_bucket_renderer<TextureUploadHandler>("l1-water-tex", BucketCategory::TEX,
BucketId::WATER_TEX_LEVEL1); // 60
BucketId::WATER_TEX_LEVEL1, m_texture_animator); // 60
init_bucket_renderer<Merc2BucketRenderer>("l1-water-merc", BucketCategory::MERC,
BucketId::MERC_WATER_LEVEL1, m_merc2); // 61
init_bucket_renderer<Generic2BucketRenderer>("l1-water-generic", BucketCategory::GENERIC,
@ -510,7 +531,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
// COMMON texture
//-----------------------
init_bucket_renderer<TextureUploadHandler>("common-tex", BucketCategory::TEX,
BucketId::PRE_SPRITE_TEX); // 65
BucketId::PRE_SPRITE_TEX, m_texture_animator); // 65
init_bucket_renderer<Sprite3>("sprite", BucketCategory::SPRITE, BucketId::SPRITE); // 66
@ -532,7 +553,6 @@ void OpenGLRenderer::init_bucket_renderers_jak1() {
}
sky_cpu_blender->init_textures(*m_render_state.texture_pool, m_version);
sky_gpu_blender->init_textures(*m_render_state.texture_pool, m_version);
m_render_state.loader->load_common(*m_render_state.texture_pool, "GAME");
}
namespace {

View file

@ -7,8 +7,10 @@
#include "game/graphics/opengl_renderer/BucketRenderer.h"
#include "game/graphics/opengl_renderer/CollideMeshRenderer.h"
#include "game/graphics/opengl_renderer/Fbo.h"
#include "game/graphics/opengl_renderer/Profiler.h"
#include "game/graphics/opengl_renderer/Shader.h"
#include "game/graphics/opengl_renderer/TextureAnimator.h"
#include "game/graphics/opengl_renderer/foreground/Generic2.h"
#include "game/graphics/opengl_renderer/foreground/Merc2.h"
#include "game/graphics/opengl_renderer/opengl_utils.h"
@ -51,52 +53,6 @@ struct RenderOptions {
bool gpu_sync = false;
};
struct Fbo {
bool valid = false; // do we have an OpenGL fbo_id?
GLuint fbo_id = -1;
// optional rgba/zbuffer/stencil data.
std::optional<GLuint> tex_id;
std::optional<GLuint> zbuf_stencil_id;
bool multisampled = false;
int multisample_count = 0; // Should be 1 if multisampled is disabled
bool is_window = false;
int width = 640;
int height = 480;
// Does this fbo match the given format? MSAA = 1 will accept a normal buffer, or a multisample 1x
bool matches(int w, int h, int msaa) const {
int effective_msaa = multisampled ? multisample_count : 1;
return valid && width == w && height == h && effective_msaa == msaa;
}
bool matches(const Fbo& other) const {
return matches(other.width, other.height, other.multisample_count);
}
// Free opengl resources, if we have any.
void clear() {
if (valid) {
glDeleteFramebuffers(1, &fbo_id);
fbo_id = -1;
if (tex_id) {
glDeleteTextures(1, &tex_id.value());
tex_id.reset();
}
if (zbuf_stencil_id) {
glDeleteRenderbuffers(1, &zbuf_stencil_id.value());
zbuf_stencil_id.reset();
}
valid = false;
}
}
};
/*!
* Main OpenGL renderer.
* This handles the glClear and all game rendering, but not actual setup, synchronization or imgui
@ -150,6 +106,7 @@ class OpenGLRenderer {
std::shared_ptr<Merc2> m_merc2;
std::shared_ptr<Generic2> m_generic2;
std::shared_ptr<TextureAnimator> m_texture_animator;
std::vector<std::unique_ptr<BucketRenderer>> m_bucket_renderers;
std::vector<BucketCategory> m_bucket_categories;

View file

@ -125,6 +125,7 @@ ShaderLibrary::ShaderLibrary(GameVersion version) {
at(ShaderId::ETIE_BASE) = {"etie_base", version};
at(ShaderId::ETIE) = {"etie", version};
at(ShaderId::SHADOW2) = {"shadow2", version};
at(ShaderId::TEX_ANIM) = {"tex_anim", version};
for (auto& shader : m_shaders) {
ASSERT_MSG(shader.okay(), "error compiling shader");

View file

@ -58,6 +58,7 @@ enum class ShaderId {
ETIE = 31,
SHADOW2 = 32,
DIRECT_BASIC_TEXTURED_MULTI_UNIT = 33,
TEX_ANIM = 34,
MAX_SHADERS
};

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,215 @@
#pragma once
#include <optional>
#include <set>
#include <unordered_map>
#include <vector>
#include "common/custom_data/Tfrag3Data.h"
#include "common/dma/dma_chain_read.h"
#include "common/dma/gs.h"
#include "common/math/Vector.h"
#include "game/graphics/opengl_renderer/Shader.h"
#include "game/graphics/opengl_renderer/opengl_utils.h"
#include "game/graphics/pipelines/opengl.h"
#include "game/graphics/texture/TextureConverter.h"
#include "game/graphics/texture/TextureID.h"
struct GpuTexture;
struct VramEntry {
enum class Kind { CLUT16_16_IN_PSM32, GENERIC_PSM32, GENERIC_PSMT8, GPU, INVALID } kind;
std::vector<u8> data;
int tex_width = 0;
int tex_height = 0;
int dest_texture_address = 0;
int cbp = 0;
std::optional<FramebufferTexturePair> tex;
// math::Vector<u8, 4> rgba_clear;
bool needs_pool_update = false;
GpuTexture* pool_gpu_tex = nullptr;
void reset() {
data.clear();
kind = Kind::INVALID;
tex_height = 0;
tex_width = 0;
cbp = 0;
// tex.reset();
needs_pool_update = false;
// pool_gpu_tex = nullptr;
}
};
struct ShaderContext {
GsTex0 tex0;
GsTex1 tex1;
GsTest test;
bool clamp_u, clamp_v;
GsAlpha alpha;
bool source_texture_set = false;
};
struct OpenGLTexturePool {
OpenGLTexturePool();
~OpenGLTexturePool();
GLuint allocate(u64 w, u64 h);
void free(GLuint texture, u64 w, u64 h);
std::unordered_map<u64, std::vector<GLuint>> textures;
};
class ClutBlender {
public:
ClutBlender(const std::string& dest,
const std::vector<std::string>& sources,
const std::optional<std::string>& level_name,
const tfrag3::Level* level,
OpenGLTexturePool* tpool);
GLuint run(const float* weights);
GLuint texture() const { return m_texture; }
private:
const tfrag3::IndexTexture* m_dest;
std::vector<const std::array<math::Vector4<u8>, 256>*> m_cluts;
std::vector<float> m_current_weights;
GLuint m_texture;
std::array<math::Vector4<u8>, 256> m_temp_clut;
std::vector<u32> m_temp_rgba;
};
class TexturePool;
class TextureAnimator {
public:
void handle_texture_anim_data(DmaFollower& dma);
TextureAnimator(ShaderLibrary& shaders, const tfrag3::Level* common_level);
~TextureAnimator();
void handle_texture_anim_data(DmaFollower& dma, const u8* ee_mem, TexturePool* texture_pool);
GLuint get_by_slot(int idx);
const std::vector<GLuint>* slots() { return &m_output_slots; }
private:
void handle_upload_clut_16_16(const DmaTransfer& tf, const u8* ee_mem);
void handle_generic_upload(const DmaTransfer& tf, const u8* ee_mem);
void handle_erase_dest(DmaFollower& dma);
void handle_set_shader(DmaFollower& dma);
void handle_draw(DmaFollower& dma, TexturePool& texture_pool);
void handle_rg_to_ba(const DmaTransfer& tf);
void handle_set_clut_alpha(const DmaTransfer& tf);
void handle_copy_clut_alpha(const DmaTransfer& tf);
VramEntry* setup_vram_entry_for_gpu_texture(int w, int h, int tbp);
void set_up_opengl_for_shader(const ShaderContext& shader,
std::optional<GLuint> texture,
bool prim_abe);
void load_clut_to_converter();
const u32* get_clut_16_16_psm32(int cbp);
GLuint make_temp_gpu_texture(const u32* data, u32 width, u32 height);
GLuint make_or_get_gpu_texture_for_current_shader(TexturePool& texture_pool);
void force_to_gpu(int tbp);
struct DrawData {
u8 tmpl1[16];
math::Vector<u32, 4> color;
math::Vector<float, 4> st0;
math::Vector<u32, 4> pos0;
math::Vector<float, 4> st1;
math::Vector<u32, 4> pos1;
math::Vector<float, 4> st2;
math::Vector<u32, 4> pos2;
math::Vector<float, 4> st3;
math::Vector<u32, 4> pos3;
};
void set_uniforms_from_draw_data(const DrawData& dd, int dest_w, int dest_h);
PcTextureId get_id_for_tbp(TexturePool* pool, u32 tbp);
VramEntry* m_tex_looking_for_clut = nullptr;
const tfrag3::Level* m_common_level = nullptr;
std::unordered_map<u32, VramEntry> m_textures;
std::unordered_map<u32, PcTextureId> m_ids_by_vram;
std::set<u32> m_erased_on_this_frame;
struct TempTexture {
GLuint tex;
u32 w, h;
};
std::vector<TempTexture> m_in_use_temp_textures;
ShaderContext m_current_shader;
TextureConverter m_converter;
int m_current_dest_tbp = -1;
GLuint m_vao;
GLuint m_vertex_buffer;
struct Vertex {
u32 index;
u32 pad1;
u32 pad2;
u32 pad3;
};
struct {
GLuint rgba;
GLuint positions;
GLuint uvs;
GLuint enable_tex;
GLuint channel_scramble;
GLuint tcc;
} m_uniforms;
GLuint m_shader_id;
GLuint m_dummy_texture;
u8 m_index_to_clut_addr[256];
OpenGLTexturePool m_opengl_texture_pool;
std::vector<GLuint> m_output_slots;
struct ClutBlenderGroup {
std::vector<ClutBlender> blenders;
std::vector<int> outputs;
};
std::vector<ClutBlenderGroup> m_clut_blender_groups;
int m_darkjak_clut_blender_idx = -1;
int m_jakb_prison_clut_blender_idx = -1;
int m_jakb_oracle_clut_blender_idx = -1;
int m_jakb_nest_clut_blender_idx = -1;
int m_kor_transform_clut_blender_idx = -1;
int create_clut_blender_group(const std::vector<std::string>& textures,
const std::string& suffix0,
const std::string& suffix1,
const std::optional<std::string>& dgo);
void add_to_clut_blender_group(int idx,
const std::vector<std::string>& textures,
const std::string& suffix0,
const std::string& suffix1,
const std::optional<std::string>& dgo);
void run_clut_blender_group(DmaTransfer& tf, int idx);
// std::vector<ClutBlender> m_darkjak_blenders;
// std::vector<int> m_darkjak_output_slots;
//
// std::vector<ClutBlender> m_jakb_prison_blenders;
// std::vector<int> m_jakb_prison_output_slots;
//
// std::vector<ClutBlender> m_jakb_oracle_blenders;
// std::vector<int> m_jakb_oracle_slots;
//
// std::vector<ClutBlender> m_jakb_nest_blenders;
// std::vector<int> m_jakb_nest_slots;
};

View file

@ -1,5 +1,6 @@
#include "TextureUploadHandler.h"
#include "common/global_profiler/GlobalProfiler.h"
#include "common/log/log.h"
#include "game/graphics/opengl_renderer/EyeRenderer.h"
@ -8,8 +9,10 @@
#include "third-party/fmt/core.h"
#include "third-party/imgui/imgui.h"
TextureUploadHandler::TextureUploadHandler(const std::string& name, int my_id)
: BucketRenderer(name, my_id) {}
TextureUploadHandler::TextureUploadHandler(const std::string& name,
int my_id,
std::shared_ptr<TextureAnimator> texture_animator)
: BucketRenderer(name, my_id), m_texture_animator(texture_animator) {}
void TextureUploadHandler::render(DmaFollower& dma,
SharedRenderState* render_state,
@ -25,7 +28,9 @@ void TextureUploadHandler::render(DmaFollower& dma,
auto vif0 = dma.current_tag_vifcode0();
if (vif0.kind == VifCode::Kind::PC_PORT && vif0.immediate == 12) {
dma.read_and_advance();
render_state->texture_animator->handle_texture_anim_data(dma);
auto p = scoped_prof("texture-animator");
m_texture_animator->handle_texture_anim_data(dma, (const u8*)render_state->ee_main_memory,
render_state->texture_pool.get());
}
// does it look like data to do eye rendering?
if (dma_tag.qwc == (128 / 16)) {
@ -63,6 +68,7 @@ void TextureUploadHandler::render(DmaFollower& dma,
void TextureUploadHandler::flush_uploads(std::vector<TextureUpload>& uploads,
SharedRenderState* render_state) {
auto p = scoped_prof("flush-uploads");
if (m_fake_uploads) {
uploads.clear();
} else {
@ -73,7 +79,7 @@ void TextureUploadHandler::flush_uploads(std::vector<TextureUpload>& uploads,
const u8* ee_mem = (const u8*)render_state->ee_main_memory;
for (auto& upload : uploads) {
render_state->texture_pool->handle_upload_now(ee_mem + upload.page, upload.mode, ee_mem,
render_state->offset_of_s7);
render_state->offset_of_s7, m_my_id == 999);
}
}
}

View file

@ -1,6 +1,7 @@
#pragma once
#include "game/graphics/opengl_renderer/BucketRenderer.h"
#include "game/graphics/opengl_renderer/TextureAnimator.h"
#include "game/graphics/texture/TexturePool.h"
/*!
@ -10,7 +11,9 @@
*/
class TextureUploadHandler : public BucketRenderer {
public:
TextureUploadHandler(const std::string& name, int my_id);
TextureUploadHandler(const std::string& name,
int my_id,
std::shared_ptr<TextureAnimator> texture_animator);
void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override;
void draw_debug_window() override;
@ -22,4 +25,5 @@ class TextureUploadHandler : public BucketRenderer {
void flush_uploads(std::vector<TextureUpload>& uploads, SharedRenderState* render_state);
bool m_fake_uploads = false;
int m_upload_count = 0;
std::shared_ptr<TextureAnimator> m_texture_animator;
};

View file

@ -48,7 +48,8 @@
std::mutex g_merc_data_mutex;
Merc2::Merc2(ShaderLibrary& shaders) {
Merc2::Merc2(ShaderLibrary& shaders, const std::vector<GLuint>* anim_slot_array)
: m_anim_slot_array(anim_slot_array) {
// Set up main vertex array. This will point to the data stored in the .FR3 level file, and will
// be uploaded to the GPU by the Loader.
glGenVertexArrays(1, &m_vao);
@ -1083,7 +1084,7 @@ Merc2::Draw* Merc2::alloc_normal_draw(const tfrag3::MercDraw& mdraw,
// but don't toggle it the other way?
}
draw->texture = mdraw.eye_id == 0xff ? mdraw.tree_tex_id : (0xffffff00 | mdraw.eye_id);
draw->texture = mdraw.eye_id == 0xff ? mdraw.tree_tex_id : (0xefffff00 | mdraw.eye_id);
draw->first_bone = first_bone;
draw->light_idx = lights;
draw->num_triangles = mdraw.num_triangles;
@ -1233,15 +1234,18 @@ void Merc2::do_draws(const Draw* draw_array,
fog_on = true;
}
bool use_mipmaps_for_filtering = true;
if ((int)draw.texture != last_tex) {
if (draw.texture < lev->textures.size()) {
if (draw.texture != last_tex) {
if (draw.texture < (int)lev->textures.size() && draw.texture >= 0) {
glBindTexture(GL_TEXTURE_2D, lev->textures.at(draw.texture));
} else if ((draw.texture & 0xffffff00) == 0xffffff00) {
} else if ((draw.texture & 0xffffff00) == 0xefffff00) {
auto maybe_eye = render_state->eye_renderer->lookup_eye_texture(draw.texture & 0xff);
if (maybe_eye) {
glBindTexture(GL_TEXTURE_2D, *maybe_eye);
}
use_mipmaps_for_filtering = false;
} else if (draw.texture < 0) {
int slot = -(draw.texture + 1);
glBindTexture(GL_TEXTURE_2D, m_anim_slot_array->at(slot));
} else {
fmt::print("Invalid draw.texture is {}, would have crashed.\n", draw.texture);
}

View file

@ -40,7 +40,7 @@ struct MercDebugStats {
class Merc2 {
public:
Merc2(ShaderLibrary& shaders);
Merc2(ShaderLibrary& shaders, const std::vector<GLuint>* anim_slot_array);
~Merc2();
void draw_debug_window(MercDebugStats* stats);
void render(DmaFollower& dma,
@ -50,6 +50,7 @@ class Merc2 {
static constexpr int kMaxBlerc = 40;
private:
const std::vector<GLuint>* m_anim_slot_array;
enum MercDataMemory {
LOW_MEMORY = 0,
BUFFER_BASE = 442,
@ -182,7 +183,7 @@ class Merc2 {
u32 first_index;
u32 index_count;
DrawMode mode;
u32 texture;
s32 texture;
u32 num_triangles;
u16 first_bone;
u16 light_idx;

View file

@ -249,7 +249,7 @@ void Loader::loader_thread() {
* Load a "common" FR3 file that has non-level textures.
* This should be called during initialization, before any threaded loading goes on.
*/
void Loader::load_common(TexturePool& tex_pool, const std::string& name) {
const tfrag3::Level& Loader::load_common(TexturePool& tex_pool, const std::string& name) {
auto data = file_util::read_binary_file(m_base_path / fmt::format("{}.fr3", name));
auto decomp_data = compression::decompress_zstd(data.data(), data.size());
@ -270,6 +270,7 @@ void Loader::load_common(TexturePool& tex_pool, const std::string& name) {
while (!done) {
done = mls.run(tim, input);
}
return *m_common_level.level;
}
bool Loader::upload_textures(Timer& timer, LevelData& data, TexturePool& texture_pool) {

View file

@ -22,7 +22,7 @@ class Loader {
void update_blocking(TexturePool& tex_pool);
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);
const tfrag3::Level& 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();
void draw_debug_window();

View file

@ -11,13 +11,11 @@ constexpr float LOAD_BUDGET = 2.5f;
*/
u64 add_texture(TexturePool& pool, const tfrag3::Texture& tex, bool is_common) {
GLuint gl_tex;
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &gl_tex);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.w, tex.h, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV,
tex.data.data());
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gl_tex);
glGenerateMipmap(GL_TEXTURE_2D);
float aniso = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso);

View file

@ -71,6 +71,9 @@ FramebufferTexturePair::FramebufferTexturePair(int w, int h, u64 texture_format,
}
FramebufferTexturePair::~FramebufferTexturePair() {
if (m_moved_from) {
return;
}
glDeleteFramebuffers(m_framebuffers.size(), m_framebuffers.data());
glDeleteTextures(1, &m_texture);
}

View file

@ -17,14 +17,35 @@ class FramebufferTexturePair {
GLuint texture() const { return m_texture; }
void update_texture_size(int w, int h) {
m_w = w;
m_h = h;
}
void update_texture_unsafe(GLuint texture) { m_texture = texture; }
FramebufferTexturePair(const FramebufferTexturePair&) = delete;
FramebufferTexturePair& operator=(const FramebufferTexturePair&) = delete;
FramebufferTexturePair(FramebufferTexturePair&& other) {
if (this == &other) {
return;
}
ASSERT(!m_moved_from && !other.m_moved_from);
other.m_moved_from = true;
m_w = other.m_w;
m_h = other.m_h;
m_texture = other.m_texture;
m_framebuffers = std::move(other.m_framebuffers);
}
int width() const { return m_w; }
int height() const { return m_h; }
private:
friend class FramebufferTexturePairContext;
std::vector<GLuint> m_framebuffers;
GLuint m_texture;
int m_w, m_h;
bool m_moved_from = false;
};
class FramebufferTexturePairContext {

View file

@ -6,7 +6,8 @@ in vec4 fragment_color;
in vec3 tex_coord;
flat in uint use_uv;
in vec4 gs_scissor;
uniform float alpha_reject;
uniform float alpha_min;
uniform float alpha_max;
uniform float color_mult;
uniform float alpha_mult;
uniform float alpha_sub;
@ -82,7 +83,7 @@ void main() {
color *= 2;
color.xyz *= color_mult;
color.w *= alpha_mult;
if (color.a < alpha_reject) {
if (color.a < alpha_min || color.a > alpha_max) {
discard;
}
if (tex_info.w == 1) {

View file

@ -0,0 +1,31 @@
#version 430 core
out vec4 color;
uniform vec4 rgba;
uniform int enable_tex;
uniform int tcc;
uniform ivec4 channel_scramble;
in vec2 uv;
layout (binding = 0) uniform sampler2D tex;
void main() {
if (enable_tex == 1) {
vec4 tex_color = texture(tex, uv);
vec4 unscambled_tex = vec4(tex_color[channel_scramble[0]],
tex_color[channel_scramble[1]],
tex_color[channel_scramble[2]],
tex_color[channel_scramble[3]]);
color = rgba / 128.;
if (tcc == 1) {
color *= unscambled_tex;
} else {
color.xyz *= unscambled_tex.xyz;
}
} else {
color = (rgba / 128.);
}
}

View file

@ -0,0 +1,15 @@
#version 430 core
layout (location = 0) in int vertex_index;
uniform vec3 positions[4];
uniform vec2 uvs[4];
uniform vec4 rgba;
// TODO flags and stuff
out vec2 uv;
void main() {
gl_Position = vec4(-1. + (positions[vertex_index] * 2), 1);
uv = uvs[vertex_index];
}

View file

@ -697,7 +697,7 @@ void gl_texture_upload_now(const u8* tpage, int mode, u32 s7_ptr) {
// just pass it to the texture pool.
// the texture pool will take care of locking.
// we don't want to lock here for the entire duration of the conversion.
g_gfx_data->texture_pool->handle_upload_now(tpage, mode, g_ee_main_mem, s7_ptr);
g_gfx_data->texture_pool->handle_upload_now(tpage, mode, g_ee_main_mem, s7_ptr, false);
}
}

View file

@ -25,6 +25,16 @@ void TextureConverter::upload(const u8* data, u32 dest, u32 size_vram_words) {
}
}
void TextureConverter::upload_width(const u8* data, u32 dest, u32 width, u32 height) {
for (u32 y = 0; y < height; y++) {
for (u32 x = 0; x < width; x++) {
// VRAM address (bytes)
auto addr32 = psmct32_addr(x, y, width) + dest * 256;
*(u32*)(m_vram.data() + addr32) = *((const u32*)(data) + (x + y * width));
}
}
}
void TextureConverter::download_rgba8888(u8* result,
u32 vram_addr,
u32 goal_tex_width,
@ -199,7 +209,3 @@ void TextureConverter::download_rgba8888(u8* result,
ASSERT(out_offset == expected_size_bytes);
}
void TextureConverter::serialize(Serializer& ser) {
ser.from_pod_vector(&m_vram);
}

View file

@ -3,12 +3,12 @@
#include <vector>
#include "common/common_types.h"
#include "common/util/Serializer.h"
class TextureConverter {
public:
TextureConverter();
void upload(const u8* data, u32 dest, u32 size_vram_words);
void upload_width(const u8* data, u32 dest, u32 width, u32 height);
void download_rgba8888(u8* result,
u32 vram_addr,
u32 goal_tex_width,
@ -18,7 +18,6 @@ class TextureConverter {
u32 clut_psm,
u32 clut_vram_addr,
u32 expected_size_bytes);
void serialize(Serializer& ser);
private:
std::vector<u8> m_vram;

View file

@ -0,0 +1,15 @@
#pragma once
#include "common/common_types.h"
struct PcTextureId {
u16 page = -1;
u16 tex = -1;
PcTextureId(u16 p, u16 t) : page(p), tex(t) {}
PcTextureId() = default;
static PcTextureId from_combo_id(u32 val) { return PcTextureId(val >> 16, val & 0xffff); }
bool operator==(const PcTextureId& other) const { return page == other.page && tex == other.tex; }
};

View file

@ -110,6 +110,21 @@ void TexturePool::move_existing_to_vram(GpuTexture* tex, u32 slot_addr) {
}
}
void TexturePool::update_gl_texture(GpuTexture* gpu_texture,
u32 new_w,
u32 new_h,
GLuint new_gl_texture) {
ASSERT(gpu_texture->gpu_textures.size() == 1);
gpu_texture->gpu_textures[0].gl = new_gl_texture;
gpu_texture->w = new_w;
gpu_texture->h = new_h;
for (int si : gpu_texture->slots) {
auto& slot = m_textures[si];
ASSERT(slot.source == gpu_texture);
slot.gpu_texture = new_gl_texture;
}
}
void TexturePool::refresh_links(GpuTexture& texture) {
u64 tex_to_use =
texture.is_placeholder ? m_placeholder_texture_id : texture.gpu_textures.front().gl;
@ -174,7 +189,11 @@ void GpuTexture::add_slot(u32 slot) {
* We could store textures in the right format to begin with, or spread the conversion out over
* multiple frames.
*/
void TexturePool::handle_upload_now(const u8* tpage, int mode, const u8* memory_base, u32 s7_ptr) {
void TexturePool::handle_upload_now(const u8* tpage,
int mode,
const u8* memory_base,
u32 s7_ptr,
bool debug) {
std::unique_lock<std::mutex> lk(m_mutex);
// extract the texture-page object. This is just a description of the page data.
GoalTexturePage texture_page;
@ -201,6 +220,12 @@ void TexturePool::handle_upload_now(const u8* tpage, int mode, const u8* memory_
for (int tex_idx = 0; tex_idx < texture_page.length; tex_idx++) {
GoalTexture tex;
if (texture_page.try_copy_texture_description(&tex, tex_idx, memory_base, tpage, s7_ptr)) {
if (debug) {
fmt::print("Pool upload {} to {}\n",
std::string(goal_string(texture_page.name_ptr, memory_base)) +
goal_string(tex.name_ptr, memory_base),
tex.dest[0]);
}
// each texture may have multiple mip levels.
for (int mip_idx = 0; mip_idx < tex.num_mips; mip_idx++) {
if (has_segment[tex.segment_of_mip(mip_idx)]) {

View file

@ -14,6 +14,7 @@
#include "game/graphics/pipelines/opengl.h"
#include "game/graphics/texture/TextureConverter.h"
#include "game/graphics/texture/TextureID.h"
// verify all texture lookups.
// will make texture lookups slower and likely caused dropped frames when loading
@ -72,18 +73,6 @@ constexpr int SKY_TEXTURE_VRAM_ADDRS[2] = {8064, 8096};
* The game will inform us when it uploads to VRAM
*/
struct PcTextureId {
u16 page = -1;
u16 tex = -1;
PcTextureId(u16 p, u16 t) : page(p), tex(t) {}
PcTextureId() = default;
static PcTextureId from_combo_id(u32 val) { return PcTextureId(val >> 16, val & 0xffff); }
bool operator==(const PcTextureId& other) const { return page == other.page && tex == other.tex; }
};
template <typename T>
class TextureMap {
public:
@ -303,10 +292,11 @@ struct GoalTexturePage {
class TexturePool {
public:
TexturePool(GameVersion version);
void handle_upload_now(const u8* tpage, int mode, const u8* memory_base, u32 s7_ptr);
void handle_upload_now(const u8* tpage, int mode, const u8* memory_base, u32 s7_ptr, bool debug);
GpuTexture* give_texture(const TextureInput& in);
GpuTexture* give_texture_and_load_to_vram(const TextureInput& in, u32 vram_slot);
void unload_texture(PcTextureId tex_id, u64 gpu_id);
void update_gl_texture(GpuTexture* texture, u32 new_w, u32 new_h, GLuint new_gl_texture);
/*!
* Look up an OpenGL texture by vram address. Return std::nullopt if the game hasn't loaded

View file

@ -5,4 +5,4 @@
#include "common/common_types.h"
const std::vector<u32>& get_jak1_tpage_dir();
constexpr u32 EXTRA_PC_PORT_TEXTURE_COUNT = 50;
constexpr u32 EXTRA_PC_PORT_TEXTURE_COUNT = 64;

View file

@ -1,5 +1,6 @@
#pragma once
#include <string>
#include <vector>
#include "common/common_types.h"

View file

@ -1,7 +1,11 @@
#include "kdgo.h"
#include "common/global_profiler/GlobalProfiler.h"
#include "common/link_types.h"
#include "common/log/log.h"
#include "common/util/BitUtils.h"
#include "common/util/FileUtil.h"
#include "common/util/Timer.h"
#include "game/kernel/common/Ptr.h"
#include "game/kernel/common/fileio.h"
@ -45,6 +49,76 @@ void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size)
load_and_link_dgo_from_c(name, heap, flag, buffer_size, false);
}
/*!
* Faster version of load_and_link_dgo_from_c that skips the IOP and reads the file directly
* to GOAL memory.
*/
void load_and_link_dgo_from_c_fast(const char* name,
Ptr<kheapinfo> heap,
u32 linkFlag,
s32 bufferSize) {
Timer timer;
lg::debug("[Load and Link DGO From C (fast)] {}", name);
// append CGO if needed
char name_on_cd[16];
kstrcpyup(name_on_cd, name);
if (name_on_cd[strlen(name_on_cd) - 4] != '.') {
strcat(name_on_cd, ".CGO");
}
// open the DGO file:
auto file_path = file_util::get_jak_project_dir() / "out" / game_version_names[g_game_version] /
"iso" / name_on_cd;
auto fp = fopen(file_path.string().c_str(), "rb");
if (!fp) {
lg::die("Failed to open DGO: {}, path {}\n", name, file_path.string());
}
// allocate temporary buffers for linking:
auto old_heap_top = heap->top;
auto buffer1 = kmalloc(heap, bufferSize, KMALLOC_TOP | KMALLOC_ALIGN_64, "dgo-buffer-1");
// read the header
DgoHeader header;
if (fread(&header, sizeof(DgoHeader), 1, fp) != 1) {
lg::die("failed to read dgo header");
}
lg::info("got {} objects, name {}\n", header.object_count, header.name);
// load all but the final
for (int i = 0; i < (int)header.object_count - 1; i++) {
if (fread(buffer1.c(), sizeof(ObjectHeader), 1, fp) != 1) {
lg::die("failed to read object header");
}
auto* obj_header = (ObjectHeader*)buffer1.c();
u32 aligned_size = align16(obj_header->size);
auto* obj_dest = buffer1.c() + sizeof(ObjectHeader);
if (fread(obj_dest, aligned_size, 1, fp) != 1) {
lg::die("Failed to read object data");
}
link_and_exec(buffer1 + sizeof(ObjectHeader), obj_header->name, obj_header->size, heap,
linkFlag, true);
}
auto final_object_dest = Ptr<u8>((heap->current + 0x3f).offset & 0xffffffc0);
if (fread(final_object_dest.c(), sizeof(ObjectHeader), 1, fp) != 1) {
lg::die("failed to read final object header");
}
auto* obj_header = (ObjectHeader*)final_object_dest.c();
u32 aligned_size = align16(obj_header->size);
auto* obj_dest = (final_object_dest + sizeof(ObjectHeader)).c();
if (fread(obj_dest, aligned_size, 1, fp) != 1) {
lg::die("Failed to read object data");
}
link_and_exec(final_object_dest + sizeof(ObjectHeader), obj_header->name, obj_header->size, heap,
linkFlag, true);
heap->top = old_heap_top;
fclose(fp);
lg::info("load_and_link_dgo_from_c_fast took {:.3f} s\n", timer.getSeconds());
}
/*!
* Load and link a DGO file.
* This does not use the mutli-threaded linker and will block until the entire file is done.e
@ -54,6 +128,7 @@ void load_and_link_dgo_from_c(const char* name,
u32 linkFlag,
s32 bufferSize,
bool jump_from_c_to_goal) {
Timer timer;
lg::debug("[Load and Link DGO From C] {}", name);
u32 oldShowStall = sShowStallMsg;
@ -116,6 +191,7 @@ void load_and_link_dgo_from_c(const char* name,
ContinueLoadingDGO(buffer1, buffer2, Ptr<u8>((heap->current + 0x3f).offset & 0xffffffc0));
}
}
lg::info("load_and_link_dgo_from_c took {:.3f} s\n", timer.getSeconds());
sShowStallMsg = oldShowStall;
}

View file

@ -10,4 +10,8 @@ void load_and_link_dgo_from_c(const char* name,
s32 bufferSize,
bool jump_from_c_to_goal);
void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size);
void load_and_link_dgo_from_c_fast(const char* name,
Ptr<kheapinfo> heap,
u32 linkFlag,
s32 bufferSize);
} // namespace jak2

View file

@ -795,9 +795,12 @@ void InitMachineScheme() {
*EnableMethodSet = *EnableMethodSet + 1;
{
auto p = scoped_prof("load-game-dgo");
load_and_link_dgo_from_c("game", kglobalheap,
LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN,
0x400000, true);
// load_and_link_dgo_from_c("game", kglobalheap,
// LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE |
// LINK_FLAG_PRINT_LOGIN, 0x400000, true);
load_and_link_dgo_from_c_fast(
"game", kglobalheap, LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN,
0x400000);
}
*EnableMethodSet = *EnableMethodSet + -1;
@ -833,6 +836,7 @@ void initialize_sql_db() {
// Attempt to open the database
const auto opened = sql_db.open_db(db_path.string());
(void)opened;
fs::path schema_file =
file_util::get_jak_project_dir() / "goal_src" / "jak2" / "tools" / "editable-schema.sql";

View file

@ -139,7 +139,7 @@ auto sceCdCallback(void (*callback)(int)) {
int sceCdRead(int lsn, int num_sectors, void* dest, void* mode) {
(void)mode;
auto do_read = [lsn, num_sectors, dest, mode](s32 thid) {
auto do_read = [lsn, num_sectors, dest](s32 thid) {
// printf("sceCdRead %d, %d -> %p\n", lsn, num_sectors, dest);
ASSERT(gFakeCd.fp);
if (fseek(gFakeCd.fp, lsn * SECTOR_SIZE, SEEK_SET)) {

View file

@ -91,7 +91,7 @@ void KeyboardDevice::clear_actions(std::shared_ptr<PadData> data) {
void KeyboardDevice::process_event(const SDL_Event& event,
const CommandBindingGroups& commands,
std::shared_ptr<PadData> data,
std::shared_ptr<PadData> /*data*/,
std::optional<InputBindAssignmentMeta>& bind_assignment) {
if (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) {
const auto key_event = event.key;

View file

@ -61,7 +61,7 @@ void MouseDevice::poll_state(std::shared_ptr<PadData> data) {
data->update_analog_sim_tracker(false);
ActiveMouseAction action;
action.player_movement = true;
action.revert_action = [](std::shared_ptr<PadData> data, InputBinding bind) {
action.revert_action = [](std::shared_ptr<PadData> data, InputBinding /*bind*/) {
data->analog_data.at(1) += 127; // stop moving forward
data->update_analog_sim_tracker(true);
};

View file

@ -48,7 +48,8 @@ void SubtitleEditor::draw_window() {
}
} else if (m_db_failed_to_load) {
ImGui::PushStyleColor(ImGuiCol_Text, m_error_text_color);
ImGui::Text(fmt::format("Error Loading - {}!", m_subtitle_db.m_load_error.value()).c_str());
ImGui::Text("%s",
fmt::format("Error Loading - {}!", m_subtitle_db.m_load_error.value()).c_str());
ImGui::PopStyleColor();
if (ImGui::Button("Try Again")) {
if (g_game_version == GameVersion::Jak1) {
@ -334,9 +335,10 @@ void SubtitleEditor::draw_all_non_cutscenes(bool base_cutscenes) {
}
}
std::string SubtitleEditor::subtitle_line_summary(const SubtitleLine& line,
const SubtitleLineMetadata& line_meta,
const std::shared_ptr<GameSubtitleBank> bank) {
std::string SubtitleEditor::subtitle_line_summary(
const SubtitleLine& line,
const SubtitleLineMetadata& line_meta,
const std::shared_ptr<GameSubtitleBank> /*bank*/) {
// Truncate the text if it's too long, it's supposed to just be a summary at a glance
std::string line_text = "";
if (!line.text.empty()) {

View file

@ -826,11 +826,11 @@ vf31: cam 0 (premultiplied by hmge)
(moon-dma (the-as sky-work s4-0) s3-0)
(draw-haze (the-as sky-work s4-0) s3-0)
(when (nonzero? *sky-texture-anim-array*) ;; added check
; (draw-clouds (the-as sky-work s4-0) s3-0) ;; DISABLED!
(draw-clouds (the-as sky-work s4-0) s3-0) ;; DISABLED!
)
(draw-base (the-as sky-work s4-0) s3-0)
(when (nonzero? *sky-texture-anim-array*) ;; added check
;(draw-fog (the-as sky-work s4-0) s3-0)
(draw-fog (the-as sky-work s4-0) s3-0)
)
)
)

View file

@ -51,9 +51,20 @@
(t0-1 (/ (-> v1-4 w) 2))
(a3-1 (/ (-> v1-4 h) 2))
)
(upload-vram-data dma-buf (the-as int (-> v1-4 dest 0)) (the-as pointer (-> v1-4 pad 0)) a3-1 t0-1)
;; modified
;(upload-vram-data dma-buf (the-as int (-> v1-4 dest 0)) (the-as pointer (-> v1-4 pad 0)) a3-1 t0-1)
(pc-texture-anim-flag upload-generic-vram dma-buf :qwc 1)
(let ((upload-record (the texture-anim-pc-upload (-> dma-buf base))))
(set! (-> upload-record data) (the-as pointer (-> v1-4 pad 0))) ;; the clut16x16 object
(set! (-> upload-record width) t0-1)
(set! (-> upload-record height) a3-1)
(set! (-> upload-record dest) (-> v1-4 dest 0))
(set! (-> upload-record format) (gs-psm ct32))
)
(&+! (-> dma-buf base) 16)
)
(dma-buffer-add-gs-set dma-buf (texflush 0))
;(dma-buffer-add-gs-set dma-buf (texflush 0)) don't need
(texture-anim-layer-add-shader dma-buf layer 1)
(texture-anim-layer-draw dma-buf width height layer)
)
@ -198,37 +209,64 @@
)
)
(let ((s5-1 (-> arg1 tex)))
(dma-buffer-add-gs-set arg0
(bitbltbuf
(new 'static 'gs-bitbltbuf :dpsm (the-as int (-> s5-1 psm)) :dbp (-> s5-1 dest 0) :dbw (-> s5-1 width 0))
)
(trxpos (new 'static 'gs-trxpos))
(trxreg (new 'static 'gs-trxreg :rrw (-> s5-1 w) :rrh (-> s5-1 h)))
(trxdir (new 'static 'gs-trxdir))
)
(dma-buffer-add-ref-texture arg0 (the-as pointer (-> s5-1 pad 0)) (-> s5-1 w) (-> s5-1 h) (-> s5-1 psm))
(upload-vram-data arg0 (the-as int (-> s5-1 clutdest)) (the-as pointer (-> s5-1 pad 1)) 16 16)
;; they use bitbltbuf directly to upload in the format of the data, rather than
;; the usual PSM32 trick
; (dma-buffer-add-gs-set arg0
; (bitbltbuf
; (new 'static 'gs-bitbltbuf :dpsm (the-as int (-> s5-1 psm)) :dbp (-> s5-1 dest 0) :dbw (-> s5-1 width 0))
; )
; (trxpos (new 'static 'gs-trxpos))
; (trxreg (new 'static 'gs-trxreg :rrw (-> s5-1 w) :rrh (-> s5-1 h)))
; (trxdir (new 'static 'gs-trxdir))
; )
; (dma-buffer-add-ref-texture arg0 (the-as pointer (-> s5-1 pad 0)) (-> s5-1 w) (-> s5-1 h) (-> s5-1 psm))
(pc-texture-anim-flag upload-generic-vram arg0 :qwc 1)
(let ((upload-record (the texture-anim-pc-upload (-> arg0 base))))
(set! (-> upload-record data) (the-as pointer (-> s5-1 pad 0))) ;; the texture data
(set! (-> upload-record width) (-> s5-1 w)) ;; see fog-texture-anim-init, there's not cropping.
(set! (-> upload-record height) (-> s5-1 h))
(set! (-> upload-record dest) (-> s5-1 dest 0))
(set! (-> upload-record format) (-> s5-1 psm))
)
(&+! (-> arg0 base) 16)
;; and the clut
;(upload-vram-data arg0 (the-as int (-> s5-1 clutdest)) (the-as pointer (-> s5-1 pad 1)) 16 16)
(pc-texture-anim-flag upload-clut-16-16 arg0 :qwc 1)
(let ((upload-record (the texture-anim-pc-upload (-> arg0 base))))
(set! (-> upload-record data) (the-as pointer (-> s5-1 pad 1))) ;; the clut16x16 object
(set! (-> upload-record width) 16)
(set! (-> upload-record height) 16)
(set! (-> upload-record dest) (-> s5-1 clutdest))
(set! (-> upload-record format) (gs-psm ct32))
)
(&+! (-> arg0 base) 16)
(set! (-> *texture-pool* ids (shr (-> s5-1 clutdest) 6)) (the-as uint 0))
(let* ((v1-29 arg0)
(a0-51 (the-as object (-> v1-29 base)))
)
(set! (-> (the-as dma-packet a0-51) dma) (new 'static 'dma-tag :qwc #x2 :id (dma-tag-id cnt)))
(set! (-> (the-as dma-packet a0-51) vif0) (new 'static 'vif-tag))
(set! (-> (the-as dma-packet a0-51) vif1) (new 'static 'vif-tag :imm #x2 :cmd (vif-cmd direct) :msk #x1))
(set! (-> v1-29 base) (&+ (the-as pointer a0-51) 16))
)
(let* ((v1-30 arg0)
(a0-53 (the-as object (-> v1-30 base)))
)
(set! (-> (the-as gs-gif-tag a0-53) tag) (new 'static 'gif-tag64 :nloop #x1 :eop #x1 :nreg #x1))
(set! (-> (the-as gs-gif-tag a0-53) regs) GIF_REGS_ALL_AD)
(set! (-> v1-30 base) (&+ (the-as pointer a0-53) 16))
)
(let ((v1-31 (-> arg0 base)))
(set! (-> (the-as (pointer int64) v1-31)) 0)
(set! (-> (the-as (pointer gs-reg64) v1-31) 1) (gs-reg64 texflush))
(set! (-> arg0 base) (&+ v1-31 16))
)
;; just a texflush.
; (let* ((v1-29 arg0)
; (a0-51 (the-as object (-> v1-29 base)))
; )
; (set! (-> (the-as dma-packet a0-51) dma) (new 'static 'dma-tag :qwc #x2 :id (dma-tag-id cnt)))
; (set! (-> (the-as dma-packet a0-51) vif0) (new 'static 'vif-tag))
; (set! (-> (the-as dma-packet a0-51) vif1) (new 'static 'vif-tag :imm #x2 :cmd (vif-cmd direct) :msk #x1))
; (set! (-> v1-29 base) (&+ (the-as pointer a0-51) 16))
; )
; (let* ((v1-30 arg0)
; (a0-53 (the-as object (-> v1-30 base)))
; )
; (set! (-> (the-as gs-gif-tag a0-53) tag) (new 'static 'gif-tag64 :nloop #x1 :eop #x1 :nreg #x1))
; (set! (-> (the-as gs-gif-tag a0-53) regs) GIF_REGS_ALL_AD)
; (set! (-> v1-30 base) (&+ (the-as pointer a0-53) 16))
; )
; (let ((v1-31 (-> arg0 base)))
; (set! (-> (the-as (pointer int64) v1-31)) 0)
; (set! (-> (the-as (pointer gs-reg64) v1-31) 1) (gs-reg64 texflush))
; (set! (-> arg0 base) (&+ v1-31 16))
; )
(let ((v1-34 (shr (-> s5-1 dest 0) 6)))
(dotimes (a0-56 3)
(set! (-> *texture-pool* ids (+ v1-34 a0-56)) (the-as uint 0))

View file

@ -166,10 +166,57 @@
)
)
(defenum texture-anim-pc
(start-anim-array 12)
(finish-anim-array 13)
(erase-and-init 14)
(upload-clut-16-16 15)
(upload-generic-vram 16)
(set-shader 17)
(draw 18)
(move-rg-to-ba 19)
(set-clut-alpha 20)
(copy-clut-alpha 21)
(darkjak 22)
(prison-jak 23)
(oracle-jak 24)
(nest-jak 25)
(kor-transform 26)
)
(deftype texture-anim-pc-upload (structure)
((data pointer)
(width uint16)
(height uint16)
(dest uint32)
(format gs-psm)
(pad uint8 3)
)
:size-assert 16
)
(deftype texture-anim-pc-texture-transform (structure)
((src-tbp uint32)
(dest-tbp uint32)
(pad0 uint32)
(pad1 uint32)
)
)
(defmacro pc-texture-anim-flag (kind buf &key (qwc 0))
`(dma-buffer-add-cnt-vif2
,buf
,qwc
(new 'static 'vif-tag :cmd (vif-cmd pc-port) :imm (texture-anim-pc ,kind))
(new 'static 'vif-tag)
)
)
(defun texture-anim-layer-add-shader ((arg0 dma-buffer) (arg1 texture-anim-layer) (arg2 int))
"Add DMA to set the GS to use the given shader. This can be used to read from the texture."
(let ((s5-0 (-> arg1 tex)))
(if s5-0
(when s5-0
(pc-texture-anim-flag set-shader arg0)
(dma-buffer-add-gs-set arg0
(tex0-1 (new 'static 'gs-tex0
:cld #x1
@ -196,7 +243,8 @@
(defun texture-anim-layer-add-clut-shader ((arg0 dma-buffer) (arg1 texture-anim-layer) (arg2 int))
"Add DMA to set the GS to use the clut as a ct32 texture. This is used to abuse the GS to blend cluts by pretending they are ct32's."
(let ((a1-1 (-> arg1 tex)))
(if a1-1
(when a1-1
(pc-texture-anim-flag set-shader arg0)
(dma-buffer-add-gs-set arg0
(tex0-1 (new 'static 'gs-tex0 :tbw #x1 :tw #x4 :th #x4 :tcc arg2 :tbp0 (-> a1-1 clutdest)))
(tex1-1 (new 'static 'gs-tex1))
@ -215,6 +263,8 @@
Always draws a tristrip with two triangles.
"
(local-vars (v1-27 float) (sv-224 matrix) (sv-228 matrix) (sv-232 matrix) (sv-236 matrix))
(pc-texture-anim-flag draw dma-buf) ;; added
(rlet ((acc :class vf)
(vf0 :class vf)
(vf1 :class vf)
@ -346,6 +396,8 @@
(defun blend-clut-texture-anim-layer-func ((dma-buf dma-buffer) (arg1 uint) (width int) (height int) (layer texture-anim-layer) (time float))
"Apply texture animation layer to the clut. This interpolates the layer, sets up the GS to _read_ from the given texture's
clut as a 16x16 psm32, then does the draw."
(format 0 "before blend-clut-texture-anim-layer-func ~X~%" (-> dma-buf base))
(when (and (>= time (-> layer start-time)) (>= (-> layer end-time) time))
(texture-anim-layer-interp layer time)
(when (!= (-> layer interpolated-color w) 0.0)
@ -353,11 +405,26 @@
(texture-anim-layer-draw dma-buf 16 16 layer)
)
)
(format 0 "after blend-clut-texture-anim-layer-func ~X~%" (-> dma-buf base))
0
)
(defun move-rg-to-ba-texture-anim-layer-func ((dma-buf dma-buffer) (fbp-to-draw uint) (width int) (height int) (layer texture-anim-layer) (time float))
"Some cursed texture drawing."
;; HACK added:
(pc-texture-anim-flag move-rg-to-ba dma-buf :qwc 1)
(let ((transform (the texture-anim-pc-texture-transform (-> dma-buf base))))
(set! (-> transform src-tbp) (-> layer tex dest 0))
(set! (-> transform dest-tbp) (* 32 fbp-to-draw))
(set! (-> transform pad0) 0)
(set! (-> transform pad1) 0)
)
(&+! (-> dma-buf base) 16)
(return 0)
;; end added
(-> layer tex)
(let ((tw (log2 (* width 2)))
(th (log2 height))
@ -503,8 +570,123 @@
)
)
(define-extern *sky-texture-anim-array* (texture-anim-array texture-anim))
(define-extern *darkjak-texture-anim-array* (texture-anim-array texture-anim))
(define-extern *jakb-prison-texture-anim-array* (texture-anim-array texture-anim))
(define-extern *darkjak-hires-texture-anim-array* (texture-anim-array texture-anim))
(define-extern *darkjak-hires-nest-texture-anim-array* (texture-anim-array texture-anim))
(define-extern *kor-transform-texture-anim-array* (texture-anim-array texture-anim))
(defun update-texture-anim ((bucket bucket-id) (anim-array texture-anim-array))
"Generate all DMA to update all textures in the given list for the given bucket."
(cond
((or (= anim-array *sky-texture-anim-array*)
)
;; for sky, we basically emulate the full thing
;; (format *stdcon* "doing sky to bucket ~d~%" bucket)
)
((= anim-array *darkjak-texture-anim-array*)
;; darkjak is simple, and we reimplemented it in C++.
;; so we just have to send the frame-time value.
;; (format *stdcon* "doing darkjak~%")
(with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf))
bucket
)
(pc-texture-anim-flag start-anim-array dma-buf)
(pc-texture-anim-flag darkjak dma-buf :qwc 1)
(let ((morph (-> anim-array array-data 0 frame-time))
(vec (the vector (-> dma-buf base)))
)
(set! (-> vec x) morph)
)
(&+! (-> dma-buf base) 16)
(pc-texture-anim-flag finish-anim-array dma-buf)
)
(return #f)
)
((= anim-array *jakb-prison-texture-anim-array*)
;; prison is simple, and we reimplemented it in C++.
;; so we just have to send the frame-time value.
;; (format *stdcon* "doing prison-jak~%")
(with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf))
bucket
)
(pc-texture-anim-flag start-anim-array dma-buf)
(pc-texture-anim-flag prison-jak dma-buf :qwc 1)
(let ((morph (-> anim-array array-data 0 frame-time))
(vec (the vector (-> dma-buf base)))
)
(set! (-> vec x) morph)
)
(&+! (-> dma-buf base) 16)
(pc-texture-anim-flag finish-anim-array dma-buf)
)
(return #f)
)
((= anim-array *darkjak-hires-texture-anim-array*)
;; oracle is simple, and we reimplemented it in C++.
;; so we just have to send the frame-time value.
;; (format *stdcon* "doing oracle jak~%")
(with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf))
bucket
)
(pc-texture-anim-flag start-anim-array dma-buf)
(pc-texture-anim-flag oracle-jak dma-buf :qwc 1)
(let ((morph (-> anim-array array-data 0 frame-time))
(vec (the vector (-> dma-buf base)))
)
(set! (-> vec x) morph)
)
(&+! (-> dma-buf base) 16)
(pc-texture-anim-flag finish-anim-array dma-buf)
)
(return #f)
)
((= anim-array *darkjak-hires-nest-texture-anim-array*)
;; oracle is simple, and we reimplemented it in C++.
;; so we just have to send the frame-time value.
;; (format *stdcon* "doing nest jak~%")
(with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf))
bucket
)
(pc-texture-anim-flag start-anim-array dma-buf)
(pc-texture-anim-flag nest-jak dma-buf :qwc 1)
(let ((morph (-> anim-array array-data 0 frame-time))
(vec (the vector (-> dma-buf base)))
)
(set! (-> vec x) morph)
)
(&+! (-> dma-buf base) 16)
(pc-texture-anim-flag finish-anim-array dma-buf)
)
(return #f)
)
((= anim-array *kor-transform-texture-anim-array*)
;; kor is simple, and we reimplemented it in C++.
;; so we just have to send the frame-time value.
;; (format *stdcon* "doing kor~%")
(with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf))
bucket
)
(pc-texture-anim-flag start-anim-array dma-buf)
(pc-texture-anim-flag kor-transform dma-buf :qwc 1)
(let ((morph (-> anim-array array-data 0 frame-time))
(vec (the vector (-> dma-buf base)))
)
(set! (-> vec x) morph)
)
(&+! (-> dma-buf base) 16)
(pc-texture-anim-flag finish-anim-array dma-buf)
)
(return #f)
)
(else
(return #f) ;; HACK!!!
)
)
;;
;; (return #f)
@ -521,17 +703,14 @@
bucket
)
;; added: flag for the PC port to indicate start of texture anim data:
(dma-buffer-add-cnt-vif2
dma-buf
0
(new 'static 'vif-tag :cmd (vif-cmd pc-port) :imm 12)
(new 'static 'vif-tag :cmd (vif-cmd pc-port))
)
(pc-texture-anim-flag start-anim-array dma-buf)
;; loop over animated textures. Each will produce a single texture.
(dotimes (anim-idx (-> anim-array length))
(let* ((anim (-> anim-array array-data anim-idx))
(dest-tex (-> anim tex))
)
; (format 0 "texture anim dest tex: ~A~%" dest-tex)
(when dest-tex
0
(let ((tex-width (-> dest-tex w)))
@ -543,7 +722,7 @@
fbp-for-tex
)
(else
;; animating the clut.
;; not really sure why, but for non-mt8h and non-ct32's we clear the clut here...
(set! fbp-for-tex (shr (-> dest-tex clutdest) 5))
(set! tex-width 16)
(set! tex-height 16)
@ -551,7 +730,9 @@
)
)
(when (and (nonzero? tex-width) (nonzero? tex-height))
; (format 0 " clearing~%")
;; configure for drawing to this texture.
(pc-texture-anim-flag erase-and-init dma-buf)
(dma-buffer-add-gs-set-flusha dma-buf
(scissor-1 (new 'static 'gs-scissor :scax1 (+ tex-width -1) :scay1 (+ tex-height -1)))
(xyoffset-1 (new 'static 'gs-xy-offset :ofx #x8000 :ofy #x8000))
@ -605,6 +786,7 @@
(set! layer-idx 0)
(while (< layer-idx (the-as int (-> anim num-layers)))
(let ((layer (-> anim data layer-idx)))
; (format 0 " running layer ~D, dma at #x~X~%" layer-idx (-> dma-buf base))
;; generate DMA for this layer's effect
((-> layer func) dma-buf fbp-for-tex tex-width tex-height layer (-> anim frame-time))
)
@ -612,7 +794,9 @@
)
)
;; some sort of final function (TODO args here)
(if (-> anim func)
(when (-> anim func)
; (format 0 " running final, dma at #x~X~%" layer-idx (-> dma-buf base))
((-> anim func) dma-buf anim)
)
@ -632,13 +816,13 @@
)
;; reset GS registers - we messed with frame/scissor.
(reset-display-gs-state *display* dma-buf)
;(reset-display-gs-state *display* dma-buf)
;; added: flag for the PC port to indicate end of texture anim data:
(dma-buffer-add-cnt-vif2
dma-buf
0
(new 'static 'vif-tag :cmd (vif-cmd pc-port) :imm 13)
(new 'static 'vif-tag :cmd (vif-cmd pc-port) :imm (texture-anim-pc finish-anim-array))
(new 'static 'vif-tag :cmd (vif-cmd pc-port))
)
)
@ -725,6 +909,17 @@
(defun copy-clut-alpha-texture-anim-layer-func ((dma-buf dma-buffer) (fbp-to-draw uint) (width int) (height int) (layer texture-anim-layer) (time float))
"Copy only alpha from the layer source clut."
(pc-texture-anim-flag copy-clut-alpha dma-buf :qwc 1)
(let ((transform (the texture-anim-pc-texture-transform (-> dma-buf base))))
(set! (-> transform src-tbp) (-> layer tex dest 0))
(set! (-> transform dest-tbp) (* 32 fbp-to-draw))
(set! (-> transform pad0) 0)
(set! (-> transform pad1) 0)
)
(&+! (-> dma-buf base) 16)
(return 0)
(when (and (>= time (-> layer start-time)) (>= (-> layer end-time) time))
(texture-anim-layer-interp layer time)
(let ((s4-0 1))
@ -831,6 +1026,19 @@
(defun set-clut-alpha-texture-anim-layer-func ((dma-buf dma-buffer) (fbp-to-draw uint) (width int) (height int) (layer texture-anim-layer) (time float))
"Set clut alpha to 128."
;; PC version
(pc-texture-anim-flag set-clut-alpha dma-buf :qwc 1)
(let ((transform (the texture-anim-pc-texture-transform (-> dma-buf base))))
(set! (-> transform src-tbp) (* 32 fbp-to-draw))
(set! (-> transform dest-tbp) (* 32 fbp-to-draw))
(set! (-> transform pad0) 0)
(set! (-> transform pad1) 0)
)
(&+! (-> dma-buf base) 16)
(return 0)
(let ((v1-0 1))
(dma-buffer-add-gs-set dma-buf
(test-1 (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always)))
@ -1060,7 +1268,18 @@
"Upload the clut for the clouds."
(when (-> arg1 tex)
(make-cloud-clut (the-as (pointer uint32) (-> arg1 tex pad 0)) (-> arg1 extra y) (-> arg1 extra z))
(upload-vram-data arg0 (the-as int (-> arg1 tex clutdest)) (the-as pointer (-> arg1 tex pad 0)) 16 16)
;(upload-vram-data arg0 (the-as int (-> arg1 tex clutdest)) (the-as pointer (-> arg1 tex pad 0)) 16 16)
(pc-texture-anim-flag upload-clut-16-16 arg0 :qwc 1)
(let ((upload-record (the texture-anim-pc-upload (-> arg0 base))))
(set! (-> upload-record data) (the-as pointer (-> arg1 tex pad 0))) ;; the clut16x16 object
(set! (-> upload-record width) 16)
(set! (-> upload-record height) 16)
(set! (-> upload-record dest) (-> arg1 tex clutdest))
(set! (-> upload-record format) (gs-psm ct32))
)
(&+! (-> arg0 base) 16)
(set! (-> *texture-pool* ids (shr (-> arg1 tex clutdest) 6)) (the-as uint 0))
0
)
@ -1202,7 +1421,18 @@
(defun texture-anim-alpha-ramp-clut-upload ((arg0 dma-buffer) (arg1 texture-anim))
(when (-> arg1 tex)
(upload-vram-data arg0 (the-as int (-> arg1 tex clutdest)) (the-as pointer (-> arg1 tex pad 0)) 16 16)
;(upload-vram-data arg0 (the-as int (-> arg1 tex clutdest)) (the-as pointer (-> arg1 tex pad 0)) 16 16)
(pc-texture-anim-flag upload-clut-16-16 arg0 :qwc 1)
(let ((upload-record (the texture-anim-pc-upload (-> arg0 base))))
(set! (-> upload-record data) (the-as pointer (-> arg1 tex pad 0))) ;; the clut16x16 object
(set! (-> upload-record width) 16)
(set! (-> upload-record height) 16)
(set! (-> upload-record dest) (-> arg1 tex clutdest))
(set! (-> upload-record format) (gs-psm ct32))
)
(&+! (-> arg0 base) 16)
(set! (-> *texture-pool* ids (shr (-> arg1 tex clutdest) 6)) (the-as uint 0))
0
)