From ef6204e3530b27b5e62acf5a139d4831f40503e8 Mon Sep 17 00:00:00 2001 From: water Date: Sat, 7 Aug 2021 18:57:13 -0400 Subject: [PATCH] closer --- common/math/Vector.h | 3 + game/CMakeLists.txt | 4 +- game/graphics/dma/dma.cpp | 90 +++ game/graphics/dma/dma.h | 45 +- game/graphics/dma/dma_chain_read.h | 3 + game/graphics/dma/gs.cpp | 336 +++++++++++ game/graphics/dma/gs.h | 245 ++++++++ game/graphics/dma_reader.cpp | 86 --- game/graphics/dma_reader.h | 38 -- .../opengl_renderer/BucketRenderer.cpp | 43 ++ .../graphics/opengl_renderer/BucketRenderer.h | 52 ++ .../opengl_renderer/DirectRenderer.cpp | 545 ++++++++++++++++++ .../graphics/opengl_renderer/DirectRenderer.h | 143 +++++ .../opengl_renderer/OpenGLRenderer.cpp | 119 +++- .../graphics/opengl_renderer/OpenGLRenderer.h | 19 +- game/graphics/opengl_renderer/Shader.cpp | 1 + game/graphics/opengl_renderer/Shader.h | 2 +- .../opengl_renderer/shaders/direct_basic.frag | 9 + .../opengl_renderer/shaders/direct_basic.vert | 11 + game/graphics/types.h | 6 + game/kernel/kmachine.cpp | 1 - 21 files changed, 1647 insertions(+), 154 deletions(-) create mode 100644 game/graphics/dma/gs.cpp create mode 100644 game/graphics/dma/gs.h delete mode 100644 game/graphics/dma_reader.cpp delete mode 100644 game/graphics/dma_reader.h create mode 100644 game/graphics/opengl_renderer/BucketRenderer.cpp create mode 100644 game/graphics/opengl_renderer/BucketRenderer.h create mode 100644 game/graphics/opengl_renderer/DirectRenderer.cpp create mode 100644 game/graphics/opengl_renderer/DirectRenderer.h create mode 100644 game/graphics/opengl_renderer/shaders/direct_basic.frag create mode 100644 game/graphics/opengl_renderer/shaders/direct_basic.vert create mode 100644 game/graphics/types.h diff --git a/common/math/Vector.h b/common/math/Vector.h index 28ea905409..cd4a8cd84b 100644 --- a/common/math/Vector.h +++ b/common/math/Vector.h @@ -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]; }; diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 9f04928513..8637875eba 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -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 diff --git a/game/graphics/dma/dma.cpp b/game/graphics/dma/dma.cpp index 8a4b8c11e4..6f65606c4c 100644 --- a/game/graphics/dma/dma.cpp +++ b/game/graphics/dma/dma.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; +} + diff --git a/game/graphics/dma/dma.h b/game/graphics/dma/dma.h index be82a84096..cc59c27886 100644 --- a/game/graphics/dma/dma.h +++ b/game/graphics/dma/dma.h @@ -6,6 +6,8 @@ */ #include +#include +#include "common/util/assert.h" #include "common/common_types.h" struct DmaTag { @@ -33,4 +35,45 @@ struct DmaTag { Kind kind; std::string print(); -}; \ No newline at end of file +}; + +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(); +}; + diff --git a/game/graphics/dma/dma_chain_read.h b/game/graphics/dma/dma_chain_read.h index 7a90608f35..8d163c9941 100644 --- a/game/graphics/dma/dma_chain_read.h +++ b/game/graphics/dma/dma_chain_read.h @@ -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 { diff --git a/game/graphics/dma/gs.cpp b/game/graphics/dma/gs.cpp new file mode 100644 index 0000000000..039f4ca60d --- /dev/null +++ b/game/graphics/dma/gs.cpp @@ -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; +} \ No newline at end of file diff --git a/game/graphics/dma/gs.h b/game/graphics/dma/gs.h new file mode 100644 index 0000000000..508a85462e --- /dev/null +++ b/game/graphics/dma/gs.h @@ -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; } +}; \ No newline at end of file diff --git a/game/graphics/dma_reader.cpp b/game/graphics/dma_reader.cpp deleted file mode 100644 index 7b8e8d65f2..0000000000 --- a/game/graphics/dma_reader.cpp +++ /dev/null @@ -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 flatten_dma(const DmaReadState& in) { -// DmaReadState state = in; -// std::vector 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(read_result.addr + 8 * i)); -// } -// } -// return result; -//} -// -//void debug_print_dma(const DmaReadState& in) { -// DmaReadState state = in; -// std::vector result; -// while (!state.ended) { -// u64 tag_data = state.read(state.tag_addr); -// DmaTag tag(tag_data); -// fmt::print("@ 0x{:8x} {}", state.tag_addr, tag.print()); -// next_dma(&state); -// } -//} -// -//std::vector> 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> flattened_buckets; -// std::vector 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(read_result.addr + 8 * i)); -// } -// } -// return flattened_buckets; -//} -// -//std::vector 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 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; -//} \ No newline at end of file diff --git a/game/graphics/dma_reader.h b/game/graphics/dma_reader.h deleted file mode 100644 index c96a520754..0000000000 --- a/game/graphics/dma_reader.h +++ /dev/null @@ -1,38 +0,0 @@ -//#pragma once -// -//#include -// -//#include -// -//#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 flatten_dma(const DmaReadState& in); -//std::vector> 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 find_buckets(const DmaReadState& in); \ No newline at end of file diff --git a/game/graphics/opengl_renderer/BucketRenderer.cpp b/game/graphics/opengl_renderer/BucketRenderer.cpp new file mode 100644 index 0000000000..00135583e3 --- /dev/null +++ b/game/graphics/opengl_renderer/BucketRenderer.cpp @@ -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); +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/BucketRenderer.h b/game/graphics/opengl_renderer/BucketRenderer.h new file mode 100644 index 0000000000..b2f6023f83 --- /dev/null +++ b/game/graphics/opengl_renderer/BucketRenderer.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#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; +}; \ No newline at end of file diff --git a/game/graphics/opengl_renderer/DirectRenderer.cpp b/game/graphics/opengl_renderer/DirectRenderer.cpp new file mode 100644 index 0000000000..a6b2b70405 --- /dev/null +++ b/game/graphics/opengl_renderer/DirectRenderer.cpp @@ -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 corner3_vert = {corner1_vert[0], corner2_vert[1], corner2_vert[2]}; + math::Vector 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& rgba, + const math::Vector& vert) { + rgba_u8[vert_count] = rgba; + verts[vert_count] = vert; + vert_count++; +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/DirectRenderer.h b/game/graphics/opengl_renderer/DirectRenderer.h new file mode 100644 index 0000000000..762c821be2 --- /dev/null +++ b/game/graphics/opengl_renderer/DirectRenderer.h @@ -0,0 +1,143 @@ +#pragma once + +#include +#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 rgba_reg = {0, 0, 0, 0}; + + std::array, 3> building_rgba; + std::array, 3> building_vert; + int building_idx = 0; + + } m_prim_building; + + struct PrimitiveBuffer { + PrimitiveBuffer(int max_triangles); + std::vector> rgba_u8; + std::vector> 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& rgba, const math::Vector& 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; +}; diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index afd60e2ec2..78ff34e34b 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -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("bucket0", BucketId::BUCKET0); + // TODO what the heck is drawing to debug-draw-0 on init? + init_bucket_renderer("debug-draw-0", BucketId::DEBUG_DRAW_0, 1024); + init_bucket_renderer("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(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); -} \ No newline at end of file +} diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.h b/game/graphics/opengl_renderer/OpenGLRenderer.h index 336e5c5a8b..313f369d4d 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.h +++ b/game/graphics/opengl_renderer/OpenGLRenderer.h @@ -1,16 +1,29 @@ #pragma once +#include +#include + #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 + void init_bucket_renderer(const std::string& name, BucketId id, Args&&... args) { + m_bucket_renderers.at((int)id) = std::make_unique(name, id, std::forward(args)...); + } + + SharedRenderState m_render_state; + + std::array, (int)BucketId::MAX_BUCKETS> m_bucket_renderers; }; - diff --git a/game/graphics/opengl_renderer/Shader.cpp b/game/graphics/opengl_renderer/Shader.cpp index 54796d33b2..8fa5ad8a2f 100644 --- a/game/graphics/opengl_renderer/Shader.cpp +++ b/game/graphics/opengl_renderer/Shader.cpp @@ -65,4 +65,5 @@ void Shader::activate() { ShaderLibrary::ShaderLibrary() { at(ShaderId::TEST_SHADER) = {"test_shader"}; + at(ShaderId::DIRECT_BASIC) = {"direct_basic"}; } \ No newline at end of file diff --git a/game/graphics/opengl_renderer/Shader.h b/game/graphics/opengl_renderer/Shader.h index cbd0e547d2..50cb4aa63e 100644 --- a/game/graphics/opengl_renderer/Shader.h +++ b/game/graphics/opengl_renderer/Shader.h @@ -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: diff --git a/game/graphics/opengl_renderer/shaders/direct_basic.frag b/game/graphics/opengl_renderer/shaders/direct_basic.frag new file mode 100644 index 0000000000..71bb34d2b2 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/direct_basic.frag @@ -0,0 +1,9 @@ +#version 330 core + +out vec4 color; + +in vec4 fragment_color; + +void main() { + color = fragment_color; +} diff --git a/game/graphics/opengl_renderer/shaders/direct_basic.vert b/game/graphics/opengl_renderer/shaders/direct_basic.vert new file mode 100644 index 0000000000..44adb20d02 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/direct_basic.vert @@ -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); +} \ No newline at end of file diff --git a/game/graphics/types.h b/game/graphics/types.h new file mode 100644 index 0000000000..8db03db38c --- /dev/null +++ b/game/graphics/types.h @@ -0,0 +1,6 @@ + + +#ifndef JAK_TYPES_H +#define JAK_TYPES_H + +#endif // JAK_TYPES_H diff --git a/game/kernel/kmachine.cpp b/game/kernel/kmachine.cpp index 29c45452d1..aa7e92beb8 100644 --- a/game/kernel/kmachine.cpp +++ b/game/kernel/kmachine.cpp @@ -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"