mirror of
https://github.com/open-goal/jak-project
synced 2026-08-21 23:00:45 -04:00
closer
This commit is contained in:
@@ -127,6 +127,9 @@ class Vector {
|
||||
return result + "]";
|
||||
}
|
||||
|
||||
T* data() { return m_data; }
|
||||
const T* data() const { return m_data; }
|
||||
|
||||
private:
|
||||
T m_data[Size];
|
||||
};
|
||||
|
||||
+3
-1
@@ -76,7 +76,9 @@ set(RUNTIME_SOURCE
|
||||
graphics/display.cpp
|
||||
graphics/sceGraphicsInterface.cpp
|
||||
graphics/dma/dma_copy.cpp
|
||||
graphics/dma_reader.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
|
||||
system/vm/dmac.cpp
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "dma.h"
|
||||
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "common/util/assert.h"
|
||||
|
||||
std::string DmaTag::print() {
|
||||
std::string result;
|
||||
@@ -12,3 +13,92 @@ std::string DmaTag::print() {
|
||||
result += "\n";
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string VifCode::print() {
|
||||
std::string result;
|
||||
|
||||
bool special_kind = false;
|
||||
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);
|
||||
// special_kind = true;
|
||||
break;
|
||||
}
|
||||
//
|
||||
// if (special_kind) {
|
||||
// if (((u32)kind & (u32)Kind::UNPACK_MASK) == (u32)Kind::UNPACK_MASK) {
|
||||
// result = "UNPACK";
|
||||
// assert(false);
|
||||
// } else {
|
||||
// assert(false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// result += ' ';
|
||||
//
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+44
-1
@@ -6,6 +6,8 @@
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include "common/util/assert.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
struct DmaTag {
|
||||
@@ -33,4 +35,45 @@ struct DmaTag {
|
||||
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();
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ struct DmaTransfer {
|
||||
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 {
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/graphics/dma/dma.h"
|
||||
|
||||
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; }
|
||||
};
|
||||
@@ -1,86 +0,0 @@
|
||||
#include "third-party/fmt/format.h"
|
||||
#include "dma_reader.h"
|
||||
#include "common/util/assert.h"
|
||||
|
||||
|
||||
//
|
||||
///*!
|
||||
// * "Flattening" DMA compacts it into a single stream.
|
||||
// * Tag transfers are padded with 64-bits of 0's to keep things qw aligned.
|
||||
// * It will be a nop for the VIF anyway.
|
||||
// * Note that flattening may make the DMA stream larger, as it will repeat data.
|
||||
// */
|
||||
//std::vector<u64> flatten_dma(const DmaReadState& in) {
|
||||
// DmaReadState state = in;
|
||||
// std::vector<u64> result;
|
||||
// while (!state.ended) {
|
||||
// auto read_result = next_dma(&state);
|
||||
// result.push_back(0); // tag transfer padding
|
||||
// result.push_back(read_result.transferred_tag);
|
||||
// for (u32 i = 0; i < 2 * read_result.qwc; i++) {
|
||||
// result.push_back(state.read<u64>(read_result.addr + 8 * i));
|
||||
// }
|
||||
// }
|
||||
// return result;
|
||||
//}
|
||||
//
|
||||
//void debug_print_dma(const DmaReadState& in) {
|
||||
// DmaReadState state = in;
|
||||
// std::vector<u64> result;
|
||||
// while (!state.ended) {
|
||||
// u64 tag_data = state.read<u64>(state.tag_addr);
|
||||
// DmaTag tag(tag_data);
|
||||
// fmt::print("@ 0x{:8x} {}", state.tag_addr, tag.print());
|
||||
// next_dma(&state);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//std::vector<std::vector<u64>> flatten_dma_per_bucket(const DmaReadState& in) {
|
||||
// // we rely on the fact that there is a separate buffer for buckets, and that each bucket is just a
|
||||
// // 16-byte tag.
|
||||
// u32 next_bucket = in.tag_addr + 16;
|
||||
// std::vector<std::vector<u64>> flattened_buckets;
|
||||
// std::vector<u64> current_bucket_data;
|
||||
// DmaReadState state = in;
|
||||
//
|
||||
// while (!state.ended) {
|
||||
// if (state.tag_addr == next_bucket) {
|
||||
// flattened_buckets.push_back(current_bucket_data);
|
||||
// current_bucket_data.clear();
|
||||
// next_bucket += 16;
|
||||
// }
|
||||
// current_bucket_data.push_back(0); // tag transfer padding
|
||||
// auto read_result = next_dma(&state);
|
||||
// current_bucket_data.push_back(read_result.transferred_tag);
|
||||
// for (u32 i = 0; i < 2 * read_result.qwc; i++) {
|
||||
// current_bucket_data.push_back(state.read<u64>(read_result.addr + 8 * i));
|
||||
// }
|
||||
// }
|
||||
// return flattened_buckets;
|
||||
//}
|
||||
//
|
||||
//std::vector<PerBucketInfo> find_buckets(const DmaReadState& in) {
|
||||
// // we rely on the fact that there is a separate buffer for buckets, and that each bucket is just a
|
||||
// // 16-byte tag.
|
||||
// u32 next_bucket = in.tag_addr + 16;
|
||||
// std::vector<PerBucketInfo> buckets;
|
||||
// PerBucketInfo current_bucket_data;
|
||||
// current_bucket_data.start_addr = in.tag_addr;
|
||||
// current_bucket_data.end_addr = in.tag_addr + 16;
|
||||
// DmaReadState state = in;
|
||||
//
|
||||
// while (!state.ended) {
|
||||
// if (state.tag_addr == next_bucket) {
|
||||
// buckets.push_back(current_bucket_data);
|
||||
// current_bucket_data = PerBucketInfo();
|
||||
// current_bucket_data.start_addr = state.tag_addr;
|
||||
// current_bucket_data.end_addr = state.tag_addr + 16;
|
||||
// next_bucket += 16;
|
||||
// }
|
||||
//
|
||||
// auto read_result = next_dma(&state);
|
||||
// current_bucket_data.tag_count++;
|
||||
// current_bucket_data.qwc += read_result.qwc;
|
||||
// }
|
||||
// return buckets;
|
||||
//}
|
||||
@@ -1,38 +0,0 @@
|
||||
//#pragma once
|
||||
//
|
||||
//#include <string>
|
||||
//
|
||||
//#include <cstring>
|
||||
//
|
||||
//#include "common/common_types.h"
|
||||
//
|
||||
///*!
|
||||
// * @file dma_reader.h
|
||||
// * This file contains utilities for reading and printing DMA data.
|
||||
// */
|
||||
//
|
||||
//struct DmaData {
|
||||
// u32 addr = 0;
|
||||
// u32 qwc = 0;
|
||||
// u64 transferred_tag = 0;
|
||||
//};
|
||||
//
|
||||
//DmaData next_dma(DmaReadState* state);
|
||||
//
|
||||
//std::vector<u64> flatten_dma(const DmaReadState& in);
|
||||
//std::vector<std::vector<u64>> flatten_dma_per_bucket(const DmaReadState& in);
|
||||
//void debug_print_dma(const DmaReadState& in);
|
||||
//
|
||||
//struct PerBucketInfo {
|
||||
// u32 start_addr = 0;
|
||||
// u32 end_addr = 0;
|
||||
// u32 qwc = 0;
|
||||
// u32 tag_count = 0;
|
||||
//};
|
||||
//
|
||||
///*!
|
||||
// * Note:
|
||||
// * this finds 71 buckets, even though there are only 69.
|
||||
// * There's a setup before the buckets and an end after the buckets.
|
||||
// */
|
||||
//std::vector<PerBucketInfo> find_buckets(const DmaReadState& in);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "game/graphics/dma/dma_chain_read.h"
|
||||
#include "game/graphics/opengl_renderer/Shader.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 {
|
||||
ShaderLibrary shaders;
|
||||
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;
|
||||
};
|
||||
@@ -0,0 +1,545 @@
|
||||
#include "DirectRenderer.h"
|
||||
#include "game/graphics/dma/gs.h"
|
||||
#include "common/log/log.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "game/graphics/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);
|
||||
|
||||
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, 0);
|
||||
}
|
||||
|
||||
DirectRenderer::~DirectRenderer() {
|
||||
glDeleteBuffers(1, &m_ogl.color_buffer);
|
||||
glDeleteBuffers(1, &m_ogl.vertex_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Render from a DMA bucket.
|
||||
*/
|
||||
void DirectRenderer::render(DmaFollower& dma, SharedRenderState* render_state) {
|
||||
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;
|
||||
}
|
||||
|
||||
lg::warn("DirectRenderer flush with {} triangles.\n", m_prim_buffer.vert_count / 3);
|
||||
// update opengl state
|
||||
if (m_prim_gl_state_needs_gl_update) {
|
||||
update_gl_prim();
|
||||
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;
|
||||
}
|
||||
|
||||
u32* data = ( u32*)m_prim_buffer.verts.data();
|
||||
|
||||
|
||||
|
||||
// render!
|
||||
// update buffers:
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, m_ogl.vertex_buffer_bytes, m_prim_buffer.verts.data());
|
||||
// glBufferSubData(GL_ARRAY_BUFFER, 0, m_ogl.vertex_buffer_bytes, verts);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.color_buffer);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, m_ogl.color_buffer_bytes, m_prim_buffer.rgba_u8.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);
|
||||
// assert(false);
|
||||
glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count);
|
||||
m_prim_buffer.vert_count = 0;
|
||||
}
|
||||
|
||||
void DirectRenderer::update_gl_prim() {
|
||||
// currently gouraud is handled in setup.
|
||||
const auto& state = m_prim_gl_state;
|
||||
if (state.texture_enable) {
|
||||
assert(false);
|
||||
}
|
||||
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::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.
|
||||
render_state->shaders[ShaderId::DIRECT_BASIC].activate();
|
||||
}
|
||||
|
||||
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) {
|
||||
fmt::print("Render GIF: {}\n", size);
|
||||
if (size == 224) {
|
||||
lg::error("Skipping 224 sized GIF render.\n");
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
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;
|
||||
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;
|
||||
|
||||
// for now, ignore textures/fog.
|
||||
case GsRegisterAddress::TEX1_1:
|
||||
case GsRegisterAddress::TEXA:
|
||||
case GsRegisterAddress::TEXCLUT:
|
||||
case GsRegisterAddress::FOGCOL:
|
||||
break;
|
||||
default:
|
||||
fmt::print("Address {} is not supported\n", register_address_name(addr));
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
fmt::print("test: {}\n", reg.print());
|
||||
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);
|
||||
fmt::print("alpha: {}\n", reg.print());
|
||||
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) {
|
||||
fmt::print("got prim: 0x{:x}\n", val);
|
||||
|
||||
// need to flush any in progress prims to the buffer.
|
||||
if (m_prim_building.building_idx > 0) {
|
||||
assert(false); // shouldn't leave any half-finished prims
|
||||
}
|
||||
|
||||
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);
|
||||
fmt::print("a = 0x{:x}\n", m_prim_building.rgba_reg[3]);
|
||||
}
|
||||
|
||||
void DirectRenderer::handle_xyzf2(u64 val, SharedRenderState* render_state) {
|
||||
if (m_prim_buffer.is_full()) {
|
||||
flush_pending(render_state);
|
||||
}
|
||||
|
||||
m_prim_building.building_rgba[m_prim_building.building_idx] = m_prim_building.rgba_reg;
|
||||
|
||||
// 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;
|
||||
|
||||
assert(f == 0);
|
||||
m_prim_building.building_vert[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;
|
||||
default:
|
||||
fmt::print("prim type {} is unsupported.\n", (int)m_prim_building.kind);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
max_verts = max_triangles * 3;
|
||||
}
|
||||
|
||||
void DirectRenderer::PrimitiveBuffer::push(const math::Vector<u8, 4>& rgba,
|
||||
const math::Vector<u32, 3>& vert) {
|
||||
rgba_u8[vert_count] = rgba;
|
||||
verts[vert_count] = vert;
|
||||
vert_count++;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "game/graphics/opengl_renderer/BucketRenderer.h"
|
||||
#include "game/graphics/dma/gs.h"
|
||||
#include "game/graphics/types.h"
|
||||
#include "common/math/Vector.h"
|
||||
#include "common/util/SmallVector.h"
|
||||
#include "game/graphics/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 update_gl_prim();
|
||||
void update_gl_blend();
|
||||
void update_gl_test();
|
||||
|
||||
|
||||
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;
|
||||
|
||||
// state set through the prim/rgbaq register that doesn't require changing GL stuff
|
||||
struct PrimBufferState {
|
||||
GsPrim::Kind kind = GsPrim::Kind::PRIM_7;
|
||||
math::Vector<u8, 4> rgba_reg = {0, 0, 0, 0};
|
||||
|
||||
std::array<math::Vector<u8, 4>, 3> building_rgba;
|
||||
std::array<math::Vector<u32, 3>, 3> building_vert;
|
||||
int building_idx = 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;
|
||||
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 - 6); }
|
||||
void push(const math::Vector<u8, 4>& rgba, const math::Vector<u32, 3>& vert);
|
||||
} m_prim_buffer;
|
||||
|
||||
struct {
|
||||
GLuint vertex_buffer, color_buffer;
|
||||
u32 vertex_buffer_bytes = 0;
|
||||
u32 color_buffer_bytes = 0;
|
||||
} m_ogl;
|
||||
|
||||
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;
|
||||
};
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
#include "common/log/log.h"
|
||||
#include "game/graphics/opengl.h"
|
||||
#include "game/graphics/opengl_renderer/DirectRenderer.h"
|
||||
|
||||
/*!
|
||||
* OpenGL Error callback. If we do something invalid, this will be called.
|
||||
*/
|
||||
void GLAPIENTRY opengl_error_callback(GLenum /*source*/,
|
||||
GLenum /*type*/,
|
||||
GLuint /*id*/,
|
||||
@@ -20,15 +24,92 @@ OpenGLRenderer::OpenGLRenderer() {
|
||||
// setup OpenGL errors
|
||||
glEnable(GL_DEBUG_OUTPUT);
|
||||
glDebugMessageCallback(opengl_error_callback, nullptr);
|
||||
|
||||
// initialize all renderers
|
||||
init_bucket_renderers();
|
||||
}
|
||||
|
||||
void OpenGLRenderer::render(DmaFollower dma, int window_width, int window_height) {
|
||||
(void)dma;
|
||||
// glClearColor(0.5, 0.5, 0.5, 1.0);
|
||||
glViewport(0, 0, window_width, window_height);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
/*!
|
||||
* 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, 1024);
|
||||
init_bucket_renderer<DirectRenderer>("debug-draw-1", BucketId::DEBUG_DRAW_1, 1024);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// TODO ending data.
|
||||
}
|
||||
|
||||
void OpenGLRenderer::draw_test_triangle() {
|
||||
@@ -45,7 +126,7 @@ void OpenGLRenderer::draw_test_triangle() {
|
||||
|
||||
// set vertex data
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
|
||||
const float verts[9] = {0.0, 0.5, 0, -0.5, -0.5 * .866, 0, 0.5, -0.5 * .866, 0};
|
||||
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
|
||||
@@ -57,30 +138,22 @@ void OpenGLRenderer::draw_test_triangle() {
|
||||
//////////
|
||||
// Draw!
|
||||
//////////
|
||||
m_shaders[ShaderId::TEST_SHADER].activate();
|
||||
m_render_state.shaders[ShaderId::TEST_SHADER].activate();
|
||||
|
||||
// location 0: the vertices
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(
|
||||
0, // attribute 0. No particular reason for 0, but must match the layout in the shader.
|
||||
3, // size
|
||||
GL_FLOAT, // type
|
||||
GL_FALSE, // normalized?
|
||||
0, // stride
|
||||
(void*)0 // array buffer offset
|
||||
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, // attribute 0. No particular reason for 0, but must match the layout in the shader.
|
||||
4, // size
|
||||
GL_FLOAT, // type
|
||||
GL_FALSE, // normalized?
|
||||
0, // stride
|
||||
(void*)0 // array buffer offset
|
||||
);
|
||||
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)0);
|
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
|
||||
@@ -90,4 +163,4 @@ void OpenGLRenderer::draw_test_triangle() {
|
||||
// delete buffer
|
||||
glDeleteBuffers(1, &color_buffer);
|
||||
glDeleteBuffers(1, &vertex_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +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();
|
||||
void render(DmaFollower dma, int window_width, int window_height);
|
||||
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();
|
||||
|
||||
ShaderLibrary m_shaders;
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@@ -65,4 +65,5 @@ void Shader::activate() {
|
||||
|
||||
ShaderLibrary::ShaderLibrary() {
|
||||
at(ShaderId::TEST_SHADER) = {"test_shader"};
|
||||
at(ShaderId::DIRECT_BASIC) = {"direct_basic"};
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class Shader {
|
||||
};
|
||||
|
||||
// note: update the constructor in Shader.cpp
|
||||
enum class ShaderId { TEST_SHADER = 0, MAX_SHADERS };
|
||||
enum class ShaderId { TEST_SHADER = 0, DIRECT_BASIC = 1, MAX_SHADERS };
|
||||
|
||||
class ShaderLibrary {
|
||||
public:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#version 330 core
|
||||
|
||||
out vec4 color;
|
||||
|
||||
in vec4 fragment_color;
|
||||
|
||||
void main() {
|
||||
color = fragment_color;
|
||||
}
|
||||
@@ -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,6 @@
|
||||
|
||||
|
||||
#ifndef JAK_TYPES_H
|
||||
#define JAK_TYPES_H
|
||||
|
||||
#endif // JAK_TYPES_H
|
||||
@@ -31,7 +31,6 @@
|
||||
#include "common/log/log.h"
|
||||
#include "common/util/Timer.h"
|
||||
#include "game/graphics/sceGraphicsInterface.h"
|
||||
#include "game/graphics/dma_reader.h"
|
||||
#include "game/graphics/gfx.h"
|
||||
#include "game/graphics/dma/dma_chain_read.h"
|
||||
#include "game/graphics/dma/dma_copy.h"
|
||||
|
||||
Reference in New Issue
Block a user