mirror of
https://github.com/open-goal/jak-project.git
synced 2024-11-30 18:00:34 +00:00
Merge remote-tracking branch 'water111/master' into decomp/nav-enemy
This commit is contained in:
commit
dda8756a35
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1,4 +1,6 @@
|
||||
third-party/**/* linguist-vendored
|
||||
test/decompiler/reference/** linguist-vendored
|
||||
*.gc linguist-language=lisp
|
||||
*.gd linguist-language=lisp
|
||||
*.gs linguist-language=Scheme
|
||||
goal_src/engine/gfx/ocean/ocean-tables.gc linguist-vendored
|
||||
|
4
.github/workflows/build-doc-app.yaml
vendored
4
.github/workflows/build-doc-app.yaml
vendored
@ -22,9 +22,7 @@ jobs:
|
||||
|
||||
# just do a sed for now
|
||||
- name: Update Line Count
|
||||
run: |
|
||||
chmod +x ./.github/workflows/scripts/line-count.sh
|
||||
./.github/workflows/scripts/line-count.sh
|
||||
run: python ./scripts/update-file-progress.py
|
||||
|
||||
- name: Update Site
|
||||
run: |
|
||||
|
5
.github/workflows/scripts/line-count.sh
vendored
5
.github/workflows/scripts/line-count.sh
vendored
@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
LINE_COUNT=$(find ./goal_src -name '*.gc' | xargs wc -l | tail -1 | awk -F'[^0-9]+' '{ print $2 }')
|
||||
|
||||
sed -i "s/.*value.*/ \"value\": ${LINE_COUNT},/g" ./docs/gh-pages-proj/src/config/progress.json
|
@ -49,6 +49,13 @@
|
||||
"name" : "Run - Runtime (with kernel)",
|
||||
"args" : [ "-fakeiso", "-debug", "-v", "-nodisplay" ]
|
||||
},
|
||||
{
|
||||
"type" : "default",
|
||||
"project" : "CMakeLists.txt",
|
||||
"projectTarget" : "gk.exe (bin\\gk.exe)",
|
||||
"name" : "Run - Runtime (with kernel + display)",
|
||||
"args" : [ "-fakeiso", "-debug", "-v" ]
|
||||
},
|
||||
{
|
||||
"type" : "default",
|
||||
"project" : "CMakeLists.txt",
|
||||
|
@ -94,6 +94,11 @@ bool check_stopped(const ThreadID& tid, SignalInfo* out) {
|
||||
int status;
|
||||
int rv = waitpid(tid.id, &status, WNOHANG);
|
||||
if (rv < 0) {
|
||||
if (errno == ECHILD) {
|
||||
// the thing died.
|
||||
out->kind = SignalInfo::DISAPPEARED;
|
||||
return true;
|
||||
}
|
||||
printf("[Debugger] Failed to waitpid: %s.\n", strerror(errno));
|
||||
// assert(false); // todo, temp because I think we should never hit this.
|
||||
return false;
|
||||
|
@ -83,7 +83,9 @@ struct SignalInfo {
|
||||
SEGFAULT, // access bad memory
|
||||
BREAK, // hit a breakpoint or execute int3
|
||||
MATH_EXCEPTION, // divide by zero
|
||||
UNKNOWN // some other signal that is unsupported
|
||||
UNKNOWN, // some other signal that is unsupported
|
||||
DISAPPEARED, // process disappeared (maybe killed by the user)
|
||||
|
||||
} kind = UNKNOWN;
|
||||
};
|
||||
|
||||
|
@ -114,6 +114,13 @@ class Vector {
|
||||
return result;
|
||||
}
|
||||
|
||||
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(),
|
||||
x() * other.y() - y() * other.x()};
|
||||
return result;
|
||||
}
|
||||
|
||||
Vector<T, Size> normalized(const T& norm = T(1)) const { return (*this) * (norm / length()); }
|
||||
|
||||
void normalize(const T& norm = T(1)) { *this = normalized(norm); }
|
||||
@ -127,6 +134,18 @@ class Vector {
|
||||
return result + "]";
|
||||
}
|
||||
|
||||
T* data() { return m_data; }
|
||||
const T* data() const { return m_data; }
|
||||
|
||||
template <typename U>
|
||||
Vector<U, Size> cast() {
|
||||
Vector<U, Size> result;
|
||||
for (int i = 0; i < Size; i++) {
|
||||
result[i] = (U)m_data[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
T m_data[Size];
|
||||
};
|
||||
|
218
common/texture/texture_conversion.h
Normal file
218
common/texture/texture_conversion.h
Normal file
@ -0,0 +1,218 @@
|
||||
#pragma once
|
||||
|
||||
/*!
|
||||
* Convert from a pixel location in a texture (x, y, texture buffer width) to VRAM address (byte).
|
||||
* Uses the PSMCT32 format.
|
||||
* This format is used either to store 8-bit RGBA (texture palettes) or to copy memory.
|
||||
* See Ch. 8, Details of GS Local Memory for these tables.
|
||||
*/
|
||||
inline u32 psmct32_addr(u32 x, u32 y, u32 width) {
|
||||
// XXX_col refers to which XXX you're in (screen)
|
||||
// XXX refers to which XXX you're in (memory)
|
||||
// XXX_x refers to the pixel within the XXX
|
||||
|
||||
// first, determine the page
|
||||
u32 pages_per_row = width / 64;
|
||||
u32 page_col = x / 64;
|
||||
u32 page_row = y / 32;
|
||||
u32 page_x = x % 64;
|
||||
u32 page_y = y % 32;
|
||||
u32 page = page_col + page_row * pages_per_row;
|
||||
|
||||
// next the block
|
||||
u32 block_col = page_x / 8;
|
||||
u32 block_row = page_y / 8;
|
||||
u32 block_x = page_x % 8;
|
||||
u32 block_y = page_y % 8;
|
||||
const u32 psm32_table[4][8] = {{0, 1, 4, 5, 16, 17, 20, 21},
|
||||
{2, 3, 6, 7, 18, 19, 22, 23},
|
||||
{8, 9, 12, 13, 24, 25, 28, 29},
|
||||
{10, 11, 14, 15, 26, 27, 30, 31}};
|
||||
|
||||
u32 block = psm32_table[block_row][block_col];
|
||||
|
||||
// next the column (there's only one "column" per column)
|
||||
u32 col_row = block_y / 2;
|
||||
u32 col_y = block_y % 2;
|
||||
u32 col_x = block_x;
|
||||
|
||||
// next the pixel
|
||||
const u32 psm32_pix_table[2][8] = {{0, 1, 4, 5, 8, 9, 12, 13}, {2, 3, 6, 7, 10, 11, 14, 15}};
|
||||
u32 pixel = psm32_pix_table[col_y][col_x];
|
||||
|
||||
// now the sum
|
||||
return ((page * 64 * 32) + (block * 8 * 8) + (col_row * 8 * 2) + pixel) * 4;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert from a pixel location in a texture (x, y, texture buffer width) to VRAM address (byte).
|
||||
* Uses the PSMT8 format.
|
||||
* This format is used either to store 8-bit palette indices, used in most textures.
|
||||
* See Ch. 8, Details of GS Local Memory for these tables.
|
||||
*/
|
||||
inline u32 psmt8_addr(u32 x, u32 y, u32 width) {
|
||||
// page is 128, 64
|
||||
// block is 16, 16
|
||||
// column is 16, 4
|
||||
|
||||
// first determine the page
|
||||
u32 pages_per_row = width / 128;
|
||||
u32 page_col = x / 128;
|
||||
u32 page_row = y / 64;
|
||||
u32 page_x = x % 128;
|
||||
u32 page_y = y % 64;
|
||||
u32 page = page_col + page_row * pages_per_row;
|
||||
|
||||
// next block
|
||||
u32 block_col = page_x / 16;
|
||||
u32 block_row = page_y / 16;
|
||||
u32 block_x = page_x % 16;
|
||||
u32 block_y = page_y % 16;
|
||||
const u32 psm32_table[4][8] = {{0, 1, 4, 5, 16, 17, 20, 21},
|
||||
{2, 3, 6, 7, 18, 19, 22, 23},
|
||||
{8, 9, 12, 13, 24, 25, 28, 29},
|
||||
{10, 11, 14, 15, 26, 27, 30, 31}};
|
||||
u32 block = psm32_table[block_row][block_col]; // it's the same table!!!
|
||||
|
||||
// both columns and pixels within columns.
|
||||
const uint8_t pix_table[16][16] = {
|
||||
{0, 4, 16, 20, 32, 36, 48, 52, 2, 6, 18, 22, 34, 38, 50, 54},
|
||||
{8, 12, 24, 28, 40, 44, 56, 60, 10, 14, 26, 30, 42, 46, 58, 62},
|
||||
{33, 37, 49, 53, 1, 5, 17, 21, 35, 39, 51, 55, 3, 7, 19, 23},
|
||||
{41, 45, 57, 61, 9, 13, 25, 29, 43, 47, 59, 63, 11, 15, 27, 31},
|
||||
{96, 100, 112, 116, 64, 68, 80, 84, 98, 102, 114, 118, 66, 70, 82, 86},
|
||||
{104, 108, 120, 124, 72, 76, 88, 92, 106, 110, 122, 126, 74, 78, 90, 94},
|
||||
{65, 69, 81, 85, 97, 101, 113, 117, 67, 71, 83, 87, 99, 103, 115, 119},
|
||||
{73, 77, 89, 93, 105, 109, 121, 125, 75, 79, 91, 95, 107, 111, 123, 127},
|
||||
{128, 132, 144, 148, 160, 164, 176, 180, 130, 134, 146, 150, 162, 166, 178, 182},
|
||||
{136, 140, 152, 156, 168, 172, 184, 188, 138, 142, 154, 158, 170, 174, 186, 190},
|
||||
{161, 165, 177, 181, 129, 133, 145, 149, 163, 167, 179, 183, 131, 135, 147, 151},
|
||||
{169, 173, 185, 189, 137, 141, 153, 157, 171, 175, 187, 191, 139, 143, 155, 159},
|
||||
{224, 228, 240, 244, 192, 196, 208, 212, 226, 230, 242, 246, 194, 198, 210, 214},
|
||||
{232, 236, 248, 252, 200, 204, 216, 220, 234, 238, 250, 254, 202, 206, 218, 222},
|
||||
{193, 197, 209, 213, 225, 229, 241, 245, 195, 199, 211, 215, 227, 231, 243, 247},
|
||||
{201, 205, 217, 221, 233, 237, 249, 253, 203, 207, 219, 223, 235, 239, 251, 255},
|
||||
};
|
||||
|
||||
u32 pixel = pix_table[block_y][block_x];
|
||||
return (page * 128 * 64) + (block * 16 * 16) + pixel;
|
||||
}
|
||||
|
||||
inline u32 psmct16_addr(u32 x, u32 y, u32 width) {
|
||||
// page is 64x64
|
||||
// block is 16x8
|
||||
// column is 16x2
|
||||
|
||||
// page
|
||||
u32 pages_per_row = width / 64;
|
||||
u32 page_col = x / 64;
|
||||
u32 page_row = y / 64;
|
||||
u32 page_x = x % 64;
|
||||
u32 page_y = y % 64;
|
||||
u32 page = page_col + page_row * pages_per_row;
|
||||
|
||||
// block
|
||||
u32 block_col = page_x / 16;
|
||||
u32 block_row = page_y / 8;
|
||||
u32 block_x = page_x % 16;
|
||||
u32 block_y = page_y % 8;
|
||||
const u32 psm16_table[8][4] = {{0, 2, 8, 10}, {1, 3, 9, 11}, {4, 6, 12, 14},
|
||||
{5, 7, 13, 15}, {16, 18, 24, 26}, {17, 19, 25, 27},
|
||||
{20, 22, 28, 30}, {21, 23, 29, 31}};
|
||||
u32 block = psm16_table[block_row][block_col];
|
||||
|
||||
const uint8_t pix_tabel[8][16] = {
|
||||
{0, 2, 8, 10, 16, 18, 24, 26, 1, 3, 9, 11, 17, 19, 25, 27},
|
||||
{4, 6, 12, 14, 20, 22, 28, 30, 5, 7, 13, 15, 21, 23, 29, 31},
|
||||
{32, 34, 40, 42, 48, 50, 56, 58, 33, 35, 41, 43, 49, 51, 57, 59},
|
||||
{36, 38, 44, 46, 52, 54, 60, 62, 37, 39, 45, 47, 53, 55, 61, 63},
|
||||
{64, 66, 72, 74, 80, 82, 88, 90, 65, 67, 73, 75, 81, 83, 89, 91},
|
||||
{68, 70, 76, 78, 84, 86, 92, 94, 69, 71, 77, 79, 85, 87, 93, 95},
|
||||
{96, 98, 104, 106, 112, 114, 120, 122, 97, 99, 105, 107, 113, 115, 121, 123},
|
||||
{100, 102, 108, 110, 116, 118, 124, 126, 101, 103, 109, 111, 117, 119, 125, 127},
|
||||
};
|
||||
u32 pixel = pix_tabel[block_y][block_x];
|
||||
return 2 * ((page * 64 * 64) + (block * 16 * 8) + pixel);
|
||||
}
|
||||
|
||||
inline u32 psmt4_addr_half_byte(u32 x, u32 y, u32 width) {
|
||||
// page is 128, 128
|
||||
// block is 32, 16
|
||||
// column is 32, 4
|
||||
|
||||
// first determine the page
|
||||
u32 pages_per_row = width / 128;
|
||||
u32 page_col = x / 128;
|
||||
u32 page_row = y / 128;
|
||||
u32 page_x = x % 128;
|
||||
u32 page_y = y % 128;
|
||||
u32 page = page_col + page_row * pages_per_row;
|
||||
|
||||
// next block
|
||||
u32 block_col = page_x / 32;
|
||||
u32 block_row = page_y / 16;
|
||||
u32 block_x = page_x % 32;
|
||||
u32 block_y = page_y % 16;
|
||||
const u32 psm4_table[8][4] = {{0, 2, 8, 10}, {1, 3, 9, 11}, {4, 6, 12, 14},
|
||||
{5, 7, 13, 15}, {16, 18, 24, 26}, {17, 19, 25, 27},
|
||||
{20, 22, 28, 30}, {21, 23, 29, 31}};
|
||||
assert(block_row < 8);
|
||||
assert(block_col < 4);
|
||||
u32 block = psm4_table[block_row][block_col]; // it's the same table!!!
|
||||
|
||||
// both columns and pixels within columns.
|
||||
const uint16_t pix_table[16][32] = {
|
||||
{0, 8, 32, 40, 64, 72, 96, 104, 2, 10, 34, 42, 66, 74, 98, 106,
|
||||
4, 12, 36, 44, 68, 76, 100, 108, 6, 14, 38, 46, 70, 78, 102, 110},
|
||||
{16, 24, 48, 56, 80, 88, 112, 120, 18, 26, 50, 58, 82, 90, 114, 122,
|
||||
20, 28, 52, 60, 84, 92, 116, 124, 22, 30, 54, 62, 86, 94, 118, 126},
|
||||
{65, 73, 97, 105, 1, 9, 33, 41, 67, 75, 99, 107, 3, 11, 35, 43,
|
||||
69, 77, 101, 109, 5, 13, 37, 45, 71, 79, 103, 111, 7, 15, 39, 47},
|
||||
{81, 89, 113, 121, 17, 25, 49, 57, 83, 91, 115, 123, 19, 27, 51, 59,
|
||||
85, 93, 117, 125, 21, 29, 53, 61, 87, 95, 119, 127, 23, 31, 55, 63},
|
||||
{192, 200, 224, 232, 128, 136, 160, 168, 194, 202, 226, 234, 130, 138, 162, 170,
|
||||
196, 204, 228, 236, 132, 140, 164, 172, 198, 206, 230, 238, 134, 142, 166, 174},
|
||||
{208, 216, 240, 248, 144, 152, 176, 184, 210, 218, 242, 250, 146, 154, 178, 186,
|
||||
212, 220, 244, 252, 148, 156, 180, 188, 214, 222, 246, 254, 150, 158, 182, 190},
|
||||
{129, 137, 161, 169, 193, 201, 225, 233, 131, 139, 163, 171, 195, 203, 227, 235,
|
||||
133, 141, 165, 173, 197, 205, 229, 237, 135, 143, 167, 175, 199, 207, 231, 239},
|
||||
{145, 153, 177, 185, 209, 217, 241, 249, 147, 155, 179, 187, 211, 219, 243, 251,
|
||||
149, 157, 181, 189, 213, 221, 245, 253, 151, 159, 183, 191, 215, 223, 247, 255},
|
||||
{256, 264, 288, 296, 320, 328, 352, 360, 258, 266, 290, 298, 322, 330, 354, 362,
|
||||
260, 268, 292, 300, 324, 332, 356, 364, 262, 270, 294, 302, 326, 334, 358, 366},
|
||||
{272, 280, 304, 312, 336, 344, 368, 376, 274, 282, 306, 314, 338, 346, 370, 378,
|
||||
276, 284, 308, 316, 340, 348, 372, 380, 278, 286, 310, 318, 342, 350, 374, 382},
|
||||
{321, 329, 353, 361, 257, 265, 289, 297, 323, 331, 355, 363, 259, 267, 291, 299,
|
||||
325, 333, 357, 365, 261, 269, 293, 301, 327, 335, 359, 367, 263, 271, 295, 303},
|
||||
{337, 345, 369, 377, 273, 281, 305, 313, 339, 347, 371, 379, 275, 283, 307, 315,
|
||||
341, 349, 373, 381, 277, 285, 309, 317, 343, 351, 375, 383, 279, 287, 311, 319},
|
||||
{448, 456, 480, 488, 384, 392, 416, 424, 450, 458, 482, 490, 386, 394, 418, 426,
|
||||
452, 460, 484, 492, 388, 396, 420, 428, 454, 462, 486, 494, 390, 398, 422, 430},
|
||||
{464, 472, 496, 504, 400, 408, 432, 440, 466, 474, 498, 506, 402, 410, 434, 442,
|
||||
468, 476, 500, 508, 404, 412, 436, 444, 470, 478, 502, 510, 406, 414, 438, 446},
|
||||
{385, 393, 417, 425, 449, 457, 481, 489, 387, 395, 419, 427, 451, 459, 483, 491,
|
||||
389, 397, 421, 429, 453, 461, 485, 493, 391, 399, 423, 431, 455, 463, 487, 495},
|
||||
{401, 409, 433, 441, 465, 473, 497, 505, 403, 411, 435, 443, 467, 475, 499, 507,
|
||||
405, 413, 437, 445, 469, 477, 501, 509, 407, 415, 439, 447, 471, 479, 503, 511},
|
||||
};
|
||||
|
||||
assert(block_y < 16);
|
||||
assert(block_x < 32);
|
||||
u32 pixel = pix_table[block_y][block_x];
|
||||
return (page * 128 * 128) + (block * 32 * 16) + pixel;
|
||||
}
|
||||
|
||||
inline u32 rgba16_to_rgba32(u32 in) {
|
||||
float ratio = 255.0 / 31.0;
|
||||
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;
|
||||
}
|
||||
|
||||
// texture format enums
|
||||
enum class PSM { PSMCT16 = 0x02, PSMT8 = 0x13, PSMT4 = 0x14 };
|
||||
// clut format enums
|
||||
enum class CPSM { PSMCT32 = 0x0, PSMCT16 = 0x02 };
|
@ -1047,7 +1047,23 @@ std::string EnumType::diff_impl(const Type& other_) const {
|
||||
}
|
||||
|
||||
if (m_entries != other.m_entries) {
|
||||
result += "Entries are different.\n";
|
||||
result += "Entries are different:\n";
|
||||
for (auto& ours : m_entries) {
|
||||
auto theirs = other.m_entries.find(ours.first);
|
||||
if (theirs == other.m_entries.end()) {
|
||||
result += fmt::format(" {} is in one, but not the other.\n", ours.first);
|
||||
} else if (ours.second != theirs->second) {
|
||||
result += fmt::format(" {} is defined differently: {} vs {}\n", ours.first, ours.second,
|
||||
theirs->second);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& theirs : other.m_entries) {
|
||||
auto ours = m_entries.find(theirs.first);
|
||||
if (ours == m_entries.end()) {
|
||||
result += fmt::format(" {} is in one, but not the other.\n", theirs.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "third-party/dragonbox.h"
|
||||
#include "print_float.h"
|
||||
#include "common/util/assert.h"
|
||||
|
||||
/*!
|
||||
* Convert a float to a string. The string is _always_ in this format:
|
||||
@ -127,4 +128,4 @@ int float_to_cstr(float value, char* buffer) {
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
@ -539,6 +539,13 @@ goos::Object AsmOp::to_form(const std::vector<DecompilerLabel>& labels, const En
|
||||
}
|
||||
}
|
||||
|
||||
// note: to correctly represent a MOVN/MOVZ in our IR, we need to both read and write the
|
||||
// destination register, so we append a read to the end here.
|
||||
if (m_instr.kind == InstructionKind::MOVZ || m_instr.kind == InstructionKind::MOVN) {
|
||||
RegisterAccess ra(AccessMode::READ, m_dst->reg(), m_dst->idx());
|
||||
forms.push_back(ra.to_form(env));
|
||||
}
|
||||
|
||||
return pretty_print::build_list(forms);
|
||||
}
|
||||
|
||||
@ -604,6 +611,11 @@ void AsmOp::update_register_info() {
|
||||
}
|
||||
}
|
||||
|
||||
if (m_instr.kind == InstructionKind::MOVN || m_instr.kind == InstructionKind::MOVZ) {
|
||||
// in the case that MOVN/MOVZ don't do the move, they effectively read the original value.
|
||||
m_read_regs.push_back(m_dst->reg());
|
||||
}
|
||||
|
||||
if (m_instr.kind >= FIRST_COP2_MACRO && m_instr.kind <= LAST_COP2_MACRO) {
|
||||
switch (m_instr.kind) {
|
||||
case InstructionKind::VMSUBQ:
|
||||
@ -711,6 +723,13 @@ void AsmOp::collect_vars(RegAccessSet& vars) const {
|
||||
vars.insert(*x);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_instr.kind == InstructionKind::MOVN || m_instr.kind == InstructionKind::MOVZ) {
|
||||
// the conditional moves read their write register, but don't have it listed as a write
|
||||
// in the actual ASM. We handle this difference for the variable naming system here.
|
||||
RegisterAccess ra(AccessMode::READ, m_dst->reg(), m_dst->idx());
|
||||
vars.insert(ra);
|
||||
}
|
||||
}
|
||||
/////////////////////////////
|
||||
// Condition
|
||||
|
@ -654,9 +654,9 @@ void SimpleExpressionElement::update_from_stack_float_2(const Env& env,
|
||||
} else {
|
||||
auto type0 = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(0).var().reg());
|
||||
auto type1 = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(1).var().reg());
|
||||
throw std::runtime_error(
|
||||
fmt::format("Floating point math attempted on invalid types: {} and {} in op {}.",
|
||||
type0.print(), type1.print(), to_string(env)));
|
||||
throw std::runtime_error(fmt::format(
|
||||
"[OP: {}] - Floating point math attempted on invalid types: {} and {} in op {}.", m_my_idx,
|
||||
type0.print(), type1.print(), to_string(env)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,6 +244,7 @@ std::unique_ptr<AtomicOp> make_asm_op(const Instruction& i0, int idx) {
|
||||
case InstructionKind::SLT:
|
||||
case InstructionKind::MOVN:
|
||||
case InstructionKind::SLTI: // a few cases used in inline asm
|
||||
case InstructionKind::MOVZ: // in font.gc
|
||||
|
||||
// VU/COP2
|
||||
case InstructionKind::VMOVE:
|
||||
@ -1143,6 +1144,14 @@ std::unique_ptr<AtomicOp> convert_bgez_2(const Instruction& i0, const Instructio
|
||||
i1, false, dest, idx);
|
||||
}
|
||||
|
||||
std::unique_ptr<AtomicOp> convert_bgtz_2(const Instruction& i0, const Instruction& i1, int idx) {
|
||||
// bgtz is never emitted outside of inline asm.
|
||||
auto dest = i0.get_src(1).get_label();
|
||||
return make_asm_branch(IR2_Condition(IR2_Condition::Kind::GREATER_THAN_ZERO_SIGNED,
|
||||
make_src_atom(i0.get_src(0).get_reg(), idx)),
|
||||
i1, false, dest, idx);
|
||||
}
|
||||
|
||||
std::unique_ptr<AtomicOp> convert_2(const Instruction& i0, const Instruction& i1, int idx) {
|
||||
switch (i0.kind) {
|
||||
case InstructionKind::DIV:
|
||||
@ -1169,6 +1178,8 @@ std::unique_ptr<AtomicOp> convert_2(const Instruction& i0, const Instruction& i1
|
||||
return convert_bltz_2(i0, i1, idx);
|
||||
case InstructionKind::BGEZ:
|
||||
return convert_bgez_2(i0, i1, idx);
|
||||
case InstructionKind::BGTZ:
|
||||
return convert_bgtz_2(i0, i1, idx);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -733,6 +733,7 @@ void clean_up_cond_no_else_final(Function& func, CondNoElseElement* cne) {
|
||||
assert(branch);
|
||||
if (branch_info_i.written_and_unused.find(reg->reg()) ==
|
||||
branch_info_i.written_and_unused.end()) {
|
||||
lg::error("Branch delay register used improperly: {}", reg->to_string(func.ir2.env));
|
||||
throw std::runtime_error("Bad delay slot in clean_up_cond_no_else_final");
|
||||
}
|
||||
// assert(branch_info_i.written_and_unused.find(reg->reg()) !=
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -582,7 +582,51 @@
|
||||
"cam-combiner": [
|
||||
[1, "(function none :behavior camera-combiner)"],
|
||||
[2, "(function basic int basic event-message-block object :behavior camera-combiner)"]
|
||||
],
|
||||
],
|
||||
|
||||
"title-obs": [
|
||||
[0, "(function none)"]
|
||||
],
|
||||
|
||||
"misty-teetertotter": [
|
||||
[1, "(function none :behavior teetertotter)"],
|
||||
[2, "(function none :behavior teetertotter)"],
|
||||
[3, "(function process int symbol event-message-block symbol :behavior teetertotter)"],
|
||||
[4, "(function none :behavior teetertotter)"],
|
||||
[5, "(function process int symbol event-message-block object :behavior teetertotter)"]
|
||||
],
|
||||
|
||||
"misty-warehouse": [
|
||||
[3, "(function symbol none :behavior silostep)"],
|
||||
[4, "(function none :behavior silostep)"],
|
||||
[6, "(function none :behavior camera-tracker)"],
|
||||
[7, "(function none :behavior silostep)"],
|
||||
[8, "(function process int symbol event-message-block none :behavior silostep)"]
|
||||
],
|
||||
|
||||
"rigid-body": [
|
||||
[5, "(function none :behavior rigid-body-platform)"],
|
||||
[6, "(function none :behavior rigid-body-platform)"],
|
||||
[7, "(function none :behavior rigid-body-platform)"],
|
||||
[8, "(function none :behavior rigid-body-platform)"]
|
||||
],
|
||||
|
||||
"water-anim": [
|
||||
[4, "(function none :behavior water-anim)"],
|
||||
[5, "(function none :behavior water-anim)"],
|
||||
[6, "(function process int symbol event-message-block process-mask :behavior water-anim)"]
|
||||
],
|
||||
|
||||
"plat-eco": [
|
||||
[9, "(function none :behavior plat-eco)"]
|
||||
],
|
||||
|
||||
"sunken-elevator": [
|
||||
[3, "(function object :behavior sunken-elevator)"],
|
||||
[4, "(function int :behavior sunken-elevator)"],
|
||||
[5, "(function symbol :behavior sunken-elevator)"],
|
||||
[6, "(function int :behavior sunken-elevator)"]
|
||||
],
|
||||
|
||||
"nav-enemy": [
|
||||
[8, "(function none :behavior nav-enemy)"],
|
||||
|
@ -189,10 +189,6 @@
|
||||
"shadow-scissor-edges",
|
||||
"shadow-calc-dual-verts",
|
||||
|
||||
// font
|
||||
"get-string-length",
|
||||
"draw-string",
|
||||
|
||||
// decomp
|
||||
//"(method 16 level)", // BUG: cfg fails
|
||||
"unpack-comp-huf",
|
||||
@ -521,7 +517,8 @@
|
||||
"level-remap-texture": [2, 3, 4, 5, 6],
|
||||
"start-perf-stat-collection": [26],
|
||||
"end-perf-stat-collection": [0],
|
||||
|
||||
"sprite-draw-distorters": [4, 5]
|
||||
"sprite-draw-distorters": [4, 5],
|
||||
"draw-string":[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, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189],
|
||||
"get-string-length":[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]
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +94,12 @@
|
||||
["L17", "font-work", true]
|
||||
],
|
||||
|
||||
"font": [
|
||||
["L95", "(inline-array vector)", true, 250],
|
||||
["L94", "(inline-array vector)", true, 289],
|
||||
["L96", "float", true]
|
||||
],
|
||||
|
||||
"display": [
|
||||
["L87", "float", true],
|
||||
["L76", "(pointer uint32)", true, 2],
|
||||
@ -110,6 +116,14 @@
|
||||
],
|
||||
|
||||
"text-h": [["L2", "_auto_", true]],
|
||||
"text": [
|
||||
["L79", "uint64", true],
|
||||
["L78", "float", true],
|
||||
["L75", "float", true],
|
||||
["L66", "vector", true],
|
||||
["L67", "matrix", true],
|
||||
["L68", "vector4w", true]
|
||||
],
|
||||
|
||||
"capture": [
|
||||
["L27", "uint64", true],
|
||||
@ -583,9 +597,12 @@
|
||||
["L317", "uint64", true],
|
||||
["L316", "uint64", true],
|
||||
["L320", "uint64", true],
|
||||
["L310", "uint64", true],
|
||||
["L314", "uint64", true],
|
||||
["L313", "uint64", true],
|
||||
["L315", "uint64", true]
|
||||
["L315", "uint64", true],
|
||||
["L318", "uint64", true],
|
||||
["L319", "uint64", true]
|
||||
],
|
||||
|
||||
"geometry": [
|
||||
@ -1412,6 +1429,162 @@
|
||||
["L497", "float", true],
|
||||
["L508", "float", true]
|
||||
],
|
||||
"shadow": [
|
||||
["L23", "sparticle-launcher", true],
|
||||
["L26", "float", true],
|
||||
["L27", "float", true],
|
||||
["L28", "float", true]
|
||||
],
|
||||
|
||||
"tippy": [
|
||||
["L7", "float", true]
|
||||
],
|
||||
|
||||
"ticky": [
|
||||
["L7", "float", true],
|
||||
["L8", "float", true],
|
||||
["L10", "uint64", true]
|
||||
],
|
||||
|
||||
"wobbler": [
|
||||
["L6", "float", true]
|
||||
],
|
||||
|
||||
"twister": [
|
||||
["L20", "float", true]
|
||||
],
|
||||
|
||||
"misty-teetertotter": [
|
||||
["L2", "_lambda_", true],
|
||||
["L4", "_lambda_", true],
|
||||
["L10", "_lambda_", true],
|
||||
["L15", "_lambda_", true],
|
||||
["L17", "_lambda_", true],
|
||||
["L24", "state", true],
|
||||
["L25", "state", true],
|
||||
["L26", "attack-info", true],
|
||||
["L27", "attack-info", true],
|
||||
["L28", "state", true],
|
||||
["L30", "skeleton-group", true]
|
||||
],
|
||||
|
||||
"misty-warehouse": [
|
||||
["L7", "_lambda_", true],
|
||||
["L11", "_lambda_", true],
|
||||
["L15", "_lambda_", true],
|
||||
["L23", "_lambda_", true],
|
||||
["L25", "_lambda_", true],
|
||||
["L29", "skeleton-group", true],
|
||||
["L34", "state", true],
|
||||
["L35", "skeleton-group", true],
|
||||
["L31", "state", true],
|
||||
["L32", "state", true],
|
||||
["L37", "float", true],
|
||||
["L46", "float", true],
|
||||
["L48", "uint64", true],
|
||||
["L49", "uint64", true],
|
||||
["L50", "uint64", true],
|
||||
["L51", "uint64", true]
|
||||
],
|
||||
|
||||
"rigid-body": [
|
||||
["L8", "_lambda_", true],
|
||||
["L10", "_lambda_", true],
|
||||
["L13", "_lambda_", true],
|
||||
["L15", "_lambda_", true],
|
||||
["L89", "rigid-body-platform-constants", true],
|
||||
["L90", "state", true],
|
||||
["L91", "state", true],
|
||||
["L106", "float", true]
|
||||
],
|
||||
|
||||
"ocean": [
|
||||
["L90", "float", true]
|
||||
],
|
||||
|
||||
"water-anim": [
|
||||
["L13", "_lambda_", true],
|
||||
["L15", "_lambda_", true],
|
||||
["L21", "_lambda_", true],
|
||||
["L26", "state", true],
|
||||
["L27", "_auto_", true],
|
||||
["L99", "skeleton-group", true],
|
||||
["L101", "skeleton-group", true],
|
||||
["L102", "skeleton-group", true],
|
||||
["L103", "skeleton-group", true],
|
||||
["L104", "skeleton-group", true],
|
||||
["L106", "skeleton-group", true],
|
||||
["L108", "skeleton-group", true],
|
||||
["L110", "skeleton-group", true],
|
||||
["L111", "skeleton-group", true],
|
||||
["L113", "skeleton-group", true],
|
||||
["L115", "skeleton-group", true],
|
||||
["L117", "skeleton-group", true],
|
||||
["L119", "skeleton-group", true],
|
||||
["L121", "skeleton-group", true],
|
||||
["L123", "skeleton-group", true],
|
||||
["L124", "skeleton-group", true],
|
||||
["L125", "skeleton-group", true],
|
||||
["L126", "skeleton-group", true],
|
||||
["L127", "skeleton-group", true],
|
||||
["L128", "skeleton-group", true],
|
||||
["L129", "skeleton-group", true],
|
||||
["L130", "skeleton-group", true],
|
||||
["L131", "skeleton-group", true],
|
||||
["L132", "skeleton-group", true],
|
||||
["L133", "skeleton-group", true],
|
||||
["L134", "skeleton-group", true],
|
||||
["L136", "skeleton-group", true],
|
||||
["L138", "skeleton-group", true],
|
||||
["L140", "skeleton-group", true],
|
||||
["L141", "skeleton-group", true],
|
||||
["L142", "skeleton-group", true],
|
||||
["L143", "skeleton-group", true],
|
||||
["L144", "skeleton-group", true],
|
||||
["L146", "skeleton-group", true],
|
||||
["L147", "skeleton-group", true],
|
||||
["L148", "skeleton-group", true],
|
||||
["L149", "skeleton-group", true],
|
||||
["L150", "skeleton-group", true],
|
||||
["L151", "skeleton-group", true],
|
||||
["L152", "skeleton-group", true],
|
||||
["L153", "skeleton-group", true],
|
||||
["L154", "skeleton-group", true],
|
||||
["L155", "skeleton-group", true],
|
||||
["L156", "skeleton-group", true],
|
||||
["L157", "skeleton-group", true],
|
||||
["L159", "skeleton-group", true],
|
||||
["L160", "skeleton-group", true],
|
||||
["L161", "skeleton-group", true],
|
||||
["L163", "float", true],
|
||||
["L165", "float", true]
|
||||
],
|
||||
|
||||
"mud": [
|
||||
["L8", "ripple-wave-set", true],
|
||||
["L9", "ripple-wave-set", true],
|
||||
["L10", "float", true]
|
||||
],
|
||||
|
||||
"sunken-elevator": [
|
||||
["L6", "_lambda_", true],
|
||||
["L9", "_lambda_", true],
|
||||
["L14", "_lambda_", true],
|
||||
["L16", "_lambda_", true],
|
||||
["L28", "state", true],
|
||||
["L29", "state", true],
|
||||
["L30", "state", true],
|
||||
["L32", "skeleton-group", true],
|
||||
["L35", "float", true],
|
||||
["L37", "float", true]
|
||||
],
|
||||
"title-obs": [["L499", "float", true]],
|
||||
|
||||
"drawable": [
|
||||
["L190", "uint64", true],
|
||||
["L191", "uint64", true],
|
||||
["L186", "uint64", true]
|
||||
],
|
||||
|
||||
"nav-enemy": [
|
||||
["L16", "_lambda_", true],
|
||||
|
@ -821,6 +821,123 @@
|
||||
[16, "sphere"]
|
||||
],
|
||||
|
||||
"(method 10 tippy)": [
|
||||
[16, "vector"]
|
||||
],
|
||||
|
||||
"compute-and-draw-shadow": [
|
||||
[16, "vector"],
|
||||
[32, "vector"],
|
||||
[48, "sparticle-cpuinfo"] // kinda a guess
|
||||
],
|
||||
|
||||
"find-ground-and-draw-shadow": [
|
||||
[16, "vector"],
|
||||
[32, "vector"],
|
||||
[48, "bone"] // what a guess!
|
||||
],
|
||||
|
||||
"(method 20 collide-cache)": [
|
||||
[16, "vector"]
|
||||
],
|
||||
|
||||
"(method 12 wobbler)": [
|
||||
[16, "vector"]
|
||||
],
|
||||
|
||||
"(method 12 twister)": [
|
||||
[16, "matrix"]
|
||||
],
|
||||
|
||||
"target-on-end-of-teetertotter?": [
|
||||
[16, "vector"],
|
||||
[32, "vector"]
|
||||
],
|
||||
|
||||
"(anon-function 3 misty-teetertotter)": [
|
||||
[16, "event-message-block"]
|
||||
],
|
||||
|
||||
"(method 17 rigid-body)": [
|
||||
[16, "vector"]
|
||||
],
|
||||
|
||||
"matrix-3x3-triple-transpose-product": [
|
||||
[16, "matrix"],
|
||||
[80, "matrix"]
|
||||
],
|
||||
|
||||
"(method 10 rigid-body)": [
|
||||
[16, "quaternion"]
|
||||
],
|
||||
|
||||
"(method 13 rigid-body)": [
|
||||
[16, "vector"],
|
||||
[32, "vector"]
|
||||
],
|
||||
|
||||
"(method 16 rigid-body)": [
|
||||
[16, "vector"],
|
||||
[32, "vector"]
|
||||
],
|
||||
|
||||
"(method 14 rigid-body)": [
|
||||
[16, "vector"],
|
||||
[32, "vector"]
|
||||
],
|
||||
|
||||
"(method 18 rigid-body)": [
|
||||
[16, "vector"]
|
||||
],
|
||||
|
||||
"(method 24 rigid-body-platform)": [
|
||||
[16, "vector"]
|
||||
],
|
||||
|
||||
"(method 26 rigid-body-platform)": [
|
||||
[16, "vector"]
|
||||
],
|
||||
|
||||
"(method 27 rigid-body-platform)": [
|
||||
[16, "vector"]
|
||||
],
|
||||
|
||||
"(method 22 water-anim)": [
|
||||
[16, "vector"]
|
||||
],
|
||||
|
||||
"(anon-function 9 plat-eco)": [
|
||||
[16, "event-message-block"]
|
||||
],
|
||||
|
||||
"default-collision-reaction": [
|
||||
[16, "vector"],
|
||||
[32, "vector"],
|
||||
[48, "vector"],
|
||||
[96, "vector"]
|
||||
],
|
||||
|
||||
"(anon-function 3 sunken-elevator)": [
|
||||
[16, "vector"],
|
||||
[32, "vector"],
|
||||
[48, "event-message-block"]
|
||||
],
|
||||
|
||||
"(method 29 sunken-elevator)": [
|
||||
[16, "vector"]
|
||||
],
|
||||
"(anon-function 0 title-obs)": [
|
||||
[16, "font-context"]
|
||||
],
|
||||
|
||||
"print-game-text": [
|
||||
[16, "font-context"]
|
||||
],
|
||||
|
||||
"draw-string-xy": [
|
||||
[16, "font-context"]
|
||||
],
|
||||
|
||||
"(method 50 nav-enemy)": [
|
||||
[16, "vector"]
|
||||
],
|
||||
|
@ -424,11 +424,47 @@
|
||||
|
||||
"adgif-shader<-texture-simple!": [[5, "v1", "uint"]],
|
||||
|
||||
"display-frame-start": [[4, "v1", "(pointer uint32)"]],
|
||||
"display-frame-start": [
|
||||
[4, "v1", "vif-bank"],
|
||||
[9, "a0", "vif-bank"],
|
||||
[[158, 161], "a0", "dma-packet"]
|
||||
],
|
||||
|
||||
"display-loop": [
|
||||
[152, "v1", "(pointer int32)"],
|
||||
[157, "a0", "(pointer process-drawable)"]
|
||||
[157, "a0", "(pointer process-drawable)"],
|
||||
[[477, 481], "a0", "dma-packet"],
|
||||
[[487, 490], "a0", "gs-gif-tag"],
|
||||
|
||||
[497, "a0", "(pointer gs-reg64)"],
|
||||
[495, "a0", "(pointer gs-alpha)"],
|
||||
|
||||
[501, "a0", "(pointer gs-reg64)"],
|
||||
[499, "a0", "(pointer gs-zbuf)"],
|
||||
|
||||
[505, "a0", "(pointer gs-reg64)"],
|
||||
[503, "a0", "(pointer gs-test)"],
|
||||
|
||||
[508, "a0", "(pointer gs-reg64)"],
|
||||
[506, "a0", "(pointer uint64)"], // pabe
|
||||
|
||||
[512, "a0", "(pointer gs-reg64)"],
|
||||
[510, "a0", "(pointer gs-clamp)"],
|
||||
|
||||
[516, "a0", "(pointer gs-reg64)"],
|
||||
[514, "a0", "(pointer gs-tex1)"],
|
||||
|
||||
[521, "a0", "(pointer gs-reg64)"],
|
||||
[519, "a0", "(pointer gs-texa)"],
|
||||
|
||||
[525, "a0", "(pointer gs-reg64)"],
|
||||
[523, "a0", "(pointer gs-texclut)"],
|
||||
|
||||
[529, "a0", "(pointer gs-reg64)"],
|
||||
[527, "a0", "(pointer gs-fogcol)"],
|
||||
|
||||
[[588, 591], "v1", "dma-packet"],
|
||||
[[672, 675], "v1", "dma-packet"]
|
||||
],
|
||||
|
||||
"load-game-text-info": [[4, "v1", "game-text-info"]],
|
||||
@ -1787,6 +1823,174 @@
|
||||
|
||||
"draw-ocean-transition": [[255, "v1", "ocean-mid-mask"]],
|
||||
|
||||
"do-target-shadow": [
|
||||
[[0, 999], "s6", "target"],
|
||||
[46, "v1", "collide-shape-prim"] // `event-other` from collide-shape
|
||||
],
|
||||
|
||||
"draw-string": [
|
||||
[[93, 96], "a0", "(pointer uint8)"],
|
||||
[[206, 209], "t3", "font-work"],
|
||||
[[210, 214], "t3", "font-work"],
|
||||
[[217, 221], "t3", "font-work"],
|
||||
[356, "t2", "(pointer uint64)"],
|
||||
[726, "t2", "(pointer uint64)"],
|
||||
[[68, 76], "t4", "(pointer uint32)"],
|
||||
[[239, 247], "t3", "font-work"],
|
||||
[[424, 878], "a1", "(pointer uint128)"],
|
||||
[[616, 634], "t4", "font-work"]
|
||||
],
|
||||
|
||||
"get-string-length": [[[29, 31], "a0", "(pointer uint8)"]],
|
||||
|
||||
"print-game-text-scaled": [[[31, 32], "f3", "float"]],
|
||||
|
||||
"print-game-text": [[[369, 372], "v1", "dma-packet"]],
|
||||
|
||||
"debug-init-buffer": [
|
||||
[[11, 15], "t1", "dma-packet"],
|
||||
[[21, 24], "t1", "gs-gif-tag"],
|
||||
[28, "t1", "(pointer gs-zbuf)"],
|
||||
[30, "t1", "(pointer gs-reg64)"],
|
||||
[31, "t1", "(pointer gs-test)"],
|
||||
[33, "t1", "(pointer gs-reg64)"],
|
||||
[[39, 42], "a1", "dma-packet"]
|
||||
],
|
||||
|
||||
"display-frame-finish": [
|
||||
[[30, 35], "a1", "dma-packet"],
|
||||
[[40, 43], "a0", "dma-packet"],
|
||||
[[63, 69], "a0", "dma-packet"],
|
||||
[[78, 79], "a0", "dma-packet"],
|
||||
[79, "a0", "(pointer uint64)"]
|
||||
],
|
||||
"(anon-function 6 water-anim)": [
|
||||
[6, "a0", "vector"]
|
||||
],
|
||||
|
||||
"(anon-function 5 water-anim)": [
|
||||
[2, "v1", "(state none)"]
|
||||
],
|
||||
|
||||
"(anon-function 8 rigid-body)": [
|
||||
[[16, 21], "t9", "(function object)"]
|
||||
],
|
||||
|
||||
"(anon-function 6 rigid-body)": [
|
||||
[[16, 21], "t9", "(function object)"]
|
||||
],
|
||||
|
||||
"(method 34 rigid-body-platform)": [
|
||||
[[0, 5], "t9", "(function object)"]
|
||||
],
|
||||
|
||||
"(method 22 water-anim)": [
|
||||
[[9, 14], "t9", "(function object object)"],
|
||||
[25, "s3", "basic"],
|
||||
[[32, 37], "t9", "(function object object)"]
|
||||
],
|
||||
|
||||
"(method 25 water-anim)": [
|
||||
[[27, 45], "v1", "transform"]
|
||||
],
|
||||
|
||||
"(method 22 rigid-body-platform)": [
|
||||
[26, "f0", "float"]
|
||||
],
|
||||
|
||||
"rigid-body-platform-event-handler": [
|
||||
[28, "v1", "process-drawable"],
|
||||
[34, "v1", "float"],
|
||||
[83, "v1", "process-drawable"],
|
||||
[119, "v1", "process-drawable"],
|
||||
[125, "v1", "process-drawable"],
|
||||
[158, "v1", "vector"],
|
||||
[170, "v1", "vector"],
|
||||
[184, "v1", "(pointer handle)"],
|
||||
[213, "v1", "process-drawable"]
|
||||
],
|
||||
|
||||
"(anon-function 9 plat-eco)": [
|
||||
[23, "v1", "(state symbol none)"],
|
||||
[58, "v1", "(state symbol none)"]
|
||||
],
|
||||
|
||||
"(method 7 rigid-body-platform)": [
|
||||
[5, "v1", "int"],
|
||||
[14, "t9", "(function process-drawable int process-drawable)"]
|
||||
],
|
||||
|
||||
"(method 10 rigid-body)": [
|
||||
[50, "v1", "vector"]
|
||||
],
|
||||
|
||||
"(method 11 rigid-body-platform)": [
|
||||
[5, "v1", "process-mask"]
|
||||
],
|
||||
|
||||
"(method 22 mud)": [
|
||||
[[37, 41], "v1", "ripple-control"]
|
||||
],
|
||||
|
||||
"(method 11 twister)": [
|
||||
[7, "s4", "twist-joint"],
|
||||
[13, "s4", "twist-joint"],
|
||||
[26, "s4", "twist-joint"],
|
||||
[28, "s4", "twist-joint"],
|
||||
[36, "s4", "twist-joint"],
|
||||
[40, "s4", "twist-joint"],
|
||||
[47, "s4", "twist-joint"],
|
||||
[55, "s4", "twist-joint"],
|
||||
[70, "s4", "twist-joint"],
|
||||
[82, "s4", "twist-joint"]
|
||||
],
|
||||
|
||||
"(anon-function 2 misty-teetertotter)": [
|
||||
[11, "v1", "art-joint-anim"]
|
||||
],
|
||||
|
||||
"(anon-function 1 misty-teetertotter)": [
|
||||
[10, "v1", "art-joint-anim"]
|
||||
],
|
||||
|
||||
"misty-camera-view": [
|
||||
[19, "t9", "(function object object object object)"],
|
||||
[25, "v1", "handle"]
|
||||
],
|
||||
|
||||
"(method 11 silostep)": [
|
||||
[100, "v1", "art-joint-anim"]
|
||||
],
|
||||
|
||||
"(anon-function 6 sunken-elevator)": [
|
||||
[2, "v1", "(state none)"],
|
||||
[2, "t9", "(function object)"],
|
||||
[4, "t9", "(function object)"],
|
||||
[40, "v1", "village2cam"],
|
||||
[73, "v1", "village2cam"]
|
||||
],
|
||||
|
||||
"(anon-function 5 sunken-elevator)": [
|
||||
[2, "v1", "(state none)"],
|
||||
[2, "t9", "(function object)"],
|
||||
[4, "t9", "(function object)"]
|
||||
],
|
||||
|
||||
"(anon-function 4 sunken-elevator)": [
|
||||
[2, "v1", "(state none)"],
|
||||
[2, "t9", "(function object)"],
|
||||
[4, "t9", "(function object)"],
|
||||
[49, "v1", "village2cam"]
|
||||
],
|
||||
|
||||
"(anon-function 3 sunken-elevator)": [
|
||||
[13, "v0", "(state none)"]
|
||||
],
|
||||
|
||||
"(method 27 sunken-elevator)": [
|
||||
[37, "v1", "art-joint-anim"]
|
||||
],
|
||||
|
||||
"(method 42 nav-enemy)": [
|
||||
[[5, 9], "t9", "(function object object)"]
|
||||
],
|
||||
|
@ -1385,7 +1385,11 @@
|
||||
},
|
||||
|
||||
"display-loop": {
|
||||
"vars": {}
|
||||
"vars": {
|
||||
"s3-0":"debug-buf",
|
||||
"gp-0":"disp",
|
||||
"s5-2":"debug-txt-buf"
|
||||
}
|
||||
},
|
||||
|
||||
"adgif-shader-login": {
|
||||
@ -3039,5 +3043,93 @@
|
||||
}
|
||||
},
|
||||
|
||||
"set-font-color-alpha": {
|
||||
"args":["idx", "alpha"]
|
||||
},
|
||||
|
||||
"print-game-text-scaled": {
|
||||
"args":["str", "scale", "font-ctxt", "alpha"]
|
||||
},
|
||||
|
||||
"print-game-text": {
|
||||
"args":["str", "font-ctxt", "alpha", "offset-thing"]
|
||||
},
|
||||
|
||||
"display-frame-start": {
|
||||
"args":["disp", "new-frame-idx", "odd-even"],
|
||||
"vars": {
|
||||
"f30-0":"time-ratio",
|
||||
"s3-0":"scaled-seconds",
|
||||
"s3-1":"new-frame"
|
||||
}
|
||||
},
|
||||
|
||||
"display-frame-finish": {
|
||||
"args":["disp"],
|
||||
"vars": {
|
||||
"s4-0":"this-frame",
|
||||
"s5-0":"this-calc-buf",
|
||||
"s3-0":"bucket-idx",
|
||||
"v1-7":"this-global-buf",
|
||||
"a0-16":"global-buf",
|
||||
"v1-19":"calc-current",
|
||||
"a2-1":"calc-start",
|
||||
"s4-1":"global-current",
|
||||
"s5-1":"global-start",
|
||||
"s3-1":"global-end"
|
||||
}
|
||||
},
|
||||
|
||||
"display-sync": {
|
||||
"args":["disp"],
|
||||
"vars": {
|
||||
"s4-0":"frame-idx",
|
||||
"s5-0":"syncv-result",
|
||||
"s3-0":"dma-buf-to-send",
|
||||
"a1-4":"next-frame"
|
||||
}
|
||||
},
|
||||
|
||||
"draw-string": {
|
||||
"args":["str-in", "context"],
|
||||
"vars": {
|
||||
"v1-5":"fw",
|
||||
"a1-1":"dma-out",
|
||||
"t2-0":"flags",
|
||||
"a3-0":"has-flag-size24",
|
||||
"a3-1":"font-table-12",
|
||||
"a3-2":"font-table-to-use",
|
||||
"t0-0":"q-lo-tmpl",
|
||||
"t1-0":"q-hi-tmpl",
|
||||
"t3-0":"in-color",
|
||||
"t3-1":"color-array-prod",
|
||||
"t4-0":"fw+col",
|
||||
"t3-2":"q-verts-0p",
|
||||
"t5-0":"q-verts-1p",
|
||||
"t3-3":"q-verts-2p",
|
||||
"t4-1":"q-verts-3p",
|
||||
"t5-1":"q-verts-1t",
|
||||
"t5-2":"q-verts-1",
|
||||
"t3-4":"q-verts-2t",
|
||||
"t3-5":"q-verts-2",
|
||||
"t6-0":"q-verts-0t",
|
||||
"t6-1":"q-verts-0",
|
||||
"t4-2":"q-verts-3t",
|
||||
"t4-3":"q-verts-3",
|
||||
"t3-6":"fw2",
|
||||
"t4-4":"str-char",
|
||||
"t5-3":"not-sc-lit1",
|
||||
"t5-4":"not-sc-~",
|
||||
"t4-14":"fc-cr",
|
||||
"r0-0":"r0",
|
||||
"r0-1":"r0",
|
||||
"r0-2":"r0",
|
||||
"r0-3":"r0"
|
||||
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
"aaaaaaaaaaaaaaaaaaaaaaa": {}
|
||||
}
|
||||
|
@ -167,9 +167,10 @@ AudioFileInfo process_audio_file(const std::vector<u8>& data,
|
||||
assert(reader.read<u8>() == 0);
|
||||
}
|
||||
|
||||
auto file_name = fmt::format("{}_{}.wav", remove_trailing_spaces(name), suffix);
|
||||
file_util::create_dir_if_needed(file_util::get_file_path({"assets", "streaming_audio", suffix}));
|
||||
auto file_name = fmt::format("{}.wav", remove_trailing_spaces(name));
|
||||
write_wave_file_mono(decoded_samples, header.sample_rate,
|
||||
file_util::get_file_path({"assets", "streaming_audio", file_name}));
|
||||
file_util::get_file_path({"assets", "streaming_audio", suffix, file_name}));
|
||||
|
||||
std::string vag_filename;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
|
@ -19,222 +19,11 @@
|
||||
#include "common/versions.h"
|
||||
#include "decompiler/ObjectFile/ObjectFileDB.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "common/texture/texture_conversion.h"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
|
||||
/*!
|
||||
* Convert from a pixel location in a texture (x, y, texture buffer width) to VRAM address (byte).
|
||||
* Uses the PSMCT32 format.
|
||||
* This format is used either to store 8-bit RGBA (texture palettes) or to copy memory.
|
||||
* See Ch. 8, Details of GS Local Memory for these tables.
|
||||
*/
|
||||
u32 psmct32_addr(u32 x, u32 y, u32 width) {
|
||||
// XXX_col refers to which XXX you're in (screen)
|
||||
// XXX refers to which XXX you're in (memory)
|
||||
// XXX_x refers to the pixel within the XXX
|
||||
|
||||
// first, determine the page
|
||||
u32 pages_per_row = width / 64;
|
||||
u32 page_col = x / 64;
|
||||
u32 page_row = y / 32;
|
||||
u32 page_x = x % 64;
|
||||
u32 page_y = y % 32;
|
||||
u32 page = page_col + page_row * pages_per_row;
|
||||
|
||||
// next the block
|
||||
u32 block_col = page_x / 8;
|
||||
u32 block_row = page_y / 8;
|
||||
u32 block_x = page_x % 8;
|
||||
u32 block_y = page_y % 8;
|
||||
const u32 psm32_table[4][8] = {{0, 1, 4, 5, 16, 17, 20, 21},
|
||||
{2, 3, 6, 7, 18, 19, 22, 23},
|
||||
{8, 9, 12, 13, 24, 25, 28, 29},
|
||||
{10, 11, 14, 15, 26, 27, 30, 31}};
|
||||
|
||||
u32 block = psm32_table[block_row][block_col];
|
||||
|
||||
// next the column (there's only one "column" per column)
|
||||
u32 col_row = block_y / 2;
|
||||
u32 col_y = block_y % 2;
|
||||
u32 col_x = block_x;
|
||||
|
||||
// next the pixel
|
||||
const u32 psm32_pix_table[2][8] = {{0, 1, 4, 5, 8, 9, 12, 13}, {2, 3, 6, 7, 10, 11, 14, 15}};
|
||||
u32 pixel = psm32_pix_table[col_y][col_x];
|
||||
|
||||
// now the sum
|
||||
return ((page * 64 * 32) + (block * 8 * 8) + (col_row * 8 * 2) + pixel) * 4;
|
||||
}
|
||||
|
||||
u32 psmct16_addr(u32 x, u32 y, u32 width) {
|
||||
// page is 64x64
|
||||
// block is 16x8
|
||||
// column is 16x2
|
||||
|
||||
// page
|
||||
u32 pages_per_row = width / 64;
|
||||
u32 page_col = x / 64;
|
||||
u32 page_row = y / 64;
|
||||
u32 page_x = x % 64;
|
||||
u32 page_y = y % 64;
|
||||
u32 page = page_col + page_row * pages_per_row;
|
||||
|
||||
// block
|
||||
u32 block_col = page_x / 16;
|
||||
u32 block_row = page_y / 8;
|
||||
u32 block_x = page_x % 16;
|
||||
u32 block_y = page_y % 8;
|
||||
const u32 psm16_table[8][4] = {{0, 2, 8, 10}, {1, 3, 9, 11}, {4, 6, 12, 14},
|
||||
{5, 7, 13, 15}, {16, 18, 24, 26}, {17, 19, 25, 27},
|
||||
{20, 22, 28, 30}, {21, 23, 29, 31}};
|
||||
u32 block = psm16_table[block_row][block_col];
|
||||
|
||||
const uint8_t pix_tabel[8][16] = {
|
||||
{0, 2, 8, 10, 16, 18, 24, 26, 1, 3, 9, 11, 17, 19, 25, 27},
|
||||
{4, 6, 12, 14, 20, 22, 28, 30, 5, 7, 13, 15, 21, 23, 29, 31},
|
||||
{32, 34, 40, 42, 48, 50, 56, 58, 33, 35, 41, 43, 49, 51, 57, 59},
|
||||
{36, 38, 44, 46, 52, 54, 60, 62, 37, 39, 45, 47, 53, 55, 61, 63},
|
||||
{64, 66, 72, 74, 80, 82, 88, 90, 65, 67, 73, 75, 81, 83, 89, 91},
|
||||
{68, 70, 76, 78, 84, 86, 92, 94, 69, 71, 77, 79, 85, 87, 93, 95},
|
||||
{96, 98, 104, 106, 112, 114, 120, 122, 97, 99, 105, 107, 113, 115, 121, 123},
|
||||
{100, 102, 108, 110, 116, 118, 124, 126, 101, 103, 109, 111, 117, 119, 125, 127},
|
||||
};
|
||||
u32 pixel = pix_tabel[block_y][block_x];
|
||||
return 2 * ((page * 64 * 64) + (block * 16 * 8) + pixel);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert from a pixel location in a texture (x, y, texture buffer width) to VRAM address (byte).
|
||||
* Uses the PSMT8 format.
|
||||
* This format is used either to store 8-bit palette indices, used in most textures.
|
||||
* See Ch. 8, Details of GS Local Memory for these tables.
|
||||
*/
|
||||
u32 psmt8_addr(u32 x, u32 y, u32 width) {
|
||||
// page is 128, 64
|
||||
// block is 16, 16
|
||||
// column is 16, 4
|
||||
|
||||
// first determine the page
|
||||
u32 pages_per_row = width / 128;
|
||||
u32 page_col = x / 128;
|
||||
u32 page_row = y / 64;
|
||||
u32 page_x = x % 128;
|
||||
u32 page_y = y % 64;
|
||||
u32 page = page_col + page_row * pages_per_row;
|
||||
|
||||
// next block
|
||||
u32 block_col = page_x / 16;
|
||||
u32 block_row = page_y / 16;
|
||||
u32 block_x = page_x % 16;
|
||||
u32 block_y = page_y % 16;
|
||||
const u32 psm32_table[4][8] = {{0, 1, 4, 5, 16, 17, 20, 21},
|
||||
{2, 3, 6, 7, 18, 19, 22, 23},
|
||||
{8, 9, 12, 13, 24, 25, 28, 29},
|
||||
{10, 11, 14, 15, 26, 27, 30, 31}};
|
||||
u32 block = psm32_table[block_row][block_col]; // it's the same table!!!
|
||||
|
||||
// both columns and pixels within columns.
|
||||
const uint8_t pix_table[16][16] = {
|
||||
{0, 4, 16, 20, 32, 36, 48, 52, 2, 6, 18, 22, 34, 38, 50, 54},
|
||||
{8, 12, 24, 28, 40, 44, 56, 60, 10, 14, 26, 30, 42, 46, 58, 62},
|
||||
{33, 37, 49, 53, 1, 5, 17, 21, 35, 39, 51, 55, 3, 7, 19, 23},
|
||||
{41, 45, 57, 61, 9, 13, 25, 29, 43, 47, 59, 63, 11, 15, 27, 31},
|
||||
{96, 100, 112, 116, 64, 68, 80, 84, 98, 102, 114, 118, 66, 70, 82, 86},
|
||||
{104, 108, 120, 124, 72, 76, 88, 92, 106, 110, 122, 126, 74, 78, 90, 94},
|
||||
{65, 69, 81, 85, 97, 101, 113, 117, 67, 71, 83, 87, 99, 103, 115, 119},
|
||||
{73, 77, 89, 93, 105, 109, 121, 125, 75, 79, 91, 95, 107, 111, 123, 127},
|
||||
{128, 132, 144, 148, 160, 164, 176, 180, 130, 134, 146, 150, 162, 166, 178, 182},
|
||||
{136, 140, 152, 156, 168, 172, 184, 188, 138, 142, 154, 158, 170, 174, 186, 190},
|
||||
{161, 165, 177, 181, 129, 133, 145, 149, 163, 167, 179, 183, 131, 135, 147, 151},
|
||||
{169, 173, 185, 189, 137, 141, 153, 157, 171, 175, 187, 191, 139, 143, 155, 159},
|
||||
{224, 228, 240, 244, 192, 196, 208, 212, 226, 230, 242, 246, 194, 198, 210, 214},
|
||||
{232, 236, 248, 252, 200, 204, 216, 220, 234, 238, 250, 254, 202, 206, 218, 222},
|
||||
{193, 197, 209, 213, 225, 229, 241, 245, 195, 199, 211, 215, 227, 231, 243, 247},
|
||||
{201, 205, 217, 221, 233, 237, 249, 253, 203, 207, 219, 223, 235, 239, 251, 255},
|
||||
};
|
||||
|
||||
u32 pixel = pix_table[block_y][block_x];
|
||||
return (page * 128 * 64) + (block * 16 * 16) + pixel;
|
||||
}
|
||||
|
||||
u32 psmt4_addr_half_byte(u32 x, u32 y, u32 width) {
|
||||
// page is 128, 128
|
||||
// block is 32, 16
|
||||
// column is 32, 4
|
||||
|
||||
// first determine the page
|
||||
u32 pages_per_row = width / 128;
|
||||
u32 page_col = x / 128;
|
||||
u32 page_row = y / 128;
|
||||
u32 page_x = x % 128;
|
||||
u32 page_y = y % 128;
|
||||
u32 page = page_col + page_row * pages_per_row;
|
||||
|
||||
// next block
|
||||
u32 block_col = page_x / 32;
|
||||
u32 block_row = page_y / 16;
|
||||
u32 block_x = page_x % 32;
|
||||
u32 block_y = page_y % 16;
|
||||
const u32 psm4_table[8][4] = {{0, 2, 8, 10}, {1, 3, 9, 11}, {4, 6, 12, 14},
|
||||
{5, 7, 13, 15}, {16, 18, 24, 26}, {17, 19, 25, 27},
|
||||
{20, 22, 28, 30}, {21, 23, 29, 31}};
|
||||
assert(block_row < 8);
|
||||
assert(block_col < 4);
|
||||
u32 block = psm4_table[block_row][block_col]; // it's the same table!!!
|
||||
|
||||
// both columns and pixels within columns.
|
||||
const uint16_t pix_table[16][32] = {
|
||||
{0, 8, 32, 40, 64, 72, 96, 104, 2, 10, 34, 42, 66, 74, 98, 106,
|
||||
4, 12, 36, 44, 68, 76, 100, 108, 6, 14, 38, 46, 70, 78, 102, 110},
|
||||
{16, 24, 48, 56, 80, 88, 112, 120, 18, 26, 50, 58, 82, 90, 114, 122,
|
||||
20, 28, 52, 60, 84, 92, 116, 124, 22, 30, 54, 62, 86, 94, 118, 126},
|
||||
{65, 73, 97, 105, 1, 9, 33, 41, 67, 75, 99, 107, 3, 11, 35, 43,
|
||||
69, 77, 101, 109, 5, 13, 37, 45, 71, 79, 103, 111, 7, 15, 39, 47},
|
||||
{81, 89, 113, 121, 17, 25, 49, 57, 83, 91, 115, 123, 19, 27, 51, 59,
|
||||
85, 93, 117, 125, 21, 29, 53, 61, 87, 95, 119, 127, 23, 31, 55, 63},
|
||||
{192, 200, 224, 232, 128, 136, 160, 168, 194, 202, 226, 234, 130, 138, 162, 170,
|
||||
196, 204, 228, 236, 132, 140, 164, 172, 198, 206, 230, 238, 134, 142, 166, 174},
|
||||
{208, 216, 240, 248, 144, 152, 176, 184, 210, 218, 242, 250, 146, 154, 178, 186,
|
||||
212, 220, 244, 252, 148, 156, 180, 188, 214, 222, 246, 254, 150, 158, 182, 190},
|
||||
{129, 137, 161, 169, 193, 201, 225, 233, 131, 139, 163, 171, 195, 203, 227, 235,
|
||||
133, 141, 165, 173, 197, 205, 229, 237, 135, 143, 167, 175, 199, 207, 231, 239},
|
||||
{145, 153, 177, 185, 209, 217, 241, 249, 147, 155, 179, 187, 211, 219, 243, 251,
|
||||
149, 157, 181, 189, 213, 221, 245, 253, 151, 159, 183, 191, 215, 223, 247, 255},
|
||||
{256, 264, 288, 296, 320, 328, 352, 360, 258, 266, 290, 298, 322, 330, 354, 362,
|
||||
260, 268, 292, 300, 324, 332, 356, 364, 262, 270, 294, 302, 326, 334, 358, 366},
|
||||
{272, 280, 304, 312, 336, 344, 368, 376, 274, 282, 306, 314, 338, 346, 370, 378,
|
||||
276, 284, 308, 316, 340, 348, 372, 380, 278, 286, 310, 318, 342, 350, 374, 382},
|
||||
{321, 329, 353, 361, 257, 265, 289, 297, 323, 331, 355, 363, 259, 267, 291, 299,
|
||||
325, 333, 357, 365, 261, 269, 293, 301, 327, 335, 359, 367, 263, 271, 295, 303},
|
||||
{337, 345, 369, 377, 273, 281, 305, 313, 339, 347, 371, 379, 275, 283, 307, 315,
|
||||
341, 349, 373, 381, 277, 285, 309, 317, 343, 351, 375, 383, 279, 287, 311, 319},
|
||||
{448, 456, 480, 488, 384, 392, 416, 424, 450, 458, 482, 490, 386, 394, 418, 426,
|
||||
452, 460, 484, 492, 388, 396, 420, 428, 454, 462, 486, 494, 390, 398, 422, 430},
|
||||
{464, 472, 496, 504, 400, 408, 432, 440, 466, 474, 498, 506, 402, 410, 434, 442,
|
||||
468, 476, 500, 508, 404, 412, 436, 444, 470, 478, 502, 510, 406, 414, 438, 446},
|
||||
{385, 393, 417, 425, 449, 457, 481, 489, 387, 395, 419, 427, 451, 459, 483, 491,
|
||||
389, 397, 421, 429, 453, 461, 485, 493, 391, 399, 423, 431, 455, 463, 487, 495},
|
||||
{401, 409, 433, 441, 465, 473, 497, 505, 403, 411, 435, 443, 467, 475, 499, 507,
|
||||
405, 413, 437, 445, 469, 477, 501, 509, 407, 415, 439, 447, 471, 479, 503, 511},
|
||||
};
|
||||
|
||||
assert(block_y < 16);
|
||||
assert(block_x < 32);
|
||||
u32 pixel = pix_table[block_y][block_x];
|
||||
return (page * 128 * 128) + (block * 32 * 16) + pixel;
|
||||
}
|
||||
|
||||
u32 rgba16_to_rgba32(u32 in) {
|
||||
float ratio = 255.0 / 31.0;
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
(deftype texture-page-segment (structure)
|
||||
((block-data pointer :offset-assert 0)
|
||||
@ -620,11 +409,6 @@ TexturePage read_texture_page(ObjectFileData& data,
|
||||
return tpage;
|
||||
}
|
||||
|
||||
// texture format enums
|
||||
enum class PSM { PSMCT16 = 0x02, PSMT8 = 0x13, PSMT4 = 0x14 };
|
||||
// clut format enums
|
||||
enum class CPSM { PSMCT32 = 0x0, PSMCT16 = 0x02 };
|
||||
|
||||
} // namespace
|
||||
|
||||
/*!
|
||||
|
@ -288,7 +288,7 @@ int index_of_closest_following_label_in_segment(int start_byte,
|
||||
for (int i = 0; i < (int)labels.size(); i++) {
|
||||
const auto& label = labels.at(i);
|
||||
if (label.target_segment == seg) {
|
||||
if (result_idx == -1) {
|
||||
if (result_idx == -1 && label.offset > start_byte) {
|
||||
result_idx = i;
|
||||
closest_byte = label.offset;
|
||||
} else {
|
||||
@ -419,6 +419,20 @@ goos::Object ocean_mid_masks_decompile(const std::vector<LinkedWord>& words,
|
||||
data_field, all_words, file,
|
||||
TypeSpec("ocean-mid-mask"), 8);
|
||||
}
|
||||
|
||||
goos::Object sp_field_init_spec_decompile(const std::vector<LinkedWord>& words,
|
||||
const std::vector<DecompilerLabel>& labels,
|
||||
int my_seg,
|
||||
int field_location,
|
||||
const TypeSystem& ts,
|
||||
const Field& data_field,
|
||||
const std::vector<std::vector<LinkedWord>>& all_words,
|
||||
const LinkedObjectFile* file) {
|
||||
return decomp_ref_to_inline_array_guess_size(words, labels, my_seg, field_location, ts,
|
||||
data_field, all_words, file,
|
||||
TypeSpec("sp-field-init-spec"), 16);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
goos::Object decompile_structure(const TypeSpec& type,
|
||||
@ -608,6 +622,10 @@ goos::Object decompile_structure(const TypeSpec& type,
|
||||
field_defs_out.emplace_back(
|
||||
field.name(), ocean_mid_masks_decompile(obj_words, labels, label.target_segment,
|
||||
field_start, ts, field, words, file));
|
||||
} else if (field.name() == "init-specs" && type.print() == "sparticle-launcher") {
|
||||
field_defs_out.emplace_back(
|
||||
field.name(), sp_field_init_spec_decompile(obj_words, labels, label.target_segment,
|
||||
field_start, ts, field, words, file));
|
||||
} else {
|
||||
std::vector<u8> bytes_out;
|
||||
for (int byte_idx = field_start; byte_idx < field_end; byte_idx++) {
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,8 +1,11 @@
|
||||
{
|
||||
"jak1": {
|
||||
"locPercentage": {
|
||||
"value": 117858,
|
||||
"label": "Lines of Code"
|
||||
"fileProgress": {
|
||||
"src_files_total": 514,
|
||||
"src_files_finished": 73,
|
||||
"src_files_started": 176,
|
||||
"data_files_total": 584,
|
||||
"data_files_started": 4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -58,13 +58,33 @@
|
||||
<v-col cols="10">
|
||||
<v-subheader>Decompilation</v-subheader>
|
||||
<v-progress-linear
|
||||
color="teal"
|
||||
color="green"
|
||||
buffer-value="0"
|
||||
:value="jak1BlackLabelStatus.decompDone"
|
||||
:value="jak1BlackLabelStatus.srcFilesFinishedValue"
|
||||
stream
|
||||
height="25"
|
||||
>
|
||||
{{jak1BlackLabelStatus.decompLabel}} - {{jak1BlackLabelStatus.decompDone}}%
|
||||
Source Files Finished - {{ jak1BlackLabelStatus.srcFilesFinished }} / {{ jak1BlackLabelStatus.srcFilesTotal }}
|
||||
</v-progress-linear>
|
||||
<br>
|
||||
<v-progress-linear
|
||||
color="yellow"
|
||||
buffer-value="0"
|
||||
:value="jak1BlackLabelStatus.srcFilesStartedValue"
|
||||
stream
|
||||
height="25"
|
||||
>
|
||||
Source Files Started - {{ jak1BlackLabelStatus.srcFilesStarted }} / {{ jak1BlackLabelStatus.srcFilesTotal }}
|
||||
</v-progress-linear>
|
||||
<br>
|
||||
<v-progress-linear
|
||||
color="teal"
|
||||
buffer-value="0"
|
||||
:value="jak1BlackLabelStatus.dataFilesStartedValue"
|
||||
stream
|
||||
height="25"
|
||||
>
|
||||
Data Files Started - {{ jak1BlackLabelStatus.dataFilesStarted }} / {{ jak1BlackLabelStatus.dataFilesTotal }}
|
||||
</v-progress-linear>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@ -129,8 +149,14 @@ export default {
|
||||
return {
|
||||
recentPRs: [],
|
||||
jak1BlackLabelStatus: {
|
||||
decompDone: (projectProgress.jak1.locPercentage.value / 750000.0) * 100.0,
|
||||
decompLabel: projectProgress.jak1.locPercentage.label
|
||||
srcFilesTotal: projectProgress.jak1.fileProgress.src_files_total,
|
||||
srcFilesFinished: projectProgress.jak1.fileProgress.src_files_finished,
|
||||
srcFilesFinishedValue: projectProgress.jak1.fileProgress.src_files_finished / projectProgress.jak1.fileProgress.src_files_total * 100,
|
||||
srcFilesStarted: projectProgress.jak1.fileProgress.src_files_started,
|
||||
srcFilesStartedValue: projectProgress.jak1.fileProgress.src_files_started / projectProgress.jak1.fileProgress.src_files_total * 100,
|
||||
dataFilesTotal: projectProgress.jak1.fileProgress.data_files_total,
|
||||
dataFilesStarted: projectProgress.jak1.fileProgress.data_files_started,
|
||||
dataFilesStartedValue: projectProgress.jak1.fileProgress.data_files_started / projectProgress.jak1.fileProgress.data_files_total * 100,
|
||||
}
|
||||
};
|
||||
},
|
||||
@ -152,7 +178,7 @@ export default {
|
||||
const numPRs = 9;
|
||||
for (var i = 0; i < numPRs; i++) {
|
||||
var pr = data.items[i];
|
||||
if (pr.body.length == 0) {
|
||||
if (pr.body == null || pr.body.length == 0) {
|
||||
pr.body = "No Description";
|
||||
}
|
||||
pr.body = this.truncateString(pr.body, 250);
|
||||
|
@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/jak-project/favicon.png"><title>OpenGOAL Tooling</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"><link href="/jak-project/css/about.8d0e9591.css" rel="prefetch"><link href="/jak-project/js/about.93743c22.js" rel="prefetch"><link href="/jak-project/css/chunk-vendors.b018c88a.css" rel="preload" as="style"><link href="/jak-project/js/app.1e67702b.js" rel="preload" as="script"><link href="/jak-project/js/chunk-vendors.f27e8f37.js" rel="preload" as="script"><link href="/jak-project/css/chunk-vendors.b018c88a.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but docs-and-tooling doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/jak-project/js/chunk-vendors.f27e8f37.js"></script><script src="/jak-project/js/app.1e67702b.js"></script></body></html>
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/jak-project/favicon.png"><title>OpenGOAL Tooling</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"><link href="/jak-project/css/about.c2633d65.css" rel="prefetch"><link href="/jak-project/js/about.a4656f69.js" rel="prefetch"><link href="/jak-project/css/chunk-vendors.b018c88a.css" rel="preload" as="style"><link href="/jak-project/js/app.99fa3a4f.js" rel="preload" as="script"><link href="/jak-project/js/chunk-vendors.b495df31.js" rel="preload" as="script"><link href="/jak-project/css/chunk-vendors.b018c88a.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but docs-and-tooling doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/jak-project/js/chunk-vendors.b495df31.js"></script><script src="/jak-project/js/app.99fa3a4f.js"></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
docs/js/about.a4656f69.js
Normal file
2
docs/js/about.a4656f69.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/js/about.a4656f69.js.map
Normal file
1
docs/js/about.a4656f69.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -1,2 +1,2 @@
|
||||
(function(e){function t(t){for(var n,o,i=t[0],c=t[1],l=t[2],s=0,p=[];s<i.length;s++)o=i[s],Object.prototype.hasOwnProperty.call(a,o)&&a[o]&&p.push(a[o][0]),a[o]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);f&&f(t);while(p.length)p.shift()();return u.push.apply(u,l||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,o=1;o<r.length;o++){var i=r[o];0!==a[i]&&(n=!1)}n&&(u.splice(t--,1),e=c(c.s=r[0]))}return e}var n={},o={app:0},a={app:0},u=[];function i(e){return c.p+"js/"+({about:"about"}[e]||e)+"."+{about:"93743c22"}[e]+".js"}function c(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,c),r.l=!0,r.exports}c.e=function(e){var t=[],r={about:1};o[e]?t.push(o[e]):0!==o[e]&&r[e]&&t.push(o[e]=new Promise((function(t,r){for(var n="css/"+({about:"about"}[e]||e)+"."+{about:"8d0e9591"}[e]+".css",a=c.p+n,u=document.getElementsByTagName("link"),i=0;i<u.length;i++){var l=u[i],s=l.getAttribute("data-href")||l.getAttribute("href");if("stylesheet"===l.rel&&(s===n||s===a))return t()}var p=document.getElementsByTagName("style");for(i=0;i<p.length;i++){l=p[i],s=l.getAttribute("data-href");if(s===n||s===a)return t()}var f=document.createElement("link");f.rel="stylesheet",f.type="text/css",f.onload=t,f.onerror=function(t){var n=t&&t.target&&t.target.src||a,u=new Error("Loading CSS chunk "+e+" failed.\n("+n+")");u.code="CSS_CHUNK_LOAD_FAILED",u.request=n,delete o[e],f.parentNode.removeChild(f),r(u)},f.href=a;var d=document.getElementsByTagName("head")[0];d.appendChild(f)})).then((function(){o[e]=0})));var n=a[e];if(0!==n)if(n)t.push(n[2]);else{var u=new Promise((function(t,r){n=a[e]=[t,r]}));t.push(n[2]=u);var l,s=document.createElement("script");s.charset="utf-8",s.timeout=120,c.nc&&s.setAttribute("nonce",c.nc),s.src=i(e);var p=new Error;l=function(t){s.onerror=s.onload=null,clearTimeout(f);var r=a[e];if(0!==r){if(r){var n=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;p.message="Loading chunk "+e+" failed.\n("+n+": "+o+")",p.name="ChunkLoadError",p.type=n,p.request=o,r[1](p)}a[e]=void 0}};var f=setTimeout((function(){l({type:"timeout",target:s})}),12e4);s.onerror=s.onload=l,document.head.appendChild(s)}return Promise.all(t)},c.m=e,c.c=n,c.d=function(e,t,r){c.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},c.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},c.t=function(e,t){if(1&t&&(e=c(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(c.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)c.d(r,n,function(t){return e[t]}.bind(null,n));return r},c.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return c.d(t,"a",t),t},c.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},c.p="/jak-project/",c.oe=function(e){throw console.error(e),e};var l=window["webpackJsonp"]=window["webpackJsonp"]||[],s=l.push.bind(l);l.push=t,l=l.slice();for(var p=0;p<l.length;p++)t(l[p]);var f=s;u.push([0,"chunk-vendors"]),r()})({0:function(e,t,r){e.exports=r("56d7")},"56d7":function(e,t,r){"use strict";r.r(t);r("e260"),r("e6cf"),r("cca6"),r("a79d");var n=r("2b0e"),o=function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("v-app",[r("v-main",[r("router-view")],1)],1)},a=[],u={name:"App",components:{},data:function(){return{}}},i=u,c=r("2877"),l=r("6544"),s=r.n(l),p=r("7496"),f=r("f6c4"),d=Object(c["a"])(i,o,a,!1,null,null,null),h=d.exports;s()(d,{VApp:p["a"],VMain:f["a"]});var m=r("f309");n["a"].use(m["a"]);var v=new m["a"]({theme:{dark:!0}}),b=(r("d3b7"),r("3ca3"),r("ddb0"),r("8c4f"));n["a"].use(b["a"]);var g=[{path:"/",name:"Home",component:function(){return r.e("about").then(r.bind(null,"bb51"))}}],y=new b["a"]({mode:"history",base:"/jak-project/",routes:g}),w=y;n["a"].config.productionTip=!1,new n["a"]({vuetify:v,router:w,render:function(e){return e(h)}}).$mount("#app")}});
|
||||
//# sourceMappingURL=app.1e67702b.js.map
|
||||
(function(e){function t(t){for(var n,o,i=t[0],c=t[1],l=t[2],s=0,p=[];s<i.length;s++)o=i[s],Object.prototype.hasOwnProperty.call(a,o)&&a[o]&&p.push(a[o][0]),a[o]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);f&&f(t);while(p.length)p.shift()();return u.push.apply(u,l||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,o=1;o<r.length;o++){var i=r[o];0!==a[i]&&(n=!1)}n&&(u.splice(t--,1),e=c(c.s=r[0]))}return e}var n={},o={app:0},a={app:0},u=[];function i(e){return c.p+"js/"+({about:"about"}[e]||e)+"."+{about:"a4656f69"}[e]+".js"}function c(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,c),r.l=!0,r.exports}c.e=function(e){var t=[],r={about:1};o[e]?t.push(o[e]):0!==o[e]&&r[e]&&t.push(o[e]=new Promise((function(t,r){for(var n="css/"+({about:"about"}[e]||e)+"."+{about:"c2633d65"}[e]+".css",a=c.p+n,u=document.getElementsByTagName("link"),i=0;i<u.length;i++){var l=u[i],s=l.getAttribute("data-href")||l.getAttribute("href");if("stylesheet"===l.rel&&(s===n||s===a))return t()}var p=document.getElementsByTagName("style");for(i=0;i<p.length;i++){l=p[i],s=l.getAttribute("data-href");if(s===n||s===a)return t()}var f=document.createElement("link");f.rel="stylesheet",f.type="text/css",f.onload=t,f.onerror=function(t){var n=t&&t.target&&t.target.src||a,u=new Error("Loading CSS chunk "+e+" failed.\n("+n+")");u.code="CSS_CHUNK_LOAD_FAILED",u.request=n,delete o[e],f.parentNode.removeChild(f),r(u)},f.href=a;var d=document.getElementsByTagName("head")[0];d.appendChild(f)})).then((function(){o[e]=0})));var n=a[e];if(0!==n)if(n)t.push(n[2]);else{var u=new Promise((function(t,r){n=a[e]=[t,r]}));t.push(n[2]=u);var l,s=document.createElement("script");s.charset="utf-8",s.timeout=120,c.nc&&s.setAttribute("nonce",c.nc),s.src=i(e);var p=new Error;l=function(t){s.onerror=s.onload=null,clearTimeout(f);var r=a[e];if(0!==r){if(r){var n=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;p.message="Loading chunk "+e+" failed.\n("+n+": "+o+")",p.name="ChunkLoadError",p.type=n,p.request=o,r[1](p)}a[e]=void 0}};var f=setTimeout((function(){l({type:"timeout",target:s})}),12e4);s.onerror=s.onload=l,document.head.appendChild(s)}return Promise.all(t)},c.m=e,c.c=n,c.d=function(e,t,r){c.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},c.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},c.t=function(e,t){if(1&t&&(e=c(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(c.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)c.d(r,n,function(t){return e[t]}.bind(null,n));return r},c.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return c.d(t,"a",t),t},c.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},c.p="/jak-project/",c.oe=function(e){throw console.error(e),e};var l=window["webpackJsonp"]=window["webpackJsonp"]||[],s=l.push.bind(l);l.push=t,l=l.slice();for(var p=0;p<l.length;p++)t(l[p]);var f=s;u.push([0,"chunk-vendors"]),r()})({0:function(e,t,r){e.exports=r("56d7")},"56d7":function(e,t,r){"use strict";r.r(t);r("e260"),r("e6cf"),r("cca6"),r("a79d");var n=r("2b0e"),o=function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("v-app",[r("v-main",[r("router-view")],1)],1)},a=[],u={name:"App",components:{},data:function(){return{}}},i=u,c=r("2877"),l=r("6544"),s=r.n(l),p=r("7496"),f=r("f6c4"),d=Object(c["a"])(i,o,a,!1,null,null,null),h=d.exports;s()(d,{VApp:p["a"],VMain:f["a"]});var m=r("f309");n["a"].use(m["a"]);var v=new m["a"]({theme:{dark:!0}}),b=(r("d3b7"),r("3ca3"),r("ddb0"),r("8c4f"));n["a"].use(b["a"]);var g=[{path:"/",name:"Home",component:function(){return r.e("about").then(r.bind(null,"bb51"))}}],y=new b["a"]({mode:"history",base:"/jak-project/",routes:g}),w=y;n["a"].config.productionTip=!1,new n["a"]({vuetify:v,router:w,render:function(e){return e(h)}}).$mount("#app")}});
|
||||
//# sourceMappingURL=app.99fa3a4f.js.map
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
docs/js/chunk-vendors.b495df31.js.map
Normal file
1
docs/js/chunk-vendors.b495df31.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -19,3 +19,6 @@
|
||||
- [Assembly Emitter](/asm_emitter.md)
|
||||
- [Porting to x86](/porting_to_x86.md)
|
||||
- [Register Handling](/registers.md)
|
||||
|
||||
- PC Port Documentation
|
||||
- [Graphics](/graphics.md)
|
128
docs/markdown/graphics.md
Normal file
128
docs/markdown/graphics.md
Normal file
@ -0,0 +1,128 @@
|
||||
# Graphics
|
||||
|
||||
There are three frames in flight at a time:
|
||||
- One frame being processed by the CPU, building DMA lists. This is called the "calc frame"
|
||||
- One frame being processed by the GPU, going through the DMA lists and drawing triangles to one frame buffer. I will call this the "draw" frame.
|
||||
- One frame being displayed on the screen, stored in the other frame buffer. This is called the "on-screen" frame.
|
||||
|
||||
|
||||
|
||||
## Synchronization
|
||||
|
||||
The PC Port synchronizes on `syncv` and on `sync-path`. The `syncv` waits for an actual buffer swap and `sync-path` waits for the renderer to finish.
|
||||
|
||||
The game's code for this is kind of messy and confusing, and calls `sync-path` twice. On the PS2, you make sure rendering is done with `sync-path`, which waits for the DMA chain to finish. But they call this earlier than I think they need to, and I don't really understand why. I don't see any place where they read back from the finished frame or depth buffer. Or where they would overwrite the memory. There's a second call to `sync-path` right where you would expect, imeediately before the `syncv`. After `syncv`, they call some Sony library function to actually display the correct framebuffer, then immediately start sending the next DMA chain.
|
||||
|
||||
The stuff between `sync-path` and `syncv` is:
|
||||
- depth cue "calc" (seems fast)
|
||||
- screen filter "calc" (very fast, just DMA for a single quad)
|
||||
- calc for letterbox bars and blackout
|
||||
- debug draw
|
||||
- draw profiler (look at all the stuff that happens after!)
|
||||
- deci count draw
|
||||
- file info draw
|
||||
- stdcon text draw
|
||||
- iop/memcard info
|
||||
- tie buffer init
|
||||
- merc buffer init
|
||||
- post draw hook (does nothing)
|
||||
- bucket patching
|
||||
- cache flush
|
||||
- a second `sync-path`
|
||||
|
||||
According to the Performance Analyzer, this takes about 1% to 2% of a frame. They subtract off 4% of a frame from the profile bar so that 100% there is really around 96% of a frame, I guess to account for this extra time.
|
||||
|
||||
I'm really not sure why they have the first `sync-path` there. It makes some sense in debug mode so that you can draw the profile bar for the GPU after it has finished. Another theory is that they didn't want debug code and non-debug rendering running at the same time - the debug code will compete with the rendering to use the main bus, and will make the rendering slower. But it seems like you don't want this in the release version.
|
||||
|
||||
For now, the PC Port does sync on `sync-path`, but it probably doesn't need to.
|
||||
|
||||
## DMA Copy
|
||||
|
||||
Starting the main graphics DMA chain is intercepted by the PC Port code. In the GOAL thread, it iterates through the entire DMA chain and creates a copy. Then, it sends this copy to the graphics thread.
|
||||
|
||||
There are two reasons for copying:
|
||||
- If there is a memory bug in the game that corrupts the DMA buffer, it will not cause the renderer to crash. This is nice for debugging.
|
||||
- It will be easy to save this copied DMA chain to a file for debugging later.
|
||||
|
||||
The DMA copier attempts to reduce the amount of memory used. It divides the 128MB of RAM into 128 kB chunks, marks which ones contain DMA data, then only copies those chunks. The chunks are compacted and the DMA pointers are updated to point to the relocated chunks.
|
||||
|
||||
This operation might be expensive and we might need to get rid of it later. But it would be easy to get rid of - the renderers themselves just read from a DMA chain and don't care if it is a copy or not.
|
||||
|
||||
## DMA Chain Iteration
|
||||
|
||||
On the C++ side, you can iterate through DMA with the `DmaFollower` class. Here is an example of flattening a DMA chain to a sequence of bytes:
|
||||
```cpp
|
||||
std::vector<u8> flatten_dma() {
|
||||
DmaFollower state(memory, start_tag_addr);
|
||||
std::vector<u8> result;
|
||||
while (!state.ended()) {
|
||||
// tag value:
|
||||
state.current_tag();
|
||||
// tag address
|
||||
state.current_tag_offset();
|
||||
|
||||
// DMA data
|
||||
auto read_result = state.read_and_advance();
|
||||
// this is the transferred tag (u64 after dma tag, usually 2x vif tags)
|
||||
u64 tag = read_result.transferred_tag;
|
||||
|
||||
// the actual bytes (pointer to data in the input chain)
|
||||
result.insert(result.end(), read_result.data, read_result.data + read_result.size_bytes);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
This will take care of following `call` and `ret` and those details.
|
||||
|
||||
|
||||
## Buckets
|
||||
The game builds the DMA chain in 69 buckets. Each bucket corresponds to a rendering pass. In the OpenGLRenderer, you can designate a specific renderer for each bucket.
|
||||
|
||||
This can be set up in `init_bucket_renderers`, and you can pass arguments to the renderers:
|
||||
```cpp
|
||||
/*!
|
||||
* Construct bucket renderers. We can specify different renderers for different buckets
|
||||
*/
|
||||
void OpenGLRenderer::init_bucket_renderers() {
|
||||
|
||||
// create a DirectRenderer for each of these two buckets
|
||||
init_bucket_renderer<DirectRenderer>("debug-draw-0", BucketId::DEBUG_DRAW_0, 1024 * 8);
|
||||
init_bucket_renderer<DirectRenderer>("debug-draw-1", BucketId::DEBUG_DRAW_1, 1024 * 8);
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Each bucket renderer will have access to shared data. For now, this is textures and shaders.
|
||||
|
||||
## Textures
|
||||
Currently, the only textures supported are those uploaded with `upload-now!`, which does an immediate upload of the texture. The `TexturePool` receives the actual GOAL `texture-page` object, and uses the metadata there to figure out how to convert the texture to RGBA8888.
|
||||
|
||||
It maintains a lookup table of converted textures by TBP which is used for fast lookup in the renderers.
|
||||
|
||||
This system isn't great yet, I think we will need to improve it for time-of-day textures and textures that are swapped in and out of VRAM.
|
||||
|
||||
## Shaders
|
||||
The shaders themselves are stored in `opengl_renderer/shaders`. They should be named with `.frag` and `.vert` extensions.
|
||||
|
||||
To add a new shader, add it to `ShaderId` in `Shader.h` and give it a name in `Shader.cpp`. It will be compiled when the graphics system is loaded.
|
||||
|
||||
## Direct Renderer
|
||||
The direct renderer interprets GIF data directly. It is for rendering stuff that was set up entirely on the CPU, with no VU1 use.
|
||||
|
||||
Currently, the only known things that use this are:
|
||||
- debug stuff
|
||||
- font
|
||||
- screen filter/blackout/depth-cue
|
||||
- the progress menu
|
||||
|
||||
All of these cases are pretty simple, and this is nowhere near a full GS emulation.
|
||||
|
||||
It does a single pass through the DMA chain and creates arrays of triangles. It is designed to reduce the number of OpenGL draw calls when consecutive primitives are drawn in the same mode.
|
||||
|
||||
## Mysteries
|
||||
|
||||
Why did they put the first call to `sync-path` in?
|
||||
|
||||
How does the `upload-now!` during texture login for near textures work? It seems like it writes too much and might write over the other level's texture.
|
||||
|
@ -187,4 +187,5 @@
|
||||
- Creating arrays on the stack now must be done with `stack-no-clear` as they are not memset to 0 or constructed in any way.
|
||||
- The register allocator has been dramatically improved and generates ~5x fewer spill instructions and is able to eliminate more moves.
|
||||
- Added a `(print-debug-compiler-stats)` form to print out statistics related to register allocation and move elimination
|
||||
- Added `get-enum-vals` which returns a list of pairs. Each pair is the name (symbol) and value (int) for each value in the enum
|
||||
- Added `get-enum-vals` which returns a list of pairs. Each pair is the name (symbol) and value (int) for each value in the enum
|
||||
- It is now possible to set a 64-bit memory location from a float, if you insert a cast. It will zero-extend the float, just like any other float -> 64-bit conversion.
|
@ -71,8 +71,19 @@ set(RUNTIME_SOURCE
|
||||
overlord/srpc.cpp
|
||||
overlord/ssound.cpp
|
||||
overlord/stream.cpp
|
||||
graphics/dma/dma.cpp
|
||||
graphics/gfx.cpp
|
||||
graphics/display.cpp
|
||||
graphics/sceGraphicsInterface.cpp
|
||||
graphics/dma/dma_copy.cpp
|
||||
graphics/dma/gs.cpp
|
||||
graphics/opengl_renderer/BucketRenderer.cpp
|
||||
graphics/opengl_renderer/DirectRenderer.cpp
|
||||
graphics/opengl_renderer/OpenGLRenderer.cpp
|
||||
graphics/opengl_renderer/Shader.cpp
|
||||
graphics/texture/TextureConverter.cpp
|
||||
graphics/texture/TexturePool.cpp
|
||||
graphics/pipelines/opengl.cpp
|
||||
system/vm/dmac.cpp
|
||||
system/vm/vm.cpp)
|
||||
|
||||
|
@ -10,5 +10,6 @@ TWEAKVAL.MUS resources/TWEAKVAL.MUS
|
||||
VAGDIR.AYB resources/VAGDIR.AYB
|
||||
SCREEN1.USA resources/SCREEN1.USA
|
||||
0COMMON.TXT out/iso/0COMMON.TXT
|
||||
5COMMON.TXT out/iso/5COMMON.TXT
|
||||
0TEST.TXT out/iso/0TEST.TXT
|
||||
VI1.DGO out/iso/VI1.DGO
|
@ -4,51 +4,129 @@
|
||||
*/
|
||||
|
||||
#include "display.h"
|
||||
#include "gfx.h"
|
||||
|
||||
#include "common/log/log.h"
|
||||
|
||||
namespace Display {
|
||||
/* ****************************** */
|
||||
/* Internal functions */
|
||||
/* ****************************** */
|
||||
|
||||
GLFWwindow* display = NULL;
|
||||
namespace {
|
||||
|
||||
void InitDisplay(int width, int height, const char* title, GLFWwindow*& d) {
|
||||
if (d) {
|
||||
lg::warn("InitDisplay has already created a display window");
|
||||
return;
|
||||
}
|
||||
|
||||
// request OpenGL 3.0 (placeholder)
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
d = glfwCreateWindow(width, height, title, NULL, NULL);
|
||||
|
||||
if (!d) {
|
||||
lg::error("InitDisplay failed - Could not create display window");
|
||||
return;
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent(d);
|
||||
if (!gladLoadGL()) {
|
||||
lg::error("GL init fail");
|
||||
KillDisplay(d);
|
||||
return;
|
||||
}
|
||||
|
||||
// enable vsync by default
|
||||
glfwSwapInterval(1);
|
||||
|
||||
lg::debug("init display #x{}", (uintptr_t)d);
|
||||
bool renderer_is_correct(const GfxRendererModule* renderer, GfxPipeline pipeline) {
|
||||
return renderer->pipeline == pipeline;
|
||||
}
|
||||
|
||||
void KillDisplay(GLFWwindow*& d) {
|
||||
lg::debug("kill display #x{}", (uintptr_t)d);
|
||||
if (!d) {
|
||||
lg::warn("KillDisplay called when no display was available");
|
||||
void set_main_display(std::shared_ptr<GfxDisplay> display) {
|
||||
if (Display::g_displays.size() > 0) {
|
||||
Display::g_displays[0] = display;
|
||||
} else {
|
||||
Display::g_displays.push_back(display);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/* ****************************** */
|
||||
/* GfxDisplay */
|
||||
/* ****************************** */
|
||||
|
||||
GfxDisplay::GfxDisplay(GLFWwindow* a_window) {
|
||||
set_renderer(GfxPipeline::OpenGL);
|
||||
set_window(a_window);
|
||||
}
|
||||
|
||||
GfxDisplay::~GfxDisplay() {
|
||||
m_renderer->kill_display(this);
|
||||
// window_generic_ptr = nullptr;
|
||||
}
|
||||
|
||||
void GfxDisplay::set_renderer(GfxPipeline pipeline) {
|
||||
if (is_active()) {
|
||||
lg::error("Can't change display's renderer while window exists.");
|
||||
return;
|
||||
}
|
||||
if (m_renderer != nullptr) {
|
||||
lg::error("A display changed renderer unexpectedly.");
|
||||
return;
|
||||
}
|
||||
|
||||
glfwDestroyWindow(d);
|
||||
d = NULL;
|
||||
m_renderer = Gfx::GetRenderer(pipeline);
|
||||
}
|
||||
|
||||
void GfxDisplay::set_window(GLFWwindow* window) {
|
||||
if (!renderer_is_correct(m_renderer, GfxPipeline::OpenGL)) {
|
||||
lg::error("Can't set OpenGL window when using {}", m_renderer->name);
|
||||
return;
|
||||
}
|
||||
if (is_active()) {
|
||||
lg::error("Already have a window. Close window first.");
|
||||
return;
|
||||
}
|
||||
|
||||
this->window_glfw = window;
|
||||
}
|
||||
|
||||
void GfxDisplay::set_title(const char* title) {
|
||||
if (!is_active()) {
|
||||
lg::error("No window to set title `{}`.", title);
|
||||
return;
|
||||
}
|
||||
|
||||
m_title = title;
|
||||
}
|
||||
|
||||
void GfxDisplay::render_graphics() {
|
||||
m_renderer->render_display(this);
|
||||
}
|
||||
|
||||
/* ****************************** */
|
||||
/* DISPLAY */
|
||||
/* ****************************** */
|
||||
|
||||
namespace Display {
|
||||
|
||||
std::vector<std::shared_ptr<GfxDisplay>> g_displays;
|
||||
std::shared_ptr<GfxDisplay> GetMainDisplay() {
|
||||
if (g_displays.size() == 0)
|
||||
return NULL;
|
||||
return g_displays.front()->is_active() ? g_displays.front() : NULL;
|
||||
}
|
||||
|
||||
int InitMainDisplay(int width, int height, const char* title, GfxSettings& settings) {
|
||||
if (GetMainDisplay() != NULL) {
|
||||
lg::warn("InitMainDisplay called when main display already exists.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto display = settings.renderer->make_main_display(width, height, title, settings);
|
||||
if (display == NULL) {
|
||||
lg::error("Failed to make main display.");
|
||||
return 1;
|
||||
}
|
||||
set_main_display(display);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void KillDisplay(std::shared_ptr<GfxDisplay> display) {
|
||||
// lg::debug("kill display #x{:x}", (uintptr_t)display);
|
||||
if (!display->is_active()) {
|
||||
lg::warn("display #x{:x} cant be killed because it is not active");
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetMainDisplay() == display) {
|
||||
// killing the main display, kill all children displays too!
|
||||
for (size_t i = 1; i < g_displays.size(); ++i) {
|
||||
KillDisplay(g_displays.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
// find this display in the list and remove it from there
|
||||
// if everything went right the smart pointer should delete the display.
|
||||
auto this_disp = std::find(g_displays.begin(), g_displays.end(), display);
|
||||
g_displays.erase(this_disp);
|
||||
}
|
||||
|
||||
} // namespace Display
|
||||
|
@ -5,21 +5,48 @@
|
||||
* Display for graphics. This is the game window, distinct from the runtime console.
|
||||
*/
|
||||
|
||||
#ifndef RUNTIME_DISPLAY_H
|
||||
#define RUNTIME_DISPLAY_H
|
||||
#include "pipelines/opengl.h"
|
||||
#include "gfx.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "opengl.h"
|
||||
// a GfxDisplay class is equivalent to a window that displays stuff. This holds an actual internal
|
||||
// window pointer used by whichever renderer. It also contains functions for setting and
|
||||
// retrieving certain window parameters.
|
||||
class GfxDisplay {
|
||||
const char* m_title;
|
||||
|
||||
const GfxRendererModule* m_renderer = nullptr;
|
||||
|
||||
public:
|
||||
GfxDisplay(GLFWwindow* a_window); // OpenGL window constructor
|
||||
~GfxDisplay(); // destructor - this calls the renderer's function for getting rid of a window,
|
||||
// and we can then get rid of the GfxDisplay itself
|
||||
|
||||
// all kinds of windows for the display
|
||||
union {
|
||||
void* window_generic_ptr = nullptr;
|
||||
GLFWwindow* window_glfw;
|
||||
};
|
||||
|
||||
bool is_active() const { return window_generic_ptr != nullptr; }
|
||||
void set_renderer(GfxPipeline pipeline);
|
||||
void set_window(GLFWwindow* window);
|
||||
void set_title(const char* title);
|
||||
const char* get_title() const { return m_title; }
|
||||
|
||||
void render_graphics();
|
||||
};
|
||||
|
||||
namespace Display {
|
||||
|
||||
// TODO - eventually we might actually want to support having multiple windows and viewpoints
|
||||
// so it would be nice if we didn't end up designing this system such that this MUST be
|
||||
// a single window.
|
||||
extern GLFWwindow* display;
|
||||
// a list of displays. the first one is the "main" display, all others are spectator-like extra
|
||||
// views.
|
||||
extern std::vector<std::shared_ptr<GfxDisplay>> g_displays;
|
||||
|
||||
void InitDisplay(int width, int height, const char* title, GLFWwindow*& d);
|
||||
void KillDisplay(GLFWwindow*& d);
|
||||
int InitMainDisplay(int width, int height, const char* title, GfxSettings& settings);
|
||||
void KillDisplay(std::shared_ptr<GfxDisplay> display);
|
||||
|
||||
std::shared_ptr<GfxDisplay> GetMainDisplay();
|
||||
|
||||
} // namespace Display
|
||||
|
||||
#endif // RUNTIME_DISPLAY_H
|
||||
|
90
game/graphics/dma/dma.cpp
Normal file
90
game/graphics/dma/dma.cpp
Normal file
@ -0,0 +1,90 @@
|
||||
#include "dma.h"
|
||||
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "common/util/assert.h"
|
||||
|
||||
std::string DmaTag::print() {
|
||||
std::string result;
|
||||
const char* mode_names[8] = {"refe", "cnt", "next", "ref", "refs", "call", "ret", "end"};
|
||||
result += fmt::format("TAG: 0x{:08x} {:4s} qwc 0x{:04x}", addr, mode_names[(int)kind], qwc);
|
||||
if (spr) {
|
||||
result += " SPR";
|
||||
}
|
||||
result += "\n";
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string VifCode::print() {
|
||||
std::string result;
|
||||
|
||||
switch (kind) {
|
||||
case Kind::NOP:
|
||||
result = "NOP";
|
||||
break;
|
||||
case Kind::STCYCL:
|
||||
result = "STCYCL";
|
||||
break;
|
||||
case Kind::OFFSET:
|
||||
result = "OFFSET";
|
||||
break;
|
||||
case Kind::BASE:
|
||||
result = "BASE";
|
||||
break;
|
||||
case Kind::ITOP:
|
||||
result = "ITOP";
|
||||
break;
|
||||
case Kind::STMOD:
|
||||
result = "STMOD";
|
||||
break;
|
||||
case Kind::MSK3PATH:
|
||||
result = "MSK3PATH";
|
||||
break;
|
||||
case Kind::MARK:
|
||||
result = "MARK";
|
||||
break;
|
||||
case Kind::FLUSHE:
|
||||
result = "FLUSHE";
|
||||
break;
|
||||
case Kind::FLUSH:
|
||||
result = "FLUSH";
|
||||
break;
|
||||
|
||||
case Kind::FLUSHA:
|
||||
result = "FLUSHA";
|
||||
break;
|
||||
case Kind::MSCAL:
|
||||
result = "MSCAL";
|
||||
break;
|
||||
case Kind::MSCNT:
|
||||
result = "MSCNT";
|
||||
break;
|
||||
case Kind::MSCALF:
|
||||
result = "MSCALF";
|
||||
break;
|
||||
case Kind::STMASK:
|
||||
result = "STMASK";
|
||||
break;
|
||||
case Kind::STROW:
|
||||
result = "STROW";
|
||||
break;
|
||||
case Kind::STCOL:
|
||||
result = "STCOL";
|
||||
break;
|
||||
case Kind::MPG:
|
||||
result = "MPG";
|
||||
break;
|
||||
case Kind::DIRECT:
|
||||
result = "DIRECT";
|
||||
break;
|
||||
case Kind::DIRECTHL:
|
||||
result = "DIRECTHL";
|
||||
break;
|
||||
default:
|
||||
fmt::print("Unhandled vif code {}", (int)kind);
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
// TODO: the rest of the VIF code.
|
||||
|
||||
return result;
|
||||
}
|
78
game/graphics/dma/dma.h
Normal file
78
game/graphics/dma/dma.h
Normal file
@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
/*!
|
||||
* @file dma.h
|
||||
* PS2 DMA and VIF types.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include "common/util/assert.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
struct DmaTag {
|
||||
enum class Kind : u8 {
|
||||
REFE = 0,
|
||||
CNT = 1,
|
||||
NEXT = 2,
|
||||
REF = 3,
|
||||
REFS = 4,
|
||||
CALL = 5,
|
||||
RET = 6,
|
||||
END = 7
|
||||
};
|
||||
|
||||
DmaTag(u64 value) {
|
||||
spr = (value >> 63);
|
||||
addr = (value >> 32) & 0x7fffffff;
|
||||
qwc = value & 0xfff;
|
||||
kind = Kind((value >> 28) & 0b111);
|
||||
}
|
||||
|
||||
u16 qwc = 0;
|
||||
u32 addr = 0;
|
||||
bool spr = false;
|
||||
Kind kind;
|
||||
|
||||
std::string print();
|
||||
};
|
||||
|
||||
struct VifCode {
|
||||
enum class Kind : u8 {
|
||||
NOP = 0b0,
|
||||
STCYCL = 0b1,
|
||||
OFFSET = 0b10,
|
||||
BASE = 0b11,
|
||||
ITOP = 0b100,
|
||||
STMOD = 0b101,
|
||||
MSK3PATH = 0b110,
|
||||
MARK = 0b111,
|
||||
FLUSHE = 0b10000,
|
||||
FLUSH = 0b10001,
|
||||
FLUSHA = 0b10011,
|
||||
MSCAL = 0b10100,
|
||||
MSCNT = 0b10111,
|
||||
MSCALF = 0b10101,
|
||||
STMASK = 0b100000,
|
||||
STROW = 0b110000,
|
||||
STCOL = 0b110001,
|
||||
MPG = 0b1001010,
|
||||
DIRECT = 0b1010000,
|
||||
DIRECTHL = 0b1010001,
|
||||
UNPACK_MASK = 0b1100000 // unpack is a bunch of commands.
|
||||
};
|
||||
|
||||
VifCode(u32 value) {
|
||||
interrupt = (value) & (1 << 31);
|
||||
kind = (Kind)((value >> 24) & 0b111'1111);
|
||||
num = (value >> 16) & 0xff;
|
||||
immediate = value & 0xffff;
|
||||
}
|
||||
|
||||
bool interrupt = false;
|
||||
Kind kind;
|
||||
u16 num;
|
||||
u16 immediate;
|
||||
|
||||
std::string print();
|
||||
};
|
108
game/graphics/dma/dma_chain_read.h
Normal file
108
game/graphics/dma/dma_chain_read.h
Normal file
@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include "game/graphics/dma/dma.h"
|
||||
#include "common/util/assert.h"
|
||||
|
||||
/*!
|
||||
* @file dma_chain_read.h
|
||||
* This file contains utilities for reading/following a DMA chain.
|
||||
*
|
||||
* This allows you to iterate through transfers like this:
|
||||
*
|
||||
* DmaFollower dma(mem, start_addr);
|
||||
* while (!reader.ended) {
|
||||
* DmaTransfer transfer = reader.advance_to_next_transfer();
|
||||
* // do something with the data in transfer.
|
||||
* }
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Represents a DMA transfer, including 64-bits of VIF tag.
|
||||
*/
|
||||
struct DmaTransfer {
|
||||
const u8* data = nullptr;
|
||||
u32 data_offset = 0;
|
||||
u32 size_bytes = 0;
|
||||
u64 transferred_tag = 0;
|
||||
|
||||
u32 vif0() const { return transferred_tag & 0xffffffff; }
|
||||
u32 vif1() const { return (transferred_tag >> 32) & 0xffffffff; }
|
||||
};
|
||||
|
||||
class DmaFollower {
|
||||
public:
|
||||
DmaFollower() { m_ended = true; }
|
||||
DmaFollower(const void* data, u32 start_offset) : m_base(data), m_tag_offset(start_offset) {}
|
||||
template <typename T>
|
||||
T read_val(u32 offset) const {
|
||||
T result;
|
||||
memcpy(&result, (const u8*)m_base + offset, sizeof(T));
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Read the current tag, return its transfer, then advance to the next.
|
||||
*/
|
||||
DmaTransfer read_and_advance() {
|
||||
DmaTag tag(read_val<u64>(m_tag_offset));
|
||||
DmaTransfer result;
|
||||
result.transferred_tag = read_val<u64>(m_tag_offset + 8);
|
||||
result.size_bytes = (u32)tag.qwc * 16;
|
||||
assert(!tag.spr);
|
||||
assert(!m_ended);
|
||||
switch (tag.kind) {
|
||||
case DmaTag::Kind::CNT:
|
||||
// data, then next tag. doesn't read address.
|
||||
assert(tag.addr == 0);
|
||||
result.data_offset = m_tag_offset + 16;
|
||||
m_tag_offset = result.data_offset + result.size_bytes;
|
||||
break;
|
||||
case DmaTag::Kind::NEXT:
|
||||
result.data_offset = m_tag_offset + 16;
|
||||
m_tag_offset = tag.addr;
|
||||
break;
|
||||
case DmaTag::Kind::REF:
|
||||
case DmaTag::Kind::REFS:
|
||||
result.data_offset = tag.addr;
|
||||
m_tag_offset = m_tag_offset + 16;
|
||||
break;
|
||||
case DmaTag::Kind::REFE:
|
||||
result.data_offset = tag.addr;
|
||||
m_tag_offset = m_tag_offset + 16;
|
||||
m_ended = true;
|
||||
break;
|
||||
case DmaTag::Kind::CALL:
|
||||
result.data_offset = m_tag_offset + 16;
|
||||
assert(m_sp <= 1);
|
||||
m_tag_offset = tag.addr;
|
||||
m_stack[m_sp++] = result.data_offset + tag.qwc * 16;
|
||||
break;
|
||||
case DmaTag::Kind::RET:
|
||||
assert(m_sp > 0);
|
||||
result.data_offset = m_tag_offset + 16;
|
||||
m_tag_offset = m_stack[--m_sp];
|
||||
break;
|
||||
case DmaTag::Kind::END:
|
||||
result.data_offset = m_tag_offset + 16;
|
||||
m_ended = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
result.data = (const u8*)m_base + result.data_offset;
|
||||
return result;
|
||||
}
|
||||
|
||||
DmaTag current_tag() const { return DmaTag(read_val<u64>(m_tag_offset)); }
|
||||
u32 current_tag_offset() const { return m_tag_offset; }
|
||||
bool ended() const { return m_ended; }
|
||||
|
||||
private:
|
||||
const void* m_base = nullptr;
|
||||
u32 m_tag_offset = 0;
|
||||
s32 m_sp = 0;
|
||||
s32 m_stack[2] = {-1, -1};
|
||||
bool m_ended = false;
|
||||
};
|
123
game/graphics/dma/dma_copy.cpp
Normal file
123
game/graphics/dma/dma_copy.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
|
||||
#include "game/graphics/dma/dma_chain_read.h"
|
||||
#include "dma_copy.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
/*!
|
||||
* Convert a DMA chain to an array of bytes that can be directly fed to VIF.
|
||||
*/
|
||||
std::vector<u8> flatten_dma(const DmaFollower& in) {
|
||||
DmaFollower state = in;
|
||||
std::vector<u8> result;
|
||||
while (!state.ended()) {
|
||||
auto read_result = state.read_and_advance();
|
||||
|
||||
// insert 1 quadword of zeros.
|
||||
// the first 8 bytes will remain zero and will be interpreted as VIF nop.
|
||||
for (int i = 0; i < 16; i++) {
|
||||
result.push_back(0);
|
||||
}
|
||||
|
||||
// the second will contain the transferred tag.
|
||||
memcpy(result.data() + result.size() - 8, &read_result.transferred_tag, 8);
|
||||
|
||||
// and the actual DMA data.
|
||||
result.insert(result.end(), read_result.data, read_result.data + read_result.size_bytes);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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.
|
||||
assert(chunk_size >= 16);
|
||||
m_chunk_mask.resize(m_chunk_count);
|
||||
}
|
||||
|
||||
const DmaData& FixedChunkDmaCopier::run(const void* memory, u32 offset, bool verify) {
|
||||
std::fill(m_chunk_mask.begin(), m_chunk_mask.end(), false);
|
||||
m_fixups.clear();
|
||||
m_result.data.clear();
|
||||
m_result.start_offset = 0;
|
||||
|
||||
DmaFollower dma(memory, offset);
|
||||
while (!dma.ended()) {
|
||||
auto tag_offset = dma.current_tag_offset();
|
||||
auto tag = dma.current_tag();
|
||||
|
||||
// first, make sure we get this tag:
|
||||
u32 tag_chunk_idx = tag_offset / chunk_size;
|
||||
u32 tag_offset_in_chunk = tag_offset % chunk_size;
|
||||
m_chunk_mask.at(tag_chunk_idx) = true;
|
||||
|
||||
if (tag.addr) {
|
||||
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)
|
||||
m_chunk_mask.at(addr_chunk_idx) = true;
|
||||
// and add a fixup
|
||||
Fixup fu;
|
||||
fu.source_chunk = tag_chunk_idx;
|
||||
fu.offset_in_source_chunk = tag_offset_in_chunk;
|
||||
fu.dest_chunk = addr_chunk_idx;
|
||||
fu.offset_in_dest_chunk = addr_offset_in_chunk;
|
||||
m_fixups.push_back(fu);
|
||||
}
|
||||
|
||||
auto transfer = dma.read_and_advance();
|
||||
if (transfer.size_bytes) {
|
||||
u32 initial_chunk = transfer.data_offset / chunk_size;
|
||||
m_chunk_mask.at(initial_chunk) = true;
|
||||
s32 bytes_remaining = transfer.size_bytes;
|
||||
bytes_remaining -= chunk_size - (transfer.size_bytes % chunk_size);
|
||||
u32 chunk = initial_chunk + 1;
|
||||
while (bytes_remaining >= 0) {
|
||||
bytes_remaining -= transfer.size_bytes;
|
||||
m_chunk_mask.at(chunk) = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// assign output chunks.
|
||||
u32 current_out_chunk = 0;
|
||||
for (auto& val : m_chunk_mask) {
|
||||
if (val) {
|
||||
val = current_out_chunk++;
|
||||
} else {
|
||||
val = UINT32_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
m_result.data.resize(current_out_chunk * chunk_size);
|
||||
|
||||
// copy
|
||||
for (u32 chunk_idx = 0; chunk_idx < m_chunk_mask.size(); chunk_idx++) {
|
||||
u32 dest_idx = m_chunk_mask[chunk_idx];
|
||||
if (dest_idx != UINT32_MAX) {
|
||||
memcpy(m_result.data.data() + (dest_idx * chunk_size),
|
||||
(const u8*)memory + (chunk_idx * chunk_size), chunk_size);
|
||||
}
|
||||
}
|
||||
|
||||
// fix up!
|
||||
for (const auto& fu : m_fixups) {
|
||||
u32 tag_addr = m_chunk_mask.at(fu.source_chunk) * chunk_size + fu.offset_in_source_chunk + 4;
|
||||
u32 dest_addr = m_chunk_mask.at(fu.dest_chunk) * chunk_size + fu.offset_in_dest_chunk;
|
||||
memcpy(m_result.data.data() + tag_addr, &dest_addr, 4);
|
||||
}
|
||||
|
||||
// setup final offset
|
||||
m_result.start_offset = m_chunk_mask.at(offset / chunk_size) * chunk_size + (offset % chunk_size);
|
||||
|
||||
if (verify) {
|
||||
auto ref = flatten_dma(DmaFollower(memory, offset));
|
||||
auto v2 = flatten_dma(DmaFollower(m_result.data.data(), m_result.start_offset));
|
||||
if (ref != v2) {
|
||||
fmt::print("Verification has failed.\n");
|
||||
} else {
|
||||
fmt::print("verification ok: {} bytes\n", ref.size());
|
||||
}
|
||||
}
|
||||
|
||||
return m_result;
|
||||
}
|
44
game/graphics/dma/dma_copy.h
Normal file
44
game/graphics/dma/dma_copy.h
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "game/graphics/dma/dma_chain_read.h"
|
||||
|
||||
struct DmaData {
|
||||
u32 start_offset = 0;
|
||||
std::vector<u8> data;
|
||||
};
|
||||
|
||||
/*!
|
||||
* The fixed chunk copier considers the game's main memory as an array of fixed sized chunks.
|
||||
* Only the chunks that have dma data are included in the copy.
|
||||
* The result is cached internally.
|
||||
*/
|
||||
class FixedChunkDmaCopier {
|
||||
public:
|
||||
static constexpr u32 chunk_size = 0x20000; // 128 kB, gives use 1024 chunks for a 128 MB RAM.
|
||||
FixedChunkDmaCopier(u32 main_memory_size);
|
||||
|
||||
const DmaData& run(const void* memory, u32 offset, bool verify = false);
|
||||
const DmaData& get_last_result() const { return m_result; }
|
||||
|
||||
private:
|
||||
struct Fixup {
|
||||
u32 source_chunk;
|
||||
u32 offset_in_source_chunk;
|
||||
u32 dest_chunk;
|
||||
u32 offset_in_dest_chunk;
|
||||
};
|
||||
std::vector<Fixup> m_fixups;
|
||||
|
||||
u32 m_main_memory_size = 0;
|
||||
u32 m_chunk_count = 0;
|
||||
std::vector<u32> m_chunk_mask;
|
||||
DmaData m_result;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Convert a DMA chain to an array of bytes that can be directly fed to VIF.
|
||||
*/
|
||||
std::vector<u8> flatten_dma(const DmaFollower& in);
|
351
game/graphics/dma/gs.cpp
Normal file
351
game/graphics/dma/gs.cpp
Normal file
@ -0,0 +1,351 @@
|
||||
#include "gs.h"
|
||||
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "common/util/assert.h"
|
||||
|
||||
std::string reg_descriptor_name(GifTag::RegisterDescriptor reg) {
|
||||
switch (reg) {
|
||||
case GifTag::RegisterDescriptor::PRIM:
|
||||
return "PRIM";
|
||||
case GifTag::RegisterDescriptor::RGBAQ:
|
||||
return "RGBAQ";
|
||||
case GifTag::RegisterDescriptor::ST:
|
||||
return "ST";
|
||||
case GifTag::RegisterDescriptor::UV:
|
||||
return "UV";
|
||||
case GifTag::RegisterDescriptor::XYZF2:
|
||||
return "XYZF2";
|
||||
case GifTag::RegisterDescriptor::XYZ2:
|
||||
return "XYZ2";
|
||||
case GifTag::RegisterDescriptor::TEX0_1:
|
||||
return "TEX0_1";
|
||||
case GifTag::RegisterDescriptor::TEX0_2:
|
||||
return "TEX0_2";
|
||||
case GifTag::RegisterDescriptor::CLAMP_1:
|
||||
return "CLAMP_1";
|
||||
case GifTag::RegisterDescriptor::CLAMP_2:
|
||||
return "CLAMP_2";
|
||||
case GifTag::RegisterDescriptor::FOG:
|
||||
return "FOG";
|
||||
case GifTag::RegisterDescriptor::XYZF3:
|
||||
return "XYZF3";
|
||||
case GifTag::RegisterDescriptor::XYZ3:
|
||||
return "XYZ3";
|
||||
case GifTag::RegisterDescriptor::AD:
|
||||
return "A+D";
|
||||
case GifTag::RegisterDescriptor::NOP:
|
||||
return "NOP";
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
std::string GifTag::print() const {
|
||||
std::string result;
|
||||
switch (flg()) {
|
||||
case Format::PACKED:
|
||||
result += "packed ";
|
||||
break;
|
||||
case Format::REGLIST:
|
||||
result += "reglist ";
|
||||
break;
|
||||
case Format::IMAGE:
|
||||
result += "image ";
|
||||
return result;
|
||||
case Format::DISABLE:
|
||||
result += "disable ";
|
||||
return result;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
result += fmt::format("nloop: {} ", nloop());
|
||||
|
||||
if (pre()) {
|
||||
result += fmt::format("prim: 0x{:x} ", prim());
|
||||
}
|
||||
|
||||
if (eop()) {
|
||||
result += "eop ";
|
||||
}
|
||||
|
||||
result += '\n';
|
||||
result += ' ';
|
||||
|
||||
for (u32 i = 0; i < nreg(); i++) {
|
||||
result += reg_descriptor_name(reg(i));
|
||||
result += ' ';
|
||||
}
|
||||
result += "\n";
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string register_address_name(GsRegisterAddress reg) {
|
||||
switch (reg) {
|
||||
case GsRegisterAddress::PRIM:
|
||||
return "PRIM";
|
||||
case GsRegisterAddress::RGBAQ:
|
||||
return "RGBAQ";
|
||||
case GsRegisterAddress::ST:
|
||||
return "ST";
|
||||
case GsRegisterAddress::UV:
|
||||
return "UV";
|
||||
case GsRegisterAddress::XYZF2:
|
||||
return "XYZF2";
|
||||
case GsRegisterAddress::XYZ2:
|
||||
return "XYZ2";
|
||||
case GsRegisterAddress::TEX0_1:
|
||||
return "TEX0_1";
|
||||
case GsRegisterAddress::TEX0_2:
|
||||
return "TEX0_2";
|
||||
case GsRegisterAddress::CLAMP_1:
|
||||
return "CLAMP_1";
|
||||
case GsRegisterAddress::CLAMP_2:
|
||||
return "CLAMP_2";
|
||||
case GsRegisterAddress::FOG:
|
||||
return "FOG";
|
||||
case GsRegisterAddress::XYZF3:
|
||||
return "XYZF3";
|
||||
case GsRegisterAddress::XYZ3:
|
||||
return "XYZ3";
|
||||
case GsRegisterAddress::TEX1_1:
|
||||
return "TEX1_1";
|
||||
case GsRegisterAddress::TEX1_2:
|
||||
return "TEX1_2";
|
||||
case GsRegisterAddress::TEX2_1:
|
||||
return "TEX2_1";
|
||||
case GsRegisterAddress::TEX2_2:
|
||||
return "TEX2_2";
|
||||
case GsRegisterAddress::XYOFFSET_1:
|
||||
return "XYOFFSET_1";
|
||||
case GsRegisterAddress::XYOFFSET_2:
|
||||
return "XYOFFSET_2";
|
||||
case GsRegisterAddress::PRMODECONT:
|
||||
return "PRMODECONT";
|
||||
case GsRegisterAddress::PRMODE:
|
||||
return "PRMODE";
|
||||
case GsRegisterAddress::TEXCLUT:
|
||||
return "TEXCLUT";
|
||||
case GsRegisterAddress::SCANMSK:
|
||||
return "SCANMSK";
|
||||
case GsRegisterAddress::MIPTBP1_1:
|
||||
return "MIPTBP1_1";
|
||||
case GsRegisterAddress::MIPTBP1_2:
|
||||
return "MIPTBP1_2";
|
||||
case GsRegisterAddress::MIPTBP2_1:
|
||||
return "MIPTBP2_1";
|
||||
case GsRegisterAddress::MIPTBP2_2:
|
||||
return "MIPTBP2_2";
|
||||
case GsRegisterAddress::TEXA:
|
||||
return "TEXA";
|
||||
case GsRegisterAddress::FOGCOL:
|
||||
return "FOGCOL";
|
||||
case GsRegisterAddress::TEXFLUSH:
|
||||
return "TEXFLUSH";
|
||||
case GsRegisterAddress::SCISSOR_1:
|
||||
return "SCISSOR_1";
|
||||
case GsRegisterAddress::SCISSOR_2:
|
||||
return "SCISSOR_2";
|
||||
case GsRegisterAddress::ALPHA_1:
|
||||
return "ALPHA_1";
|
||||
case GsRegisterAddress::ALPHA_2:
|
||||
return "ALPHA_2";
|
||||
case GsRegisterAddress::DIMX:
|
||||
return "DIMX";
|
||||
case GsRegisterAddress::DTHE:
|
||||
return "DTHE";
|
||||
case GsRegisterAddress::COLCLAMP:
|
||||
return "COLCLAMP";
|
||||
case GsRegisterAddress::TEST_1:
|
||||
return "TEST_1";
|
||||
case GsRegisterAddress::TEST_2:
|
||||
return "TEST_2";
|
||||
case GsRegisterAddress::PABE:
|
||||
return "PABE";
|
||||
case GsRegisterAddress::FBA_1:
|
||||
return "FBA_1";
|
||||
case GsRegisterAddress::FBA_2:
|
||||
return "FBA_2";
|
||||
case GsRegisterAddress::FRAME_1:
|
||||
return "FRAME_1";
|
||||
case GsRegisterAddress::FRAME_2:
|
||||
return "FRAME_2";
|
||||
case GsRegisterAddress::ZBUF_1:
|
||||
return "ZBUF_1";
|
||||
case GsRegisterAddress::ZBUF_2:
|
||||
return "ZBUF_2";
|
||||
case GsRegisterAddress::BITBLTBUF:
|
||||
return "BITBLTBUF";
|
||||
case GsRegisterAddress::TRXPOS:
|
||||
return "TRXPOS";
|
||||
case GsRegisterAddress::TRXREG:
|
||||
return "TRXREG";
|
||||
case GsRegisterAddress::TRXDIR:
|
||||
return "TRXDIR";
|
||||
case GsRegisterAddress::HWREG:
|
||||
return "HWREG";
|
||||
case GsRegisterAddress::SIGNAL:
|
||||
return "SIGNAL";
|
||||
case GsRegisterAddress::FINISH:
|
||||
return "FINISH";
|
||||
case GsRegisterAddress::LABEL:
|
||||
return "LABEL";
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
std::string GsTest::print() const {
|
||||
std::string result;
|
||||
if (alpha_test_enable()) {
|
||||
result += "alpha-test: ";
|
||||
switch (alpha_test()) {
|
||||
case AlphaTest::NEVER:
|
||||
result += "NEVER ";
|
||||
break;
|
||||
case AlphaTest::ALWAYS:
|
||||
result += "ALWAYS ";
|
||||
break;
|
||||
case AlphaTest::LESS:
|
||||
result += "LESS ";
|
||||
break;
|
||||
case AlphaTest::LEQUAL:
|
||||
result += "LEQUAL ";
|
||||
break;
|
||||
case AlphaTest::EQUAL:
|
||||
result += "EQUAL ";
|
||||
break;
|
||||
case AlphaTest::GEQUAL:
|
||||
result += "GEQUAL ";
|
||||
break;
|
||||
case AlphaTest::GREATER:
|
||||
result += "GREATER ";
|
||||
break;
|
||||
case AlphaTest::NOTEQUAL:
|
||||
result += "NOTEQUAL ";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
result += fmt::format("ref: 0x{:x} alpha-fail: ", aref());
|
||||
switch (afail()) {
|
||||
case AlphaFail::KEEP:
|
||||
result += "KEEP ";
|
||||
break;
|
||||
case AlphaFail::FB_ONLY:
|
||||
result += "FB_ONLY ";
|
||||
break;
|
||||
case AlphaFail::ZB_ONLY:
|
||||
result += "ZB_ONLY ";
|
||||
break;
|
||||
case AlphaFail::RGB_ONLY:
|
||||
result += "RGB_ONLY ";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (date()) {
|
||||
result += fmt::format("dest-alpha-mode {} ", (int)datm());
|
||||
}
|
||||
|
||||
if (zte()) {
|
||||
result += fmt::format("ztest: ");
|
||||
switch (ztest()) {
|
||||
case ZTest::NEVER:
|
||||
result += "NEVER";
|
||||
break;
|
||||
case ZTest::ALWAYS:
|
||||
result += "ALWAYS";
|
||||
break;
|
||||
case ZTest::GEQUAL:
|
||||
result += "GEQUAL";
|
||||
break;
|
||||
case ZTest::GREATER:
|
||||
result += "GREATER";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string GsAlpha::print() const {
|
||||
std::string result = "(";
|
||||
switch (a_mode()) {
|
||||
case BlendMode::SOURCE:
|
||||
result += "Cs ";
|
||||
break;
|
||||
case BlendMode::DEST:
|
||||
result += "Cd ";
|
||||
break;
|
||||
case BlendMode::ZERO_OR_FIXED:
|
||||
result += "0 ";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
switch (b_mode()) {
|
||||
case BlendMode::SOURCE:
|
||||
result += "- Cs) * ";
|
||||
break;
|
||||
case BlendMode::DEST:
|
||||
result += "- Cd) * ";
|
||||
break;
|
||||
case BlendMode::ZERO_OR_FIXED:
|
||||
result += "- 0) * ";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
switch (c_mode()) {
|
||||
case BlendMode::SOURCE:
|
||||
result += "As / 128.0";
|
||||
break;
|
||||
case BlendMode::DEST:
|
||||
result += "Ad / 128.0";
|
||||
break;
|
||||
case BlendMode::ZERO_OR_FIXED: {
|
||||
float div = fix();
|
||||
div /= 128.0;
|
||||
result += fmt::format("{:.4f}", div);
|
||||
} break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
switch (d_mode()) {
|
||||
case BlendMode::SOURCE:
|
||||
result += " + Cs";
|
||||
break;
|
||||
case BlendMode::DEST:
|
||||
result += " + Cd";
|
||||
break;
|
||||
case BlendMode::ZERO_OR_FIXED:
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string GsTex1::print() const {
|
||||
return fmt::format("lcm: {} mxl: {} mmag: {} mmin: {} mtba: {} l: {} k: {}\n", lcm(), mxl(),
|
||||
mmag(), mmin(), mtba(), l(), k());
|
||||
}
|
||||
|
||||
std::string GsTexa::print() const {
|
||||
return fmt::format("ta0: {} aem: {} ta1: {}\n", ta0(), aem(), ta1());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
328
game/graphics/dma/gs.h
Normal file
328
game/graphics/dma/gs.h
Normal file
@ -0,0 +1,328 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/graphics/dma/dma.h"
|
||||
|
||||
/*!
|
||||
* @file gs.h
|
||||
* PS2 GS/GIF hardware types
|
||||
*/
|
||||
|
||||
struct GifTag {
|
||||
enum class Format : u8 { PACKED = 0, REGLIST = 1, IMAGE = 2, DISABLE = 3 };
|
||||
|
||||
enum class RegisterDescriptor : u8 {
|
||||
PRIM = 0,
|
||||
RGBAQ = 1,
|
||||
ST = 2,
|
||||
UV = 3,
|
||||
XYZF2 = 4,
|
||||
XYZ2 = 5,
|
||||
TEX0_1 = 6,
|
||||
TEX0_2 = 7,
|
||||
CLAMP_1 = 8,
|
||||
CLAMP_2 = 9,
|
||||
FOG = 10,
|
||||
// no 11
|
||||
XYZF3 = 12,
|
||||
XYZ3 = 13,
|
||||
AD = 14,
|
||||
NOP = 15,
|
||||
};
|
||||
|
||||
u32 nloop() const {
|
||||
return data[0] & 0x7f; // 15 bits
|
||||
}
|
||||
|
||||
bool eop() const { return data[0] & (1ull << 15); }
|
||||
bool pre() const { return data[0] & (1ull << 46); }
|
||||
u32 prim() const { return (data[0] >> 47) & 0b111'1111'1111; }
|
||||
Format flg() const { return (Format)((data[0] >> 58) & 0b11); }
|
||||
|
||||
u32 nreg() const {
|
||||
u32 result = (data[0] >> 60) & 0b1111;
|
||||
if (!result) {
|
||||
return 16;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
RegisterDescriptor reg(u32 idx) const {
|
||||
return (RegisterDescriptor)((data[1] >> (4 * idx)) & 0b1111);
|
||||
}
|
||||
|
||||
std::string print() const;
|
||||
|
||||
GifTag(const u8* ptr) { memcpy(data, ptr, 16); }
|
||||
|
||||
u64 data[2];
|
||||
};
|
||||
|
||||
std::string reg_descriptor_name(GifTag::RegisterDescriptor reg);
|
||||
|
||||
enum class GsRegisterAddress : u8 {
|
||||
PRIM = 0,
|
||||
RGBAQ = 1,
|
||||
ST = 2,
|
||||
UV = 3,
|
||||
XYZF2 = 4,
|
||||
XYZ2 = 5,
|
||||
TEX0_1 = 6,
|
||||
TEX0_2 = 7,
|
||||
CLAMP_1 = 8,
|
||||
CLAMP_2 = 9,
|
||||
FOG = 0xa,
|
||||
XYZF3 = 0xc,
|
||||
XYZ3 = 0xd,
|
||||
TEX1_1 = 0x14,
|
||||
TEX1_2 = 0x15,
|
||||
TEX2_1 = 0x16,
|
||||
TEX2_2 = 0x17,
|
||||
XYOFFSET_1 = 0x18,
|
||||
XYOFFSET_2 = 0x19,
|
||||
PRMODECONT = 0x1a,
|
||||
PRMODE = 0x1b,
|
||||
TEXCLUT = 0x1c,
|
||||
SCANMSK = 0x22,
|
||||
MIPTBP1_1 = 0x34,
|
||||
MIPTBP1_2 = 0x35,
|
||||
MIPTBP2_1 = 0x36,
|
||||
MIPTBP2_2 = 0x37,
|
||||
TEXA = 0x3b,
|
||||
FOGCOL = 0x3d,
|
||||
TEXFLUSH = 0x3f,
|
||||
SCISSOR_1 = 0x40,
|
||||
SCISSOR_2 = 0x41,
|
||||
ALPHA_1 = 0x42,
|
||||
ALPHA_2 = 0x43,
|
||||
DIMX = 0x44,
|
||||
DTHE = 0x45,
|
||||
COLCLAMP = 0x46,
|
||||
TEST_1 = 0x47,
|
||||
TEST_2 = 0x48,
|
||||
PABE = 0x49,
|
||||
FBA_1 = 0x4a,
|
||||
FBA_2 = 0x4b,
|
||||
FRAME_1 = 0x4c,
|
||||
FRAME_2 = 0x4d,
|
||||
ZBUF_1 = 0x4e,
|
||||
ZBUF_2 = 0x4f,
|
||||
BITBLTBUF = 0x50,
|
||||
TRXPOS = 0x51,
|
||||
TRXREG = 0x52,
|
||||
TRXDIR = 0x53,
|
||||
HWREG = 0x54,
|
||||
SIGNAL = 0x60,
|
||||
FINISH = 0x61,
|
||||
LABEL = 0x62
|
||||
};
|
||||
|
||||
enum class TextureFormat { PSMZ32, PSMZ24, PSMZ16, PSMZ16S };
|
||||
|
||||
std::string register_address_name(GsRegisterAddress reg);
|
||||
|
||||
struct GsZbuf {
|
||||
GsZbuf(u64 val) : data(val) {}
|
||||
|
||||
u32 zbp() const { return data & 0b1'1111'1111; }
|
||||
|
||||
TextureFormat psm() const {
|
||||
u32 psm_field = (data >> 24) & 0b1111;
|
||||
switch (psm_field) {
|
||||
case 0b0000:
|
||||
return TextureFormat::PSMZ32;
|
||||
case 0b0001:
|
||||
return TextureFormat::PSMZ24;
|
||||
case 0b0010:
|
||||
return TextureFormat::PSMZ16;
|
||||
case 0b1010:
|
||||
return TextureFormat::PSMZ16S;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool zmsk() const { return data & (1ull << 32); }
|
||||
|
||||
u64 data;
|
||||
};
|
||||
|
||||
struct GsTest {
|
||||
GsTest() = default;
|
||||
GsTest(u64 val) : data(val) {}
|
||||
|
||||
bool alpha_test_enable() const { return data & 1; }
|
||||
|
||||
enum class AlphaTest : u8 {
|
||||
NEVER = 0,
|
||||
ALWAYS = 1,
|
||||
LESS = 2,
|
||||
LEQUAL = 3,
|
||||
EQUAL = 4,
|
||||
GEQUAL = 5,
|
||||
GREATER = 6,
|
||||
NOTEQUAL = 7
|
||||
};
|
||||
|
||||
AlphaTest alpha_test() const { return (AlphaTest)((data >> 1) & 0b111); }
|
||||
|
||||
u8 aref() const { return (data >> 4); }
|
||||
|
||||
enum class AlphaFail : u8 { KEEP = 0, FB_ONLY = 1, ZB_ONLY = 2, RGB_ONLY = 3 };
|
||||
|
||||
AlphaFail afail() const { return (AlphaFail)((data >> 12) & 0b11); }
|
||||
|
||||
bool date() const { return data & (1 << 14); }
|
||||
|
||||
bool datm() const { return data & (1 << 15); }
|
||||
|
||||
bool zte() const { return data & (1 << 16); }
|
||||
|
||||
enum class ZTest : u8 { NEVER = 0, ALWAYS = 1, GEQUAL = 2, GREATER = 3 };
|
||||
|
||||
ZTest ztest() const { return (ZTest)((data >> 17) & 0b11); }
|
||||
|
||||
std::string print() const;
|
||||
|
||||
u64 data = 0;
|
||||
|
||||
bool operator==(const GsTest& other) const { return data == other.data; }
|
||||
bool operator!=(const GsTest& other) const { return data != other.data; }
|
||||
};
|
||||
|
||||
struct GsAlpha {
|
||||
GsAlpha() = default;
|
||||
GsAlpha(u64 val) : data(val) {}
|
||||
|
||||
enum class BlendMode {
|
||||
SOURCE = 0,
|
||||
DEST = 1, // frame buffer
|
||||
ZERO_OR_FIXED = 2, // 0 for a, b, d, fixed for c
|
||||
INVALID = 3
|
||||
};
|
||||
|
||||
BlendMode a_mode() const { return (BlendMode)(data & 0b11); }
|
||||
BlendMode b_mode() const { return (BlendMode)((data >> 2) & 0b11); }
|
||||
BlendMode c_mode() const { return (BlendMode)((data >> 4) & 0b11); }
|
||||
BlendMode d_mode() const { return (BlendMode)((data >> 6) & 0b11); }
|
||||
u8 fix() const { return (data >> 32); }
|
||||
|
||||
std::string print() const;
|
||||
|
||||
u64 data = 0;
|
||||
bool operator==(const GsAlpha& other) const { return data == other.data; }
|
||||
bool operator!=(const GsAlpha& other) const { return data != other.data; }
|
||||
};
|
||||
|
||||
struct GsPrim {
|
||||
GsPrim() = default;
|
||||
GsPrim(u64 val) : data(val & 0b111'1111'1111) {}
|
||||
|
||||
enum class Kind {
|
||||
POINT = 0,
|
||||
LINE = 1,
|
||||
LINE_STRIP = 2,
|
||||
TRI = 3,
|
||||
TRI_STRIP = 4,
|
||||
TRI_FAN = 5,
|
||||
SPRITE = 6,
|
||||
PRIM_7 = 7
|
||||
};
|
||||
|
||||
Kind kind() const { return (Kind)(data & 0b111); }
|
||||
|
||||
bool gouraud() const { // iip
|
||||
return data & (1 << 3);
|
||||
}
|
||||
|
||||
bool tme() const { return data & (1 << 4); }
|
||||
bool fge() const { return data & (1 << 5); }
|
||||
bool abe() const { return data & (1 << 6); }
|
||||
bool aa1() const { return data & (1 << 7); }
|
||||
bool fst() const { return data & (1 << 8); }
|
||||
bool ctxt() const { return data & (1 << 9); }
|
||||
bool fix() const { return data & (1 << 10); }
|
||||
|
||||
u64 data = 0;
|
||||
|
||||
bool operator==(const GsPrim& other) const { return data == other.data; }
|
||||
bool operator!=(const GsPrim& other) const { return data != other.data; }
|
||||
};
|
||||
|
||||
struct GsTex0 {
|
||||
GsTex0() = default;
|
||||
GsTex0(u64 val) : data(val) {}
|
||||
|
||||
u32 tbp0() const { return data & 0b11'1111'1111'1111; }
|
||||
u32 tbw() const { return (data >> 14) & 0b111111; }
|
||||
enum class PSM {
|
||||
PSMCT32 = 0,
|
||||
PSMCT24 = 1,
|
||||
PSMCT16 = 2,
|
||||
PSMCT16S = 0b1010,
|
||||
PSMT8 = 0b10011,
|
||||
PSMT4 = 0b10100,
|
||||
PSMT8H = 0b011011,
|
||||
PSMT4HL = 0b100100,
|
||||
PSMT4HH = 0b101100,
|
||||
PSMZ32 = 0b110000,
|
||||
PSMZ24 = 0b110001,
|
||||
PSMZ16 = 0b110010,
|
||||
PSMZ16S = 0b111010
|
||||
};
|
||||
PSM psm() const { return (PSM)((data >> 20) & 0b111111); }
|
||||
u32 tw() const { return (data >> 26) & 0b1111; }
|
||||
u32 th() const { return (data >> 30) & 0b1111; }
|
||||
enum class TextureFunction : u8 {
|
||||
MODULATE = 0,
|
||||
DECAL = 1,
|
||||
HIGHLIGHT = 2,
|
||||
HIGHLIGHT2 = 3,
|
||||
};
|
||||
u32 tcc() const { return ((data >> 34) & 0b1); }
|
||||
TextureFunction tfx() const { return (TextureFunction)((data >> 35) & 0b11); }
|
||||
u32 cbp() const { return (data >> 37) & 0b11'1111'1111'1111; }
|
||||
u32 cpsm() const { return (data >> 51) & 0b1111; }
|
||||
u32 csm() const { return (data >> 55) & 1; }
|
||||
|
||||
std::string print() const;
|
||||
|
||||
bool operator==(const GsTex0& other) const { return data == other.data; }
|
||||
bool operator!=(const GsTex0& other) const { return data != other.data; }
|
||||
|
||||
u64 data = 0;
|
||||
};
|
||||
|
||||
struct GsTex1 { // tex1_1 and tex1_2
|
||||
GsTex1() = default;
|
||||
GsTex1(u64 val) : data(val) {}
|
||||
|
||||
bool lcm() const {
|
||||
// 1 = fixed
|
||||
return data & 1;
|
||||
}
|
||||
|
||||
u32 mxl() const { return (data >> 2) & 0b111; }
|
||||
bool mmag() const { return data & (1 << 5); }
|
||||
u32 mmin() const { return (data >> 6) & 0b111; }
|
||||
bool mtba() const { return data & (1 << 9); }
|
||||
u32 l() const { return (data >> 19) & 0b11; }
|
||||
u32 k() const { return (data >> 32) & 0b1111'1111'1111; }
|
||||
|
||||
std::string print() const;
|
||||
|
||||
u64 data = 0;
|
||||
};
|
||||
|
||||
struct GsTexa {
|
||||
GsTexa() = default;
|
||||
GsTexa(u64 val) : data(val) {}
|
||||
|
||||
u8 ta0() const { return data; }
|
||||
bool aem() const { return data & (1 << 15); }
|
||||
u8 ta1() const { return (data >> 32); }
|
||||
|
||||
std::string print() const;
|
||||
|
||||
u64 data = 0;
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
/*!
|
||||
* @file gfx.cpp
|
||||
* Graphics component for the runtime. Handles some low-level routines.
|
||||
* Graphics component for the runtime. Abstraction layer for the main graphics routines.
|
||||
*/
|
||||
|
||||
#include "gfx.h"
|
||||
@ -9,63 +9,106 @@
|
||||
#include "game/runtime.h"
|
||||
#include "display.h"
|
||||
|
||||
#include "opengl.h"
|
||||
#include "pipelines/opengl.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// initializes a gfx settings.
|
||||
// TODO save and load from file
|
||||
void InitSettings(GfxSettings& settings) {
|
||||
// use opengl by default for now
|
||||
settings.renderer = Gfx::GetRenderer(GfxPipeline::OpenGL); // Gfx::renderers[0];
|
||||
|
||||
// 1 screen update per frame
|
||||
settings.vsync = 1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
void GlfwErrorCallback(int err, const char* msg) {
|
||||
lg::error("GLFW ERR {}: " + std::string(msg), err);
|
||||
GfxVertex g_vertices_temp[VERTEX_BUFFER_LENGTH_TEMP];
|
||||
|
||||
GfxSettings g_settings;
|
||||
// const std::vector<const GfxRendererModule*> renderers = {&moduleOpenGL};
|
||||
|
||||
const GfxRendererModule* GetRenderer(GfxPipeline pipeline) {
|
||||
switch (pipeline) {
|
||||
case GfxPipeline::Invalid:
|
||||
lg::error("Requested invalid graphics pipeline!");
|
||||
return NULL;
|
||||
break;
|
||||
case GfxPipeline::OpenGL:
|
||||
return &moduleOpenGL;
|
||||
default:
|
||||
lg::error("Unknown graphics pipeline {}", (u64)pipeline);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
u32 Init() {
|
||||
if (glfwSetErrorCallback(GlfwErrorCallback) != NULL) {
|
||||
lg::warn("glfwSetErrorCallback has been re-set!");
|
||||
}
|
||||
lg::info("GFX Init");
|
||||
// initialize settings
|
||||
InitSettings(g_settings);
|
||||
|
||||
if (!glfwInit()) {
|
||||
lg::error("glfwInit error");
|
||||
if (g_settings.renderer->init()) {
|
||||
lg::error("Gfx::Init error");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (g_main_thread_id != std::this_thread::get_id()) {
|
||||
lg::warn("ran Gfx::Init outside main thread. Init display elsewhere?");
|
||||
} else {
|
||||
Display::InitDisplay(640, 480, "testy", Display::display);
|
||||
Display::InitMainDisplay(640, 480, "testy", g_settings);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Loop(std::function<bool()> f) {
|
||||
lg::info("GFX Loop");
|
||||
while (f()) {
|
||||
// run display-specific things
|
||||
if (Display::display) {
|
||||
// check if we have a display
|
||||
if (Display::GetMainDisplay()) {
|
||||
// lg::debug("run display");
|
||||
glfwMakeContextCurrent(Display::display);
|
||||
|
||||
// render graphics
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
glfwSwapBuffers(Display::display);
|
||||
|
||||
// poll events TODO integrate input with cpad
|
||||
glfwPollEvents();
|
||||
|
||||
// exit if display window was closed
|
||||
if (glfwWindowShouldClose(Display::display)) {
|
||||
// Display::KillDisplay(Display::display);
|
||||
MasterExit = 2;
|
||||
}
|
||||
Display::GetMainDisplay()->render_graphics();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 Exit() {
|
||||
lg::debug("gfx exit");
|
||||
Display::KillDisplay(Display::display);
|
||||
glfwTerminate();
|
||||
glfwSetErrorCallback(NULL);
|
||||
lg::info("GFX Exit");
|
||||
Display::KillDisplay(Display::GetMainDisplay());
|
||||
g_settings.renderer->exit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 vsync() {
|
||||
return g_settings.renderer->vsync();
|
||||
}
|
||||
|
||||
u32 sync_path() {
|
||||
return g_settings.renderer->sync_path();
|
||||
}
|
||||
|
||||
void send_chain(const void* data, u32 offset) {
|
||||
if (g_settings.renderer) {
|
||||
g_settings.renderer->send_chain(data, offset);
|
||||
}
|
||||
}
|
||||
|
||||
void texture_upload_now(const u8* tpage, int mode, u32 s7_ptr) {
|
||||
if (g_settings.renderer) {
|
||||
g_settings.renderer->texture_upload_now(tpage, mode, s7_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void texture_relocate(u32 destination, u32 source, u32 format) {
|
||||
if (g_settings.renderer) {
|
||||
g_settings.renderer->texture_relocate(destination, source, format);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Gfx
|
||||
|
@ -2,23 +2,78 @@
|
||||
|
||||
/*!
|
||||
* @file gfx.h
|
||||
* Graphics component for the runtime. Handles some low-level routines.
|
||||
* Graphics component for the runtime. Abstraction layer for the main graphics routines.
|
||||
*/
|
||||
|
||||
#ifndef RUNTIME_GFX_H
|
||||
#define RUNTIME_GFX_H
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "display.h"
|
||||
#include "game/kernel/kboot.h"
|
||||
|
||||
// forward declarations
|
||||
struct GfxSettings;
|
||||
class GfxDisplay;
|
||||
|
||||
// enum for rendering pipeline
|
||||
enum class GfxPipeline { Invalid = 0, OpenGL };
|
||||
|
||||
// module for the different rendering pipelines
|
||||
struct GfxRendererModule {
|
||||
std::function<int()> init;
|
||||
std::function<std::shared_ptr<GfxDisplay>(int w, int h, const char* title, GfxSettings& settings)>
|
||||
make_main_display;
|
||||
std::function<void(GfxDisplay* display)> kill_display;
|
||||
std::function<void(GfxDisplay* display)> render_display;
|
||||
std::function<void()> exit;
|
||||
std::function<u32()> vsync;
|
||||
std::function<u32()> sync_path;
|
||||
std::function<void(const void*, u32)> send_chain;
|
||||
std::function<void(const u8*, int, u32)> texture_upload_now;
|
||||
std::function<void(u32, u32, u32)> texture_relocate;
|
||||
|
||||
GfxPipeline pipeline;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
// store settings related to the gfx systems
|
||||
struct GfxSettings {
|
||||
const GfxRendererModule* renderer; // which rendering pipeline to use.
|
||||
|
||||
int vsync; // (temp) number of screen update per frame
|
||||
};
|
||||
|
||||
// struct for a single vertex. this should in theory be renderer-agnostic
|
||||
struct GfxVertex {
|
||||
// x y z
|
||||
float x, y, z;
|
||||
|
||||
// rgba or the full u32 thing.
|
||||
union {
|
||||
u32 rgba;
|
||||
struct {
|
||||
u8 r, g, b, a;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
static constexpr int VERTEX_BUFFER_LENGTH_TEMP = 4096;
|
||||
extern GfxVertex g_vertices_temp[VERTEX_BUFFER_LENGTH_TEMP];
|
||||
|
||||
extern GfxSettings g_settings;
|
||||
// extern const std::vector<const GfxRendererModule*> renderers;
|
||||
|
||||
const GfxRendererModule* GetRenderer(GfxPipeline pipeline);
|
||||
|
||||
u32 Init();
|
||||
void Loop(std::function<bool()> f);
|
||||
u32 Exit();
|
||||
|
||||
} // namespace Gfx
|
||||
u32 vsync();
|
||||
u32 sync_path();
|
||||
void send_chain(const void* data, u32 offset);
|
||||
void texture_upload_now(const u8* tpage, int mode, u32 s7_ptr);
|
||||
void texture_relocate(u32 destination, u32 source, u32 format);
|
||||
|
||||
#endif // RUNTIME_GFX_H
|
||||
} // namespace Gfx
|
||||
|
@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/*!
|
||||
* @file opengl.h
|
||||
* OpenGL includes.
|
||||
*/
|
||||
|
||||
#ifndef RUNTIME_OPENGL_H
|
||||
#define RUNTIME_OPENGL_H
|
||||
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include <glad/glad.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#endif // RUNTIME_OPENGL_H
|
43
game/graphics/opengl_renderer/BucketRenderer.cpp
Normal file
43
game/graphics/opengl_renderer/BucketRenderer.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include "BucketRenderer.h"
|
||||
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
std::string BucketRenderer::name_and_id() const {
|
||||
return fmt::format("[{:2d}] {}", (int)m_my_id, m_name);
|
||||
}
|
||||
|
||||
EmptyBucketRenderer::EmptyBucketRenderer(const std::string& name, BucketId my_id)
|
||||
: BucketRenderer(name, my_id) {}
|
||||
|
||||
void EmptyBucketRenderer::render(DmaFollower& dma, SharedRenderState* render_state) {
|
||||
// an empty bucket should have 4 things:
|
||||
// a NEXT in the bucket buffer
|
||||
// a CALL that calls the default register buffer chain
|
||||
// a CNT then RET to get out of the default register buffer chain
|
||||
// a NEXT to get to the next bucket.
|
||||
|
||||
// NEXT
|
||||
auto first_tag = dma.current_tag();
|
||||
dma.read_and_advance();
|
||||
assert(first_tag.kind == DmaTag::Kind::NEXT && first_tag.qwc == 0);
|
||||
|
||||
// CALL
|
||||
auto call_tag = dma.current_tag();
|
||||
dma.read_and_advance();
|
||||
assert(call_tag.kind == DmaTag::Kind::CALL && call_tag.qwc == 0);
|
||||
|
||||
// in the default reg buffer:
|
||||
assert(dma.current_tag_offset() == render_state->default_regs_buffer);
|
||||
dma.read_and_advance();
|
||||
assert(dma.current_tag().kind == DmaTag::Kind::RET);
|
||||
dma.read_and_advance();
|
||||
|
||||
// NEXT to next buffer
|
||||
auto to_next_buffer = dma.current_tag();
|
||||
assert(to_next_buffer.kind == DmaTag::Kind::NEXT);
|
||||
assert(to_next_buffer.qwc == 0);
|
||||
dma.read_and_advance();
|
||||
|
||||
// and we should now be in the next bucket!
|
||||
assert(dma.current_tag_offset() == render_state->next_bucket);
|
||||
}
|
57
game/graphics/opengl_renderer/BucketRenderer.h
Normal file
57
game/graphics/opengl_renderer/BucketRenderer.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "game/graphics/dma/dma_chain_read.h"
|
||||
#include "game/graphics/opengl_renderer/Shader.h"
|
||||
#include "game/graphics/texture/TexturePool.h"
|
||||
|
||||
/*!
|
||||
* Matches the bucket-id enum in GOAL
|
||||
*/
|
||||
enum class BucketId {
|
||||
BUCKET0 = 0,
|
||||
BUCKET1 = 1,
|
||||
// ...
|
||||
DEBUG_DRAW_0 = 67,
|
||||
DEBUG_DRAW_1 = 68,
|
||||
MAX_BUCKETS = 69
|
||||
};
|
||||
|
||||
/*!
|
||||
* The main renderer will contain a single SharedRenderState that's passed to all bucket renderers.
|
||||
* This allows bucket renders to share textures and shaders.
|
||||
*/
|
||||
struct SharedRenderState {
|
||||
explicit SharedRenderState(std::shared_ptr<TexturePool> _texture_pool)
|
||||
: texture_pool(_texture_pool) {}
|
||||
ShaderLibrary shaders;
|
||||
std::shared_ptr<TexturePool> texture_pool;
|
||||
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.
|
||||
};
|
||||
|
||||
/*!
|
||||
* Interface for bucket renders. Each bucket will have its own BucketRenderer.
|
||||
*/
|
||||
class BucketRenderer {
|
||||
public:
|
||||
BucketRenderer(const std::string& name, BucketId my_id) : m_name(name), m_my_id(my_id) {}
|
||||
virtual void render(DmaFollower& dma, SharedRenderState* render_state) = 0;
|
||||
std::string name_and_id() const;
|
||||
virtual ~BucketRenderer() = default;
|
||||
|
||||
protected:
|
||||
std::string m_name;
|
||||
BucketId m_my_id;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Renderer that makes sure the bucket is empty and ignores it.
|
||||
*/
|
||||
class EmptyBucketRenderer : public BucketRenderer {
|
||||
public:
|
||||
EmptyBucketRenderer(const std::string& name, BucketId my_id);
|
||||
void render(DmaFollower& dma, SharedRenderState* render_state) override;
|
||||
};
|
791
game/graphics/opengl_renderer/DirectRenderer.cpp
Normal file
791
game/graphics/opengl_renderer/DirectRenderer.cpp
Normal file
@ -0,0 +1,791 @@
|
||||
#include "DirectRenderer.h"
|
||||
#include "game/graphics/dma/gs.h"
|
||||
#include "common/log/log.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "game/graphics/pipelines/opengl.h"
|
||||
|
||||
DirectRenderer::DirectRenderer(const std::string& name, BucketId my_id, int batch_size)
|
||||
: BucketRenderer(name, my_id), m_prim_buffer(batch_size) {
|
||||
glGenBuffers(1, &m_ogl.vertex_buffer);
|
||||
glGenBuffers(1, &m_ogl.color_buffer);
|
||||
glGenBuffers(1, &m_ogl.st_buffer);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
|
||||
m_ogl.vertex_buffer_bytes = batch_size * 3 * 3 * sizeof(u32);
|
||||
glBufferData(GL_ARRAY_BUFFER, m_ogl.vertex_buffer_bytes, nullptr, GL_DYNAMIC_DRAW);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.color_buffer);
|
||||
m_ogl.color_buffer_bytes = batch_size * 3 * 4 * sizeof(u8);
|
||||
glBufferData(GL_ARRAY_BUFFER, m_ogl.color_buffer_bytes, nullptr, GL_DYNAMIC_DRAW);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.st_buffer);
|
||||
m_ogl.st_buffer_bytes = batch_size * 3 * 2 * sizeof(float);
|
||||
glBufferData(GL_ARRAY_BUFFER, m_ogl.st_buffer_bytes, nullptr, GL_DYNAMIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
DirectRenderer::~DirectRenderer() {
|
||||
glDeleteBuffers(1, &m_ogl.color_buffer);
|
||||
glDeleteBuffers(1, &m_ogl.vertex_buffer);
|
||||
glDeleteBuffers(1, &m_ogl.st_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Render from a DMA bucket.
|
||||
*/
|
||||
void DirectRenderer::render(DmaFollower& dma, SharedRenderState* render_state) {
|
||||
m_triangles = 0;
|
||||
// fmt::print("direct: {}\n", m_my_id);
|
||||
// if we're rendering from a bucket, we should start off we a totally reset state:
|
||||
reset_state();
|
||||
setup_common_state(render_state);
|
||||
|
||||
// just dump the DMA data into the other the render function
|
||||
while (dma.current_tag_offset() != render_state->next_bucket) {
|
||||
auto data = dma.read_and_advance();
|
||||
if (data.size_bytes) {
|
||||
render_vif(data.vif0(), data.vif1(), data.data, data.size_bytes, render_state);
|
||||
}
|
||||
|
||||
if (dma.current_tag_offset() == render_state->default_regs_buffer) {
|
||||
// reset_state();
|
||||
dma.read_and_advance(); // cnt
|
||||
assert(dma.current_tag().kind == DmaTag::Kind::RET);
|
||||
dma.read_and_advance(); // ret
|
||||
}
|
||||
}
|
||||
|
||||
flush_pending(render_state);
|
||||
}
|
||||
|
||||
void DirectRenderer::flush_pending(SharedRenderState* render_state) {
|
||||
if (m_prim_buffer.vert_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// update opengl state
|
||||
if (m_prim_gl_state_needs_gl_update) {
|
||||
update_gl_prim(render_state);
|
||||
m_prim_gl_state_needs_gl_update = false;
|
||||
}
|
||||
|
||||
if (m_blend_state_needs_gl_update) {
|
||||
update_gl_blend();
|
||||
m_blend_state_needs_gl_update = false;
|
||||
}
|
||||
|
||||
if (m_test_state_needs_gl_update) {
|
||||
update_gl_test();
|
||||
m_test_state_needs_gl_update = false;
|
||||
}
|
||||
|
||||
// hacks
|
||||
// glEnable(GL_DEPTH_TEST);
|
||||
// glDepthFunc(GL_ALWAYS);
|
||||
|
||||
GLuint vao;
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
|
||||
// render!
|
||||
// update buffers:
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, m_prim_buffer.verts.size() * sizeof(math::Vector<u32, 3>),
|
||||
m_prim_buffer.verts.data());
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.color_buffer);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, m_prim_buffer.rgba_u8.size() * sizeof(math::Vector<u8, 4>),
|
||||
m_prim_buffer.rgba_u8.data());
|
||||
if (m_prim_gl_state.texture_enable) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.st_buffer);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, m_prim_buffer.sts.size() * sizeof(math::Vector<float, 2>),
|
||||
m_prim_buffer.sts.data());
|
||||
}
|
||||
|
||||
// setup attributes:
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, // location 0 in the shader
|
||||
3, // 3 floats per vert
|
||||
GL_UNSIGNED_INT, // floats
|
||||
GL_TRUE, // normalized, ignored,
|
||||
0, // tightly packed
|
||||
0 // offset in array (why is is this a pointer...)
|
||||
);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.color_buffer);
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, // location 0 in the shader
|
||||
4, // 3 floats per vert
|
||||
GL_UNSIGNED_BYTE, // floats
|
||||
GL_TRUE, // normalized, ignored,
|
||||
0, // tightly packed
|
||||
0);
|
||||
|
||||
if (m_prim_gl_state.texture_enable) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.st_buffer);
|
||||
glEnableVertexAttribArray(2);
|
||||
glVertexAttribPointer(2, // location 0 in the shader
|
||||
2, // 3 floats per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized, ignored,
|
||||
0, // tightly packed
|
||||
0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
// assert(false);
|
||||
glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count);
|
||||
glBindVertexArray(0);
|
||||
m_triangles += m_prim_buffer.vert_count / 3;
|
||||
m_prim_buffer.vert_count = 0;
|
||||
|
||||
glDeleteVertexArrays(1, &vao);
|
||||
}
|
||||
|
||||
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();
|
||||
update_gl_texture(render_state);
|
||||
} else {
|
||||
render_state->shaders[ShaderId::DIRECT_BASIC].activate();
|
||||
}
|
||||
if (state.fogging_enable) {
|
||||
assert(false);
|
||||
}
|
||||
if (state.aa_enable) {
|
||||
assert(false);
|
||||
}
|
||||
if (state.use_uv) {
|
||||
assert(false);
|
||||
}
|
||||
if (state.ctxt) {
|
||||
assert(false);
|
||||
}
|
||||
if (state.fix) {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void DirectRenderer::upload_texture(TextureRecord* tex) {
|
||||
assert(!tex->on_gpu);
|
||||
GLuint tex_id;
|
||||
glGenTextures(1, &tex_id);
|
||||
tex->gpu_texture = tex_id;
|
||||
glBindTexture(GL_TEXTURE_2D, tex_id);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->w, tex->h, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||
tex->data.data());
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
tex->on_gpu = true;
|
||||
}
|
||||
|
||||
void DirectRenderer::update_gl_texture(SharedRenderState* render_state) {
|
||||
TextureRecord* tex = nullptr;
|
||||
if (m_texture_state.using_mt4hh) {
|
||||
tex = render_state->texture_pool->lookup_mt4hh(m_texture_state.texture_base_ptr);
|
||||
} else {
|
||||
tex = render_state->texture_pool->lookup(m_texture_state.texture_base_ptr);
|
||||
}
|
||||
assert(tex);
|
||||
// fmt::print("Successful texture lookup! {} {}\n", tex->page_name, tex->name);
|
||||
|
||||
// first: do we need to load the texture?
|
||||
if (!tex->on_gpu) {
|
||||
upload_texture(tex);
|
||||
}
|
||||
|
||||
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);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glUniform1i(
|
||||
glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(), "T0"), 0);
|
||||
}
|
||||
|
||||
void DirectRenderer::update_gl_blend() {
|
||||
const auto& state = m_blend_state;
|
||||
if (state.a == GsAlpha::BlendMode::SOURCE && state.b == GsAlpha::BlendMode::DEST &&
|
||||
state.c == GsAlpha::BlendMode::SOURCE && state.d == GsAlpha::BlendMode::DEST) {
|
||||
// (Cs - Cd) * As + Cd
|
||||
// Cs * As + (1 - As) * Cd
|
||||
glEnable(GL_BLEND);
|
||||
// s, d
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
} else {
|
||||
fmt::print("unsupported blend\n");
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void DirectRenderer::update_gl_test() {
|
||||
const auto& state = m_test_state;
|
||||
if (state.zte) {
|
||||
switch (state.ztst) {
|
||||
case GsTest::ZTest::NEVER:
|
||||
glDepthFunc(GL_NEVER);
|
||||
break;
|
||||
case GsTest::ZTest::ALWAYS:
|
||||
glDepthFunc(GL_ALWAYS);
|
||||
break;
|
||||
case GsTest::ZTest::GEQUAL:
|
||||
glDepthFunc(GL_GEQUAL);
|
||||
break;
|
||||
case GsTest::ZTest::GREATER:
|
||||
glDepthFunc(GL_GREATER);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (state.date) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (state.alpha_test_enable) {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void DirectRenderer::setup_common_state(SharedRenderState* /*render_state*/) {
|
||||
// todo texture clamp.
|
||||
}
|
||||
|
||||
namespace {
|
||||
/*!
|
||||
* If it's a direct, returns the qwc.
|
||||
* If it's ignorable (nop, flush), returns 0.
|
||||
* Otherwise, assert.
|
||||
*/
|
||||
u32 get_direct_qwc_or_nop(const VifCode& code) {
|
||||
switch (code.kind) {
|
||||
case VifCode::Kind::NOP:
|
||||
case VifCode::Kind::FLUSHA:
|
||||
return 0;
|
||||
case VifCode::Kind::DIRECT:
|
||||
if (code.immediate == 0) {
|
||||
return 65536;
|
||||
} else {
|
||||
return code.immediate;
|
||||
}
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/*!
|
||||
* Render VIF data.
|
||||
*/
|
||||
void DirectRenderer::render_vif(u32 vif0,
|
||||
u32 vif1,
|
||||
const u8* data,
|
||||
u32 size,
|
||||
SharedRenderState* render_state) {
|
||||
// here we process VIF data. Basically we just go forward, looking for DIRECTs.
|
||||
// We skip stuff like flush and nops.
|
||||
|
||||
// read the vif cmds at the front.
|
||||
u32 gif_qwc = get_direct_qwc_or_nop(VifCode(vif0));
|
||||
if (gif_qwc) {
|
||||
// we got a direct. expect the second thing to be a nop/similar.
|
||||
assert(get_direct_qwc_or_nop(VifCode(vif1)) == 0);
|
||||
} else {
|
||||
gif_qwc = get_direct_qwc_or_nop(VifCode(vif1));
|
||||
}
|
||||
|
||||
u32 offset_into_data = 0;
|
||||
while (offset_into_data < size) {
|
||||
if (gif_qwc) {
|
||||
if (offset_into_data & 0xf) {
|
||||
// not aligned. should get nops.
|
||||
u32 vif;
|
||||
memcpy(&vif, data + offset_into_data, 4);
|
||||
offset_into_data += 4;
|
||||
assert(get_direct_qwc_or_nop(VifCode(vif)) == 0);
|
||||
} else {
|
||||
// aligned! do a gif transfer!
|
||||
render_gif(data + offset_into_data, gif_qwc * 16, render_state);
|
||||
offset_into_data += gif_qwc * 16;
|
||||
}
|
||||
} else {
|
||||
// we are reading VIF data.
|
||||
u32 vif;
|
||||
memcpy(&vif, data + offset_into_data, 4);
|
||||
offset_into_data += 4;
|
||||
gif_qwc = get_direct_qwc_or_nop(VifCode(vif));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Render GIF data.
|
||||
*/
|
||||
void DirectRenderer::render_gif(const u8* data, u32 size, SharedRenderState* render_state) {
|
||||
assert(size >= 16);
|
||||
bool eop = false;
|
||||
|
||||
u32 offset = 0;
|
||||
while (!eop) {
|
||||
GifTag tag(data + offset);
|
||||
offset += 16;
|
||||
// fmt::print("Tag: {}\n", tag.print());
|
||||
|
||||
// unpack registers.
|
||||
// faster to do it once outside of the nloop loop.
|
||||
GifTag::RegisterDescriptor reg_desc[16];
|
||||
u32 nreg = tag.nreg();
|
||||
for (u32 i = 0; i < nreg; i++) {
|
||||
reg_desc[i] = tag.reg(i);
|
||||
}
|
||||
|
||||
auto format = tag.flg();
|
||||
if (format == GifTag::Format::PACKED) {
|
||||
if (tag.pre()) {
|
||||
handle_prim(tag.prim(), render_state);
|
||||
}
|
||||
for (u32 loop = 0; loop < tag.nloop(); loop++) {
|
||||
for (u32 reg = 0; reg < nreg; reg++) {
|
||||
switch (reg_desc[reg]) {
|
||||
case GifTag::RegisterDescriptor::AD:
|
||||
handle_ad(data + offset, render_state);
|
||||
break;
|
||||
case GifTag::RegisterDescriptor::ST:
|
||||
handle_st_packed(data + offset);
|
||||
break;
|
||||
case GifTag::RegisterDescriptor::RGBAQ:
|
||||
handle_rgbaq_packed(data + offset);
|
||||
break;
|
||||
case GifTag::RegisterDescriptor::XYZF2:
|
||||
handle_xyzf2_packed(data + offset, render_state);
|
||||
break;
|
||||
default:
|
||||
fmt::print("Register {} is not supported in packed mode yet\n",
|
||||
reg_descriptor_name(reg_desc[reg]));
|
||||
assert(false);
|
||||
}
|
||||
offset += 16; // PACKED = quadwords
|
||||
}
|
||||
}
|
||||
} else if (format == GifTag::Format::REGLIST) {
|
||||
for (u32 loop = 0; loop < tag.nloop(); loop++) {
|
||||
for (u32 reg = 0; reg < nreg; reg++) {
|
||||
u64 register_data;
|
||||
memcpy(®ister_data, data + offset, 8);
|
||||
// fmt::print("loop: {} reg: {} {}\n", loop, reg,
|
||||
// reg_descriptor_name(reg_desc[reg]));
|
||||
switch (reg_desc[reg]) {
|
||||
case GifTag::RegisterDescriptor::PRIM:
|
||||
handle_prim(register_data, render_state);
|
||||
break;
|
||||
case GifTag::RegisterDescriptor::RGBAQ:
|
||||
handle_rgbaq(register_data);
|
||||
break;
|
||||
case GifTag::RegisterDescriptor::XYZF2:
|
||||
handle_xyzf2(register_data, render_state);
|
||||
break;
|
||||
default:
|
||||
fmt::print("Register {} is not supported in reglist mode yet\n",
|
||||
reg_descriptor_name(reg_desc[reg]));
|
||||
assert(false);
|
||||
}
|
||||
offset += 8; // PACKED = quadwords
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(false); // format not packed or reglist.
|
||||
}
|
||||
|
||||
eop = tag.eop();
|
||||
}
|
||||
|
||||
assert(offset == size);
|
||||
|
||||
// fmt::print("{}\n", GifTag(data).print());
|
||||
}
|
||||
|
||||
void DirectRenderer::handle_ad(const u8* data, SharedRenderState* render_state) {
|
||||
u64 value;
|
||||
GsRegisterAddress addr;
|
||||
memcpy(&value, data, sizeof(u64));
|
||||
memcpy(&addr, data + 8, sizeof(GsRegisterAddress));
|
||||
|
||||
switch (addr) {
|
||||
case GsRegisterAddress::ZBUF_1:
|
||||
handle_zbuf1(value);
|
||||
break;
|
||||
case GsRegisterAddress::TEST_1:
|
||||
handle_test1(value, render_state);
|
||||
break;
|
||||
case GsRegisterAddress::ALPHA_1:
|
||||
handle_alpha1(value, render_state);
|
||||
break;
|
||||
case GsRegisterAddress::PABE:
|
||||
handle_pabe(value);
|
||||
break;
|
||||
case GsRegisterAddress::CLAMP_1:
|
||||
handle_clamp1(value);
|
||||
break;
|
||||
case GsRegisterAddress::PRIM:
|
||||
handle_prim(value, render_state);
|
||||
break;
|
||||
|
||||
case GsRegisterAddress::TEX1_1:
|
||||
handle_tex1_1(value);
|
||||
break;
|
||||
case GsRegisterAddress::TEXA:
|
||||
handle_texa(value);
|
||||
break;
|
||||
case GsRegisterAddress::TEXCLUT:
|
||||
// TODO
|
||||
// the only thing the direct renderer does with texture is font, which does no tricks with
|
||||
// CLUT. The texture upload process will do all of the lookups with the default CLUT.
|
||||
// So we'll just assume that the TEXCLUT is set properly and ignore this.
|
||||
break;
|
||||
case GsRegisterAddress::FOGCOL:
|
||||
// TODO
|
||||
break;
|
||||
case GsRegisterAddress::TEX0_1:
|
||||
handle_tex0_1(value, render_state);
|
||||
break;
|
||||
default:
|
||||
fmt::print("Address {} is not supported\n", register_address_name(addr));
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
// 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);
|
||||
|
||||
// fmt::print("{}\n", reg.print());
|
||||
}
|
||||
|
||||
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) {
|
||||
flush_pending(render_state);
|
||||
m_texture_state.texture_base_ptr = reg.tbp0();
|
||||
m_texture_state.using_mt4hh = reg.psm() == GsTex0::PSM::PSMT4HH;
|
||||
m_prim_gl_state_needs_gl_update = true;
|
||||
m_texture_state.current_register = reg;
|
||||
}
|
||||
|
||||
// tbw: assume they got it right
|
||||
// psm: assume they got it right
|
||||
// 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
|
||||
// cpsm: assume they got it right
|
||||
// csm: assume they got it right
|
||||
}
|
||||
|
||||
void DirectRenderer::handle_texa(u64 val) {
|
||||
GsTexa reg(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.aem() == false);
|
||||
}
|
||||
|
||||
void DirectRenderer::handle_st_packed(const u8* data) {
|
||||
memcpy(&m_prim_building.st_reg.x(), data + 0, 4);
|
||||
memcpy(&m_prim_building.st_reg.y(), data + 4, 4);
|
||||
memcpy(&m_prim_building.Q, data + 8, 4);
|
||||
}
|
||||
|
||||
void DirectRenderer::handle_rgbaq_packed(const u8* data) {
|
||||
// TODO update Q from st.
|
||||
m_prim_building.rgba_reg[0] = data[0];
|
||||
m_prim_building.rgba_reg[1] = data[4];
|
||||
m_prim_building.rgba_reg[2] = data[8];
|
||||
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);
|
||||
memcpy(&y, data + 4, 4);
|
||||
|
||||
u64 upper;
|
||||
memcpy(&upper, data + 8, 8);
|
||||
u32 z = (upper >> 4) & 0xffffff;
|
||||
u8 f = (upper >> 36);
|
||||
bool adc = upper & (1ull << 47);
|
||||
assert(!adc);
|
||||
assert(!f);
|
||||
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) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
void DirectRenderer::handle_test1(u64 val, SharedRenderState* render_state) {
|
||||
GsTest reg(val);
|
||||
if (m_test_state.current_register != reg) {
|
||||
flush_pending(render_state);
|
||||
m_test_state.from_register(reg);
|
||||
m_test_state_needs_gl_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DirectRenderer::handle_alpha1(u64 val, SharedRenderState* render_state) {
|
||||
GsAlpha reg(val);
|
||||
if (m_blend_state.current_register != reg) {
|
||||
flush_pending(render_state);
|
||||
m_blend_state.from_register(reg);
|
||||
m_blend_state_needs_gl_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DirectRenderer::handle_pabe(u64 val) {
|
||||
assert(val == 0); // not really sure how to handle this yet.
|
||||
}
|
||||
|
||||
void DirectRenderer::handle_clamp1(u64 val) {
|
||||
assert(val == 0b101); // clamp s and t.
|
||||
}
|
||||
|
||||
void DirectRenderer::handle_prim(u64 val, SharedRenderState* render_state) {
|
||||
if (m_prim_building.tri_strip_startup) {
|
||||
m_prim_building.tri_strip_startup = 0;
|
||||
m_prim_building.building_idx = 0;
|
||||
} else {
|
||||
if (m_prim_building.building_idx > 0) {
|
||||
assert(false); // shouldn't leave any half-finished prims
|
||||
}
|
||||
}
|
||||
// need to flush any in progress prims to the buffer.
|
||||
|
||||
GsPrim prim(val);
|
||||
if (m_prim_gl_state.current_register != prim || m_blend_state.alpha_blend_enable != prim.abe()) {
|
||||
flush_pending(render_state);
|
||||
m_prim_gl_state.from_register(prim);
|
||||
m_blend_state.alpha_blend_enable = prim.abe();
|
||||
m_prim_gl_state_needs_gl_update = true;
|
||||
m_blend_state_needs_gl_update = true;
|
||||
}
|
||||
|
||||
m_prim_building.kind = prim.kind();
|
||||
}
|
||||
|
||||
void DirectRenderer::handle_rgbaq(u64 val) {
|
||||
assert((val >> 32) == 0); // q = 0
|
||||
memcpy(m_prim_building.rgba_reg.data(), &val, 4);
|
||||
}
|
||||
|
||||
void DirectRenderer::handle_xyzf2_common(u32 x,
|
||||
u32 y,
|
||||
u32 z,
|
||||
u8 f,
|
||||
SharedRenderState* render_state) {
|
||||
if (m_prim_buffer.is_full()) {
|
||||
flush_pending(render_state);
|
||||
}
|
||||
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_idx++;
|
||||
|
||||
switch (m_prim_building.kind) {
|
||||
case GsPrim::Kind::SPRITE: {
|
||||
if (m_prim_building.building_idx == 2) {
|
||||
// build triangles from the sprite.
|
||||
auto& corner1_vert = m_prim_building.building_vert[0];
|
||||
auto& corner1_rgba = m_prim_building.building_rgba[0];
|
||||
auto& corner2_vert = m_prim_building.building_vert[1];
|
||||
auto& corner2_rgba = m_prim_building.building_rgba[1];
|
||||
// should use most recent vertex z.
|
||||
math::Vector<u32, 3> corner3_vert = {corner1_vert[0], corner2_vert[1], corner2_vert[2]};
|
||||
math::Vector<u32, 3> corner4_vert = {corner2_vert[0], corner1_vert[1], corner2_vert[2]};
|
||||
|
||||
if (m_prim_gl_state.gouraud_enable) {
|
||||
// I'm not really sure what the GS does here.
|
||||
assert(false);
|
||||
}
|
||||
auto& corner3_rgba = corner2_rgba;
|
||||
auto& corner4_rgba = corner2_rgba;
|
||||
|
||||
m_prim_buffer.push(corner1_rgba, corner1_vert, {});
|
||||
m_prim_buffer.push(corner3_rgba, corner3_vert, {});
|
||||
m_prim_buffer.push(corner2_rgba, corner2_vert, {});
|
||||
m_prim_buffer.push(corner2_rgba, corner2_vert, {});
|
||||
m_prim_buffer.push(corner4_rgba, corner4_vert, {});
|
||||
m_prim_buffer.push(corner1_rgba, corner1_vert, {});
|
||||
m_prim_building.building_idx = 0;
|
||||
}
|
||||
} break;
|
||||
case GsPrim::Kind::TRI_STRIP: {
|
||||
if (m_prim_building.building_idx == 3) {
|
||||
m_prim_building.building_idx = 0;
|
||||
}
|
||||
|
||||
if (m_prim_building.tri_strip_startup < 3) {
|
||||
m_prim_building.tri_strip_startup++;
|
||||
}
|
||||
if (m_prim_building.tri_strip_startup >= 3) {
|
||||
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 == 1) {
|
||||
// math::Vector<double, 3> pt0 = m_prim_building.building_vert[0].cast<double>();
|
||||
// math::Vector<double, 3> pt1 = m_prim_building.building_vert[1].cast<double>();
|
||||
// auto normal = (pt1 - pt0).normalized().cross({0, 0, 1});
|
||||
//
|
||||
// double line_width = (1 << 28);
|
||||
// fmt::print("Line:\n ");
|
||||
// fmt::print(" {} {} {} {}\n", m_prim_building.building_vert[0].x(),
|
||||
// m_prim_building.building_vert[0].y(),
|
||||
// m_prim_building.building_vert[1].x(),
|
||||
// m_prim_building.building_vert[1].y());
|
||||
// // debug_print_vtx(m_prim_building.building_vert[0]);
|
||||
// // debug_print_vtx(m_prim_building.building_vert[1]);
|
||||
//
|
||||
// math::Vector<double, 3> a = pt0 + normal * line_width;
|
||||
// math::Vector<double, 3> b = pt1 + normal * line_width;
|
||||
// math::Vector<double, 3> c = pt0 - normal * line_width;
|
||||
// math::Vector<double, 3> d = pt1 - normal * line_width;
|
||||
//
|
||||
// // ACB:
|
||||
// m_prim_buffer.push(m_prim_building.building_rgba[0], a.cast<u32>(), {});
|
||||
// m_prim_buffer.push(m_prim_building.building_rgba[0], c.cast<u32>(), {});
|
||||
// m_prim_buffer.push(m_prim_building.building_rgba[1], b.cast<u32>(), {});
|
||||
// // b c d
|
||||
// m_prim_buffer.push(m_prim_building.building_rgba[1], b.cast<u32>(), {});
|
||||
// m_prim_buffer.push(m_prim_building.building_rgba[0], c.cast<u32>(), {});
|
||||
// m_prim_buffer.push(m_prim_building.building_rgba[1], d.cast<u32>(), {});
|
||||
// //
|
||||
//
|
||||
// m_prim_building.building_idx = 0;
|
||||
// }
|
||||
// } break;
|
||||
default:
|
||||
fmt::print("prim type {} is unsupported.\n", (int)m_prim_building.kind);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
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 f = (val >> 56) & 0xff;
|
||||
|
||||
handle_xyzf2_common(x, y, z, f, render_state);
|
||||
}
|
||||
|
||||
void DirectRenderer::reset_state() {
|
||||
m_test_state_needs_gl_update = true;
|
||||
m_test_state = TestState();
|
||||
|
||||
m_blend_state_needs_gl_update = true;
|
||||
m_blend_state = BlendState();
|
||||
|
||||
m_prim_gl_state_needs_gl_update = true;
|
||||
m_prim_gl_state = PrimGlState();
|
||||
|
||||
m_texture_state = TextureState();
|
||||
|
||||
m_prim_building = PrimBuildState();
|
||||
}
|
||||
|
||||
void DirectRenderer::TestState::from_register(GsTest reg) {
|
||||
current_register = reg;
|
||||
alpha_test_enable = reg.alpha_test_enable();
|
||||
if (alpha_test_enable) {
|
||||
alpha_test = reg.alpha_test();
|
||||
aref = reg.aref();
|
||||
afail = reg.afail();
|
||||
}
|
||||
|
||||
date = reg.date();
|
||||
if (date) {
|
||||
datm = reg.datm();
|
||||
}
|
||||
|
||||
zte = reg.zte();
|
||||
if (zte) {
|
||||
ztst = reg.ztest();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectRenderer::BlendState::from_register(GsAlpha reg) {
|
||||
current_register = reg;
|
||||
a = reg.a_mode();
|
||||
b = reg.b_mode();
|
||||
c = reg.c_mode();
|
||||
d = reg.d_mode();
|
||||
fix = reg.fix();
|
||||
}
|
||||
|
||||
void DirectRenderer::PrimGlState::from_register(GsPrim reg) {
|
||||
current_register = reg;
|
||||
gouraud_enable = reg.gouraud();
|
||||
texture_enable = reg.tme();
|
||||
fogging_enable = reg.fge();
|
||||
aa_enable = reg.aa1();
|
||||
use_uv = reg.fst();
|
||||
ctxt = reg.ctxt();
|
||||
fix = reg.fix();
|
||||
}
|
||||
|
||||
DirectRenderer::PrimitiveBuffer::PrimitiveBuffer(int max_triangles) {
|
||||
rgba_u8.resize(max_triangles * 3);
|
||||
verts.resize(max_triangles * 3);
|
||||
sts.resize(max_triangles * 3);
|
||||
max_verts = max_triangles * 3;
|
||||
}
|
||||
|
||||
void DirectRenderer::PrimitiveBuffer::push(const math::Vector<u8, 4>& rgba,
|
||||
const math::Vector<u32, 3>& vert,
|
||||
const math::Vector<float, 2>& st) {
|
||||
rgba_u8[vert_count] = rgba;
|
||||
verts[vert_count] = vert;
|
||||
sts[vert_count] = st;
|
||||
vert_count++;
|
||||
}
|
168
game/graphics/opengl_renderer/DirectRenderer.h
Normal file
168
game/graphics/opengl_renderer/DirectRenderer.h
Normal file
@ -0,0 +1,168 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "game/graphics/opengl_renderer/BucketRenderer.h"
|
||||
#include "game/graphics/dma/gs.h"
|
||||
#include "common/math/Vector.h"
|
||||
#include "common/util/SmallVector.h"
|
||||
#include "game/graphics/pipelines/opengl.h"
|
||||
|
||||
/*!
|
||||
* The direct renderer will handle rendering GIFtags directly.
|
||||
* It's named after the DIRECT VIFCode which sends data directly to the GS.
|
||||
*
|
||||
* It should mostly be used for debugging/text stuff as this rendering style does all the math on
|
||||
* the EE and just sends geometry directly to the GS without using the VUs.
|
||||
*
|
||||
* It can be used as a BucketRenderer, or as a subcomponent of another renderer.
|
||||
*/
|
||||
class DirectRenderer : public BucketRenderer {
|
||||
public:
|
||||
DirectRenderer(const std::string& name, BucketId my_id, int batch_size);
|
||||
~DirectRenderer();
|
||||
void render(DmaFollower& dma, SharedRenderState* render_state) override;
|
||||
|
||||
/*!
|
||||
* Render directly from _VIF_ data.
|
||||
* You can optionally provide two vif tags that come in front of data.
|
||||
* These can be set to 0 if you don't have these.
|
||||
*/
|
||||
void render_vif(u32 vif0, u32 vif1, const u8* data, u32 size, SharedRenderState* render_state);
|
||||
|
||||
/*!
|
||||
* Render directly from _GIF_ data.
|
||||
*/
|
||||
void render_gif(const u8* data, u32 size, SharedRenderState* render_state);
|
||||
|
||||
void reset_state();
|
||||
|
||||
/*!
|
||||
* If you don't use the render interface, call this first to set up OpenGL.
|
||||
*/
|
||||
void setup_common_state(SharedRenderState* render_state);
|
||||
|
||||
/*!
|
||||
* If you don't use the render interface, call this at the very end.
|
||||
*/
|
||||
void flush_pending(SharedRenderState* render_state);
|
||||
|
||||
private:
|
||||
void handle_ad(const u8* data, SharedRenderState* render_state);
|
||||
void handle_zbuf1(u64 val);
|
||||
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_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(u64 val, SharedRenderState* render_state);
|
||||
void handle_tex1_1(u64 val);
|
||||
void handle_texa(u64 val);
|
||||
|
||||
void handle_xyzf2_common(u32 x, u32 y, u32 z, u8 f, SharedRenderState* render_state);
|
||||
|
||||
void update_gl_prim(SharedRenderState* render_state);
|
||||
void update_gl_blend();
|
||||
void update_gl_test();
|
||||
void update_gl_texture(SharedRenderState* render_state);
|
||||
|
||||
void upload_texture(TextureRecord* tex);
|
||||
|
||||
struct TestState {
|
||||
void from_register(GsTest reg);
|
||||
|
||||
GsTest current_register;
|
||||
bool alpha_test_enable = false;
|
||||
bool prim_alpha_enable = false;
|
||||
GsTest::AlphaTest alpha_test = GsTest::AlphaTest::NOTEQUAL;
|
||||
u8 aref = 0;
|
||||
GsTest::AlphaFail afail = GsTest::AlphaFail::KEEP;
|
||||
bool date = false;
|
||||
bool datm = false;
|
||||
bool zte = true;
|
||||
GsTest::ZTest ztst = GsTest::ZTest::GEQUAL;
|
||||
|
||||
} m_test_state;
|
||||
|
||||
struct BlendState {
|
||||
void from_register(GsAlpha reg);
|
||||
|
||||
GsAlpha current_register;
|
||||
GsAlpha::BlendMode a = GsAlpha::BlendMode::SOURCE;
|
||||
GsAlpha::BlendMode b = GsAlpha::BlendMode::DEST;
|
||||
GsAlpha::BlendMode c = GsAlpha::BlendMode::SOURCE;
|
||||
GsAlpha::BlendMode d = GsAlpha::BlendMode::DEST;
|
||||
bool alpha_blend_enable = false;
|
||||
u8 fix = 0;
|
||||
|
||||
} m_blend_state;
|
||||
|
||||
// state set through the prim register that requires changing GL stuff.
|
||||
struct PrimGlState {
|
||||
void from_register(GsPrim reg);
|
||||
|
||||
GsPrim current_register;
|
||||
bool gouraud_enable = false;
|
||||
bool texture_enable = false;
|
||||
bool fogging_enable = false;
|
||||
|
||||
bool aa_enable = false;
|
||||
bool use_uv = false; // todo: might not require a gl state change
|
||||
bool ctxt = false; // do they ever use ctxt2?
|
||||
bool fix = false; // what does this even do?
|
||||
} m_prim_gl_state;
|
||||
|
||||
struct TextureState {
|
||||
GsTex0 current_register;
|
||||
u32 texture_base_ptr = 0;
|
||||
bool using_mt4hh = false;
|
||||
} m_texture_state;
|
||||
|
||||
// state set through the prim/rgbaq register that doesn't require changing GL stuff
|
||||
struct PrimBuildState {
|
||||
GsPrim::Kind kind = GsPrim::Kind::PRIM_7;
|
||||
math::Vector<u8, 4> rgba_reg = {0, 0, 0, 0};
|
||||
math::Vector<float, 2> st_reg;
|
||||
|
||||
std::array<math::Vector<u8, 4>, 3> building_rgba;
|
||||
std::array<math::Vector<u32, 3>, 3> building_vert;
|
||||
std::array<math::Vector<float, 2>, 3> building_st;
|
||||
int building_idx = 0;
|
||||
int tri_strip_startup = 0;
|
||||
|
||||
float Q = 0;
|
||||
|
||||
} m_prim_building;
|
||||
|
||||
struct PrimitiveBuffer {
|
||||
PrimitiveBuffer(int max_triangles);
|
||||
std::vector<math::Vector<u8, 4>> rgba_u8;
|
||||
std::vector<math::Vector<u32, 3>> verts;
|
||||
std::vector<math::Vector<float, 2>> sts;
|
||||
int vert_count = 0;
|
||||
int max_verts = 0;
|
||||
|
||||
// leave 6 free on the end so we always have room to flush one last primitive.
|
||||
bool is_full() { return max_verts < (vert_count + 18); }
|
||||
void push(const math::Vector<u8, 4>& rgba,
|
||||
const math::Vector<u32, 3>& vert,
|
||||
const math::Vector<float, 2>& st);
|
||||
} m_prim_buffer;
|
||||
|
||||
struct {
|
||||
GLuint vertex_buffer, color_buffer, st_buffer;
|
||||
u32 vertex_buffer_bytes = 0;
|
||||
u32 color_buffer_bytes = 0;
|
||||
u32 st_buffer_bytes = 0;
|
||||
} m_ogl;
|
||||
|
||||
int m_triangles = 0;
|
||||
|
||||
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;
|
||||
};
|
189
game/graphics/opengl_renderer/OpenGLRenderer.cpp
Normal file
189
game/graphics/opengl_renderer/OpenGLRenderer.cpp
Normal file
@ -0,0 +1,189 @@
|
||||
#include "OpenGLRenderer.h"
|
||||
|
||||
#include "common/log/log.h"
|
||||
#include "game/graphics/pipelines/opengl.h"
|
||||
#include "game/graphics/opengl_renderer/DirectRenderer.h"
|
||||
|
||||
// for the vif callback
|
||||
#include "game/kernel/kmachine.h"
|
||||
|
||||
/*!
|
||||
* OpenGL Error callback. If we do something invalid, this will be called.
|
||||
*/
|
||||
void GLAPIENTRY opengl_error_callback(GLenum source,
|
||||
GLenum type,
|
||||
GLuint id,
|
||||
GLenum severity,
|
||||
GLsizei /*length*/,
|
||||
const GLchar* message,
|
||||
const void* /*userParam*/) {
|
||||
if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) {
|
||||
return;
|
||||
} else if (severity == GL_DEBUG_SEVERITY_LOW) {
|
||||
lg::info("OpenGL message 0x{:X} S{:X} T{:X}: {}", id, source, type, message);
|
||||
} else if (severity == GL_DEBUG_SEVERITY_MEDIUM) {
|
||||
lg::warn("OpenGL warn 0x{:X} S{:X} T{:X}: {}", id, source, type, message);
|
||||
} else if (severity == GL_DEBUG_SEVERITY_HIGH) {
|
||||
lg::error("OpenGL error 0x{:X} S{:X} T{:X}: {}", id, source, type, message);
|
||||
}
|
||||
}
|
||||
|
||||
OpenGLRenderer::OpenGLRenderer(std::shared_ptr<TexturePool> texture_pool)
|
||||
: m_render_state(texture_pool) {
|
||||
// setup OpenGL errors
|
||||
|
||||
// disable specific errors
|
||||
const GLuint l_gl_error_ignores[1] = {
|
||||
0x64 // [API-PERFORMANCE] glDrawArrays uses non-native input attribute type
|
||||
};
|
||||
glEnable(GL_DEBUG_OUTPUT);
|
||||
glDebugMessageCallback(opengl_error_callback, nullptr);
|
||||
// filter
|
||||
glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_PERFORMANCE, GL_DONT_CARE, 1,
|
||||
&l_gl_error_ignores[0], GL_FALSE);
|
||||
|
||||
// initialize all renderers
|
||||
init_bucket_renderers();
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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>("debug-draw-0", BucketId::DEBUG_DRAW_0, 102);
|
||||
init_bucket_renderer<DirectRenderer>("debug-draw-1", BucketId::DEBUG_DRAW_1, 102);
|
||||
|
||||
// for now, for any unset renderers, just set them to an EmptyBucketRenderer.
|
||||
for (size_t i = 0; i < m_bucket_renderers.size(); i++) {
|
||||
if (!m_bucket_renderers[i]) {
|
||||
init_bucket_renderer<EmptyBucketRenderer>(fmt::format("bucket{}", i), (BucketId)i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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) {
|
||||
setup_frame(window_width_px, window_height_px);
|
||||
draw_test_triangle();
|
||||
// render the buckets!
|
||||
dispatch_buckets(dma);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Pre-render frame setup.
|
||||
*/
|
||||
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);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
/*!
|
||||
* This function finds buckets and dispatches them to the appropriate part.
|
||||
*/
|
||||
void OpenGLRenderer::dispatch_buckets(DmaFollower dma) {
|
||||
// The first thing the DMA chain should be a call to a common default-registers chain.
|
||||
// this chain resets the state of the GS. After this is buckets
|
||||
|
||||
m_render_state.buckets_base =
|
||||
dma.current_tag_offset() + 16; // offset by 1 qw for the initial call
|
||||
m_render_state.next_bucket = m_render_state.buckets_base;
|
||||
|
||||
// Find the default regs buffer
|
||||
auto initial_call_tag = dma.current_tag();
|
||||
assert(initial_call_tag.kind == DmaTag::Kind::CALL);
|
||||
auto initial_call_default_regs = dma.read_and_advance();
|
||||
assert(initial_call_default_regs.transferred_tag == 0); // should be a nop.
|
||||
m_render_state.default_regs_buffer = dma.current_tag_offset();
|
||||
auto default_regs_tag = dma.current_tag();
|
||||
assert(default_regs_tag.kind == DmaTag::Kind::CNT);
|
||||
assert(default_regs_tag.qwc == 10);
|
||||
// TODO verify data in here.
|
||||
dma.read_and_advance();
|
||||
auto default_ret_tag = dma.current_tag();
|
||||
assert(default_ret_tag.qwc == 0);
|
||||
assert(default_ret_tag.kind == DmaTag::Kind::RET);
|
||||
dma.read_and_advance();
|
||||
|
||||
// now we should point to the first bucket!
|
||||
assert(dma.current_tag_offset() == m_render_state.next_bucket);
|
||||
m_render_state.next_bucket += 16;
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// TODO ending data.
|
||||
}
|
||||
|
||||
void OpenGLRenderer::draw_test_triangle() {
|
||||
// just remembering how to use opengl here.
|
||||
|
||||
//////////
|
||||
// Setup
|
||||
//////////
|
||||
|
||||
// create "buffer object names"
|
||||
GLuint vertex_buffer, color_buffer, vao;
|
||||
glGenBuffers(1, &vertex_buffer);
|
||||
glGenBuffers(1, &color_buffer);
|
||||
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
|
||||
// set vertex data
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
|
||||
const float verts[9] = {0.0, 0.8, 0, -0.5, -0.5 * .866, 0, 0.5, -0.5 * .866, 0};
|
||||
glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), verts, GL_STATIC_DRAW);
|
||||
|
||||
// set color data
|
||||
glBindBuffer(GL_ARRAY_BUFFER, color_buffer);
|
||||
const float colors[12] = {1., 0, 0., 1., 0., 1., 0., 1., 0., 0., 1., 1.};
|
||||
glBufferData(GL_ARRAY_BUFFER, 12 * sizeof(float), colors, GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
//////////
|
||||
// Draw!
|
||||
//////////
|
||||
m_render_state.shaders[ShaderId::TEST_SHADER].activate();
|
||||
|
||||
// location 0: the vertices
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, // location 0 in the shader
|
||||
3, // 3 floats per vert
|
||||
GL_FLOAT, // floats
|
||||
GL_FALSE, // normalized, ignored,
|
||||
0, // tightly packed
|
||||
0 // offset in array (why is is this a pointer...)
|
||||
);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, color_buffer);
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)0);
|
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glBindVertexArray(0);
|
||||
|
||||
////////////
|
||||
// Clean Up
|
||||
////////////
|
||||
// delete buffer
|
||||
glDeleteBuffers(1, &color_buffer);
|
||||
glDeleteBuffers(1, &vertex_buffer);
|
||||
glDeleteVertexArrays(1, &vao);
|
||||
}
|
29
game/graphics/opengl_renderer/OpenGLRenderer.h
Normal file
29
game/graphics/opengl_renderer/OpenGLRenderer.h
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include "game/graphics/dma/dma_chain_read.h"
|
||||
#include "game/graphics/opengl_renderer/Shader.h"
|
||||
#include "game/graphics/opengl_renderer/BucketRenderer.h"
|
||||
|
||||
class OpenGLRenderer {
|
||||
public:
|
||||
OpenGLRenderer(std::shared_ptr<TexturePool> texture_pool);
|
||||
void render(DmaFollower dma, int window_width_px, int window_height_px);
|
||||
|
||||
private:
|
||||
void setup_frame(int window_width_px, int window_height_px);
|
||||
void draw_test_triangle();
|
||||
void dispatch_buckets(DmaFollower dma);
|
||||
void init_bucket_renderers();
|
||||
|
||||
template <typename T, class... Args>
|
||||
void init_bucket_renderer(const std::string& name, BucketId id, Args&&... args) {
|
||||
m_bucket_renderers.at((int)id) = std::make_unique<T>(name, id, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
SharedRenderState m_render_state;
|
||||
|
||||
std::array<std::unique_ptr<BucketRenderer>, (int)BucketId::MAX_BUCKETS> m_bucket_renderers;
|
||||
};
|
70
game/graphics/opengl_renderer/Shader.cpp
Normal file
70
game/graphics/opengl_renderer/Shader.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
#include "Shader.h"
|
||||
#include "common/util/assert.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "game/graphics/pipelines/opengl.h"
|
||||
|
||||
Shader::Shader(const std::string& shader_name) {
|
||||
// read the shader source
|
||||
auto vert_src =
|
||||
file_util::read_text_file(file_util::get_file_path({shader_folder, shader_name + ".vert"}));
|
||||
auto frag_src =
|
||||
file_util::read_text_file(file_util::get_file_path({shader_folder, shader_name + ".frag"}));
|
||||
|
||||
m_vert_shader = glCreateShader(GL_VERTEX_SHADER);
|
||||
const char* src = vert_src.c_str();
|
||||
glShaderSource(m_vert_shader, 1, &src, nullptr);
|
||||
glCompileShader(m_vert_shader);
|
||||
|
||||
constexpr int len = 1024;
|
||||
int compile_ok;
|
||||
char err[len];
|
||||
|
||||
glGetShaderiv(m_vert_shader, GL_COMPILE_STATUS, &compile_ok);
|
||||
if (!compile_ok) {
|
||||
glGetShaderInfoLog(m_vert_shader, len, nullptr, err);
|
||||
printf("Failed to compile vertex shader %s:\n%s\n", shader_name.c_str(), err);
|
||||
m_is_okay = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_frag_shader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
src = frag_src.c_str();
|
||||
glShaderSource(m_frag_shader, 1, &src, nullptr);
|
||||
glCompileShader(m_frag_shader);
|
||||
|
||||
glGetShaderiv(m_frag_shader, GL_COMPILE_STATUS, &compile_ok);
|
||||
if (!compile_ok) {
|
||||
glGetShaderInfoLog(m_frag_shader, len, nullptr, err);
|
||||
printf("Failed to compile fragment shader %s:\n%s\n", shader_name.c_str(), err);
|
||||
m_is_okay = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_program = glCreateProgram();
|
||||
glAttachShader(m_program, m_vert_shader);
|
||||
glAttachShader(m_program, m_frag_shader);
|
||||
glLinkProgram(m_program);
|
||||
|
||||
glGetProgramiv(m_program, GL_LINK_STATUS, &compile_ok);
|
||||
if (!compile_ok) {
|
||||
glGetProgramInfoLog(m_program, len, nullptr, err);
|
||||
printf("Failed to link shader %s:\n%s\n", shader_name.c_str(), err);
|
||||
m_is_okay = false;
|
||||
return;
|
||||
}
|
||||
|
||||
glDeleteShader(m_vert_shader);
|
||||
glDeleteShader(m_frag_shader);
|
||||
m_is_okay = true;
|
||||
}
|
||||
|
||||
void Shader::activate() {
|
||||
assert(m_is_okay);
|
||||
glUseProgram(m_program);
|
||||
}
|
||||
|
||||
ShaderLibrary::ShaderLibrary() {
|
||||
at(ShaderId::TEST_SHADER) = {"test_shader"};
|
||||
at(ShaderId::DIRECT_BASIC) = {"direct_basic"};
|
||||
at(ShaderId::DIRECT_BASIC_TEXTURED) = {"direct_basic_textured"};
|
||||
}
|
35
game/graphics/opengl_renderer/Shader.h
Normal file
35
game/graphics/opengl_renderer/Shader.h
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
class Shader {
|
||||
public:
|
||||
static constexpr char shader_folder[] = "game/graphics/opengl_renderer/shaders/";
|
||||
Shader(const std::string& shader_name);
|
||||
Shader() = default;
|
||||
void activate();
|
||||
bool okay() const { return m_is_okay; }
|
||||
u64 id() const { return m_program; }
|
||||
|
||||
private:
|
||||
u64 m_frag_shader = 0;
|
||||
u64 m_vert_shader = 0;
|
||||
u64 m_program = 0;
|
||||
bool m_is_okay = false;
|
||||
};
|
||||
|
||||
// note: update the constructor in Shader.cpp
|
||||
enum class ShaderId { TEST_SHADER = 0, DIRECT_BASIC = 1, DIRECT_BASIC_TEXTURED = 2, MAX_SHADERS };
|
||||
|
||||
class ShaderLibrary {
|
||||
public:
|
||||
ShaderLibrary();
|
||||
Shader& operator[](ShaderId id) { return m_shaders[(int)id]; }
|
||||
|
||||
Shader& at(ShaderId id) { return m_shaders[(int)id]; }
|
||||
|
||||
private:
|
||||
Shader m_shaders[(int)ShaderId::MAX_SHADERS];
|
||||
};
|
9
game/graphics/opengl_renderer/shaders/direct_basic.frag
Normal file
9
game/graphics/opengl_renderer/shaders/direct_basic.frag
Normal file
@ -0,0 +1,9 @@
|
||||
#version 330 core
|
||||
|
||||
out vec4 color;
|
||||
|
||||
in vec4 fragment_color;
|
||||
|
||||
void main() {
|
||||
color = fragment_color;
|
||||
}
|
11
game/graphics/opengl_renderer/shaders/direct_basic.vert
Normal file
11
game/graphics/opengl_renderer/shaders/direct_basic.vert
Normal file
@ -0,0 +1,11 @@
|
||||
#version 330 core
|
||||
|
||||
layout (location = 0) in vec3 position_in;
|
||||
layout (location = 1) in vec4 rgba_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(rgba_in.x, rgba_in.y, rgba_in.z, rgba_in.w + 0.5);
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
#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);
|
||||
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.w * 2.);
|
||||
tex_coord = tex_coord_in;
|
||||
}
|
9
game/graphics/opengl_renderer/shaders/test_shader.frag
Normal file
9
game/graphics/opengl_renderer/shaders/test_shader.frag
Normal file
@ -0,0 +1,9 @@
|
||||
#version 330 core
|
||||
|
||||
out vec3 color;
|
||||
|
||||
in vec4 fragment_color;
|
||||
|
||||
void main() {
|
||||
color = fragment_color.xyz;
|
||||
}
|
11
game/graphics/opengl_renderer/shaders/test_shader.vert
Normal file
11
game/graphics/opengl_renderer/shaders/test_shader.vert
Normal file
@ -0,0 +1,11 @@
|
||||
#version 330 core
|
||||
|
||||
layout (location = 0) in vec3 position_in;
|
||||
layout (location = 1) in vec4 rgba_in;
|
||||
|
||||
out vec4 fragment_color;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(position_in, 1.0);
|
||||
fragment_color = rgba_in;
|
||||
}
|
279
game/graphics/pipelines/opengl.cpp
Normal file
279
game/graphics/pipelines/opengl.cpp
Normal file
@ -0,0 +1,279 @@
|
||||
/*!
|
||||
* @file opengl.cpp
|
||||
* Lower-level OpenGL implementation.
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "opengl.h"
|
||||
|
||||
#include "game/graphics/gfx.h"
|
||||
#include "game/graphics/display.h"
|
||||
#include "game/graphics/opengl_renderer/OpenGLRenderer.h"
|
||||
#include "game/graphics/texture/TexturePool.h"
|
||||
#include "game/graphics/dma/dma_copy.h"
|
||||
#include "common/log/log.h"
|
||||
#include "common/goal_constants.h"
|
||||
#include "game/runtime.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct GraphicsData {
|
||||
// vsync
|
||||
std::mutex sync_mutex;
|
||||
std::condition_variable sync_cv;
|
||||
|
||||
// dma chain transfer
|
||||
std::mutex dma_mutex;
|
||||
std::condition_variable dma_cv;
|
||||
u64 frame_idx = 0;
|
||||
bool has_data_to_render = false;
|
||||
FixedChunkDmaCopier dma_copier;
|
||||
|
||||
// texture pool
|
||||
std::shared_ptr<TexturePool> texture_pool;
|
||||
|
||||
// temporary opengl renderer
|
||||
OpenGLRenderer ogl_renderer;
|
||||
|
||||
GraphicsData()
|
||||
: dma_copier(EE_MAIN_MEM_SIZE),
|
||||
texture_pool(std::make_shared<TexturePool>()),
|
||||
ogl_renderer(texture_pool) {}
|
||||
};
|
||||
|
||||
std::unique_ptr<GraphicsData> g_gfx_data;
|
||||
|
||||
void SetDisplayCallbacks(GLFWwindow* d) {
|
||||
glfwSetKeyCallback(d, [](GLFWwindow* /*window*/, int key, int scancode, int action, int mods) {
|
||||
if (action == GlfwKeyAction::Press) {
|
||||
lg::debug("KEY PRESS: key: {} scancode: {} mods: {:X}", key, scancode, mods);
|
||||
} else if (action == GlfwKeyAction::Release) {
|
||||
lg::debug("KEY RELEASE: key: {} scancode: {} mods: {:X}", key, scancode, mods);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ErrorCallback(int err, const char* msg) {
|
||||
lg::error("GLFW ERR {}: " + std::string(msg), err);
|
||||
}
|
||||
|
||||
bool HasError() {
|
||||
return glfwGetError(NULL) != GLFW_NO_ERROR;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static bool gl_inited = false;
|
||||
static int gl_init() {
|
||||
if (glfwSetErrorCallback(ErrorCallback) != NULL) {
|
||||
lg::warn("glfwSetErrorCallback has been re-set!");
|
||||
}
|
||||
|
||||
if (glfwInit() == GLFW_FALSE) {
|
||||
lg::error("glfwInit error");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// request Debug OpenGL 3.3 Core
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // 3.3
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
// glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // debug
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // core profile, not compat
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gl_exit() {
|
||||
g_gfx_data.reset();
|
||||
glfwTerminate();
|
||||
glfwSetErrorCallback(NULL);
|
||||
gl_inited = false;
|
||||
}
|
||||
|
||||
static std::shared_ptr<GfxDisplay> gl_make_main_display(int width,
|
||||
int height,
|
||||
const char* title,
|
||||
GfxSettings& settings) {
|
||||
GLFWwindow* window = glfwCreateWindow(width, height, title, NULL, NULL);
|
||||
|
||||
if (!window) {
|
||||
lg::error("gl_make_main_display failed - Could not create display window");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent(window);
|
||||
|
||||
if (!gl_inited && !gladLoadGL()) {
|
||||
lg::error("GL init fail");
|
||||
return NULL;
|
||||
}
|
||||
g_gfx_data = std::make_unique<GraphicsData>();
|
||||
gl_inited = true;
|
||||
|
||||
// enable vsync by default
|
||||
// glfwSwapInterval(1);
|
||||
glfwSwapInterval(settings.vsync);
|
||||
|
||||
SetDisplayCallbacks(window);
|
||||
|
||||
if (HasError()) {
|
||||
lg::error("gl_make_main_display error");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::shared_ptr<GfxDisplay> display = std::make_shared<GfxDisplay>(window);
|
||||
// lg::debug("init display #x{:x}", (uintptr_t)display);
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
static void gl_kill_display(GfxDisplay* display) {
|
||||
glfwDestroyWindow(display->window_glfw);
|
||||
}
|
||||
|
||||
static void gl_render_display(GfxDisplay* display) {
|
||||
GLFWwindow* window = display->window_glfw;
|
||||
|
||||
glfwMakeContextCurrent(window);
|
||||
|
||||
// wait for a copied chain.
|
||||
bool got_chain = false;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(g_gfx_data->dma_mutex);
|
||||
// note: there's a timeout here. If the engine is messed up and not sending us frames,
|
||||
// we still want to run the glfw loop.
|
||||
got_chain = g_gfx_data->dma_cv.wait_for(lock, std::chrono::milliseconds(20),
|
||||
[=] { return g_gfx_data->has_data_to_render; });
|
||||
}
|
||||
|
||||
// render that chain.
|
||||
if (got_chain) {
|
||||
auto& chain = g_gfx_data->dma_copier.get_last_result();
|
||||
int width, height;
|
||||
glfwGetFramebufferSize(window, &width, &height);
|
||||
g_gfx_data->ogl_renderer.render(DmaFollower(chain.data.data(), chain.start_offset), width,
|
||||
height);
|
||||
}
|
||||
|
||||
// before vsync, mark the chain as rendered.
|
||||
{
|
||||
// should be fine to remove this mutex if the game actually waits for vsync to call
|
||||
// send_chain again. but let's be safe for now.
|
||||
std::unique_lock<std::mutex> lock(g_gfx_data->dma_mutex);
|
||||
g_gfx_data->has_data_to_render = false;
|
||||
g_gfx_data->sync_cv.notify_all();
|
||||
}
|
||||
|
||||
// actual vsync
|
||||
glfwSwapBuffers(window);
|
||||
|
||||
// toggle even odd and wake up engine waiting on vsync.
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(g_gfx_data->sync_mutex);
|
||||
g_gfx_data->frame_idx++;
|
||||
g_gfx_data->sync_cv.notify_all();
|
||||
}
|
||||
// poll events TODO integrate input with cpad
|
||||
glfwPollEvents();
|
||||
|
||||
// exit if display window was closed
|
||||
if (glfwWindowShouldClose(window)) {
|
||||
// Display::KillDisplay(window);
|
||||
MasterExit = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Wait for the next vsync. Returns 0 or 1 depending on if frame is even or odd.
|
||||
* Called from the game thread, on a GOAL stack.
|
||||
*/
|
||||
u32 gl_vsync() {
|
||||
if (!g_gfx_data) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(g_gfx_data->sync_mutex);
|
||||
auto init_frame = g_gfx_data->frame_idx;
|
||||
g_gfx_data->sync_cv.wait(lock, [=] { return g_gfx_data->frame_idx > init_frame; });
|
||||
|
||||
return g_gfx_data->frame_idx & 1;
|
||||
}
|
||||
|
||||
u32 gl_sync_path() {
|
||||
if (!g_gfx_data) {
|
||||
return 0;
|
||||
}
|
||||
std::unique_lock<std::mutex> lock(g_gfx_data->sync_mutex);
|
||||
if (!g_gfx_data->has_data_to_render) {
|
||||
return 0;
|
||||
}
|
||||
g_gfx_data->sync_cv.wait(lock, [=] { return !g_gfx_data->has_data_to_render; });
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Send DMA to the renderer.
|
||||
* Called from the game thread, on a GOAL stack.
|
||||
*/
|
||||
void gl_send_chain(const void* data, u32 offset) {
|
||||
if (g_gfx_data) {
|
||||
std::unique_lock<std::mutex> lock(g_gfx_data->dma_mutex);
|
||||
if (g_gfx_data->has_data_to_render) {
|
||||
lg::error(
|
||||
"Gfx::send_chain called when the graphics renderer has pending data. Was this called "
|
||||
"multiple times per frame?");
|
||||
return;
|
||||
}
|
||||
|
||||
// we copy the dma data and give a copy of it to the render.
|
||||
// the copy has a few advantages:
|
||||
// - if the game code has a bug and corrupts the DMA buffer, the renderer won't see it.
|
||||
// - the copied DMA is much smaller than the entire game memory, so it can be dumped to a file
|
||||
// separate of the entire RAM.
|
||||
// - it verifies the DMA data is valid early on.
|
||||
// but it may also be pretty expensive. Both the renderer and the game wait on this to complete.
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
void gl_texture_upload_now(const u8* tpage, int mode, u32 s7_ptr) {
|
||||
if (g_gfx_data) {
|
||||
// just pass it to the texture pool.
|
||||
// the texture pool will take care of locking.
|
||||
// we don't want to lock here for the entire duration of the conversion.
|
||||
g_gfx_data->texture_pool->handle_upload_now(tpage, mode, g_ee_main_mem, s7_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void gl_texture_relocate(u32 destination, u32 source, u32 format) {
|
||||
if (g_gfx_data) {
|
||||
g_gfx_data->texture_pool->relocate(destination, source, format);
|
||||
}
|
||||
}
|
||||
|
||||
const GfxRendererModule moduleOpenGL = {
|
||||
gl_init, // init
|
||||
gl_make_main_display, // make_main_display
|
||||
gl_kill_display, // kill_display
|
||||
gl_render_display, // render_display
|
||||
gl_exit, // exit
|
||||
gl_vsync, // vsync
|
||||
gl_sync_path, // sync_path
|
||||
gl_send_chain, // send_chain
|
||||
gl_texture_upload_now, // texture_upload_now
|
||||
gl_texture_relocate, // texture_relocate
|
||||
GfxPipeline::OpenGL, // pipeline
|
||||
"OpenGL 3.0" // name
|
||||
};
|
20
game/graphics/pipelines/opengl.h
Normal file
20
game/graphics/pipelines/opengl.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
/*!
|
||||
* @file opengl.h
|
||||
* OpenGL includes.
|
||||
*/
|
||||
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include <glad/glad.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include "game/graphics/gfx.h"
|
||||
|
||||
enum GlfwKeyAction {
|
||||
Release = GLFW_RELEASE, // falling edge of key press
|
||||
Press = GLFW_PRESS, // rising edge of key press
|
||||
Repeat = GLFW_REPEAT // repeated input on hold e.g. when typing something
|
||||
};
|
||||
|
||||
extern const GfxRendererModule moduleOpenGL;
|
33
game/graphics/sceGraphicsInterface.cpp
Normal file
33
game/graphics/sceGraphicsInterface.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include "game/graphics/sceGraphicsInterface.h"
|
||||
#include "common/util/assert.h"
|
||||
#include "game/graphics/gfx.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
/*!
|
||||
* Wait for rendering to complete.
|
||||
* In the PC Port, this currently does nothing.
|
||||
*
|
||||
* From my current understanding, we can get away with this and just sync everything on vsync.
|
||||
* However, there are two calls to this per frame.
|
||||
*
|
||||
* But I don't fully understand why they call sceGsSyncPath where they do (right before depth cue)
|
||||
* so maybe the depth cue looks at the z-buffer of the last rendered frame when setting up the dma
|
||||
* for the next frame? The debug drawing also happens after this.
|
||||
*
|
||||
* The second call is right before swapping buffers/vsync, so that makes sense.
|
||||
*
|
||||
*
|
||||
*/
|
||||
u32 sceGsSyncPath(u32 mode, u32 timeout) {
|
||||
assert(mode == 0 && timeout == 0);
|
||||
return Gfx::sync_path();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Actual vsync.
|
||||
*/
|
||||
u32 sceGsSyncV(u32 mode) {
|
||||
assert(mode == 0);
|
||||
return Gfx::vsync();
|
||||
}
|
13
game/graphics/sceGraphicsInterface.h
Normal file
13
game/graphics/sceGraphicsInterface.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/util/assert.h"
|
||||
|
||||
/*!
|
||||
* @file sceGraphicInterface.h
|
||||
* This file contains implementations of the SCE graphics library functions that manage
|
||||
* synchronization and settings.
|
||||
*/
|
||||
|
||||
u32 sceGsSyncPath(u32 mode, u32 timeout);
|
||||
u32 sceGsSyncV(u32 mode);
|
177
game/graphics/texture/TextureConverter.cpp
Normal file
177
game/graphics/texture/TextureConverter.cpp
Normal file
@ -0,0 +1,177 @@
|
||||
#include "TextureConverter.h"
|
||||
#include "common/util/assert.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/texture/texture_conversion.h"
|
||||
|
||||
TextureConverter::TextureConverter() {
|
||||
m_vram.resize(4 * 1024 * 1024);
|
||||
}
|
||||
|
||||
void TextureConverter::upload(const u8* data, u32 dest, u32 size_vram_words) {
|
||||
// all textures are copied to vram 128 pixels wide, regardless of actual width
|
||||
int copy_width = 128;
|
||||
// scale the copy height to be whatever it needs to be to transfer the right amount of data.
|
||||
int copy_height = size_vram_words / copy_width;
|
||||
|
||||
for (int y = 0; y < copy_height; y++) {
|
||||
for (int x = 0; x < copy_width; x++) {
|
||||
// VRAM address (bytes)
|
||||
auto addr32 = psmct32_addr(x, y, copy_width) + dest * 4;
|
||||
*(u32*)(m_vram.data() + addr32) = *((const u32*)(data) + (x + y * copy_width));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextureConverter::download_rgba8888(u8* result,
|
||||
u32 vram_addr,
|
||||
u32 goal_tex_width,
|
||||
u32 w,
|
||||
u32 h,
|
||||
u32 psm,
|
||||
u32 clut_psm,
|
||||
u32 clut_vram_addr,
|
||||
u32 expected_size_bytes) {
|
||||
u32 out_offset = 0;
|
||||
if (psm == int(PSM::PSMT8) && clut_psm == int(CPSM::PSMCT32)) {
|
||||
// width is like the TEX0 register, in 64 texel units.
|
||||
// not sure what the other widths are yet.
|
||||
int read_width = 64 * goal_tex_width;
|
||||
// loop over pixels in output texture image
|
||||
for (u32 y = 0; y < h; y++) {
|
||||
for (u32 x = 0; x < w; x++) {
|
||||
// read as the PSMT8 type. The dest field tells us a block offset.
|
||||
auto addr8 = psmt8_addr(x, y, read_width) + vram_addr * 256;
|
||||
u8 value = *(u8*)(m_vram.data() + addr8);
|
||||
|
||||
// there's yet another scramble from the CLUT. The palette index turns into an X, Y value
|
||||
// See GS manual 2.7.3 CLUT Storage Mode, IDTEX8 in CSM1 mode.
|
||||
u32 clut_chunk = value / 16;
|
||||
u32 off_in_chunk = value % 16;
|
||||
u8 clx = 0, cly = 0;
|
||||
if (clut_chunk & 1) {
|
||||
clx = 8;
|
||||
}
|
||||
cly = (clut_chunk >> 1) * 2;
|
||||
if (off_in_chunk >= 8) {
|
||||
off_in_chunk -= 8;
|
||||
cly++;
|
||||
}
|
||||
clx += off_in_chunk;
|
||||
|
||||
// the x, y CLUT value is looked up in PSMCT32 mode
|
||||
u32 clut_addr = psmct32_addr(clx, cly, 64) + clut_vram_addr * 256;
|
||||
u32 clut_value = *(u32*)(m_vram.data() + clut_addr);
|
||||
memcpy(result + out_offset, &clut_value, 4);
|
||||
out_offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (psm == int(PSM::PSMT8) && clut_psm == int(CPSM::PSMCT16)) {
|
||||
// width is like the TEX0 register, in 64 texel units.
|
||||
// not sure what the other widths are yet.
|
||||
int read_width = 64 * goal_tex_width;
|
||||
|
||||
// loop over pixels in output texture image
|
||||
for (u32 y = 0; y < h; y++) {
|
||||
for (u32 x = 0; x < w; x++) {
|
||||
// read as the PSMT8 type. The dest field tells us a block offset.
|
||||
auto addr8 = psmt8_addr(x, y, read_width) + vram_addr * 256;
|
||||
u8 value = *(u8*)(m_vram.data() + addr8);
|
||||
|
||||
// there's yet another scramble from the CLUT. The palette index turns into an X, Y value
|
||||
// See GS manual 2.7.3 CLUT Storage Mode, IDTEX8 in CSM1 mode.
|
||||
u32 clut_chunk = value / 16;
|
||||
u32 off_in_chunk = value % 16;
|
||||
u8 clx = 0, cly = 0;
|
||||
if (clut_chunk & 1) {
|
||||
clx = 8;
|
||||
}
|
||||
cly = (clut_chunk >> 1) * 2;
|
||||
if (off_in_chunk >= 8) {
|
||||
off_in_chunk -= 8;
|
||||
cly++;
|
||||
}
|
||||
clx += off_in_chunk;
|
||||
|
||||
// the x, y CLUT value is looked up in PSMCT32 mode
|
||||
u32 clut_addr = psmct16_addr(clx, cly, 64) + clut_vram_addr * 256;
|
||||
u32 clut_value = *(u16*)(m_vram.data() + clut_addr);
|
||||
u32 rgba32 = rgba16_to_rgba32(clut_value);
|
||||
memcpy(result + out_offset, &rgba32, 4);
|
||||
out_offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (psm == int(PSM::PSMT4) && clut_psm == int(CPSM::PSMCT16)) {
|
||||
// width is like the TEX0 register, in 64 texel units.
|
||||
// not sure what the other widths are yet.
|
||||
int read_width = 64 * goal_tex_width;
|
||||
|
||||
// loop over pixels in output texture image
|
||||
for (u32 y = 0; y < h; y++) {
|
||||
for (u32 x = 0; x < w; x++) {
|
||||
// read as the PSMT4 type, use half byte addressing
|
||||
auto addr4 = psmt4_addr_half_byte(x, y, read_width) + vram_addr * 512;
|
||||
|
||||
// read (half bytes)
|
||||
u8 value = *(u8*)(m_vram.data() + addr4 / 2);
|
||||
if (addr4 & 1) {
|
||||
value >>= 4;
|
||||
} else {
|
||||
value = value & 0x0f;
|
||||
}
|
||||
|
||||
// there's yet another scramble from the CLUT. The palette index turns into an X, Y value
|
||||
// See GS manual 2.7.3 CLUT Storage Mode, IDTEX4 in CSM1 mode.
|
||||
|
||||
u8 clx = value & 0x7;
|
||||
u8 cly = value >> 3;
|
||||
|
||||
// the x, y CLUT value is looked up in PSMCT16 mode
|
||||
u32 clut_addr = psmct16_addr(clx, cly, 64) + clut_vram_addr * 256;
|
||||
u32 clut_value = *(u16*)(m_vram.data() + clut_addr);
|
||||
u32 rgba32 = rgba16_to_rgba32(clut_value);
|
||||
memcpy(result + out_offset, &rgba32, 4);
|
||||
out_offset += 4;
|
||||
}
|
||||
}
|
||||
} else if (psm == int(PSM::PSMT4) && clut_psm == int(CPSM::PSMCT32)) {
|
||||
// width is like the TEX0 register, in 64 texel units.
|
||||
// not sure what the other widths are yet.
|
||||
int read_width = 64 * goal_tex_width;
|
||||
|
||||
// loop over pixels in output texture image
|
||||
for (u32 y = 0; y < h; y++) {
|
||||
for (u32 x = 0; x < w; x++) {
|
||||
// read as the PSMT4 type, use half byte addressing
|
||||
auto addr4 = psmt4_addr_half_byte(x, y, read_width) + vram_addr * 512;
|
||||
|
||||
// read (half bytes)
|
||||
u8 value = *(u8*)(m_vram.data() + addr4 / 2);
|
||||
if (addr4 & 1) {
|
||||
value >>= 4;
|
||||
} else {
|
||||
value = value & 0x0f;
|
||||
}
|
||||
|
||||
// there's yet another scramble from the CLUT. The palette index turns into an X, Y value
|
||||
// See GS manual 2.7.3 CLUT Storage Mode, IDTEX4 in CSM1 mode.
|
||||
|
||||
u8 clx = value & 0x7;
|
||||
u8 cly = value >> 3;
|
||||
|
||||
// the x, y CLUT value is looked up in PSMCT16 mode
|
||||
u32 clut_addr = psmct32_addr(clx, cly, 64) + clut_vram_addr * 256;
|
||||
u32 clut_value = *(u32*)(m_vram.data() + clut_addr);
|
||||
// fmt::print("{} {}\n", value, clut_value);
|
||||
memcpy(result + out_offset, &clut_value, 4);
|
||||
out_offset += 4;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
assert(out_offset == expected_size_bytes);
|
||||
}
|
23
game/graphics/texture/TextureConverter.h
Normal file
23
game/graphics/texture/TextureConverter.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
class TextureConverter {
|
||||
public:
|
||||
TextureConverter();
|
||||
void upload(const u8* data, u32 dest, u32 size_vram_words);
|
||||
void download_rgba8888(u8* result,
|
||||
u32 vram_addr,
|
||||
u32 goal_tex_width,
|
||||
u32 w,
|
||||
u32 h,
|
||||
u32 psm,
|
||||
u32 clut_psm,
|
||||
u32 clut_vram_addr,
|
||||
u32 expected_size_bytes);
|
||||
|
||||
private:
|
||||
std::vector<u8> m_vram;
|
||||
};
|
205
game/graphics/texture/TexturePool.cpp
Normal file
205
game/graphics/texture/TexturePool.cpp
Normal file
@ -0,0 +1,205 @@
|
||||
#include "TexturePool.h"
|
||||
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "common/util/assert.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/util/Timer.h"
|
||||
#include "common/log/log.h"
|
||||
|
||||
////////////////////////////////
|
||||
// Extraction of textures
|
||||
// Note: this is intended to be temporary until we have a better system.
|
||||
// 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;
|
||||
|
||||
const char empty_string[] = "";
|
||||
const char* goal_string(u32 ptr, const u8* memory_base) {
|
||||
if (ptr == 0) {
|
||||
return empty_string;
|
||||
}
|
||||
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];
|
||||
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Handle a GOAL texture-page object being uploaded to VRAM.
|
||||
* The strategy:
|
||||
* - upload the texture-age to a fake 4MB VRAM, like the GOAL code would have done.
|
||||
* - "download" each texture in a reasonable format for the PC Port (currently RGBA8888)
|
||||
* - add this to the PC pool.
|
||||
*
|
||||
* The textures are scrambled around in a confusing way.
|
||||
*
|
||||
* NOTE: the actual conversion is currently done here, but this might be too slow.
|
||||
* We could store textures in the right format to begin with, or spread the conversion out over
|
||||
* multiple frames.
|
||||
*/
|
||||
void TexturePool::handle_upload_now(const u8* tpage, int mode, const u8* memory_base, u32 s7_ptr) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Move a texture.
|
||||
*/
|
||||
void TexturePool::relocate(u32 destination, u32 source, u32 format) {
|
||||
auto& src = m_textures.at(source).normal_texture;
|
||||
assert(src);
|
||||
if (format == 44) {
|
||||
m_textures.at(destination).mt4hh_texture = std::move(src);
|
||||
} else {
|
||||
m_textures.at(destination).normal_texture = std::move(src);
|
||||
}
|
||||
}
|
57
game/graphics/texture/TexturePool.h
Normal file
57
game/graphics/texture/TexturePool.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "game/graphics/texture/TextureConverter.h"
|
||||
|
||||
struct TextureRecord {
|
||||
std::string page_name;
|
||||
std::string name;
|
||||
u8 mip_level;
|
||||
u16 w, h;
|
||||
std::vector<u8> data;
|
||||
u8 data_segment;
|
||||
u64 gpu_texture = 0;
|
||||
bool on_gpu = false;
|
||||
};
|
||||
|
||||
struct TextureData {
|
||||
std::unique_ptr<TextureRecord> normal_texture;
|
||||
std::unique_ptr<TextureRecord> mt4hh_texture;
|
||||
};
|
||||
|
||||
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);
|
||||
TextureRecord* lookup(u32 location) {
|
||||
if (m_textures.at(location).normal_texture) {
|
||||
return m_textures[location].normal_texture.get();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
TextureRecord* lookup_mt4hh(u32 location) {
|
||||
if (m_textures.at(location).mt4hh_texture) {
|
||||
return m_textures[location].mt4hh_texture.get();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void relocate(u32 destination, u32 source, u32 format);
|
||||
|
||||
private:
|
||||
TextureConverter m_tex_converter;
|
||||
|
||||
// uses tex.dest[mip] indexing. (bytes / 256). Currently only sets the base of a texture.
|
||||
std::array<TextureData, 1024 * 1024 * 4 / 256> m_textures;
|
||||
|
||||
// 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;
|
||||
};
|
@ -105,6 +105,9 @@ s32 goal_main(int argc, const char* const* argv) {
|
||||
masterConfig.language = (u16)Language::German;
|
||||
} else if (masterConfig.language == SCE_ITALIAN_LANGUAGE) {
|
||||
masterConfig.language = (u16)Language::Italian;
|
||||
} else if (masterConfig.language == SCE_JAPANESE_LANGUAGE) {
|
||||
// Note: this case was added so it is easier to test Japanese fonts.
|
||||
masterConfig.language = (u16)Language::Japanese;
|
||||
} else {
|
||||
// pick english by default, if language is not supported.
|
||||
masterConfig.language = (u16)Language::English;
|
||||
@ -170,7 +173,7 @@ void KernelCheckAndDispatch() {
|
||||
}
|
||||
|
||||
auto time_ms = kernel_dispatch_timer.getMs();
|
||||
if (time_ms > 3) {
|
||||
if (time_ms > 30) {
|
||||
printf("Kernel dispatch time: %.3f ms\n", time_ms);
|
||||
}
|
||||
|
||||
@ -181,7 +184,9 @@ void KernelCheckAndDispatch() {
|
||||
SendAck();
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(1000));
|
||||
if (time_ms < 4) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -833,8 +833,10 @@ Ptr<uint8_t> link_and_exec(Ptr<uint8_t> data,
|
||||
/*!
|
||||
* Wrapper so this can be called from GOAL. Not in original game.
|
||||
*/
|
||||
u64 link_and_exec_wrapper(u64 data, u64 name, s64 size, u64 heap, u64 flags) {
|
||||
return link_and_exec(Ptr<u8>(data), Ptr<char>(name).c(), size, Ptr<kheapinfo>(heap), flags)
|
||||
u64 link_and_exec_wrapper(u64* args) {
|
||||
// data, name, size, heap, flags
|
||||
return link_and_exec(Ptr<u8>(args[0]), Ptr<char>(args[1]).c(), args[2], Ptr<kheapinfo>(args[3]),
|
||||
args[4])
|
||||
.offset;
|
||||
}
|
||||
|
||||
@ -843,13 +845,10 @@ u64 link_and_exec_wrapper(u64 data, u64 name, s64 size, u64 heap, u64 flags) {
|
||||
* 47 -> output_load, output_true, execute, 8, force fast
|
||||
* 39 -> no 8 (s7)
|
||||
*/
|
||||
uint64_t link_begin(uint64_t object_data,
|
||||
uint64_t name,
|
||||
int32_t size,
|
||||
uint64_t heap,
|
||||
uint32_t flags) {
|
||||
saved_link_control.begin(Ptr<u8>(object_data), Ptr<char>(name).c(), size, Ptr<kheapinfo>(heap),
|
||||
flags);
|
||||
uint64_t link_begin(u64* args) {
|
||||
// object data, name size, heap flags
|
||||
saved_link_control.begin(Ptr<u8>(args[0]), Ptr<char>(args[1]).c(), args[2],
|
||||
Ptr<kheapinfo>(args[3]), args[4]);
|
||||
auto work_result = saved_link_control.work();
|
||||
// if we managed to finish in one shot, take care of calling finish
|
||||
if (work_result) {
|
||||
|
@ -6,9 +6,6 @@
|
||||
* DONE!
|
||||
*/
|
||||
|
||||
#ifndef JAK_KLINK_H
|
||||
#define JAK_KLINK_H
|
||||
|
||||
#include "Ptr.h"
|
||||
#include "kmalloc.h"
|
||||
#include "common/link_types.h"
|
||||
@ -94,7 +91,7 @@ struct ObjectFileHeader {
|
||||
|
||||
void klink_init_globals();
|
||||
|
||||
u64 link_and_exec_wrapper(u64 data, u64 name, s64 size, u64 heap, u64 flags);
|
||||
u64 link_and_exec_wrapper(u64* args);
|
||||
|
||||
Ptr<uint8_t> link_and_exec(Ptr<uint8_t> data,
|
||||
const char* name,
|
||||
@ -102,15 +99,9 @@ Ptr<uint8_t> link_and_exec(Ptr<uint8_t> data,
|
||||
Ptr<kheapinfo> heap,
|
||||
uint32_t flags);
|
||||
|
||||
uint64_t link_begin(uint64_t object_data,
|
||||
uint64_t name,
|
||||
int32_t size,
|
||||
uint64_t heap,
|
||||
uint32_t flags);
|
||||
uint64_t link_begin(u64* args);
|
||||
|
||||
uint64_t link_resume();
|
||||
void ultimate_memcpy(void* dst, void* src, uint32_t size);
|
||||
|
||||
extern link_control saved_link_control;
|
||||
|
||||
#endif // JAK_KLINK_H
|
||||
|
@ -30,6 +30,10 @@
|
||||
#include "common/symbols.h"
|
||||
#include "common/log/log.h"
|
||||
#include "common/util/Timer.h"
|
||||
#include "game/graphics/sceGraphicsInterface.h"
|
||||
#include "game/graphics/gfx.h"
|
||||
#include "game/graphics/dma/dma_chain_read.h"
|
||||
#include "game/graphics/dma/dma_copy.h"
|
||||
|
||||
#include "game/system/vm/vm.h"
|
||||
using namespace ee;
|
||||
@ -51,12 +55,16 @@ const char* init_types[] = {"fakeiso", "deviso", "iso_cd"};
|
||||
|
||||
Timer ee_clock_timer;
|
||||
|
||||
// added
|
||||
bool machine_booted = false;
|
||||
|
||||
void kmachine_init_globals() {
|
||||
isodrv = iso_cd;
|
||||
modsrc = 1;
|
||||
reboot = 1;
|
||||
memset(pad_dma_buf, 0, sizeof(pad_dma_buf));
|
||||
ee_clock_timer = Timer();
|
||||
machine_booted = false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -417,7 +425,11 @@ u64 CPadOpen(u64 cpad_info, s32 pad_number) {
|
||||
|
||||
// TODO CPadGetData
|
||||
void CPadGetData() {
|
||||
assert(false);
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
lg::warn("ignoring calls to CPadGetData");
|
||||
warned = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO InstallHandler
|
||||
@ -429,6 +441,18 @@ void InstallDebugHandler() {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
void send_gfx_dma_chain(u32 /*bank*/, u32 chain) {
|
||||
Gfx::send_chain(g_ee_main_mem, chain);
|
||||
}
|
||||
|
||||
void pc_texture_upload_now(u32 page, u32 mode) {
|
||||
Gfx::texture_upload_now(Ptr<u8>(page).c(), mode, s7.offset);
|
||||
}
|
||||
|
||||
void pc_texture_relocate(u32 dst, u32 src, u32 format) {
|
||||
Gfx::texture_relocate(dst, src, format);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Open a file-stream. Name is a GOAL string. Mode is a GOAL symbol. Use 'read for readonly
|
||||
* and anything else for write only.
|
||||
@ -565,7 +589,7 @@ void DecodeTime() {
|
||||
|
||||
// TODO PutDisplayEnv
|
||||
void PutDisplayEnv() {
|
||||
assert(false);
|
||||
// assert(false);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -587,6 +611,19 @@ void InitMachine_PCPort() {
|
||||
// PC Port added functions
|
||||
make_function_symbol_from_c("__read-ee-timer", (void*)read_ee_timer);
|
||||
make_function_symbol_from_c("__mem-move", (void*)c_memmove);
|
||||
make_function_symbol_from_c("__send-gfx-dma-chain", (void*)send_gfx_dma_chain);
|
||||
make_function_symbol_from_c("__pc-texture-upload-now", (void*)pc_texture_upload_now);
|
||||
make_function_symbol_from_c("__pc-texture-relocate", (void*)pc_texture_relocate);
|
||||
}
|
||||
|
||||
void vif_interrupt_callback() {
|
||||
// added for the PC port for faking VIF interrupts from the graphics system.
|
||||
if (machine_booted && MasterExit == 0) {
|
||||
auto sym = intern_from_c("vif1-handler-debug");
|
||||
if (sym->value) {
|
||||
call_goal(Ptr<Function>(sym->value), 0, 0, 0, s7.offset, g_ee_main_mem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -598,7 +635,7 @@ void InitMachine_PCPort() {
|
||||
*/
|
||||
void InitMachineScheme() {
|
||||
make_function_symbol_from_c("put-display-env", (void*)PutDisplayEnv); // used in drawable
|
||||
make_function_symbol_from_c("syncv", (void*)ee::sceGsSyncV); // used in drawable
|
||||
make_function_symbol_from_c("syncv", (void*)sceGsSyncV); // used in drawable
|
||||
make_function_symbol_from_c("sync-path", (void*)sceGsSyncPath); // used
|
||||
make_function_symbol_from_c("reset-path", (void*)sceGsResetPath); // used in dma
|
||||
make_function_symbol_from_c("reset-graph", (void*)sceGsResetGraph); // used
|
||||
@ -661,4 +698,5 @@ void InitMachineScheme() {
|
||||
lg::info("calling fake play~");
|
||||
call_goal_function_by_name("play");
|
||||
}
|
||||
machine_booted = true;
|
||||
}
|
||||
|
@ -5,9 +5,6 @@
|
||||
* GOAL Machine. Contains low-level hardware interfaces for GOAL.
|
||||
*/
|
||||
|
||||
#ifndef RUNTIME_KMACHINE_H
|
||||
#define RUNTIME_KMACHINE_H
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "Ptr.h"
|
||||
|
||||
@ -118,4 +115,4 @@ struct FileStream {
|
||||
|
||||
// static_assert(offsetof(CpadInfo, new_pad) == 76, "cpad type offset");
|
||||
|
||||
#endif // RUNTIME_KMACHINE_H
|
||||
void vif_interrupt_callback();
|
||||
|
@ -1927,7 +1927,7 @@ s32 InitHeapAndSymbol() {
|
||||
make_function_symbol_from_c("method-set!", (void*)method_set);
|
||||
|
||||
// dgo
|
||||
make_function_symbol_from_c("link", (void*)link_and_exec_wrapper);
|
||||
make_stack_arg_function_symbol_from_c("link", (void*)link_and_exec_wrapper);
|
||||
make_function_symbol_from_c("dgo-load", (void*)load_and_link_dgo);
|
||||
|
||||
// forward declare
|
||||
@ -1937,7 +1937,7 @@ s32 InitHeapAndSymbol() {
|
||||
make_raw_function_symbol_from_c("symlink3", 0);
|
||||
|
||||
// game stuff
|
||||
make_function_symbol_from_c("link-begin", (void*)link_begin);
|
||||
make_stack_arg_function_symbol_from_c("link-begin", (void*)link_begin);
|
||||
make_function_symbol_from_c("link-resume", (void*)link_resume);
|
||||
// make_function_symbol_from_c("mc-run", &CKernel::not_yet_implemented);
|
||||
// make_function_symbol_from_c("mc-format", &CKernel::not_yet_implemented);
|
||||
|
@ -302,6 +302,8 @@ uint32_t FS_BeginRead(LoadStackEntry* fd, void* buffer, int32_t len) {
|
||||
fd->location += (len / SECTOR_SIZE);
|
||||
read_in_progress = true;
|
||||
|
||||
fclose(fp);
|
||||
|
||||
return CMD_STATUS_IN_PROGRESS;
|
||||
}
|
||||
|
||||
|
@ -48,9 +48,7 @@
|
||||
#include "game/overlord/stream.h"
|
||||
#include "game/overlord/sbank.h"
|
||||
|
||||
#include "game/graphics/opengl.h"
|
||||
#include "game/graphics/gfx.h"
|
||||
#include "game/graphics/display.h"
|
||||
|
||||
#include "game/system/vm/vm.h"
|
||||
#include "game/system/vm/dmac.h"
|
||||
@ -257,7 +255,7 @@ void dmac_runner(SystemThreadInterface& iface) {
|
||||
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);
|
||||
// lg::info("DMA detected on channel {}, clearing", i);
|
||||
VM::dmac_ch[i]->chcr.str = 0;
|
||||
}
|
||||
}
|
||||
|
@ -4,14 +4,6 @@
|
||||
|
||||
namespace ee {
|
||||
|
||||
void sceGsSyncV() {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
void sceGsSyncPath() {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
void sceGsResetGraph() {
|
||||
assert(false);
|
||||
}
|
||||
|
@ -3,8 +3,6 @@
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace ee {
|
||||
void sceGsSyncV();
|
||||
void sceGsSyncPath();
|
||||
void sceGsPutIMR();
|
||||
void sceGsGetIMR();
|
||||
void sceGsExecStoreImage();
|
||||
|
@ -6,9 +6,6 @@
|
||||
* Not meant to work as a full DMAC emulator, just enough to inspect DMA packets.
|
||||
*/
|
||||
|
||||
#ifndef VM_DMAC_H
|
||||
#define VM_DMAC_H
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "game/kernel/Ptr.h"
|
||||
|
||||
@ -73,5 +70,3 @@ static_assert(alignof(DmaChannelRegisters) == 0x10, "DmaChannelRegisters unalign
|
||||
void dmac_init_globals();
|
||||
|
||||
} // namespace VM
|
||||
|
||||
#endif // VM_DMAC_H
|
||||
|
@ -7,9 +7,6 @@
|
||||
* Not an emulator!
|
||||
*/
|
||||
|
||||
#ifndef VM_H
|
||||
#define VM_H
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace VM {
|
||||
@ -33,5 +30,3 @@ void unsubscribe_component();
|
||||
u64 get_vm_ptr(u32 ptr);
|
||||
|
||||
} // namespace VM
|
||||
|
||||
#endif // VM_H
|
||||
|
@ -5,3 +5,5 @@
|
||||
;; name in dgo: pov-camera
|
||||
;; dgos: GAME, ENGINE
|
||||
|
||||
;; TODO - for sunken-elevator
|
||||
(define-extern pov-camera-init-by-other (function vector skeleton-group string int symbol pair none)) ;; TODO - not confirmed -- sunken-elevator
|
||||
|
@ -110,15 +110,15 @@
|
||||
(dummy-9 () none 9)
|
||||
(dummy-10 () none 10)
|
||||
(dummy-11 () none 11)
|
||||
(dummy-12 () none 12)
|
||||
(dummy-12 (_type_ vector float uint process bone uint) float 12) ;; TODO - bone not confirmed
|
||||
(dummy-13 () none 13)
|
||||
(dummy-14 () none 14)
|
||||
(dummy-15 () none 15)
|
||||
(dummy-16 () none 16)
|
||||
(dummy-16 (_type_ vector float uint process uint) none 16)
|
||||
(dummy-17 () none 17)
|
||||
(dummy-18 () none 18)
|
||||
(dummy-19 () none 19)
|
||||
(dummy-20 () none 20)
|
||||
(dummy-20 (_type_ vector float uint bone uint) none 20) ;; TODO - bone not confirmed
|
||||
(dummy-21 () none 21)
|
||||
(dummy-22 () none 22)
|
||||
(dummy-23 () none 23)
|
||||
|
@ -62,33 +62,35 @@
|
||||
:flag-assert #x900000020
|
||||
)
|
||||
|
||||
(declare-type collide-shape-prim basic)
|
||||
(deftype collide-shape-intersect (basic)
|
||||
((move-vec vector :inline :offset-assert 16)
|
||||
(best-u float :offset-assert 32)
|
||||
(best-tri collide-tri-result :inline :offset-assert 48)
|
||||
(best-from-prim basic :offset-assert 132)
|
||||
(best-to-prim basic :offset-assert 136)
|
||||
(best-from-prim collide-shape-prim :offset-assert 132)
|
||||
(best-to-prim collide-shape-prim :offset-assert 136)
|
||||
)
|
||||
:method-count-assert 10
|
||||
:size-assert #x8c
|
||||
:flag-assert #xa0000008c
|
||||
(:methods
|
||||
(dummy-9 () none 9)
|
||||
)
|
||||
(dummy-9 () none 9)
|
||||
)
|
||||
)
|
||||
|
||||
(declare-type collide-shape-prim basic)
|
||||
(deftype collide-overlap-result (structure)
|
||||
((best-dist float :offset-assert 0)
|
||||
(best-from-prim basic :offset-assert 4)
|
||||
(best-to-prim basic :offset-assert 8)
|
||||
(best-from-prim collide-shape-prim :offset-assert 4)
|
||||
(best-to-prim collide-shape-prim :offset-assert 8)
|
||||
(best-from-tri collide-tri-result :inline :offset-assert 16)
|
||||
)
|
||||
:method-count-assert 10
|
||||
:size-assert #x64
|
||||
:flag-assert #xa00000064
|
||||
(:methods
|
||||
(reset! (_type_) none 9)
|
||||
)
|
||||
(reset! (_type_) none 9)
|
||||
)
|
||||
)
|
||||
|
||||
(defmethod reset! collide-overlap-result ((obj collide-overlap-result))
|
||||
|
@ -5,3 +5,5 @@
|
||||
;; name in dgo: collide-shape
|
||||
;; dgos: GAME, ENGINE
|
||||
|
||||
;; TODO - for misty-teetertotter | rigid-body
|
||||
(define-extern default-collision-reaction (function collide-shape-moving collide-shape-intersect vector vector none)) ;; TODO - not completely confirmed
|
||||
|
@ -32,7 +32,10 @@
|
||||
|
||||
(defun dma-buffer-add-buckets ((dma-buf dma-buffer) (count int))
|
||||
"Add count buckets. Each bucket is initialized as empty and won't transfer anything."
|
||||
(let ((current-bucket (the-as dma-bucket (-> dma-buf base))))
|
||||
(let* ((initial-bucket (the-as dma-bucket (-> dma-buf base)))
|
||||
(current-bucket initial-bucket))
|
||||
|
||||
;;(let ((current-bucket (the-as dma-bucket (-> dma-buf base))))
|
||||
(dotimes (i count)
|
||||
;; set the DMA tag to next, with a qwc of zero.
|
||||
;; the address is set to the next bucket.
|
||||
@ -51,8 +54,8 @@
|
||||
)
|
||||
;; update base ptr of dma-buffer to point after the buckets.
|
||||
(set! (-> dma-buf base) (the-as pointer current-bucket))
|
||||
initial-bucket
|
||||
)
|
||||
(none)
|
||||
)
|
||||
|
||||
|
||||
@ -74,7 +77,6 @@
|
||||
|
||||
(defun dma-bucket-insert-tag ((base dma-bucket) (idx bucket-id) (tag-start pointer) (tag-end (pointer dma-tag)))
|
||||
"Add a dma chain to the idx bucket"
|
||||
|
||||
;; find the bucket
|
||||
(let ((bucket (the-as dma-bucket (&+ base (the-as uint (shl idx 4))))))
|
||||
;; update our last bucket to point to this one.
|
||||
|
@ -343,43 +343,7 @@
|
||||
|
||||
(defun disasm-dma-tag ((arg0 dma-tag) (arg1 symbol))
|
||||
(format arg1 "(dma-tag ")
|
||||
;; this is a case statement.
|
||||
(let ((t9-1 format)
|
||||
(a0-2 arg1)
|
||||
(a1-2 "~s")
|
||||
(v1-1 (-> arg0 id))
|
||||
)
|
||||
(t9-1 a0-2 a1-2 (cond
|
||||
((= v1-1 (dma-tag-id end))
|
||||
"end"
|
||||
)
|
||||
((= v1-1 (dma-tag-id ret))
|
||||
"ret"
|
||||
)
|
||||
((= v1-1 (dma-tag-id call))
|
||||
"call"
|
||||
)
|
||||
((= v1-1 (dma-tag-id refs))
|
||||
"refs"
|
||||
)
|
||||
((= v1-1 (dma-tag-id ref))
|
||||
"ref"
|
||||
)
|
||||
((= v1-1 (dma-tag-id next))
|
||||
"next"
|
||||
)
|
||||
((= v1-1 (dma-tag-id cnt))
|
||||
"cnt"
|
||||
)
|
||||
((zero? v1-1)
|
||||
"refe"
|
||||
)
|
||||
(else
|
||||
"*unknown*"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(format arg1 "~s" (enum->string dma-tag-id (-> arg0 id)))
|
||||
(if (> (the-as uint (-> arg0 addr)) 0)
|
||||
(format arg1 " :addr #x~8x" (-> arg0 addr))
|
||||
)
|
||||
|
@ -89,9 +89,9 @@
|
||||
|
||||
(defun dma-send-chain ((arg0 dma-bank-source) (tadr uint))
|
||||
"Send DMA! tadr should be a tag address, possibly in spad ram.
|
||||
This is useful for sending to VIF.
|
||||
Tag transfer is enabled, and DIR is set so a VIF1 transfer
|
||||
goes from tadr -> VIF."
|
||||
This is useful for sending to VIF.
|
||||
Tag transfer is enabled, and DIR is set so a VIF1 transfer
|
||||
goes from tadr -> VIF."
|
||||
(dma-sync (the-as pointer arg0) 0 0)
|
||||
(flush-cache 0)
|
||||
(.sync.l)
|
||||
@ -102,15 +102,16 @@
|
||||
;;(set! (-> arg0 chcr) 325)
|
||||
(set! (-> arg0 chcr)
|
||||
(new 'static 'dma-chcr
|
||||
:dir 1 ;; from memory
|
||||
:mod 1 ;; source chain
|
||||
:tte 1 ;; send tags
|
||||
:str 1) ;; go!
|
||||
:dir 1 ;; from memory
|
||||
:mod 1 ;; source chain
|
||||
:tte 1 ;; send tags
|
||||
:str 1) ;; go!
|
||||
)
|
||||
(.sync.l)
|
||||
(none)
|
||||
)
|
||||
|
||||
|
||||
(defun dma-send-chain-no-tte ((arg0 dma-bank-source) (arg1 uint))
|
||||
"Send DMA chain! TTE bit is not set, don't transfer tags.
|
||||
This is never used."
|
||||
|
@ -5,3 +5,506 @@
|
||||
;; name in dgo: drawable
|
||||
;; dgos: GAME, ENGINE
|
||||
|
||||
(defun debug-init-buffer ((arg0 bucket-id) (arg1 gs-zbuf) (arg2 gs-test))
|
||||
"Initialize a bucket for debug draw with the given zbuf and test settings"
|
||||
(let* ((t0-0 (-> *display* frames (-> *display* on-screen) frame global-buf))
|
||||
(v1-3 (-> t0-0 base))
|
||||
)
|
||||
(let* ((a3-3 t0-0)
|
||||
(t1-0 (the-as object (-> a3-3 base)))
|
||||
)
|
||||
(set! (-> (the-as dma-packet t1-0) dma) (new 'static 'dma-tag :qwc #x3 :id (dma-tag-id cnt)))
|
||||
(set! (-> (the-as dma-packet t1-0) vif0) (new 'static 'vif-tag))
|
||||
(set! (-> (the-as dma-packet t1-0) vif1) (new 'static 'vif-tag :imm #x3 :cmd (vif-cmd direct) :msk #x1))
|
||||
(set! (-> a3-3 base) (&+ (the-as pointer t1-0) 16))
|
||||
)
|
||||
(let* ((a3-4 t0-0)
|
||||
(t1-2 (the-as object (-> a3-4 base)))
|
||||
)
|
||||
(set! (-> (the-as gs-gif-tag t1-2) tag)
|
||||
(new 'static 'gif-tag64 :nloop #x1 :eop #x1 :nreg #x2)
|
||||
)
|
||||
(set! (-> (the-as gs-gif-tag t1-2) regs)
|
||||
(new 'static 'gif-tag-regs
|
||||
:regs0 (gif-reg-id a+d)
|
||||
:regs1 (gif-reg-id a+d)
|
||||
:regs2 (gif-reg-id a+d)
|
||||
:regs3 (gif-reg-id a+d)
|
||||
:regs4 (gif-reg-id a+d)
|
||||
:regs5 (gif-reg-id a+d)
|
||||
:regs6 (gif-reg-id a+d)
|
||||
:regs7 (gif-reg-id a+d)
|
||||
:regs8 (gif-reg-id a+d)
|
||||
:regs9 (gif-reg-id a+d)
|
||||
:regs10 (gif-reg-id a+d)
|
||||
:regs11 (gif-reg-id a+d)
|
||||
:regs12 (gif-reg-id a+d)
|
||||
:regs13 (gif-reg-id a+d)
|
||||
:regs14 (gif-reg-id a+d)
|
||||
:regs15 (gif-reg-id a+d)
|
||||
)
|
||||
)
|
||||
(set! (-> a3-4 base) (&+ (the-as pointer t1-2) 16))
|
||||
)
|
||||
(let* ((a3-5 t0-0)
|
||||
(t1-4 (-> a3-5 base))
|
||||
)
|
||||
(set! (-> (the-as (pointer gs-zbuf) t1-4) 0) arg1)
|
||||
(set! (-> (the-as (pointer gs-reg64) t1-4) 1) (gs-reg64 zbuf-1))
|
||||
(set! (-> (the-as (pointer gs-test) t1-4) 2) arg2)
|
||||
(set! (-> (the-as (pointer gs-reg64) t1-4) 3) (gs-reg64 test-1))
|
||||
(set! (-> a3-5 base) (&+ t1-4 32))
|
||||
)
|
||||
(let ((a3-6 (-> t0-0 base)))
|
||||
(let ((a1-4 (the-as object (-> t0-0 base))))
|
||||
(set! (-> (the-as dma-packet a1-4) dma) (new 'static 'dma-tag :id (dma-tag-id next)))
|
||||
(set! (-> (the-as dma-packet a1-4) vif0) (new 'static 'vif-tag))
|
||||
(set! (-> (the-as dma-packet a1-4) vif1) (new 'static 'vif-tag))
|
||||
(set! (-> t0-0 base) (&+ (the-as pointer a1-4) 16))
|
||||
)
|
||||
(dma-bucket-insert-tag
|
||||
(-> *display* frames (-> *display* on-screen) frame bucket-group)
|
||||
arg0
|
||||
v1-3
|
||||
(the-as (pointer dma-tag) a3-6)
|
||||
)
|
||||
)
|
||||
)
|
||||
(none)
|
||||
)
|
||||
|
||||
(define *screen-shot* #f)
|
||||
(defun display-frame-start ((disp display) (new-frame-idx int) (odd-even int))
|
||||
"Set up a new frame. Call this before drawing anything.
|
||||
new-frame-idx is the display frame that will be set up.
|
||||
odd-even is the odd-even of the new frame"
|
||||
|
||||
|
||||
;; due to a HW bug in the PS2, you must set this.
|
||||
;;(set! (-> (the-as vif-bank #x10003c00) err me0) 1)
|
||||
|
||||
;; figure out how fast we're going compared to the desired.
|
||||
;; larger = slower than we should.
|
||||
;; due to vsync, we should never go too fast.
|
||||
(let ((time-ratio (the float
|
||||
(+ (/ (timer-count (the-as timer-bank #x10000800)) (the-as uint *ticks-per-frame*))
|
||||
1 ;; so we round up.
|
||||
)
|
||||
)
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
(let ((float-time-ratio (/ (the float (timer-count (the-as timer-bank #x10000800))) (the float *ticks-per-frame*))))
|
||||
;; on the PS2, if you have > 1/60 seconds between frames, it means you missed a vsync.
|
||||
;; this doesn't seem to be the case on my machine. It appears that glfwSwapBuffers sometimes returns ~1 ms early,
|
||||
;; making the next frame ~1 ms too long.
|
||||
|
||||
;; to work around with, we internally run the game at 60 fps if it appears to be slightly too slow.
|
||||
;; if we actually do miss a frame, the time ratio will be around 2.
|
||||
|
||||
(#when PC_PORT
|
||||
(if (< float-time-ratio 1.3)
|
||||
(set! time-ratio 1.0)
|
||||
)
|
||||
#|
|
||||
(if (> time-ratio 1.)
|
||||
(format #t "LAG ~f frames~%" (- time-ratio 1.))
|
||||
)
|
||||
|#
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; inform display system of our speed. This will adjust the scaling used in all physics calculations
|
||||
(set-time-ratios *display* time-ratio)
|
||||
|
||||
;; set our "old" counters. In the event of a game load/save, these will not jump
|
||||
(set! (-> disp old-base-frame-counter) (-> disp base-frame-counter))
|
||||
(set! (-> disp old-game-frame-counter) (-> disp game-frame-counter))
|
||||
(set! (-> disp old-real-frame-counter) (-> disp real-frame-counter))
|
||||
(set! (-> disp old-integral-frame-counter) (-> disp integral-frame-counter))
|
||||
(set! (-> disp old-real-integral-frame-counter) (-> disp real-integral-frame-counter))
|
||||
(set! (-> disp old-part-frame-counter) (-> disp part-frame-counter))
|
||||
(set! (-> disp old-actual-frame-counter) (-> disp actual-frame-counter))
|
||||
(set! (-> disp old-real-actual-frame-counter) (-> disp real-actual-frame-counter))
|
||||
|
||||
;; get the increment in seconds unit.
|
||||
(let ((scaled-seconds (* (the int time-ratio) (the int (-> disp time-factor)))))
|
||||
;; tell the sparticle system
|
||||
(set-particle-frame-time (min 12 scaled-seconds))
|
||||
|
||||
;; the "not real" frame counters only count when unpaused
|
||||
(when (not (paused?))
|
||||
;; these count by scaled time
|
||||
(+! (-> disp base-frame-counter) scaled-seconds)
|
||||
(+! (-> disp part-frame-counter) scaled-seconds)
|
||||
;; this counts actual frames, not seconds. Will count 2 frames if we lag
|
||||
(+! (-> disp integral-frame-counter) (the int time-ratio))
|
||||
;; this counts actual frames, not doubling for lag. Will count 1 per frame drawn
|
||||
(+! (-> disp actual-frame-counter) 1)
|
||||
;; game counter will count seconds that we're not in a movie
|
||||
(if (not (movie?))
|
||||
(+! (-> disp game-frame-counter) scaled-seconds)
|
||||
)
|
||||
)
|
||||
;; real counts like base, but increments when paused
|
||||
(+! (-> disp real-frame-counter) scaled-seconds)
|
||||
)
|
||||
;; actual frames, lag counts as 2x
|
||||
(+! (-> disp real-integral-frame-counter) (the int time-ratio))
|
||||
)
|
||||
;; actual real frames (for real)
|
||||
(+! (-> disp real-actual-frame-counter) 1)
|
||||
|
||||
;; reset the timer.
|
||||
(timer-reset (the-as timer-bank #x10000800))
|
||||
|
||||
;; take a screenshot, if desired
|
||||
(when *screen-shot*
|
||||
(if *debug-segment*
|
||||
(store-image odd-even)
|
||||
)
|
||||
(set! *screen-shot* #f)
|
||||
)
|
||||
|
||||
;; set up the frame object.
|
||||
(let ((new-frame (-> disp frames new-frame-idx frame)))
|
||||
;; profile setup
|
||||
(when *debug-segment*
|
||||
(dotimes (s2-0 2)
|
||||
(reset (-> new-frame profile-bar s2-0))
|
||||
)
|
||||
)
|
||||
;; right now, the old frame is being rendered.
|
||||
;; if we set *sync-dma*, we will wait here until it finishes rendering.
|
||||
(if *sync-dma*
|
||||
(sync-path 0 0)
|
||||
)
|
||||
|
||||
;; reset the global dma buffer.
|
||||
(let ((v1-56 (-> new-frame global-buf)))
|
||||
(set! (-> v1-56 base) (-> v1-56 data))
|
||||
(set! (-> v1-56 end) (&-> v1-56 data-buffer (-> v1-56 allocated-length)))
|
||||
)
|
||||
|
||||
;; reset the debug dma buffer
|
||||
(when *debug-segment*
|
||||
(let ((v1-59 (-> new-frame debug-buf)))
|
||||
(set! (-> v1-59 base) (-> v1-59 data))
|
||||
(set! (-> v1-59 end) (&-> v1-59 data-buffer (-> v1-59 allocated-length)))
|
||||
)
|
||||
)
|
||||
|
||||
;; reset the calc buffer. This holds the buckets themselves and what is sent
|
||||
;; to actually draw the frame.
|
||||
(let ((v1-60 (-> new-frame calc-buf)))
|
||||
(set! (-> v1-60 base) (-> v1-60 data))
|
||||
(set! (-> v1-60 end) (&-> v1-60 data-buffer (-> v1-60 allocated-length)))
|
||||
)
|
||||
|
||||
;; the default buffer holds a DMA chain to fully reset the GS.
|
||||
;; reinitialize it, just to be safe
|
||||
(default-buffer-init *default-regs-buffer*)
|
||||
;; and add it to the very beginning of the calc buf
|
||||
(let* ((v1-61 (-> new-frame calc-buf))
|
||||
(a2-1 *default-regs-buffer*)
|
||||
(a0-28 (the-as object (-> v1-61 base)))
|
||||
)
|
||||
(set! (-> (the-as dma-packet a0-28) dma)
|
||||
(new 'static 'dma-tag
|
||||
:id (dma-tag-id call)
|
||||
:addr (the-as int (-> a2-1 data))
|
||||
)
|
||||
)
|
||||
(set! (-> (the-as dma-packet a0-28) vif0) (new 'static 'vif-tag))
|
||||
(set! (-> (the-as dma-packet a0-28) vif1) (new 'static 'vif-tag))
|
||||
(set! (-> v1-61 base) (&+ (the-as pointer a0-28) 16))
|
||||
)
|
||||
|
||||
;; could be used for debugging or something, but is set to nothing.
|
||||
(*pre-draw-hook* (-> new-frame calc-buf))
|
||||
|
||||
;; reset debugging stuff
|
||||
(when (not (paused?))
|
||||
(clear *stdcon1*)
|
||||
(debug-reset-buffers)
|
||||
)
|
||||
|
||||
;; add the buckets
|
||||
(set! (-> new-frame bucket-group)
|
||||
(the-as dma-bucket (dma-buffer-add-buckets (-> new-frame calc-buf) 69))
|
||||
)
|
||||
)
|
||||
|
||||
;; initialize the debug bucket
|
||||
(debug-init-buffer
|
||||
(bucket-id debug-draw1)
|
||||
(new 'static 'gs-zbuf :zbp #x1c0 :psm (gs-psm ct24) :zmsk #x1)
|
||||
(new 'static 'gs-test :zte #x1 :ztst (gs-ztest always))
|
||||
)
|
||||
|
||||
;; setup our drawing offset for even/odd offset
|
||||
(set-draw-env-offset (-> disp frames new-frame-idx draw) 2048 2048 odd-even)
|
||||
|
||||
;; read controllers
|
||||
(service-cpads)
|
||||
;; now we are ready to run a frame!
|
||||
(none)
|
||||
)
|
||||
|
||||
(defun display-frame-finish ((disp display))
|
||||
"End drawing. Call this after drawing everything.
|
||||
Note that this does not start a DMA transfer, just finishes up the buffered data for
|
||||
the frame."
|
||||
(let* ((this-frame (-> disp frames (-> disp on-screen) frame))
|
||||
(this-calc-buf (-> this-frame calc-buf))
|
||||
)
|
||||
;; post draw stuff
|
||||
(tie-init-buffers this-calc-buf)
|
||||
(merc-vu1-init-buffers)
|
||||
(*post-draw-hook* (-> disp frames (-> disp on-screen) frame calc-buf))
|
||||
|
||||
;; iterate through all buckets and append a final GS state reset.
|
||||
(dotimes (bucket-idx 69)
|
||||
(let* ((this-global-buf (-> this-frame global-buf))
|
||||
(a2-0 (-> this-global-buf base))
|
||||
)
|
||||
;; clear GS state after the bucket
|
||||
(let* ((a0-3 this-global-buf)
|
||||
(t0-0 *default-regs-buffer*)
|
||||
(a1-0 (the-as object (-> a0-3 base)))
|
||||
)
|
||||
(set! (-> (the-as dma-packet a1-0) dma)
|
||||
(new 'static 'dma-tag
|
||||
:id (dma-tag-id call)
|
||||
:addr (the-as int (-> t0-0 data))
|
||||
)
|
||||
)
|
||||
(set! (-> (the-as dma-packet a1-0) vif0) (new 'static 'vif-tag :irq #x1))
|
||||
(set! (-> (the-as dma-packet a1-0) vif1) (new 'static 'vif-tag))
|
||||
(set! (-> a0-3 base) (&+ (the-as pointer a1-0) 16))
|
||||
)
|
||||
(let ((a3-4 (-> this-global-buf base)))
|
||||
(let ((a0-4 (the-as object (-> this-global-buf base))))
|
||||
(set!
|
||||
(-> (the-as dma-packet a0-4) dma)
|
||||
(new 'static 'dma-tag :id (dma-tag-id next))
|
||||
)
|
||||
(set! (-> (the-as dma-packet a0-4) vif0) (new 'static 'vif-tag))
|
||||
(set! (-> (the-as dma-packet a0-4) vif1) (new 'static 'vif-tag))
|
||||
(set! (-> this-global-buf base) (&+ (the-as pointer a0-4) 16))
|
||||
)
|
||||
(dma-bucket-insert-tag
|
||||
(-> *display* frames (-> *display* on-screen) frame bucket-group)
|
||||
(the-as bucket-id bucket-idx)
|
||||
a2-0
|
||||
(the-as (pointer dma-tag) a3-4)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
;; append a FLUSHE and IRQ to end the calc-buf
|
||||
(let* ((v1-14 this-calc-buf)
|
||||
(a0-10 (the-as object (-> v1-14 base)))
|
||||
)
|
||||
(set! (-> (the-as dma-packet a0-10) dma)
|
||||
(new 'static 'dma-tag :id (dma-tag-id cnt))
|
||||
)
|
||||
(set! (-> (the-as dma-packet a0-10) vif0)
|
||||
(new 'static 'vif-tag :cmd (vif-cmd flushe) :msk #x1)
|
||||
)
|
||||
(set! (-> (the-as dma-packet a0-10) vif1) (new 'static 'vif-tag :irq #x1))
|
||||
(set! (-> v1-14 base) (&+ (the-as pointer a0-10) 16))
|
||||
)
|
||||
;; patch the buckets. Now sending the calc buf will send everything!
|
||||
(dma-buffer-patch-buckets (-> this-frame bucket-group) 69)
|
||||
;; append the final END.
|
||||
(let* ((v1-15 this-calc-buf)
|
||||
(a0-13 (the-as object (-> v1-15 base)))
|
||||
)
|
||||
(set! (-> (the-as dma-packet a0-13) dma)
|
||||
(new 'static 'dma-tag :id (dma-tag-id end))
|
||||
)
|
||||
(set! (-> (the-as (pointer uint64) a0-13) 1) (the-as uint 0))
|
||||
(set! (-> v1-15 base) (&+ (the-as pointer a0-13) 16))
|
||||
)
|
||||
;; final cache flush after finishing DMA chains
|
||||
(flush-cache 0)
|
||||
;; print debug stats.
|
||||
(when (not (paused?))
|
||||
(when *stats-buffer*
|
||||
(let* ((global-buf (-> this-frame global-buf))
|
||||
(calc-current (-> this-calc-buf base))
|
||||
(calc-start (-> this-calc-buf data))
|
||||
(global-current (-> global-buf base))
|
||||
(global-start (-> global-buf data))
|
||||
(global-end (-> global-buf end))
|
||||
)
|
||||
(format *stdcon* "~0kvu1 buf = ~d~%" (&- calc-current (the-as uint calc-start)))
|
||||
(format *stdcon* "~0kglobal buf = ~d~%" (&- global-current (the-as uint global-start)))
|
||||
(format *stdcon* "~0kbase = #x~x~%" global-current)
|
||||
(format *stdcon* "~0kend = #x~x~%" global-end)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
disp
|
||||
)
|
||||
|
||||
(defun determine-pause-mode ()
|
||||
"Update pause system"
|
||||
|
||||
;; debug frame advance
|
||||
(when (and *debug-pause* (= *master-mode* 'pause))
|
||||
(logclear! (-> *cpad-list* cpads 0 button0-abs 0) (pad-buttons start r2))
|
||||
(logclear! (-> *cpad-list* cpads 0 button0-rel 0) (pad-buttons start r2))
|
||||
(while (and (= *master-mode* 'pause)
|
||||
(zero? (logand (-> *cpad-list* cpads 0 button0-rel 0) (pad-buttons start r2)))
|
||||
)
|
||||
(sync-path 0 0)
|
||||
(service-cpads)
|
||||
)
|
||||
(toggle-pause)
|
||||
)
|
||||
(when (or (not *progress-process*) (dummy-32 (-> *progress-process* 0)))
|
||||
(if (or (logtest? (-> *cpad-list* cpads 0 button0-rel 0) (pad-buttons select r3 start)) ;; push pause
|
||||
(and ;; controller lost
|
||||
(logtest? (-> *cpad-list* cpads 0 valid) 128)
|
||||
(= *master-mode* 'game)
|
||||
(>= (the-as int (-> *display* base-frame-counter)) (the-as int (-> *game-info* blackout-time)))
|
||||
;; this is a hack. this is initialized to #x493e0. It prevents controller-loss pause from
|
||||
;; triggering in the first few seconds of gameplay.
|
||||
(< #x49764 (the-as int (-> *display* real-frame-counter)))
|
||||
)
|
||||
(and (logtest? (-> *cpad-list* cpads 0 button0-rel 0) (pad-buttons r2)) ;; debug press
|
||||
(paused?)
|
||||
)
|
||||
*pause-lock*
|
||||
)
|
||||
(toggle-pause)
|
||||
)
|
||||
)
|
||||
;; if we toggled out of pause, kill it.
|
||||
(if (!= *master-mode* 'progress)
|
||||
(deactivate-progress)
|
||||
)
|
||||
0
|
||||
)
|
||||
|
||||
(define *surrogate-dma-buffer* (the dma-buffer #f))
|
||||
|
||||
(defmacro cpu-usage ()
|
||||
"print out the cpu usage of the most recently rendered frame"
|
||||
`(format #t "CPU: ~,,2f%~%frame-time: ~,,1fms~%"
|
||||
(* 100. (/ (the float (current-display-frame run-time)) *ticks-per-frame*))
|
||||
(* 1000. (/ 1. 60.) (/ (the float (current-display-frame run-time)) *ticks-per-frame*))
|
||||
)
|
||||
)
|
||||
|
||||
(#when PC_PORT
|
||||
(define *disasm-count* 0)
|
||||
|
||||
(defmacro disasm-next-dma (&key (count 1))
|
||||
`(set! *disasm-count* ,count)
|
||||
)
|
||||
)
|
||||
|
||||
(defun display-sync ((disp display))
|
||||
"Switch frames! This assumes that you have called display-frame-finish on the current frame.
|
||||
It will:
|
||||
- wait for the current rendering frame to stop.
|
||||
- do a vsync to get that frame on screen (possibly waiting up to 1 frame, if the prev frame
|
||||
did not finish in time. This is why we drop to 30fps
|
||||
as soon as we don't fit into 60 fps)
|
||||
- start rendering the current frame
|
||||
- initialize DMA buffers for the next frame drawing, advance to next frame"
|
||||
|
||||
;; wait for rendering to finish.
|
||||
(sync-path 0 0)
|
||||
;; remember when.
|
||||
(set! (-> disp frames (-> disp on-screen) frame run-time)
|
||||
(timer-count (the-as timer-bank #x10000800))
|
||||
)
|
||||
|
||||
;; now, do a vsync. If we finished rendering in time, this will just wait until the next.
|
||||
(let ((frame-idx (-> disp on-screen))
|
||||
(syncv-result (syncv 0))
|
||||
)
|
||||
|
||||
;; starting here, we are in a new frame
|
||||
;; syncv returns odd/even (if you miss multiple syncv's due to the sync-path above taking many
|
||||
;; frames, this will get us back on the correct field)
|
||||
(set! *oddeven* syncv-result)
|
||||
|
||||
;; if we need to change video modes:
|
||||
(when (-> *video-parms* set-video-mode)
|
||||
;; to GS
|
||||
(set-display2 *display* 0 512 (-> *video-parms* screen-sy) 2 49)
|
||||
(set! (-> *video-parms* set-video-mode) #f)
|
||||
;; reset video mode is for changing ntsc/pal
|
||||
(when (-> *video-parms* reset-video-mode)
|
||||
(set! (-> *video-parms* reset-video-mode) #f)
|
||||
;; need to call reset-graph with some magic number
|
||||
;; also stash this parameter so that if things go really wrong and our DMA transfer
|
||||
;; times out, we can reset-graph to the appropriate video mode
|
||||
(if (= (-> *setting-control* current video-mode) 'ntsc)
|
||||
(set! *video-reset-parm* 2)
|
||||
(set! *video-reset-parm* 3)
|
||||
)
|
||||
(reset-graph 0 1 *video-reset-parm* 1)
|
||||
)
|
||||
)
|
||||
|
||||
;; setup the env (Sony functions)
|
||||
(put-display-env (-> disp frames frame-idx display))
|
||||
(put-draw-env (the-as (pointer gif-tag) (-> disp frames frame-idx gif)))
|
||||
|
||||
;; begin rendering the next frame
|
||||
(let ((dma-buf-to-send (-> disp frames frame-idx frame calc-buf)))
|
||||
(when (nonzero? (dma-buffer-length dma-buf-to-send))
|
||||
;; was dma-send-chain originally.
|
||||
(#when PC_PORT
|
||||
(when (> *disasm-count* 0)
|
||||
(disasm-dma-list (the-as dma-packet (-> dma-buf-to-send data-buffer)) 'details #t #t -1)
|
||||
(-! *disasm-count* 1)
|
||||
)
|
||||
)
|
||||
;;(cpu-usage)
|
||||
(__send-gfx-dma-chain (the-as dma-bank-source #x10009000)
|
||||
(cond
|
||||
;; some buffer for debugging, not used
|
||||
(*surrogate-dma-buffer*
|
||||
*surrogate-dma-buffer*
|
||||
)
|
||||
(else
|
||||
(-> dma-buf-to-send data-buffer)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(determine-pause-mode)
|
||||
|
||||
;; update display frame
|
||||
(let ((next-frame (+ frame-idx 1)))
|
||||
(if (< 1 next-frame)
|
||||
(set! next-frame 0)
|
||||
)
|
||||
(set! (-> disp last-screen) (-> disp on-screen))
|
||||
(set! (-> disp on-screen) next-frame)
|
||||
|
||||
;; initialize next frame
|
||||
(display-frame-start disp next-frame syncv-result)
|
||||
)
|
||||
)
|
||||
(none)
|
||||
)
|
||||
|
||||
(defun swap-display ((disp display))
|
||||
"Swap frames! Synchronizes with rendering and vsync, kicks off the next render, and initializes the
|
||||
to-draw frame"
|
||||
(display-frame-finish disp)
|
||||
(display-sync disp) ;; also starts next
|
||||
)
|
||||
|
@ -5,3 +5,13 @@
|
||||
;; name in dgo: process-drawable
|
||||
;; dgos: GAME, ENGINE
|
||||
|
||||
;; TODO - for water-anim | sunken-elevator
|
||||
(define-extern ja-post (function int))
|
||||
;; TODO - for rigid-body
|
||||
(define-extern rider-post (function int))
|
||||
(define-extern transform-post (function int))
|
||||
;; TODO - for misty-teetertotter
|
||||
(define-extern transform-post (function int))
|
||||
(define-extern ja-done? (function int symbol))
|
||||
;; TODO - for misty-warehouse
|
||||
(define-extern rider-trans (function int))
|
||||
|
@ -117,3 +117,8 @@
|
||||
)
|
||||
(the-as entity-actor #f)
|
||||
)
|
||||
|
||||
;; TODO - for misty-teetertotter | rigid-body
|
||||
(define-extern process-drawable-from-entity! (function process-drawable object none))
|
||||
;; TODO - for misty-warehouse
|
||||
(define-extern process-entity-status! (function process entity-perm-status symbol int))
|
||||
|
@ -62,19 +62,7 @@
|
||||
)
|
||||
|
||||
(defun pickup-type->string ((arg0 pickup-type))
|
||||
(case arg0
|
||||
(((pickup-type eco-pill-random)) "eco-pill-random")
|
||||
(((pickup-type buzzer)) "buzzer")
|
||||
(((pickup-type eco-pill)) "eco-pill")
|
||||
(((pickup-type fuel-cell)) "fuel-cell")
|
||||
(((pickup-type money)) "money")
|
||||
(((pickup-type eco-green)) "eco-green")
|
||||
(((pickup-type eco-blue)) "eco-blue")
|
||||
(((pickup-type eco-red)) "eco-red")
|
||||
(((pickup-type eco-yellow)) "eco-yellow")
|
||||
(((pickup-type none)) "none")
|
||||
(else "*unknown*")
|
||||
)
|
||||
(enum->string pickup-type arg0)
|
||||
)
|
||||
|
||||
;; Each individual enemy and pickup process will allocate a fact-info on its process heap
|
||||
|
@ -110,21 +110,21 @@
|
||||
)
|
||||
|
||||
(deftype attack-info (structure)
|
||||
((trans vector :inline :offset-assert 0)
|
||||
(vector vector :inline :offset-assert 16)
|
||||
(intersection vector :inline :offset-assert 32)
|
||||
(attacker uint64 :offset-assert 48) ;; handle
|
||||
(invinc-time uint64 :offset-assert 56)
|
||||
(mask uint32 :offset-assert 64)
|
||||
(mode basic :offset-assert 68)
|
||||
(shove-back meters :offset-assert 72)
|
||||
(shove-up meters :offset-assert 76)
|
||||
(speed meters :offset-assert 80)
|
||||
(dist meters :offset-assert 84)
|
||||
(control float :offset-assert 88)
|
||||
(angle basic :offset-assert 92)
|
||||
(rotate-to degrees :offset-assert 96)
|
||||
(prev-state basic :offset-assert 100)
|
||||
((trans vector :inline :offset-assert 0)
|
||||
(vector vector :inline :offset-assert 16)
|
||||
(intersection vector :inline :offset-assert 32)
|
||||
(attacker uint64 :offset-assert 48) ;; handle
|
||||
(invinc-time uint64 :offset-assert 56)
|
||||
(mask uint32 :offset-assert 64)
|
||||
(mode symbol :offset-assert 68)
|
||||
(shove-back meters :offset-assert 72)
|
||||
(shove-up meters :offset-assert 76)
|
||||
(speed meters :offset-assert 80)
|
||||
(dist meters :offset-assert 84)
|
||||
(control float :offset-assert 88)
|
||||
(angle symbol :offset-assert 92)
|
||||
(rotate-to degrees :offset-assert 96)
|
||||
(prev-state state :offset-assert 100)
|
||||
)
|
||||
:method-count-assert 10
|
||||
:size-assert #x68
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user