jak-project/game/graphics/opengl_renderer/DirectRenderer.h
ManDude 6884b0f73e
start blit-displays decomp & renderer + improve decompilation of some DMA macros (#2616)
Adds sprite distort, fixes buggy sprite rendering in progress, adds
scissoring support (used in various scrolling menus) and a very basic
implementation of `blit-displays`. This is enough to make the fade
effect in the progress menu work, along with all the menus working
properly without needing to use the REPL. This does not make screen
flipping and the filter when failing a mission work.

Added support in the decompiler for detecting `dma-buffer-add-gs-set`
and `dma-buffer-add-gs-set-flusha` and updated all of the Jak 2 code to
use it. Readability improved!

Fixes decompiler issue with `with-dma-buffer-add-bucket` not inlining
forms which broke syntax. Fixes store error warnings showing up for
non-existent stores, there is now a dedicated pass for this at the end.

I started work on making `BITBLTBUF` stuff work in the DirectRenderer,
but stopped for now because it wasn't strictly necessary. It will still
assert like before.
2023-05-04 18:34:09 -04:00

323 lines
10 KiB
C++

#pragma once
#include <vector>
#include "common/dma/gs.h"
#include "common/math/Vector.h"
#include "common/util/SmallVector.h"
#include "game/graphics/opengl_renderer/BucketRenderer.h"
#include "game/graphics/pipelines/opengl.h"
/*!
* The direct renderer will handle rendering GIFtags directly.
* It's named after the DIRECT VIFCode which sends data directly to the GS.
*
* It should mostly be used for debugging/text stuff as this rendering style does all the math on
* the EE and just sends geometry directly to the GS without using the VUs.
*
* It can be used as a BucketRenderer, or as a subcomponent of another renderer.
*/
class DirectRenderer : public BucketRenderer {
public:
DirectRenderer(const std::string& name, int my_id, int batch_size);
~DirectRenderer();
void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override;
virtual void pre_render() {}
virtual void post_render() {}
/*!
* 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,
ScopedProfilerNode& prof);
/*!
* Render directly from _GIF_ data.
*/
void render_gif(const u8* data,
u32 size,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
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, ScopedProfilerNode& prof);
void draw_debug_window() override;
void hack_disable_blend() {
m_blend_state.a = GsAlpha::BlendMode::SOURCE;
m_blend_state.b = GsAlpha::BlendMode::SOURCE;
m_blend_state.c = GsAlpha::BlendMode::SOURCE;
m_blend_state.d = GsAlpha::BlendMode::SOURCE;
}
void set_mipmap(bool en) { m_debug_state.disable_mipmap = !en; }
void handle_prim(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof);
void handle_ad(const u8* data, SharedRenderState* render_state, ScopedProfilerNode& prof);
void handle_st_packed(const u8* data);
void handle_rgbaq_packed(const u8* data);
void handle_xyzf2_packed(const u8* data,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
void handle_xyz2_packed(const u8* data,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
void handle_prim_packed(const u8* data,
SharedRenderState* render_state,
ScopedProfilerNode& prof);
void handle_tex0_1_packed(const u8* data);
void handle_uv_packed(const u8* data);
void handle_rgbaq(u64 val);
void handle_xyzf2(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof);
protected:
virtual void handle_frame(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof);
void handle_scissor(u64 val);
void handle_zbuf1(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof);
void handle_test1(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof);
void handle_alpha1(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof);
void handle_pabe(u64 val);
void handle_clamp1(u64 val);
void handle_tex0_1(u64 val);
void handle_tex1_1(u64 val);
void handle_texa(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof);
void handle_xyoffset(u64 val);
void handle_bitbltbuf(u64 val);
void handle_trxpos(u64 val);
void handle_trxreg(u64 val);
void handle_trxdir(u64 dir, SharedRenderState* render_state, ScopedProfilerNode& prof);
void handle_xyzf2_common(u32 x,
u32 y,
u32 z,
u8 f,
SharedRenderState* render_state,
ScopedProfilerNode& prof,
bool advance);
void update_gl_prim(SharedRenderState* render_state);
void update_gl_blend();
void update_gl_test();
void update_gl_texture(SharedRenderState* render_state, int unit);
bool m_offscreen_mode = false;
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;
bool write_rgb = true;
bool depth_writes = true;
} 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?
u32 ta0 = 0;
} m_prim_gl_state;
static constexpr int TEXTURE_STATE_COUNT = 1;
struct TextureState {
GsTex0 current_register;
u32 texture_base_ptr = 0;
bool using_mt4hh = false;
bool tcc = false;
bool decal = false;
bool enable_tex_filt = true;
struct ClampState {
void from_register(u64 value) { current_register = value; }
u64 current_register = 0b101;
bool clamp_s = true;
bool clamp_t = true;
} m_clamp_state;
bool used = false;
bool compatible_with(const TextureState& other) {
return current_register == other.current_register &&
m_clamp_state.current_register == other.m_clamp_state.current_register &&
enable_tex_filt == other.enable_tex_filt;
}
};
// vertices will reference these texture states
TextureState m_buffered_tex_state[TEXTURE_STATE_COUNT];
int m_next_free_tex_state = 0;
// this texture state mirrors the current GS register.
TextureState m_tex_state_from_reg;
// if this is not -1, then it is the index of a texture state in m_buffered_tex_state that
// matches m_tex_state_from_reg.
int m_current_tex_state_idx = -1;
int get_texture_unit_for_current_reg(SharedRenderState* render_state, ScopedProfilerNode& prof);
// state set through the prim/rgbaq register that doesn't require changing GL stuff
struct PrimBuildState {
GsPrim::Kind kind = GsPrim::Kind::PRIM_7;
math::Vector<u8, 4> rgba_reg = math::Vector<u8, 4>{0, 0, 0, 0};
math::Vector<float, 2> st_reg;
std::array<math::Vector<u8, 4>, 3> building_rgba;
std::array<math::Vector<u32, 4>, 3> building_vert;
std::array<math::Vector<float, 3>, 3> building_stq;
int building_idx = 0;
int tri_strip_startup = 0;
float Q = 1.0;
} m_prim_building;
struct Vertex {
math::Vector<float, 4> xyzf;
math::Vector<float, 3> stq;
math::Vector<u8, 4> rgba;
u8 tex_unit;
u8 tcc;
u8 decal;
u8 fog_enable;
u8 use_uv;
math::Vector<u8, 11> __pad;
// this can be simplified to use gs coords, if needed
math::Vector<float, 4> scissor;
};
static_assert(sizeof(Vertex) == 64);
static_assert(offsetof(Vertex, tex_unit) == 32);
struct PrimitiveBuffer {
PrimitiveBuffer(int max_triangles);
std::vector<Vertex> vertices;
int vert_count = 0;
int max_verts = 0;
float x_off = 0;
float y_off = 0;
// leave 6 free on the end so we always have room to flush one last primitive.
bool is_full() { return max_verts < (vert_count + 18); }
void push(const math::Vector<u8, 4>& rgba,
const math::Vector<u32, 4>& vert,
const math::Vector<float, 3>& stq,
const math::Vector<float, 4>& scissor,
int unit,
bool tcc,
bool decal,
bool fog_enable,
bool use_uv);
} m_prim_buffer;
// the scissor state tends to be shared across buckets, so it is static here
static struct ScissorState {
u16 scax0 = 0, scay0 = 0;
u16 scax1 = 0, scay1 = 0;
} m_scissor;
// however the toggle for it is per-bucket
bool m_scissor_enable = false;
struct BufferBlitState {
// used to keep track of blit progress
u8 expect = 0;
// blit buffer source+dest settings
u16 sbp = 0, dbp = 0;
u8 sbw = 0, dbw = 0;
u8 spsm = 0, dpsm = 0;
// transfer pos
u16 ssax = 0, dsax = 0;
u16 ssay = 0, dsay = 0;
// transfer region
u16 width = 0, height = 0;
// transfer dir
u8 pixel_dir = 0;
// gif IMAGE transfer size
u16 qwc = 0;
} m_blit_buf_state;
struct {
GLuint vertex_buffer;
GLuint vao;
u32 vertex_buffer_bytes = 0;
u32 vertex_buffer_max_verts = 0;
float color_mult = 1.0;
float alpha_mult = 1.0;
} m_ogl;
struct {
bool disable_texture = false;
bool wireframe = false;
bool red = false;
bool always_draw = false;
bool disable_mipmap = true;
} m_debug_state;
struct {
int triangles = 0;
int draw_calls = 0;
int flush_from_tex_0 = 0;
int flush_from_tex_1 = 0;
int flush_from_zbuf = 0;
int flush_from_test = 0;
int flush_from_ta0 = 0;
int flush_from_alpha = 0;
int flush_from_clamp = 0;
int flush_from_prim = 0;
int flush_from_state_exhaust = 0;
} m_stats;
bool m_prim_gl_state_needs_gl_update = true;
bool m_test_state_needs_gl_update = true;
bool m_blend_state_needs_gl_update = true;
struct SpriteMode {
bool do_first_draw = true;
} m_sprite_mode;
};