This commit is contained in:
water
2021-08-07 18:57:13 -04:00
parent d1674ea9b6
commit ef6204e353
21 changed files with 1647 additions and 154 deletions
+3
View File
@@ -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
View File
@@ -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
+90
View File
@@ -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
View File
@@ -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();
};
+3
View File
@@ -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 {
+336
View File
@@ -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;
}
+245
View File
@@ -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; }
};
-86
View File
@@ -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;
//}
-38
View File
@@ -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(&register_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);
}
}
+16 -3
View File
@@ -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;
};
+1
View File
@@ -65,4 +65,5 @@ void Shader::activate() {
ShaderLibrary::ShaderLibrary() {
at(ShaderId::TEST_SHADER) = {"test_shader"};
at(ShaderId::DIRECT_BASIC) = {"direct_basic"};
}
+1 -1
View File
@@ -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);
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef JAK_TYPES_H
#define JAK_TYPES_H
#endif // JAK_TYPES_H
-1
View File
@@ -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"