[sparticle] 2d hud particles (#849)

* wip, taking a break to work on asm stuff first

* the goal code for sparticle

* mips2c the first sparticle asm function

* temp

* particle processing no longer crashing

* temp

* working texture cache for vi1 and hud textures

* sprites

* cleanup 1

* temp

* temp

* add zstd library

* temp

* working

* tests

* include fix

* uncomment

* better decomp of sparticle stuff, part 1

* update references
This commit is contained in:
water111
2021-09-26 11:41:58 -04:00
committed by GitHub
parent d777337095
commit f0ceea8b2e
158 changed files with 19773 additions and 44989 deletions
+4
View File
@@ -24,6 +24,10 @@ savestate-out/
failures/
ee-results.json
# graphics debug
debug_out/*
gfx_dumps/*
# game stuff
game_config/*
imgui.ini
+2 -1
View File
@@ -27,7 +27,8 @@ if(UNIX)
-Woverloaded-virtual \
-Wredundant-decls \
-Wshadow \
-Wsign-promo")
-Wsign-promo \
-fdiagnostics-color=always")
else()
set(CMAKE_CXX_FLAGS "/EHsc")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:10000000")
+52
View File
@@ -78,6 +78,21 @@ class Vector {
return result;
}
Vector<T, Size>& operator+=(const Vector<T, Size>& other) {
for (int i = 0; i < Size; i++) {
m_data[i] += other[i];
}
return *this;
}
Vector<T, Size> elementwise_multiply(const Vector<T, Size>& other) const {
Vector<T, Size> result;
for (int i = 0; i < Size; i++) {
result[i] = m_data[i] * other[i];
}
return result;
}
Vector<T, Size> operator-(const Vector<T, Size>& other) const {
Vector<T, Size> result;
for (int i = 0; i < Size; i++) {
@@ -114,6 +129,13 @@ class Vector {
return result;
}
Vector<T, Size>& operator*=(const T& val) {
for (int i = 0; i < Size; i++) {
m_data[i] *= val;
}
return *this;
}
Vector<T, Size> cross(const Vector<T, Size>& other) const {
static_assert(Size == 3, "Size for cross");
Vector<T, Size> result = {y() * other.z() - z() * other.y(), z() * other.x() - x() * other.z(),
@@ -150,6 +172,34 @@ class Vector {
T m_data[Size];
};
// column major
template <typename T, int Rows, int Cols>
struct Matrix {
Matrix() = default;
static Matrix zero() {
Matrix result;
for (auto& x : result.m_data) {
x = 0;
}
return result;
}
const T& operator()(int r, int c) const { return m_data[c + r * Cols]; }
T& operator()(int r, int c) { return m_data[r + c * Rows]; }
Vector<T, Rows> col(int c) const {
Vector<T, Rows> result;
for (int i = 0; i < Rows; i++) {
result[i] = m_data[c * Rows + i];
}
return result;
}
private:
T m_data[Rows * Cols];
};
template <typename T>
using Vector2 = Vector<T, 2>;
@@ -165,4 +215,6 @@ using Vector4f = Vector4<float>;
using Vector2d = Vector2<double>;
using Vector3d = Vector3<double>;
using Vector4d = Vector4<double>;
using Matrix4f = Matrix<float, 4, 4>;
} // namespace math
+5 -2
View File
@@ -207,9 +207,12 @@ inline u32 rgba16_to_rgba32(u32 in) {
u32 r = (in & 0b11111) * ratio;
u32 g = ((in >> 5) & 0b11111) * ratio;
u32 b = ((in >> 10) & 0b11111) * ratio;
u32 a = (in & 0x8000) * 0x1FE00;
return a | (b << 16) | (g << 8) | r;
// rgba has only 1 bit for a and how it gets converted depends on the value of ta1.
// for now, it looks like they always use 0x80, so this is fine.
u32 a = (in & 0x8000) ? 0x80 : 0;
return (a << 24) | (b << 16) | (g << 8) | r;
}
// texture format enums
+20
View File
@@ -64,3 +64,23 @@ std::optional<int> get_power_of_two(T in) {
bool integer_fits(s64 in, int size, bool is_signed);
u32 float_as_u32(float x);
template <typename T>
T align16(T in) {
return (in + 15) & (~T(15));
}
template <typename T>
T align8(T in) {
return (in + 7) & (~T(7));
}
template <typename T>
T align4(T in) {
return (in + 3) & (~T(3));
}
template <typename T>
T align64(T in) {
return (in + 63) & (~T(63));
}
+219
View File
@@ -0,0 +1,219 @@
#pragma once
#include "common/util/assert.h"
#include <cstring>
#include <string>
#include <vector>
/*!
* The Serializer is a tool to load or save data from a buffer.
* It's currently used to save graphics dumps, but could also be used for savestates in the future.
*
* The serializer can be constructed in either a "save" or a "load" mode, for saving or loading.
* When saving, it copies stuff into a buffer.
* When loading, it copies stuff out of this buffer. This will only work if you do things in
* exactly the same order. To make this easier, most of the commonly used functions can work for
* either saving or loading, so you can have a method like
*
* void MyObject::serialize(Serializer& ser) {
* ser.from_ptr(&m_foo);
* ser.from_ptr(&m_bar);
* }
*
* and it will work for either saving or loading.
*
* Methods that are named like "save_" can only be used in save mode.
*
* Methods like "from_ptr" can work in either save or load mode, and will work in either.
* In general, you can only use most of these on POD, and saving more complicated data structures
* requires special handling.
*/
class Serializer {
public:
/*!
* Construct a serializer in writing mode. This saves data from the program into a buffer that can
* later be accessed with get_save_result.
*/
Serializer() : m_writing(true) {
const size_t initial_size = 32;
m_data = (u8*)malloc(initial_size);
m_size = initial_size;
}
/*!
* Construct a serializer that reads from the given data.
* The data is copied to an internal buffer managed by the serializer, there is no need to keep
* the input data around.
*/
Serializer(const u8* data, size_t size) : m_size(size), m_writing(false) {
m_data = (u8*)malloc(size);
memcpy(m_data, data, size);
}
// don't allow copying, assigning, or move constructing.
Serializer(const Serializer& other) = delete;
Serializer& operator=(const Serializer& other) = delete;
Serializer(Serializer&& other) = delete;
// move assignment is supported.
Serializer& operator=(Serializer&& other) noexcept {
if (this == &other) {
return *this;
}
m_data = other.m_data;
m_size = other.m_size;
m_offset = other.m_offset;
m_writing = other.m_writing;
other.m_data = nullptr;
other.m_size = 0;
return *this;
}
~Serializer() { free(m_data); }
/*!
* Save or load the thing pointed to by ptr.
* T must be POD.
*/
template <typename T>
void from_ptr(T* ptr) {
read_or_write(ptr, sizeof(T));
}
/*!
* Save or load size bytes from ptr.
*/
void from_raw_data(void* ptr, size_t size) { read_or_write(ptr, size); }
/*!
* Load a T. T should be POD.
*/
template <typename T>
T load() {
assert(!m_writing);
T result;
read_or_write(&result, sizeof(T));
return result;
}
/*!
* Save a T. T should be POD.
*/
template <typename T>
void save(const T& thing) {
assert(m_writing);
read_or_write(const_cast<T*>(&thing), sizeof(T));
}
/*!
* Save or load a string.
*/
void from_str(std::string* str) {
// size, then data.
if (is_loading()) {
str->resize(load<size_t>());
} else {
save<size_t>(str->size());
}
from_raw_data(str->data(), str->size());
}
/*!
* Save a std::string. This is just so you can save a const string without needing to copy or do a
* const_cast.
*/
void save_str(const std::string* str) {
assert(is_saving());
// safe, we're saving, so from_str will only read.
from_str(const_cast<std::string*>(str));
}
/*!
* Load a std::string and return it.
*/
std::string load_string() {
assert(is_loading());
std::string s;
from_str(&s);
return s;
}
/*!
* Save or load a vector of POD. This won't work on vectors of vectors, for example.
*/
template <typename T>
void from_pod_vector(std::vector<T>* vec) {
if (is_saving()) {
save<size_t>(vec->size());
} else {
vec->resize(load<size_t>());
}
from_raw_data(vec->data(), vec->size());
}
/*!
* Are we saving?
*/
bool is_saving() const { return m_writing; }
/*!
* Are we loading?
*/
bool is_loading() const { return !m_writing; }
/*!
* Reset a load back to the beginning.
*/
void reset_load() {
assert(is_loading());
m_offset = 0;
}
/*!
* Get the result of the save. This is a view of the buffer owned by the Serializer.
*/
std::pair<const u8*, size_t> get_save_result() {
assert(m_writing);
return {m_data, m_offset};
}
/*!
* Have we reached the end of the load?
*/
bool get_load_finished() const {
assert(!m_writing);
return m_offset == m_size;
}
/*!
* Size of buffer, in bytes.
*/
size_t data_size() const { return m_size; }
private:
/*!
* Main function to read and write the buffer.
*/
void read_or_write(void* data, size_t size) {
if (m_writing) {
// if we would overflow, just resize the buffer.
if (m_offset + size > m_size) {
m_data = (u8*)realloc(m_data, (m_offset + size) * 2);
}
memcpy(m_data + m_offset, data, size);
} else {
// if we would overflow, it's an error.
assert(m_offset + size <= m_size);
memcpy(data, m_data + m_offset, size);
}
m_offset += size;
}
u8* m_data = nullptr;
size_t m_size = 0;
size_t m_offset = 0;
bool m_writing = false;
};
+11 -3
View File
@@ -6,12 +6,16 @@
#include "common/util/assert.h"
namespace compression {
/*!
* Compress data with zstd. There is an 8-byte header containing the decompressed data's size.
*/
std::vector<u8> compress_zstd(const void* data, size_t size) {
auto max_compressed = ZSTD_compressBound(size);
std::vector<u8> result(sizeof(size_t) + max_compressed);
memcpy(result.data(), &size, sizeof(size_t));
auto compressed_size = ZSTD_compress(result.data() + sizeof(size_t), max_compressed, data, size,
ZSTD_CLEVEL_DEFAULT);
auto compressed_size =
ZSTD_compress(result.data() + sizeof(size_t), max_compressed, data, size, 1);
if (ZSTD_isError(compressed_size)) {
printf("ZSTD error: %s\n", ZSTD_getErrorName(compressed_size));
assert(false);
@@ -20,6 +24,10 @@ std::vector<u8> compress_zstd(const void* data, size_t size) {
return result;
}
/*!
* Decompress data with zstd. The first 8-bytes of the data should be a header containing the
* decompressed data's size.
*/
std::vector<u8> decompress_zstd(const void* data, size_t size) {
assert(size >= sizeof(size_t));
size_t decompressed_size;
@@ -37,4 +45,4 @@ std::vector<u8> decompress_zstd(const void* data, size_t size) {
assert(decomp_size == decompressed_size);
return result;
}
} // namespace compression
} // namespace compression
+1
View File
@@ -62,6 +62,7 @@ add_library(
util/data_decompile.cpp
util/DataParser.cpp
util/DecompilerTypeSystem.cpp
util/sparticle_decompile.cpp
util/TP_Type.cpp
VuDisasm/VuDisassembler.cpp
+2 -1
View File
@@ -302,12 +302,13 @@ void init_opcode_info() {
drd_srs_srt(def(IK::PCPYLD, "pcpyld").gpr128()); // Parallel Copy Lower Doubleword
drd_srs_srt(def(IK::PMADDH, "pmaddh").gpr128()); // Parallel Multiply-Add Halfword
drd_srs_srt(def(IK::PMULTH, "pmulth").gpr128()); // Parallel Multiply Halfword
drd_srs_srt(def(IK::PEXEW, "pexew").gpr128()); // Parallel Exchange Even Word
drd_srs_srt(def(IK::PINTEH, "pinteh").gpr128()); // Parallel Interleave Even Halfword
drd_srs_srt(def(IK::PAND, "pand").gpr128()); // Parallel And
drd_srs_srt(def(IK::POR, "por").gpr128()); // Parallel Or
drd_srs_srt(def(IK::PNOR, "pnor").gpr128()); // Parallel Not Or
def(IK::PEXEW, "pexew").gpr128().dst_gpr(FT::RD).src_gpr(FT::RT); // Parallel Exchange Even Word
drd_srt_ssa(def(IK::PSLLW, "psllw").gpr128()); // Parallel Shift Left Logical Word
drd_srt_ssa(def(IK::PSLLH, "psllh").gpr128()); // Parallel Shift Left Logical Halfword
drd_srt_ssa(def(IK::PSRAW, "psraw").gpr128()); // Parallel Shift Right Arithmetic Word
+34 -1
View File
@@ -59,6 +59,18 @@ void CfgVtx::replace_succ_and_check(CfgVtx* old_succ, CfgVtx* new_succ) {
assert(replaced);
}
void CfgVtx::remove_pred(CfgVtx* to_remove) {
bool found = false;
for (auto it = pred.begin(); it != pred.end(); it++) {
if (*it == to_remove) {
pred.erase(it);
found = true;
break;
}
}
assert(found);
}
/*!
* Replace references to old_preds with a single new_pred.
* Doesn't insert duplicates.
@@ -477,7 +489,15 @@ bool ControlFlowGraph::is_while_loop(CfgVtx* b0, CfgVtx* b1, CfgVtx* b2) {
if (!b0 || !b1 || !b2)
return false;
bool debug = b0->to_string() == "Seq CONDNE104 ... Block 18100";
if (debug) {
fmt::print("try while: {} | {} | {}\n", b0->to_string(), b1->to_string(), b2->to_string());
}
if (b0->end_branch.asm_branch || b1->end_branch.asm_branch) {
if (debug)
fmt::print("reject 1 {} {}\n", b0->end_branch.asm_branch, b1->end_branch.asm_branch);
return false;
}
@@ -1101,10 +1121,19 @@ bool ControlFlowGraph::clean_up_asm_branches() {
return true;
}
if (!b0->end_branch.asm_branch) {
if (!b0->end_branch.asm_branch || !b0->end_branch.has_branch) {
return true;
}
if (b1->succ_branch == b1) {
// asm branch to yourself. just remove it.
b1->succ_branch = nullptr;
b1->end_branch.has_branch = false;
b1->remove_pred(b1);
replaced = true;
return false;
}
// don't want to combine two with an incoming edge in between.
if (b1->pred.size() > 1) {
return true;
@@ -2094,6 +2123,10 @@ bool ControlFlowGraph::find_cond_n_else() {
return true;
}
if (prev_condition->end_branch.asm_branch) {
return true;
}
// prev_body should fall through to end todo - this was wrong?
if (prev_body->succ_ft != end_block) {
printf("reject 7\n");
+2
View File
@@ -127,6 +127,8 @@ class CfgVtx {
void replace_succ_and_check(CfgVtx* old_succ, CfgVtx* new_succ);
void replace_preds_with_and_check(std::vector<CfgVtx*> old_preds, CfgVtx* new_pred);
void remove_pred(CfgVtx* to_remove);
std::string links_to_string();
};
+1 -12
View File
@@ -8,6 +8,7 @@
#include "TypeInspector.h"
#include "decompiler/IR/IR.h"
#include "decompiler/IR2/Form.h"
#include "common/util/BitUtils.h"
namespace decompiler {
namespace {
@@ -30,18 +31,6 @@ Register get_expected_fpr_backup(int n, int total) {
return fpr_backups.at((total - 1) - n);
}
uint32_t align16(uint32_t in) {
return (in + 15) & (~15);
}
uint32_t align8(uint32_t in) {
return (in + 7) & (~7);
}
uint32_t align4(uint32_t in) {
return (in + 3) & (~3);
}
} // namespace
Function::Function(int _start_word, int _end_word) : start_word(_start_word), end_word(_end_word) {
+14
View File
@@ -6,6 +6,7 @@
#include "decompiler/util/DecompilerTypeSystem.h"
#include "decompiler/IR2/bitfields.h"
#include "common/type_system/state.h"
#include "common/util/BitUtils.h"
namespace decompiler {
@@ -319,6 +320,7 @@ TP_Type get_stack_type_at_constant_offset(int offset,
throw std::runtime_error(
fmt::format("Failed to find a stack variable or structure at offset {}", offset));
}
} // namespace
/*!
@@ -601,9 +603,16 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input,
if (m_kind == Kind::ADD && tc(dts, TypeSpec("structure"), arg0_type) &&
arg1_type.is_integer_constant()) {
auto type_info = dts.ts.lookup_type(arg0_type.typespec());
// get next in memory, allow this as &+
if ((u64)type_info->get_size_in_memory() == arg1_type.get_integer_constant()) {
return TP_Type::make_from_ts(arg0_type.typespec());
}
// also allow it, if 16-byte aligned stride.
if ((u64)align16(type_info->get_size_in_memory()) == arg1_type.get_integer_constant()) {
return TP_Type::make_from_ts(arg0_type.typespec());
}
}
if (tc(dts, TypeSpec("structure"), arg1_type) && !m_args[0].is_int() &&
@@ -663,6 +672,11 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input,
}
}
// allow shifting stuff for setting bitfields
if (m_kind == Kind::LEFT_SHIFT) {
return TP_Type::make_from_ts("int");
}
throw std::runtime_error(fmt::format("Cannot get_type_int2: {}, args {} and {}",
to_form(env.file->labels, env).print(), arg0_type.print(),
arg1_type.print()));
@@ -10,6 +10,7 @@
#include "decompiler/config.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "common/link_types.h"
#include "common/util/BitUtils.h"
namespace decompiler {
// There are three link versions:
@@ -197,14 +198,6 @@ static uint32_t c_symlink3(LinkedObjectFile& f,
return link_ptr + 1;
}
static uint32_t align64(uint32_t in) {
return (in + 63) & (~63);
}
static uint32_t align16(uint32_t in) {
return (in + 15) & (~15);
}
/*!
* Process link data for a "V4" or "V2" object file.
* In reality a V4 seems to be just a V2 object, but with the link data after the real data.
+1 -1
View File
@@ -126,7 +126,7 @@ VuDisassembler::VuDisassembler() {
add_op(VuInstrK::MUL, "mul").iemdt().dst_mask().dss_fd_fs_ft();
add_op(VuInstrK::MULq, "mul").iemdt().dst_mask().dst_vfd().src_vfs().src_q().vft_zero();
add_op(VuInstrK::SUB, "sub").iemdt().dst_mask().dss_fd_fs_ft();
add_op(VuInstrK::MSUBbc, "msub").iemdt().dst_mask().dss_fd_fs_ft();
add_op(VuInstrK::MSUBbc, "msub").iemdt().dst_mask().dss_fd_fs_ft().bc();
add_op(VuInstrK::MADDA, "madda").iemdt().dst_mask().dst_acc().src_vfs().src_vft();
add_op(VuInstrK::MULA, "mula").iemdt().dst_mask().dst_acc().src_vfs().src_vft();
add_op(VuInstrK::MINIbc, "mini").iemdt().dst_mask().bc().dss_fd_fs_ft();
+297 -47
View File
@@ -1,12 +1,31 @@
#include <set>
#include "mips2c.h"
#include "common/util/print_float.h"
#include "decompiler/Disasm/InstructionMatching.h"
#include "decompiler/Function/Function.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
/*!
* Mips2C:
* The mips2c analysis pass converts mips assembly into C code. It is a very literal translation.
* This relies on the helper functions in mips2c_private.h header.
*
* We generate a "link" function and an "execute" function. The "link" function performs symbol
* table lookups and saves the address of the slots in a cache structure used during runtime. It
* also allocates a stub function on the GOAL heap that jumps to the C++ function in the proper way.
*
* The "link" function should be called by the linker when the appropriate object file is linked.
* It should happen after GOAL linking, but before executing the top-level segment.
* This _only_ allocates a function, but doesn't set the symbol.
* You have to do that yourself, in the top level.
* This order seems weird and annoying, but it makes sure that we get the order of allocations
* right. It's likely that nothing depends on this, but better to be safe.
*
* The "execute" function is the function that should be called from GOAL.
*/
namespace decompiler {
//////////////////////
@@ -45,6 +64,10 @@ Register make_vf(int idx) {
return Register(Reg::VF, idx);
}
/*!
* Convert a GOAL symbol name to a valid C++ variable name.
* dashes become underscores, and !/?/ * are dropped.
*/
std::string goal_to_c_name(const std::string& name) {
std::string result;
for (auto c : name) {
@@ -61,6 +84,9 @@ std::string goal_to_c_name(const std::string& name) {
return result;
}
/*!
* Convert a decompiler function name to a valid C++ variable name.
*/
std::string goal_to_c_function_name(const FunctionName& name) {
switch (name.kind) {
case FunctionName::FunctionKind::GLOBAL:
@@ -70,11 +96,17 @@ std::string goal_to_c_function_name(const FunctionName& name) {
}
}
/*!
* Convert a decompiler register into the name of the register constant in mips2c_private.h
*/
const char* reg_to_name(const InstructionAtom& atom) {
assert(atom.is_reg());
return atom.get_reg().to_charp();
}
/*!
* A line of code in the mips2c function output. Just code + end of line comment.
*/
struct Mips2C_Line {
std::string code;
std::string comment;
@@ -85,14 +117,32 @@ struct Mips2C_Line {
: code(_code), comment(_comment) {}
};
/*!
* Mips2C output. Contains the execute and link function.
* This is built up in the order of MIPS instructions/labels/comments using the output_* functions.
* The output is built by write_to_string.
*/
struct Mips2C_Output {
/*!
* Add a label at the current line.
*/
void output_label(int block_idx) { lines.push_back(fmt::format("\nblock_{}:", block_idx)); }
/*!
* Add a full line comment at the current line. Includes "//" automatically
*/
void output_line_comment(const std::string& text) { lines.emplace_back("// " + text); }
/*!
* Output code and comment for an instruction.
*/
void output_instr(const std::string& instr, const std::string& comment) {
lines.emplace_back(instr, comment);
}
/*!
* Convert the output to a string.
*/
std::string write_to_string(const FunctionName& goal_func_name) const {
std::string name = goal_to_c_function_name(goal_func_name);
std::string result = "//--------------------------MIPS2C---------------------\n";
@@ -103,6 +153,7 @@ struct Mips2C_Output {
result += "namespace Mips2C {\n";
result += fmt::format("namespace {} {{\n", name);
// definition of the symbol cache.
if (!symbol_cache.empty()) {
result += "struct Cache {\n";
for (auto& sym : symbol_cache) {
@@ -111,9 +162,23 @@ struct Mips2C_Output {
result += "} cache;\n\n";
}
// definition of the function
// the mips2c_call function will build and pass an ExecutionContext
result += "u64 execute(void* ctxt) {\n";
result += " auto* c = (ExecutionContext*)ctxt;\n";
result += " bool bc = false;";
// the branch condition (for delay slots)
result += " bool bc = false;\n";
// the function call address (for jalr delay slots)
result += " u32 call_addr = 0;\n";
if (needs_cop1_bc) {
// the cop1 branch flag (separate from delay slot bc).
result += " bool cop1_bc = false;\n";
}
// add all lines
for (auto& line : lines) {
result += " ";
result += line.code;
@@ -130,14 +195,17 @@ struct Mips2C_Output {
result += '\n';
}
// return!
result += "end_of_function:\n return c->gprs[v0].du64[0];\n";
result += "}\n\n";
// link function:
result += "void link() {\n";
// lookup all symbols
for (auto& sym : symbol_cache) {
result += fmt::format(" cache.{} = intern_from_c(\"{}\").c();\n", goal_to_c_name(sym), sym);
}
// this adds us to a table for lookup later, and also allocates our trampoline.
result +=
fmt::format(" gLinkedFunctionTable.reg(\"{}\", execute);\n", goal_func_name.to_string());
result += "}\n\n";
@@ -145,6 +213,7 @@ struct Mips2C_Output {
result += fmt::format("}} // namespace {}\n", name);
result += "} // namespace Mips2C\n";
// reminder to the user to add a callback to the link function in the linker.
result +=
fmt::format("// add {}::link to the link callback table for the object file.\n", name);
result += "// FWD DEC:\n";
@@ -152,24 +221,31 @@ struct Mips2C_Output {
return result;
}
/*!
* Adds name to the symbol cache, if it's not there already.
*/
void require_symbol(const std::string& name) { symbol_cache.insert(name); }
std::vector<Mips2C_Line> lines;
std::set<std::string> symbol_cache;
bool needs_cop1_bc = false;
};
/*!
* Basic block used for mips2c.
*/
struct M2C_Block {
int idx = -1;
int succ_branch = -1;
int succ_ft = -1;
std::vector<int> pred;
int idx = -1; // block idx
int succ_branch = -1; // block idx if we take the branch
int succ_ft = -1; // block idx if we don't take the branch (or there is none)
std::vector<int> pred; // block idx of predecessors
int start_instr = -1;
int end_instr = -1;
int start_instr = -1; // first instruction idx
int end_instr = -1; // last instruction idx (not inclusive)
bool has_branch = false;
bool branch_likely = false;
bool branch_always = false;
bool has_branch = false; // ends in a branch instruction?
bool branch_likely = false; // that branch is likely branch?
bool branch_always = false; // that branch is always taken?
bool has_pred(int pidx) const {
for (auto p : pred) {
@@ -181,6 +257,9 @@ struct M2C_Block {
}
};
/*!
* Make second_idx be a fallthrough of first_idx.
*/
void link_fall_through(int first_idx, int second_idx, std::vector<M2C_Block>& blocks) {
auto& first = blocks.at(first_idx);
auto& second = blocks.at(second_idx);
@@ -198,6 +277,9 @@ void link_fall_through(int first_idx, int second_idx, std::vector<M2C_Block>& bl
}
}
/*!
* Make second_idx be the branch destination of first_idx.
*/
void link_branch(int first_idx, int second_idx, std::vector<M2C_Block>& blocks) {
auto& first = blocks.at(first_idx);
auto& second = blocks.at(second_idx);
@@ -210,6 +292,9 @@ void link_branch(int first_idx, int second_idx, std::vector<M2C_Block>& blocks)
}
}
/*!
* Make second_idx be the fall through of a likely branch (after the delay slot)
*/
void link_fall_through_likely(int first_idx, int second_idx, std::vector<M2C_Block>& blocks) {
auto& first = blocks.at(first_idx);
auto& second = blocks.at(second_idx);
@@ -236,7 +321,9 @@ void link_fall_through_likely(int first_idx, int second_idx, std::vector<M2C_Blo
* Otherwise, has_branch is true, and the succ_branch is taken if the branch condition is true.
* The succ_ft and succ_branch may be _anywhere_. succ_ft may not always be the next destination.
*/
std::vector<M2C_Block> setup_preds_and_succs(const Function& func, const LinkedObjectFile& file) {
std::vector<M2C_Block> setup_preds_and_succs(const Function& func,
const LinkedObjectFile& file,
std::unordered_set<int>& likely_delay_slot_blocks) {
// create m2c blocks
std::vector<M2C_Block> blocks;
blocks.resize(func.basic_blocks.size());
@@ -249,7 +336,13 @@ std::vector<M2C_Block> setup_preds_and_succs(const Function& func, const LinkedO
// set up succ / pred
for (int i = 0; i < int(func.basic_blocks.size()); i++) {
auto& b = func.basic_blocks[i];
assert(!blocks.at(i).branch_always);
if (blocks.at(i).branch_always) {
// likely branch, already set up.
assert(likely_delay_slot_blocks.count(i));
continue;
} else {
assert(!likely_delay_slot_blocks.count(i));
}
bool not_last = (i + 1) < int(func.basic_blocks.size());
if (b.end_word == b.start_word) {
@@ -306,7 +399,8 @@ std::vector<M2C_Block> setup_preds_and_succs(const Function& func, const LinkedO
delay_block.branch_likely = false;
delay_block.branch_always = true;
delay_block.has_branch = true;
// delay_block.kind = CfgVtx::DelaySlotKind::NO_DELAY;
auto inserted = likely_delay_slot_blocks.insert(i + 1).second;
assert(inserted);
link_branch(i + 1, block_target, blocks);
} else {
@@ -320,11 +414,9 @@ std::vector<M2C_Block> setup_preds_and_succs(const Function& func, const LinkedO
int idx = b.end_word - 2;
assert(idx >= b.start_word);
auto& branch_candidate = func.instructions.at(idx);
// auto& delay_slot_candidate = func.instructions.at(idx + 1);
if (is_branch(branch_candidate, false)) {
blocks.at(i).has_branch = true;
blocks.at(i).branch_likely = false;
// blocks.at(i).kind = get_delay_slot(delay_slot_candidate);
bool branch_always = is_always_branch(branch_candidate);
// need to find block target
@@ -337,10 +429,6 @@ std::vector<M2C_Block> setup_preds_and_succs(const Function& func, const LinkedO
int offset = label.offset / 4 - func.start_word;
assert(offset >= 0);
// the order here matters when there are zero size blocks. Unclear what the best answer
// is.
// i think in end it doesn't actually matter??
// for (int j = 0; j < int(func.basic_blocks.size()); j++) {
for (int j = int(func.basic_blocks.size()); j-- > 0;) {
if (func.basic_blocks[j].start_word == offset) {
block_target = j;
@@ -373,6 +461,9 @@ std::vector<M2C_Block> setup_preds_and_succs(const Function& func, const LinkedO
return blocks;
}
/*!
* Does the given block require a label in front of it?
*/
bool block_requires_label(const Function* f,
const std::vector<M2C_Block>& blocks,
size_t block_idx) {
@@ -388,26 +479,46 @@ bool block_requires_label(const Function* f,
if (block.pred.size() == 1 && block_idx > 0 && block.pred.front() == (int)block_idx - 1 &&
blocks.at(block_idx - 1).succ_ft == (int)block_idx) {
// the only way to get to this block is to fall through.
// the only way to get to this block is to fall through, no need for a label.
return false;
}
return true;
}
namespace {
// hack counter for total number of unknown instruction. TODO remove
int g_unknown = 0;
} // namespace
/*!
* Complain about an unknown instruction.
*/
Mips2C_Line handle_unknown(const std::string& instr_str) {
g_unknown++;
lg::warn("mips2c unknown: {}", instr_str);
return fmt::format("// Unknown instr: {}", instr_str);
}
Mips2C_Line handle_generic_load(const Instruction& i0, const std::string& instr_str) {
if (i0.get_src(1).is_reg(rsp())) {
return handle_unknown(instr_str);
} else {
return {fmt::format("c->{}({}, {}, {});", i0.op_name_to_string(), reg_to_name(i0.get_dst(0)),
i0.get_src(0).get_imm(), reg_to_name(i0.get_src(1))),
return {fmt::format("c->{}({}, {}, {});", i0.op_name_to_string(), reg_to_name(i0.get_dst(0)),
i0.get_src(0).get_imm(), reg_to_name(i0.get_src(1))),
instr_str};
}
Mips2C_Line handle_lwc1(const Instruction& i0,
const std::string& instr_str,
const LinkedObjectFile* file) {
if (i0.get_src(0).is_label() && i0.get_src(1).is_reg(Register(Reg::GPR, Reg::FP))) {
auto& label = file->labels.at(i0.get_src(0).get_label());
auto& word = file->words_by_seg.at(label.target_segment).at(label.offset / 4);
assert(word.kind == LinkedWord::PLAIN_DATA);
float f;
memcpy(&f, &word.data, 4);
return {fmt::format("c->fprs[{}] = {};", reg_to_name(i0.get_dst(0)), float_to_string(f)),
instr_str};
} else {
return handle_generic_load(i0, instr_str);
}
}
@@ -428,13 +539,9 @@ Mips2C_Line handle_lw(Mips2C_Output& out, const Instruction& i0, const std::stri
Mips2C_Line handle_generic_store(Mips2C_Output& /*out*/,
const Instruction& i0,
const std::string& instr_str) {
if (i0.get_src(2).is_reg(Register(Reg::GPR, Reg::SP))) {
return handle_unknown(instr_str);
} else {
return {fmt::format("c->{}({}, {}, {});", i0.op_name_to_string(), reg_to_name(i0.get_src(0)),
i0.get_src(1).get_imm(), reg_to_name(i0.get_src(2))),
instr_str};
}
return {fmt::format("c->{}({}, {}, {});", i0.op_name_to_string(), reg_to_name(i0.get_src(0)),
i0.get_src(1).get_imm(), reg_to_name(i0.get_src(2))),
instr_str};
}
Mips2C_Line handle_generic_op2_u16(const Instruction& i0, const std::string& instr_str) {
@@ -528,10 +635,12 @@ Mips2C_Line handle_generic_op2(const Instruction& i0,
Mips2C_Line handle_or(const Instruction& i0, const std::string& instr_str) {
if (is_gpr_3(i0, InstructionKind::OR, {}, rs7(), rr0())) {
// set reg_dest to #f : or reg_dest, s7, r0
return handle_unknown(instr_str);
return {
fmt::format("c->mov64({}, {});", reg_to_name(i0.get_dst(0)), reg_to_name(i0.get_src(0))),
instr_str};
} else if (is_gpr_3(i0, InstructionKind::OR, {}, rr0(), rr0())) {
// set reg_dest to 0 : or reg_dest, r0, r0
return handle_unknown(instr_str);
return {fmt::format("c->gprs[{}].du64[0] = 0;", reg_to_name(i0.get_dst(0))), instr_str};
} else if (is_gpr_3(i0, InstructionKind::OR, {}, {}, rr0())) {
// set dst to src : or dst, src, r0
return {
@@ -589,6 +698,25 @@ Mips2C_Line handle_non_likely_branch_bc(const Instruction& i0, const std::string
return {fmt::format("bc = ((s64){}) < 0;", reg64_or_zero(i0.get_src(0))), instr_str};
case InstructionKind::BGTZ:
return {fmt::format("bc = ((s64){}) > 0;", reg64_or_zero(i0.get_src(0))), instr_str};
case InstructionKind::BGEZ:
return {fmt::format("bc = ((s64){}) >= 0;", reg64_or_zero(i0.get_src(0))), instr_str};
case InstructionKind::BLEZ:
return {fmt::format("bc = ((s64){}) <= 0;", reg64_or_zero(i0.get_src(0))), instr_str};
case InstructionKind::BC1F:
return {fmt::format("bc = !cop1_bc;"), instr_str};
default:
return handle_unknown(instr_str);
}
}
Mips2C_Line handle_likely_branch_bc(const Instruction& i0, const std::string& instr_str) {
switch (i0.kind) {
case InstructionKind::BLTZL:
return {fmt::format("((s64){}) < 0", reg64_or_zero(i0.get_src(0))), instr_str};
case InstructionKind::BNEL:
return {fmt::format("((s64){}) != ((s64){})", reg64_or_zero(i0.get_src(0)),
reg64_or_zero(i0.get_src(1))),
instr_str};
default:
return handle_unknown(instr_str);
}
@@ -601,6 +729,29 @@ Mips2C_Line handle_vdiv(const Instruction& i0, const std::string& instr_string)
instr_string};
}
Mips2C_Line handle_vsqrt(const Instruction& i0, const std::string& instr_string) {
return {fmt::format("c->vsqrt({}, BC::{});", reg_to_name(i0.get_src(0)),
"xyzw"[i0.get_src(1).get_vf_field()]),
instr_string};
}
Mips2C_Line handle_vrxor(const Instruction& i0, const std::string& instr_string) {
return {fmt::format("c->vrxor({}, BC::{});", reg_to_name(i0.get_src(0)), i0.cop2_bc_to_char()),
instr_string};
}
Mips2C_Line handle_vrget(const Instruction& i0, const std::string& instr_string) {
return {fmt::format("c->vrget(DEST::{}, {});", dest_to_char(i0.cop2_dest),
reg_to_name(i0.get_dst(0))),
instr_string};
}
Mips2C_Line handle_vrnext(const Instruction& i0, const std::string& instr_string) {
return {fmt::format("c->vrnext(DEST::{}, {});", dest_to_char(i0.cop2_dest),
reg_to_name(i0.get_dst(0))),
instr_string};
}
Mips2C_Line handle_por(const Instruction& i0, const std::string& instr_string) {
if (is_gpr_3(i0, InstructionKind::POR, {}, {}, rr0())) {
return {fmt::format("c->mov128_gpr_gpr({}, {});", reg_to_name(i0.get_dst(0)),
@@ -611,10 +762,22 @@ Mips2C_Line handle_por(const Instruction& i0, const std::string& instr_string) {
}
}
Mips2C_Line handle_lui(const Instruction& i0, const std::string& instr_string) {
return {fmt::format("c->lui({}, {});", reg_to_name(i0.get_dst(0)), i0.get_src(0).get_imm()),
instr_string};
}
Mips2C_Line handle_clts(const Instruction& i0, const std::string& instr_string) {
return {fmt::format("cop1_bc = c->fprs[{}] < c->fprs[{}];", reg_to_name(i0.get_src(0)),
reg_to_name(i0.get_src(1))),
instr_string};
}
Mips2C_Line handle_normal_instr(Mips2C_Output& output,
const Instruction& i0,
const std::string& instr_str,
int& unknown_count) {
int& unknown_count,
const LinkedObjectFile* file) {
switch (i0.kind) {
case InstructionKind::LW:
return handle_lw(output, i0, instr_str);
@@ -622,12 +785,24 @@ Mips2C_Line handle_normal_instr(Mips2C_Output& output,
case InstructionKind::LWU:
case InstructionKind::LQ:
case InstructionKind::LQC2:
case InstructionKind::LH:
case InstructionKind::LHU:
case InstructionKind::LD:
return handle_generic_load(i0, instr_str);
case InstructionKind::LWC1:
return handle_lwc1(i0, instr_str, file);
case InstructionKind::SQ:
case InstructionKind::SQC2:
case InstructionKind::SH:
case InstructionKind::SD:
case InstructionKind::SWC1:
return handle_generic_store(output, i0, instr_str);
case InstructionKind::VADD_BC:
return handle_generic_op3_bc_mask(i0, instr_str, "vadd_bc");
case InstructionKind::VMINI_BC:
return handle_generic_op3_bc_mask(i0, instr_str, "vmini_bc");
case InstructionKind::VMAX_BC:
return handle_generic_op3_bc_mask(i0, instr_str, "vmax_bc");
case InstructionKind::VSUB_BC:
return handle_generic_op3_bc_mask(i0, instr_str, "vsub_bc");
case InstructionKind::VMUL_BC:
@@ -646,21 +821,43 @@ Mips2C_Line handle_normal_instr(Mips2C_Output& output,
return handle_generic_op2_mask(i0, instr_str, "vmove");
case InstructionKind::VITOF0:
return handle_generic_op2_mask(i0, instr_str, "vitof0");
case InstructionKind::VFTOI0:
return handle_generic_op2_mask(i0, instr_str, "vftoi0");
case InstructionKind::VFTOI4:
return handle_generic_op2_mask(i0, instr_str, "vftoi4");
case InstructionKind::VADDQ:
return handle_generic_op2_mask(i0, instr_str, "vaddq");
case InstructionKind::ANDI:
case InstructionKind::ORI:
case InstructionKind::SRA:
case InstructionKind::DSLL:
case InstructionKind::DSLL32:
case InstructionKind::DSRA:
case InstructionKind::DSRA32:
return handle_generic_op2_u16(i0, instr_str);
case InstructionKind::SLL:
return handle_sll(i0, instr_str);
case InstructionKind::DADDU:
case InstructionKind::DSUBU:
case InstructionKind::ADDU:
case InstructionKind::PEXTLH:
case InstructionKind::PEXTLB:
case InstructionKind::MOVN:
case InstructionKind::PEXTUW:
case InstructionKind::PCPYUD:
case InstructionKind::MOVZ:
case InstructionKind::MULT3:
case InstructionKind::PMINW:
case InstructionKind::PMAXW:
return handle_generic_op3(i0, instr_str, {});
case InstructionKind::MULS:
return handle_generic_op3(i0, instr_str, "muls");
case InstructionKind::ADDS:
return handle_generic_op3(i0, instr_str, "adds");
case InstructionKind::SUBS:
return handle_generic_op3(i0, instr_str, "subs");
case InstructionKind::XOR:
return handle_generic_op3(i0, instr_str, "xor_");
case InstructionKind::AND:
return handle_generic_op3(i0, instr_str, "and_"); // and isn't allowed in C++
case InstructionKind::DADDIU:
@@ -678,6 +875,14 @@ Mips2C_Line handle_normal_instr(Mips2C_Output& output,
return handle_generic_op3_bc_mask(i0, instr_str, "vmadd_bc");
case InstructionKind::VDIV:
return handle_vdiv(i0, instr_str);
case InstructionKind::VSQRT:
return handle_vsqrt(i0, instr_str);
case InstructionKind::VRXOR:
return handle_vrxor(i0, instr_str);
case InstructionKind::VRGET:
return handle_vrget(i0, instr_str);
case InstructionKind::VRNEXT:
return handle_vrnext(i0, instr_str);
case InstructionKind::POR:
return handle_por(i0, instr_str);
case InstructionKind::VMULQ:
@@ -686,6 +891,21 @@ Mips2C_Line handle_normal_instr(Mips2C_Output& output,
return {"// nop", instr_str};
case InstructionKind::MFC1:
return handle_generic_op2(i0, instr_str, "mfc1");
case InstructionKind::MTC1:
return handle_generic_op2(i0, instr_str, "mtc1");
case InstructionKind::CVTWS:
return handle_generic_op2(i0, instr_str, "cvtws");
case InstructionKind::CVTSW:
return handle_generic_op2(i0, instr_str, "cvtsw");
case InstructionKind::PEXEW:
return handle_generic_op2(i0, instr_str, "pexew");
case InstructionKind::SQRTS:
return handle_generic_op2(i0, instr_str, "sqrts");
case InstructionKind::LUI:
return handle_lui(i0, instr_str);
case InstructionKind::CLTS:
output.needs_cop1_bc = true;
return handle_clts(i0, instr_str);
default:
unknown_count++;
return handle_unknown(instr_str);
@@ -697,17 +917,20 @@ Mips2C_Line handle_normal_instr(Mips2C_Output& output,
void run_mips2c(Function* f) {
g_unknown = 0;
auto* file = f->ir2.env.file;
auto blocks = setup_preds_and_succs(*f, *file);
std::unordered_set<int> likely_delay_blocks;
auto blocks = setup_preds_and_succs(*f, *file, likely_delay_blocks);
Mips2C_Output output;
int unknown_count = 0;
for (size_t block_idx = 0; block_idx < blocks.size(); block_idx++) {
const auto& block = blocks[block_idx];
// fmt::print("block {}: {} to {}\n", block_idx, block.start_instr, block.end_instr);
if (likely_delay_blocks.count(block_idx)) {
continue;
}
if (block_requires_label(f, blocks, block_idx)) {
output.output_label(block_idx);
} else {
// output.comment_line("block {}",)
}
for (int i = block.start_instr; i < block.end_instr; i++) {
@@ -717,7 +940,22 @@ void run_mips2c(Function* f) {
if (is_branch(instr, {})) {
if (block.branch_likely) {
output.lines.push_back(handle_unknown(instr_str));
auto branch_line = handle_likely_branch_bc(instr, instr_str);
output.lines.emplace_back(fmt::format("if ({}) {{", branch_line.code),
branch_line.comment);
// next block should be the delay slot
assert((int)block_idx + 1 == block.succ_branch);
auto& delay_block = blocks.at(block.succ_branch);
assert(delay_block.end_instr - delay_block.start_instr == 1); // only 1 instr.
auto& delay_instr = f->instructions.at(delay_block.start_instr);
auto delay_instr_str = delay_instr.to_string(file->labels);
auto delay_instr_line =
handle_normal_instr(output, delay_instr, delay_instr_str, unknown_count, file);
output.lines.emplace_back(fmt::format(" {}", delay_instr_line.code),
delay_instr_line.comment);
assert(delay_block.succ_ft == -1);
output.lines.emplace_back(fmt::format(" goto block_{};", delay_block.succ_branch), "");
output.lines.emplace_back("}", "");
} else {
if (is_always_branch(instr)) {
// skip the branch ins.
@@ -728,7 +966,7 @@ void run_mips2c(Function* f) {
auto& delay_i = f->instructions.at(i);
auto delay_i_str = delay_i.to_string(file->labels);
output.lines.push_back(
handle_normal_instr(output, delay_i, delay_i_str, unknown_count));
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file));
assert(i + 1 == block.end_instr);
// then the goto
output.lines.emplace_back(fmt::format("goto block_{};", block.succ_branch),
@@ -742,7 +980,7 @@ void run_mips2c(Function* f) {
auto& delay_i = f->instructions.at(i);
auto delay_i_str = delay_i.to_string(file->labels);
output.lines.push_back(
handle_normal_instr(output, delay_i, delay_i_str, unknown_count));
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file));
assert(i + 1 == block.end_instr);
// then the goto
output.lines.emplace_back(fmt::format("if (bc) {{goto block_{};}}", block.succ_branch),
@@ -757,15 +995,27 @@ void run_mips2c(Function* f) {
i++;
auto& delay_i = f->instructions.at(i);
auto delay_i_str = delay_i.to_string(file->labels);
output.lines.push_back(handle_normal_instr(output, delay_i, delay_i_str, unknown_count));
assert(i + 1 == block.end_instr);
output.lines.push_back(
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file));
// then the goto
output.lines.emplace_back(fmt::format("goto end_of_function;", block.succ_branch),
"return\n");
} else if (instr.kind == InstructionKind::JALR) {
assert(instr.get_dst(0).is_reg(Register(Reg::GPR, Reg::RA)));
assert(i < block.end_instr - 1);
output.lines.emplace_back(
fmt::format("call_addr = c->gprs[{}].du32[0];", reg_to_name(instr.get_src(0))),
"function call:");
i++;
auto& delay_i = f->instructions.at(i);
auto delay_i_str = delay_i.to_string(file->labels);
output.lines.push_back(
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file));
output.lines.emplace_back("c->jalr(call_addr);", instr_str);
} else {
output.lines.push_back(handle_normal_instr(output, instr, instr_str, unknown_count));
output.lines.push_back(handle_normal_instr(output, instr, instr_str, unknown_count, file));
}
// fmt::print("I: {}\n", instr_str);
assert(output.lines.size() > old_line_count);
}
+244 -101
View File
@@ -213,6 +213,7 @@
(stmod 5) ;; set mode register
(mskpath3 6) ;; set path 3 mask
(mark 7) ;; set mark register
(pc-port 8) ;; special tag for PC Port data.
(flushe 16) ;; wait for end of microprogram
(flush 17) ;; wait for end of microprogram and transfer (path1/path2)
(flusha 19) ;; wait for end of microprogram and transfer (path1/path2/path3)
@@ -547,7 +548,7 @@
;; merc1 61
;; generic1 62
(depth-cue 64)
(bucket-65 65)
(pre-sprite-textures 65) ;; common
(sprite 66)
;; debug spheres? 67
(debug-draw0 67)
@@ -1019,7 +1020,7 @@
(ogre-end #x600)
(ogre-buzzer #x601)
(ogre-boss #x603)
(assistant-voicebox-intro-ogre-race #x605)
(sidekick-speech-hint-ogre-race #x61c)
@@ -5091,7 +5092,7 @@
(debug-print-entities (_type_ symbol type) none 13)
(debug-draw-actors (_type_ symbol) none 14)
(dummy-15 (_type_) object 15)
(dummy-16 (_type_) int 16)
(level-update (_type_) int 16)
(level-get-target-inside (_type_) level 17)
(alloc-levels! (_type_ symbol) int 18)
(load-commands-set! (_type_ pair) pair 19)
@@ -10106,6 +10107,7 @@
:method-count-assert 10
:size-assert #x68
:flag-assert #xa00000068
;; field handle is likely a value type
(:methods
(dummy-9 (_type_ attack-info) none 9)
)
@@ -11874,8 +11876,8 @@
(texture-remap-table (pointer uint64) :offset-assert 52)
(texture-remap-table-len int32 :offset-assert 56)
(unk-data-1 pointer :offset-assert 60)
(unk-data-1-len int32 :offset-assert 64)
(texture-ids (pointer texture-id) :offset-assert 60)
(texture-page-count int32 :offset-assert 64)
(unk-zero-0 basic :offset-assert 68)
@@ -13443,11 +13445,99 @@
;; Containing DGOs - ['GAME', 'ENGINE']
;; Version - 3
(defenum sp-field-id
:type uint16
(misc-fields-start 0)
(spt-texture 1)
(spt-anim 2)
(spt-anim-speed 3)
(spt-birth-func 4)
(spt-joint/refpoint 5)
(spt-num 6)
(spt-sound 7)
(misc-fields-end 8)
(sprite-fields-start 9)
(spt-x 10)
(spt-y 11)
(spt-z 12)
(spt-scale-x 13)
(spt-rot-x 14)
(spt-rot-y 15)
(spt-rot-z 16)
(spt-scale-y 17)
(spt-r 18)
(spt-g 19)
(spt-b 20)
(spt-a 21)
(sprite-fields-end 22)
(cpu-fields-start 23)
(spt-omega 24)
(spt-vel-x 25)
(spt-vel-y 26)
(spt-vel-z 27)
(spt-scalevel-x 28)
(spt-rotvel-x 29)
(spt-rotvel-y 30)
(spt-rotvel-z 31)
(spt-scalevel-y 32)
(spt-fade-r 33)
(spt-fade-g 34)
(spt-fade-b 35)
(spt-fade-a 36)
(spt-accel-x 37)
(spt-accel-y 38)
(spt-accel-z 39)
(spt-dummy 40)
(spt-quat-x 41)
(spt-quat-y 42)
(spt-quat-z 43)
(spt-quad-w 44)
(spt-friction 45)
(spt-timer 46)
(spt-flags 47)
(spt-userdata 48)
(spt-func 49)
(spt-next-time 50)
(spt-next-launcher 51)
(cpu-fields-end 52)
(launch-fields-start 53)
(spt-launchrot-x 54)
(spt-launchrot-y 55)
(spt-launchrot-z 56)
(spt-launchrot-w 57)
(spt-conerot-x 58)
(spt-conerot-y 59)
(spt-conerot-z 60)
(spt-conerot-w 61)
(spt-conerot-radius 62)
(spt-rotate-y 63)
(launch-fields-end 64)
(spt-scale 65)
(spt-scalevel 66)
(spt-end 67)
)
(defenum sp-flag
:type uint16
(plain-v1 0) ;; just a plain signed integer. No random crap.
(float-with-rand 1)
(int-with-rand 2)
(copy-from-other-field 3)
(plain-v2 4)
(from-pointer 5)
(part-by-id 6)
)
;; - Types
(deftype sp-field-init-spec (structure)
((field uint16 :offset-assert 0)
(flags uint16 :offset-assert 2)
((field sp-field-id :offset-assert 0)
(flags sp-flag :offset-assert 2)
(initial-valuef float :offset-assert 4)
(random-rangef float :offset-assert 8)
(random-multf float :offset-assert 12)
@@ -13476,11 +13566,20 @@
:flag-assert #x900000010
)
(defenum sp-group-item-flag
:bitfield #t
:type uint16
(is-3d 0)
(bit1 1)
(start-dead 2)
(launch-asap 3)
)
(deftype sparticle-group-item (structure)
((launcher uint32 :offset-assert 0)
(fade-after meters :offset-assert 4)
(falloff-to meters :offset-assert 8)
(flags uint16 :offset-assert 12)
(flags sp-group-item-flag :offset-assert 12)
(period uint16 :offset-assert 14)
(length uint16 :offset-assert 16)
(offset uint16 :offset-assert 18)
@@ -13492,13 +13591,23 @@
:flag-assert #x90000001c
)
(defenum sp-launch-state-flags
:bitfield #t
:type uint16
(launcher-active 0) ;; active
(particles-active 1) ;; wants to launch
(bit2 2)
)
(declare-type sparticle-cpuinfo structure)
(deftype sparticle-launch-state (structure)
((group-item sparticle-group-item :offset-assert 0)
(flags uint16 :offset-assert 4)
(flags sp-launch-state-flags :offset-assert 4)
(randomize uint16 :offset-assert 6)
(origin vector :offset-assert 8)
(sprite3d sprite-vec-data-3d :offset-assert 12)
(sprite basic :offset-assert 16)
(sprite sparticle-cpuinfo :offset-assert 16)
(offset uint32 :offset-assert 20)
(accum float :offset-assert 24)
(spawn-time uint32 :offset-assert 28)
@@ -13513,12 +13622,20 @@
:flag-assert #x900000020
)
(defenum sp-group-flag
:bitfield #t
:type uint16
(use-local-clock 0)
(always-draw 1)
(screen-space 2)
)
(deftype sparticle-launch-group (basic)
((length int16 :offset-assert 4)
(duration uint16 :offset-assert 6)
(linger-duration uint16 :offset-assert 8)
(flags uint16 :offset-assert 10)
(name basic :offset-assert 12)
(flags sp-group-flag :offset-assert 10)
(name string :offset-assert 12)
(launcher (inline-array sparticle-group-item) :offset-assert 16)
(bounds sphere :inline :offset-assert 32)
)
@@ -13546,10 +13663,10 @@
:flag-assert #xe00000040
(:methods
(initialize (_type_ sparticle-launch-group process) none 9)
(dummy-10 () none 10)
(dummy-11 (_type_ vector) none 11)
(deactivate (_type_) none 12)
(dummy-13 () none 13)
(is-visible? (_type_ vector) symbol 10)
(spawn (_type_ vector) object 11)
(kill-and-free-particles (_type_) none 12)
(kill-particles (_type_) none 13)
)
)
@@ -13564,6 +13681,28 @@
;; - Types
(defenum sp-cpuinfo-flag
:bitfield #t
:type uint32
(bit0 0)
(bit2 2) ;; cleared after an aux has its func set to add-to-sprite-aux-lst
(bit3 3)
(ready-to-launch 6) ;; maybe just just death?
(bit7 7)
(aux-list 8) ;; prevents relaunch, adds to aux
(bit9 9)
(level0 10)
(level1 11)
(bit12 12) ;; required to relaunch
(bit13 13)
(bit14 14)
(use-global-acc 16)
(launch-along-z 17)
(left-multiply-quat 18)
(right-multiply-quat 19)
(set-conerot 20)
)
(deftype sparticle-cpuinfo (structure)
((sprite sprite-vec-data-2d :offset-assert 0)
(adgif adgif-shader :offset-assert 4)
@@ -13580,7 +13719,7 @@
(scalevely float :offset 44)
(friction float :offset-assert 96)
(timer int32 :offset-assert 100)
(flags uint32 :offset-assert 104)
(flags sp-cpuinfo-flag :offset-assert 104)
(user-int32 int32 :offset-assert 108)
(user-uint32 uint32 :offset 108)
(user-float float :score 100 :offset 108)
@@ -13617,8 +13756,8 @@
(deftype sparticle-system (basic)
((blocks int32 2 :offset-assert 4)
(length uint32 2 :offset-assert 12)
(num-alloc uint32 2 :offset-assert 20)
(length int32 2 :offset-assert 12)
(num-alloc int32 2 :offset-assert 20)
(is-3d basic :offset-assert 28)
(flags uint32 :offset-assert 32)
(alloc-table (pointer uint64) :offset-assert 36)
@@ -15312,7 +15451,7 @@
(define-extern sprite-add-2d-chunk (function sprite-array-2d int int dma-buffer int none))
(define-extern sprite-setup-frame-data (function sprite-frame-data int none))
(define-extern clear-sprite-aux-list (function none))
(define-extern add-to-sprite-aux-list function) ;; it's a callback.
(define-extern add-to-sprite-aux-list (function sparticle-system sparticle-cpuinfo sprite-vec-data-3d none)) ;; it's a callback.
(define-extern sprite-set-3d-quaternion! (function sprite-vec-data-3d quaternion quaternion))
(define-extern sprite-get-3d-quaternion! (function quaternion sprite-vec-data-3d quaternion))
(define-extern sprite-draw (function display none))
@@ -16384,25 +16523,25 @@
;; - Types
; (deftype sparticle-birthinfo (structure)
; ((sprite uint32 :offset-assert 0)
; (anim int32 :offset-assert 4)
; (anim-speed float :offset-assert 8)
; (birth-func basic :offset-assert 12)
; (joint-ppoint int32 :offset-assert 16)
; (num-to-birth float :offset-assert 20)
; (sound basic :offset-assert 24)
; (dataf UNKNOWN 1 :offset-assert 0)
; (data UNKNOWN 1 :offset-assert 0)
; )
; :method-count-assert 9
; :size-assert #x1c
; :flag-assert #x90000001c
; )
(deftype sparticle-birthinfo (structure)
((sprite uint32 :offset-assert 0)
(anim int32 :offset-assert 4)
(anim-speed float :offset-assert 8)
(birth-func basic :offset-assert 12)
(joint-ppoint int32 :offset-assert 16)
(num-to-birth float :offset-assert 20)
(sound basic :offset-assert 24)
(dataf float 1 :offset 0)
(data uint32 1 :offset 0)
)
:method-count-assert 9
:size-assert #x1c
:flag-assert #x90000001c
)
(deftype sp-queued-launch-particles (structure)
((sp-system basic :offset-assert 0)
(sp-launcher basic :offset-assert 4)
((sp-system sparticle-system :offset-assert 0)
(sp-launcher sparticle-launcher :offset-assert 4)
(pos vector :inline :offset-assert 16)
)
:method-count-assert 9
@@ -16410,66 +16549,70 @@
:flag-assert #x900000020
)
; (deftype sp-launch-queue (basic)
; ((in-use int32 :offset-assert 4)
; (queue UNKNOWN 32 :offset-assert 16)
; )
; :method-count-assert 9
; :size-assert #x410
; :flag-assert #x900000410
; )
(deftype sp-launch-queue (basic)
((in-use int32 :offset-assert 4)
(queue sp-queued-launch-particles 32 :inline :offset-assert 16)
)
:method-count-assert 9
:size-assert #x410
:flag-assert #x900000410
)
; (deftype particle-adgif-cache (basic)
; ((used int32 :offset-assert 4)
; (last uint16 :offset-assert 8)
; (lastgif adgif-shader :offset-assert 12)
; (tidhash UNKNOWN 80 :offset-assert 16)
; (spadgif UNKNOWN 80 :offset-assert 176)
; )
; :method-count-assert 9
; :size-assert #x19b0
; :flag-assert #x9000019b0
; )
(deftype particle-adgif-cache (basic)
((used int32 :offset-assert 4)
(last uint16 :offset-assert 8)
(lastgif adgif-shader :offset-assert 12)
(tidhash uint16 80 :offset-assert 16)
(spadgif adgif-shader 80 :inline :offset-assert 176)
)
:method-count-assert 9
:size-assert #x19b0
:flag-assert #x9000019b0
)
;; - Functions
(define-extern sphere-in-view-frustum? (function vector symbol))
(define-extern sphere-in-view-frustum? (function sphere symbol))
(define-extern kill-all-particles-with-key (function sparticle-launch-control none))
(define-extern sp-relaunch-setup-fields function)
(define-extern sp-init-fields! function)
(define-extern sp-launch-particles-var (function sparticle-system sparticle-launcher vector symbol symbol float none)) ;; asm - ret not confirmed
(define-extern sp-get-particle function)
(define-extern particle-adgif function)
(define-extern sp-relaunch-setup-fields (function object sparticle-launcher sparticle-cpuinfo sprite-vec-data-3d none))
;; data, field-inits, start field idx, end field idx, bool?
(define-extern sp-init-fields! (function object (inline-array sp-field-init-spec) sp-field-id sp-field-id symbol object))
(define-extern sp-launch-particles-var (function sparticle-system sparticle-launcher vector sparticle-launch-state sparticle-launch-control float none)) ;; asm - ret not confirmed
(define-extern sp-get-particle (function sparticle-system int sparticle-launch-state sparticle-cpuinfo))
(define-extern particle-adgif (function adgif-shader texture-id none))
(define-extern lookup-part-group-by-name (function string basic))
(define-extern lookup-part-group-pointer-by-name function)
(define-extern lookup-part-group-pointer-by-name (function string (pointer sparticle-launch-group)))
(define-extern unlink-part-group-by-heap (function kheap int))
(define-extern particle-setup-adgif function)
(define-extern sp-queue-launch function)
(define-extern sp-adjust-launch function)
(define-extern sp-euler-convert function)
(define-extern sp-rotate-system function)
(define-extern sp-launch-particles-death function)
(define-extern sp-clear-queue function)
(define-extern sp-relaunch-particle-2d function)
(define-extern sp-relaunch-particle-3d function)
(define-extern sparticle-track-root function)
(define-extern sparticle-track-root-prim function)
(define-extern birth-func-copy-rot-color function)
(define-extern birth-func-copy2-rot-color function)
(define-extern birth-func-copy-omega-to-z function)
(define-extern birth-func-random-next-time function)
(define-extern particle-setup-adgif (function adgif-shader texture-id none))
(define-extern sp-queue-launch (function sparticle-system sparticle-launcher vector int))
(define-extern sp-adjust-launch (function sparticle-launchinfo sparticle-cpuinfo (inline-array sp-field-init-spec) none))
(define-extern sp-euler-convert (function sparticle-launchinfo sparticle-cpuinfo none))
(define-extern sp-rotate-system (function sparticle-launchinfo sparticle-cpuinfo transformq none))
(define-extern sp-launch-particles-death (function sparticle-system sparticle-launcher sparticle-launchinfo none))
(define-extern sp-clear-queue (function none))
(define-extern sp-relaunch-particle-2d (function object sparticle-launcher sparticle-cpuinfo sprite-vec-data-3d none))
(define-extern sp-relaunch-particle-3d (function object sparticle-launcher sparticle-cpuinfo sprite-vec-data-3d none))
(define-extern sparticle-track-root (function object sparticle-cpuinfo vector none))
(define-extern sparticle-track-root-prim (function object sparticle-cpuinfo vector none))
(define-extern birth-func-copy-rot-color (function sparticle-system sparticle-cpuinfo sprite-vec-data-3d sparticle-launcher sparticle-launch-state none))
(define-extern birth-func-copy2-rot-color (function sparticle-system sparticle-cpuinfo sprite-vec-data-3d sparticle-launcher sparticle-launch-state none))
(define-extern birth-func-copy-omega-to-z (function sparticle-system sparticle-cpuinfo sprite-vec-data-3d sparticle-launcher sparticle-launch-state none))
(define-extern birth-func-random-next-time (function sparticle-system sparticle-cpuinfo sprite-vec-data-3d sparticle-launcher sparticle-launch-state none))
;; - Unknowns
;;(define-extern *global-toggle* object) ;; unknown type
(define-extern *global-toggle* int) ;; unknown type
(define-extern *part-id-table* (array sparticle-launcher))
(define-extern *particle-300hz-timer* int)
;;(define-extern *sp-launch-queue* object) ;; unknown type
;;(define-extern *death-adgif* object) ;; unknown type
(define-extern *sp-launch-queue* sp-launch-queue)
(define-extern *death-adgif* adgif-shader) ;; guess
(define-extern *part-group-id-table* (array sparticle-launch-group))
;;(define-extern *sp-launcher-lock* object) ;; unknown type
(define-extern *sp-launcher-lock* symbol)
(define-extern *sp-launcher-enable* symbol)
;;(define-extern *particle-adgif-cache* object) ;; unknown type
(define-extern *particle-adgif-cache* particle-adgif-cache)
;; ----------------------
@@ -16480,31 +16623,31 @@
;; - Functions
(define-extern all-particles-60-to-50 function)
(define-extern all-particles-50-to-60 function)
(define-extern sp-process-particle-system function)
(define-extern forall-particles-runner function)
(define-extern sparticle-60-to-50 function)
(define-extern sparticle-50-to-60 function)
(define-extern forall-particles function)
(define-extern all-particles-60-to-50 (function none))
(define-extern all-particles-50-to-60 (function none))
(define-extern sp-process-particle-system (function sparticle-system int sprite-array-2d none))
(define-extern forall-particles-runner (function (function sparticle-system sparticle-cpuinfo pointer none) sparticle-system none))
(define-extern sparticle-60-to-50 (function sparticle-system sparticle-cpuinfo pointer none))
(define-extern sparticle-50-to-60 (function sparticle-system sparticle-cpuinfo pointer none))
(define-extern forall-particles (function function symbol symbol none))
(define-extern sparticle-kill-it-level0 (function sparticle-system sparticle-cpuinfo none))
(define-extern sparticle-kill-it-level1 (function sparticle-system sparticle-cpuinfo none))
(define-extern forall-particles-with-key (function sparticle-launch-control (function sparticle-system sparticle-cpuinfo none) symbol symbol none))
(define-extern sparticle-kill-it (function sparticle-system sparticle-cpuinfo none))
(define-extern forall-particles-with-key-runner (function sparticle-launch-control (function sparticle-system sparticle-cpuinfo none) sparticle-system none))
(define-extern sp-get-approx-alloc-size function)
(define-extern sp-process-block function)
(define-extern sp-copy-to-spr function)
(define-extern sp-process-block-3d function)
(define-extern sp-process-block-2d function)
(define-extern sp-copy-from-spr function)
(define-extern sp-free-particle function)
(define-extern sp-particle-copy! function)
(define-extern sp-get-block-size function)
(define-extern sp-get-approx-alloc-size (function sparticle-system int int))
(define-extern sp-process-block (function sparticle-system int sprite-array-2d int none))
(define-extern sp-copy-to-spr (function int pointer int none))
(define-extern sp-process-block-3d (function sparticle-system int int int int symbol none))
(define-extern sp-process-block-2d (function sparticle-system int int int int symbol none))
(define-extern sp-copy-from-spr (function int pointer int none))
(define-extern sp-free-particle (function sparticle-system int sparticle-cpuinfo sprite-vec-data-2d none))
(define-extern sp-particle-copy! (function sparticle-cpuinfo sparticle-cpuinfo none))
(define-extern sp-get-block-size (function sparticle-system int int))
(define-extern sp-kill-particle (function sparticle-system sparticle-cpuinfo none))
(define-extern sp-orbiter function)
(define-extern sp-orbiter (function sparticle-system sparticle-cpuinfo vector none))
(define-extern memcpy function)
(define-extern kill-all-particles-in-level (function int))
(define-extern kill-all-particles-in-level (function level int))
(define-extern set-particle-frame-time (function int none))
(define-extern process-particles (function none))
@@ -114,7 +114,6 @@
"cspace<-parented-transformq-joint!",
// sprite
"add-to-sprite-aux-list", // fine, but don't know types yet.
// merc-blend-shape
"setup-blerc-chains-for-one-fragment", // F: asm branching
@@ -211,16 +210,12 @@
"draw-inline-array-instance-tie",
// sparticle-launcher
"(method 11 sparticle-launch-control)", // BUG: cfg ir
"sp-launch-particles-var",
"particle-adgif",
"sp-init-fields!",
// sparticle
"memcpy",
"sp-process-block-3d",
"sp-process-block-2d",
"sp-get-particle",
// mood BUG
"update-mood-lava", // BUG:
@@ -503,7 +498,10 @@
"unpack-comp-huf":[2, 4, 5, 6, 7, 8, 9],
"blerc-execute":[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33],
"(method 11 fact-info-target)":[42],
"(code format-card auto-save)":[3, 4, 5, 6, 7, 8]
"(code format-card auto-save)":[3, 4, 5, 6, 7, 8],
"particle-adgif":[0, 1, 2, 3, 4, 5, 7],
"sp-launch-particles-var":[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66],
"(method 11 sparticle-launch-control)": [ 27, 28, 35, 46, 48, 49, 77]
},
// Sometimes the game might use format strings that are fetched dynamically,
@@ -542,7 +540,11 @@
},
"mips2c_functions_by_name":[
"draw-string"
"sp-init-fields!",
"particle-adgif",
"sp-launch-particles-var",
"sp-process-block-2d",
"sp-process-block-3d"
]
}
@@ -611,6 +611,9 @@
"hint-control": [
["L45", "(array task-hint-control-group)"]
],
"sparticle-launcher": [
["L192", "adgif-shader"]
],
// please do not add things after this entry! git is dumb.
"object-file-that-doesnt-actually-exist-and-i-just-put-this-here-to-prevent-merge-conflicts-with-this-file": []
@@ -1892,5 +1892,60 @@
"ambient-type-sound": [[32, "sound-spec"]],
"ambient-type-sound-loop": [[16, "sound-spec"]],
"sp-relaunch-particle-3d": [
[16, "quaternion"],
[32, "vector"],
[48, "quaternion"]
],
"sp-adjust-launch": [
[16, "sparticle-launchinfo"],
[64, "matrix"],
[128, "vector"],
[144, "matrix"]
],
"(method 10 sparticle-launch-control)":[
[16, "vector"]
],
"sparticle-50-to-60":[
[16, "quaternion"]
],
"sparticle-60-to-50":[
[16, "quaternion"]
],
"sp-orbiter":[
[16, "vector"],
[32, "vector"],
[48, "matrix"]
],
"sp-euler-convert":[
[16, "vector"],
[32, "quaternion"]
],
"sp-rotate-system": [
[16, "matrix"],
[80, "quaternion"]
],
"sp-launch-particles-death": [
[16, "sprite-vec-data-2d"] // TODO this is probably wrong.
],
"birth-func-copy-rot-color": [
[16, "vector"]
],
"birth-func-copy2-rot-color": [
[16, "vector"],
[32, "vector"]
],
"placeholder-do-not-add-below!": []
}
@@ -3178,6 +3178,34 @@
["_stack_", 112, "res-tag"],
[57, "v0", "symbol"]
],
"forall-particles-runner": [
[[19,28], "s4", "sparticle-cpuinfo"],
[34, "s4", "pointer"],
[35, "s3", "pointer"]
],
"(method 2 sparticle-cpuinfo)": [
[14, "f0", "float"]
],
"sp-kill-particle": [
[7, "a1", "uint"],
[7, "v1", "uint"]
],
"sparticle-track-root":[
[2, "v1", "process-drawable"]
],
"sparticle-track-root-prim":[
[2, "v1", "process-drawable"],
[3, "v1", "collide-shape"]
],
"sp-orbiter":[
[[73, 82], "v1", "sprite-vec-data-2d"]
],
"placeholder-do-not-add-below": []
}
+3 -6
View File
@@ -6,6 +6,7 @@
#include "game_text.h"
#include "decompiler/ObjectFile/ObjectFileDB.h"
#include "common/goos/Reader.h"
#include "common/util/BitUtils.h"
namespace decompiler {
namespace {
@@ -23,10 +24,6 @@ DecompilerLabel get_label(ObjectFileData& data, const LinkedWord& word) {
return data.linked_data.labels.at(word.label_id);
}
int align16(int in) {
return (in + 15) & ~15;
}
} // namespace
/*
@@ -76,7 +73,7 @@ GameTextResult process_game_text(ObjectFileData& data) {
assert(group_name == "common");
// remember that we read these bytes
auto group_start = (group_label.offset / 4) - 1;
for (int j = 0; j < align16(8 + 1 + group_name.length()) / 4; j++) {
for (int j = 0; j < align16(8 + 1 + (int)group_name.length()) / 4; j++) {
read_words.at(group_start + j)++;
}
@@ -106,7 +103,7 @@ GameTextResult process_game_text(ObjectFileData& data) {
// remember what we read (-1 for the type tag)
auto string_start = (text_label.offset / 4) - 1;
// 8 for type tag and length fields, 1 for null char.
for (int j = 0; j < align16(8 + 1 + text.length()) / 4; j++) {
for (int j = 0; j < align16(8 + 1 + (int)text.length()) / 4; j++) {
read_words.at(string_start + j)++;
}
}
+11 -5
View File
@@ -9,6 +9,7 @@
#include "decompiler/ObjectFile/LinkedObjectFile.h"
#include "decompiler/IR2/Form.h"
#include "decompiler/analysis/final_output.h"
#include "decompiler/util/sparticle_decompile.h"
namespace decompiler {
@@ -186,7 +187,7 @@ goos::Object decompile_at_label(const TypeSpec& type,
}
if (ts.tc(TypeSpec("structure"), type)) {
return decompile_structure(type, label, labels, words, ts, file);
return decompile_structure(type, label, labels, words, ts, file, true);
}
if (type == TypeSpec("pair")) {
@@ -473,7 +474,15 @@ goos::Object decompile_structure(const TypeSpec& type,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts,
const LinkedObjectFile* file) {
const LinkedObjectFile* file,
bool use_fancy_macros) {
if (use_fancy_macros && type == TypeSpec("sp-field-init-spec")) {
return decompile_sparticle_field_init(type, label, labels, words, ts, file);
}
if (use_fancy_macros && type == TypeSpec("sparticle-group-item")) {
return decompile_sparticle_group_item(type, label, labels, words, ts, file);
}
// first step, get type info and words
TypeSpec actual_type = type;
auto uncast_type_info = ts.lookup_type(actual_type);
@@ -823,7 +832,6 @@ goos::Object decompile_structure(const TypeSpec& type,
return pretty_print::build_list(result_def);
}
namespace {
goos::Object bitfield_defs_print(const TypeSpec& type,
const std::vector<BitFieldConstantDef>& defs) {
std::vector<goos::Object> result;
@@ -843,8 +851,6 @@ goos::Object bitfield_defs_print(const TypeSpec& type,
return pretty_print::build_list(result);
}
} // namespace
goos::Object decompile_value(const TypeSpec& type,
const std::vector<u8>& bytes,
const TypeSystem& ts) {
+4 -2
View File
@@ -39,7 +39,8 @@ goos::Object decompile_structure(const TypeSpec& actual_type,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts,
const LinkedObjectFile* file);
const LinkedObjectFile* file,
bool use_fancy_macros);
goos::Object decompile_pair(const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
@@ -105,5 +106,6 @@ std::vector<std::string> decompile_bitfield_enum_from_int(const TypeSpec& type,
const TypeSystem& ts,
u64 value);
std::string decompile_int_enum_from_int(const TypeSpec& type, const TypeSystem& ts, u64 value);
goos::Object bitfield_defs_print(const TypeSpec& type,
const std::vector<BitFieldConstantDef>& defs);
} // namespace decompiler
+622
View File
@@ -0,0 +1,622 @@
#include "sparticle_decompile.h"
#include "decompiler/util/data_decompile.h"
#include "common/goos/PrettyPrinter.h"
#include "common/util/print_float.h"
namespace decompiler {
// sparticle fields.
// should match the enum in the game.
enum class FieldId {
MISC_FIELDS_START = 0,
SPT_TEXTURE = 1,
SPT_ANIM = 2,
SPT_ANIM_SPEED = 3,
SPT_BIRTH_FUNC = 4,
SPT_JOINT_REFPOINT = 5,
SPT_NUM = 6,
SPT_SOUND = 7,
MISC_FIELDS_END = 8,
SPRITE_FIELDS_START = 9,
SPT_X = 10,
SPT_Y = 11,
SPT_Z = 12,
SPT_SCALE_X = 13,
SPT_ROT_X = 14,
SPT_ROT_Y = 15,
SPT_ROT_Z = 16,
SPT_SCALE_Y = 17,
SPT_R = 18,
SPT_G = 19,
SPT_B = 20,
SPT_A = 21,
SPRITE_FIELDS_END = 22,
CPU_FIELDS_START = 23,
SPT_OMEGA = 24,
SPT_VEL_X = 25,
SPT_VEL_Y = 26,
SPT_VEL_Z = 27,
SPT_SCALEVEL_X = 28,
SPT_ROTVEL_X = 29,
SPT_ROTVEL_Y = 30,
SPT_ROTVEL_Z = 31,
SPT_SCALEVEL_Y = 32,
SPT_FADE_R = 33,
SPT_FADE_G = 34,
SPT_FADE_B = 35,
SPT_FADE_A = 36,
SPT_ACCEL_X = 37,
SPT_ACCEL_Y = 38,
SPT_ACCEL_Z = 39,
SPT_DUMMY = 40,
SPT_QUAT_X = 41,
SPT_QUAT_Y = 42,
SPT_QUAT_Z = 43,
SPT_QUAD_W = 44,
SPT_FRICTION = 45,
SPT_TIMER = 46,
SPT_FLAGS = 47,
SPT_USERDATA = 48,
SPT_FUNC = 49,
SPT_NEXT_TIME = 50,
SPT_NEXT_LAUNCHER = 51,
CPU_FIELDS_END = 52,
LAUNCH_FIELDS_START = 53,
SPT_LAUNCHROT_X = 54,
SPT_LAUNCHROT_Y = 55,
SPT_LAUNCHROT_Z = 56,
SPT_LAUNCHROT_W = 57,
SPT_CONEROT_X = 58,
SPT_CONEROT_Y = 59,
SPT_CONEROT_Z = 60,
SPT_CONEROT_W = 61,
SPT_CONEROT_RADIUS = 62,
SPT_ROTATE_Y = 63,
LAUNCH_FIELDS_END = 64,
SPT_SCALE = 65,
SPT_SCALEVEL = 66,
SPT_END = 67,
};
// flag vals:
// 0: timer, flags, end
// 1: texture, float, random-rangef
// 3: integer
// 6: next launcher
// flag bits
// 2: number is an integer
// 4: launcher index
enum class FieldKind {
FLOAT,
TEXTURE_ID,
FLOAT_WITH_RAND,
METER_WITH_RAND,
DEGREES_WITH_RAND,
// INT_WITH_RAND,
PLAIN_INT,
PLAIN_INT_WITH_RANDS,
CPUINFO_FLAGS,
END_FLAG,
LAUNCHER_BY_ID,
NO_FANCY_DECOMP,
FUNCTION,
USERDATA,
INVALID
};
struct SparticleFieldDecomp {
bool known = false; // error if we try to decomp one that isn't known
FieldKind kind = FieldKind::INVALID;
};
const SparticleFieldDecomp field_kinds[68] = {
{false}, // MISC_FIELDS_START = 0
{true, FieldKind::TEXTURE_ID}, // SPT_TEXTURE = 1
{false}, // SPT_ANIM = 2
{false}, // SPT_ANIM_SPEED = 3
{true, FieldKind::FUNCTION}, // SPT_BIRTH_FUNC = 4
{false}, // SPT_JOINT/REFPOINT = 5
{true, FieldKind::FLOAT_WITH_RAND}, // SPT_NUM = 6
{false}, // SPT_SOUND = 7
{false}, // MISC_FIELDS_END = 8
{false}, // SPRITE_FIELDS_START = 9
{true, FieldKind::METER_WITH_RAND}, // SPT_X = 10
{true, FieldKind::METER_WITH_RAND}, // SPT_Y = 11
{true, FieldKind::FLOAT_WITH_RAND}, // SPT_Z = 12
{true, FieldKind::METER_WITH_RAND}, // SPT_SCALE_X = 13
{true, FieldKind::PLAIN_INT}, // SPT_ROT_X = 14
{true, FieldKind::DEGREES_WITH_RAND}, // SPT_ROT_Y = 15
{true, FieldKind::DEGREES_WITH_RAND}, // SPT_ROT_Z = 16
{true, FieldKind::METER_WITH_RAND}, // SPT_SCALE_Y = 17
{true, FieldKind::FLOAT_WITH_RAND}, // SPT_R = 18
{true, FieldKind::FLOAT_WITH_RAND}, // SPT_G = 19
{true, FieldKind::FLOAT_WITH_RAND}, // SPT_B = 20
{true, FieldKind::FLOAT_WITH_RAND}, // SPT_A = 21
{false}, // SPRITE_FIELDS_END = 22
{false}, // CPU_FIELDS_START = 23
{true, FieldKind::FLOAT_WITH_RAND}, // SPT_OMEGA = 24
{true, FieldKind::METER_WITH_RAND}, // SPT_VEL_X = 25 (likely m/s)
{true, FieldKind::METER_WITH_RAND}, // SPT_VEL_Y = 26
{true, FieldKind::METER_WITH_RAND}, // SPT_VEL_Z = 27
{true, FieldKind::METER_WITH_RAND}, // SPT_SCALEVEL_X = 28
{false}, // SPT_ROTVEL_X = 29
{false}, // SPT_ROTVEL_Y = 30
{true, FieldKind::DEGREES_WITH_RAND}, // SPT_ROTVEL_Z = 31
{true, FieldKind::METER_WITH_RAND}, // SPT_SCALEVEL_Y = 32
{true, FieldKind::FLOAT_WITH_RAND}, // SPT_FADE_R = 33
{true, FieldKind::FLOAT_WITH_RAND}, // SPT_FADE_G = 34
{true, FieldKind::FLOAT_WITH_RAND}, // SPT_FADE_B = 35
{true, FieldKind::FLOAT_WITH_RAND}, // SPT_FADE_A = 36
{false}, // SPT_ACCEL_X = 37
{true, FieldKind::FLOAT_WITH_RAND}, // SPT_ACCEL_Y = 38
{false}, // SPT_ACCEL_Z = 39
{false}, // SPT_DUMMY = 40
{false}, // SPT_QUAT_X = 41
{false}, // SPT_QUAT_Y = 42
{false}, // SPT_QUAT_Z = 43
{false}, // SPT_QUAD_W = 44
{true, FieldKind::FLOAT_WITH_RAND}, // SPT_FRICTION = 45
{true, FieldKind::PLAIN_INT_WITH_RANDS}, // SPT_TIMER = 46
{true, FieldKind::CPUINFO_FLAGS}, // SPT_FLAGS = 47
{true, FieldKind::USERDATA}, // SPT_USERDATA = 48
{true, FieldKind::FUNCTION}, // SPT_FUNC = 49
{true, FieldKind::PLAIN_INT_WITH_RANDS}, // SPT_NEXT_TIME = 50
{true, FieldKind::LAUNCHER_BY_ID}, // SPT_NEXT_LAUNCHER = 51
{false}, // CPU_FIELDS_END = 52
{false}, // LAUNCH_FIELDS_START = 53
{true, FieldKind::DEGREES_WITH_RAND}, // SPT_LAUNCHROT_X = 54
{true, FieldKind::DEGREES_WITH_RAND}, // SPT_LAUNCHROT_Y = 55
{true, FieldKind::DEGREES_WITH_RAND}, // SPT_LAUNCHROT_Z = 56
{true, FieldKind::DEGREES_WITH_RAND}, // SPT_LAUNCHROT_W = 57
{true, FieldKind::DEGREES_WITH_RAND}, // SPT_CONEROT_X = 58
{true, FieldKind::DEGREES_WITH_RAND}, // SPT_CONEROT_Y = 59
{true, FieldKind::DEGREES_WITH_RAND}, // SPT_CONEROT_Z = 60
{false}, // SPT_CONEROT_W = 61
{true, FieldKind::METER_WITH_RAND}, // SPT_CONEROT_RADIUS = 62
{true, FieldKind::DEGREES_WITH_RAND}, // SPT_ROTATE_Y = 63
{false}, // LAUNCH_FIELDS_END = 64
{false}, // SPT_SCALE = 65
{false}, // SPT_SCALEVEL = 66
{true, FieldKind::END_FLAG}, // SPT_END = 67
};
std::string make_flags_str(const std::vector<std::string>& flags) {
if (flags.empty()) {
return "";
}
std::string result = " :flags (";
for (auto& x : flags) {
result += x;
result += ' ';
}
result.pop_back();
result += ')';
return result;
}
goos::Object decompile_sparticle_tex_field_init(const std::vector<LinkedWord>& words,
const TypeSystem& ts,
const std::string& field_name,
const std::string& flag_name) {
assert(words.at(1).kind == LinkedWord::PLAIN_DATA);
assert(words.at(2).kind == LinkedWord::PLAIN_DATA);
assert(words.at(2).data == 0);
assert(words.at(3).kind == LinkedWord::PLAIN_DATA);
assert(words.at(3).data == 0);
assert(flag_name == "plain-v1");
auto tex_id_type = TypeSpec("texture-id");
auto tex_id_str = bitfield_defs_print(
tex_id_type, decompile_bitfield_from_int(tex_id_type, ts, words.at(1).data));
return pretty_print::to_symbol(fmt::format("(sp-tex {} {})", field_name, tex_id_str.print()));
}
float word_as_float(const LinkedWord& w) {
assert(w.kind == LinkedWord::PLAIN_DATA);
float v;
memcpy(&v, &w.data, 4);
return v;
}
s32 word_as_s32(const LinkedWord& w) {
assert(w.kind == LinkedWord::PLAIN_DATA);
return w.data;
}
goos::Object decompile_sparticle_func(const std::vector<LinkedWord>& words,
const std::string& field_name,
const std::string& flag_name) {
assert(words.at(1).kind == LinkedWord::SYM_PTR);
assert(words.at(2).kind == LinkedWord::PLAIN_DATA);
assert(words.at(2).data == 0);
assert(words.at(3).kind == LinkedWord::PLAIN_DATA);
assert(words.at(3).data == 0);
assert(flag_name == "from-pointer");
return pretty_print::to_symbol(
fmt::format("(sp-func {} '{})", field_name, words.at(1).symbol_name));
}
goos::Object decompile_sparticle_end(const std::vector<LinkedWord>& words,
const std::string& field_name,
const std::string& flag_name) {
assert(words.at(1).kind == LinkedWord::PLAIN_DATA);
assert(words.at(1).data == 0);
assert(words.at(2).kind == LinkedWord::PLAIN_DATA);
assert(words.at(2).data == 0);
assert(words.at(3).kind == LinkedWord::PLAIN_DATA);
assert(words.at(3).data == 0);
assert(flag_name == "plain-v1");
assert(field_name == "spt-end");
return pretty_print::to_symbol("(sp-end)");
}
goos::Object decompile_sparticle_int_with_rand_to_float(const std::vector<LinkedWord>& words,
const std::string& field_name,
const std::string& flag_name) {
assert(flag_name == "int-with-rand");
return pretty_print::to_symbol(fmt::format("(sp-rnd-int {} {} {} {})", field_name,
word_as_s32(words.at(1)), word_as_s32(words.at(2)),
float_to_string(word_as_float(words.at(3)))));
}
goos::Object decompile_sparticle_float_with_rand_init(const std::vector<LinkedWord>& words,
const std::string& field_name,
const std::string& flag_name) {
if (flag_name == "int-with-rand") {
return decompile_sparticle_int_with_rand_to_float(words, field_name, flag_name);
}
assert(flag_name == "float-with-rand");
float range = word_as_float(words.at(2));
float mult = word_as_float(words.at(3));
if (range == 0.f && mult == 1.f) {
return pretty_print::to_symbol(
fmt::format("(sp-flt {} {})", field_name, float_to_string(word_as_float(words.at(1)))));
} else {
return pretty_print::to_symbol(fmt::format("(sp-rnd-flt {} {} {} {})", field_name,
float_to_string(word_as_float(words.at(1))),
float_to_string(range), float_to_string(mult)));
}
}
goos::Object decompile_sparticle_userdata(const std::vector<LinkedWord>& words,
const std::string& field_name,
const std::string& flag_name,
const goos::Object& original) {
if (flag_name == "int-with-rand" || flag_name == "float-with-rand") {
return decompile_sparticle_float_with_rand_init(words, field_name, flag_name);
} else {
return original;
}
}
goos::Object decompile_sparticle_int_init(const std::vector<LinkedWord>& words,
const std::string& field_name,
const std::string& flag_name) {
assert(words.at(1).kind == LinkedWord::PLAIN_DATA);
assert(words.at(2).kind == LinkedWord::PLAIN_DATA);
assert(words.at(2).data == 0);
assert(words.at(3).kind == LinkedWord::PLAIN_DATA);
assert(words.at(3).data == 1);
assert(flag_name == "plain-v1");
return pretty_print::to_symbol(
fmt::format("(sp-int {} {})", field_name, word_as_s32(words.at(1))));
}
goos::Object decompile_sparticle_int_with_rand_init(const std::vector<LinkedWord>& words,
const std::string& field_name,
const std::string& flag_name) {
assert(flag_name == "plain-v1");
if (word_as_s32(words.at(2)) == 0 && word_as_s32(words.at(3)) == 1) {
return decompile_sparticle_int_init(words, field_name, flag_name);
}
return pretty_print::to_symbol(fmt::format("(sp-int-plain-rnd {} {} {} {})", field_name,
word_as_s32(words.at(1)), word_as_s32(words.at(2)),
word_as_s32(words.at(3))));
}
goos::Object decompile_sparticle_launcher_by_id(const std::vector<LinkedWord>& words,
const std::string& field_name,
const std::string& flag_name) {
assert(words.at(1).kind == LinkedWord::PLAIN_DATA);
assert(words.at(2).kind == LinkedWord::PLAIN_DATA);
assert(words.at(2).data == 0);
assert(words.at(3).kind == LinkedWord::PLAIN_DATA);
assert(words.at(3).data == 0);
assert(flag_name == "part-by-id");
return pretty_print::to_symbol(
fmt::format("(sp-launcher-by-id {} {})", field_name, word_as_s32(words.at(1))));
}
goos::Object decompile_sparticle_flags(const std::vector<LinkedWord>& words,
const TypeSystem& ts,
const std::string& field_name,
const std::string& flag_name) {
assert(flag_name == "plain-v1");
assert(field_name == "spt-flags");
assert(words.at(3).kind == LinkedWord::PLAIN_DATA);
assert(words.at(3).data == 1);
assert(words.at(2).kind == LinkedWord::PLAIN_DATA);
assert(words.at(2).data == 0);
auto flag_def =
decompile_bitfield_enum_from_int(TypeSpec("sp-cpuinfo-flag"), ts, word_as_s32(words.at(1)));
std::string result = "(sp-cpuinfo-flags";
for (const auto& def : flag_def) {
result += ' ';
result += def;
}
result += ')';
return pretty_print::to_symbol(result);
}
goos::Object decompile_sparticle_from_other(const std::vector<LinkedWord>& words,
const std::string& field_name,
const std::string& flag_name) {
assert(words.at(1).kind == LinkedWord::PLAIN_DATA);
assert(words.at(2).kind == LinkedWord::PLAIN_DATA);
assert(words.at(2).data == 0);
assert(words.at(3).kind == LinkedWord::PLAIN_DATA);
assert(words.at(3).data == 1);
assert(flag_name == "copy-from-other-field");
return pretty_print::to_symbol(
fmt::format("(sp-copy-from-other {} {})", field_name, word_as_s32(words.at(1))));
}
goos::Object decompile_sparticle_float_meters_with_rand_init(const std::vector<LinkedWord>& words,
const std::string& field_name,
const std::string& flag_name) {
if (flag_name == "int-with-rand") {
return pretty_print::to_symbol(
fmt::format("(sp-rnd-int-flt {} (meters {}) {} {})", field_name,
float_to_string(word_as_float(words.at(1)) / METER_LENGTH),
word_as_s32(words.at(2)), float_to_string(word_as_float(words.at(3)))));
}
assert(flag_name == "float-with-rand");
float range = word_as_float(words.at(2));
float mult = word_as_float(words.at(3));
if (range == 0.f && mult == 1.f) {
return pretty_print::to_symbol(
fmt::format("(sp-flt {} (meters {}))", field_name,
float_to_string(word_as_float(words.at(1)) / METER_LENGTH)));
} else {
return pretty_print::to_symbol(
fmt::format("(sp-rnd-flt {} (meters {}) (meters {}) {})", field_name,
float_to_string(word_as_float(words.at(1)) / METER_LENGTH),
float_to_string(word_as_float(words.at(2)) / METER_LENGTH),
float_to_string(word_as_float(words.at(3)))));
}
}
goos::Object decompile_sparticle_float_degrees_with_rand_init(const std::vector<LinkedWord>& words,
const std::string& field_name,
const std::string& flag_name) {
if (flag_name == "int-with-rand") {
return pretty_print::to_symbol(
fmt::format("(sp-rnd-int-flt {} (degrees {}) {} {})", field_name,
float_to_string(word_as_float(words.at(1)) / DEGREES_LENGTH),
word_as_s32(words.at(2)), float_to_string(word_as_float(words.at(3)))));
}
assert(flag_name == "float-with-rand");
float range = word_as_float(words.at(2));
float mult = word_as_float(words.at(3));
if (range == 0.f && mult == 1.f) {
return pretty_print::to_symbol(
fmt::format("(sp-flt {} (degrees {}))", field_name,
float_to_string(word_as_float(words.at(1)) / DEGREES_LENGTH)));
} else {
return pretty_print::to_symbol(
fmt::format("(sp-rnd-flt {} (degrees {}) (degrees {}) {})", field_name,
float_to_string(word_as_float(words.at(1)) / DEGREES_LENGTH),
float_to_string(word_as_float(words.at(2)) / DEGREES_LENGTH),
float_to_string(word_as_float(words.at(3)))));
}
}
goos::Object decompile_sparticle_group_item(const TypeSpec& type,
const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts,
const LinkedObjectFile* file) {
auto normal = decompile_structure(type, label, labels, words, ts, file, false);
fmt::print("Doing: {}\n", normal.print());
auto uncast_type_info = ts.lookup_type(type);
auto type_info = dynamic_cast<StructureType*>(uncast_type_info);
if (!type_info) {
throw std::runtime_error(fmt::format("Type {} wasn't a structure type.", type.print()));
}
assert(type_info->get_size_in_memory() == 0x1c);
// get words for real
auto offset_location = label.offset - type_info->get_offset();
int word_count = (type_info->get_size_in_memory() + 3) / 4;
std::vector<LinkedWord> obj_words;
obj_words.insert(obj_words.begin(),
words.at(label.target_segment).begin() + (offset_location / 4),
words.at(label.target_segment).begin() + (offset_location / 4) + word_count);
// 0 launcher
// 4 fade-after (meters)
// 8 falloff-to (meters)
// flags, period
// length, offset
// hour-mask
// binding
s32 launcher = word_as_s32(obj_words.at(0));
float fade_after_meters = word_as_float(obj_words.at(1)) / METER_LENGTH;
float falloff_to_meters = word_as_float(obj_words.at(2)) / METER_LENGTH;
u32 fp = word_as_s32(obj_words.at(3));
u16 flags = fp & 0xffff;
u16 period = fp >> 16;
u32 lo = word_as_s32(obj_words.at(4));
u16 length = lo & 0xffff;
u16 offset = lo >> 16;
u32 hour_mask = word_as_s32(obj_words.at(5));
u32 binding = word_as_s32(obj_words.at(6));
std::string result =
fmt::format("(sp-item {}", launcher); // use decimal, so it matches array idx
if (fade_after_meters != 0.0) {
result += fmt::format(" :fade-after (meters {})", float_to_string(fade_after_meters));
}
if (falloff_to_meters != 0.0) {
result += fmt::format(" :falloff-to (meters {})", float_to_string(falloff_to_meters));
}
if (flags) {
auto things = decompile_bitfield_enum_from_int(TypeSpec("sp-group-item-flag"), ts, flags);
result += " :flags (";
for (auto& thing : things) {
result += thing;
result += ' ';
}
result.pop_back();
result += ')';
}
if (period) {
result += fmt::format(" :period {}", period);
}
if (length) {
result += fmt::format(" :length {}", length);
}
if (offset) {
result += fmt::format(" :offset {}", offset);
}
if (hour_mask) {
result += fmt::format(" :hour-mask #b{:b}", hour_mask);
}
if (binding) {
result += fmt::format(" :binding {}", binding);
}
result += ')';
fmt::print("Result: {}\n", result);
return pretty_print::to_symbol(result);
}
goos::Object decompile_sparticle_field_init(const TypeSpec& type,
const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts,
const LinkedObjectFile* file) {
auto normal = decompile_structure(type, label, labels, words, ts, file, false);
fmt::print("Doing: {}\n", normal.print());
auto uncast_type_info = ts.lookup_type(type);
auto type_info = dynamic_cast<StructureType*>(uncast_type_info);
if (!type_info) {
throw std::runtime_error(fmt::format("Type {} wasn't a structure type.", type.print()));
}
assert(type_info->get_size_in_memory() == 16);
// get words for real
auto offset_location = label.offset - type_info->get_offset();
int word_count = (type_info->get_size_in_memory() + 3) / 4;
std::vector<LinkedWord> obj_words;
obj_words.insert(obj_words.begin(),
words.at(label.target_segment).begin() + (offset_location / 4),
words.at(label.target_segment).begin() + (offset_location / 4) + word_count);
assert(obj_words.at(0).kind == LinkedWord::PLAIN_DATA);
u16 field_id = obj_words.at(0).data & 0xffff;
u16 flags = obj_words.at(0).data >> 16;
assert(field_id <= (u32)FieldId::SPT_END);
auto field_name = decompile_int_enum_from_int(TypeSpec("sp-field-id"), ts, field_id);
const auto& field_info = field_kinds[field_id];
if (!field_info.known) {
throw std::runtime_error("Unknown sparticle field: " + field_name);
}
auto flag_name = decompile_int_enum_from_int(TypeSpec("sp-flag"), ts, flags);
goos::Object result;
if (flag_name == "copy-from-other-field") {
result = decompile_sparticle_from_other(obj_words, field_name, flag_name);
} else {
switch (field_info.kind) {
case FieldKind::TEXTURE_ID:
result = decompile_sparticle_tex_field_init(obj_words, ts, field_name, flag_name);
break;
case FieldKind::FLOAT_WITH_RAND:
result = decompile_sparticle_float_with_rand_init(obj_words, field_name, flag_name);
break;
case FieldKind::METER_WITH_RAND:
result = decompile_sparticle_float_meters_with_rand_init(obj_words, field_name, flag_name);
break;
case FieldKind::DEGREES_WITH_RAND:
result = decompile_sparticle_float_degrees_with_rand_init(obj_words, field_name, flag_name);
break;
// case FieldKind::INT_WITH_RAND:
// result = decompile_sparticle_int_with_rand_init(obj_words, field_name, flag_name);
// break;
case FieldKind::PLAIN_INT_WITH_RANDS:
result = decompile_sparticle_int_with_rand_init(obj_words, field_name, flag_name);
break;
case FieldKind::PLAIN_INT:
result = decompile_sparticle_int_init(obj_words, field_name, flag_name);
break;
case FieldKind::CPUINFO_FLAGS:
result = decompile_sparticle_flags(obj_words, ts, field_name, flag_name);
break;
case FieldKind::END_FLAG:
result = decompile_sparticle_end(obj_words, field_name, flag_name);
break;
case FieldKind::LAUNCHER_BY_ID:
result = decompile_sparticle_launcher_by_id(obj_words, field_name, flag_name);
break;
case FieldKind::NO_FANCY_DECOMP:
result = normal;
break;
case FieldKind::FUNCTION:
result = decompile_sparticle_func(obj_words, field_name, flag_name);
break;
case FieldKind::USERDATA:
result = decompile_sparticle_userdata(obj_words, field_name, flag_name, normal);
break;
default:
assert(false);
}
}
fmt::print("Result: {}\n\n", result.print());
return result;
}
} // namespace decompiler
/*
(deftype sp-field-init-spec (structure)
((field sp-field-id :offset-assert 0)
(flags sp-flag :offset-assert 2)
(initial-valuef float :offset-assert 4)
(random-rangef float :offset-assert 8)
(random-multf float :offset-assert 12)
(initial-value int32 :offset 4)
(random-range int32 :offset 8)
(random-mult int32 :offset 12)
(sym symbol :offset 4) ;; moved
(func function :offset 4)
(tex uint32 :offset 4)
(pntr pointer :offset 4)
;; gap
(sound basic :offset 4)
)
:method-count-assert 9
:size-assert #x10
:flag-assert #x900000010
)
*/
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "common/goos/Object.h"
#include "common/type_system/TypeSpec.h"
#include "decompiler/Disasm/DecompilerLabel.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
namespace decompiler {
goos::Object decompile_sparticle_field_init(const TypeSpec& type,
const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts,
const LinkedObjectFile* file);
goos::Object decompile_sparticle_group_item(const TypeSpec& type,
const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts,
const LinkedObjectFile* file);
} // namespace decompiler
+2 -1
View File
@@ -203,4 +203,5 @@
- It is now a warning to redefine a constant.
- Fix a bug where the size of static boxed arrays was only `length` and not `allocated-length`
- It is now possible to call a method on a forward declared type. The forward declared type must be a basic.
- Using `->` on a plain `pointer` or `inline-array` now generates an error instead of crashing the compiler
- Using `->` on a plain `pointer` or `inline-array` now generates an error instead of crashing the compiler
- It is now possible to use a macro to provide a static inline array element definition
+326
View File
@@ -0,0 +1,326 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; .function sp-init-fields!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;BAD PROLOGUE
;; Warnings:
;; INFO: Flagged as asm by config
;; INFO: Assembly Function
# inputs: a0 output-data (multiple types)
# a1 init-spec-array
# a2 start-field-idx
# a3 end-field-idx (not inclusive, not that it matters?)
# t0 usually #f.
## Initialize
B0:
L155:
or v1, a0, r0
or v1, a2, r0
or v1, a3, r0
or v1, t0, r0
sll r0, r0, 0
daddiu a2, a2, 1 # actual first field to care about.
or v0, a1, r0 # v0 = current field?
## Loop to advance our init spec until we hit a field >= our minimum
B1:
L156:
lh a1, 0(v0) # a1 = field-id
sll r0, r0, 0
dsubu a1, a1, a2 #
sll r0, r0, 0
sll r0, r0, 0
sll r0, r0, 0
bltzl a1, L156
B2:
daddiu v0, v0, 16
## If our range is size 0 (or negative), exit early, we have no fields to initialize
B3:
dsubu a1, a2, a3
sll r0, r0, 0
bgez a1, L169
sll r0, r0, 0
# LOOP TOP:
# v0 = init-spec, a2 = field to init,
B4:
L157:
lh a1, 0(v0)
sll r0, r0, 0
bne a1, a2, L167 # jump to end if field doesn't match spec.
vrget.xyzw vf1
B5:
vsqrt Q, vf1.x
lh a1, 2(v0) # a1 = flags
vaddq.x vf2, vf0, Q # more random?
lw t2, 8(v0) # t2 = random-range (float or int possible here)
addiu v1, r0, 7
beq a2, v1, L159 # spt-sound
addiu t1, r0, 1
B6:
beq a1, t1, L160 # flags = 1.
addiu t1, r0, 2
B7:
beq a1, t1, L162 # flags = 2.
addiu t1, r0, 3
B8:
beq a1, t1, L163 # flags = 3
addiu t1, r0, 5
B9:
beq a1, t1, L164 # flags = 5
addiu t1, r0, 6
B10:
beq a1, t1, L165 # flags = 6
addiu t1, r0, 4
B11:
beq a1, t1, L166 # flags = 4
sll r0, r0, 0
B12:
beq t2, r0, L158 # flags = 0
sll r0, r0, 0
B13:
vrxorw vf2 # other flags
lw t1, 12(v0)
vrnext.xyzw vf1
lw t3, 4(v0)
vsubw.xyzw vf1, vf1, vf0
sll r0, r0, 0
qmtc2.i vf2, t2
sll r0, r0, 0
vitof0.xyzw vf2, vf2
sll r0, r0, 0
vmul.xyzw vf1, vf1, vf2
sll r0, r0, 0
vftoi0.xyzw vf1, vf1
sll r0, r0, 0
qmfc2.i t2, vf1
sll r0, r0, 0
mult3 t2, t2, t1
sll r0, r0, 0
daddu t2, t2, t3
daddiu a2, a2, 1
sw t2, 0(a0)
daddiu a0, a0, 4
bne a2, a3, L157
daddiu v0, v0, 16
B14:
jr ra
sll r0, r0, 0
B15:
L158: # flags = 0
lw t3, 4(v0) ## int32
daddiu a2, a2, 1
sw t3, 0(a0)
daddiu a0, a0, 4
bne a2, a3, L157
daddiu v0, v0, 16
B16:
jr ra
sll r0, r0, 0
B17:
L159: ## special case for sound.
lw t3, 4(v0)
daddiu a2, a2, 1
sw t3, 0(a0)
daddiu a0, a0, 4
bne a2, a3, L157
daddiu v0, v0, 16
B18:
jr ra
sll r0, r0, 0
B19:
L160: ## flags = 1
beq t2, r0, L161 # skip if range = 0
vrxorw vf2
B20:
vrnext.xyzw vf1
lw t1, 12(v0) ## t1 = multiplier
vsubw.xyzw vf1, vf1, vf0
lw t3, 4(v0) ## t3 = initial-value
qmtc2.i vf2, t2
sll r0, r0, 0
vmul.xyzw vf1, vf1, vf2
sll r0, r0, 0
qmtc2.i vf2, t1
sll r0, r0, 0
vmul.xyzw vf1, vf1, vf2
sll r0, r0, 0
qmtc2.i vf2, t3
sll r0, r0, 0
vadd.xyzw vf1, vf1, vf2
sll r0, r0, 0
qmfc2.i t2, vf1
daddiu a2, a2, 1
sw t2, 0(a0)
daddiu a0, a0, 4
bne a2, a3, L157
daddiu v0, v0, 16
B21:
jr ra
sll r0, r0, 0
B22:
L161:
lw t3, 4(v0)
daddiu a2, a2, 1
sw t3, 0(a0)
daddiu a0, a0, 4
bne a2, a3, L157
daddiu v0, v0, 16
B23:
jr ra
sll r0, r0, 0
B24:
L162:
beq t2, r0, L161 ## flags = 2
vrxorw vf2
B25:
vrnext.xyzw vf1
lw t1, 12(v0)
vsubw.xyzw vf1, vf1, vf0
daddiu t2, t2, 1
qmtc2.i vf2, t2
lw t3, 4(v0)
vitof0.xyzw vf2, vf2
sll r0, r0, 0
vmul.xyzw vf1, vf1, vf2
sll r0, r0, 0
vftoi0.xyzw vf1, vf1
sll r0, r0, 0
vitof0.xyzw vf1, vf1
sll r0, r0, 0
qmtc2.i vf2, t1
sll r0, r0, 0
vmul.xyzw vf1, vf1, vf2
sll r0, r0, 0
qmtc2.i vf2, t3
sll r0, r0, 0
vadd.xyzw vf1, vf1, vf2
sll r0, r0, 0
qmfc2.i t2, vf1
daddiu a2, a2, 1
sw t2, 0(a0)
daddiu a0, a0, 4
bne a2, a3, L157
daddiu v0, v0, 16
B26:
jr ra
sll r0, r0, 0
B27:
L163: ## flags = 3
lw t1, 4(v0) # t1 = init-val
sll r0, r0, 0
dsll t1, t1, 2 # val * 4
sll r0, r0, 0
daddu t1, t1, a0 # t1 = dest + val*4
sll r0, r0, 0
lw t3, 0(t1)
daddiu a2, a2, 1
sw t3, 0(a0)
daddiu a0, a0, 4
bne a2, a3, L157
daddiu v0, v0, 16
B28:
jr ra
sll r0, r0, 0
B29:
L164: ## flags = 5
lw t1, 4(v0)
sll r0, r0, 0
lw t3, 0(t1)
daddiu a2, a2, 1
sw t3, 0(a0)
daddiu a0, a0, 4
bne a2, a3, L157
daddiu v0, v0, 16
B30:
jr ra
sll r0, r0, 0
B31:
L165:
lw t1, *part-id-table*(s7) ## flags = 6
sll r0, r0, 0
lw t3, 4(v0)
sll r0, r0, 0
dsll t3, t3, 2
daddiu t1, t1, 12
daddu t3, t3, t1
sll r0, r0, 0
lw t2, 0(t3)
daddiu a2, a2, 1
sw t2, 0(a0)
daddiu a0, a0, 4
bne a2, a3, L157
daddiu v0, v0, 16
B32:
jr ra
sll r0, r0, 0
B33:
L166:
lw t3, 4(v0) ## flags = 4
daddiu a2, a2, 1
sw t3, 0(a0)
daddiu a0, a0, 4
bne a2, a3, L157
daddiu v0, v0, 16
B34:
jr ra
sll r0, r0, 0
B35:
L167:
bnel t0, s7, L168
B36:
sw r0, 0(a0)
B37:
L168:
daddiu a2, a2, 1
daddiu a0, a0, 4
bne a2, a3, L157
sll r0, r0, 0
B38:
L169:
jr ra
sll r0, r0, 0
jr ra
daddu sp, sp, r0
sll r0, r0, 0
sll r0, r0, 0
sll r0, r0, 0
File diff suppressed because it is too large Load Diff
+5
View File
@@ -60,6 +60,8 @@ set(RUNTIME_SOURCE
kernel/ksound.cpp
mips2c/mips2c_table.cpp
mips2c/functions/draw_string.cpp
mips2c/functions/sparticle.cpp
mips2c/functions/sparticle_launcher.cpp
mips2c/functions/test_func.cpp
overlord/dma.cpp
overlord/fake_iso.cpp
@@ -82,9 +84,12 @@ set(RUNTIME_SOURCE
graphics/dma/dma_copy.cpp
graphics/dma/gs.cpp
graphics/opengl_renderer/BucketRenderer.cpp
graphics/opengl_renderer/debug_gui.cpp
graphics/opengl_renderer/DirectRenderer.cpp
graphics/opengl_renderer/OpenGLRenderer.cpp
graphics/opengl_renderer/Shader.cpp
graphics/opengl_renderer/SpriteRenderer.cpp
graphics/opengl_renderer/TextureUploadHandler.cpp
graphics/texture/TextureConverter.cpp
graphics/texture/TexturePool.cpp
graphics/pipelines/opengl.cpp
+4
View File
@@ -10,6 +10,10 @@ TWEAKVAL.MUS resources/TWEAKVAL.MUS
VAGDIR.AYB resources/VAGDIR.AYB
SCREEN1.USA resources/SCREEN1.USA
0COMMON.TXT out/iso/0COMMON.TXT
1COMMON.TXT out/iso/1COMMON.TXT
2COMMON.TXT out/iso/2COMMON.TXT
3COMMON.TXT out/iso/3COMMON.TXT
4COMMON.TXT out/iso/4COMMON.TXT
5COMMON.TXT out/iso/5COMMON.TXT
0TEST.TXT out/iso/0TEST.TXT
VI1.DGO out/iso/VI1.DGO
+6 -5
View File
@@ -21,9 +21,10 @@ std::string VifCode::print() {
case Kind::NOP:
result = "NOP";
break;
case Kind::STCYCL:
result = "STCYCL";
break;
case Kind::STCYCL: {
VifCodeStcycl stcycl(immediate);
result = fmt::format("STCYCL cl: {} wl: {}", stcycl.cl, stcycl.wl);
} break;
case Kind::OFFSET:
result = "OFFSET";
break;
@@ -48,7 +49,6 @@ std::string VifCode::print() {
case Kind::FLUSH:
result = "FLUSH";
break;
case Kind::FLUSHA:
result = "FLUSHA";
break;
@@ -81,7 +81,8 @@ std::string VifCode::print() {
break;
default:
fmt::print("Unhandled vif code {}", (int)kind);
assert(false);
result = "???";
// assert(false);
break;
}
// TODO: the rest of the VIF code.
+34 -1
View File
@@ -10,6 +10,15 @@
#include "common/util/assert.h"
#include "common/common_types.h"
struct DmaStats {
double sync_time_ms = 0;
int num_tags = 0;
int num_data_bytes = 0;
int num_chunks = 0;
int num_copied_bytes = 0;
int num_fixups = 0;
};
struct DmaTag {
enum class Kind : u8 {
REFE = 0,
@@ -45,6 +54,7 @@ struct VifCode {
BASE = 0b11,
ITOP = 0b100,
STMOD = 0b101,
PC_PORT = 0b1000, // not a valid PS2 VIF code, but we use this to signal PC-PORT specific stuff
MSK3PATH = 0b110,
MARK = 0b111,
FLUSHE = 0b10000,
@@ -59,7 +69,8 @@ struct VifCode {
MPG = 0b1001010,
DIRECT = 0b1010000,
DIRECTHL = 0b1010001,
UNPACK_MASK = 0b1100000 // unpack is a bunch of commands.
UNPACK_MASK = 0b1100000, // unpack is a bunch of commands.
UNPACK_V4_32 = 0b1101100,
};
VifCode(u32 value) {
@@ -76,3 +87,25 @@ struct VifCode {
std::string print();
};
struct VifCodeStcycl {
explicit VifCodeStcycl(const VifCode& code) {
cl = code.immediate & 0xff;
wl = (code.immediate >> 8);
}
u16 cl;
u16 wl;
};
struct VifCodeUnpack {
explicit VifCodeUnpack(const VifCode& code) {
addr_qw = code.immediate & 0b1111111111;
is_unsigned = (code.immediate & (1 << 14));
use_tops_flag = (code.immediate & (1 << 15));
}
u16 addr_qw;
bool is_unsigned; // only care for 8/16 bit data.
bool use_tops_flag; // uses double buffering
};
+3
View File
@@ -28,6 +28,9 @@ struct DmaTransfer {
u32 vif0() const { return transferred_tag & 0xffffffff; }
u32 vif1() const { return (transferred_tag >> 32) & 0xffffffff; }
VifCode vifcode0() const { return VifCode(vif0()); }
VifCode vifcode1() const { return VifCode(vif1()); }
};
class DmaFollower {
+18
View File
@@ -1,7 +1,9 @@
#include "common/goal_constants.h"
#include "game/graphics/dma/dma_chain_read.h"
#include "dma_copy.h"
#include "third-party/fmt/core.h"
#include "common/util/Timer.h"
/*!
* Convert a DMA chain to an array of bytes that can be directly fed to VIF.
@@ -27,6 +29,11 @@ std::vector<u8> flatten_dma(const DmaFollower& in) {
return result;
}
void FixedChunkDmaCopier::serialize_last_result(Serializer& serializer) {
serializer.from_ptr(&m_result.start_offset);
serializer.from_pod_vector(&m_result.data);
}
FixedChunkDmaCopier::FixedChunkDmaCopier(u32 main_memory_size)
: m_main_memory_size(main_memory_size), m_chunk_count(main_memory_size / chunk_size) {
assert(chunk_size * m_chunk_count == m_main_memory_size); // make sure the memory size is valid.
@@ -35,15 +42,20 @@ FixedChunkDmaCopier::FixedChunkDmaCopier(u32 main_memory_size)
}
const DmaData& FixedChunkDmaCopier::run(const void* memory, u32 offset, bool verify) {
Timer timer;
m_input_offset = offset;
m_input_data = memory;
std::fill(m_chunk_mask.begin(), m_chunk_mask.end(), false);
m_fixups.clear();
m_result.data.clear();
m_result.stats = DmaStats();
m_result.start_offset = 0;
DmaFollower dma(memory, offset);
while (!dma.ended()) {
auto tag_offset = dma.current_tag_offset();
auto tag = dma.current_tag();
m_result.stats.num_tags++;
// first, make sure we get this tag:
u32 tag_chunk_idx = tag_offset / chunk_size;
@@ -51,6 +63,7 @@ const DmaData& FixedChunkDmaCopier::run(const void* memory, u32 offset, bool ver
m_chunk_mask.at(tag_chunk_idx) = true;
if (tag.addr) {
assert(tag.addr > EE_MAIN_MEM_LOW_PROTECT);
u32 addr_chunk_idx = tag.addr / chunk_size;
u32 addr_offset_in_chunk = tag.addr % chunk_size;
// next, make sure that we get the address (if applicable)
@@ -66,6 +79,7 @@ const DmaData& FixedChunkDmaCopier::run(const void* memory, u32 offset, bool ver
auto transfer = dma.read_and_advance();
if (transfer.size_bytes) {
m_result.stats.num_data_bytes += transfer.size_bytes;
u32 initial_chunk = transfer.data_offset / chunk_size;
m_chunk_mask.at(initial_chunk) = true;
s32 bytes_remaining = transfer.size_bytes;
@@ -89,6 +103,9 @@ const DmaData& FixedChunkDmaCopier::run(const void* memory, u32 offset, bool ver
}
m_result.data.resize(current_out_chunk * chunk_size);
m_result.stats.num_chunks = current_out_chunk;
m_result.stats.num_copied_bytes = m_result.data.size();
m_result.stats.num_fixups = m_fixups.size();
// copy
for (u32 chunk_idx = 0; chunk_idx < m_chunk_mask.size(); chunk_idx++) {
@@ -119,5 +136,6 @@ const DmaData& FixedChunkDmaCopier::run(const void* memory, u32 offset, bool ver
}
}
m_result.stats.sync_time_ms = timer.getMs();
return m_result;
}
+11
View File
@@ -4,10 +4,12 @@
#include "common/common_types.h"
#include "game/graphics/dma/dma_chain_read.h"
#include "common/util/Serializer.h"
struct DmaData {
u32 start_offset = 0;
std::vector<u8> data;
DmaStats stats;
};
/*!
@@ -21,8 +23,14 @@ class FixedChunkDmaCopier {
FixedChunkDmaCopier(u32 main_memory_size);
const DmaData& run(const void* memory, u32 offset, bool verify = false);
void serialize_last_result(Serializer& serializer);
const DmaData& get_last_result() const { return m_result; }
const void* get_last_input_data() const { return m_input_data; }
u32 get_last_input_offset() const { return m_input_offset; }
private:
struct Fixup {
u32 source_chunk;
@@ -36,6 +44,9 @@ class FixedChunkDmaCopier {
u32 m_chunk_count = 0;
std::vector<u32> m_chunk_mask;
DmaData m_result;
u32 m_input_offset = 0;
const void* m_input_data = nullptr;
};
/*!
+4
View File
@@ -348,4 +348,8 @@ std::string GsTex0::print() const {
return fmt::format(
"tbp0: {} tbw: {} psm: {} tw: {} th: {} tcc: {} tfx: {} cbp: {} cpsm: {} csm: {}\n", tbp0(),
tbw(), psm(), tw(), th(), tcc(), tfx(), cbp(), cpsm(), csm());
}
std::string GsPrim::print() const {
return fmt::format("0x{:x}, kind {}\n", data, kind());
}
+7
View File
@@ -54,10 +54,15 @@ struct GifTag {
std::string print() const;
GifTag(const u8* ptr) { memcpy(data, ptr, 16); }
GifTag() = default;
u64 data[2];
};
struct AdGif {
GifTag giftag[5];
};
std::string reg_descriptor_name(GifTag::RegisterDescriptor reg);
enum class GsRegisterAddress : u8 {
@@ -247,6 +252,8 @@ struct GsPrim {
bool operator==(const GsPrim& other) const { return data == other.data; }
bool operator!=(const GsPrim& other) const { return data != other.data; }
std::string print() const;
};
struct GsTex0 {
+13 -1
View File
@@ -12,7 +12,13 @@
enum class BucketId {
BUCKET0 = 0,
BUCKET1 = 1,
TFRAG_TEX_LEVEL0 = 5,
SHRUB_TEX_LEVEL0 = 19,
ALPHA_TEX_LEVEL0 = 31,
PRIS_TEX_LEVEL0 = 48,
WATER_TEX_LEVEL0 = 57,
// ...
PRE_SPRITE_TEX = 65, // maybe it's just common textures?
SPRITE = 66,
DEBUG_DRAW_0 = 67,
DEBUG_DRAW_1 = 68,
@@ -31,6 +37,10 @@ struct SharedRenderState {
u32 buckets_base = 0; // address of buckets array.
u32 next_bucket = 0; // address of next bucket that we haven't started rendering in buckets
u32 default_regs_buffer = 0; // address of the default regs chain.
void* ee_main_memory = nullptr;
u32 offset_of_s7;
bool dump_playback = false;
};
/*!
@@ -44,7 +54,8 @@ class BucketRenderer {
virtual ~BucketRenderer() = default;
bool& enabled() { return m_enabled; }
virtual bool empty() const { return false; }
virtual void draw_debug_window() {}
virtual void draw_debug_window() = 0;
virtual void serialize(Serializer&) {}
protected:
std::string m_name;
@@ -60,4 +71,5 @@ class EmptyBucketRenderer : public BucketRenderer {
EmptyBucketRenderer(const std::string& name, BucketId my_id);
void render(DmaFollower& dma, SharedRenderState* render_state) override;
bool empty() const override { return true; }
void draw_debug_window() override {}
};
+160 -40
View File
@@ -5,8 +5,8 @@
#include "game/graphics/pipelines/opengl.h"
#include "third-party/imgui/imgui.h"
DirectRenderer::DirectRenderer(const std::string& name, BucketId my_id, int batch_size)
: BucketRenderer(name, my_id), m_prim_buffer(batch_size) {
DirectRenderer::DirectRenderer(const std::string& name, BucketId my_id, int batch_size, Mode mode)
: BucketRenderer(name, my_id), m_prim_buffer(batch_size), m_mode(mode) {
glGenBuffers(1, &m_ogl.vertex_buffer);
glGenBuffers(1, &m_ogl.color_buffer);
glGenBuffers(1, &m_ogl.st_buffer);
@@ -35,9 +35,6 @@ DirectRenderer::~DirectRenderer() {
* Render from a DMA bucket.
*/
void DirectRenderer::render(DmaFollower& dma, SharedRenderState* render_state) {
m_triangles = 0;
m_draw_calls = 0;
// if we're rendering from a bucket, we should start off we a totally reset state:
reset_state();
setup_common_state(render_state);
@@ -66,11 +63,32 @@ void DirectRenderer::draw_debug_window() {
ImGui::Checkbox("Wireframe", &m_debug_state.wireframe);
ImGui::SameLine();
ImGui::Checkbox("No-texture", &m_debug_state.disable_texture);
ImGui::SameLine();
ImGui::Checkbox("red", &m_debug_state.red);
ImGui::SameLine();
ImGui::Checkbox("always", &m_debug_state.always_draw);
if (m_mode == Mode::SPRITE_CPU) {
ImGui::Checkbox("draw1", &m_sprite_mode.do_first_draw);
ImGui::SameLine();
ImGui::Checkbox("draw2", &m_sprite_mode.do_second_draw);
}
ImGui::Text("Triangles: %d", m_triangles);
ImGui::SameLine();
ImGui::Text("Draws: %d", m_draw_calls);
}
float u32_to_float(u32 in) {
double x = (double)in / UINT32_MAX;
return x;
}
float u32_to_sc(u32 in) {
float flt = u32_to_float(in);
return (flt - 0.5) * 16.0;
}
void DirectRenderer::flush_pending(SharedRenderState* render_state) {
if (m_prim_buffer.vert_count == 0) {
return;
@@ -92,6 +110,11 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state) {
m_test_state_needs_gl_update = false;
}
if (m_texture_state.needs_gl_update) {
update_gl_texture(render_state);
m_texture_state.needs_gl_update = false;
}
if (m_debug_state.wireframe) {
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
} else {
@@ -103,9 +126,16 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state) {
render_state->shaders[ShaderId::DIRECT_BASIC].activate();
}
if (m_debug_state.red) {
render_state->shaders[ShaderId::DEBUG_RED].activate();
glDisable(GL_BLEND);
}
// hacks
// glEnable(GL_DEPTH_TEST);
// glDepthFunc(GL_ALWAYS);
if (m_debug_state.always_draw) {
glDisable(GL_DEPTH_TEST);
glDepthFunc(GL_ALWAYS);
}
GLuint vao;
glGenVertexArrays(1, &vao);
@@ -157,10 +187,30 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state) {
glActiveTexture(GL_TEXTURE0);
}
// assert(false);
glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count);
int draw_count = 0;
if (m_mode == Mode::SPRITE_CPU) {
assert(m_texture_state.tcc);
assert(m_prim_gl_state.texture_enable);
if (m_sprite_mode.do_first_draw) {
glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count);
draw_count++;
}
if (m_sprite_mode.do_second_draw) {
render_state->shaders[ShaderId::SPRITE_CPU_AFAIL].activate();
glDepthMask(GL_FALSE);
glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count);
glDepthMask(GL_TRUE);
draw_count++;
}
} else {
glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count);
draw_count++;
}
glBindVertexArray(0);
m_triangles += m_prim_buffer.vert_count / 3;
m_draw_calls++;
m_triangles += draw_count * (m_prim_buffer.vert_count / 3);
m_draw_calls += draw_count;
m_prim_buffer.vert_count = 0;
glDeleteVertexArrays(1, &vao);
@@ -170,7 +220,15 @@ 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) {
render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].activate();
if (m_texture_state.tcc) {
if (m_mode == Mode::SPRITE_CPU) {
render_state->shaders[ShaderId::SPRITE_CPU].activate();
} else {
render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].activate();
}
} else {
render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED_TCC0].activate();
}
update_gl_texture(render_state);
} else {
render_state->shaders[ShaderId::DIRECT_BASIC].activate();
@@ -209,9 +267,9 @@ void DirectRenderer::update_gl_texture(SharedRenderState* render_state) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex->gpu_texture);
// TODO these wrappings are probably wrong.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// Note: CLAMP and CLAMP_TO_EDGE are different...
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glUniform1i(
@@ -227,14 +285,24 @@ void DirectRenderer::update_gl_blend() {
glEnable(GL_BLEND);
// s, d
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
} else if (state.a == GsAlpha::BlendMode::SOURCE &&
state.b == GsAlpha::BlendMode::ZERO_OR_FIXED &&
state.c == GsAlpha::BlendMode::SOURCE && state.d == GsAlpha::BlendMode::DEST) {
// (Cs - 0) * As + Cd
// Cs * As + (1) * CD
glEnable(GL_BLEND);
// s, d
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
} else {
fmt::print("unsupported blend\n");
lg::error("unsupported blend: a {} b {} c {} d {}\n", (int)state.a, (int)state.b, (int)state.c,
(int)state.d);
assert(false);
}
}
void DirectRenderer::update_gl_test() {
const auto& state = m_test_state;
glEnable(GL_DEPTH_TEST);
if (state.zte) {
switch (state.ztst) {
case GsTest::ZTest::NEVER:
@@ -263,6 +331,12 @@ void DirectRenderer::update_gl_test() {
if (state.alpha_test_enable) {
assert(false);
}
if (state.depth_writes) {
glDepthMask(GL_TRUE);
} else {
glDepthMask(GL_FALSE);
}
}
void DirectRenderer::setup_common_state(SharedRenderState* /*render_state*/) {
@@ -345,9 +419,10 @@ void DirectRenderer::render_gif(const u8* data, u32 size, SharedRenderState* ren
u32 offset = 0;
while (!eop) {
assert(offset < size);
GifTag tag(data + offset);
offset += 16;
// fmt::print("Tag: {}\n", tag.print());
// fmt::print("Tag at offset {}: {}\n", offset, tag.print());
// unpack registers.
// faster to do it once outside of the nloop loop.
@@ -377,6 +452,12 @@ void DirectRenderer::render_gif(const u8* data, u32 size, SharedRenderState* ren
case GifTag::RegisterDescriptor::XYZF2:
handle_xyzf2_packed(data + offset, render_state);
break;
case GifTag::RegisterDescriptor::PRIM:
handle_prim_packed(data + offset, render_state);
break;
case GifTag::RegisterDescriptor::TEX0_1:
handle_tex0_1_packed(data + offset, render_state);
break;
default:
fmt::print("Register {} is not supported in packed mode yet\n",
reg_descriptor_name(reg_desc[reg]));
@@ -430,7 +511,7 @@ void DirectRenderer::handle_ad(const u8* data, SharedRenderState* render_state)
switch (addr) {
case GsRegisterAddress::ZBUF_1:
handle_zbuf1(value);
handle_zbuf1(value, render_state);
break;
case GsRegisterAddress::TEST_1:
handle_test1(value, render_state);
@@ -466,6 +547,9 @@ void DirectRenderer::handle_ad(const u8* data, SharedRenderState* render_state)
case GsRegisterAddress::TEX0_1:
handle_tex0_1(value, render_state);
break;
case GsRegisterAddress::MIPTBP1_1:
// TODO this has the address of different mip levels.
break;
default:
fmt::print("Address {} is not supported\n", register_address_name(addr));
assert(false);
@@ -475,19 +559,26 @@ void DirectRenderer::handle_ad(const u8* data, SharedRenderState* render_state)
void DirectRenderer::handle_tex1_1(u64 val) {
GsTex1 reg(val);
// for now, we aren't going to handle mipmapping. I don't think it's used with direct.
assert(reg.mxl() == 0);
// assert(reg.mxl() == 0);
// if that's true, we can ignore LCM, MTBA, L, K
// MMAG/MMIN specify texture filtering. For now, assume always linear
assert(reg.mmag() == true);
assert(reg.mmin() == 1);
if (!(reg.mmin() == 1 || reg.mmin() == 4)) { // with mipmap off, both of these are linear
// lg::error("unsupported mmin");
}
// fmt::print("{}\n", reg.print());
//
}
void DirectRenderer::handle_tex0_1_packed(const u8* data, SharedRenderState* render_state) {
u64 val;
memcpy(&val, data, sizeof(u64));
handle_tex0_1(val, render_state);
}
void DirectRenderer::handle_tex0_1(u64 val, SharedRenderState* render_state) {
GsTex0 reg(val);
// fmt::print("{}\n", reg.print());
// update tbp
if (m_texture_state.current_register != reg) {
@@ -496,6 +587,10 @@ void DirectRenderer::handle_tex0_1(u64 val, SharedRenderState* render_state) {
m_texture_state.using_mt4hh = reg.psm() == GsTex0::PSM::PSMT4HH;
m_prim_gl_state_needs_gl_update = true;
m_texture_state.current_register = reg;
if (m_texture_state.tcc != reg.tcc()) {
m_texture_state.needs_gl_update = true;
}
m_texture_state.tcc = reg.tcc();
}
// tbw: assume they got it right
@@ -503,8 +598,6 @@ void DirectRenderer::handle_tex0_1(u64 val, SharedRenderState* render_state) {
// tw: assume they got it right
// th: assume they got it right
// these mean that the texture is multiplied, and uses the alpha from the clut.
assert(reg.tcc() == 1);
assert(reg.tfx() == GsTex0::TextureFunction::MODULATE);
// cbp: assume they got it right
@@ -518,7 +611,7 @@ void DirectRenderer::handle_texa(u64 val) {
// rgba16 isn't used so this doesn't matter?
// but they use sane defaults anyway
assert(reg.ta0() == 0);
assert(reg.ta1() == 0x80);
assert(reg.ta1() == 0x80); // note: check rgba16_to_rgba32 if this changes.
assert(reg.aem() == false);
}
@@ -537,11 +630,6 @@ void DirectRenderer::handle_rgbaq_packed(const u8* data) {
m_prim_building.rgba_reg[3] = data[12];
}
float u32_to_float(u32 in) {
double x = (double)in / UINT32_MAX;
return x * 2 - 1;
}
void DirectRenderer::handle_xyzf2_packed(const u8* data, SharedRenderState* render_state) {
u32 x, y;
memcpy(&x, data, 4);
@@ -550,6 +638,7 @@ void DirectRenderer::handle_xyzf2_packed(const u8* data, SharedRenderState* rend
u64 upper;
memcpy(&upper, data + 8, 8);
u32 z = (upper >> 4) & 0xffffff;
u8 f = (upper >> 36);
bool adc = upper & (1ull << 47);
assert(!adc);
@@ -557,21 +646,28 @@ void DirectRenderer::handle_xyzf2_packed(const u8* data, SharedRenderState* rend
handle_xyzf2_common(x, y, z, f, render_state);
}
void debug_print_vtx(const math::Vector<u32, 3>& vtx) {
fmt::print("{} {}\n", u32_to_float(vtx.x()), u32_to_float(vtx.y()));
}
void DirectRenderer::handle_zbuf1(u64 val) {
void DirectRenderer::handle_zbuf1(u64 val, SharedRenderState* render_state) {
// note: we can basically ignore this. There's a single z buffer that's always configured the same
// way - 24-bit, at offset 448.
GsZbuf x(val);
assert(x.zmsk()); // note: not sure if this ever changes or not.
assert(x.psm() == TextureFormat::PSMZ24);
assert(x.zbp() == 448);
bool write = !x.zmsk();
// assert(write);
if (write != m_test_state.depth_writes) {
flush_pending(render_state);
m_test_state_needs_gl_update = true;
m_test_state.depth_writes = !write;
}
}
void DirectRenderer::handle_test1(u64 val, SharedRenderState* render_state) {
GsTest reg(val);
assert(!reg.alpha_test_enable());
assert(!reg.date());
assert(!(val & 1));
if (m_test_state.current_register != reg) {
flush_pending(render_state);
m_test_state.from_register(reg);
@@ -596,6 +692,12 @@ void DirectRenderer::handle_clamp1(u64 val) {
assert(val == 0b101); // clamp s and t.
}
void DirectRenderer::handle_prim_packed(const u8* data, SharedRenderState* render_state) {
u64 val;
memcpy(&val, data, sizeof(u64));
handle_prim(val, render_state);
}
void DirectRenderer::handle_prim(u64 val, SharedRenderState* render_state) {
if (m_prim_building.tri_strip_startup) {
m_prim_building.tri_strip_startup = 0;
@@ -629,15 +731,16 @@ void DirectRenderer::handle_xyzf2_common(u32 x,
u32 z,
u8 f,
SharedRenderState* render_state) {
assert(z < (1 << 24));
(void)f; // TODO: do something with this.
if (m_prim_buffer.is_full()) {
flush_pending(render_state);
}
// assert(f == 0);
// assert(f == 0);
m_prim_building.building_st.at(m_prim_building.building_idx) = m_prim_building.st_reg;
m_prim_building.building_rgba.at(m_prim_building.building_idx) = m_prim_building.rgba_reg;
m_prim_building.building_vert.at(m_prim_building.building_idx) = {x << 16, y << 16, z};
m_prim_building.building_vert.at(m_prim_building.building_idx) = {x << 16, y << 16, z << 8};
m_prim_building.building_idx++;
switch (m_prim_building.kind) {
@@ -694,6 +797,22 @@ void DirectRenderer::handle_xyzf2_common(u32 x,
}
}
break;
case GsPrim::Kind::TRI_FAN: {
if (m_prim_building.tri_strip_startup < 2) {
m_prim_building.tri_strip_startup++;
} else {
if (m_prim_building.building_idx == 2) {
// nothing.
} else if (m_prim_building.building_idx == 3) {
m_prim_building.building_idx = 1;
}
for (int i = 0; i < 3; i++) {
m_prim_buffer.push(m_prim_building.building_rgba[i], m_prim_building.building_vert[i],
m_prim_building.building_st[i]);
}
}
} break;
case GsPrim::Kind::LINE: {
if (m_prim_building.building_idx == 2) {
math::Vector<double, 3> pt0 = m_prim_building.building_vert[0].cast<double>();
@@ -729,11 +848,9 @@ void DirectRenderer::handle_xyzf2_common(u32 x,
}
void DirectRenderer::handle_xyzf2(u64 val, SharedRenderState* render_state) {
// m_prim_buffer.rgba_u8[m_prim_buffer.vert_count] = m_prim_building.rgba;
u32 x = val & 0xffff;
u32 y = (val >> 16) & 0xffff;
u32 z = (val >> 32) & 0xfffff;
u32 z = (val >> 32) & 0xffffff;
u32 f = (val >> 56) & 0xff;
handle_xyzf2_common(x, y, z, f, render_state);
@@ -752,6 +869,9 @@ void DirectRenderer::reset_state() {
m_texture_state = TextureState();
m_prim_building = PrimBuildState();
m_triangles = 0;
m_draw_calls = 0;
}
void DirectRenderer::TestState::from_register(GsTest reg) {
+24 -4
View File
@@ -18,7 +18,12 @@
*/
class DirectRenderer : public BucketRenderer {
public:
DirectRenderer(const std::string& name, BucketId my_id, int batch_size);
// specializations of direct renderer to handle certain outputs.
enum class Mode {
NORMAL, // use for general debug drawing, font.
SPRITE_CPU // use for sprites (does the appropriate alpha test)
};
DirectRenderer(const std::string& name, BucketId my_id, int batch_size, Mode mode);
~DirectRenderer();
void render(DmaFollower& dma, SharedRenderState* render_state) override;
@@ -46,19 +51,23 @@ class DirectRenderer : public BucketRenderer {
*/
void flush_pending(SharedRenderState* render_state);
void draw_debug_window() override;
private:
void handle_ad(const u8* data, SharedRenderState* render_state);
void handle_zbuf1(u64 val);
void handle_zbuf1(u64 val, SharedRenderState* render_state);
void handle_test1(u64 val, SharedRenderState* render_state);
void handle_alpha1(u64 val, SharedRenderState* render_state);
void handle_pabe(u64 val);
void handle_clamp1(u64 val);
void handle_prim(u64 val, SharedRenderState* render_state);
void handle_prim_packed(const u8* data, SharedRenderState* render_state);
void handle_rgbaq(u64 val);
void handle_xyzf2(u64 val, SharedRenderState* render_state);
void handle_st_packed(const u8* data);
void handle_rgbaq_packed(const u8* data);
void handle_xyzf2_packed(const u8* data, SharedRenderState* render_state);
void handle_tex0_1_packed(const u8* data, SharedRenderState* render_state);
void handle_tex0_1(u64 val, SharedRenderState* render_state);
void handle_tex1_1(u64 val);
void handle_texa(u64 val);
@@ -70,8 +79,6 @@ class DirectRenderer : public BucketRenderer {
void update_gl_test();
void update_gl_texture(SharedRenderState* render_state);
void draw_debug_window() override;
struct TestState {
void from_register(GsTest reg);
@@ -86,6 +93,8 @@ class DirectRenderer : public BucketRenderer {
bool zte = true;
GsTest::ZTest ztst = GsTest::ZTest::GEQUAL;
bool depth_writes = true;
} m_test_state;
struct BlendState {
@@ -120,6 +129,8 @@ class DirectRenderer : public BucketRenderer {
GsTex0 current_register;
u32 texture_base_ptr = 0;
bool using_mt4hh = false;
bool tcc = false;
bool needs_gl_update = true;
} m_texture_state;
// state set through the prim/rgbaq register that doesn't require changing GL stuff
@@ -163,6 +174,8 @@ class DirectRenderer : public BucketRenderer {
struct {
bool disable_texture = false;
bool wireframe = false;
bool red = false;
bool always_draw = false;
} m_debug_state;
int m_triangles = 0;
@@ -171,4 +184,11 @@ class DirectRenderer : public BucketRenderer {
bool m_prim_gl_state_needs_gl_update = true;
bool m_test_state_needs_gl_update = true;
bool m_blend_state_needs_gl_update = true;
struct SpriteMode {
bool do_first_draw = true;
bool do_second_draw = true;
} m_sprite_mode;
Mode m_mode;
};
@@ -3,6 +3,8 @@
#include "common/log/log.h"
#include "game/graphics/pipelines/opengl.h"
#include "game/graphics/opengl_renderer/DirectRenderer.h"
#include "game/graphics/opengl_renderer/SpriteRenderer.h"
#include "game/graphics/opengl_renderer/TextureUploadHandler.h"
#include "third-party/imgui/imgui.h"
// for the vif callback
@@ -51,13 +53,18 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr<TexturePool> texture_pool)
* Construct bucket renderers. We can specify different renderers for different buckets
*/
void OpenGLRenderer::init_bucket_renderers() {
// For example, set up bucket 0:
init_bucket_renderer<EmptyBucketRenderer>("bucket0", BucketId::BUCKET0);
// TODO what the heck is drawing to debug-draw-0 on init?
init_bucket_renderer<DirectRenderer>("sprite", BucketId::SPRITE, 102);
init_bucket_renderer<DirectRenderer>("debug-draw-0", BucketId::DEBUG_DRAW_0, 102);
init_bucket_renderer<DirectRenderer>("debug-draw-1", BucketId::DEBUG_DRAW_1, 102);
init_bucket_renderer<TextureUploadHandler>("tfrag-tex-0", BucketId::TFRAG_TEX_LEVEL0);
init_bucket_renderer<TextureUploadHandler>("shrub-tex-0", BucketId::SHRUB_TEX_LEVEL0);
init_bucket_renderer<TextureUploadHandler>("alpha-tex-0", BucketId::ALPHA_TEX_LEVEL0);
init_bucket_renderer<TextureUploadHandler>("pris-tex-0", BucketId::PRIS_TEX_LEVEL0);
init_bucket_renderer<TextureUploadHandler>("water-tex-0", BucketId::WATER_TEX_LEVEL0);
init_bucket_renderer<TextureUploadHandler>("pre-sprite-tex", BucketId::PRE_SPRITE_TEX);
init_bucket_renderer<SpriteRenderer>("sprite", BucketId::SPRITE);
init_bucket_renderer<DirectRenderer>("debug-draw-0", BucketId::DEBUG_DRAW_0, 102,
DirectRenderer::Mode::NORMAL);
init_bucket_renderer<DirectRenderer>("debug-draw-1", BucketId::DEBUG_DRAW_1, 102,
DirectRenderer::Mode::NORMAL);
// for now, for any unset renderers, just set them to an EmptyBucketRenderer.
for (size_t i = 0; i < m_bucket_renderers.size(); i++) {
@@ -70,15 +77,34 @@ void OpenGLRenderer::init_bucket_renderers() {
/*!
* Main render function. This is called from the gfx loop with the chain passed from the game.
*/
void OpenGLRenderer::render(DmaFollower dma, int window_width_px, int window_height_px) {
void OpenGLRenderer::render(DmaFollower dma,
int window_width_px,
int window_height_px,
bool draw_debug_window,
bool dump_playback) {
m_render_state.dump_playback = dump_playback;
m_render_state.ee_main_memory = dump_playback ? nullptr : g_ee_main_mem;
m_render_state.offset_of_s7 = offset_of_s7();
setup_frame(window_width_px, window_height_px);
m_render_state.texture_pool->remove_garbage_textures();
// draw_test_triangle();
// render the buckets!
dispatch_buckets(dma);
draw_renderer_selection_window();
// add a profile bar for the imgui stuff
vif_interrupt_callback();
if (draw_debug_window) {
draw_renderer_selection_window();
// add a profile bar for the imgui stuff
if (!m_render_state.dump_playback) {
vif_interrupt_callback();
}
}
}
void OpenGLRenderer::serialize(Serializer& ser) {
m_render_state.texture_pool->serialize(ser);
for (auto& renderer : m_bucket_renderers) {
renderer->serialize(ser);
}
}
void OpenGLRenderer::draw_renderer_selection_window() {
@@ -87,15 +113,17 @@ void OpenGLRenderer::draw_renderer_selection_window() {
auto renderer = m_bucket_renderers[i].get();
if (renderer && !renderer->empty()) {
ImGui::PushID(i);
if (ImGui::CollapsingHeader(renderer->name_and_id().c_str())) {
if (ImGui::TreeNode(renderer->name_and_id().c_str())) {
ImGui::Checkbox("Enable", &renderer->enabled());
renderer->draw_debug_window();
ImGui::TreePop();
}
ImGui::PopID();
}
}
if (ImGui::CollapsingHeader("Texture Pool")) {
if (ImGui::TreeNode("Texture Pool")) {
m_render_state.texture_pool->draw_debug_window();
ImGui::TreePop();
}
ImGui::End();
}
@@ -105,8 +133,10 @@ void OpenGLRenderer::draw_renderer_selection_window() {
*/
void OpenGLRenderer::setup_frame(int window_width_px, int window_height_px) {
glViewport(0, 0, window_width_px, window_height_px);
glClearColor(0.5, 0.5, 0.5, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0.5, 0.5, 0.5, 1.0);
glClearDepth(0.0);
glDepthMask(GL_TRUE);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_BLEND);
}
@@ -144,12 +174,14 @@ void OpenGLRenderer::dispatch_buckets(DmaFollower dma) {
// loop over the buckets!
for (int bucket_id = 0; bucket_id < (int)BucketId::MAX_BUCKETS; bucket_id++) {
auto& renderer = m_bucket_renderers[bucket_id];
// fmt::print("render bucket {} with {}\n", bucket_id, renderer->name_and_id());
renderer->render(dma, &m_render_state);
// should have ended at the start of the next chain
assert(dma.current_tag_offset() == m_render_state.next_bucket);
m_render_state.next_bucket += 16;
vif_interrupt_callback();
if (!m_render_state.dump_playback) {
vif_interrupt_callback();
}
}
// TODO ending data.
@@ -10,7 +10,12 @@
class OpenGLRenderer {
public:
OpenGLRenderer(std::shared_ptr<TexturePool> texture_pool);
void render(DmaFollower dma, int window_width_px, int window_height_px);
void render(DmaFollower dma,
int window_width_px,
int window_height_px,
bool draw_debug_window,
bool dump_playback);
void serialize(Serializer& ser);
private:
void setup_frame(int window_width_px, int window_height_px);
@@ -18,7 +23,6 @@ class OpenGLRenderer {
void dispatch_buckets(DmaFollower dma);
void init_bucket_renderers();
void draw_renderer_selection_window();
void draw_texture_window();
template <typename T, class... Args>
void init_bucket_renderer(const std::string& name, BucketId id, Args&&... args) {
+4
View File
@@ -66,5 +66,9 @@ void Shader::activate() {
ShaderLibrary::ShaderLibrary() {
at(ShaderId::TEST_SHADER) = {"test_shader"};
at(ShaderId::DIRECT_BASIC) = {"direct_basic"};
at(ShaderId::DIRECT_BASIC_TEXTURED_TCC0) = {"direct_basic_textured_tcc0"};
at(ShaderId::DIRECT_BASIC_TEXTURED) = {"direct_basic_textured"};
at(ShaderId::DEBUG_RED) = {"debug_red"};
at(ShaderId::SPRITE_CPU) = {"sprite_cpu"};
at(ShaderId::SPRITE_CPU_AFAIL) = {"sprite_cpu_afail"};
}
+10 -1
View File
@@ -21,7 +21,16 @@ class Shader {
};
// note: update the constructor in Shader.cpp
enum class ShaderId { TEST_SHADER = 0, DIRECT_BASIC = 1, DIRECT_BASIC_TEXTURED = 2, MAX_SHADERS };
enum class ShaderId {
TEST_SHADER = 0,
DIRECT_BASIC = 1,
DIRECT_BASIC_TEXTURED = 2,
DIRECT_BASIC_TEXTURED_TCC0 = 3,
DEBUG_RED = 4,
SPRITE_CPU = 5,
SPRITE_CPU_AFAIL = 6,
MAX_SHADERS
};
class ShaderLibrary {
public:
@@ -0,0 +1,769 @@
#include "third-party/fmt/core.h"
#include "third-party/imgui/imgui.h"
#include "SpriteRenderer.h"
namespace {
/*!
* Make sure that the DMA Transfer is a VIF unpack (copy data to VIF memory) with the given
* setup. This is for a transfer with STCYCL followed by UNPACK.
*/
bool verify_unpack_with_stcycl(const DmaTransfer& transfer,
VifCode::Kind unpack_kind,
u16 cl,
u16 wl,
u32 qwc,
u32 addr,
bool usn,
bool flg) {
if (transfer.size_bytes != qwc * 16) {
fmt::print("verify_unpack: bad size {} vs {}\n", transfer.size_bytes, qwc * 16);
return false;
}
if (transfer.vifcode0().kind != VifCode::Kind::STCYCL) {
fmt::print("verify_unpack: bad vifcode 0\n");
return false;
}
if (transfer.vifcode1().kind != unpack_kind) {
fmt::print("verify_unpack: bad vifcode 1\n");
return false;
}
VifCodeStcycl stcycl(transfer.vifcode0());
VifCodeUnpack unpack(transfer.vifcode1());
if (stcycl.cl != cl || stcycl.wl != wl) {
fmt::print("verify_unpack: bad cl/wl {}/{} vs {}/{}\n", stcycl.cl, stcycl.wl, cl, wl);
return false;
}
if (unpack.addr_qw != addr || unpack.use_tops_flag != flg || unpack.is_unsigned != usn) {
fmt::print("verify_unpack: bad unpack {}/{}/{} vs {}/{}/{}", unpack.addr_qw,
unpack.use_tops_flag, unpack.is_unsigned, addr, flg, usn);
return false;
}
if (transfer.vifcode1().num != qwc) {
fmt::print("verify_unpack: bad num {} vs {}\n", transfer.vifcode1().num, qwc);
return false;
}
return true;
}
/*!
* Make sure that the DMA transfer is a VIF unpack with the given setup.
* This is for when there's just an UNPACK.
*/
bool verify_unpack_no_stcycl(const DmaTransfer& transfer,
VifCode::Kind unpack_kind,
u32 qwc,
u32 addr,
bool usn,
bool flg) {
if (transfer.size_bytes != qwc * 16) {
fmt::print("verify_unpack: bad size {} vs {}\n", transfer.size_bytes, qwc * 16);
return false;
}
if (transfer.vifcode0().kind != VifCode::Kind::NOP) {
fmt::print("verify_unpack: bad vifcode 0\n");
return false;
}
if (transfer.vifcode1().kind != unpack_kind) {
fmt::print("verify_unpack: bad vifcode 1\n");
return false;
}
VifCodeUnpack unpack(transfer.vifcode1());
if (unpack.addr_qw != addr || unpack.use_tops_flag != flg || unpack.is_unsigned != usn) {
fmt::print("verify_unpack: bad unpack {}/{}/{} vs {}/{}/{}", unpack.addr_qw,
unpack.use_tops_flag, unpack.is_unsigned, addr, flg, usn);
return false;
}
if (transfer.vifcode1().num != qwc) {
fmt::print("verify_unpack: bad num {} vs {}\n", transfer.vifcode1().num, qwc);
return false;
}
return true;
}
/*!
* Verify the DMA transfer is a VIF unpack (with no STCYCL tag).
* Then, unpack the data to dst.
*/
void unpack_to_no_stcycl(void* dst,
const DmaTransfer& transfer,
VifCode::Kind unpack_kind,
u32 size_bytes,
u32 addr,
bool usn,
bool flg) {
bool ok = verify_unpack_no_stcycl(transfer, unpack_kind, size_bytes / 16, addr, usn, flg);
assert(ok);
assert((size_bytes & 0xf) == 0);
memcpy(dst, transfer.data, size_bytes);
}
/*!
* Does the next DMA transfer look like it could be the start of a 2D group?
*/
bool looks_like_2d_chunk_start(const DmaFollower& dma) {
return dma.current_tag().qwc == 1 && dma.current_tag().kind == DmaTag::Kind::CNT;
}
/*!
* Read the header. Asserts if it's bad.
* Returns the number of sprites.
* Advances 1 dma transfer
*/
u32 process_sprite_chunk_header(DmaFollower& dma) {
auto transfer = dma.read_and_advance();
// note that flg = true, this should use double buffering
bool ok = verify_unpack_with_stcycl(transfer, VifCode::Kind::UNPACK_V4_32, 4, 4, 1,
SpriteDataMem::Header, false, true);
assert(ok);
u32 header[4];
memcpy(header, transfer.data, 16);
assert(header[0] <= SpriteRenderer::SPRITES_PER_CHUNK);
return header[0];
}
} // namespace
SpriteRenderer::SpriteRenderer(const std::string& name, BucketId my_id)
: BucketRenderer(name, my_id),
m_sprite_renderer(fmt::format("{}.sprites", name),
my_id,
100,
DirectRenderer::Mode::SPRITE_CPU),
m_direct_renderer(fmt::format("{}.direct", name), my_id, 100, DirectRenderer::Mode::NORMAL) {}
/*!
* Run the sprite distorter. Currently nothing uses sprite-distorter so this just skips through
* the table upload stuff that runs every frame, even if there are no sprites.
*/
void SpriteRenderer::render_distorter(DmaFollower& dma, SharedRenderState* render_state) {
// Next thing should be the sprite-distorter setup
m_direct_renderer.reset_state();
while (dma.current_tag().qwc != 7) {
auto direct_data = dma.read_and_advance();
m_direct_renderer.render_vif(direct_data.vif0(), direct_data.vif1(), direct_data.data,
direct_data.size_bytes, render_state);
}
m_direct_renderer.flush_pending(render_state);
auto sprite_distorter_direct_setup = dma.read_and_advance();
assert(sprite_distorter_direct_setup.vifcode0().kind == VifCode::Kind::NOP);
assert(sprite_distorter_direct_setup.vifcode1().kind == VifCode::Kind::DIRECT);
assert(sprite_distorter_direct_setup.vifcode1().immediate == 7);
memcpy(m_sprite_distorter_setup, sprite_distorter_direct_setup.data, 7 * 16);
// Next thing should be the sprite-distorter tables
auto sprite_distorter_tables = dma.read_and_advance();
assert(sprite_distorter_tables.size_bytes == 0x8b * 16);
assert(sprite_distorter_tables.vifcode0().kind == VifCode::Kind::STCYCL);
VifCodeStcycl distorter_table_transfer(sprite_distorter_tables.vifcode0());
assert(distorter_table_transfer.cl == 4);
assert(distorter_table_transfer.wl == 4);
// TODO: check unpack cmd (vif1)
// TODO: do something with the table
// next would be the program, but we don't have it.
// TODO: next is the sprite-distorter (currently not used)
}
/*!
* Handle DMA data that does the per-frame setup.
* This should get the dma chain immediately after the call to sprite-draw-distorters.
* It ends right before the sprite-add-matrix-data for the 3d's
*/
void SpriteRenderer::handle_sprite_frame_setup(DmaFollower& dma) {
// first is some direct data
auto direct_data = dma.read_and_advance();
assert(direct_data.size_bytes == 3 * 16);
memcpy(m_sprite_direct_setup, direct_data.data, 3 * 16);
// next would be the program, but it's 0 size on the PC and isn't sent.
// next is the "frame data"
auto frame_data = dma.read_and_advance();
assert(frame_data.size_bytes == (int)sizeof(SpriteFrameData)); // very cool
assert(frame_data.vifcode0().kind == VifCode::Kind::STCYCL);
VifCodeStcycl frame_data_stcycl(frame_data.vifcode0());
assert(frame_data_stcycl.cl == 4);
assert(frame_data_stcycl.wl == 4);
assert(frame_data.vifcode1().kind == VifCode::Kind::UNPACK_V4_32);
VifCodeUnpack frame_data_unpack(frame_data.vifcode1());
assert(frame_data_unpack.addr_qw == SpriteDataMem::FrameData);
assert(frame_data_unpack.use_tops_flag == false);
memcpy(&m_frame_data, frame_data.data, sizeof(SpriteFrameData));
// next, a MSCALF.
auto mscalf = dma.read_and_advance();
assert(mscalf.size_bytes == 0);
assert(mscalf.vifcode0().kind == VifCode::Kind::MSCALF);
assert(mscalf.vifcode0().immediate == SpriteProgMem::Init);
assert(mscalf.vifcode1().kind == VifCode::Kind::FLUSHE);
// next base and offset
auto base_offset = dma.read_and_advance();
assert(base_offset.size_bytes == 0);
assert(base_offset.vifcode0().kind == VifCode::Kind::BASE);
assert(base_offset.vifcode0().immediate == SpriteDataMem::Buffer0);
assert(base_offset.vifcode1().kind == VifCode::Kind::OFFSET);
assert(base_offset.vifcode1().immediate == SpriteDataMem::Buffer1);
}
void SpriteRenderer::render_3d(DmaFollower& dma) {
// one time matrix data
auto matrix_data = dma.read_and_advance();
assert(matrix_data.size_bytes == sizeof(Sprite3DMatrixData));
bool unpack_ok = verify_unpack_with_stcycl(matrix_data, VifCode::Kind::UNPACK_V4_32, 4, 4, 5,
SpriteDataMem::Matrix, false, false);
assert(unpack_ok);
static_assert(sizeof(m_3d_matrix_data) == 5 * 16);
memcpy(&m_3d_matrix_data, matrix_data.data, sizeof(m_3d_matrix_data));
// TODO
}
void SpriteRenderer::render_2d_group0(DmaFollower& dma) {
(void)dma;
// TODO
}
void SpriteRenderer::render_fake_shadow(DmaFollower& dma) {
// TODO
// nop + flushe
auto nop_flushe = dma.read_and_advance();
assert(nop_flushe.vifcode0().kind == VifCode::Kind::NOP);
assert(nop_flushe.vifcode1().kind == VifCode::Kind::FLUSHE);
}
/*!
* Handle DMA data for group1 2d's (HUD)
*/
void SpriteRenderer::render_2d_group1(DmaFollower& dma, SharedRenderState* render_state) {
// one time matrix data upload
auto mat_upload = dma.read_and_advance();
bool mat_ok = verify_unpack_with_stcycl(mat_upload, VifCode::Kind::UNPACK_V4_32, 4, 4, 80,
SpriteDataMem::Matrix, false, false);
assert(mat_ok);
assert(mat_upload.size_bytes == sizeof(m_hud_matrix_data));
memcpy(&m_hud_matrix_data, mat_upload.data, sizeof(m_hud_matrix_data));
// loop through chunks.
while (looks_like_2d_chunk_start(dma)) {
m_debug_stats.blocks_2d_grp1++;
// 4 packets per chunk
// first is the header
u32 sprite_count = process_sprite_chunk_header(dma);
m_debug_stats.count_2d_grp1 += sprite_count;
// second is the vector data
u32 expected_vec_size = sizeof(SpriteVecData2d) * sprite_count;
auto vec_data = dma.read_and_advance();
assert(expected_vec_size <= sizeof(m_vec_data_2d));
unpack_to_no_stcycl(&m_vec_data_2d, vec_data, VifCode::Kind::UNPACK_V4_32, expected_vec_size,
SpriteDataMem::Vector, false, true);
// third is the adgif data
u32 expected_adgif_size = sizeof(AdGif) * sprite_count;
auto adgif_data = dma.read_and_advance();
assert(expected_adgif_size <= sizeof(m_adgif));
unpack_to_no_stcycl(&m_adgif, adgif_data, VifCode::Kind::UNPACK_V4_32, expected_adgif_size,
SpriteDataMem::Adgif, false, true);
// fourth is the actual run!!!!!
auto run = dma.read_and_advance();
assert(run.vifcode0().kind == VifCode::Kind::NOP);
assert(run.vifcode1().kind == VifCode::Kind::MSCAL);
assert(run.vifcode1().immediate == SpriteProgMem::Sprites2dHud);
if (m_enabled) {
do_2d_group1_block_cpu(sprite_count, render_state);
}
}
}
void SpriteRenderer::render(DmaFollower& dma, SharedRenderState* render_state) {
m_debug_stats = {};
// First thing should be a NEXT with two nops. this is a jump from buckets to sprite data
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) {
// sprite 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;
}
// First is the distorter
render_distorter(dma, render_state);
// next, sprite frame setup.
handle_sprite_frame_setup(dma);
// 3d sprites
render_3d(dma);
// 2d draw
render_2d_group0(dma);
// shadow draw
render_fake_shadow(dma);
// 2d draw (HUD)
m_sprite_renderer.reset_state();
render_2d_group1(dma, render_state);
m_sprite_renderer.flush_pending(render_state);
// TODO finish this up.
// fmt::print("next bucket is 0x{}\n", render_state->next_bucket);
while (dma.current_tag_offset() != render_state->next_bucket) {
// auto tag = dma.current_tag();
// fmt::print("@ 0x{:x} tag: {}", dma.current_tag_offset(), tag.print());
auto data = dma.read_and_advance();
VifCode code(data.vif0());
// fmt::print(" vif: {}\n", code.print());
if (code.kind == VifCode::Kind::NOP) {
// fmt::print(" vif: {}\n", VifCode(data.vif1()).print());
}
}
}
void SpriteRenderer::draw_debug_window() {
ImGui::Separator();
ImGui::Text("2D Group 1 (HUD) blocks: %d sprites: %d", m_debug_stats.blocks_2d_grp1,
m_debug_stats.count_2d_grp1);
ImGui::Checkbox("Extra Debug", &m_extra_debug);
if (ImGui::TreeNode("direct")) {
m_sprite_renderer.draw_debug_window();
ImGui::TreePop();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Render (for real)
namespace {
Vector4f matrix_transform(const Matrix4f& mat, const Vector4f& pt) {
// mulaw.xyzw ACC, vf28, vf00
// maddax.xyzw ACC, vf25, vf01
// madday.xyzw ACC, vf26, vf01
// maddz.xyzw vf02, vf27, vf01
return mat.col(3) + (mat.col(0) * pt[0]) + (mat.col(1) * pt[1]) + (mat.col(2) * pt[2]);
}
bool clip_xyz_plus_minus(const Vector4f& pt) {
float pw = std::abs(pt.w());
float mw = -pw;
for (int i = 0; i < 3; i++) {
if (pt[i] > pw) {
return true;
}
if (pt[i] < mw) {
return true;
}
}
return false;
}
void imgui_vec(const Vector4f& vec, const char* name = nullptr, int indent = 0) {
std::string spacing(indent, ' ');
if (name) {
ImGui::Text("%s%s: %f, %f, %f, %f", spacing.c_str(), name, vec.x(), vec.y(), vec.z(), vec.w());
} else {
ImGui::Text("%s%f, %f, %f, %f", spacing.c_str(), vec.x(), vec.y(), vec.z(), vec.w());
}
}
} // namespace
/*!
* Render the sprites!
* This is a somewhat inefficient way to do it:
* The VU program is (poorly) translated to C, then the gs packet is sent to a DirectRenderer.
* In the future we should make a sprite-specific renderer which would have some benefits:
* - do this math on the GPU
* - special case the primitive buffer stuff
*/
void SpriteRenderer::do_2d_group1_block_cpu(u32 count, SharedRenderState* render_state) {
if (m_extra_debug) {
ImGui::Begin("Sprite Extra Debug");
}
// set up double buffering
// xtop vi02 | nop
// nop | nop
// load sprite count from header
// vi04 = count
// ilwr.x vi04, vi02 | nop
// vi02 = m_vec_data_2d
// iaddi vi02, vi02, 0x1 | nop
// vi03 = m_adgif
// iaddiu vi03, vi02, 0x90 | nop
// this VU program uses "software pipelining"
// it's a little bit tricky to use software pipelining in a case like
// this where sometimes you want to reject a sprite entirely and jump ahead
// so sometimes they reset back to L7 on rejection.
// The approach in this translation is to assume we loop back to L7 every time
// and not worry about the pipeline stuff that shows up in L8 and on.
// you can enter from L7 at anytime, they are not assumed to only run on the first go.
// (though if their implementation has bugs we will not replicate them correctly...)
Matrix4f camera_matrix = m_hud_matrix_data.matrix; // vf25, vf26, vf27, vf28
for (u32 sprite_idx = 0; sprite_idx < count; sprite_idx++) {
if (m_extra_debug) {
ImGui::Text("Sprite: %d", sprite_idx);
}
SpriteHud2DPacket packet;
memset(&packet, 0, sizeof(packet));
// L7 (prologue, and early abort)
// ilw.y vi08, 1(vi02) | nop vi08 = matrix
u32 offset_selector = m_vec_data_2d[sprite_idx].matrix();
// moved this out of the loop.
// lq.xyzw vf25, 900(vi00) | nop vf25 = cam_mat
// lq.xyzw vf26, 901(vi00) | nop
// lq.xyzw vf27, 902(vi00) | nop
// lq.xyzw vf28, 903(vi00) | nop
// lq.xyzw vf30, 904(vi08) | nop vf30 = hvdf_offset
// vf30
Vector4f hvdf_offset = offset_selector == 0 ? m_hud_matrix_data.hvdf_offset
: m_hud_matrix_data.user_hvdf[offset_selector - 1];
// lqi.xyzw vf01, vi02 | nop
Vector4f pos_vf01 = m_vec_data_2d[sprite_idx].xyz_sx;
// lqi.xyzw vf05, vi02 | nop
Vector4f flags_vf05 = m_vec_data_2d[sprite_idx].flag_rot_sy;
// lqi.xyzw vf11, vi02 | nop
Vector4f color_vf11 = m_vec_data_2d[sprite_idx].rgba;
// multiplications from the right column
Vector4f transformed_pos_vf02 = matrix_transform(camera_matrix, pos_vf01);
Vector4f scales_vf01 = pos_vf01; // now used for something else.
// lq.xyzw vf12, 1020(vi00) | mulaw.xyzw ACC, vf28, vf00
// vf12 is fog consts
Vector4f fog_consts_vf12(m_frame_data.fog_min, m_frame_data.fog_max, m_frame_data.max_scale,
m_frame_data.bonus);
// ilw.y vi08, 1(vi02) | maddax.xyzw ACC, vf25, vf01
// load offset selector for the next round.
// nop | madday.xyzw ACC, vf26, vf01
// nop | maddz.xyzw vf02, vf27, vf01
// move.w vf05, vf00 | addw.z vf01, vf00, vf05
// scales_vf01.z = sy
scales_vf01.z() = flags_vf05.w(); // start building the scale vector
flags_vf05.w() = 1.f; // what are we building in flags right now??
// nop | nop
// div Q, vf31.x, vf02.w | muly.z vf05, vf05, vf31
float Q = m_frame_data.pfog0 / transformed_pos_vf02.w();
flags_vf05.z() *= m_frame_data.deg_to_rad;
// nop | mul.xyzw vf03, vf02, vf29
Vector4f scaled_pos_vf03 = transformed_pos_vf02.elementwise_multiply(m_frame_data.hmge_scale);
// nop | nop
// nop | nop
// nop | mulz.z vf04, vf05, vf05 (ts)
// fmt::print("rot is {} degrees\n", flags_vf05.z() * 360.0 / (2.0 * M_PI));
// the load is for rotation stuff,
// lq.xyzw vf14, 1001(vi00) | clipw.xyz vf03, vf03 (used for fcand)
// iaddi vi06, vi00, 0x1 | adda.xyzw ACC, vf11, vf11 (used for fmand)
// upcoming fcand with 0x3f, that checks all of them.
bool fcand_result = clip_xyz_plus_minus(scaled_pos_vf03);
bool fmand_result = color_vf11.w() == 0; // (really w+w, but I don't think it matters?)
// L8:
// xgkick double buffer setup
// ior vi05, vi15, vi00 | mul.zw vf01, vf01, Q
scales_vf01.z() *= Q; // sy
scales_vf01.w() *= Q; // sx
// lq.xyzw vf06, 998(vi00) | mulz.xyzw vf15, vf05, vf04 (ts)
auto adgif_vf06 = m_frame_data.adgif_giftag;
// lq.xyzw vf14, 1002(vi00) ts| mula.xyzw ACC, vf05, vf14 (ts)
// fmand vi01, vi06 | mul.xyz vf02, vf02, Q
transformed_pos_vf02.x() *= Q;
transformed_pos_vf02.y() *= Q;
transformed_pos_vf02.z() *= Q;
// if (m_extra_debug) {
// imgui_vec(transformed_pos_vf02, "scaled xf");
// }
// ibne vi00, vi01, L10 | addz.x vf01, vf00, vf01
scales_vf01.x() = scales_vf01.z(); // = sy
if (fmand_result) {
if (m_extra_debug) {
ImGui::TextColored(ImVec4(0.8, 0.2, 0.2, 1.0), "fmand reject");
ImGui::Separator();
}
continue; // reject!
}
// lqi.xyzw vf07, vi03 | mulz.xyzw vf16, vf15, vf04 (ts)
// vf07 is first use adgif
// lq.xyzw vf14, 1003(vi00) | madda.xyzw ACC, vf15, vf14 (ts both)
// lqi.xyzw vf08, vi03 | add.xyzw vf10, vf02, vf30
// vf08 is second user adgif
Vector4f offset_pos_vf10 = transformed_pos_vf02 + hvdf_offset;
// if (m_extra_debug) {
// ImGui::Text("sel %d", offset_selector);
// //ImGui::Text("hvdf off z: %f tf/w z: %f", hvdf_offset.z(), transformed_pos_vf02.z());
// imgui_vec(hvdf_offset, "hvdf");
// imgui_vec(transformed_pos_vf02, "tf'd");
// }
// lqi.xyzw vf09, vi03 | mulw.x vf01, vf01, vf01
// vf09 is third user adgif
scales_vf01.x() *= scales_vf01.w(); // x = sx * sy
// sqi.xyzw vf06, vi05 | mulz.xyzw vf15, vf16, vf04 (ts)
// FIRST ADGIF IS adgif_vf06
packet.adgif_giftag = adgif_vf06;
// lq.xyzw vf14, 1004(vi00) | madda.xyzw ACC, vf16, vf14 (ts both)
// sqi.xyzw vf07, vi05 | maxx.w vf10, vf10, vf12
// SECOND ADGIF is first user
// just do all 5 now.
packet.user_adgif = m_adgif[sprite_idx];
offset_pos_vf10.w() = std::max(offset_pos_vf10.w(), m_frame_data.fog_max);
// sqi.xyzw vf08, vi05 | maxz.zw vf01, vf01, vf31
// THIRD ADGIF is second user
scales_vf01.z() = std::max(scales_vf01.z(), m_frame_data.min_scale);
scales_vf01.w() = std::max(scales_vf01.w(), m_frame_data.min_scale);
// sqi.xyzw vf09, vi05 | mulz.xyzw vf16, vf15, vf04 (ts)
// FOURTH ADGIF is third user
// lq.xyzw vf14, 1005(vi00) | madda.xyzw ACC, vf15, vf14 (ts both)
// lqi.xyzw vf06, vi03 | mulw.x vf01, vf01, vf31
// vf06 is fourth user adgif
scales_vf01.x() *= m_frame_data.inv_area; // x = sx * sy * inv_area (area ratio)
// lqi.xyzw vf07, vi03 | miniy.w vf10, vf10, vf12
// vf07 is fifth user adgif
offset_pos_vf10.w() = std::min(offset_pos_vf10.w(), m_frame_data.fog_min);
// lq.xyzw vf08, 1000(vi00) | nop
// vf08 is 2d giftag 2
// ilw.x vi07, -2(vi02) | madd.xyzw vf05, vf16, vf14
auto flag_vi07 = m_vec_data_2d[sprite_idx].flag();
Vector4f vf05_sincos(0, 0, std::sin(flags_vf05.z()), std::cos(flags_vf05.z()));
// lq.xyzw vf30, 904(vi08) | nop
// pipline
// lqi.xyzw vf23, vi02 | miniw.x vf01, vf01, vf00
// pipeline
scales_vf01.x() = std::min(scales_vf01.x(), 1.f);
// lqi.xyzw vf24, vi02 | mulx.w vf11, vf11, vf01
// pipeline
color_vf11.w() *= scales_vf01.x(); // is this right? doesn't this stall??
// fcand vi01, 0x3f | mulaw.xyzw ACC, vf28, vf00
// already computed pipeline
// lq.xyzw vf17, 1006(vi00) | maddax.xyzw ACC, vf25, vf23 (pipeline)
Vector4f basis_x_vf17 = m_frame_data.basis_x;
// lq.xyzw vf18, 1007(vi00) | madday.xyzw ACC, vf26, vf23 (pipeline)
Vector4f basis_y_vf18 = m_frame_data.basis_y;
assert(flag_vi07 == 0);
Vector4f* xy_array = m_frame_data.xy_array + flag_vi07;
// lq.xyzw vf19, 980(vi07) | ftoi0.xyzw vf11, vf11
Vector4f xy0_vf19 = xy_array[0];
math::Vector<s32, 4> color_integer_vf11 = color_vf11.cast<s32>();
// lq.xyzw vf20, 981(vi07) | maddz.xyzw vf02, vf27, vf23 (pipeline)
Vector4f xy1_vf20 = xy_array[1];
// lq.xyzw vf21, 982(vi07) | mulaw.xyzw ACC, vf17, vf05
Vector4f xy2_vf21 = xy_array[2];
Vector4f acc = basis_x_vf17 * vf05_sincos.w();
// lq.xyzw vf22, 983(vi07) | msubz.xyzw vf12, vf18, vf05
Vector4f xy3_vf22 = xy_array[3];
Vector4f vf12_rotated = acc - (basis_y_vf18 * vf05_sincos.z());
// sq.xyzw vf11, 3(vi05) | mulaz.xyzw ACC, vf17, vf05
// EIGHTH is color integer
packet.color = color_integer_vf11;
acc = basis_x_vf17 * vf05_sincos.z();
// lqi.xyzw vf11, vi02 | maddw.xyzw vf13, vf18, vf05
// (pipeline)
Vector4f vf13_rotated_trans = acc + basis_y_vf18 * vf05_sincos.w();
// move.w vf24, vf00 | addw.z vf23, vf00, vf24 (pipeline both)
// div Q, vf31.x, vf02.w | mulw.xyzw vf12, vf12, vf01
// (pipeline)
vf12_rotated *= scales_vf01.w();
// ibne vi00, vi01, L9 | muly.z vf24, vf24, vf31 (pipeline)
if (fcand_result) {
if (m_extra_debug) {
ImGui::TextColored(ImVec4(0.8, 0.2, 0.2, 1.0), "fcand reject");
ImGui::Separator();
}
continue; // reject (could move earlier)
}
// ilw.y vi08, 1(vi02) | mulz.xyzw vf13, vf13, vf01
// (pipeline)
vf13_rotated_trans *= scales_vf01.z();
// LEFT OFF HERE!
// sqi.xyzw vf06, vi05 | mul.xyzw vf03, vf02, vf29
// FIFTH is fourth user
// sqi.xyzw vf07, vi05 | mulaw.xyzw ACC, vf10, vf00
// SIXTH is fifth user
acc = offset_pos_vf10;
// sqi.xyzw vf08, vi05 | maddax.xyzw ACC, vf12, vf19
// SEVENTH is giftag2
packet.sprite_giftag = m_frame_data.sprite_2d_giftag2;
acc += vf12_rotated * xy0_vf19.x();
// lq.xyzw vf06, 988(vi00) | maddy.xyzw vf19, vf13, vf19
Vector4f st0_vf06 = m_frame_data.st_array[0];
xy0_vf19 = acc + vf13_rotated_trans * xy0_vf19.y();
// lq.xyzw vf07, 989(vi00) | mulaw.xyzw ACC, vf10, vf00
Vector4f st1_vf07 = m_frame_data.st_array[1];
acc = offset_pos_vf10;
// lq.xyzw vf08, 990(vi00) | maddax.xyzw ACC, vf12, vf20
Vector4f st2_vf08 = m_frame_data.st_array[2];
acc += vf12_rotated * xy1_vf20.x();
// lq.xyzw vf09, 991(vi00) | maddy.xyzw vf20, vf13, vf20
Vector4f st3_vf09 = m_frame_data.st_array[3];
xy1_vf20 = acc + vf13_rotated_trans * xy1_vf20.y();
// sq.xyzw vf06, 1(vi05) | mulaw.xyzw ACC, vf10, vf00
// NINTH is st0
packet.st0 = st0_vf06;
acc = offset_pos_vf10;
// sq.xyzw vf07, 3(vi05) | maddax.xyzw ACC, vf12, vf21
// ELEVEN is st1
packet.st1 = st1_vf07;
acc += vf12_rotated * xy2_vf21.x();
// sq.xyzw vf08, 5(vi05) | maddy.xyzw vf21, vf13, vf21
// THIRTEEN is st2
packet.st2 = st2_vf08;
xy2_vf21 = acc + vf13_rotated_trans * xy2_vf21.y();
// sq.xyzw vf09, 7(vi05) | mulaw.xyzw ACC, vf10, vf00
// FIFTEEN is st3
packet.st3 = st3_vf09;
acc = offset_pos_vf10;
// nop | maddax.xyzw ACC, vf12, vf22
acc += vf12_rotated * xy3_vf22.x();
// nop | maddy.xyzw vf22, vf13, vf22
xy3_vf22 = acc + vf13_rotated_trans * xy3_vf22.y();
// lq.xyzw vf12, 1020(vi00) | ftoi4.xyzw vf19, vf19
// (pipeline)
auto xy0_vf19_int = (xy0_vf19 * 16.f).cast<s32>();
// lq.xyzw vf14, 1001(vi00) | ftoi4.xyzw vf20, vf20
// (pipeline)
auto xy1_vf20_int = (xy1_vf20 * 16.f).cast<s32>();
// move.xyzw vf05, vf24 | ftoi4.xyzw vf21, vf21
// (pipeline)
auto xy2_vf21_int = (xy2_vf21 * 16.f).cast<s32>();
// move.xyzw vf01, vf23 | ftoi4.xyzw vf22, vf22
// (pipeline)
auto xy3_vf22_int = (xy3_vf22 * 16.f).cast<s32>();
if (m_extra_debug) {
u32 zi = xy3_vf22_int.z() >> 4;
ImGui::Text("z (int): 0x%08x %s", zi, zi >= (1 << 24) ? "bad" : "");
ImGui::Text("z (flt): %f", (double)(((u32)zi) << 8) / UINT32_MAX);
}
// sq.xyzw vf19, 2(vi05) | mulz.z vf04, vf24, vf24 (pipeline)
// TENTH is xy0int
packet.xy0 = xy0_vf19_int;
// sq.xyzw vf20, 4(vi05) | clipw.xyz vf03, vf03 (pipeline)
// TWELVE is xy1int
packet.xy1 = xy1_vf20_int;
// sq.xyzw vf21, 6(vi05) | nop
// FOURTEEN is xy2int
packet.xy2 = xy2_vf21_int;
// sq.xyzw vf22, 8(vi05) | nop
// SIXTEEN is xy3int
packet.xy3 = xy3_vf22_int;
m_sprite_renderer.render_gif((const u8*)&packet, sizeof(packet), render_state);
if (m_extra_debug) {
imgui_vec(vf12_rotated, "vf12", 2);
imgui_vec(vf13_rotated_trans, "vf13", 2);
ImGui::Separator();
}
// xgkick vi15 | nop
// iaddi vi04, vi04, -0x1 | nop
// iaddiu vi01, vi00, 0x672 | nop
// ibne vi00, vi04, L8 | nop
// isub vi15, vi01, vi15 | adda.xyzw ACC, vf11, vf11
// nop | nop :e
// nop | nop
// L9:
// iaddi vi04, vi04, -0x1 | nop
// iaddi vi02, vi02, -0x3 | nop
// ibne vi00, vi04, L7 | nop
// nop | nop
// nop | nop :e
// nop | nop
// L10:
// iaddi vi04, vi04, -0x1 | nop
// iaddi vi03, vi03, 0x4 | nop
// ibne vi00, vi04, L7 | nop
// nop | nop
// nop | nop :e
// nop | nop
}
if (m_extra_debug) {
ImGui::End();
}
}
@@ -0,0 +1,180 @@
#pragma once
#include "game/graphics/opengl_renderer/BucketRenderer.h"
#include "game/graphics/opengl_renderer/DirectRenderer.h"
#include "game/graphics/dma/gs.h"
#include "common/math/Vector.h"
using math::Matrix4f;
using math::Vector4f;
/*!
* GOAL sprite-frame-data, all the data that's uploaded once per frame for the sprite system.
*/
struct SpriteFrameData {
Vector4f xy_array[8];
Vector4f st_array[4];
Vector4f xyz_array[4];
Vector4f hmge_scale;
float pfog0;
float deg_to_rad;
float min_scale;
float inv_area;
GifTag adgif_giftag;
GifTag sprite_2d_giftag;
GifTag sprite_2d_giftag2;
Vector4f sincos[5];
Vector4f basis_x;
Vector4f basis_y;
GifTag sprite_3d_giftag;
AdGif screen_shader;
GifTag clipped_giftag;
Vector4f inv_hmge_scale;
Vector4f stq_offset;
Vector4f stq_scale;
Vector4f rgba_plain;
GifTag warp_giftag;
float fog_min;
float fog_max;
float max_scale;
float bonus;
};
/*!
* "Matrix Data" for 3D sprites. This is shared for all 3D sprites
*/
struct Sprite3DMatrixData {
Matrix4f camera;
Vector4f hvdf_offset;
};
/*!
* "Matrix Data" for 2D screen space sprites. These are shared for all 2D HUD sprites
*/
struct SpriteHudMatrixData {
Matrix4f matrix;
// the "matrix" field is an index into these 76 quadwords
Vector4f hvdf_offset;
Vector4f user_hvdf[75];
};
/*!
* The "vector data" (sprite-vec-data-2d). Each sprite has its own vector data.
*/
struct SpriteVecData2d {
Vector4f xyz_sx; // position + x scale
Vector4f flag_rot_sy; // flags, rotation, and scale y
Vector4f rgba; // color
float sx() const { return xyz_sx.w(); }
// for HUD, this is the hvdf offset index
s32 flag() {
s32 result;
memcpy(&result, &flag_rot_sy.x(), sizeof(s32));
return result;
}
// unused for HUD
s32 matrix() {
s32 result;
memcpy(&result, &flag_rot_sy.y(), sizeof(s32));
return result;
}
// rotation in degrees
float rot() const { return flag_rot_sy.z(); }
// scale y.
float sy() const { return flag_rot_sy.w(); }
};
/*!
* The layout of VU1 data memory, in quadword addresses
* The lower 800 qw's hold two buffers for double buffering drawing/loading.
*/
enum SpriteDataMem {
// these three can have an offset of 0 or 400 depending on which buffer
Header = 0, // number of sprites (updated per chunk)
Vector = 1, // vector data (updated per chunk)
Adgif = 145, // adgifs (updated per chunk)
// offset of first buffer
Buffer0 = 0,
// offset of second buffer
Buffer1 = 400,
GiftagBuilding = 800, // used to store gs packets for xgkicking
// matrix data (different depending on group)
Matrix = 900,
// frame data (same for the whole frame)
FrameData = 980
};
/*!
* The GS packet built by the sprite renderer.
*/
struct SpriteHud2DPacket {
GifTag adgif_giftag; // starts the adgif shader. 0
AdGif user_adgif; // the adgif shader 16
GifTag sprite_giftag; // 96
math::Vector<s32, 4> color;
Vector4f st0;
math::Vector<s32, 4> xy0;
Vector4f st1;
math::Vector<s32, 4> xy1;
Vector4f st2;
math::Vector<s32, 4> xy2;
Vector4f st3;
math::Vector<s32, 4> xy3;
};
/*!
* The layout of VU1 code memory
*/
enum SpriteProgMem {
Init = 0, // the sprite initialization program. runs once per frame.
Sprites2dGrp0 = 3, // world space 2d sprites
Sprites2dHud = 109, // hud sprites
Sprites3d = 211 // 3d sprites
};
static_assert(offsetof(SpriteFrameData, hmge_scale) == 256);
static_assert(sizeof(SpriteFrameData) == 0x290, "SpriteFrameData size");
class SpriteRenderer : public BucketRenderer {
public:
SpriteRenderer(const std::string& name, BucketId my_id);
void render(DmaFollower& dma, SharedRenderState* render_state) override;
void draw_debug_window() override;
static constexpr int SPRITES_PER_CHUNK = 48;
private:
void render_distorter(DmaFollower& dma, SharedRenderState* render_state);
void handle_sprite_frame_setup(DmaFollower& dma);
void render_3d(DmaFollower& dma);
void render_2d_group0(DmaFollower& dma);
void render_fake_shadow(DmaFollower& dma);
void render_2d_group1(DmaFollower& dma, SharedRenderState* render_state);
void do_2d_group1_block_cpu(u32 count, SharedRenderState* render_state);
u8 m_sprite_distorter_setup[7 * 16]; // direct data
u8 m_sprite_direct_setup[3 * 16];
SpriteFrameData m_frame_data; // qwa: 980
Sprite3DMatrixData m_3d_matrix_data;
SpriteHudMatrixData m_hud_matrix_data;
SpriteVecData2d m_vec_data_2d[SPRITES_PER_CHUNK];
AdGif m_adgif[SPRITES_PER_CHUNK];
struct DebugStats {
int blocks_2d_grp1 = 0;
int count_2d_grp1 = 0;
} m_debug_stats;
bool m_extra_debug = false;
DirectRenderer m_sprite_renderer;
DirectRenderer m_direct_renderer;
};
@@ -0,0 +1,249 @@
#include "third-party/fmt/core.h"
#include "third-party/imgui/imgui.h"
#include "TextureUploadHandler.h"
#include "game/graphics/pipelines/opengl.h"
TextureUploadHandler::TextureUploadHandler(const std::string& name, BucketId my_id)
: BucketRenderer(name, my_id) {}
void TextureUploadHandler::render(DmaFollower& dma, SharedRenderState* render_state) {
m_stats = {};
// this is the data we get from the PC Port modification.
struct TextureUpload {
u64 page;
s64 mode;
};
std::vector<TextureUpload> uploads;
// loop through all data, grabbing buckets
while (dma.current_tag_offset() != render_state->next_bucket) {
auto dma_tag = dma.current_tag();
auto data = dma.read_and_advance();
if (data.size_bytes == 0 && data.vif0() == 0 && data.vif1() == 0) {
continue;
}
if (data.size_bytes == 16 && data.vifcode0().kind == VifCode::Kind::PC_PORT &&
data.vif1() == 3) {
TextureUpload upload_data;
memcpy(&upload_data, data.data, sizeof(upload_data));
uploads.push_back(upload_data);
continue;
}
if (dma_tag.kind == DmaTag::Kind::CALL) {
dma.read_and_advance(); // call
dma.read_and_advance(); // cnt
dma.read_and_advance(); // ret
// on next
assert(dma.current_tag_offset() == render_state->next_bucket);
}
}
// if we're replaying a graphics dump, don't try to read ee memory
// TODO, we might still want to grab stuff from the cache
if (render_state->dump_playback) {
return;
}
// NOTE: we don't actually copy the textures in the dma chain copying because they aren't
// reference by DMA tag. So there's the potential for race conditions if the game gets messed
// up and corrupts the texture memory.
const u8* ee_mem = (const u8*)render_state->ee_main_memory;
// The logic here is a bit confusing. It works around an issue where higher LODs are uploaded
// before their CLUT in some cases.
if (uploads.size() == 2 && uploads[0].mode == 2 && uploads[1].mode == -2 &&
uploads[0].page == uploads[1].page) {
bool has_segment[3] = {true, true, true};
if (!try_to_populate_from_cache(uploads[0].page, has_segment, render_state)) {
// couldn't find this texture in cache, need to convert it
populate_cache(render_state->texture_pool->convert_textures(
ee_mem + uploads[0].page, -2, ee_mem, render_state->offset_of_s7),
render_state);
populate_cache(render_state->texture_pool->convert_textures(
ee_mem + uploads[0].page, 2, ee_mem, render_state->offset_of_s7),
render_state);
// after conversion, we should be able to populate the texture pool.
bool ok = try_to_populate_from_cache(uploads[0].page, has_segment, render_state);
assert(ok);
}
} else if (uploads.size() == 1 && uploads[0].mode == -1) {
// look at the texture page and determine if we have it in cache.
bool has_segment[3] = {true, true, true};
if (!try_to_populate_from_cache(uploads[0].page, has_segment, render_state)) {
populate_cache(render_state->texture_pool->convert_textures(
ee_mem + uploads[0].page, -1, ee_mem, render_state->offset_of_s7),
render_state);
bool ok = try_to_populate_from_cache(uploads[0].page, has_segment, render_state);
assert(ok);
}
} else if (uploads.size() == 1 && uploads[0].mode == -2) {
bool has_segment[3] = {true, true, true};
if (!try_to_populate_from_cache(uploads[0].page, has_segment, render_state)) {
populate_cache(render_state->texture_pool->convert_textures(
ee_mem + uploads[0].page, -2, ee_mem, render_state->offset_of_s7),
render_state);
bool ok = try_to_populate_from_cache(uploads[0].page, has_segment, render_state);
assert(ok);
}
} else if (uploads.empty()) {
// do nothing.
} else {
fmt::print("unhandled upload sequence in {}:\n", m_name);
for (auto& upload : uploads) {
fmt::print(" page: 0x{:x} mode: {}\n", upload.page, upload.mode);
}
assert(false);
}
}
void TextureUploadHandler::draw_debug_window() {
ImGui::Text("Textures this frame: %d", m_stats.textures_provided);
ImGui::Text("Textures converted: %d", m_stats.textures_converted);
ImGui::Text("Textures replaced: %d", m_stats.textures_evicted);
}
namespace {
const char* goal_string(u32 ptr, const u8* memory_base) {
if (ptr == 0) {
assert(false);
}
return (const char*)(memory_base + ptr + 4);
}
} // namespace
/*!
* Try to set an entry in the texture pool from a cached texture for the given page (GOAL pointer).
*/
bool TextureUploadHandler::try_to_populate_from_cache(u64 page,
const bool with_seg[3],
SharedRenderState* render_state) {
auto old_tex_provided = m_stats.textures_provided;
const u8* ee_mem = (const u8*)render_state->ee_main_memory;
auto tpage = ee_mem + page;
GoalTexturePage texture_page;
memcpy(&texture_page, tpage, sizeof(GoalTexturePage));
// loop over all textures in the page
for (int tex_idx = 0; tex_idx < texture_page.length; tex_idx++) {
// we might have some invalid textures, for whatever reason. The PS2 side checks for this.
GoalTexture tex;
if (texture_page.try_copy_texture_description(&tex, tex_idx, ee_mem, tpage,
render_state->offset_of_s7)) {
// loop over all mip levels of this texture
for (int mip_idx = 0; mip_idx < tex.num_mips; mip_idx++) {
// only grab mip levels that we requested (we don't want to overwrite vram that the engine
// expects us to not touch)
if (with_seg[tex.segment_of_mip(mip_idx)]) {
m_stats.textures_provided++;
// lookup the texture by name!
auto it = m_tex_cache.find(goal_string(tex.name_ptr, ee_mem));
if (it == m_tex_cache.end() || !it->second.at(mip_idx)) {
// failed to find it, reject the entire page load
m_stats.textures_provided = old_tex_provided;
return false;
} else {
// found it! Set it in the pool (just setting a pointer)
render_state->texture_pool->set_texture(tex.dest[mip_idx], it->second.at(mip_idx));
}
}
}
}
}
return true;
}
/*!
* Cache the given textures and set in pool
*/
void TextureUploadHandler::populate_cache(
const std::vector<std::shared_ptr<TextureRecord>>& textures,
SharedRenderState* render_state) {
for (auto& tex : textures) {
// disable automatic GC of these textures. We need this - even if the texture becomes evicted
// from PS2 VRAM, we want to hold on to the conversion. Now this cache will be responsible for
// managing this texture.
tex->do_gc = false;
m_stats.textures_provided++;
m_stats.textures_converted++;
// put in pool too
render_state->texture_pool->set_texture(tex->dest, tex);
auto it = m_tex_cache.find(tex->name);
if (it != m_tex_cache.end()) {
if (it->second.at(tex->mip_level)) {
// replacing an existing, don't forget to kill the original.
m_stats.textures_evicted++;
render_state->texture_pool->discard(it->second.at(tex->mip_level));
}
it->second.at(tex->mip_level) = tex;
} else {
std::vector<std::shared_ptr<TextureRecord>> recs(7); // max mip
recs.at(tex->mip_level) = tex;
m_tex_cache.insert({tex->name, std::move(recs)});
}
}
}
/*!
* Unload any cached textures from GPU.
* Remove all textures from this cache.
* Set do_gc on all textures, as they may be in use in the pool and we may need them.
*
* Effectively, this will require all textures to re-converted and re-uploaded next time they are
* uploaded from the game.
*/
void TextureUploadHandler::evict_all() {
for (auto& e : m_tex_cache) {
for (auto& x : e.second) {
if (x) {
if (x->on_gpu) {
x->unload_from_gpu();
}
x->do_gc = true;
}
}
}
m_tex_cache = {};
}
void TextureUploadHandler::serialize(Serializer& ser) {
if (ser.is_saving()) {
ser.save<size_t>(m_tex_cache.size());
for (auto& entry : m_tex_cache) {
ser.save_str(&entry.first);
ser.save<size_t>(entry.second.size());
for (auto& x : entry.second) {
if (x) {
ser.save<u8>(1);
x->serialize(ser);
} else {
ser.save<u8>(0);
}
}
}
} else {
evict_all();
auto size = ser.load<size_t>();
for (size_t i = 0; i < size; i++) {
auto str = ser.load_string();
std::vector<std::shared_ptr<TextureRecord>> recs(ser.load<size_t>());
for (auto& x : recs) {
if (ser.load<u8>()) {
x = std::make_shared<TextureRecord>();
x->serialize(ser);
x->on_gpu = false;
}
}
m_tex_cache.insert({str, std::move(recs)});
}
}
}
@@ -0,0 +1,36 @@
#pragma once
#include "game/graphics/opengl_renderer/BucketRenderer.h"
#include "game/graphics/texture/TexturePool.h"
/*!
* The TextureUploadHandler receives textures uploads in the DMA chain and updates the TexturePool.
* It will attempt to cache textures when possible as converting and uploading them to the GPU is
* pretty expensive.
*
* Note that the PC Port sends a somewhat simplified texture upload message and this can't handle
* any arbitrary PS2 texture transfer. We rely on the texture metadata in GOAL to simplify this.
*/
class TextureUploadHandler : public BucketRenderer {
public:
TextureUploadHandler(const std::string& name, BucketId my_id);
void render(DmaFollower& dma, SharedRenderState* render_state) override;
void draw_debug_window() override;
void serialize(Serializer& ser) override;
private:
void evict_all();
bool try_to_populate_from_cache(u64 page,
const bool with_seg[3],
SharedRenderState* render_state);
void populate_cache(const std::vector<std::shared_ptr<TextureRecord>>& textures,
SharedRenderState* render_state);
std::unordered_map<std::string, std::vector<std::shared_ptr<TextureRecord>>> m_tex_cache;
struct {
u32 textures_provided = 0;
u32 textures_converted = 0;
u32 textures_evicted = 0;
} m_stats;
};
+108
View File
@@ -0,0 +1,108 @@
#include "debug_gui.h"
#include <algorithm>
#include "third-party/imgui/imgui.h"
void FrameTimeRecorder::finish_frame() {
m_frame_times[m_idx++] = m_timer.getMs();
if (m_idx == SIZE) {
m_idx = 0;
}
}
void FrameTimeRecorder::start_frame() {
m_timer.start();
}
void FrameTimeRecorder::draw_window(const DmaStats& dma_stats) {
auto* p_open = &m_open;
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;
const float PAD = 10.0f;
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImVec2 work_pos = viewport->WorkPos; // Use work area to avoid menu-bar/task-bar, if any!
ImVec2 work_size = viewport->WorkSize;
ImVec2 window_pos, window_pos_pivot;
window_pos.x = (work_pos.x + work_size.x - PAD);
window_pos.y = (work_pos.y + work_size.y - PAD);
window_pos_pivot.x = 1.0f;
window_pos_pivot.y = 1.0f;
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background
if (ImGui::Begin("Frame Timing", p_open, window_flags)) {
ImGui::Text("DMA: sync ms %.1f, tc %4d, sz %3d KB, ch %d", dma_stats.sync_time_ms,
dma_stats.num_tags, (dma_stats.num_data_bytes) / (1 << 10), dma_stats.num_chunks);
float worst = 0, total = 0;
for (auto x : m_frame_times) {
worst = std::max(x, worst);
total += x;
}
if (total / SIZE > 17.) {
ImGui::TextColored(ImVec4(1.0, 0.3, 0.3, 1.0), "avg: %.1f", total / SIZE);
} else {
ImGui::Text("avg: %.1f", total / SIZE);
}
ImGui::SameLine();
if (worst > 17.) {
ImGui::TextColored(ImVec4(1.0, 0.3, 0.3, 1.0), "worst: %.1f", worst);
} else {
ImGui::Text("worst: %.1f", worst);
}
ImGui::Separator();
ImGui::PlotLines(
"0-20ms",
[](void* data, int idx) {
auto* me = (FrameTimeRecorder*)data;
return me->m_frame_times[(me->m_idx + idx) % SIZE];
},
(void*)this, SIZE, 0, nullptr, 0, 20., ImVec2(300, 40));
ImGui::Checkbox("Run", &m_play);
ImGui::SameLine();
if (ImGui::Button("Single Frame Advance")) {
m_single_frame = true;
}
}
ImGui::End();
}
void OpenGlDebugGui::start_frame() {
m_frame_timer.start_frame();
}
void OpenGlDebugGui::finish_frame() {
m_frame_timer.finish_frame();
}
void OpenGlDebugGui::draw(const DmaStats& dma_stats) {
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("Windows")) {
ImGui::MenuItem("Frame Time Plot", nullptr, &m_draw_frame_time);
ImGui::MenuItem("Render Debug", nullptr, &m_draw_debug);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Gfx Dump")) {
ImGui::MenuItem("Dump Next Frame!", nullptr, &m_want_save);
bool old_replay = m_want_replay;
ImGui::MenuItem("Load Saved Dump", nullptr, &m_want_replay);
if (!old_replay && m_want_replay) {
m_want_dump_load = true;
}
ImGui::Separator();
ImGui::InputText("Filename", m_dump_save_name, 12);
ImGui::EndMenu();
}
}
ImGui::EndMainMenuBar();
if (m_draw_frame_time) {
m_frame_timer.draw_window(dma_stats);
}
}
+57
View File
@@ -0,0 +1,57 @@
#pragma once
/*!
* @file debug_gui.h
* The debug menu-bar and frame timing window
*/
#include "common/util/Timer.h"
#include "game/graphics/dma/dma.h"
class FrameTimeRecorder {
public:
static constexpr int SIZE = 60 * 5;
void finish_frame();
void start_frame();
void draw_window(const DmaStats& dma_stats);
bool should_advance_frame() {
if (m_single_frame) {
m_single_frame = false;
return true;
}
return m_play;
}
private:
float m_frame_times[SIZE] = {0};
int m_idx = 0;
Timer m_timer;
bool m_open = true;
bool m_play = true;
bool m_single_frame = false;
};
class OpenGlDebugGui {
public:
void start_frame();
void finish_frame();
void draw(const DmaStats& dma_stats);
bool should_draw_render_debug() const { return m_draw_debug; }
bool& want_save() { return m_want_save; }
bool& want_dump_replay() { return m_want_replay; }
bool& want_dump_load() { return m_want_dump_load; }
const char* dump_name() const { return m_dump_save_name; }
bool should_advance_frame() { return m_frame_timer.should_advance_frame(); }
private:
FrameTimeRecorder m_frame_timer;
bool m_draw_frame_time = false;
bool m_draw_debug = false;
bool m_want_save = false;
bool m_want_replay = false;
bool m_want_dump_load = false;
char m_dump_save_name[256] = "dump.bin";
};
@@ -0,0 +1,11 @@
// Debug shader for drawing things in red. Uses the same conventions as direct_basic, see there for more details
#version 330 core
out vec4 color;
in vec4 fragment_color;
void main() {
color = fragment_color;
}
@@ -0,0 +1,12 @@
// Debug shader for drawing things in red. Uses the same conventions as direct_basic, see there for more details
#version 330 core
layout (location = 0) in vec3 position_in;
out vec4 fragment_color;
void main() {
gl_Position = vec4((position_in.x - 0.5) * 16., -(position_in.y - 0.5) * 32, position_in.z, 1.0);
fragment_color = vec4(1.0, 0, 0, 0.7);
}
@@ -1,3 +1,5 @@
// Shader for the DirectRenderer. Inputs are RGBA + position.
#version 330 core
layout (location = 0) in vec3 position_in;
@@ -6,6 +8,7 @@ layout (location = 1) in vec4 rgba_in;
out vec4 fragment_color;
void main() {
// Note: position.y is multiplied by 32 instead of 16 to undo the half-height for interlacing stuff.
gl_Position = vec4((position_in.x - 0.5) * 16., -(position_in.y - 0.5) * 32, position_in.z, 1.0);
fragment_color = vec4(rgba_in.x, rgba_in.y, rgba_in.z, rgba_in.w + 0.5);
}
@@ -0,0 +1,13 @@
#version 330 core
out vec4 color;
in vec4 fragment_color;
in vec2 tex_coord;
uniform sampler2D tex_T0;
void main() {
vec4 T0 = texture(tex_T0, tex_coord);
T0.w = 1.0;
color = fragment_color * T0 * 2.0;
}
@@ -0,0 +1,14 @@
#version 330 core
layout (location = 0) in vec3 position_in;
layout (location = 1) in vec4 rgba_in;
layout (location = 2) in vec2 tex_coord_in;
out vec4 fragment_color;
out vec2 tex_coord;
void main() {
gl_Position = vec4((position_in.x - 0.5) * 16., -(position_in.y - 0.5) * 32, position_in.z, 1.0);
fragment_color = vec4(rgba_in.x, rgba_in.y, rgba_in.z, rgba_in.a * 2);
tex_coord = tex_coord_in;
}
@@ -0,0 +1,16 @@
#version 330 core
out vec4 color;
in vec4 fragment_color;
in vec2 tex_coord;
uniform sampler2D tex_T0;
void main() {
vec4 T0 = texture(tex_T0, tex_coord);
vec4 tex_color = fragment_color * T0 * 2.0;
if (tex_color.a <= 38./255.) {
discard;
}
color = tex_color;
}
@@ -0,0 +1,14 @@
#version 330 core
layout (location = 0) in vec3 position_in;
layout (location = 1) in vec4 rgba_in;
layout (location = 2) in vec2 tex_coord_in;
out vec4 fragment_color;
out vec2 tex_coord;
void main() {
gl_Position = vec4((position_in.x - 0.5) * 16., -(position_in.y - 0.5) * 32, position_in.z, 1.0);
fragment_color = vec4(rgba_in.x, rgba_in.y, rgba_in.z, rgba_in.w * 2.);
tex_coord = tex_coord_in;
}
@@ -0,0 +1,16 @@
#version 330 core
out vec4 color;
in vec4 fragment_color;
in vec2 tex_coord;
uniform sampler2D tex_T0;
void main() {
vec4 T0 = texture(tex_T0, tex_coord);
vec4 tex_color = fragment_color * T0 * 2.0;
if (tex_color.a > 38./255.) {
discard;
}
color = tex_color;
}
@@ -0,0 +1,14 @@
#version 330 core
layout (location = 0) in vec3 position_in;
layout (location = 1) in vec4 rgba_in;
layout (location = 2) in vec2 tex_coord_in;
out vec4 fragment_color;
out vec2 tex_coord;
void main() {
gl_Position = vec4((position_in.x - 0.5) * 16., -(position_in.y - 0.5) * 32, position_in.z, 1.0);
fragment_color = vec4(rgba_in.x, rgba_in.y, rgba_in.z, rgba_in.w * 2.);
tex_coord = tex_coord_in;
}
+99 -18
View File
@@ -22,6 +22,10 @@
#include "common/log/log.h"
#include "common/goal_constants.h"
#include "game/runtime.h"
#include "common/util/Timer.h"
#include "game/graphics/opengl_renderer/debug_gui.h"
#include "common/util/FileUtil.h"
#include "common/util/compress.h"
namespace {
@@ -34,6 +38,7 @@ struct GraphicsData {
std::mutex dma_mutex;
std::condition_variable dma_cv;
u64 frame_idx = 0;
u64 frame_idx_of_input_data = 0;
bool has_data_to_render = false;
FixedChunkDmaCopier dma_copier;
@@ -43,6 +48,15 @@ struct GraphicsData {
// temporary opengl renderer
OpenGLRenderer ogl_renderer;
OpenGlDebugGui debug_gui;
Serializer loaded_dump;
void serialize(Serializer& ser) {
dma_copier.serialize_last_result(ser);
ogl_renderer.serialize(ser);
}
GraphicsData()
: dma_copier(EE_MAIN_MEM_SIZE),
texture_pool(std::make_shared<TexturePool>()),
@@ -137,6 +151,7 @@ static std::shared_ptr<GfxDisplay> gl_make_main_display(int width,
glfwSwapInterval(settings.vsync);
SetDisplayCallbacks(window);
Pad::initialize();
if (HasError()) {
lg::error("gl_make_main_display error");
@@ -175,18 +190,26 @@ static void gl_kill_display(GfxDisplay* display) {
glfwDestroyWindow(display->window_glfw);
}
static void gl_render_display(GfxDisplay* display) {
GLFWwindow* window = display->window_glfw;
void make_gfx_dump() {
Timer ser_timer;
Serializer ser;
// poll events
glfwPollEvents();
glfwMakeContextCurrent(window);
// save the dma chain and renderer state
g_gfx_data->serialize(ser);
auto result = ser.get_save_result();
Timer compression_timer;
auto compressed = compression::compress_zstd(result.first, result.second);
lg::info("Serialized graphics state in {:.1f} ms, {:.3f} MB, compressed {:.3f} MB {:.1f} ms",
ser_timer.getMs(), ((double)result.second) / (1 << 20),
((double)compressed.size() / (1 << 20)), compression_timer.getMs());
// imgui start of frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
file_util::create_dir_if_needed(file_util::get_file_path({"gfx_dumps"}));
file_util::write_binary_file(
file_util::get_file_path({"gfx_dumps", g_gfx_data->debug_gui.dump_name()}), compressed.data(),
compressed.size());
}
void render_game_frame(int width, int height) {
// wait for a copied chain.
bool got_chain = false;
{
@@ -199,11 +222,21 @@ static void gl_render_display(GfxDisplay* display) {
// render that chain.
if (got_chain) {
// g_gfx_data->ogl_renderer.render(DmaFollower(g_gfx_data->dma_copier.get_last_input_data(),
// g_gfx_data->dma_copier.get_last_input_offset()),
// width, height);
// we want to serialize before rendering
if (g_gfx_data->debug_gui.want_save()) {
make_gfx_dump();
g_gfx_data->debug_gui.want_save() = false;
}
auto& chain = g_gfx_data->dma_copier.get_last_result();
int width, height;
glfwGetFramebufferSize(window, &width, &height);
g_gfx_data->frame_idx_of_input_data = g_gfx_data->frame_idx;
g_gfx_data->ogl_renderer.render(DmaFollower(chain.data.data(), chain.start_offset), width,
height);
height, g_gfx_data->debug_gui.should_draw_render_debug(),
false);
}
// before vsync, mark the chain as rendered.
@@ -214,16 +247,65 @@ static void gl_render_display(GfxDisplay* display) {
g_gfx_data->has_data_to_render = false;
g_gfx_data->sync_cv.notify_all();
}
}
void render_dump_frame(int width, int height) {
Timer deser_timer;
if (g_gfx_data->debug_gui.want_dump_load()) {
auto data = file_util::read_binary_file(
file_util::get_file_path({"gfx_dumps", g_gfx_data->debug_gui.dump_name()}));
auto decompressed = compression::decompress_zstd(data.data(), data.size());
g_gfx_data->loaded_dump = Serializer(decompressed.data(), decompressed.size());
}
g_gfx_data->loaded_dump.reset_load();
g_gfx_data->serialize(g_gfx_data->loaded_dump);
if (g_gfx_data->debug_gui.want_dump_load()) {
lg::info("Loaded and deserialized graphics state in {:.1f} ms, {:.3f} MB", deser_timer.getMs(),
((double)g_gfx_data->loaded_dump.data_size()) / (1 << 20));
}
g_gfx_data->debug_gui.want_dump_load() = false;
auto& chain = g_gfx_data->dma_copier.get_last_result();
g_gfx_data->ogl_renderer.render(DmaFollower(chain.data.data(), chain.start_offset), width, height,
g_gfx_data->debug_gui.should_draw_render_debug(), true);
}
static void gl_render_display(GfxDisplay* display) {
GLFWwindow* window = display->window_glfw;
// poll events
glfwPollEvents();
glfwMakeContextCurrent(window);
Pad::update_gamepads();
// imgui start of frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
int width, height;
glfwGetFramebufferSize(window, &width, &height);
if (g_gfx_data->debug_gui.want_dump_replay()) {
render_dump_frame(width, height);
} else if (g_gfx_data->debug_gui.should_advance_frame()) {
render_game_frame(width, height);
}
// render imgui
g_gfx_data->debug_gui.draw(g_gfx_data->dma_copier.get_last_result().stats);
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
// actual vsync
g_gfx_data->debug_gui.finish_frame();
glfwSwapBuffers(window);
g_gfx_data->debug_gui.start_frame();
// toggle even odd and wake up engine waiting on vsync.
{
if (!g_gfx_data->debug_gui.want_dump_replay()) {
std::unique_lock<std::mutex> lock(g_gfx_data->sync_mutex);
g_gfx_data->frame_idx++;
@@ -247,7 +329,7 @@ u32 gl_vsync() {
}
std::unique_lock<std::mutex> lock(g_gfx_data->sync_mutex);
auto init_frame = g_gfx_data->frame_idx;
auto init_frame = g_gfx_data->frame_idx_of_input_data;
g_gfx_data->sync_cv.wait(lock, [=] { return g_gfx_data->frame_idx > init_frame; });
return g_gfx_data->frame_idx & 1;
@@ -290,9 +372,7 @@ void gl_send_chain(const void* data, u32 offset) {
// The renderers should just operate on DMA chains, so eliminating this step in the future may
// be easy.
// Timer copy_timer;
g_gfx_data->dma_copier.run(data, offset);
// fmt::print("copy took {:.3f}ms\n", copy_timer.getMs());
g_gfx_data->has_data_to_render = true;
g_gfx_data->dma_cv.notify_all();
@@ -300,7 +380,8 @@ void gl_send_chain(const void* data, u32 offset) {
}
void gl_texture_upload_now(const u8* tpage, int mode, u32 s7_ptr) {
if (g_gfx_data) {
// block
if (g_gfx_data && !g_gfx_data->debug_gui.want_dump_replay()) {
// 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.
@@ -309,7 +390,7 @@ void gl_texture_upload_now(const u8* tpage, int mode, u32 s7_ptr) {
}
void gl_texture_relocate(u32 destination, u32 source, u32 format) {
if (g_gfx_data) {
if (g_gfx_data && !g_gfx_data->debug_gui.want_dump_replay()) {
g_gfx_data->texture_pool->relocate(destination, source, format);
}
}
@@ -174,4 +174,8 @@ void TextureConverter::download_rgba8888(u8* result,
}
assert(out_offset == expected_size_bytes);
}
void TextureConverter::serialize(Serializer& ser) {
ser.from_pod_vector(&m_vram);
}
+2
View File
@@ -3,6 +3,7 @@
#include <vector>
#include "common/common_types.h"
#include "common/util/Serializer.h"
class TextureConverter {
public:
@@ -17,6 +18,7 @@ class TextureConverter {
u32 clut_psm,
u32 clut_vram_addr,
u32 expected_size_bytes);
void serialize(Serializer& ser);
private:
std::vector<u8> m_vram;
+298 -151
View File
@@ -1,3 +1,5 @@
#include <regex>
#include "TexturePool.h"
#include "third-party/fmt/core.h"
@@ -14,8 +16,9 @@
// this simply converts the PS2 format textures loaded by the game, then puts them into the PC
// port texture pool.
constexpr bool dump_textures_to_file = false;
// constexpr bool dump_textures_to_file = false;
namespace {
const char empty_string[] = "";
const char* goal_string(u32 ptr, const u8* memory_base) {
if (ptr == 0) {
@@ -24,71 +27,237 @@ const char* goal_string(u32 ptr, const u8* memory_base) {
return (const char*)(memory_base + ptr + 4);
}
struct GoalTexture {
s16 w;
s16 h;
u8 num_mips;
u8 tex1_control;
u8 psm;
u8 mip_shift;
u16 clutpsm;
u16 dest[7];
u16 clut_dest;
u8 width[7];
u32 name_ptr;
u32 size;
float uv_dist;
u32 masks[3];
} // namespace
s32 segment_of_mip(s32 mip) const {
if (2 >= num_mips) {
return num_mips - mip - 1;
std::string GoalTexturePage::print() const {
return fmt::format("Tpage id {} textures {} seg0 {} {} seg1 {} {} seg2 {} {}\n", id, length,
segment[0].size, segment[0].dest, segment[1].size, segment[1].dest,
segment[2].size, segment[2].dest);
}
void TextureRecord::serialize(Serializer& ser) {
ser.from_str(&page_name);
ser.from_str(&name);
ser.from_ptr(&mip_level);
ser.from_ptr(&psm);
ser.from_ptr(&cpsm);
ser.from_ptr(&w);
ser.from_ptr(&h);
ser.from_ptr(&data_segment);
ser.from_ptr(&on_gpu);
ser.from_ptr(&do_gc);
ser.from_ptr(&gpu_texture);
ser.from_ptr(&dest);
ser.from_pod_vector(&data);
ser.from_ptr(&min_a_zero);
ser.from_ptr(&max_a_zero);
ser.from_ptr(&min_a_nonzero);
ser.from_ptr(&max_a_nonzero);
}
void TextureData::serialize(Serializer& ser) {
if (ser.is_saving()) {
if (normal_texture) {
ser.save<u8>(1); // has it.
normal_texture->serialize(ser);
} else {
return std::max(0, 2 - mip);
ser.save<u8>(0);
}
if (mt4hh_texture) {
ser.save<u8>(1); // has it.
mt4hh_texture->serialize(ser);
} else {
ser.save<u8>(0);
}
} else {
u8 has_normal = ser.load<u8>();
if (has_normal) {
normal_texture = std::make_shared<TextureRecord>();
normal_texture->serialize(ser);
// after deserializing, nothing is on the GPU
normal_texture->on_gpu = false;
// there will be a duplicate copy of this texture in the bucket, we want this one to be gc'd
normal_texture->do_gc = true;
} else {
normal_texture.reset();
}
u8 has_mt4 = ser.load<u8>();
if (has_mt4) {
mt4hh_texture = std::make_shared<TextureRecord>();
mt4hh_texture->serialize(ser);
mt4hh_texture->on_gpu = false;
mt4hh_texture->do_gc = true;
} else {
mt4hh_texture.reset();
}
}
};
}
static_assert(sizeof(GoalTexture) == 60, "GoalTexture size");
static_assert(offsetof(GoalTexture, clutpsm) == 8);
static_assert(offsetof(GoalTexture, clut_dest) == 24);
void TexturePool::serialize(Serializer& ser) {
m_tex_converter.serialize(ser);
struct GoalTexturePage {
struct Seg {
u32 block_data_ptr;
u32 size;
u32 dest;
};
u32 file_info_ptr;
u32 name_ptr;
u32 id;
s32 length; // texture count
u32 mip0_size;
u32 size;
Seg segment[3];
u32 pad[16];
// start of array.
std::string print() const {
return fmt::format("Tpage id {} textures {} seg0 {} {} seg1 {} {} seg2 {} {}\n", id, length,
segment[0].size, segment[0].dest, segment[1].size, segment[1].dest,
segment[2].size, segment[2].dest);
if (ser.is_loading()) {
remove_garbage_textures();
unload_all_textures();
}
for (auto& tex : m_textures) {
tex.serialize(ser);
}
}
bool try_copy_texture_description(GoalTexture* dest,
int idx,
const u8* memory_base,
const u8* tpage,
u32 s7_ptr) {
u32 ptr;
memcpy(&ptr, tpage + sizeof(GoalTexturePage) + 4 * idx, 4);
if (ptr == s7_ptr) {
return false;
void TexturePool::unload_all_textures() {
for (auto& tex : m_textures) {
if (tex.normal_texture && tex.normal_texture->on_gpu) {
tex.normal_texture->unload_from_gpu();
}
if (tex.mt4hh_texture && tex.mt4hh_texture->on_gpu) {
tex.mt4hh_texture->unload_from_gpu();
}
memcpy(dest, memory_base + ptr, sizeof(GoalTexture));
return true;
}
};
}
void TextureRecord::unload_from_gpu() {
assert(on_gpu);
GLuint tex_id = gpu_texture;
glBindTexture(GL_TEXTURE_2D, tex_id);
glDeleteTextures(1, &tex_id);
on_gpu = false;
gpu_texture = -1;
}
std::vector<std::shared_ptr<TextureRecord>> TexturePool::convert_textures(const u8* tpage,
int mode,
const u8* memory_base,
u32 s7_ptr) {
Timer timer;
std::vector<std::shared_ptr<TextureRecord>> result;
bool dump_textures_to_file = false;
// extract the texture-page object. This is just a description of the page data.
GoalTexturePage texture_page;
memcpy(&texture_page, tpage, sizeof(GoalTexturePage));
bool has_segment[3] = {true, true, true};
u32 sizes[3] = {texture_page.segment[0].size, texture_page.segment[1].size,
texture_page.segment[2].size};
if (mode == -1) {
// I don't really understand what's going on here with the size.
// the sizes given aren't the actual sizes in memory, so if you just use that, you get the
// wrong answer. I solved this in the decompiler by using the size of the actual data, but we
// don't really have that here.
u32 size = ((sizes[0] + sizes[1] + sizes[2] + 255) / 256) * 256;
m_tex_converter.upload(memory_base + texture_page.segment[0].block_data_ptr,
texture_page.segment[0].dest, size);
} else if (mode == 2) {
// dump_textures_to_file = true;
has_segment[0] = false;
has_segment[1] = false;
u32 size = ((sizes[2] + 255) / 256) * 256;
// dest is in 4-byte vram words
m_tex_converter.upload(memory_base + texture_page.segment[2].block_data_ptr,
texture_page.segment[2].dest, size);
} else if (mode == -2) {
has_segment[2] = false;
// I don't really understand what's going on here with the size.
// the selector texture the hud page will be missing the clut unless I make this bigger.
u32 size = ((sizes[0] + sizes[1] + 2047) / 256) * 256;
m_tex_converter.upload(memory_base + texture_page.segment[0].block_data_ptr,
texture_page.segment[0].dest, size);
} else {
// no reason to skip this, other than
lg::error("TexturePool skipping upload now with mode {}.", mode);
return {};
}
// loop over all texture in the tpage and download them.
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)) {
// 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)]) {
u32 ww = tex.w >> mip_idx;
u32 hh = tex.h >> mip_idx;
u32 size_bytes = ww * hh * 4;
auto texture_record = std::make_shared<TextureRecord>();
texture_record->page_name = goal_string(texture_page.name_ptr, memory_base);
texture_record->name = goal_string(tex.name_ptr, memory_base);
texture_record->mip_level = mip_idx;
texture_record->w = ww;
texture_record->h = hh;
texture_record->data_segment = tex.segment_of_mip(mip_idx);
texture_record->data.resize(size_bytes);
texture_record->psm = tex.psm;
texture_record->cpsm = tex.clutpsm;
texture_record->dest = tex.dest[mip_idx];
m_tex_converter.download_rgba8888(texture_record->data.data(), tex.dest[mip_idx],
tex.width[mip_idx], ww, hh, tex.psm, tex.clutpsm,
tex.clut_dest, size_bytes);
u8 max_a_zero = 0;
u8 min_a_zero = 255;
u8 max_a_nonzero = 0;
u8 min_a_nonzero = 255;
for (u32 i = 0; i < ww * hh; i++) {
u8 r = texture_record->data[i * 4 + 0];
u8 g = texture_record->data[i * 4 + 1];
u8 b = texture_record->data[i * 4 + 2];
u8 a = texture_record->data[i * 4 + 3];
if (r || g || b) {
max_a_nonzero = std::max(max_a_nonzero, a);
min_a_nonzero = std::min(min_a_nonzero, a);
} else {
max_a_zero = std::max(max_a_zero, a);
min_a_zero = std::min(min_a_zero, a);
}
}
texture_record->max_a_zero = max_a_zero;
texture_record->min_a_zero = min_a_zero;
texture_record->max_a_nonzero = max_a_nonzero;
texture_record->min_a_nonzero = min_a_nonzero;
if (texture_record->name == "selector" || texture_record->name == "next") {
fmt::print("{}: {} {} {} {}\n", texture_record->name, tex.psm, tex.clutpsm,
tex.clut_dest * 256 / 4,
texture_page.segment[0].dest + ((sizes[0] + sizes[1] + 255) / 256) * 256);
}
fmt::print("TEX: {} nz ({}, {}) z ({}, {}0\n", texture_record->name,
texture_record->min_a_nonzero, texture_record->max_a_nonzero,
texture_record->min_a_zero, texture_record->max_a_zero);
// Debug output.
if (dump_textures_to_file) {
const char* tpage_name = goal_string(texture_page.name_ptr, memory_base);
const char* tex_name = goal_string(tex.name_ptr, memory_base);
file_util::create_dir_if_needed(
file_util::get_file_path({"debug_out", "textures", tpage_name}));
file_util::write_rgba_png(
fmt::format(
file_util::get_file_path({"debug_out", "textures", tpage_name, "{}-{}-{}.png"}),
tex_idx, tex_name, mip_idx),
texture_record->data.data(), ww, hh);
}
result.push_back(std::move(texture_record));
}
}
} else {
// texture was #f, skip it.
}
}
fmt::print("upload now took {:.2f} ms\n", timer.getMs());
return result;
}
/*!
* Handle a GOAL texture-page object being uploaded to VRAM.
@@ -104,93 +273,31 @@ struct GoalTexturePage {
* multiple frames.
*/
void TexturePool::handle_upload_now(const u8* tpage, int mode, const u8* memory_base, u32 s7_ptr) {
Timer timer;
// extract the texture-page object. This is just a description of the page data.
GoalTexturePage texture_page;
memcpy(&texture_page, tpage, sizeof(GoalTexturePage));
u32 sizes[3] = {texture_page.segment[0].size, texture_page.segment[1].size,
texture_page.segment[2].size};
if (mode == -1) {
// I don't really understand what's going on here with the size.
// the sizes given aren't the actual sizes in memory, so if you just use that, you get the
// wrong answer. I solved this in the decompiler by using the size of the actual data, but we
// don't really have that here.
u32 size = ((sizes[0] + sizes[1] + sizes[2] + 255) / 256) * 256;
m_tex_converter.upload(memory_base + texture_page.segment[0].block_data_ptr,
texture_page.segment[0].dest, size);
} else {
// no reason to skip this, other than
lg::error("TexturePool skipping upload now with mode {}.", mode);
return;
auto textures = convert_textures(tpage, mode, memory_base, s7_ptr);
for (auto& tex : textures) {
set_texture(tex->dest, tex);
}
// loop over all texture in the tpage and download them.
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)) {
// each texture may have multiple mip levels.
for (int mip_idx = 0; mip_idx < tex.num_mips; mip_idx++) {
u32 ww = tex.w >> mip_idx;
u32 hh = tex.h >> mip_idx;
u32 size_bytes = ww * hh * 4;
auto texture_record = std::make_unique<TextureRecord>();
texture_record->page_name = goal_string(texture_page.name_ptr, memory_base);
texture_record->name = goal_string(tex.name_ptr, memory_base);
texture_record->mip_level = mip_idx;
texture_record->w = ww;
texture_record->h = hh;
texture_record->data_segment = tex.segment_of_mip(mip_idx);
texture_record->data.resize(size_bytes);
m_tex_converter.download_rgba8888(texture_record->data.data(), tex.dest[mip_idx],
tex.width[mip_idx], ww, hh, tex.psm, tex.clutpsm,
tex.clut_dest, size_bytes);
// Debug output.
if (dump_textures_to_file) {
const char* tpage_name = goal_string(texture_page.name_ptr, memory_base);
const char* tex_name = goal_string(tex.name_ptr, memory_base);
file_util::create_dir_if_needed(
file_util::get_file_path({"debug_out", "textures", tpage_name}));
file_util::write_rgba_png(
fmt::format(
file_util::get_file_path({"debug_out", "textures", tpage_name, "{}-{}-{}.png"}),
tex_idx, tex_name, mip_idx),
texture_record->data.data(), ww, hh);
}
if (tex.psm == 44) {
set_mt4hh_texture(tex.dest[mip_idx], std::move(texture_record));
} else {
set_texture(tex.dest[mip_idx], std::move(texture_record));
}
}
} else {
// texture was #f, skip it.
}
}
fmt::print("upload now took {:.2f} ms\n", timer.getMs());
}
/*!
* Store a texture in the pool. Location is specified like TBP.
*/
void TexturePool::set_texture(u32 location, std::unique_ptr<TextureRecord>&& record) {
if (m_textures.at(location).normal_texture) {
m_garbage_textures.push_back(std::move(m_textures[location].normal_texture));
void TexturePool::set_texture(u32 location, std::shared_ptr<TextureRecord> record) {
if (record->psm == 44) {
if (m_textures.at(location).mt4hh_texture) {
if (record->do_gc && m_textures.at(location).mt4hh_texture != record) {
m_garbage_textures.push_back(std::move(m_textures[location].mt4hh_texture));
}
}
m_textures[location].mt4hh_texture = std::move(record);
} else {
if (m_textures.at(location).normal_texture) {
if (record->do_gc && m_textures.at(location).normal_texture != record) {
m_garbage_textures.push_back(std::move(m_textures[location].normal_texture));
}
}
m_textures[location].normal_texture = std::move(record);
}
m_textures[location].normal_texture = std::move(record);
}
void TexturePool::set_mt4hh_texture(u32 location, std::unique_ptr<TextureRecord>&& record) {
if (m_textures.at(location).mt4hh_texture) {
m_garbage_textures.push_back(std::move(m_textures[location].mt4hh_texture));
}
m_textures[location].mt4hh_texture = std::move(record);
}
/*!
@@ -210,14 +317,23 @@ void TexturePool::draw_debug_window() {
int id = 0;
int total_vram_bytes = 0;
int total_textures = 0;
int total_displayed_textures = 0;
int total_uploaded_textures = 0;
ImGui::Text("GC %d on GPU %d", m_most_recent_gc_count, m_most_recent_gc_count_gpu);
ImGui::InputText("texture search", m_regex_input, sizeof(m_regex_input));
std::regex regex(m_regex_input[0] ? m_regex_input : ".*");
for (auto& record : m_textures) {
if (record.normal_texture) {
ImGui::PushID(id++);
auto& tex = *record.normal_texture;
draw_debug_for_tex(tex.name, tex);
ImGui::PopID();
total_textures++;
auto& tex = *record.normal_texture;
if (std::regex_search(tex.name, regex)) {
ImGui::PushID(id++);
draw_debug_for_tex(tex.name, tex);
ImGui::PopID();
total_displayed_textures++;
}
if (tex.on_gpu) {
total_vram_bytes += tex.w * tex.h * 4; // todo, if we support other formats
total_uploaded_textures++;
@@ -225,25 +341,33 @@ void TexturePool::draw_debug_window() {
}
if (record.mt4hh_texture) {
ImGui::PushID(id++);
auto& tex = *record.mt4hh_texture;
draw_debug_for_tex(tex.name, tex);
ImGui::PopID();
total_textures++;
auto& tex = *record.mt4hh_texture;
if (std::regex_search(tex.name, regex)) {
ImGui::PushID(id++);
draw_debug_for_tex(tex.name, tex);
ImGui::PopID();
total_displayed_textures++;
}
if (tex.on_gpu) {
total_vram_bytes += tex.w * tex.h * 4; // todo, if we support other formats
total_uploaded_textures++;
}
}
}
ImGui::Text("Total Textures: %d Uploaded: %d VRAM: %.3f MB", total_textures,
total_uploaded_textures, (float)total_vram_bytes / (1024 * 1024));
ImGui::Text("Total Textures: %d Uploaded: %d Shown: %d VRAM: %.3f MB", total_textures,
total_uploaded_textures, total_displayed_textures,
(float)total_vram_bytes / (1024 * 1024));
}
void TexturePool::draw_debug_for_tex(const std::string& name, TextureRecord& tex) {
if (ImGui::CollapsingHeader(name.c_str())) {
ImGui::Text("Page: %s Size: %d x %d mip %d On GPU? %d", tex.page_name.c_str(), tex.w, tex.h,
tex.mip_level, tex.on_gpu);
if (tex.on_gpu) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3, 0.8, 0.3, 1.0));
}
if (ImGui::TreeNode(name.c_str())) {
ImGui::Text("P: %s sz: %d x %d mip %d GPU? %d psm %d cpsm %d", tex.page_name.c_str(), tex.w,
tex.h, tex.mip_level, tex.on_gpu, tex.psm, tex.cpsm);
if (tex.on_gpu) {
ImGui::Image((void*)tex.gpu_texture, ImVec2(tex.w, tex.h));
} else {
@@ -251,6 +375,11 @@ void TexturePool::draw_debug_for_tex(const std::string& name, TextureRecord& tex
upload_to_gpu(&tex);
}
}
ImGui::TreePop();
ImGui::Separator();
}
if (tex.on_gpu) {
ImGui::PopStyleColor();
}
}
@@ -267,10 +396,28 @@ void TexturePool::upload_to_gpu(TextureRecord* tex) {
// we have to set these, imgui won't do it automatically
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex->gpu_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
tex->on_gpu = true;
}
void TexturePool::remove_garbage_textures() {
m_most_recent_gc_count = m_garbage_textures.size();
m_most_recent_gc_count_gpu = 0;
for (auto& t : m_garbage_textures) {
if (t->on_gpu) {
m_most_recent_gc_count_gpu++;
t->unload_from_gpu();
}
}
m_garbage_textures.clear();
}
void TexturePool::discard(std::shared_ptr<TextureRecord> tex) {
assert(!tex->do_gc);
m_garbage_textures.push_back(tex);
}
+98 -7
View File
@@ -5,28 +5,108 @@
#include <string>
#include "common/common_types.h"
#include "game/graphics/texture/TextureConverter.h"
#include "common/util/Serializer.h"
struct TextureRecord {
std::string page_name;
std::string name;
u8 mip_level;
u8 psm = -1;
u8 cpsm = -1;
u16 w, h;
std::vector<u8> data;
u8 data_segment;
u64 gpu_texture = 0;
bool on_gpu = false;
bool do_gc = true;
std::vector<u8> data;
u64 gpu_texture = 0;
u32 dest = -1;
u8 min_a_zero, max_a_zero, min_a_nonzero, max_a_nonzero;
void unload_from_gpu();
void serialize(Serializer& ser);
};
struct TextureData {
std::unique_ptr<TextureRecord> normal_texture;
std::unique_ptr<TextureRecord> mt4hh_texture;
std::shared_ptr<TextureRecord> normal_texture;
std::shared_ptr<TextureRecord> mt4hh_texture;
void serialize(Serializer& ser);
};
struct GoalTexture {
s16 w;
s16 h;
u8 num_mips;
u8 tex1_control;
u8 psm;
u8 mip_shift;
u16 clutpsm;
u16 dest[7];
u16 clut_dest;
u8 width[7];
u32 name_ptr;
u32 size;
float uv_dist;
u32 masks[3];
s32 segment_of_mip(s32 mip) const {
if (2 >= num_mips) {
return num_mips - mip - 1;
} else {
return std::max(0, 2 - mip);
}
}
};
static_assert(sizeof(GoalTexture) == 60, "GoalTexture size");
static_assert(offsetof(GoalTexture, clutpsm) == 8);
static_assert(offsetof(GoalTexture, clut_dest) == 24);
struct GoalTexturePage {
struct Seg {
u32 block_data_ptr;
u32 size;
u32 dest;
};
u32 file_info_ptr;
u32 name_ptr;
u32 id;
s32 length; // texture count
u32 mip0_size;
u32 size;
Seg segment[3];
u32 pad[16];
// start of array.
std::string print() const;
bool try_copy_texture_description(GoalTexture* dest,
int idx,
const u8* memory_base,
const u8* tpage,
u32 s7_ptr) {
u32 ptr;
memcpy(&ptr, tpage + sizeof(GoalTexturePage) + 4 * idx, 4);
if (ptr == s7_ptr) {
return false;
}
memcpy(dest, memory_base + ptr, sizeof(GoalTexture));
return true;
}
};
class TexturePool {
public:
void handle_upload_now(const u8* tpage, int mode, const u8* memory_base, u32 s7_ptr);
void set_texture(u32 location, std::unique_ptr<TextureRecord>&& record);
void set_mt4hh_texture(u32 location, std::unique_ptr<TextureRecord>&& record);
std::vector<std::shared_ptr<TextureRecord>> convert_textures(const u8* tpage,
int mode,
const u8* memory_base,
u32 s7_ptr);
void set_texture(u32 location, std::shared_ptr<TextureRecord> record);
void draw_debug_window();
TextureRecord* lookup(u32 location) {
if (m_textures.at(location).normal_texture) {
@@ -48,7 +128,13 @@ class TexturePool {
void relocate(u32 destination, u32 source, u32 format);
void remove_garbage_textures();
void discard(std::shared_ptr<TextureRecord> tex);
void serialize(Serializer& ser);
private:
void unload_all_textures();
void draw_debug_for_tex(const std::string& name, TextureRecord& tex);
TextureConverter m_tex_converter;
@@ -57,5 +143,10 @@ class TexturePool {
// textures that the game overwrote, but may be still allocated on the GPU.
// TODO: free these periodically.
std::vector<std::unique_ptr<TextureRecord>> m_garbage_textures;
std::vector<std::shared_ptr<TextureRecord>> m_garbage_textures;
char m_regex_input[256] = "";
int m_most_recent_gc_count = 0;
int m_most_recent_gc_count_gpu = 0;
};
+121 -2
View File
@@ -137,7 +137,12 @@ _mips2c_call_linux:
mov [rsp + 144], r9 ;; arg5
mov [rsp + 160], r10 ;; arg6
mov [rsp + 176], r11 ;; arg7
mov [rsp + 464], rsp ;; mip2c code's MIPS stack
mov [rsp + 352], r13 ;; s6 (pp)
mov [rsp + 368], r14 ;; s7 (st)
mov rdi, rsp
sub rdi, r15
mov [rsp + 464], rdi ;; mip2c code's MIPS stack
mov rdi, rsp
@@ -205,7 +210,12 @@ _mips2c_call_windows:
mov [rsp + 144], r9 ;; arg5
mov [rsp + 160], r10 ;; arg6
mov [rsp + 176], r11 ;; arg7
mov [rsp + 464], rsp ;; mip2c code's MIPS stack
mov [rsp + 352], r13 ;; s6 (pp)
mov [rsp + 368], r14 ;; s7 (st)
mov rdi, rsp
sub rdi, r15
mov [rsp + 464], rdi ;; mip2c code's MIPS stack
mov rcx, rsp
@@ -343,6 +353,47 @@ _call_goal_asm_linux:
pop r13
ret
global _call_goal8_asm_linux
_call_goal8_asm_linux:
;; x86 saved registers we need to modify for GOAL should be saved
push r13
push r14
push r15
;; RDI - first arg (func)
;; RSI - second arg (arg array)
;; RDX - third arg (0)
;; RCX - pp (goes in r13)
;; R8 - st (goes in r14)
;; R9 - off (goes in r15)
;; set GOAL function pointer
mov r13, rcx
;; st
mov r14, r8
;; offset
mov r15, r9
;; move function to temp
mov rax, rdi
;; extract arguments
mov rdi, [rsi + 0] ;; 0
mov rdx, [rsi + 16] ;; 2
mov rcx, [rsi + 24] ;; 3
mov r8, [rsi + 32] ;; 4
mov r9, [rsi + 40] ;; 5
mov r10, [rsi + 48] ;; 6
mov r11, [rsi + 56] ;; 7
mov rsi, [rsi + 8] ;; 1 (do this last)
;; call GOAL by function pointer
call rax
;; retore x86 registers.
pop r15
pop r14
pop r13
ret
;; Call goal, but switch stacks.
global _call_goal_on_stack_asm_linux
@@ -448,6 +499,74 @@ _call_goal_asm_win32:
ret
global _call_goal8_asm_win32
_call_goal8_asm_win32:
push rdx ; 8
push rbx ; 16
push rbp ; 24
push rsi ; 32
push rdi ; 40
push r8 ; 48
push r9 ; 56
push r10 ; 64
push r11 ; 72
push r12 ; 80
push r13 ; 88
push r14 ; 96
push r15 ; 104
sub rsp, 16
movups [rsp], xmm6
sub rsp, 16
movups [rsp], xmm7
;; mov rdi, rcx ;; rdi is GOAL first argument, rcx is windows first argument
;; mov rsi, rdx ;; rsi is GOAL second argument, rdx is windows second argument
;; mov rdx, r8 ;; rdx is GOAL third argument, r8 is windows third argument
;; mov r13, r9 ;; r13 is GOAL fp, r9 is windows fourth argument
;; mov r15, [rsp + 184] ;; symbol table
;; mov r14, [rsp + 176] ;; offset
;; call r13
mov r13, r9 ;; pp
mov r15, [rsp + 184] ;; symbol table
mov r14, [rsp + 176] ;; offset
mov rax, rcx ;; func temp
mov rsi, rdx ;; arg table
mov rdi, [rsi + 0] ;; 0
mov rdx, [rsi + 16] ;; 2
mov rcx, [rsi + 24] ;; 3
mov r8, [rsi + 32] ;; 4
mov r9, [rsi + 40] ;; 5
mov r10, [rsi + 48] ;; 6
mov r11, [rsi + 56] ;; 7
mov rsi, [rsi + 8] ;; 1 (do this last)
;; call GOAL by function pointer
call rax
movups xmm7, [rsp]
add rsp, 16
movups xmm6, [rsp]
add rsp, 16
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rbp
pop rbx
pop rdx
ret
global _call_goal_on_stack_asm_win32
_call_goal_on_stack_asm_win32:
+7
View File
@@ -748,6 +748,13 @@ void vif_interrupt_callback() {
}
}
/*!
* Added in PC port.
*/
u32 offset_of_s7() {
return s7.offset;
}
/*!
* Final initialization of the system after the kernel is loaded.
* This is called from InitHeapAndSymbol at the very end.
+1
View File
@@ -128,3 +128,4 @@ struct FileStream {
// static_assert(offsetof(CpadInfo, new_pad) == 76, "cpad type offset");
void vif_interrupt_callback();
u32 offset_of_s7();
+689
View File
@@ -0,0 +1,689 @@
// clang-format off
//--------------------------MIPS2C---------------------
#include "game/mips2c/mips2c_private.h"
#include "game/kernel/kscheme.h"
namespace Mips2C {
namespace sp_process_block_3d {
struct Cache {
void* sp_frame_time; // *sp-frame-time*
void* quaternion; // quaternion*!
void* sp_free_particle; // sp-free-particle
void* sp_relaunch_particle_3d; // sp-relaunch-particle-3d
} cache;
u64 execute(void* ctxt) {
auto* c = (ExecutionContext*)ctxt;
bool bc = false;
u32 call_addr = 0;
bool cop1_bc = false;
c->daddiu(sp, sp, -160); // daddiu sp, sp, -160
c->sd(ra, 0, sp); // sd ra, 0(sp)
c->sd(fp, 8, sp); // sd fp, 8(sp)
c->mov64(fp, t9); // or fp, t9, r0
c->sq(s0, 48, sp); // sq s0, 48(sp)
c->sq(s1, 64, sp); // sq s1, 64(sp)
c->sq(s2, 80, sp); // sq s2, 80(sp)
c->sq(s3, 96, sp); // sq s3, 96(sp)
c->sq(s4, 112, sp); // sq s4, 112(sp)
c->sq(s5, 128, sp); // sq s5, 128(sp)
c->sq(gp, 144, sp); // sq gp, 144(sp)
c->mov64(gp, a0); // or gp, a0, r0
c->mov64(s5, a1); // or s5, a1, r0
c->mov64(s4, a2); // or s4, a2, r0
c->mov64(s0, a3); // or s0, a3, r0
c->mov64(s3, t0); // or s3, t0, r0
c->mov64(s2, t1); // or s2, t1, r0
c->daddiu(s1, sp, 16); // daddiu s1, sp, 16
c->sq(r0, 0, s1); // sq r0, 0(s1)
c->load_symbol(v1, cache.sp_frame_time); // lw v1, *sp-frame-time*(s7)
c->lqc2(vf16, 0, v1); // lqc2 vf16, 0(v1)
c->mov128_gpr_vf(v1, vf16); // qmfc2.i v1, vf16
c->andi(v1, v1, 255); // andi v1, v1, 255
c->sq(v1, 32, sp); // sq v1, 32(sp)
// nop // sll r0, r0, 0
block_1:
c->lw(v1, 128, s5); // lw v1, 128(s5)
bc = c->sgpr64(v1) == c->sgpr64(s7); // beq v1, s7, L83
// nop // sll r0, r0, 0
if (bc) {goto block_34;} // branch non-likely
bc = c->sgpr64(s2) == c->sgpr64(s7); // beq s2, s7, L71
c->lw(v1, 104, s5); // lw v1, 104(s5)
if (bc) {goto block_8;} // branch non-likely
c->andi(v1, v1, 8192); // andi v1, v1, 8192
bc = c->sgpr64(v1) != 0; // bne v1, r0, L71
// nop // sll r0, r0, 0
if (bc) {goto block_8;} // branch non-likely
c->lw(v1, 100, s5); // lw v1, 100(s5)
c->addiu(a0, r0, -1); // addiu a0, r0, -1
bc = c->sgpr64(v1) == c->sgpr64(a0); // beq v1, a0, L70
// nop // sll r0, r0, 0
if (bc) {goto block_6;} // branch non-likely
bc = c->sgpr64(v1) == 0; // beq v1, r0, L82
// nop // sll r0, r0, 0
if (bc) {goto block_33;} // branch non-likely
block_6:
c->lw(a0, 104, s5); // lw a0, 104(s5)
c->andi(v1, a0, 64); // andi v1, a0, 64
c->xor_(a0, a0, v1); // xor a0, a0, v1
bc = c->sgpr64(v1) == 0; // beq v1, r0, L83
c->sw(a0, 104, s5); // sw a0, 104(s5)
if (bc) {goto block_34;} // branch non-likely
c->lw(v1, 124, s5); // lw v1, 124(s5)
//beq r0, r0, L83 // beq r0, r0, L83
c->sw(v1, 44, s4); // sw v1, 44(s4)
goto block_34; // branch always
block_8:
c->lw(v1, 100, s5); // lw v1, 100(s5)
c->addiu(a0, r0, -1); // addiu a0, r0, -1
bc = c->sgpr64(v1) == c->sgpr64(a0); // beq v1, a0, L72
c->lq(a0, 32, sp); // lq a0, 32(sp)
if (bc) {goto block_11;} // branch non-likely
c->dsubu(a0, v1, a0); // dsubu a0, v1, a0
bc = c->sgpr64(v1) == 0; // beq v1, r0, L82
c->pmaxw(v1, a0, r0); // pmaxw v1, a0, r0
if (bc) {goto block_33;} // branch non-likely
c->sw(v1, 100, s5); // sw v1, 100(s5)
block_11:
c->lw(a0, 104, s5); // lw a0, 104(s5)
c->andi(v1, a0, 64); // andi v1, a0, 64
c->xor_(a0, a0, v1); // xor a0, a0, v1
bc = c->sgpr64(v1) == 0; // beq v1, r0, L73
c->sw(a0, 104, s5); // sw a0, 104(s5)
if (bc) {goto block_13;} // branch non-likely
c->lw(v1, 124, s5); // lw v1, 124(s5)
c->sw(v1, 44, s4); // sw v1, 44(s4)
block_13:
c->lw(t9, 112, s5); // lw t9, 112(s5)
bc = c->sgpr64(t9) == 0; // beq t9, r0, L74
// nop // sll r0, r0, 0
if (bc) {goto block_15;} // branch non-likely
c->daddiu(sp, sp, -96); // daddiu sp, sp, -96
c->sq(gp, 0, sp); // sq gp, 0(sp)
c->sq(s5, 16, sp); // sq s5, 16(sp)
c->sq(s4, 32, sp); // sq s4, 32(sp)
c->sq(s0, 48, sp); // sq s0, 48(sp)
c->sq(s3, 64, sp); // sq s3, 64(sp)
c->mov64(a0, gp); // or a0, gp, r0
c->mov64(a1, s5); // or a1, s5, r0
c->mov64(a2, s4); // or a2, s4, r0
call_addr = c->gprs[t9].du32[0]; // function call:
c->sq(s2, 80, sp); // sq s2, 80(sp)
c->jalr(call_addr); // jalr ra, t9
c->lq(gp, 0, sp); // lq gp, 0(sp)
c->lq(s5, 16, sp); // lq s5, 16(sp)
c->lq(s4, 32, sp); // lq s4, 32(sp)
c->lq(s0, 48, sp); // lq s0, 48(sp)
c->lq(s3, 64, sp); // lq s3, 64(sp)
c->lq(s2, 80, sp); // lq s2, 80(sp)
c->daddiu(sp, sp, 96); // daddiu sp, sp, 96
block_15:
c->lw(a1, 120, s5); // lw a1, 120(s5)
c->lw(v1, 116, s5); // lw v1, 116(s5)
bc = c->sgpr64(a1) == 0; // beq a1, r0, L75
c->lq(a0, 32, sp); // lq a0, 32(sp)
if (bc) {goto block_18;} // branch non-likely
c->dsubu(v1, v1, a0); // dsubu v1, v1, a0
bc = ((s64)c->sgpr64(v1)) >= 0; // bgez v1, L75
c->sw(v1, 116, s5); // sw v1, 116(s5)
if (bc) {goto block_18;} // branch non-likely
c->daddiu(sp, sp, -96); // daddiu sp, sp, -96
c->sq(gp, 0, sp); // sq gp, 0(sp)
c->sq(s5, 16, sp); // sq s5, 16(sp)
c->sq(s4, 32, sp); // sq s4, 32(sp)
c->sq(s0, 48, sp); // sq s0, 48(sp)
c->sq(s3, 64, sp); // sq s3, 64(sp)
c->sq(s2, 80, sp); // sq s2, 80(sp)
c->mov64(a0, gp); // or a0, gp, r0
c->mov64(a3, s4); // or a3, s4, r0
c->mov64(a2, s5); // or a2, s5, r0
c->load_symbol(t9, cache.sp_relaunch_particle_3d);// lw t9, sp-relaunch-particle-3d(s7)
call_addr = c->gprs[t9].du32[0]; // function call:
c->sll(v0, ra, 0); // sll v0, ra, 0
c->jalr(call_addr); // jalr ra, t9
c->lq(gp, 0, sp); // lq gp, 0(sp)
c->lq(s5, 16, sp); // lq s5, 16(sp)
c->lq(s4, 32, sp); // lq s4, 32(sp)
c->lq(s0, 48, sp); // lq s0, 48(sp)
c->lq(s3, 64, sp); // lq s3, 64(sp)
c->lq(s2, 80, sp); // lq s2, 80(sp)
c->daddiu(sp, sp, 96); // daddiu sp, sp, 96
block_18:
c->lqc2(vf8, 0, s4); // lqc2 vf8, 0(s4)
c->lqc2(vf9, 16, s4); // lqc2 vf9, 16(s4)
c->lqc2(vf10, 32, s4); // lqc2 vf10, 32(s4)
c->lqc2(vf11, 16, s5); // lqc2 vf11, 16(s5)
c->lqc2(vf12, 32, s5); // lqc2 vf12, 32(s5)
c->lqc2(vf13, 48, s5); // lqc2 vf13, 48(s5)
c->lqc2(vf14, 64, s5); // lqc2 vf14, 64(s5)
c->lwc1(f0, 96, s5); // lwc1 f0, 96(s5)
c->mfc1(v1, f0); // mfc1 v1, f0
c->vmul_bc(DEST::xyzw, BC::z, vf14, vf14, vf16); // vmulz.xyzw vf14, vf14, vf16
bc = c->sgpr64(v1) == 0; // beq v1, r0, L76
c->vadd(DEST::xyz, vf11, vf11, vf14); // vadd.xyz vf11, vf11, vf14
if (bc) {goto block_20;} // branch non-likely
c->mov128_vf_gpr(vf15, v1); // qmtc2.i vf15, v1
c->vsub_bc(DEST::w, BC::x, vf15, vf0, vf15); // vsubx.w vf15, vf0, vf15
c->vmul_bc(DEST::xyzw, BC::w, vf15, vf15, vf16); // vmulw.xyzw vf15, vf15, vf16
c->vsub_bc(DEST::w, BC::w, vf15, vf0, vf15); // vsubw.w vf15, vf0, vf15
c->vmul_bc(DEST::xyz, BC::w, vf11, vf11, vf15); // vmulw.xyz vf11, vf11, vf15
block_20:
c->vmul_bc(DEST::xyzw, BC::y, vf17, vf11, vf16); // vmuly.xyzw vf17, vf11, vf16
c->vmul_bc(DEST::xyzw, BC::y, vf18, vf12, vf16); // vmuly.xyzw vf18, vf12, vf16
c->vmul_bc(DEST::xyzw, BC::y, vf19, vf13, vf16); // vmuly.xyzw vf19, vf13, vf16
c->vadd(DEST::xyzw, vf8, vf8, vf17); // vadd.xyzw vf8, vf8, vf17
c->vadd_bc(DEST::w, BC::w, vf9, vf9, vf18); // vaddw.w vf9, vf9, vf18
c->vadd(DEST::xyzw, vf10, vf10, vf19); // vadd.xyzw vf10, vf10, vf19
c->vmax_bc(DEST::xyzw, BC::x, vf10, vf10, vf0); // vmaxx.xyzw vf10, vf10, vf0
c->sqc2(vf11, 16, s5); // sqc2 vf11, 16(s5)
c->sqc2(vf8, 0, s4); // sqc2 vf8, 0(s4)
c->sqc2(vf9, 16, s4); // sqc2 vf9, 16(s4)
c->sqc2(vf10, 32, s4); // sqc2 vf10, 32(s4)
c->mov64(v1, s1); // or v1, s1, r0
c->mov64(a0, s4); // or a0, s4, r0
c->lwc1(f0, 16, a0); // lwc1 f0, 16(a0)
c->lwc1(f1, 20, a0); // lwc1 f1, 20(a0)
c->lwc1(f3, 24, a0); // lwc1 f3, 24(a0)
c->swc1(f0, 0, v1); // swc1 f0, 0(v1)
c->swc1(f1, 4, v1); // swc1 f1, 4(v1)
c->swc1(f3, 8, v1); // swc1 f3, 8(v1)
c->fprs[f2] = 1.0; // lwc1 f2, L157(fp)
c->muls(f3, f3, f3); // mul.s f3, f3, f3
c->subs(f2, f2, f3); // sub.s f2, f2, f3
c->muls(f1, f1, f1); // mul.s f1, f1, f1
c->subs(f1, f2, f1); // sub.s f1, f2, f1
c->muls(f0, f0, f0); // mul.s f0, f0, f0
c->subs(f0, f1, f0); // sub.s f0, f1, f0
c->sqrts(f0, f0); // sqrt.s f0, f0
c->swc1(f0, 12, v1); // swc1 f0, 12(v1)
c->mfc1(a0, f0); // mfc1 a0, f0
c->load_symbol(v1, cache.sp_frame_time); // lw v1, *sp-frame-time*(s7)
c->lwc1(f0, 0, v1); // lwc1 f0, 0(v1)
c->mfc1(v1, f0); // mfc1 v1, f0
c->andi(v1, v1, 255); // andi v1, v1, 255
c->daddiu(v1, v1, -10); // daddiu v1, v1, -10
bc = ((s64)c->sgpr64(v1)) < 0; // bltz v1, L77
// nop // sll r0, r0, 0
if (bc) {goto block_22;} // branch non-likely
c->load_symbol(t9, cache.quaternion); // lw t9, quaternion*!(s7)
c->mov64(a0, s1); // or a0, s1, r0
c->mov64(a1, s1); // or a1, s1, r0
c->daddiu(a2, s5, 80); // daddiu a2, s5, 80
call_addr = c->gprs[t9].du32[0]; // function call:
c->sll(v0, ra, 0); // sll v0, ra, 0
c->jalr(call_addr); // jalr ra, t9
block_22:
c->load_symbol(t9, cache.quaternion); // lw t9, quaternion*!(s7)
c->mov64(a0, s1); // or a0, s1, r0
c->mov64(a1, s1); // or a1, s1, r0
c->daddiu(a2, s5, 80); // daddiu a2, s5, 80
call_addr = c->gprs[t9].du32[0]; // function call:
c->sll(v0, ra, 0); // sll v0, ra, 0
c->jalr(call_addr); // jalr ra, t9
c->mov64(a0, s4); // or a0, s4, r0
c->mov64(v1, s1); // or v1, s1, r0
c->lwc1(f0, 12, v1); // lwc1 f0, 12(v1)
c->mtc1(f1, r0); // mtc1 f1, r0
cop1_bc = c->fprs[f0] < c->fprs[f1]; // c.lt.s f0, f1
bc = !cop1_bc; // bc1f L78
// nop // sll r0, r0, 0
if (bc) {goto block_24;} // branch non-likely
c->lqc2(vf1, 16, a0); // lqc2 vf1, 16(a0)
c->lqc2(vf2, 0, v1); // lqc2 vf2, 0(v1)
c->vsub(DEST::xyz, vf1, vf0, vf2); // vsub.xyz vf1, vf0, vf2
c->sqc2(vf1, 16, a0); // sqc2 vf1, 16(a0)
c->mov128_gpr_vf(a0, vf1); // qmfc2.i a0, vf1
//beq r0, r0, L79 // beq r0, r0, L79
// nop // sll r0, r0, 0
goto block_25; // branch always
block_24:
c->lqc2(vf1, 16, a0); // lqc2 vf1, 16(a0)
c->lqc2(vf2, 0, v1); // lqc2 vf2, 0(v1)
c->vadd(DEST::xyz, vf1, vf0, vf2); // vadd.xyz vf1, vf0, vf2
c->sqc2(vf1, 16, a0); // sqc2 vf1, 16(a0)
c->mov128_gpr_vf(a0, vf1); // qmfc2.i a0, vf1
block_25:
c->mov128_gpr_vf(v1, vf10); // qmfc2.i v1, vf10
c->lw(a0, 104, s5); // lw a0, 104(s5)
c->andi(a1, a0, 2); // andi a1, a0, 2
bc = c->sgpr64(a1) == 0; // beq a1, r0, L80
c->andi(a1, a0, 4); // andi a1, a0, 4
if (bc) {goto block_28;} // branch non-likely
bc = c->sgpr64(v1) != 0; // bne v1, r0, L80
c->pextuw(a2, v1, r0); // pextuw a2, v1, r0
if (bc) {goto block_28;} // branch non-likely
bc = c->sgpr64(a2) == 0; // beq a2, r0, L82
// nop // sll r0, r0, 0
if (bc) {goto block_33;} // branch non-likely
block_28:
bc = c->sgpr64(a1) == 0; // beq a1, r0, L81
c->andi(a0, a0, 1); // andi a0, a0, 1
if (bc) {goto block_30;} // branch non-likely
c->pcpyud(v1, v1, r0); // pcpyud v1, v1, r0
c->pexew(v1, v1); // pexew v1, v1
bc = ((s64)c->sgpr64(v1)) <= 0; // blez v1, L82
// nop // sll r0, r0, 0
if (bc) {goto block_33;} // branch non-likely
block_30:
bc = c->sgpr64(a0) == 0; // beq a0, r0, L83
// nop // sll r0, r0, 0
if (bc) {goto block_34;} // branch non-likely
c->mov128_gpr_vf(v1, vf8); // qmfc2.i v1, vf8
c->pcpyud(v1, v1, r0); // pcpyud v1, v1, r0
c->pexew(v1, v1); // pexew v1, v1
bc = ((s64)c->sgpr64(v1)) < 0; // bltz v1, L82
c->mov128_gpr_vf(v1, vf9); // qmfc2.i v1, vf9
if (bc) {goto block_33;} // branch non-likely
c->pcpyud(v1, v1, r0); // pcpyud v1, v1, r0
c->pexew(v1, v1); // pexew v1, v1
bc = ((s64)c->sgpr64(v1)) >= 0; // bgez v1, L83
// nop // sll r0, r0, 0
if (bc) {goto block_34;} // branch non-likely
block_33:
c->load_symbol(t9, cache.sp_free_particle); // lw t9, sp-free-particle(s7)
c->mov64(a0, gp); // or a0, gp, r0
c->mov64(a1, s0); // or a1, s0, r0
c->mov64(a2, s5); // or a2, s5, r0
c->mov64(a3, s4); // or a3, s4, r0
call_addr = c->gprs[t9].du32[0]; // function call:
c->sll(v0, ra, 0); // sll v0, ra, 0
c->jalr(call_addr); // jalr ra, t9
block_34:
c->daddiu(s3, s3, -1); // daddiu s3, s3, -1
c->daddiu(s5, s5, 144); // daddiu s5, s5, 144
c->daddiu(s4, s4, 48); // daddiu s4, s4, 48
bc = c->sgpr64(s3) != 0; // bne s3, r0, L69
c->daddiu(s0, s0, 1); // daddiu s0, s0, 1
if (bc) {goto block_1;} // branch non-likely
c->mov64(v0, s0); // or v0, s0, r0
c->ld(ra, 0, sp); // ld ra, 0(sp)
c->ld(fp, 8, sp); // ld fp, 8(sp)
c->lq(gp, 144, sp); // lq gp, 144(sp)
c->lq(s5, 128, sp); // lq s5, 128(sp)
c->lq(s4, 112, sp); // lq s4, 112(sp)
c->lq(s3, 96, sp); // lq s3, 96(sp)
c->lq(s2, 80, sp); // lq s2, 80(sp)
c->lq(s1, 64, sp); // lq s1, 64(sp)
c->lq(s0, 48, sp); // lq s0, 48(sp)
//jr ra // jr ra
c->daddiu(sp, sp, 160); // daddiu sp, sp, 160
goto end_of_function; // return
// nop // sll r0, r0, 0
// nop // sll r0, r0, 0
// nop // sll r0, r0, 0
end_of_function:
return c->gprs[v0].du64[0];
}
void link() {
cache.sp_frame_time = intern_from_c("*sp-frame-time*").c();
cache.quaternion = intern_from_c("quaternion*!").c();
cache.sp_free_particle = intern_from_c("sp-free-particle").c();
cache.sp_relaunch_particle_3d = intern_from_c("sp-relaunch-particle-3d").c();
gLinkedFunctionTable.reg("sp-process-block-3d", execute, 256);
}
} // namespace sp_process_block_3d
} // namespace Mips2C
//--------------------------MIPS2C---------------------
#include "game/mips2c/mips2c_private.h"
#include "game/kernel/kscheme.h"
namespace Mips2C {
namespace sp_process_block_2d {
struct Cache {
void* sp_frame_time; // *sp-frame-time*
void* sp_free_particle; // sp-free-particle
void* sp_orbiter; // sp-orbiter
void* sp_relaunch_particle_2d; // sp-relaunch-particle-2d
} cache;
u64 execute(void* ctxt) {
auto* c = (ExecutionContext*)ctxt;
bool bc = false;
u32 call_addr = 0;
c->daddiu(sp, sp, -128); // daddiu sp, sp, -128
c->sd(ra, 0, sp); // sd ra, 0(sp)
c->sq(s0, 16, sp); // sq s0, 16(sp)
c->sq(s1, 32, sp); // sq s1, 32(sp)
c->sq(s2, 48, sp); // sq s2, 48(sp)
c->sq(s3, 64, sp); // sq s3, 64(sp)
c->sq(s4, 80, sp); // sq s4, 80(sp)
c->sq(s5, 96, sp); // sq s5, 96(sp)
c->sq(gp, 112, sp); // sq gp, 112(sp)
c->mov64(gp, a0); // or gp, a0, r0
c->mov64(s5, a1); // or s5, a1, r0
c->mov64(s4, a2); // or s4, a2, r0
c->mov64(s1, a3); // or s1, a3, r0
c->mov64(s3, t0); // or s3, t0, r0
c->mov64(s2, t1); // or s2, t1, r0
c->load_symbol(v1, cache.sp_frame_time); // lw v1, *sp-frame-time*(s7)
c->lqc2(vf9, 0, v1); // lqc2 vf9, 0(v1)
c->mov128_gpr_vf(v1, vf9); // qmfc2.i v1, vf9
c->andi(s0, v1, 255); // andi s0, v1, 255
block_1:
c->lw(v1, 128, s5); // lw v1, 128(s5)
bc = c->sgpr64(v1) == c->sgpr64(s7); // beq v1, s7, L97
// nop // sll r0, r0, 0
if (bc) {goto block_31;} // branch non-likely
bc = c->sgpr64(s2) == c->sgpr64(s7); // beq s2, s7, L87
c->lw(v1, 104, s5); // lw v1, 104(s5)
if (bc) {goto block_8;} // branch non-likely
c->andi(v1, v1, 8192); // andi v1, v1, 8192
bc = c->sgpr64(v1) != 0; // bne v1, r0, L87
// nop // sll r0, r0, 0
if (bc) {goto block_8;} // branch non-likely
c->lw(v1, 100, s5); // lw v1, 100(s5)
c->addiu(a0, r0, -1); // addiu a0, r0, -1
bc = c->sgpr64(v1) == c->sgpr64(a0); // beq v1, a0, L86
// nop // sll r0, r0, 0
if (bc) {goto block_6;} // branch non-likely
bc = c->sgpr64(v1) == 0; // beq v1, r0, L96
// nop // sll r0, r0, 0
if (bc) {goto block_30;} // branch non-likely
block_6:
c->lw(a0, 104, s5); // lw a0, 104(s5)
c->andi(v1, a0, 64); // andi v1, a0, 64
c->xor_(a0, a0, v1); // xor a0, a0, v1
bc = c->sgpr64(v1) == 0; // beq v1, r0, L97
c->sw(a0, 104, s5); // sw a0, 104(s5)
if (bc) {goto block_31;} // branch non-likely
c->lw(v1, 124, s5); // lw v1, 124(s5)
//beq r0, r0, L97 // beq r0, r0, L97
c->sw(v1, 44, s4); // sw v1, 44(s4)
goto block_31; // branch always
block_8:
c->lw(v1, 100, s5); // lw v1, 100(s5)
c->addiu(a0, r0, -1); // addiu a0, r0, -1
bc = c->sgpr64(v1) == c->sgpr64(a0); // beq v1, a0, L88
c->dsubu(a0, v1, s0); // dsubu a0, v1, s0
if (bc) {goto block_11;} // branch non-likely
bc = c->sgpr64(v1) == 0; // beq v1, r0, L96
c->pmaxw(v1, a0, r0); // pmaxw v1, a0, r0
if (bc) {goto block_30;} // branch non-likely
c->sw(v1, 100, s5); // sw v1, 100(s5)
block_11:
c->lw(a0, 104, s5); // lw a0, 104(s5)
c->andi(v1, a0, 64); // andi v1, a0, 64
c->xor_(a0, a0, v1); // xor a0, a0, v1
bc = c->sgpr64(v1) == 0; // beq v1, r0, L89
c->sw(a0, 104, s5); // sw a0, 104(s5)
if (bc) {goto block_13;} // branch non-likely
c->lw(v1, 124, s5); // lw v1, 124(s5)
c->sw(v1, 44, s4); // sw v1, 44(s4)
block_13:
c->lw(t9, 112, s5); // lw t9, 112(s5)
bc = c->sgpr64(t9) == 0; // beq t9, r0, L90
// nop // sll r0, r0, 0
if (bc) {goto block_15;} // branch non-likely
c->daddiu(sp, sp, -80); // daddiu sp, sp, -80
c->sq(gp, 0, sp); // sq gp, 0(sp)
c->sq(s5, 16, sp); // sq s5, 16(sp)
c->sq(s4, 32, sp); // sq s4, 32(sp)
c->sq(s1, 48, sp); // sq s1, 48(sp)
c->mov64(a0, gp); // or a0, gp, r0
c->mov64(a1, s5); // or a1, s5, r0
c->mov64(a2, s4); // or a2, s4, r0
call_addr = c->gprs[t9].du32[0]; // function call:
c->sq(s3, 64, sp); // sq s3, 64(sp)
c->jalr(call_addr); // jalr ra, t9
c->lq(gp, 0, sp); // lq gp, 0(sp)
c->lq(s5, 16, sp); // lq s5, 16(sp)
c->lq(s4, 32, sp); // lq s4, 32(sp)
c->lq(s1, 48, sp); // lq s1, 48(sp)
c->lq(s3, 64, sp); // lq s3, 64(sp)
c->daddiu(sp, sp, 80); // daddiu sp, sp, 80
block_15:
c->lw(a1, 120, s5); // lw a1, 120(s5)
c->lw(v1, 116, s5); // lw v1, 116(s5)
bc = c->sgpr64(a1) == 0; // beq a1, r0, L91
c->dsubu(a0, v1, s0); // dsubu a0, v1, s0
if (bc) {goto block_18;} // branch non-likely
c->daddiu(v1, a0, -1); // daddiu v1, a0, -1
bc = ((s64)c->sgpr64(v1)) >= 0; // bgez v1, L91
c->sw(a0, 116, s5); // sw a0, 116(s5)
if (bc) {goto block_18;} // branch non-likely
c->daddiu(sp, sp, -96); // daddiu sp, sp, -96
c->sq(gp, 0, sp); // sq gp, 0(sp)
c->sq(s5, 16, sp); // sq s5, 16(sp)
c->sq(s4, 32, sp); // sq s4, 32(sp)
c->sq(s1, 48, sp); // sq s1, 48(sp)
c->sq(s3, 64, sp); // sq s3, 64(sp)
c->sq(s2, 80, sp); // sq s2, 80(sp)
c->mov64(a0, gp); // or a0, gp, r0
c->mov64(a3, s4); // or a3, s4, r0
c->mov64(a2, s5); // or a2, s5, r0
c->load_symbol(t9, cache.sp_relaunch_particle_2d);// lw t9, sp-relaunch-particle-2d(s7)
call_addr = c->gprs[t9].du32[0]; // function call:
c->sll(v0, ra, 0); // sll v0, ra, 0
c->jalr(call_addr); // jalr ra, t9
c->lq(gp, 0, sp); // lq gp, 0(sp)
c->lq(s5, 16, sp); // lq s5, 16(sp)
c->lq(s4, 32, sp); // lq s4, 32(sp)
c->lq(s1, 48, sp); // lq s1, 48(sp)
c->lq(s3, 64, sp); // lq s3, 64(sp)
c->lq(s2, 80, sp); // lq s2, 80(sp)
c->daddiu(sp, sp, 96); // daddiu sp, sp, 96
block_18:
c->lqc2(vf1, 0, s4); // lqc2 vf1, 0(s4)
c->lqc2(vf2, 16, s4); // lqc2 vf2, 16(s4)
c->lqc2(vf3, 32, s4); // lqc2 vf3, 32(s4)
c->lqc2(vf4, 16, s5); // lqc2 vf4, 16(s5)
c->lqc2(vf5, 32, s5); // lqc2 vf5, 32(s5)
c->lqc2(vf6, 48, s5); // lqc2 vf6, 48(s5)
c->lqc2(vf7, 64, s5); // lqc2 vf7, 64(s5)
c->lwc1(f0, 96, s5); // lwc1 f0, 96(s5)
c->mfc1(v1, f0); // mfc1 v1, f0
c->vmul_bc(DEST::xyzw, BC::z, vf7, vf7, vf9); // vmulz.xyzw vf7, vf7, vf9
bc = c->sgpr64(v1) == 0; // beq v1, r0, L92
c->vadd(DEST::xyz, vf4, vf4, vf7); // vadd.xyz vf4, vf4, vf7
if (bc) {goto block_20;} // branch non-likely
c->mov128_vf_gpr(vf8, v1); // qmtc2.i vf8, v1
c->vsub_bc(DEST::w, BC::x, vf8, vf0, vf8); // vsubx.w vf8, vf0, vf8
c->vmul_bc(DEST::xyzw, BC::w, vf8, vf8, vf9); // vmulw.xyzw vf8, vf8, vf9
c->vsub_bc(DEST::w, BC::w, vf8, vf0, vf8); // vsubw.w vf8, vf0, vf8
c->vmul_bc(DEST::xyz, BC::w, vf4, vf4, vf8); // vmulw.xyz vf4, vf4, vf8
block_20:
c->vmul_bc(DEST::xyzw, BC::y, vf10, vf4, vf9); // vmuly.xyzw vf10, vf4, vf9
c->vmul_bc(DEST::xyzw, BC::y, vf11, vf5, vf9); // vmuly.xyzw vf11, vf5, vf9
c->vmul_bc(DEST::xyzw, BC::y, vf12, vf6, vf9); // vmuly.xyzw vf12, vf6, vf9
c->vadd(DEST::xyzw, vf1, vf1, vf10); // vadd.xyzw vf1, vf1, vf10
c->vadd(DEST::zw, vf2, vf2, vf11); // vadd.zw vf2, vf2, vf11
c->vadd(DEST::xyzw, vf3, vf3, vf12); // vadd.xyzw vf3, vf3, vf12
c->vmax_bc(DEST::xyzw, BC::x, vf3, vf3, vf0); // vmaxx.xyzw vf3, vf3, vf0
c->sqc2(vf4, 16, s5); // sqc2 vf4, 16(s5)
c->sqc2(vf1, 0, s4); // sqc2 vf1, 0(s4)
c->sqc2(vf2, 16, s4); // sqc2 vf2, 16(s4)
c->sqc2(vf3, 32, s4); // sqc2 vf3, 32(s4)
c->lwc1(f0, 24, s4); // lwc1 f0, 24(s4)
c->cvtws(f0, f0); // cvt.w.s f0, f0
c->mfc1(v1, f0); // mfc1 v1, f0
c->dsll32(v1, v1, 16); // dsll32 v1, v1, 16
c->dsra32(v1, v1, 16); // dsra32 v1, v1, 16
c->mtc1(f0, v1); // mtc1 f0, v1
c->cvtsw(f0, f0); // cvt.s.w f0, f0
c->swc1(f0, 24, s4); // swc1 f0, 24(s4)
c->lw(v1, 104, s5); // lw v1, 104(s5)
c->andi(v1, v1, 128); // andi v1, v1, 128
bc = c->sgpr64(v1) == 0; // beq v1, r0, L93
c->load_symbol(t9, cache.sp_orbiter); // lw t9, sp-orbiter(s7)
if (bc) {goto block_22;} // branch non-likely
c->daddiu(sp, sp, -96); // daddiu sp, sp, -96
c->sq(gp, 0, sp); // sq gp, 0(sp)
c->sq(s5, 16, sp); // sq s5, 16(sp)
c->sq(s4, 32, sp); // sq s4, 32(sp)
c->sq(s1, 48, sp); // sq s1, 48(sp)
c->sq(s3, 64, sp); // sq s3, 64(sp)
c->mov64(a0, gp); // or a0, gp, r0
c->mov64(a1, s5); // or a1, s5, r0
c->mov64(a2, s4); // or a2, s4, r0
call_addr = c->gprs[t9].du32[0]; // function call:
c->sq(s2, 80, sp); // sq s2, 80(sp)
c->jalr(call_addr); // jalr ra, t9
c->lq(gp, 0, sp); // lq gp, 0(sp)
c->lq(s5, 16, sp); // lq s5, 16(sp)
c->lq(s4, 32, sp); // lq s4, 32(sp)
c->lq(s1, 48, sp); // lq s1, 48(sp)
c->lq(s3, 64, sp); // lq s3, 64(sp)
c->lq(s2, 80, sp); // lq s2, 80(sp)
c->daddiu(sp, sp, 96); // daddiu sp, sp, 96
block_22:
c->lq(v1, 32, s4); // lq v1, 32(s4)
c->lw(a0, 104, s5); // lw a0, 104(s5)
c->andi(a1, a0, 2); // andi a1, a0, 2
bc = c->sgpr64(a1) == 0; // beq a1, r0, L94
c->andi(a1, a0, 4); // andi a1, a0, 4
if (bc) {goto block_25;} // branch non-likely
bc = c->sgpr64(v1) != 0; // bne v1, r0, L94
c->pextuw(t4, v1, r0); // pextuw t4, v1, r0
if (bc) {goto block_25;} // branch non-likely
bc = c->sgpr64(t4) == 0; // beq t4, r0, L96
// nop // sll r0, r0, 0
if (bc) {goto block_30;} // branch non-likely
block_25:
bc = c->sgpr64(a1) == 0; // beq a1, r0, L95
c->andi(a0, a0, 1); // andi a0, a0, 1
if (bc) {goto block_27;} // branch non-likely
c->pcpyud(t4, v1, r0); // pcpyud t4, v1, r0
c->pexew(t4, t4); // pexew t4, r0, t4
bc = ((s64)c->sgpr64(t4)) <= 0; // blez t4, L96
// nop // sll r0, r0, 0
if (bc) {goto block_30;} // branch non-likely
block_27:
bc = c->sgpr64(a0) == 0; // beq a0, r0, L97
// nop // sll r0, r0, 0
if (bc) {goto block_31;} // branch non-likely
c->mov128_gpr_vf(v1, vf1); // qmfc2.i v1, vf1
c->pcpyud(v1, v1, r0); // pcpyud v1, v1, r0
c->pexew(v1, v1); // pexew v1, r0, v1
bc = ((s64)c->sgpr64(v1)) < 0; // bltz v1, L96
c->mov128_gpr_vf(v1, vf2); // qmfc2.i v1, vf2
if (bc) {goto block_30;} // branch non-likely
c->pcpyud(v1, v1, r0); // pcpyud v1, v1, r0
c->pexew(v1, v1); // pexew v1, r0, v1
bc = ((s64)c->sgpr64(v1)) >= 0; // bgez v1, L97
// nop // sll r0, r0, 0
if (bc) {goto block_31;} // branch non-likely
block_30:
c->load_symbol(t9, cache.sp_free_particle); // lw t9, sp-free-particle(s7)
c->mov64(a0, gp); // or a0, gp, r0
c->mov64(a1, s1); // or a1, s1, r0
c->mov64(a2, s5); // or a2, s5, r0
c->mov64(a3, s4); // or a3, s4, r0
call_addr = c->gprs[t9].du32[0]; // function call:
c->sll(v0, ra, 0); // sll v0, ra, 0
c->jalr(call_addr); // jalr ra, t9
block_31:
c->daddiu(s3, s3, -1); // daddiu s3, s3, -1
c->daddiu(s5, s5, 144); // daddiu s5, s5, 144
c->daddiu(s4, s4, 48); // daddiu s4, s4, 48
bc = c->sgpr64(s3) != 0; // bne s3, r0, L85
c->daddiu(s1, s1, 1); // daddiu s1, s1, 1
if (bc) {goto block_1;} // branch non-likely
c->mov64(v0, s1); // or v0, s1, r0
c->ld(ra, 0, sp); // ld ra, 0(sp)
c->lq(gp, 112, sp); // lq gp, 112(sp)
c->lq(s5, 96, sp); // lq s5, 96(sp)
c->lq(s4, 80, sp); // lq s4, 80(sp)
c->lq(s3, 64, sp); // lq s3, 64(sp)
c->lq(s2, 48, sp); // lq s2, 48(sp)
c->lq(s1, 32, sp); // lq s1, 32(sp)
c->lq(s0, 16, sp); // lq s0, 16(sp)
//jr ra // jr ra
c->daddiu(sp, sp, 128); // daddiu sp, sp, 128
goto end_of_function; // return
// nop // sll r0, r0, 0
// nop // sll r0, r0, 0
// nop // sll r0, r0, 0
end_of_function:
return c->gprs[v0].du64[0];
}
void link() {
cache.sp_frame_time = intern_from_c("*sp-frame-time*").c();
cache.sp_free_particle = intern_from_c("sp-free-particle").c();
cache.sp_orbiter = intern_from_c("sp-orbiter").c();
cache.sp_relaunch_particle_2d = intern_from_c("sp-relaunch-particle-2d").c();
gLinkedFunctionTable.reg("sp-process-block-2d", execute, 256);
}
} // namespace sp_process_block_2d
} // namespace Mips2C
File diff suppressed because it is too large Load Diff
+51 -1
View File
@@ -15,4 +15,54 @@ u64 execute(void* ctxt) {
return 0;
}
} // namespace test_func
} // namespace Mips2C
} // namespace Mips2C
//--------------------------MIPS2C---------------------
#include "game/mips2c/mips2c_private.h"
#include "game/kernel/kscheme.h"
#include "game/kernel/kprint.h"
namespace Mips2C {
namespace goal_call_test {
struct Cache {
void* goal_check_function;
} cache;
u64 execute(void* ctxt) {
auto* c = (ExecutionContext*)ctxt;
u32 call_addr = 0;
c->daddiu(sp, sp, -128); // daddiu sp, sp, -128
c->sd(ra, 0, sp); // sd ra, 0(sp)
c->sq(s0, 16, sp); // sq s0, 16(sp)
c->sq(s1, 32, sp); // sq s1, 32(sp)
c->sq(s2, 48, sp); // sq s2, 48(sp)
c->sq(s3, 64, sp); // sq s3, 64(sp)
c->sq(s4, 80, sp); // sq s4, 80(sp)
c->sq(s5, 96, sp); // sq s5, 96(sp)
c->sq(gp, 112, sp); // sq gp, 112(sp)
c->load_symbol(t9, cache.goal_check_function);
call_addr = c->gprs[t9].du32[0]; // function call:
c->sll(v0, ra, 0); // sll v0, ra, 0
c->jalr(call_addr); // jalr ra, t9
c->ld(ra, 0, sp); // ld ra, 0(sp)
c->lq(gp, 112, sp); // lq gp, 112(sp)
c->lq(s5, 96, sp); // lq s5, 96(sp)
c->lq(s4, 80, sp); // lq s4, 80(sp)
c->lq(s3, 64, sp); // lq s3, 64(sp)
c->lq(s2, 48, sp); // lq s2, 48(sp)
c->lq(s1, 32, sp); // lq s1, 32(sp)
c->lq(s0, 16, sp); // lq s0, 16(sp)
c->daddiu(sp, sp, 128); // daddiu sp, sp, 128
goto end_of_function; // return
end_of_function:
return c->gprs[v0].du64[0];
}
void link() {
cache.goal_check_function = intern_from_c("goal_check_function").c();
}
} // namespace goal_call_test
} // namespace Mips2C
+201
View File
@@ -1,15 +1,23 @@
#pragma once
#include <cstring>
#include <cmath>
#include "common/common_types.h"
#include "game/mips2c/mips2c_table.h"
#include "common/util/assert.h"
#include "third-party/fmt/core.h"
// This file contains utility functions for code generated by the mips2c pass.
// This is only useful for
extern u8* g_ee_main_mem;
extern "C" {
u64 _call_goal8_asm_linux(void* func, u64* arg_array, u64 zero, u64 pp, u64 st, void* off);
u64 _call_goal8_asm_win32(void* func, u64* arg_array, u64 zero, u64 pp, u64 st, void* off);
}
namespace Mips2C {
// nicknames for GPRs
@@ -198,12 +206,28 @@ struct ExecutionContext {
memcpy(&vfs[vf], g_ee_main_mem + gpr_src(gpr).du32[0] + offset, 16);
}
void lwc1(int dst, int offset, int gpr) {
memcpy(&fprs[dst], g_ee_main_mem + gpr_src(gpr).du32[0] + offset, 4);
}
void lw(int dst, int offset, int src) {
s32 val;
memcpy(&val, g_ee_main_mem + gpr_src(src).du32[0] + offset, 4);
gprs[dst].ds64[0] = val;
}
void lh(int dst, int offset, int src) {
s16 val;
memcpy(&val, g_ee_main_mem + gpr_src(src).du32[0] + offset, 2);
gprs[dst].ds64[0] = val;
}
void lhu(int dst, int offset, int src) {
u16 val;
memcpy(&val, g_ee_main_mem + gpr_src(src).du32[0] + offset, 2);
gprs[dst].du64[0] = val;
}
void lwu(int dst, int offset, int src) {
u32 val;
memcpy(&val, g_ee_main_mem + gpr_src(src).du32[0] + offset, 4);
@@ -214,11 +238,38 @@ struct ExecutionContext {
memcpy(&gprs[dst].du64[0], g_ee_main_mem + gpr_addr(src) + offset, 16);
}
void ld(int dst, int offset, int src) {
memcpy(&gprs[dst].du64[0], g_ee_main_mem + gpr_addr(src) + offset, 8);
}
void sw(int src, int offset, int addr) {
auto s = gpr_src(src);
memcpy(g_ee_main_mem + gpr_addr(addr) + offset, &s.du32[0], 4);
}
void jalr(u32 addr) {
// u64 _call_goal8_asm_linux(u64 func, u64* arg_array, u64 zero, u64 pp, u64 st, u64 off);
u64 args[8] = {gprs[a0].du64[0], gprs[a1].du64[0], gprs[a2].du64[0], gprs[a3].du64[0],
gprs[t0].du64[0], gprs[t1].du64[0], gprs[t2].du64[0], gprs[t3].du64[0]};
#ifdef __linux__
gprs[v0].du64[0] = _call_goal8_asm_linux(g_ee_main_mem + addr, args, 0, gprs[s6].du64[0],
gprs[s7].du64[0], g_ee_main_mem);
#elif _WIN32
gprs[v0].du64[0] = _call_goal8_asm_win32(g_ee_main_mem + addr, args, 0, gprs[s6].du64[0],
gprs[s7].du64[0], g_ee_main_mem);
#endif
}
void sh(int src, int offset, int addr) {
auto s = gpr_src(src);
memcpy(g_ee_main_mem + gpr_addr(addr) + offset, &s.du32[0], 2);
}
void sd(int src, int offset, int addr) {
auto s = gpr_src(src);
memcpy(g_ee_main_mem + gpr_addr(addr) + offset, &s.du32[0], 8);
}
void sq(int src, int offset, int addr) {
auto s = gpr_src(src);
memcpy(g_ee_main_mem + gpr_addr(addr) + offset, &s.du32[0], 16);
@@ -226,9 +277,14 @@ struct ExecutionContext {
void sqc2(int src, int offset, int addr) {
auto s = vf_src(src);
assert(((gpr_addr(addr) + offset) & 0xf) == 0);
memcpy(g_ee_main_mem + gpr_addr(addr) + offset, &s.du32[0], 16);
}
void swc1(int src, int offset, int addr) {
memcpy(g_ee_main_mem + gpr_addr(addr) + offset, &fprs[src], 4);
}
void vadd_bc(DEST mask, BC bc, int dest, int src0, int src1) {
auto s0 = vf_src(src0);
auto s1 = vf_src(src1);
@@ -240,6 +296,52 @@ struct ExecutionContext {
}
}
void vmini_bc(DEST mask, BC bc, int dest, int src0, int src1) {
auto s0 = vf_src(src0);
auto s1 = vf_src(src1);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dest].f[i] = std::min(s0.f[i], s1.f[(int)bc]);
}
}
}
void vmax_bc(DEST mask, BC bc, int dest, int src0, int src1) {
auto s0 = vf_src(src0);
auto s1 = vf_src(src1);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dest].f[i] = std::max(s0.f[i], s1.f[(int)bc]);
}
}
}
void pextuw(int dst, int src0, int src1) {
auto s0 = gpr_src(src0);
auto s1 = gpr_src(src1);
gprs[dst].du32[0] = s1.du32[2];
gprs[dst].du32[1] = s0.du32[2];
gprs[dst].du32[2] = s1.du32[3];
gprs[dst].du32[3] = s0.du32[3];
}
void pcpyud(int dst, int src0, int src1) {
auto s0 = gpr_src(src0);
auto s1 = gpr_src(src1);
gprs[dst].du64[0] = s0.du64[1];
gprs[dst].du64[1] = s1.du64[1];
}
void pexew(int dst, int src) {
auto s = gpr_src(src);
gprs[dst].du32[0] = s.du32[2];
gprs[dst].du32[1] = s.du32[1];
gprs[dst].du32[2] = s.du32[0];
gprs[dst].du32[3] = s.du32[3];
}
void vsub_bc(DEST mask, BC bc, int dest, int src0, int src1) {
auto s0 = vf_src(src0);
auto s1 = vf_src(src1);
@@ -332,6 +434,10 @@ struct ExecutionContext {
Q = vf_src(src0).f[(int)bc0] / vf_src(src1).f[(int)bc1];
}
void vsqrt(int src, BC bc) { Q = std::sqrt(std::abs(vf_src(src).f[(int)bc])); }
void sqrts(int src, int dst) { fprs[dst] = std::sqrt(std::abs(fprs[src])); }
void vmulq(DEST mask, int dst, int src) {
auto s0 = vf_src(src);
for (int i = 0; i < 4; i++) {
@@ -341,6 +447,36 @@ struct ExecutionContext {
}
}
void vrget(DEST mask, int dst) {
float r = gRng.R;
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dst].f[i] = r;
}
}
}
void vrxor(int src, BC bc) { gRng.rxor(vf_src(src).du32[(int)bc]); }
void vaddq(DEST mask, int dst, int src) {
auto s = vf_src(src);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dst].f[i] = s.f[i] + Q;
}
}
}
void vrnext(DEST mask, int dst) {
gRng.advance();
float r = gRng.R;
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dst].f[i] = r;
}
}
}
void mov64(int dest, int src) { gprs[dest].ds64[0] = gpr_src(src).du64[0]; }
void vmove(DEST mask, int dest, int src) {
@@ -358,7 +494,11 @@ struct ExecutionContext {
gprs[dst].ds64[0] = value_signed;
}
void dsra(int dst, int src, int sa) { gprs[dst].ds64[0] = gpr_src(src).ds64[0] >> sa; }
void dsra32(int dst, int src, int sa) { gprs[dst].ds64[0] = gpr_src(src).ds64[0] >> (32 + sa); }
void sra(int dst, int src, int sa) { gprs[dst].ds64[0] = gpr_src(src).ds32[0] >> sa; }
void dsll(int dst, int src0, int sa) { gprs[dst].du64[0] = gpr_src(src0).du64[0] << sa; }
void dsll32(int dst, int src0, int sa) { gprs[dst].du64[0] = gpr_src(src0).du64[0] << (32 + sa); }
void daddu(int dst, int src0, int src1) { gprs[dst].du64[0] = sgpr64(src0) + sgpr64(src1); }
void daddiu(int dst, int src0, s64 imm) { gprs[dst].du64[0] = sgpr64(src0) + imm; }
@@ -367,11 +507,19 @@ struct ExecutionContext {
gprs[dst].ds64[0] = temp;
}
void lui(int dst, u32 src) {
s32 val = (src << 16);
gprs[dst].ds64[0] = val;
}
void addu(int dst, int src0, int src1) {
s32 temp = sgpr64(src0) + sgpr64(src1);
gprs[dst].ds64[0] = temp;
}
void dsubu(int dst, int src0, int src1) { gprs[dst].du64[0] = sgpr64(src0) - sgpr64(src1); }
void xor_(int dst, int src0, int src1) { gprs[dst].du64[0] = sgpr64(src0) ^ sgpr64(src1); }
void movz(int dst, int src0, int src1) {
if (sgpr64(src1) == 0) {
gprs[dst].du64[0] = sgpr64(src0);
@@ -384,6 +532,12 @@ struct ExecutionContext {
}
}
void mult3(int dst, int src0, int src1) {
u32 result = gpr_src(src0).du32[0] * gpr_src(src1).du32[0];
s32 sresult = result;
gprs[dst].ds64[0] = sresult;
}
void andi(int dest, int src, u64 imm) { gprs[dest].du64[0] = gpr_src(src).du64[0] & imm; }
void ori(int dest, int src, u64 imm) { gprs[dest].du64[0] = gpr_src(src).du64[0] | imm; }
void and_(int dest, int src0, int src1) {
@@ -431,6 +585,22 @@ struct ExecutionContext {
gprs[dst].du64[1] = s0.du64[1] | s1.du64[1];
}
void pmaxw(int dst, int src0, int src1) {
auto s0 = gpr_src(src0);
auto s1 = gpr_src(src1);
for (int i = 0; i < 4; i++) {
gprs[dst].ds32[i] = std::max(s0.ds32[i], s1.ds32[i]);
}
}
void pminw(int dst, int src0, int src1) {
auto s0 = gpr_src(src0);
auto s1 = gpr_src(src1);
for (int i = 0; i < 4; i++) {
gprs[dst].ds32[i] = std::min(s0.ds32[i], s1.ds32[i]);
}
}
void mov128_vf_gpr(int dst, int src) { vfs[dst] = gpr_src(src); }
void mov128_gpr_vf(int dst, int src) { gprs[dst] = vf_src(src); }
void mov128_gpr_gpr(int dst, int src) { gprs[dst] = gpr_src(src); }
@@ -453,11 +623,42 @@ struct ExecutionContext {
}
}
void vftoi0(DEST mask, int dst, int src) {
auto s = vf_src(src);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dst].ds32[i] = s.f[i];
}
}
}
void mfc1(int dst, int src) {
s32 val;
memcpy(&val, &fprs[src], 4);
gprs[dst].ds64[0] = val;
}
void mtc1(int dst, int src) {
u32 val = gpr_src(src).du32[0];
memcpy(&fprs[dst], &val, 4);
}
void muls(int dst, int src0, int src1) { fprs[dst] = fprs[src0] * fprs[src1]; }
void adds(int dst, int src0, int src1) { fprs[dst] = fprs[src0] + fprs[src1]; }
void subs(int dst, int src0, int src1) { fprs[dst] = fprs[src0] - fprs[src1]; }
void cvtws(int dst, int src) {
// float to int
s32 value = fprs[src];
memcpy(&fprs[dst], &value, 4);
}
void cvtsw(int dst, int src) {
// int to float
s32 value;
memcpy(&value, &fprs[src], 4);
fprs[dst] = value;
}
};
} // namespace Mips2C
+25 -1
View File
@@ -16,9 +16,33 @@ namespace draw_string {
extern void link();
}
namespace sp_init_fields {
extern void link();
}
namespace particle_adgif {
extern void link();
}
namespace sp_launch_particles_var {
extern void link();
}
namespace sp_process_block_3d {
extern void link();
}
namespace sp_process_block_2d {
extern void link();
}
LinkedFunctionTable gLinkedFunctionTable;
Rng gRng;
std::unordered_map<std::string, std::vector<void (*)()>> gMips2CLinkCallbacks = {
{"font", {draw_string::link}}};
{"font", {draw_string::link}},
{"sparticle-launcher",
{sp_init_fields::link, particle_adgif::link, sp_launch_particles_var::link}},
{"sparticle", {sp_process_block_3d::link, sp_process_block_2d::link}}};
void LinkedFunctionTable::reg(const std::string& name, u64 (*exec)(void*), u32 stack_size) {
const auto& it = m_executes.insert({name, {exec, Ptr<u8>()}});
+41
View File
@@ -3,6 +3,7 @@
#include <unordered_map>
#include <vector>
#include <string>
#include <cstring>
#include "game/kernel/Ptr.h"
#include "common/common_types.h"
@@ -26,4 +27,44 @@ class LinkedFunctionTable {
extern std::unordered_map<std::string, std::vector<void (*)()>> gMips2CLinkCallbacks;
extern LinkedFunctionTable gLinkedFunctionTable;
struct Rng {
Rng() { init(); }
float R = 0.;
u32 R_u32() {
u32 result;
memcpy(&result, &R, 4);
return result;
}
float from23_bits(float in) {
u32 val;
memcpy(&val, &in, 4);
val = 0x3F800000 | (0x0007FFFFF & val);
memcpy(&in, &val, 4);
return in;
}
float from23_bits(u32 val) {
val = 0x3F800000 | (0x0007FFFFF & val);
float out;
memcpy(&out, &val, 4);
return out;
}
void init(float rinit = 1.418091058731079f) { R = from23_bits(rinit); }
void advance() {
u32 r32 = R_u32();
u32 x = 1 & (r32 >> 4);
u32 y = 1 & (r32 >> 22);
r32 <<= 1;
r32 = r32 ^ x ^ y;
R = from23_bits(r32);
}
void rxor(u32 in) { R = from23_bits(in ^ R_u32()); }
};
extern Rng gRng;
} // namespace Mips2C
+7 -7
View File
@@ -253,12 +253,12 @@ void dmac_runner(SystemThreadInterface& iface) {
iface.initialization_complete();
while (!iface.get_want_exit() && !VM::vm_want_exit()) {
for (int i = 0; i < 10; ++i) {
if (VM::dmac_ch[i]->chcr.str) {
// lg::info("DMA detected on channel {}, clearing", i);
VM::dmac_ch[i]->chcr.str = 0;
}
}
// for (int i = 0; i < 10; ++i) {
// if (VM::dmac_ch[i]->chcr.str) {
// // lg::info("DMA detected on channel {}, clearing", i);
// VM::dmac_ch[i]->chcr.str = 0;
// }
// }
// avoid running the DMAC on full blast (this does not sync to its clockrate)
std::this_thread::sleep_for(std::chrono::microseconds(50));
}
@@ -318,7 +318,7 @@ u32 exec_runtime(int argc, char** argv) {
// TODO also sync this up with how the game actually renders things (this is just a placeholder)
if (enable_display) {
Gfx::Init();
Gfx::Loop([&tm]() { return !tm.all_threads_exiting(); });
Gfx::Loop([]() { return !MasterExit; });
Gfx::Exit();
}
+1 -1
View File
@@ -63,7 +63,7 @@ static const Pad::Button libpad_PadPressureButtons[] = {
// returns buffer size (32) or 0 on error.
int scePadRead(int port, int /*slot*/, u8* rdata) {
auto cpad = (CPadInfo*)(rdata);
Gfx::poll_events();
// Gfx::poll_events();
cpad->valid = 0; // success
+64
View File
@@ -21,6 +21,8 @@ namespace Pad {
std::unordered_map<int, int> g_key_status;
std::unordered_map<int, int> g_buffered_key_status;
bool g_gamepad_buttons[(int)Button::Max] = {0};
// input mode for controller mapping
InputModeStatus input_mode = InputModeStatus::Disabled;
u64 input_mode_pad = 0;
@@ -101,6 +103,10 @@ int IsPressed(MappingInfo& mapping, Button button, int pad = 0) {
if (CheckPadIdx(pad) == -1) {
return 0;
}
if (g_gamepad_buttons[(int)button]) {
return 1;
}
auto key = mapping.pad_mapping[pad][(int)button];
if (key == -1)
return 0;
@@ -175,4 +181,62 @@ u64 input_mode_get_index() {
return input_mode_index;
}
/*
********************************
* Gamepad Support
********************************
*/
struct GamepadState {
int gamepad_idx = -1;
} g_gamepads;
void initialize() {
for (int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; i++) {
if (glfwJoystickPresent(i) && glfwJoystickIsGamepad(i)) {
g_gamepads.gamepad_idx = i;
lg::info("Using joystick {}: {}, {}", i, glfwGetJoystickName(i), glfwGetGamepadName(i));
break;
}
}
if (g_gamepads.gamepad_idx == -1) {
lg::info("No joysticks found.");
}
}
void update_gamepads() {
if (g_gamepads.gamepad_idx == -1) {
return;
}
if (!glfwJoystickPresent(g_gamepads.gamepad_idx)) {
g_gamepads.gamepad_idx = -1;
lg::info("Gamepad has been disconnected");
return;
}
GLFWgamepadstate state;
glfwGetGamepadState(g_gamepads.gamepad_idx, &state);
constexpr std::pair<Button, int> gamepad_map[] = {
{Button::Select, GLFW_GAMEPAD_BUTTON_BACK},
{Button::L3, GLFW_GAMEPAD_BUTTON_LEFT_THUMB},
{Button::R3, GLFW_GAMEPAD_BUTTON_RIGHT_THUMB},
{Button::Start, GLFW_GAMEPAD_BUTTON_START},
{Button::Up, GLFW_GAMEPAD_BUTTON_DPAD_UP},
{Button::Right, GLFW_GAMEPAD_BUTTON_DPAD_RIGHT},
{Button::Down, GLFW_GAMEPAD_BUTTON_DPAD_DOWN},
{Button::Left, GLFW_GAMEPAD_BUTTON_DPAD_LEFT},
{Button::L1, GLFW_GAMEPAD_BUTTON_LEFT_BUMPER},
{Button::R1, GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER},
{Button::Triangle, GLFW_GAMEPAD_BUTTON_TRIANGLE},
{Button::Circle, GLFW_GAMEPAD_BUTTON_CIRCLE},
{Button::X, GLFW_GAMEPAD_BUTTON_CROSS},
{Button::Square, GLFW_GAMEPAD_BUTTON_SQUARE}};
for (const auto& [button, idx] : gamepad_map) {
g_gamepad_buttons[(int)button] = state.buttons[idx];
}
}
}; // namespace Pad
+3
View File
@@ -82,4 +82,7 @@ u64 input_mode_get();
u64 input_mode_get_key();
u64 input_mode_get_index();
void initialize();
void update_gamepads();
} // namespace Pad
File diff suppressed because it is too large Load Diff
+11
View File
@@ -190,6 +190,15 @@
(set! (-> math-cam guard z) 1.0)
(set! (-> math-cam guard w) 1.0)
(set! (-> math-cam isometric data 14) (- 16777215.0 hvdf-z))
;; PC HACK!
;; for whatever reason, the font render ends up computing a depth #x1000000 instead of
;; #xffffffff, which overflows the 24-bit z buffer.
;; cheating this by 1 bit seems to fix it.
(#when PC_PORT
;; #x4b002032 -> #x4b002031
(-! (-> math-cam isometric data 14) 1.)
)
)
(set! (-> math-cam isometric data 15) fog-at-near-plane)
@@ -277,6 +286,8 @@
math-cam
)
(defmethod new math-camera ((allocation symbol) (type-to-make type))
(let ((gp-0 (object-new allocation type-to-make (the-as int (-> type-to-make size)))))
(set! (-> gp-0 d) 1024.0)
+2 -1
View File
@@ -266,7 +266,7 @@
;; merc1 61
;; generic1 62
(depth-cue 64)
(bucket-65 65)
(pre-sprite-textures 65) ;; always common
(sprite 66)
;; debug spheres? 67
(debug-draw0 67)
@@ -341,6 +341,7 @@
(stmod 5) ;; set mode register
(mskpath3 6) ;; set path 3 mask
(mark 7) ;; set mark register
(pc-port 8) ;; special tag for PC Port data.
(flushe 16) ;; wait for end of microprogram
(flush 17) ;; wait for end of microprogram and transfer (path1/path2)
(flusha 19) ;; wait for end of microprogram and transfer (path1/path2/path3)
+1
View File
@@ -40,3 +40,4 @@
(declare-type process-drawable process)
(define-extern process-drawable-art-error (state string process-drawable))
(define-extern foreground-engine-execute (function engine display-frame int int none))
(define-extern sphere-in-view-frustum? (function sphere symbol))
+133 -1
View File
@@ -5,6 +5,38 @@
;; name in dgo: drawable
;; dgos: GAME, ENGINE
(defun sphere-in-view-frustum? ((arg0 sphere))
(local-vars (r0-0 uint128) (v1-1 uint128) (v1-2 uint128) (v1-3 uint128))
(rlet ((acc :class vf)
(vf0 :class vf)
(vf1 :class vf)
(vf2 :class vf)
(vf3 :class vf)
(vf4 :class vf)
(vf5 :class vf)
(vf6 :class vf)
)
(init-vf0-vector)
(set! r0-0 (the uint128 0))
(let ((v1-0 *math-camera*))
(.lvf vf6 (&-> arg0 quad))
(.lvf vf1 (&-> v1-0 plane 0 quad))
(.lvf vf2 (&-> v1-0 plane 1 quad))
(.lvf vf3 (&-> v1-0 plane 2 quad))
(.lvf vf4 (&-> v1-0 plane 3 quad))
)
(.mul.x.vf acc vf1 vf6)
(.add.mul.y.vf acc vf2 vf6 acc)
(.add.mul.z.vf acc vf3 vf6 acc)
(.sub.mul.w.vf vf5 vf4 vf0 acc)
(.add.w.vf vf5 vf5 vf6)
(.mov v1-1 vf5)
(.pcgtw v1-2 r0-0 v1-1)
(.ppach v1-3 r0-0 v1-2)
(zero? (the-as int v1-3))
)
)
(defun real-main-draw-hook ()
(when *slow-frame-rate*
(dotimes (v1-2 #xc3500)
@@ -17,10 +49,111 @@
)
)
"Function to be executed to set up for engine dma"
(set! *vu1-enable-user* *vu1-enable-user-menu*)
(set! *texture-enable-user* *texture-enable-user-menu*)
;; todo debug memory
;; todo shrub matrix
;; todo generic init
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; texture uploads
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; tfrag
(when (logtest? *texture-enable-user* 1)
(dotimes (gp-1 (-> *level* length))
(let ((a1-2 (-> *level* level gp-1)))
(if (= (-> a1-2 status) 'active)
(add-tex-to-dma! *texture-pool* a1-2 0)
)
)
)
)
;; pris
(when (logtest? *texture-enable-user* 2)
(dotimes (gp-2 (-> *level* length))
(let ((a1-3 (-> *level* level gp-2)))
(if (= (-> a1-3 status) 'active)
(add-tex-to-dma! *texture-pool* a1-3 1)
)
)
)
)
;; shrub
(when (logtest? *texture-enable-user* 4)
(dotimes (gp-3 (-> *level* length))
(let ((a1-4 (-> *level* level gp-3)))
(if (= (-> a1-4 status) 'active)
(add-tex-to-dma! *texture-pool* a1-4 2)
)
)
)
)
;; alpha and common.
(when (logtest? *texture-enable-user* 8)
(let ((uploaded-common #f))
(dotimes (gp-4 (-> *level* length))
(let ((a1-5 (-> *level* level gp-4)))
(when (= (-> a1-5 status) 'active)
(add-tex-to-dma! *texture-pool* a1-5 3)
(when (not uploaded-common)
(upload-one-common! *texture-pool* (-> *level* level0))
(set! uploaded-common #t)
)
)
)
)
(when (not uploaded-common)
(upload-one-common! *texture-pool* (-> *level* level0))
#t
)
)
)
;; water.
(when (logtest? *texture-enable-user* 16)
(dotimes (gp-5 (-> *level* length))
(let ((a1-8 (-> *level* level gp-5)))
(if (= (-> a1-8 status) 'active)
(add-tex-to-dma! *texture-pool* a1-8 4)
)
)
)
)
;; texture common
;; sky
;; tod update
;; closest
;; ocean
;; merc
;; init bg
;; exec bg
;; finish bg
;; stats
;; fg engine
;; bones
;; gmerc
;; shadow
;; eyes
(when (logtest? #x10000 *vu1-enable-user*)
(swap-fake-shadow-buffers)
(sprite-draw *display*)
)
;; lots more in this function.
(when *debug-segment*
(debug-draw-actors *level* *display-actor-marks*)
;; collide-shape-debug
)
;; boundaries
;; method15 level
;; collide stats
)
(defun main-draw-hook ()
@@ -542,5 +675,4 @@
(none)
)
(define-extern sphere-in-view-frustum? (function vector symbol))
+19
View File
@@ -26,3 +26,22 @@
(define-extern anim-loop (function symbol))
;; TODO - for bouncer
(define-extern ja-min? (function int symbol))
(defun vector<-cspace! ((arg0 vector) (arg1 cspace))
(rlet ((Q :class vf)
(vf0 :class vf)
(vf2 :class vf)
)
(init-vf0-vector)
(.lvf vf2 (&-> (-> arg1 bone) transform vector 3 quad))
(.div.vf Q vf0 vf2 :fsf #b11 :ftf #b11)
(.wait.vf)
(.mul.vf vf2 vf2 Q :mask #b111)
(.nop.vf)
(.nop.vf)
(.mov.vf vf2 vf0 :mask #b1000)
(.svf (&-> arg0 quad) vf2)
arg0
)
)
+30 -76
View File
@@ -1613,82 +1613,36 @@
;; used for the flashing auto-save icon, I think.
(set! (-> *part-group-id-table* 656)
(new 'static 'sparticle-launch-group
:length 1
:duration #xbb8
:linger-duration #x5dc
:flags #x4
:name "group-part-save-icon"
:launcher
(new 'static 'inline-array sparticle-group-item 1
(new 'static 'sparticle-group-item :launcher #xa66)
)
:bounds (new 'static 'sphere :w 409600.0)
)
)
(new 'static 'sparticle-launch-group
:length 1
:duration #xbb8
:linger-duration #x5dc
:flags (sp-group-flag screen-space)
:name "group-part-save-icon"
:launcher
(new 'static 'inline-array sparticle-group-item 1 (sp-item 2662))
:bounds (new 'static 'sphere :w 409600.0)
)
)
;; todo floats/ints for these values.
(set! (-> *part-id-table* 2662)
(new 'static 'sparticle-launcher
:init-specs
(new 'static 'inline-array sp-field-init-spec 11
(new 'static 'sp-field-init-spec :field #x1 :initial-value #x1cf06b00)
(new 'static 'sp-field-init-spec
:field #x6
:flags #x1
:initial-value #x3f800000
:random-mult #x3f800000
)
(new 'static 'sp-field-init-spec
:field #xd
:flags #x1
:initial-value #x45c00000
:random-mult #x3f800000
)
(new 'static 'sp-field-init-spec
:field #x11
:flags #x3
:initial-value -4
:random-mult 1
)
(new 'static 'sp-field-init-spec
:field #x12
:flags #x1
:initial-value #x43000000
:random-mult #x3f800000
)
(new 'static 'sp-field-init-spec
:field #x13
:flags #x1
:initial-value #x43000000
:random-mult #x3f800000
)
(new 'static 'sp-field-init-spec
:field #x14
:flags #x1
:initial-value #x43000000
:random-mult #x3f800000
)
(new 'static 'sp-field-init-spec
:field #x15
:flags #x1
:initial-value #x43000000
:random-mult #x3f800000
)
(new 'static 'sp-field-init-spec
:field #x2e
:initial-value 5
:random-mult 1
)
(new 'static 'sp-field-init-spec
:field #x2f
:initial-value #x2204
:random-mult 1
)
(new 'static 'sp-field-init-spec :field #x43)
)
)
)
(new 'static 'sparticle-launcher
:init-specs
(new 'static 'inline-array sp-field-init-spec 11
(sp-tex spt-texture (new 'static 'texture-id :index #x6b :page #x1cf))
(sp-flt spt-num 1.0)
(sp-flt spt-scale-x (meters 1.5))
(sp-copy-from-other spt-scale-y -4)
(sp-flt spt-r 128.0)
(sp-flt spt-g 128.0)
(sp-flt spt-b 128.0)
(sp-flt spt-a 128.0)
(sp-int spt-timer 5)
(sp-cpuinfo-flags bit2 bit9 bit13)
(sp-end)
)
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; auto-save process
@@ -1731,7 +1685,7 @@
"Deactivate the auto-save process."
;; kill the particles
(if (nonzero? (-> obj part))
(deactivate (-> obj part))
(kill-and-free-particles (-> obj part))
)
;; and do a normal deactivate.
((method-of-type process deactivate) obj)
@@ -1870,7 +1824,7 @@
(set! (-> gp-2 vector4w w) (the-as int (-> *math-camera* hvdf-off w)))
)
)
(dummy-11 (-> self part) *zero-vector*)
(spawn (-> self part) *zero-vector*)
)
)
(none)
+10 -3
View File
@@ -684,15 +684,22 @@
;; console buffers
(set! *stdcon* (clear *stdcon0*))
;; here it is:
;; <--------------------------- SWAP DISPLAY!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(swap-display disp)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; teleport stuff
;; perf stats
(process-particles)
;; swap display
;; particles
;; vif0 collid
;; swap sound
;; str play
;; level update
(level-update *level*) ;; also updates settings.
;; run mc
;; auto save check
;; suspend
File diff suppressed because it is too large Load Diff
+1
View File
@@ -224,6 +224,7 @@
((= z 0.0)
(let ((v1-4 obj))
(set! (-> v1-4 origin z) (-> *math-camera* isometric vector 3 z))
;;(format #t "fc: ~F~%" (-> v1-4 origin z))
)
)
(else
+2
View File
@@ -43,3 +43,5 @@
;; definition for symbol *fake-shadow-buffer*, type fake-shadow-buffer
(define *fake-shadow-buffer* *fake-shadow-buffer-1*)
(define-extern swap-fake-shadow-buffers (function none))
+20 -116
View File
@@ -234,125 +234,29 @@
(new 'static 'sparticle-launcher
:init-specs
(new 'static 'inline-array sp-field-init-spec 20
(new 'static 'sp-field-init-spec
:field #x1
:initial-valuef (the-as float #x200000)
)
(new 'static 'sp-field-init-spec
:field #x6
:flags #x1
:initial-valuef 8.0
:random-multf 1.0
)
(new 'static 'sp-field-init-spec
:field #xd
:flags #x1
:initial-valuef 1228.8
:random-multf 1.0
)
(new 'static 'sp-field-init-spec
:field #x10
:flags #x1
:initial-valuef -32768.0
:random-rangef 65536.0
:random-multf 1.0
)
(new 'static 'sp-field-init-spec
:field #x11
:flags #x3
:initial-valuef (the-as float #xfffffffc)
:random-multf (the-as float #x1)
)
(new 'static 'sp-field-init-spec
:field #x12
:flags #x1
:initial-valuef 90.0
:random-multf 1.0
)
(new 'static 'sp-field-init-spec
:field #x13
:flags #x1
:initial-valuef 90.0
:random-multf 1.0
)
(new 'static 'sp-field-init-spec
:field #x14
:flags #x1
:initial-valuef 90.0
:random-multf 1.0
)
(new 'static 'sp-field-init-spec
:field #x15
:flags #x1
:initial-valuef 20.0
:random-rangef 20.0
:random-multf 1.0
)
(new 'static 'sp-field-init-spec
:field #x1a
:flags #x1
:initial-valuef 8.192
:random-multf 1.0
)
(new 'static 'sp-field-init-spec
:field #x1c
:flags #x1
:initial-valuef 17.066668
:random-multf 1.0
)
(new 'static 'sp-field-init-spec
:field #x20
:flags #x3
:initial-valuef (the-as float #xfffffffc)
:random-multf (the-as float #x1)
)
(new 'static 'sp-field-init-spec
:field #x24
:flags #x1
:initial-valuef -0.3
:random-multf 1.0
)
(new 'static 'sp-field-init-spec
:field #x2e
:initial-valuef (the-as float #x138c)
:random-multf (the-as float #x1)
)
(new 'static 'sp-field-init-spec
:field #x2f
:initial-valuef (the-as float #xc)
:random-multf (the-as float #x1)
)
(new 'static 'sp-field-init-spec
:field #x36
:flags #x1
:initial-valuef -3640.889
:random-rangef 2730.6667
:random-multf 1.0
)
(new 'static 'sp-field-init-spec
:field #x3a
:flags #x1
:initial-valuef 16384.0
:random-multf 1.0
)
(new 'static 'sp-field-init-spec
:field #x3b
:flags #x1
:initial-valuef -32768.0
:random-rangef 65536.0
:random-multf 1.0
)
(new 'static 'sp-field-init-spec
:field #x3e
:flags #x1
:initial-valuef 819.2
:random-multf 1.0
)
(new 'static 'sp-field-init-spec :field #x43)
(sp-tex spt-texture (new 'static 'texture-id :page #x2))
(sp-flt spt-num 8.0)
(sp-flt spt-scale-x (meters 0.3))
(sp-rnd-flt spt-rot-z (degrees -180.0) (degrees 360.0) 1.0)
(sp-copy-from-other spt-scale-y -4)
(sp-flt spt-r 90.0)
(sp-flt spt-g 90.0)
(sp-flt spt-b 90.0)
(sp-rnd-flt spt-a 20.0 20.0 1.0)
(sp-flt spt-vel-y (meters 0.002))
(sp-flt spt-scalevel-x (meters 0.004166667))
(sp-copy-from-other spt-scalevel-y -4)
(sp-flt spt-fade-a -0.3)
(sp-int spt-timer 5004)
(sp-cpuinfo-flags bit2 bit3)
(sp-rnd-flt spt-launchrot-x (degrees -20.0) (degrees 15.0) 1.0)
(sp-flt spt-conerot-x (degrees 90.0))
(sp-rnd-flt spt-conerot-y (degrees -180.0) (degrees 360.0) 1.0)
(sp-flt spt-conerot-radius (meters 0.2))
(sp-end)
)
)
)
+3 -2
View File
@@ -107,12 +107,13 @@
)
;; TODO
(define sprite-distort-vu1-block (the-as vu-function 0))
;; we need to at least put something here so dma-buffer-add-vu-function doesn't crash.
(define sprite-distort-vu1-block (new 'static 'vu-function))
(defun sprite-init-distorter ((arg0 dma-buffer) (arg1 uint))
"Set up DMA for setting up the sprite-distorter renderer"
;;(format #t "distorter: ~d~%" (-> *sprite-aux-list* entry))
(let* ((v1-0 arg0)
(a2-0 (the-as object (-> v1-0 base)))
)
+55 -6
View File
@@ -98,18 +98,15 @@
)
)
#|
;; until we figure out the entry type, we can't have this function.
(defmethod inspect sprite-aux-list ((obj sprite-aux-list))
(format #t "[~X] sprite-aux-list:~%" obj)
(format #t "~Tnum-entries: ~D~%" (-> obj num-entries))
(format #t "~Tentry: ~D~%" (-> obj entry))
(dotimes (s5-0 (-> obj entry))
(format #t "~T~D : ~X~%" s5-0 (l.wu (+ (+ (* s5-0 4) (the-as int obj)) 8)))
(format #t "~T~D : ~X~%" s5-0 (-> obj data s5-0))
)
(the-as sprite-aux-list #f)
)
|#
(define *sprite-aux-list* (new 'global 'sprite-aux-list 256))
@@ -119,7 +116,17 @@
(none)
)
;; TODO function add-to-sprite-aux-list
(defun add-to-sprite-aux-list ((arg0 sparticle-system) (arg1 sparticle-cpuinfo) (arg2 sprite-vec-data-3d))
(let ((v1-0 *sprite-aux-list*))
(when (< (-> v1-0 entry) (-> v1-0 num-entries))
(set! (-> v1-0 data (-> v1-0 entry)) (-> arg1 sprite))
(+! (-> v1-0 entry) 1)
)
)
(set! (-> arg2 r-g-b-a w) 0.0)
0
(none)
)
;; The sprite-frame-data is data transferred to VU1 and remains there for all chunks of sprites.
(deftype sprite-frame-data (structure)
@@ -372,7 +379,8 @@
(none)
)
;;(define sprite-vu1-block (the-as vu-function L58))
;; we need to at least put something here so dma-buffer-add-vu-function doesn't crash.
(define sprite-vu1-block (new 'static 'vu-function))
;;;;;;;;;;;;;;;;;;
;; sprite-arrays
@@ -569,6 +577,7 @@
(none)
)
#|
(defun sprite-add-frame-data ((dma-buff dma-buffer) (tbp-offset uint))
"Upload the frame data."
(let ((s5-0 41)) ;; qwc of frame data.
@@ -587,6 +596,29 @@
)
(none)
)
|#
(defun sprite-add-frame-data ((dma-buff dma-buffer) (tbp-offset uint))
(let ((s5-0 41))
(let* ((v1-0 dma-buff)
(pkt (the-as dma-packet (-> v1-0 base)))
)
(set! (-> pkt dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc s5-0))
(set! (-> pkt vif0) (new 'static 'vif-tag :imm #x404 :cmd (vif-cmd stcycl)))
(set!
(-> pkt vif1)
(new 'static 'vif-tag :imm #x3d4 :cmd (vif-cmd unpack-v4-32) :num s5-0)
)
(set! (-> v1-0 base) (&+ (the-as pointer pkt) 16))
)
(sprite-setup-frame-data
(the-as sprite-frame-data (-> dma-buff base))
(the-as int tbp-offset)
)
(&+! (-> dma-buff base) (* s5-0 16))
)
(none)
)
(defun sprite-add-2d-chunk ((sprites sprite-array-2d) (start-sprite-idx int) (num-sprites int) (dma-buff dma-buffer) (mscal-addr int))
"Upload sprite data from elements in the array."
@@ -612,6 +644,18 @@
:imm (new 'static 'vif-unpack-imm :flg 1 :addr 1))
)
)
; (dotimes (i num-sprites)
; (let ((spidx (+ i start-sprite-idx)))
; (when (or (= spidx (-> sprites num-sprites 0)) (= spidx (+ 1 (-> sprites num-sprites 0))))
; (let ((data (the sprite-vec-data-2d (&+ (-> sprites vec-data) (* 48 (+ i start-sprite-idx))))))
; (format #t "spidx: ~d~%")
; (inspect data)
; (inspect (-> data r-g-b-a))
; )
; )
; )
; )
;; third packet is adgif data (5 qw/sprite)
(let ((qwc-pkt3 (* 5 num-sprites)))
@@ -643,6 +687,8 @@
;; and use the other mpg.
(set! mscal-addr 109)
)
;;(format #t "group ~d 2D ~d~%" group-idx (-> sprites num-valid group-idx))
;; loop over chunks
(let ((remaining-sprites (-> sprites num-valid group-idx)))
@@ -815,6 +861,7 @@
(none)
)
(defun sprite-draw ((disp display))
"Main sprite draw function."
;; start of our DMA for all the sprite data
@@ -896,6 +943,8 @@
(none)
)
(defun sprite-allocate-user-hvdf ()
"Allocate an HVDF entry. Returns the index. Or 0 if it fails"
(dotimes (v1-0 76)
+43 -22
View File
@@ -627,6 +627,7 @@
;; allocate the common and near segments
(allocate-segment! obj (-> obj segment-common) COMMON_SEGMENT_WORDS) ;; ~0.5 MB
(allocate-segment! obj (-> obj segment-near) NEAR_SEGMENT_WORDS) ;; ~1.6 MB.
(format #t "near segment is at ~D to ~d~%" (-> obj segment-near dest) (+ (-> obj segment-near dest) (-> obj segment-near size)))
;; Allocate the random crap
(set! *sky-base-vram-word* (allocate-vram-words! obj SPECIAL_VRAM_WORDS))
@@ -799,6 +800,8 @@
(first-chunk-idx-to-upload int)
(tex-id uint)
)
(let ((total-upload-size 0))
(with-dma-buffer-add-bucket ((dma-buf (-> (current-frame) global-buf)) ;; the global DMA buffer
bucket-idx)
@@ -843,7 +846,7 @@
(dotimes (upload-chunk-idx (the-as int chunk-count))
;; the destination of the chunk.
(let ((current-dest-chunk
(+ tex-dest-base-chunk (the-as uint upload-chunk-idx))
(+ tex-dest-base-chunk (the-as uint upload-chunk-idx))
)
)
;; now we see if we can get away with not uploading the chunk.
@@ -860,42 +863,57 @@
((= (-> pool ids current-dest-chunk) tex-id)
;; and the run ends, we found a chunk that's already loaded.
;; so we upload the run:
(upload-vram-data dma-buf
(the int (shl (+ tex-dest-base-chunk (the-as uint first-chunk-idx-to-upload)) 6))
(&+ tex-data (shl first-chunk-idx-to-upload 14))
(shl chunks-to-upload-count 5)
)
(#when (not PC_PORT)
(upload-vram-data dma-buf
(the int (shl (+ tex-dest-base-chunk (the-as uint first-chunk-idx-to-upload)) 6))
(&+ tex-data (shl first-chunk-idx-to-upload 14))
(shl chunks-to-upload-count 5)
)
)
(+! total-upload-size chunks-to-upload-count)
;; reset
(set! chunks-to-upload-count 0)
)
(else
;; the run continues!
(set! (-> pool ids current-dest-chunk) tex-id)
(set! chunks-to-upload-count (+ chunks-to-upload-count 1))
)
;; the run continues!
(set! (-> pool ids current-dest-chunk) tex-id)
(set! chunks-to-upload-count (+ chunks-to-upload-count 1))
)
)
)
)
)
;; if we finished with a run of "needs upload", set up the upload.
(when (nonzero? chunks-to-upload-count)
(upload-vram-data
dma-buf
(the int (shl (+ tex-dest-base-chunk (the-as uint first-chunk-idx-to-upload)) 6))
(&+ tex-data (shl first-chunk-idx-to-upload 14))
(shl chunks-to-upload-count 5)
)
(#when (not PC_PORT)
(upload-vram-data
dma-buf
(the int (shl (+ tex-dest-base-chunk (the-as uint first-chunk-idx-to-upload)) 6))
(&+ tex-data (shl first-chunk-idx-to-upload 14))
(shl chunks-to-upload-count 5)
)
)
(+! total-upload-size chunks-to-upload-count)
)
;; do a texflush
;; send gif (a+d)
(dma-buffer-add-gs-set dma-buf
(texflush 1) ;; texflush
)
(#when (not PC_PORT)
(dma-buffer-add-gs-set dma-buf
(texflush 1) ;; texflush
)
)
;; in PC PORT we just skip all that stuff and just send a pointer to the texture page and the mode.
(#when PC_PORT
(dma-buffer-add-cnt-vif2 dma-buf 1 (new 'static 'vif-tag :cmd (vif-cmd pc-port)) (the-as vif-tag 3))
(dma-buffer-add-uint64 dma-buf page)
(dma-buffer-add-uint64 dma-buf mode)
)
)
(shl total-upload-size 14)
)
@@ -1580,7 +1598,7 @@
(and (nonzero? a2-0) (logtest? (-> obj common-page-mask) (ash 1 v1-0))) ;; in the mask.
)
;; upload it!
(upload-vram-pages obj (-> obj segment-common) a2-0 -2 (bucket-id bucket-65))
(upload-vram-pages obj (-> obj segment-common) a2-0 -2 (bucket-id pre-sprite-textures))
(return #f)
)
)
@@ -2080,6 +2098,7 @@
)
)
)
;; (format #t "relocate dests: ~A seg ~D from ~D to ~D~%" obj seg-id (-> obj segment seg-id dest) new-dest)
(set! (-> obj segment seg-id dest) (the-as uint new-dest))
)
)
@@ -2227,7 +2246,9 @@
"Look up a texture by ID, loading it from debug network if its not loaded.
Default allocates if it has to load, so it will permanently use VRAM"
(let ((v1-0 (texture-page-login arg0 texture-page-default-allocate loading-level)))
(if (and v1-0 (< (-> arg0 index) (the-as uint (-> v1-0 page length))))
(when (and v1-0 (< (-> arg0 index) (the-as uint (-> v1-0 page length))))
;;(format #t "texture:~%")
;;(inspect (-> v1-0 page data (-> arg0 index)))
(-> v1-0 page data (-> arg0 index))
)
)
+1
View File
@@ -5,3 +5,4 @@
;; name in dgo: time-of-day
;; dgos: GAME, ENGINE
(define *time-of-day-proc* (the (pointer time-of-day-proc) #f))
+2 -2
View File
@@ -43,8 +43,8 @@
(pat-length int32 :offset-assert 48)
(texture-remap-table (pointer uint64) :offset-assert 52)
(texture-remap-table-len int32 :offset-assert 56)
(unk-data-1 pointer :offset-assert 60)
(unk-data-1-len int32 :offset-assert 64)
(texture-ids (pointer texture-id) :offset-assert 60)
(texture-page-count int32 :offset-assert 64)
(unk-zero-0 basic :offset-assert 68)
(name symbol :offset-assert 72)
(nickname symbol :offset-assert 76)
+1 -1
View File
@@ -93,7 +93,7 @@
(set! (-> mem-use length) (max 58 (-> mem-use length)))
(set! (-> mem-use data 57 name) "bsp-misc")
(+! (-> mem-use data 57 count) 1)
(let ((v1-56 (* (-> obj unk-data-1-len) 4)))
(let ((v1-56 (* (-> obj texture-page-count) 4)))
(+! (-> mem-use data 57 used) v1-56)
(+! (-> mem-use data 57 total) (logand -16 (+ v1-56 15)))
)
+1 -1
View File
@@ -214,7 +214,7 @@
(debug-print-entities (_type_ symbol type) none 13)
(debug-draw-actors (_type_ symbol) none 14)
(dummy-15 (_type_) object 15)
(dummy-16 (_type_) int 16)
(level-update (_type_) int 16)
(level-get-target-inside (_type_) level 17)
(alloc-levels! (_type_ symbol) int 18)
(load-commands-set! (_type_ pair) pair 19)
+8 -2
View File
@@ -488,12 +488,12 @@
(cond
((-> obj bsp)
(set! (-> *level* log-in-level-bsp) (-> obj bsp))
;; TODO
;;(login-level-textures *texture-pool* obj (-> obj bsp unk-data-1-len) (the-as (pointer texture-id) (-> obj bsp unk-data-1)))
(login-level-textures *texture-pool* obj (-> obj bsp texture-page-count) (-> obj bsp texture-ids))
(let ((bsp (-> obj bsp)))
(when (nonzero? (-> bsp adgifs))
(let ((adgifs (-> bsp adgifs)))
(dotimes (i (-> adgifs length))
;; TODO
;;(adgif-shader-login-no-remap (-> adgifs data i)) TODO texture.gc
)
)
@@ -1549,6 +1549,12 @@
;; method 16 level-group (debug text stuff)
(defmethod level-update level-group ((obj level-group))
;; todo lots of stuff
(update-per-frame-settings! *setting-control*)
0
)
(defun-debug show-level ((level-name symbol))
(set! (-> *setting-control* default border-mode) #t)
(load-state-want-levels (-> (level-get-target-inside *level*) name) level-name)
+28 -3
View File
@@ -17,6 +17,28 @@
(define *sp-60-hz* #t)
(defenum sp-cpuinfo-flag
:bitfield #t
:type uint32
(bit0 0)
(bit2 2) ;; cleared after an aux has its func set to add-to-sprite-aux-lst
(bit3 3)
(ready-to-launch 6) ;; maybe just just death?
(bit7 7)
(aux-list 8) ;; prevents relaunch, adds to aux
(bit9 9)
(level0 10)
(level1 11)
(bit12 12) ;; required to relaunch
(bit13 13)
(bit14 14)
(use-global-acc 16)
(launch-along-z 17)
(left-multiply-quat 18)
(right-multiply-quat 19)
(set-conerot 20)
)
(deftype sparticle-cpuinfo (structure)
((sprite sprite-vec-data-2d :offset-assert 0)
(adgif adgif-shader :offset-assert 4)
@@ -33,7 +55,7 @@
(scalevely float :offset 44)
(friction float :offset-assert 96)
(timer int32 :offset-assert 100)
(flags uint32 :offset-assert 104)
(flags sp-cpuinfo-flag :offset-assert 104)
(user-int32 int32 :offset-assert 108)
(user-uint32 uint32 :offset 108)
(user-float float :score 100 :offset 108)
@@ -53,8 +75,10 @@
:method-count-assert 9
:size-assert #x8c
:flag-assert #x90000008c
;; field key is a basic loaded with a signed load
)
(deftype sparticle-launchinfo (structure)
((launchrot vector :inline :offset-assert 0)
(conerot vector :inline :offset-assert 16)
@@ -69,8 +93,8 @@
(deftype sparticle-system (basic)
((blocks int32 2 :offset-assert 4)
(length uint32 2 :offset-assert 12)
(num-alloc uint32 2 :offset-assert 20)
(length int32 2 :offset-assert 12)
(num-alloc int32 2 :offset-assert 20)
(is-3d basic :offset-assert 28)
(flags uint32 :offset-assert 32)
(alloc-table (pointer uint64) :offset-assert 36)
@@ -98,3 +122,4 @@
(defun-extern kill-all-particles-with-key sparticle-launch-control none)
(define-extern sp-get-particle (function sparticle-system int sparticle-launch-state sparticle-cpuinfo))
+298 -11
View File
@@ -5,9 +5,139 @@
;; name in dgo: sparticle-launcher-h
;; dgos: GAME, ENGINE
;; The "sparticle" system is the particle system.
;; Features
;; - Support for 2D particles (autosave icon, progress menu graphics)
;; - Support for 3D particles (many of the effects)
;; - Uses the "sprite" renderer to draw particles
;; The "sparticle-launcher" code is the framework for describing particle effects
;; The "sparticle" code is the system that runs particles
;; Note that neither of these link particles to the process system. See part-tracker for that.
;; The higheset level class here is sparticle-launch-control.
;; Each instance of a particle effect must have one of these.
;; For example, there would be one of these per eco-vent.
;; These store some state (a sparticle-launch-state) and a reference to a sparticle-launch-group
;; Multiple launch-controls can refer to the same launch-group.
;; A sparticle-launch-group is a description of a particle effect.
;; It can contain multiple types of particles.
;; The `*part-group-id-table*` array stores a reference to every launch-group, indexed by group id.
;; Each launch-group is just a list of sparticle-launchers, stored as an index
;; A launcher is a single particle effect.
;; The `*part-id-table*` has references to all particle effects.
;; It contains a list of "field-init-specs". When the particle effect starts, the system
;; iterates through this list and sets parameters about particles.
;; There are five types of fields:
;; misc fields
;; sprite fields
;; cpu fields
;; launch fields
;; weird fields
;; The built-in parameters can be used for many simple effects, but sometimes it is not enough.
;; You can provide a callback function to update the particle's state if needed.
;; These are the user-settable state variables for each particle effect
(defenum sp-field-id
:type uint16
(misc-fields-start 0)
(spt-texture 1)
(spt-anim 2)
(spt-anim-speed 3)
(spt-birth-func 4)
(spt-joint/refpoint 5)
(spt-num 6)
(spt-sound 7)
(misc-fields-end 8)
(sprite-fields-start 9)
(spt-x 10)
(spt-y 11)
(spt-z 12)
(spt-scale-x 13)
(spt-rot-x 14)
(spt-rot-y 15)
(spt-rot-z 16)
(spt-scale-y 17)
(spt-r 18)
(spt-g 19)
(spt-b 20)
(spt-a 21)
(sprite-fields-end 22)
(cpu-fields-start 23)
(spt-omega 24)
(spt-vel-x 25)
(spt-vel-y 26)
(spt-vel-z 27)
(spt-scalevel-x 28)
(spt-rotvel-x 29)
(spt-rotvel-y 30)
(spt-rotvel-z 31)
(spt-scalevel-y 32)
(spt-fade-r 33)
(spt-fade-g 34)
(spt-fade-b 35)
(spt-fade-a 36)
(spt-accel-x 37)
(spt-accel-y 38)
(spt-accel-z 39)
(spt-dummy 40)
(spt-quat-x 41)
(spt-quat-y 42)
(spt-quat-z 43)
(spt-quad-w 44)
(spt-friction 45)
(spt-timer 46)
(spt-flags 47)
(spt-userdata 48)
(spt-func 49)
(spt-next-time 50)
(spt-next-launcher 51)
(cpu-fields-end 52)
(launch-fields-start 53)
(spt-launchrot-x 54)
(spt-launchrot-y 55)
(spt-launchrot-z 56)
(spt-launchrot-w 57)
(spt-conerot-x 58)
(spt-conerot-y 59)
(spt-conerot-z 60)
(spt-conerot-w 61)
(spt-conerot-radius 62)
(spt-rotate-y 63)
(launch-fields-end 64)
(spt-scale 65)
(spt-scalevel 66)
(spt-end 67)
)
(defenum sp-flag
:type uint16
(plain-v1 0) ;; just a plain signed integer. No random crap.
(float-with-rand 1)
(int-with-rand 2)
(copy-from-other-field 3)
(plain-v2 4)
(from-pointer 5)
(part-by-id 6)
)
;; This describes the initial value and some more info for a single field
;; Note that there are overlays here and some values only make sense in some
;; cases.
(deftype sp-field-init-spec (structure)
((field uint16 :offset-assert 0)
(flags uint16 :offset-assert 2)
((field sp-field-id :offset-assert 0)
(flags sp-flag :offset-assert 2)
(initial-valuef float :offset-assert 4)
(random-rangef float :offset-assert 8)
(random-multf float :offset-assert 12)
@@ -26,6 +156,112 @@
:flag-assert #x900000010
)
;; sparticle field macros
(defmacro sp-tex (field-name tex-id)
`(new 'static 'sp-field-init-spec :field (sp-field-id ,field-name) :tex ,tex-id)
)
(defmacro sp-rnd-flt (field-name val range mult)
`(new 'static 'sp-field-init-spec
:field (sp-field-id ,field-name)
:initial-valuef ,val
:random-rangef ,range
:random-multf ,mult
:flags (sp-flag float-with-rand)
)
)
(defmacro sp-flt (field-name val)
`(new 'static 'sp-field-init-spec
:field (sp-field-id ,field-name)
:initial-valuef ,val
:random-rangef 0.0
:random-multf 1.0
:flags (sp-flag float-with-rand)
)
)
(defmacro sp-int (field-name val)
`(new 'static 'sp-field-init-spec
:field (sp-field-id ,field-name)
:initial-value ,val
:random-range 0
:random-mult 1
)
)
(defmacro sp-int-plain-rnd (field-name val range mult)
"For when we use plain integer, but set the randoms."
`(new 'static 'sp-field-init-spec
:field (sp-field-id ,field-name)
:initial-value ,val
:random-range ,range
:random-mult ,mult
)
)
(defmacro sp-rnd-int (field-name val range mult)
`(new 'static 'sp-field-init-spec
:field (sp-field-id ,field-name)
:initial-value ,val
:random-range ,range
:random-multf ,mult
:flags (sp-flag int-with-rand)
)
)
(defmacro sp-rnd-int-flt (field-name val range mult)
`(new 'static 'sp-field-init-spec
:field (sp-field-id ,field-name)
:initial-valuef ,val
:random-range ,range
:random-multf ,mult
:flags (sp-flag int-with-rand)
)
)
(defmacro sp-cpuinfo-flags (&rest flags)
`(new 'static 'sp-field-init-spec
:field (sp-field-id spt-flags)
:initial-value (sp-cpuinfo-flag ,@flags)
:random-mult 1
)
)
(defmacro sp-launcher-by-id (field-name val)
`(new 'static 'sp-field-init-spec
:field (sp-field-id ,field-name)
:initial-value ,val
:flags (sp-flag part-by-id)
)
)
(defmacro sp-func (field-name val)
`(new 'static 'sp-field-init-spec
:field (sp-field-id ,field-name)
:sym ,val
:flags (sp-flag from-pointer)
)
)
(defmacro sp-end ()
`(new 'static 'sp-field-init-spec
:field (sp-field-id spt-end)
)
)
(defmacro sp-copy-from-other (field-name offset)
`(new 'static 'sp-field-init-spec
:field (sp-field-id ,field-name)
:initial-value ,offset
:random-mult 1
:flags (sp-flag copy-from-other-field)
)
)
(deftype sparticle-launcher (basic)
((birthaccum float :offset-assert 4)
(soundaccum float :offset-assert 8)
@@ -36,11 +272,20 @@
:flag-assert #x900000010
)
(defenum sp-group-item-flag
:bitfield #t
:type uint16
(is-3d 0)
(bit1 1)
(start-dead 2)
(launch-asap 3)
)
(deftype sparticle-group-item (structure)
((launcher uint32 :offset-assert 0)
(fade-after meters :offset-assert 4)
(falloff-to meters :offset-assert 8)
(flags uint16 :offset-assert 12)
(flags sp-group-item-flag :offset-assert 12)
(period uint16 :offset-assert 14)
(length uint16 :offset-assert 16)
(offset uint16 :offset-assert 18)
@@ -52,13 +297,46 @@
:flag-assert #x90000001c
)
(defmacro sp-item (launcher
&key (fade-after 0.0)
&key (falloff-to 0.0)
&key (flags ())
&key (period 0)
&key (length 0)
&key (offset 0)
&key (hour-mask 0)
&key (binding 0)
)
`(new 'static 'sparticle-group-item
:launcher ,launcher
:fade-after ,fade-after
:falloff-to ,falloff-to
:flags (sp-group-item-flag ,@flags)
:period ,period
:length ,length
:offset ,offset
:hour-mask ,hour-mask
:binding ,binding
)
)
(defenum sp-launch-state-flags
:bitfield #t
:type uint16
(launcher-active 0) ;; active
(particles-active 1) ;; wants to launch
(bit2 2)
)
(declare-type sparticle-cpuinfo structure)
(deftype sparticle-launch-state (structure)
((group-item sparticle-group-item :offset-assert 0)
(flags uint16 :offset-assert 4)
(flags sp-launch-state-flags :offset-assert 4)
(randomize uint16 :offset-assert 6)
(origin vector :offset-assert 8)
(sprite3d sprite-vec-data-3d :offset-assert 12)
(sprite basic :offset-assert 16)
(sprite sparticle-cpuinfo :offset-assert 16)
(offset uint32 :offset-assert 20)
(accum float :offset-assert 24)
(spawn-time uint32 :offset-assert 28)
@@ -73,12 +351,20 @@
:flag-assert #x900000020
)
(defenum sp-group-flag
:bitfield #t
:type uint16
(use-local-clock 0)
(always-draw 1)
(screen-space 2)
)
(deftype sparticle-launch-group (basic)
((length int16 :offset-assert 4)
(duration uint16 :offset-assert 6)
(linger-duration uint16 :offset-assert 8)
(flags uint16 :offset-assert 10)
(name basic :offset-assert 12)
(flags sp-group-flag :offset-assert 10)
(name string :offset-assert 12)
(launcher (inline-array sparticle-group-item) :offset-assert 16)
(bounds sphere :inline :offset-assert 32)
)
@@ -106,10 +392,11 @@
:flag-assert #xe00000040
(:methods
(initialize (_type_ sparticle-launch-group process) none 9)
(dummy-10 () none 10)
(dummy-11 (_type_ vector) none 11)
(deactivate (_type_) none 12)
(dummy-13 () none 13)
(is-visible? (_type_ vector) symbol 10)
(spawn (_type_ vector) object 11)
(kill-and-free-particles (_type_) none 12)
(kill-particles (_type_) none 13)
)
)
(set! (-> sparticle-launch-control heap-base) (the-as uint 32))

Some files were not shown because too many files have changed in this diff Show More