mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 00:57:44 -04:00
add an optional, less-accurate-but-faster sprite render and fix silly math bug (#1102)
* also add a new sprite renderer * claaaang * goal build fix * fix tests, add stack singleton option * make all event-message-blocks the same * diskboot
This commit is contained in:
parent
4648f78733
commit
35bdc9b1d3
|
@ -366,6 +366,13 @@ class DrawMode {
|
|||
bool get_depth_write_enable() const { return m_val & 0b1; }
|
||||
void enable_depth_write() { m_val = m_val | 0b1; }
|
||||
void disable_depth_write() { m_val = m_val & ~(0b1); }
|
||||
void set_depth_write_enable(bool x) {
|
||||
if (x) {
|
||||
enable_depth_write();
|
||||
} else {
|
||||
disable_depth_write();
|
||||
}
|
||||
}
|
||||
|
||||
GsTest::ZTest get_depth_test() const { return (GsTest::ZTest)((m_val >> 1) & 0b11); }
|
||||
void set_depth_test(GsTest::ZTest dt) { m_val = (m_val & ~(0b110)) | ((u32)(dt) << 1); }
|
||||
|
|
|
@ -696,9 +696,9 @@ StructureType::StructureType(std::string parent,
|
|||
std::string StructureType::print() const {
|
||||
std::string result = fmt::format(
|
||||
"[StructureType] {}\n parent: {}\n boxed: {}\n dynamic: {}\n size: {}\n pack: {}\n misalign: "
|
||||
"{}\n heap-base: {}\n fields:\n",
|
||||
m_name, m_parent, m_is_boxed, m_dynamic, m_size_in_mem, m_pack, m_allow_misalign,
|
||||
m_heap_base);
|
||||
"{}\n heap-base: {}\n stack-singleton: {}\n fields:\n",
|
||||
m_name, m_parent, m_is_boxed, m_dynamic, m_size_in_mem, m_pack, m_allow_misalign, m_heap_base,
|
||||
m_always_stack_singleton);
|
||||
for (auto& x : m_fields) {
|
||||
result += " " + x.print() + "\n";
|
||||
}
|
||||
|
@ -727,7 +727,8 @@ bool StructureType::operator==(const Type& other) const {
|
|||
m_pack == p_other->m_pack &&
|
||||
m_allow_misalign == p_other->m_allow_misalign &&
|
||||
m_offset == p_other->m_offset &&
|
||||
m_idx_of_first_unique_field == p_other->m_idx_of_first_unique_field;
|
||||
m_idx_of_first_unique_field == p_other->m_idx_of_first_unique_field &&
|
||||
m_always_stack_singleton == p_other->m_always_stack_singleton;
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
|
@ -773,6 +774,11 @@ std::string StructureType::diff_structure_common(const StructureType& other) con
|
|||
result += fmt::format("allow_misalign: {} vs. {}\n", m_allow_misalign, other.m_allow_misalign);
|
||||
}
|
||||
|
||||
if (m_always_stack_singleton != other.m_always_stack_singleton) {
|
||||
result += fmt::format("always_stack_singleton: {} vs. {}\n", m_always_stack_singleton,
|
||||
other.m_always_stack_singleton);
|
||||
}
|
||||
|
||||
if (m_offset != other.m_offset) {
|
||||
result += fmt::format("offset: {} vs. {}\n", m_offset, other.m_offset);
|
||||
}
|
||||
|
@ -906,7 +912,8 @@ bool BasicType::operator==(const Type& other) const {
|
|||
m_allow_misalign == p_other->m_allow_misalign &&
|
||||
m_offset == p_other->m_offset &&
|
||||
m_idx_of_first_unique_field == p_other->m_idx_of_first_unique_field &&
|
||||
m_final == p_other->m_final;
|
||||
m_final == p_other->m_final &&
|
||||
m_always_stack_singleton == p_other->m_always_stack_singleton;
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
|
|
|
@ -275,9 +275,11 @@ class StructureType : public ReferenceType {
|
|||
bool is_dynamic() const { return m_dynamic; }
|
||||
~StructureType() = default;
|
||||
void set_pack(bool pack) { m_pack = pack; }
|
||||
void set_always_stack_singleton() { m_always_stack_singleton = true; }
|
||||
void set_heap_base(int hb) { m_heap_base = hb; }
|
||||
bool is_packed() const { return m_pack; }
|
||||
bool is_allowed_misalign() const { return m_allow_misalign; };
|
||||
bool is_always_stack_singleton() const { return m_always_stack_singleton; }
|
||||
void set_allow_misalign(bool misalign) { m_allow_misalign = misalign; }
|
||||
void set_gen_inspect(bool gen_inspect) { m_generate_inspect = gen_inspect; }
|
||||
|
||||
|
@ -300,6 +302,7 @@ class StructureType : public ReferenceType {
|
|||
bool m_pack = false;
|
||||
bool m_allow_misalign = false;
|
||||
int m_offset = 0;
|
||||
bool m_always_stack_singleton = false;
|
||||
size_t m_idx_of_first_unique_field = 0;
|
||||
};
|
||||
|
||||
|
|
|
@ -1617,6 +1617,9 @@ std::string TypeSystem::generate_deftype_footer(const Type* type) const {
|
|||
if (as_structure->is_allowed_misalign()) {
|
||||
result.append(" :allow-misaligned\n");
|
||||
}
|
||||
if (as_structure->is_always_stack_singleton()) {
|
||||
result.append(" :always-stack-singleton\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (type->heap_base()) {
|
||||
|
|
|
@ -277,6 +277,7 @@ struct StructureDefResult {
|
|||
bool pack_me = false;
|
||||
bool allow_misaligned = false;
|
||||
bool final = false;
|
||||
bool always_stack_singleton = false;
|
||||
};
|
||||
|
||||
StructureDefResult parse_structure_def(StructureType* type,
|
||||
|
@ -347,6 +348,8 @@ StructureDefResult parse_structure_def(StructureType* type,
|
|||
result.allow_misaligned = true;
|
||||
} else if (opt_name == ":final") {
|
||||
result.final = true;
|
||||
} else if (opt_name == ":always-stack-singleton") {
|
||||
result.always_stack_singleton = true;
|
||||
} else {
|
||||
throw std::runtime_error("Invalid option in field specification: " + opt_name);
|
||||
}
|
||||
|
@ -572,6 +575,13 @@ DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts) {
|
|||
name);
|
||||
throw std::runtime_error("invalid pack option on basic");
|
||||
}
|
||||
if (sr.always_stack_singleton) {
|
||||
fmt::print(
|
||||
"[TypeSystem] :always-stack-singleton was set on {}, which is a basic and cannot "
|
||||
"be a stack singleton\n",
|
||||
name);
|
||||
throw std::runtime_error("invalid stack singleton option on basic");
|
||||
}
|
||||
new_type->set_heap_base(result.flags.heap_base);
|
||||
if (sr.final) {
|
||||
new_type->set_final();
|
||||
|
@ -592,6 +602,9 @@ DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts) {
|
|||
if (sr.allow_misaligned) {
|
||||
new_type->set_allow_misalign(true);
|
||||
}
|
||||
if (sr.always_stack_singleton) {
|
||||
new_type->set_always_stack_singleton();
|
||||
}
|
||||
if (sr.final) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("[TypeSystem] :final option cannot be used on structure type {}", name));
|
||||
|
|
|
@ -1469,6 +1469,7 @@
|
|||
:method-count-assert 9
|
||||
:size-assert #x48
|
||||
:flag-assert #x900000048
|
||||
:always-stack-singleton
|
||||
)
|
||||
|
||||
;; - Symbols
|
||||
|
|
|
@ -211,4 +211,5 @@
|
|||
- It is now an error to use a `none`-typed variable in a condition
|
||||
- Debugger will now correctly track when object files are loaded over previous files
|
||||
- Asm ops requiring 128-bit inputs will now try harder to convert their inputs when it is appropriate.
|
||||
- 0's that are constant propagated to the input of a 128-bit instruction will use `vpxor` instruction to generate the value, instead of `xor` and a `mov`.
|
||||
- 0's that are constant propagated to the input of a 128-bit instruction will use `vpxor` instruction to generate the value, instead of `xor` and a `mov`.
|
||||
- Add a `stack-singleton-no-clear` stack construction type. It will create a "singleton" inside this function - all other `(new 'stack-singleton` forms with the same type will return the same stack object.
|
|
@ -101,6 +101,7 @@ set(RUNTIME_SOURCE
|
|||
graphics/opengl_renderer/SkyBlendCPU.cpp
|
||||
graphics/opengl_renderer/SkyBlendGPU.cpp
|
||||
graphics/opengl_renderer/SkyRenderer.cpp
|
||||
graphics/opengl_renderer/Sprite3.cpp
|
||||
graphics/opengl_renderer/SpriteRenderer.cpp
|
||||
graphics/opengl_renderer/TextureUploadHandler.cpp
|
||||
graphics/opengl_renderer/tfrag/BufferedRenderer.cpp
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "BucketRenderer.h"
|
||||
#include "third-party/imgui/imgui.h"
|
||||
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
|
@ -62,4 +63,32 @@ void SharedRenderState::reset() {
|
|||
for (auto& x : occlusion_vis) {
|
||||
x.valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
RenderMux::RenderMux(const std::string& name,
|
||||
BucketId my_id,
|
||||
std::vector<std::unique_ptr<BucketRenderer>> renderers)
|
||||
: BucketRenderer(name, my_id), m_renderers(std::move(renderers)) {
|
||||
for (auto& r : m_renderers) {
|
||||
m_name_strs.push_back(r->name_and_id());
|
||||
m_name_str_ptrs.push_back(m_name_strs.back().data());
|
||||
}
|
||||
}
|
||||
|
||||
void RenderMux::render(DmaFollower& dma,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof) {
|
||||
m_renderers[m_render_idx]->render(dma, render_state, prof);
|
||||
}
|
||||
|
||||
void RenderMux::serialize(Serializer& ser) {
|
||||
for (auto& r : m_renderers) {
|
||||
r->serialize(ser);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderMux::draw_debug_window() {
|
||||
ImGui::ListBox("Pick", &m_render_idx, m_name_str_ptrs.data(), m_renderers.size());
|
||||
ImGui::Separator();
|
||||
m_renderers[m_render_idx]->draw_debug_window();
|
||||
}
|
|
@ -98,6 +98,22 @@ class BucketRenderer {
|
|||
bool m_enabled = true;
|
||||
};
|
||||
|
||||
class RenderMux : public BucketRenderer {
|
||||
public:
|
||||
RenderMux(const std::string& name,
|
||||
BucketId my_id,
|
||||
std::vector<std::unique_ptr<BucketRenderer>> renderers);
|
||||
void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override;
|
||||
void draw_debug_window() override;
|
||||
void serialize(Serializer& ser) override;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<BucketRenderer>> m_renderers;
|
||||
int m_render_idx = 0;
|
||||
std::vector<std::string> m_name_strs;
|
||||
std::vector<const char*> m_name_str_ptrs;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Renderer that makes sure the bucket is empty and ignores it.
|
||||
*/
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "third-party/imgui/imgui.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "game/graphics/opengl_renderer/SkyRenderer.h"
|
||||
#include "game/graphics/opengl_renderer/Sprite3.h"
|
||||
#include "game/graphics/opengl_renderer/tfrag/TFragment.h"
|
||||
#include "game/graphics/opengl_renderer/tfrag/Tie3.h"
|
||||
|
||||
|
@ -100,7 +101,11 @@ void OpenGLRenderer::init_bucket_renderers() {
|
|||
init_bucket_renderer<TextureUploadHandler>("water-tex-0", BucketId::WATER_TEX_LEVEL0);
|
||||
init_bucket_renderer<TextureUploadHandler>("water-tex-1", BucketId::WATER_TEX_LEVEL1);
|
||||
init_bucket_renderer<TextureUploadHandler>("pre-sprite-tex", BucketId::PRE_SPRITE_TEX);
|
||||
init_bucket_renderer<SpriteRenderer>("sprite", BucketId::SPRITE);
|
||||
std::vector<std::unique_ptr<BucketRenderer>> sprite_renderers;
|
||||
sprite_renderers.push_back(std::make_unique<SpriteRenderer>("sprite-renderer", BucketId::SPRITE));
|
||||
sprite_renderers.push_back(std::make_unique<Sprite3>("sprite-3", BucketId::SPRITE));
|
||||
|
||||
init_bucket_renderer<RenderMux>("sprite", BucketId::SPRITE, std::move(sprite_renderers));
|
||||
init_bucket_renderer<DirectRenderer>("debug-draw-0", BucketId::DEBUG_DRAW_0, 0x8000,
|
||||
DirectRenderer::Mode::NORMAL);
|
||||
init_bucket_renderer<DirectRenderer>("debug-draw-1", BucketId::DEBUG_DRAW_1, 0x8000,
|
||||
|
|
|
@ -77,4 +77,5 @@ ShaderLibrary::ShaderLibrary() {
|
|||
at(ShaderId::BUFFERED_TCC1) = {"buffered_tcc1"};
|
||||
at(ShaderId::TFRAG3) = {"tfrag3"};
|
||||
at(ShaderId::TFRAG3_NO_TEX) = {"tfrag3_no_tex"};
|
||||
at(ShaderId::SPRITE3) = {"sprite3_3d"};
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ enum class ShaderId {
|
|||
TFRAG3 = 12,
|
||||
TFRAG3_NO_TEX = 13,
|
||||
SPRITE = 14,
|
||||
SPRITE3 = 15,
|
||||
MAX_SHADERS
|
||||
};
|
||||
|
||||
|
|
697
game/graphics/opengl_renderer/Sprite3.cpp
Normal file
697
game/graphics/opengl_renderer/Sprite3.cpp
Normal file
|
@ -0,0 +1,697 @@
|
|||
|
||||
|
||||
#include "Sprite3.h"
|
||||
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "third-party/imgui/imgui.h"
|
||||
#include "game/graphics/opengl_renderer/dma_helpers.h"
|
||||
#include "game/graphics/opengl_renderer/tfrag/tfrag_common.h"
|
||||
|
||||
namespace {
|
||||
|
||||
/*!
|
||||
* Does the next DMA transfer look like it could be the start of a 2D group?
|
||||
*/
|
||||
bool looks_like_2d_chunk_start(const DmaFollower& dma) {
|
||||
return dma.current_tag().qwc == 1 && dma.current_tag().kind == DmaTag::Kind::CNT;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Read the header. Asserts if it's bad.
|
||||
* Returns the number of sprites.
|
||||
* Advances 1 dma transfer
|
||||
*/
|
||||
u32 process_sprite_chunk_header(DmaFollower& dma) {
|
||||
auto transfer = dma.read_and_advance();
|
||||
// note that flg = true, this should use double buffering
|
||||
bool ok = verify_unpack_with_stcycl(transfer, VifCode::Kind::UNPACK_V4_32, 4, 4, 1,
|
||||
SpriteDataMem::Header, false, true);
|
||||
assert(ok);
|
||||
u32 header[4];
|
||||
memcpy(header, transfer.data, 16);
|
||||
assert(header[0] <= Sprite3::SPRITES_PER_CHUNK);
|
||||
return header[0];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
constexpr int SPRITE_RENDERER_MAX_SPRITES = 8000;
|
||||
|
||||
Sprite3::Sprite3(const std::string& name, BucketId my_id) : BucketRenderer(name, my_id) {
|
||||
glGenBuffers(1, &m_ogl.vertex_buffer);
|
||||
glGenVertexArrays(1, &m_ogl.vao);
|
||||
glBindVertexArray(m_ogl.vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
|
||||
auto verts = SPRITE_RENDERER_MAX_SPRITES * 4;
|
||||
auto bytes = verts * sizeof(SpriteVertex3D);
|
||||
glBufferData(GL_ARRAY_BUFFER, bytes, nullptr, GL_STREAM_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(
|
||||
0, // location 0 in the shader
|
||||
4, // 4 floats per vert (w unused)
|
||||
GL_FLOAT, // floats
|
||||
GL_TRUE, // normalized, ignored,
|
||||
sizeof(SpriteVertex3D), //
|
||||
(void*)offsetof(SpriteVertex3D, xyz_sx) // offset in array (why is this a pointer...)
|
||||
);
|
||||
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(
|
||||
1, // location 0 in the shader
|
||||
4, // 4 color components
|
||||
GL_FLOAT, // floats
|
||||
GL_TRUE, // normalized, ignored,
|
||||
sizeof(SpriteVertex3D), //
|
||||
(void*)offsetof(SpriteVertex3D, quat_sy) // offset in array (why is this a pointer...)
|
||||
);
|
||||
|
||||
glEnableVertexAttribArray(2);
|
||||
glVertexAttribPointer(
|
||||
2, // location 0 in the shader
|
||||
4, // 4 color components
|
||||
GL_FLOAT, // floats
|
||||
GL_TRUE, // normalized, ignored,
|
||||
sizeof(SpriteVertex3D), //
|
||||
(void*)offsetof(SpriteVertex3D, rgba) // offset in array (why is this a pointer...)
|
||||
);
|
||||
|
||||
glEnableVertexAttribArray(3);
|
||||
glVertexAttribIPointer(
|
||||
3, // location 0 in the shader
|
||||
2, // 4 color components
|
||||
GL_UNSIGNED_SHORT, // floats
|
||||
sizeof(SpriteVertex3D), //
|
||||
(void*)offsetof(SpriteVertex3D, flags_matrix) // offset in array (why is this a pointer...)
|
||||
);
|
||||
|
||||
glEnableVertexAttribArray(4);
|
||||
glVertexAttribIPointer(
|
||||
4, // location 0 in the shader
|
||||
4, // 3 floats per vert
|
||||
GL_UNSIGNED_SHORT, // floats
|
||||
sizeof(SpriteVertex3D), //
|
||||
(void*)offsetof(SpriteVertex3D, info) // offset in array (why is this a pointer...)
|
||||
);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
u32 idx_buffer_len = SPRITE_RENDERER_MAX_SPRITES * 5;
|
||||
glGenBuffers(1, &m_ogl.index_buffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ogl.index_buffer);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr, GL_STREAM_DRAW);
|
||||
|
||||
glBindVertexArray(0);
|
||||
|
||||
m_vertices_3d.resize(verts);
|
||||
m_index_buffer_data.resize(idx_buffer_len);
|
||||
|
||||
m_default_mode.disable_depth_write();
|
||||
m_default_mode.set_depth_test(GsTest::ZTest::GEQUAL);
|
||||
m_default_mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST);
|
||||
m_default_mode.set_aref(38);
|
||||
m_default_mode.set_alpha_test(DrawMode::AlphaTest::GEQUAL);
|
||||
m_default_mode.set_alpha_fail(GsTest::AlphaFail::FB_ONLY);
|
||||
m_default_mode.set_at(true);
|
||||
m_default_mode.set_zt(true);
|
||||
m_default_mode.set_ab(true);
|
||||
|
||||
m_current_mode = m_default_mode;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Run the sprite distorter. Currently nothing uses sprite-distorter so this just skips through
|
||||
* the table upload stuff that runs every frame, even if there are no sprites.
|
||||
*/
|
||||
void Sprite3::render_distorter(DmaFollower& dma,
|
||||
SharedRenderState* /*render_state*/,
|
||||
ScopedProfilerNode& /*prof*/) {
|
||||
// Next thing should be the sprite-distorter setup
|
||||
// m_direct_renderer.reset_state();
|
||||
while (dma.current_tag().qwc != 7) {
|
||||
dma.read_and_advance();
|
||||
// m_direct_renderer.render_vif(direct_data.vif0(), direct_data.vif1(), direct_data.data,
|
||||
// direct_data.size_bytes, render_state, prof);
|
||||
}
|
||||
// m_direct_renderer.flush_pending(render_state, prof);
|
||||
auto sprite_distorter_direct_setup = dma.read_and_advance();
|
||||
assert(sprite_distorter_direct_setup.vifcode0().kind == VifCode::Kind::NOP);
|
||||
assert(sprite_distorter_direct_setup.vifcode1().kind == VifCode::Kind::DIRECT);
|
||||
assert(sprite_distorter_direct_setup.vifcode1().immediate == 7);
|
||||
memcpy(m_sprite_distorter_setup, sprite_distorter_direct_setup.data, 7 * 16);
|
||||
|
||||
// Next thing should be the sprite-distorter tables
|
||||
auto sprite_distorter_tables = dma.read_and_advance();
|
||||
assert(sprite_distorter_tables.size_bytes == 0x8b * 16);
|
||||
assert(sprite_distorter_tables.vifcode0().kind == VifCode::Kind::STCYCL);
|
||||
VifCodeStcycl distorter_table_transfer(sprite_distorter_tables.vifcode0());
|
||||
assert(distorter_table_transfer.cl == 4);
|
||||
assert(distorter_table_transfer.wl == 4);
|
||||
// TODO: check unpack cmd (vif1)
|
||||
|
||||
// TODO: do something with the table
|
||||
|
||||
// next would be the program, but we don't have it.
|
||||
|
||||
// TODO: next is the sprite-distorter (currently not used)
|
||||
}
|
||||
|
||||
/*!
|
||||
* Handle DMA data that does the per-frame setup.
|
||||
* This should get the dma chain immediately after the call to sprite-draw-distorters.
|
||||
* It ends right before the sprite-add-matrix-data for the 3d's
|
||||
*/
|
||||
void Sprite3::handle_sprite_frame_setup(DmaFollower& dma) {
|
||||
// first is some direct data
|
||||
auto direct_data = dma.read_and_advance();
|
||||
assert(direct_data.size_bytes == 3 * 16);
|
||||
memcpy(m_sprite_direct_setup, direct_data.data, 3 * 16);
|
||||
|
||||
// next would be the program, but it's 0 size on the PC and isn't sent.
|
||||
|
||||
// next is the "frame data"
|
||||
auto frame_data = dma.read_and_advance();
|
||||
assert(frame_data.size_bytes == (int)sizeof(SpriteFrameData)); // very cool
|
||||
assert(frame_data.vifcode0().kind == VifCode::Kind::STCYCL);
|
||||
VifCodeStcycl frame_data_stcycl(frame_data.vifcode0());
|
||||
assert(frame_data_stcycl.cl == 4);
|
||||
assert(frame_data_stcycl.wl == 4);
|
||||
assert(frame_data.vifcode1().kind == VifCode::Kind::UNPACK_V4_32);
|
||||
VifCodeUnpack frame_data_unpack(frame_data.vifcode1());
|
||||
assert(frame_data_unpack.addr_qw == SpriteDataMem::FrameData);
|
||||
assert(frame_data_unpack.use_tops_flag == false);
|
||||
memcpy(&m_frame_data, frame_data.data, sizeof(SpriteFrameData));
|
||||
|
||||
// next, a MSCALF.
|
||||
auto mscalf = dma.read_and_advance();
|
||||
assert(mscalf.size_bytes == 0);
|
||||
assert(mscalf.vifcode0().kind == VifCode::Kind::MSCALF);
|
||||
assert(mscalf.vifcode0().immediate == SpriteProgMem::Init);
|
||||
assert(mscalf.vifcode1().kind == VifCode::Kind::FLUSHE);
|
||||
|
||||
// next base and offset
|
||||
auto base_offset = dma.read_and_advance();
|
||||
assert(base_offset.size_bytes == 0);
|
||||
assert(base_offset.vifcode0().kind == VifCode::Kind::BASE);
|
||||
assert(base_offset.vifcode0().immediate == SpriteDataMem::Buffer0);
|
||||
assert(base_offset.vifcode1().kind == VifCode::Kind::OFFSET);
|
||||
assert(base_offset.vifcode1().immediate == SpriteDataMem::Buffer1);
|
||||
}
|
||||
|
||||
void Sprite3::render_3d(DmaFollower& dma) {
|
||||
// one time matrix data
|
||||
auto matrix_data = dma.read_and_advance();
|
||||
assert(matrix_data.size_bytes == sizeof(Sprite3DMatrixData));
|
||||
|
||||
bool unpack_ok = verify_unpack_with_stcycl(matrix_data, VifCode::Kind::UNPACK_V4_32, 4, 4, 5,
|
||||
SpriteDataMem::Matrix, false, false);
|
||||
assert(unpack_ok);
|
||||
static_assert(sizeof(m_3d_matrix_data) == 5 * 16);
|
||||
memcpy(&m_3d_matrix_data, matrix_data.data, sizeof(m_3d_matrix_data));
|
||||
// TODO
|
||||
}
|
||||
|
||||
void Sprite3::render_2d_group0(DmaFollower& dma,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof) {
|
||||
// opengl sprite frame setup
|
||||
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "hvdf_offset"),
|
||||
1, m_3d_matrix_data.hvdf_offset.data());
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "pfog0"),
|
||||
m_frame_data.pfog0);
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "min_scale"),
|
||||
m_frame_data.min_scale);
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "max_scale"),
|
||||
m_frame_data.max_scale);
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "bonus"),
|
||||
m_frame_data.bonus);
|
||||
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "hmge_scale"), 1,
|
||||
m_frame_data.hmge_scale.data());
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "deg_to_rad"),
|
||||
m_frame_data.deg_to_rad);
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "inv_area"),
|
||||
m_frame_data.inv_area);
|
||||
glUniformMatrix4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "camera"),
|
||||
1, GL_FALSE, m_3d_matrix_data.camera.data());
|
||||
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "xy_array"), 8,
|
||||
m_frame_data.xy_array[0].data());
|
||||
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "xyz_array"), 4,
|
||||
m_frame_data.xyz_array[0].data());
|
||||
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "st_array"), 4,
|
||||
m_frame_data.st_array[0].data());
|
||||
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "basis_x"), 1,
|
||||
m_frame_data.basis_x.data());
|
||||
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "basis_y"), 1,
|
||||
m_frame_data.basis_y.data());
|
||||
|
||||
u16 last_prog = -1;
|
||||
|
||||
while (looks_like_2d_chunk_start(dma)) {
|
||||
m_debug_stats.blocks_2d_grp0++;
|
||||
// 4 packets per chunk
|
||||
|
||||
// first is the header
|
||||
u32 sprite_count = process_sprite_chunk_header(dma);
|
||||
m_debug_stats.count_2d_grp0 += sprite_count;
|
||||
|
||||
// second is the vector data
|
||||
u32 expected_vec_size = sizeof(SpriteVecData2d) * sprite_count;
|
||||
auto vec_data = dma.read_and_advance();
|
||||
assert(expected_vec_size <= sizeof(m_vec_data_2d));
|
||||
unpack_to_no_stcycl(&m_vec_data_2d, vec_data, VifCode::Kind::UNPACK_V4_32, expected_vec_size,
|
||||
SpriteDataMem::Vector, false, true);
|
||||
|
||||
// third is the adgif data
|
||||
u32 expected_adgif_size = sizeof(AdGifData) * sprite_count;
|
||||
auto adgif_data = dma.read_and_advance();
|
||||
assert(expected_adgif_size <= sizeof(m_adgif));
|
||||
unpack_to_no_stcycl(&m_adgif, adgif_data, VifCode::Kind::UNPACK_V4_32, expected_adgif_size,
|
||||
SpriteDataMem::Adgif, false, true);
|
||||
|
||||
// fourth is the actual run!!!!!
|
||||
auto run = dma.read_and_advance();
|
||||
assert(run.vifcode0().kind == VifCode::Kind::NOP);
|
||||
assert(run.vifcode1().kind == VifCode::Kind::MSCAL);
|
||||
|
||||
if (m_enabled) {
|
||||
if (run.vifcode1().immediate != last_prog) {
|
||||
// one-time setups and flushing
|
||||
flush_sprites(render_state, prof, false);
|
||||
}
|
||||
|
||||
if (run.vifcode1().immediate == SpriteProgMem::Sprites2dGrp0) {
|
||||
if (m_2d_enable) {
|
||||
do_block_common(SpriteMode::Mode2D, sprite_count, render_state, prof);
|
||||
}
|
||||
} else {
|
||||
if (m_3d_enable) {
|
||||
do_block_common(SpriteMode::Mode3D, sprite_count, render_state, prof);
|
||||
}
|
||||
}
|
||||
last_prog = run.vifcode1().immediate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite3::render_fake_shadow(DmaFollower& dma) {
|
||||
// TODO
|
||||
// nop + flushe
|
||||
auto nop_flushe = dma.read_and_advance();
|
||||
assert(nop_flushe.vifcode0().kind == VifCode::Kind::NOP);
|
||||
assert(nop_flushe.vifcode1().kind == VifCode::Kind::FLUSHE);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Handle DMA data for group1 2d's (HUD)
|
||||
*/
|
||||
void Sprite3::render_2d_group1(DmaFollower& dma,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof) {
|
||||
// one time matrix data upload
|
||||
auto mat_upload = dma.read_and_advance();
|
||||
bool mat_ok = verify_unpack_with_stcycl(mat_upload, VifCode::Kind::UNPACK_V4_32, 4, 4, 80,
|
||||
SpriteDataMem::Matrix, false, false);
|
||||
assert(mat_ok);
|
||||
assert(mat_upload.size_bytes == sizeof(m_hud_matrix_data));
|
||||
memcpy(&m_hud_matrix_data, mat_upload.data, sizeof(m_hud_matrix_data));
|
||||
|
||||
// opengl sprite frame setup
|
||||
glUniform4fv(
|
||||
glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "hud_hvdf_offset"), 1,
|
||||
m_hud_matrix_data.hvdf_offset.data());
|
||||
glUniform4fv(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "hud_hvdf_user"),
|
||||
75, m_hud_matrix_data.user_hvdf[0].data());
|
||||
glUniformMatrix4fv(
|
||||
glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "hud_matrix"), 1,
|
||||
GL_FALSE, m_hud_matrix_data.matrix.data());
|
||||
|
||||
// loop through chunks.
|
||||
while (looks_like_2d_chunk_start(dma)) {
|
||||
m_debug_stats.blocks_2d_grp1++;
|
||||
// 4 packets per chunk
|
||||
|
||||
// first is the header
|
||||
u32 sprite_count = process_sprite_chunk_header(dma);
|
||||
m_debug_stats.count_2d_grp1 += sprite_count;
|
||||
|
||||
// second is the vector data
|
||||
u32 expected_vec_size = sizeof(SpriteVecData2d) * sprite_count;
|
||||
auto vec_data = dma.read_and_advance();
|
||||
assert(expected_vec_size <= sizeof(m_vec_data_2d));
|
||||
unpack_to_no_stcycl(&m_vec_data_2d, vec_data, VifCode::Kind::UNPACK_V4_32, expected_vec_size,
|
||||
SpriteDataMem::Vector, false, true);
|
||||
|
||||
// third is the adgif data
|
||||
u32 expected_adgif_size = sizeof(AdGifData) * sprite_count;
|
||||
auto adgif_data = dma.read_and_advance();
|
||||
assert(expected_adgif_size <= sizeof(m_adgif));
|
||||
unpack_to_no_stcycl(&m_adgif, adgif_data, VifCode::Kind::UNPACK_V4_32, expected_adgif_size,
|
||||
SpriteDataMem::Adgif, false, true);
|
||||
|
||||
// fourth is the actual run!!!!!
|
||||
auto run = dma.read_and_advance();
|
||||
assert(run.vifcode0().kind == VifCode::Kind::NOP);
|
||||
assert(run.vifcode1().kind == VifCode::Kind::MSCAL);
|
||||
assert(run.vifcode1().immediate == SpriteProgMem::Sprites2dHud);
|
||||
if (m_enabled && m_2d_enable) {
|
||||
do_block_common(SpriteMode::ModeHUD, sprite_count, render_state, prof);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) {
|
||||
m_debug_stats = {};
|
||||
// First thing should be a NEXT with two nops. this is a jump from buckets to sprite data
|
||||
auto data0 = dma.read_and_advance();
|
||||
assert(data0.vif1() == 0);
|
||||
assert(data0.vif0() == 0);
|
||||
assert(data0.size_bytes == 0);
|
||||
|
||||
if (dma.current_tag().kind == DmaTag::Kind::CALL) {
|
||||
// sprite renderer didn't run, let's just get out of here.
|
||||
for (int i = 0; i < 4; i++) {
|
||||
dma.read_and_advance();
|
||||
}
|
||||
assert(dma.current_tag_offset() == render_state->next_bucket);
|
||||
return;
|
||||
}
|
||||
|
||||
render_state->shaders[ShaderId::SPRITE3].activate();
|
||||
|
||||
// First is the distorter
|
||||
{
|
||||
auto child = prof.make_scoped_child("distorter");
|
||||
render_distorter(dma, render_state, child);
|
||||
}
|
||||
|
||||
// next, sprite frame setup.
|
||||
handle_sprite_frame_setup(dma);
|
||||
|
||||
// 3d sprites
|
||||
render_3d(dma);
|
||||
|
||||
// 2d draw
|
||||
// m_sprite_renderer.reset_state();
|
||||
{
|
||||
auto child = prof.make_scoped_child("2d-group0");
|
||||
render_2d_group0(dma, render_state, child);
|
||||
flush_sprites(render_state, prof, false);
|
||||
}
|
||||
|
||||
// shadow draw
|
||||
render_fake_shadow(dma);
|
||||
|
||||
// 2d draw (HUD)
|
||||
{
|
||||
auto child = prof.make_scoped_child("2d-group1");
|
||||
render_2d_group1(dma, render_state, child);
|
||||
flush_sprites(render_state, prof, true);
|
||||
}
|
||||
|
||||
// TODO finish this up.
|
||||
// fmt::print("next bucket is 0x{}\n", render_state->next_bucket);
|
||||
while (dma.current_tag_offset() != render_state->next_bucket) {
|
||||
// auto tag = dma.current_tag();
|
||||
// fmt::print("@ 0x{:x} tag: {}", dma.current_tag_offset(), tag.print());
|
||||
auto data = dma.read_and_advance();
|
||||
VifCode code(data.vif0());
|
||||
// fmt::print(" vif: {}\n", code.print());
|
||||
if (code.kind == VifCode::Kind::NOP) {
|
||||
// fmt::print(" vif: {}\n", VifCode(data.vif1()).print());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite3::draw_debug_window() {
|
||||
ImGui::Separator();
|
||||
ImGui::Text("2D Group 0 (World) blocks: %d sprites: %d", m_debug_stats.blocks_2d_grp0,
|
||||
m_debug_stats.count_2d_grp0);
|
||||
ImGui::Text("2D Group 1 (HUD) blocks: %d sprites: %d", m_debug_stats.blocks_2d_grp1,
|
||||
m_debug_stats.count_2d_grp1);
|
||||
ImGui::Checkbox("Culling", &m_enable_culling);
|
||||
ImGui::Checkbox("2d", &m_2d_enable);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("3d", &m_3d_enable);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Render (for real)
|
||||
|
||||
void Sprite3::flush_sprites(SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof,
|
||||
bool double_draw) {
|
||||
glBindVertexArray(m_ogl.vao);
|
||||
|
||||
glEnable(GL_PRIMITIVE_RESTART);
|
||||
glPrimitiveRestartIndex(UINT32_MAX);
|
||||
|
||||
// upload vertex buffer
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, m_sprite_idx * sizeof(SpriteVertex3D) * 4, m_vertices_3d.data(),
|
||||
GL_STREAM_DRAW);
|
||||
|
||||
// two passes through the buckets. first to build the index buffer
|
||||
u32 idx_offset = 0;
|
||||
for (auto& kv : m_sprite_buckets) {
|
||||
memcpy(&m_index_buffer_data[idx_offset], kv.second.ids.data(),
|
||||
kv.second.ids.size() * sizeof(u32));
|
||||
kv.second.offset_in_idx_buffer = idx_offset;
|
||||
idx_offset += kv.second.ids.size();
|
||||
}
|
||||
|
||||
// now upload it
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ogl.index_buffer);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_offset * sizeof(u32), m_index_buffer_data.data(),
|
||||
GL_STREAM_DRAW);
|
||||
|
||||
// now do draws!
|
||||
for (auto& kv : m_sprite_buckets) {
|
||||
u32 tbp = kv.first >> 32;
|
||||
DrawMode mode;
|
||||
mode.as_int() = kv.first & 0xffffffff;
|
||||
|
||||
TextureRecord* tex = nullptr;
|
||||
tex = render_state->texture_pool->lookup(tbp);
|
||||
|
||||
if (!tex) {
|
||||
fmt::print("Failed to find texture at {}, using random\n", tbp);
|
||||
tex = render_state->texture_pool->get_random_texture();
|
||||
}
|
||||
assert(tex);
|
||||
|
||||
// first: do we need to load the texture?
|
||||
if (!tex->on_gpu) {
|
||||
render_state->texture_pool->upload_to_gpu(tex);
|
||||
}
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, tex->gpu_texture);
|
||||
|
||||
auto settings = setup_opengl_from_draw_mode(mode, GL_TEXTURE0, false);
|
||||
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "alpha_min"),
|
||||
double_draw ? settings.aref_first : 0.016);
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "alpha_max"),
|
||||
10.f);
|
||||
glUniform1i(glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "tex_T0"), 0);
|
||||
|
||||
prof.add_draw_call();
|
||||
prof.add_tri(2 * (kv.second.ids.size() / 5));
|
||||
|
||||
glDrawElements(GL_TRIANGLE_STRIP, kv.second.ids.size(), GL_UNSIGNED_INT,
|
||||
(void*)(kv.second.offset_in_idx_buffer * sizeof(u32)));
|
||||
|
||||
if (double_draw) {
|
||||
switch (settings.kind) {
|
||||
case DoubleDrawKind::NONE:
|
||||
break;
|
||||
case DoubleDrawKind::AFAIL_NO_DEPTH_WRITE:
|
||||
prof.add_draw_call();
|
||||
prof.add_tri(2 * (kv.second.ids.size() / 5));
|
||||
glUniform1f(
|
||||
glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "alpha_min"),
|
||||
-10.f);
|
||||
glUniform1f(
|
||||
glGetUniformLocation(render_state->shaders[ShaderId::SPRITE3].id(), "alpha_max"),
|
||||
settings.aref_second);
|
||||
glDepthMask(GL_FALSE);
|
||||
glDrawElements(GL_TRIANGLE_STRIP, kv.second.ids.size(), GL_UNSIGNED_INT,
|
||||
(void*)(kv.second.offset_in_idx_buffer * sizeof(u32)));
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_sprite_buckets.clear();
|
||||
m_last_bucket_key = UINT64_MAX;
|
||||
m_last_bucket = nullptr;
|
||||
m_sprite_idx = 0;
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void Sprite3::handle_tex0(u64 val,
|
||||
SharedRenderState* /*render_state*/,
|
||||
ScopedProfilerNode& /*prof*/) {
|
||||
GsTex0 reg(val);
|
||||
|
||||
// update tbp
|
||||
m_current_tbp = reg.tbp0();
|
||||
m_current_mode.set_tcc(reg.tcc());
|
||||
|
||||
// tbw: assume they got it right
|
||||
// psm: assume they got it right
|
||||
// tw: assume they got it right
|
||||
// th: assume they got it right
|
||||
|
||||
assert(reg.tfx() == GsTex0::TextureFunction::MODULATE);
|
||||
assert(reg.psm() != GsTex0::PSM::PSMT4HH);
|
||||
|
||||
// cbp: assume they got it right
|
||||
// cpsm: assume they got it right
|
||||
// csm: assume they got it right
|
||||
}
|
||||
|
||||
void Sprite3::handle_tex1(u64 val,
|
||||
SharedRenderState* /*render_state*/,
|
||||
ScopedProfilerNode& /*prof*/) {
|
||||
GsTex1 reg(val);
|
||||
m_current_mode.set_filt_enable(reg.mmag());
|
||||
}
|
||||
|
||||
void Sprite3::handle_zbuf(u64 val,
|
||||
SharedRenderState* /*render_state*/,
|
||||
ScopedProfilerNode& /*prof*/) {
|
||||
// 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.psm() == TextureFormat::PSMZ24);
|
||||
assert(x.zbp() == 448);
|
||||
|
||||
m_current_mode.set_depth_write_enable(!x.zmsk());
|
||||
}
|
||||
|
||||
void Sprite3::handle_clamp(u64 val,
|
||||
SharedRenderState* /*render_state*/,
|
||||
ScopedProfilerNode& /*prof*/) {
|
||||
if (!(val == 0b101 || val == 0 || val == 1 || val == 0b100)) {
|
||||
fmt::print("clamp: 0x{:x}\n", val);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
m_current_mode.set_clamp_s_enable(val & 0b001);
|
||||
m_current_mode.set_clamp_t_enable(val & 0b100);
|
||||
}
|
||||
|
||||
void update_mode_from_alpha1(u64 val, DrawMode& mode) {
|
||||
GsAlpha reg(val);
|
||||
if (reg.a_mode() == GsAlpha::BlendMode::SOURCE && reg.b_mode() == GsAlpha::BlendMode::DEST &&
|
||||
reg.c_mode() == GsAlpha::BlendMode::SOURCE && reg.d_mode() == GsAlpha::BlendMode::DEST) {
|
||||
// (Cs - Cd) * As + Cd
|
||||
// Cs * As + (1 - As) * Cd
|
||||
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST);
|
||||
|
||||
} else if (reg.a_mode() == GsAlpha::BlendMode::SOURCE &&
|
||||
reg.b_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
||||
reg.c_mode() == GsAlpha::BlendMode::SOURCE &&
|
||||
reg.d_mode() == GsAlpha::BlendMode::DEST) {
|
||||
// (Cs - 0) * As + Cd
|
||||
// Cs * As + (1) * CD
|
||||
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_SRC_DST);
|
||||
} else if (reg.a_mode() == GsAlpha::BlendMode::SOURCE &&
|
||||
reg.b_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
||||
reg.c_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
||||
reg.d_mode() == GsAlpha::BlendMode::DEST) {
|
||||
assert(reg.fix() == 128);
|
||||
// Cv = (Cs - 0) * FIX + Cd
|
||||
// if fix = 128, it works out to 1.0
|
||||
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_FIX_DST);
|
||||
// src plus dest
|
||||
} else if (reg.a_mode() == GsAlpha::BlendMode::SOURCE &&
|
||||
reg.b_mode() == GsAlpha::BlendMode::DEST &&
|
||||
reg.c_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
||||
reg.d_mode() == GsAlpha::BlendMode::DEST) {
|
||||
// Cv = (Cs - Cd) * FIX + Cd
|
||||
assert(reg.fix() == 64);
|
||||
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_FIX_DST);
|
||||
}
|
||||
|
||||
else {
|
||||
fmt::print("unsupported blend: a {} b {} c {} d {}\n", (int)reg.a_mode(), (int)reg.b_mode(),
|
||||
(int)reg.c_mode(), (int)reg.d_mode());
|
||||
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite3::handle_alpha(u64 val,
|
||||
SharedRenderState* /*render_state*/,
|
||||
ScopedProfilerNode& /*prof*/) {
|
||||
update_mode_from_alpha1(val, m_current_mode);
|
||||
}
|
||||
|
||||
void Sprite3::do_block_common(SpriteMode mode,
|
||||
u32 count,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof) {
|
||||
m_current_mode = m_default_mode;
|
||||
for (u32 sprite_idx = 0; sprite_idx < count; sprite_idx++) {
|
||||
if (m_sprite_idx == SPRITE_RENDERER_MAX_SPRITES) {
|
||||
flush_sprites(render_state, prof, mode == ModeHUD);
|
||||
}
|
||||
|
||||
if (mode == Mode2D && render_state->has_camera_planes && m_enable_culling) {
|
||||
// we can skip sprites that are out of view
|
||||
// it's probably possible to do this for 3D as well.
|
||||
auto bsphere = m_vec_data_2d[sprite_idx].xyz_sx;
|
||||
bsphere.w() = std::max(bsphere.w(), m_vec_data_2d[sprite_idx].sy());
|
||||
if (bsphere.w() == 0 || !sphere_in_view_ref(bsphere, render_state->camera_planes)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto& adgif = m_adgif[sprite_idx];
|
||||
handle_tex0(adgif.tex0_data, render_state, prof);
|
||||
handle_tex1(adgif.tex1_data, render_state, prof);
|
||||
if (GsRegisterAddress(adgif.clamp_addr) == GsRegisterAddress::ZBUF_1) {
|
||||
handle_zbuf(adgif.clamp_data, render_state, prof);
|
||||
} else {
|
||||
handle_clamp(adgif.clamp_data, render_state, prof);
|
||||
}
|
||||
handle_alpha(adgif.alpha_data, render_state, prof);
|
||||
|
||||
u64 key = (((u64)m_current_tbp) << 32) | m_current_mode.as_int();
|
||||
Bucket* bucket;
|
||||
if (key == m_last_bucket_key) {
|
||||
bucket = m_last_bucket;
|
||||
} else {
|
||||
bucket = &m_sprite_buckets[key];
|
||||
}
|
||||
u32 start_vtx_id = m_sprite_idx * 4;
|
||||
bucket->ids.push_back(start_vtx_id);
|
||||
bucket->ids.push_back(start_vtx_id + 1);
|
||||
bucket->ids.push_back(start_vtx_id + 2);
|
||||
bucket->ids.push_back(start_vtx_id + 3);
|
||||
bucket->ids.push_back(UINT32_MAX);
|
||||
|
||||
auto& vert1 = m_vertices_3d.at(start_vtx_id + 0);
|
||||
|
||||
vert1.xyz_sx = m_vec_data_2d[sprite_idx].xyz_sx;
|
||||
vert1.quat_sy = m_vec_data_2d[sprite_idx].flag_rot_sy;
|
||||
vert1.rgba = m_vec_data_2d[sprite_idx].rgba / 255;
|
||||
vert1.flags_matrix[0] = m_vec_data_2d[sprite_idx].flag();
|
||||
vert1.flags_matrix[1] = m_vec_data_2d[sprite_idx].matrix();
|
||||
vert1.info[0] = 0; // hack
|
||||
vert1.info[1] = m_current_mode.get_tcc_enable();
|
||||
vert1.info[2] = 0;
|
||||
vert1.info[3] = mode;
|
||||
|
||||
m_vertices_3d.at(start_vtx_id + 1) = vert1;
|
||||
m_vertices_3d.at(start_vtx_id + 2) = vert1;
|
||||
m_vertices_3d.at(start_vtx_id + 3) = vert1;
|
||||
|
||||
m_vertices_3d.at(start_vtx_id + 1).info[2] = 1;
|
||||
m_vertices_3d.at(start_vtx_id + 2).info[2] = 3;
|
||||
m_vertices_3d.at(start_vtx_id + 3).info[2] = 2;
|
||||
|
||||
++m_sprite_idx;
|
||||
}
|
||||
}
|
103
game/graphics/opengl_renderer/Sprite3.h
Normal file
103
game/graphics/opengl_renderer/Sprite3.h
Normal file
|
@ -0,0 +1,103 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "game/graphics/opengl_renderer/BucketRenderer.h"
|
||||
#include "game/graphics/opengl_renderer/DirectRenderer.h"
|
||||
#include "common/dma/gs.h"
|
||||
#include "common/math/Vector.h"
|
||||
#include "game/graphics/opengl_renderer/sprite_common.h"
|
||||
#include "game/graphics/opengl_renderer/tfrag/tfrag_common.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
class Sprite3 : public BucketRenderer {
|
||||
public:
|
||||
Sprite3(const std::string& name, BucketId my_id);
|
||||
void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override;
|
||||
void draw_debug_window() override;
|
||||
static constexpr int SPRITES_PER_CHUNK = 48;
|
||||
|
||||
private:
|
||||
void render_distorter(DmaFollower& dma,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof);
|
||||
void handle_sprite_frame_setup(DmaFollower& dma);
|
||||
void render_3d(DmaFollower& dma);
|
||||
void render_2d_group0(DmaFollower& dma,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof);
|
||||
void render_fake_shadow(DmaFollower& dma);
|
||||
void render_2d_group1(DmaFollower& dma,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof);
|
||||
enum SpriteMode { Mode2D = 1, ModeHUD = 2, Mode3D = 3 };
|
||||
void do_block_common(SpriteMode mode,
|
||||
u32 count,
|
||||
SharedRenderState* render_state,
|
||||
ScopedProfilerNode& prof);
|
||||
|
||||
void handle_tex0(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof);
|
||||
void handle_tex1(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof);
|
||||
// void handle_mip(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof);
|
||||
void handle_zbuf(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof);
|
||||
void handle_clamp(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof);
|
||||
void handle_alpha(u64 val, SharedRenderState* render_state, ScopedProfilerNode& prof);
|
||||
|
||||
void flush_sprites(SharedRenderState* render_state, ScopedProfilerNode& prof, bool double_draw);
|
||||
|
||||
u8 m_sprite_distorter_setup[7 * 16]; // direct data
|
||||
u8 m_sprite_direct_setup[3 * 16];
|
||||
SpriteFrameData m_frame_data; // qwa: 980
|
||||
Sprite3DMatrixData m_3d_matrix_data;
|
||||
SpriteHudMatrixData m_hud_matrix_data;
|
||||
|
||||
SpriteVecData2d m_vec_data_2d[SPRITES_PER_CHUNK];
|
||||
AdGifData m_adgif[SPRITES_PER_CHUNK];
|
||||
|
||||
struct DebugStats {
|
||||
int blocks_2d_grp0 = 0;
|
||||
int count_2d_grp0 = 0;
|
||||
int blocks_2d_grp1 = 0;
|
||||
int count_2d_grp1 = 0;
|
||||
} m_debug_stats;
|
||||
|
||||
bool m_enable_culling = true;
|
||||
|
||||
bool m_2d_enable = true;
|
||||
bool m_3d_enable = true;
|
||||
|
||||
struct SpriteVertex3D {
|
||||
math::Vector4f xyz_sx; // position + x scale
|
||||
math::Vector4f quat_sy; // quaternion + y scale
|
||||
math::Vector4f rgba; // color
|
||||
math::Vector<u16, 2> flags_matrix; // flags + matrix... split
|
||||
math::Vector<u16, 4> info;
|
||||
math::Vector<u8, 4> pad;
|
||||
};
|
||||
static_assert(sizeof(SpriteVertex3D) == 64);
|
||||
|
||||
std::vector<SpriteVertex3D> m_vertices_3d;
|
||||
|
||||
struct {
|
||||
GLuint vertex_buffer;
|
||||
GLuint vao;
|
||||
GLuint index_buffer;
|
||||
} m_ogl;
|
||||
|
||||
DrawMode m_current_mode, m_default_mode;
|
||||
u32 m_current_tbp = 0;
|
||||
|
||||
struct Bucket {
|
||||
std::vector<u32> ids;
|
||||
u32 offset_in_idx_buffer = 0;
|
||||
};
|
||||
|
||||
std::map<u64, Bucket> m_sprite_buckets;
|
||||
|
||||
u64 m_last_bucket_key = UINT64_MAX;
|
||||
Bucket* m_last_bucket = nullptr;
|
||||
|
||||
u64 m_sprite_idx = 0;
|
||||
|
||||
std::vector<u32> m_index_buffer_data;
|
||||
};
|
|
@ -4,145 +4,7 @@
|
|||
#include "game/graphics/opengl_renderer/DirectRenderer.h"
|
||||
#include "common/dma/gs.h"
|
||||
#include "common/math/Vector.h"
|
||||
|
||||
using math::Matrix4f;
|
||||
using math::Vector4f;
|
||||
|
||||
/*!
|
||||
* GOAL sprite-frame-data, all the data that's uploaded once per frame for the sprite system.
|
||||
*/
|
||||
struct SpriteFrameData {
|
||||
Vector4f xy_array[8];
|
||||
Vector4f st_array[4];
|
||||
Vector4f xyz_array[4];
|
||||
Vector4f hmge_scale;
|
||||
float pfog0;
|
||||
float deg_to_rad;
|
||||
float min_scale;
|
||||
float inv_area;
|
||||
GifTag adgif_giftag;
|
||||
GifTag sprite_2d_giftag;
|
||||
GifTag sprite_2d_giftag2;
|
||||
Vector4f sincos[5];
|
||||
Vector4f basis_x;
|
||||
Vector4f basis_y;
|
||||
GifTag sprite_3d_giftag;
|
||||
AdGifData screen_shader;
|
||||
GifTag clipped_giftag;
|
||||
Vector4f inv_hmge_scale;
|
||||
Vector4f stq_offset;
|
||||
Vector4f stq_scale;
|
||||
Vector4f rgba_plain;
|
||||
GifTag warp_giftag;
|
||||
float fog_min;
|
||||
float fog_max;
|
||||
float max_scale;
|
||||
float bonus;
|
||||
};
|
||||
|
||||
/*!
|
||||
* "Matrix Data" for 3D sprites. This is shared for all 3D sprites
|
||||
*/
|
||||
struct Sprite3DMatrixData {
|
||||
Matrix4f camera;
|
||||
Vector4f hvdf_offset;
|
||||
};
|
||||
|
||||
/*!
|
||||
* "Matrix Data" for 2D screen space sprites. These are shared for all 2D HUD sprites
|
||||
*/
|
||||
struct SpriteHudMatrixData {
|
||||
Matrix4f matrix;
|
||||
|
||||
// the "matrix" field is an index into these 76 quadwords
|
||||
Vector4f hvdf_offset;
|
||||
Vector4f user_hvdf[75];
|
||||
};
|
||||
|
||||
/*!
|
||||
* The "vector data" (sprite-vec-data-2d). Each sprite has its own vector data.
|
||||
*/
|
||||
struct SpriteVecData2d {
|
||||
Vector4f xyz_sx; // position + x scale
|
||||
Vector4f flag_rot_sy; // flags, rotation, and scale y
|
||||
Vector4f rgba; // color
|
||||
|
||||
float sx() const { return xyz_sx.w(); }
|
||||
|
||||
// for HUD, this is the hvdf offset index
|
||||
s32 flag() {
|
||||
s32 result;
|
||||
memcpy(&result, &flag_rot_sy.x(), sizeof(s32));
|
||||
return result;
|
||||
}
|
||||
|
||||
// unused for HUD
|
||||
s32 matrix() {
|
||||
s32 result;
|
||||
memcpy(&result, &flag_rot_sy.y(), sizeof(s32));
|
||||
return result;
|
||||
}
|
||||
|
||||
// rotation in degrees
|
||||
float rot() const { return flag_rot_sy.z(); }
|
||||
|
||||
// scale y.
|
||||
float sy() const { return flag_rot_sy.w(); }
|
||||
};
|
||||
static_assert(sizeof(SpriteVecData2d) == 48);
|
||||
|
||||
/*!
|
||||
* The layout of VU1 data memory, in quadword addresses
|
||||
* The lower 800 qw's hold two buffers for double buffering drawing/loading.
|
||||
*/
|
||||
enum SpriteDataMem {
|
||||
// these three can have an offset of 0 or 400 depending on which buffer
|
||||
Header = 0, // number of sprites (updated per chunk)
|
||||
Vector = 1, // vector data (updated per chunk)
|
||||
Adgif = 145, // adgifs (updated per chunk)
|
||||
|
||||
// offset of first buffer
|
||||
Buffer0 = 0,
|
||||
// offset of second buffer
|
||||
Buffer1 = 400,
|
||||
|
||||
GiftagBuilding = 800, // used to store gs packets for xgkicking
|
||||
// matrix data (different depending on group)
|
||||
Matrix = 900,
|
||||
// frame data (same for the whole frame)
|
||||
FrameData = 980
|
||||
};
|
||||
|
||||
/*!
|
||||
* The GS packet built by the sprite renderer.
|
||||
*/
|
||||
struct SpriteHud2DPacket {
|
||||
GifTag adgif_giftag; // starts the adgif shader. 0
|
||||
AdGifData user_adgif; // the adgif shader 16
|
||||
GifTag sprite_giftag; // 96
|
||||
math::Vector<s32, 4> color;
|
||||
Vector4f st0;
|
||||
math::Vector<s32, 4> xy0;
|
||||
Vector4f st1;
|
||||
math::Vector<s32, 4> xy1;
|
||||
Vector4f st2;
|
||||
math::Vector<s32, 4> xy2;
|
||||
Vector4f st3;
|
||||
math::Vector<s32, 4> xy3;
|
||||
};
|
||||
|
||||
/*!
|
||||
* The layout of VU1 code memory
|
||||
*/
|
||||
enum SpriteProgMem {
|
||||
Init = 0, // the sprite initialization program. runs once per frame.
|
||||
Sprites2dGrp0 = 3, // world space 2d sprites
|
||||
Sprites2dHud = 109, // hud sprites
|
||||
Sprites3d = 211 // 3d sprites
|
||||
};
|
||||
|
||||
static_assert(offsetof(SpriteFrameData, hmge_scale) == 256);
|
||||
static_assert(sizeof(SpriteFrameData) == 0x290, "SpriteFrameData size");
|
||||
#include "game/graphics/opengl_renderer/sprite_common.h"
|
||||
|
||||
class SpriteRenderer : public BucketRenderer {
|
||||
public:
|
||||
|
|
50
game/graphics/opengl_renderer/shaders/sprite3_3d.frag
Normal file
50
game/graphics/opengl_renderer/shaders/sprite3_3d.frag
Normal file
|
@ -0,0 +1,50 @@
|
|||
#version 430 core
|
||||
|
||||
out vec4 color;
|
||||
|
||||
in flat vec4 fragment_color;
|
||||
in vec3 tex_coord;
|
||||
in flat uvec2 tex_info;
|
||||
|
||||
uniform sampler2D tex_T0;
|
||||
uniform float alpha_min;
|
||||
uniform float alpha_max;
|
||||
|
||||
void main() {
|
||||
vec4 T0 = texture(tex_T0, tex_coord.xy);
|
||||
if (tex_info.y == 0) {
|
||||
T0.w = 1.0;
|
||||
}
|
||||
color = fragment_color * T0 * 2.0;
|
||||
|
||||
|
||||
|
||||
if (color.a < alpha_min) {
|
||||
discard;
|
||||
}
|
||||
|
||||
if (color.a > alpha_max) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
|
||||
//out vec4 color;
|
||||
//
|
||||
//in flat vec4 fragment_color;
|
||||
//in vec3 tex_coord;
|
||||
//in flat uvec2 tex_info;
|
||||
//
|
||||
//uniform sampler2D tex_T0;
|
||||
//
|
||||
//
|
||||
//void main() {
|
||||
// vec4 T0 = texture(tex_T0, tex_coord.xy);
|
||||
// if (tex_info.y == 0) {
|
||||
// T0.w = 1.0;
|
||||
// }
|
||||
// vec4 tex_color = fragment_color * T0 * 2.0;
|
||||
// if (tex_color.a < 0.016) {
|
||||
// discard;
|
||||
// }
|
||||
// color = tex_color;
|
||||
//}
|
178
game/graphics/opengl_renderer/shaders/sprite3_3d.vert
Normal file
178
game/graphics/opengl_renderer/shaders/sprite3_3d.vert
Normal file
|
@ -0,0 +1,178 @@
|
|||
#version 430 core
|
||||
|
||||
layout (location = 0) in vec4 xyz_sx;
|
||||
layout (location = 1) in vec4 quat_sy;
|
||||
layout (location = 2) in vec4 rgba;
|
||||
layout (location = 3) in uvec2 flags_matrix;
|
||||
layout (location = 4) in uvec4 tex_info_in;
|
||||
|
||||
uniform vec4 hvdf_offset;
|
||||
uniform mat4 camera;
|
||||
uniform mat4 hud_matrix;
|
||||
uniform vec4 hud_hvdf_offset;
|
||||
uniform vec4 hud_hvdf_user[75];
|
||||
uniform float pfog0;
|
||||
uniform float min_scale;
|
||||
uniform float max_scale;
|
||||
uniform float bonus;
|
||||
uniform float deg_to_rad;
|
||||
uniform float inv_area;
|
||||
uniform vec4 basis_x;
|
||||
uniform vec4 basis_y;
|
||||
uniform vec4 hmge_scale;
|
||||
uniform vec4 xy_array[8];
|
||||
uniform vec4 xyz_array[4];
|
||||
uniform vec4 st_array[4];
|
||||
|
||||
out flat vec4 fragment_color;
|
||||
out vec3 tex_coord;
|
||||
out flat uvec2 tex_info;
|
||||
|
||||
vec4 matrix_transform(mat4 mtx, vec3 pt) {
|
||||
return mtx[3]
|
||||
+ mtx[0] * pt.x
|
||||
+ mtx[1] * pt.y
|
||||
+ mtx[2] * pt.z;
|
||||
}
|
||||
|
||||
mat3 sprite_quat_to_rot(vec3 quat) {
|
||||
mat3 result;
|
||||
float qr = sqrt(abs(1.0 - (quat.x * quat.x + quat.y * quat.y + quat.z * quat.z)));
|
||||
result[0][0] = 1.0 - 2.0 * (quat.y * quat.y + quat.z * quat.z);
|
||||
result[1][0] = 2.0 * (quat.x * quat.y - quat.z * qr);
|
||||
result[2][0] = 2.0 * (quat.x * quat.z + quat.y * qr);
|
||||
result[0][1] = 2.0 * (quat.x * quat.y + quat.z * qr);
|
||||
result[1][1] = 1.0 - 2.0 * (quat.x * quat.x + quat.z * quat.z);
|
||||
result[2][1] = 2.0 * (quat.y * quat.z - quat.x * qr);
|
||||
result[0][2] = 2.0 * (quat.x * quat.z - quat.y * qr);
|
||||
result[1][2] = 2.0 * (quat.y * quat.z + quat.x * qr);
|
||||
result[2][2] = 1.0 - 2.0 * (quat.x * quat.x + quat.y * quat.y);
|
||||
return result;
|
||||
}
|
||||
|
||||
vec4 sprite_transform2(vec3 root, vec4 off, mat3 sprite_rot, float sx, float sy) {
|
||||
vec3 pos = root;
|
||||
|
||||
vec3 offset = sprite_rot[0] * off.x * sx + sprite_rot[1] * off.y + sprite_rot[2] * off.z * sy;
|
||||
|
||||
pos += offset;
|
||||
vec4 transformed_pos = -matrix_transform(camera, pos);
|
||||
float Q = pfog0 / transformed_pos.w;
|
||||
transformed_pos.xyz *= Q;
|
||||
transformed_pos.xyz += hvdf_offset.xyz;
|
||||
|
||||
return transformed_pos;
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
||||
// STEP 1: UNPACK DATA AND CREATE READABLE VARIABLES
|
||||
|
||||
vec3 position = xyz_sx.xyz;
|
||||
float sx = xyz_sx.w;
|
||||
float sy = quat_sy.w;
|
||||
fragment_color = rgba;
|
||||
uint vert_id = tex_info_in.z;
|
||||
uint rendermode = tex_info_in.w; // 2D, HUD, 3D
|
||||
vec3 quat = quat_sy.xyz;
|
||||
uint matrix = flags_matrix.y;
|
||||
|
||||
vec4 transformed;
|
||||
|
||||
// STEP 2: perspective transform for distance
|
||||
vec4 transformed_pos_vf02 = matrix_transform(rendermode == 2 ? hud_matrix : camera, position);
|
||||
float Q = pfog0 / transformed_pos_vf02.w;
|
||||
|
||||
|
||||
// STEP 3: fade out sprite!
|
||||
vec4 scales_vf01 = xyz_sx; // now used for something else.
|
||||
scales_vf01.z = sy; // start building the scale vector
|
||||
scales_vf01.zw *= Q; // sy sx
|
||||
scales_vf01.x = scales_vf01.z; // = sy
|
||||
scales_vf01.x *= scales_vf01.w; // x = sx * sy
|
||||
scales_vf01.x *= inv_area; // x = sx * sy * inv_area (area ratio)
|
||||
fragment_color.w *= min(scales_vf01.x, 1.0); // is this right? doesn't this stall??
|
||||
|
||||
|
||||
// STEP 4: actual vertex transformation
|
||||
if (rendermode == 3) { // 3D sprites
|
||||
|
||||
mat3 rot = sprite_quat_to_rot(quat);
|
||||
transformed = sprite_transform2(position, xyz_array[vert_id], rot, sx, sy);
|
||||
|
||||
} else if (rendermode == 1) { // 2D sprites
|
||||
|
||||
transformed_pos_vf02.xyz *= Q;
|
||||
vec4 offset_pos_vf10 = transformed_pos_vf02 + hvdf_offset;
|
||||
|
||||
/* transformed_pos_vf02.w = offset_pos_vf10.w - fog_max;
|
||||
int fge = matrix == 0;
|
||||
if (transformed_pos_vf02.w != 0) {
|
||||
fge = false;
|
||||
} */
|
||||
|
||||
scales_vf01.z = min(max(scales_vf01.z, min_scale), max_scale);
|
||||
scales_vf01.w = min(max(scales_vf01.w, min_scale), max_scale);
|
||||
|
||||
quat.z *= deg_to_rad;
|
||||
float sp_sin = sin(quat.z);
|
||||
float sp_cos = cos(quat.z);
|
||||
|
||||
vec4 xy0_vf19 = xy_array[vert_id + flags_matrix.x];
|
||||
vec4 vf12_rotated = (basis_x * sp_cos) - (basis_y * sp_sin);
|
||||
vec4 vf13_rotated_trans = (basis_x * sp_sin) + (basis_y * sp_cos);
|
||||
|
||||
vf12_rotated *= scales_vf01.w;
|
||||
vf13_rotated_trans *= scales_vf01.z;
|
||||
|
||||
transformed = offset_pos_vf10 + vf12_rotated * xy0_vf19.x + vf13_rotated_trans * xy0_vf19.y;
|
||||
|
||||
} else if (rendermode == 2) { // hud sprites
|
||||
|
||||
transformed_pos_vf02.xyz *= Q;
|
||||
vec4 offset_pos_vf10 = transformed_pos_vf02 + (matrix == 0 ? hud_hvdf_offset : hud_hvdf_user[matrix - 1]);
|
||||
|
||||
scales_vf01.z = min(max(scales_vf01.z, min_scale), max_scale);
|
||||
scales_vf01.w = min(max(scales_vf01.w, min_scale), max_scale);
|
||||
|
||||
quat.z *= deg_to_rad;
|
||||
float sp_sin = sin(quat.z);
|
||||
float sp_cos = cos(quat.z);
|
||||
|
||||
vec4 xy0_vf19 = xy_array[vert_id + flags_matrix.x];
|
||||
vec4 vf12_rotated = (basis_x * sp_cos) - (basis_y * sp_sin);
|
||||
vec4 vf13_rotated_trans = (basis_x * sp_sin) + (basis_y * sp_cos);
|
||||
|
||||
vf12_rotated *= scales_vf01.w;
|
||||
vf13_rotated_trans *= scales_vf01.z;
|
||||
|
||||
transformed = offset_pos_vf10 + vf12_rotated * xy0_vf19.x + vf13_rotated_trans * xy0_vf19.y;
|
||||
|
||||
}
|
||||
|
||||
tex_coord = st_array[vert_id].xyz;
|
||||
|
||||
|
||||
// STEP 5: final adjustments
|
||||
// correct xy offset
|
||||
transformed.xy -= (2048.);
|
||||
|
||||
// correct z scale
|
||||
transformed.z /= (8388608);
|
||||
transformed.z -= 1;
|
||||
|
||||
// correct xy scale
|
||||
transformed.x /= (256);
|
||||
transformed.y /= -(128);
|
||||
|
||||
// hack
|
||||
transformed.xyz *= transformed.w;
|
||||
|
||||
gl_Position = transformed;
|
||||
// scissoring area adjust
|
||||
gl_Position.y *= 512.0/448.0;
|
||||
|
||||
fragment_color.w *= 2;
|
||||
|
||||
tex_info = tex_info_in.xy;
|
||||
}
|
145
game/graphics/opengl_renderer/sprite_common.h
Normal file
145
game/graphics/opengl_renderer/sprite_common.h
Normal file
|
@ -0,0 +1,145 @@
|
|||
#pragma once
|
||||
|
||||
#include "game/graphics/opengl_renderer/BucketRenderer.h"
|
||||
#include "game/graphics/opengl_renderer/DirectRenderer.h"
|
||||
#include "common/dma/gs.h"
|
||||
#include "common/math/Vector.h"
|
||||
|
||||
using math::Matrix4f;
|
||||
using math::Vector4f;
|
||||
|
||||
/*!
|
||||
* GOAL sprite-frame-data, all the data that's uploaded once per frame for the sprite system.
|
||||
*/
|
||||
struct SpriteFrameData {
|
||||
Vector4f xy_array[8];
|
||||
Vector4f st_array[4];
|
||||
Vector4f xyz_array[4];
|
||||
Vector4f hmge_scale;
|
||||
float pfog0;
|
||||
float deg_to_rad;
|
||||
float min_scale;
|
||||
float inv_area;
|
||||
GifTag adgif_giftag;
|
||||
GifTag sprite_2d_giftag;
|
||||
GifTag sprite_2d_giftag2;
|
||||
Vector4f sincos[5];
|
||||
Vector4f basis_x;
|
||||
Vector4f basis_y;
|
||||
GifTag sprite_3d_giftag;
|
||||
AdGifData screen_shader;
|
||||
GifTag clipped_giftag;
|
||||
Vector4f inv_hmge_scale;
|
||||
Vector4f stq_offset;
|
||||
Vector4f stq_scale;
|
||||
Vector4f rgba_plain;
|
||||
GifTag warp_giftag;
|
||||
float fog_min;
|
||||
float fog_max;
|
||||
float max_scale;
|
||||
float bonus;
|
||||
};
|
||||
|
||||
/*!
|
||||
* "Matrix Data" for 3D sprites. This is shared for all 3D sprites
|
||||
*/
|
||||
struct Sprite3DMatrixData {
|
||||
Matrix4f camera;
|
||||
Vector4f hvdf_offset;
|
||||
};
|
||||
|
||||
/*!
|
||||
* "Matrix Data" for 2D screen space sprites. These are shared for all 2D HUD sprites
|
||||
*/
|
||||
struct SpriteHudMatrixData {
|
||||
Matrix4f matrix;
|
||||
|
||||
// the "matrix" field is an index into these 76 quadwords
|
||||
Vector4f hvdf_offset;
|
||||
Vector4f user_hvdf[75];
|
||||
};
|
||||
|
||||
/*!
|
||||
* The "vector data" (sprite-vec-data-2d). Each sprite has its own vector data.
|
||||
*/
|
||||
struct SpriteVecData2d {
|
||||
Vector4f xyz_sx; // position + x scale
|
||||
Vector4f flag_rot_sy; // flags, rotation, and scale y
|
||||
Vector4f rgba; // color
|
||||
|
||||
float sx() const { return xyz_sx.w(); }
|
||||
|
||||
// for HUD, this is the hvdf offset index
|
||||
s32 flag() {
|
||||
s32 result;
|
||||
memcpy(&result, &flag_rot_sy.x(), sizeof(s32));
|
||||
return result;
|
||||
}
|
||||
|
||||
// unused for HUD
|
||||
s32 matrix() {
|
||||
s32 result;
|
||||
memcpy(&result, &flag_rot_sy.y(), sizeof(s32));
|
||||
return result;
|
||||
}
|
||||
|
||||
// rotation in degrees
|
||||
float rot() const { return flag_rot_sy.z(); }
|
||||
|
||||
// scale y.
|
||||
float sy() const { return flag_rot_sy.w(); }
|
||||
};
|
||||
static_assert(sizeof(SpriteVecData2d) == 48);
|
||||
|
||||
/*!
|
||||
* The layout of VU1 data memory, in quadword addresses
|
||||
* The lower 800 qw's hold two buffers for double buffering drawing/loading.
|
||||
*/
|
||||
enum SpriteDataMem {
|
||||
// these three can have an offset of 0 or 400 depending on which buffer
|
||||
Header = 0, // number of sprites (updated per chunk)
|
||||
Vector = 1, // vector data (updated per chunk)
|
||||
Adgif = 145, // adgifs (updated per chunk)
|
||||
|
||||
// offset of first buffer
|
||||
Buffer0 = 0,
|
||||
// offset of second buffer
|
||||
Buffer1 = 400,
|
||||
|
||||
GiftagBuilding = 800, // used to store gs packets for xgkicking
|
||||
// matrix data (different depending on group)
|
||||
Matrix = 900,
|
||||
// frame data (same for the whole frame)
|
||||
FrameData = 980
|
||||
};
|
||||
|
||||
/*!
|
||||
* The GS packet built by the sprite renderer.
|
||||
*/
|
||||
struct SpriteHud2DPacket {
|
||||
GifTag adgif_giftag; // starts the adgif shader. 0
|
||||
AdGifData user_adgif; // the adgif shader 16
|
||||
GifTag sprite_giftag; // 96
|
||||
math::Vector<s32, 4> color;
|
||||
Vector4f st0;
|
||||
math::Vector<s32, 4> xy0;
|
||||
Vector4f st1;
|
||||
math::Vector<s32, 4> xy1;
|
||||
Vector4f st2;
|
||||
math::Vector<s32, 4> xy2;
|
||||
Vector4f st3;
|
||||
math::Vector<s32, 4> xy3;
|
||||
};
|
||||
|
||||
/*!
|
||||
* The layout of VU1 code memory
|
||||
*/
|
||||
enum SpriteProgMem {
|
||||
Init = 0, // the sprite initialization program. runs once per frame.
|
||||
Sprites2dGrp0 = 3, // world space 2d sprites
|
||||
Sprites2dHud = 109, // hud sprites
|
||||
Sprites3d = 211 // 3d sprites
|
||||
};
|
||||
|
||||
static_assert(offsetof(SpriteFrameData, hmge_scale) == 256);
|
||||
static_assert(sizeof(SpriteFrameData) == 0x290, "SpriteFrameData size");
|
|
@ -278,7 +278,7 @@ void Tfrag3::render_tree(const TfragRenderSettings& settings,
|
|||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, m_textures.at(draw.tree_tex_id));
|
||||
auto double_draw = setup_tfrag_shader(settings, render_state, draw.mode);
|
||||
auto double_draw = setup_tfrag_shader(render_state, draw.mode);
|
||||
tree.tris_this_frame += draw.num_triangles;
|
||||
tree.draws_this_frame++;
|
||||
int draw_size = indices.second - indices.first;
|
||||
|
@ -298,7 +298,7 @@ void Tfrag3::render_tree(const TfragRenderSettings& settings,
|
|||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_min"),
|
||||
-10.f);
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_max"),
|
||||
double_draw.aref);
|
||||
double_draw.aref_second);
|
||||
glDepthMask(GL_FALSE);
|
||||
glDrawElements(GL_TRIANGLE_STRIP, draw_size, GL_UNSIGNED_INT, (void*)offset);
|
||||
break;
|
||||
|
|
|
@ -531,7 +531,7 @@ void Tie3::render_tree_wind(int idx,
|
|||
glBindTexture(GL_TEXTURE_2D, m_textures.at(draw.tree_tex_id));
|
||||
last_texture = draw.tree_tex_id;
|
||||
}
|
||||
auto double_draw = setup_tfrag_shader(settings, render_state, draw.mode);
|
||||
auto double_draw = setup_tfrag_shader(render_state, draw.mode);
|
||||
|
||||
int off = 0;
|
||||
for (auto& grp : draw.instance_groups) {
|
||||
|
@ -569,7 +569,7 @@ void Tie3::render_tree_wind(int idx,
|
|||
-10.f);
|
||||
glUniform1f(
|
||||
glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_max"),
|
||||
double_draw.aref);
|
||||
double_draw.aref_second);
|
||||
glDepthMask(GL_FALSE);
|
||||
glDrawElements(GL_TRIANGLE_STRIP, draw.vertex_index_stream.size(), GL_UNSIGNED_INT,
|
||||
(void*)0);
|
||||
|
@ -663,7 +663,7 @@ void Tie3::render_tree(int idx,
|
|||
last_texture = draw.tree_tex_id;
|
||||
}
|
||||
|
||||
auto double_draw = setup_tfrag_shader(settings, render_state, draw.mode);
|
||||
auto double_draw = setup_tfrag_shader(render_state, draw.mode);
|
||||
int draw_size = indices.second - indices.first;
|
||||
void* offset = (void*)(indices.first * sizeof(u32));
|
||||
|
||||
|
@ -694,7 +694,7 @@ void Tie3::render_tree(int idx,
|
|||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_min"),
|
||||
-10.f);
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_max"),
|
||||
double_draw.aref);
|
||||
double_draw.aref_second);
|
||||
glDepthMask(GL_FALSE);
|
||||
glDrawElements(GL_TRIANGLE_STRIP, draw_size, GL_UNSIGNED_INT, (void*)offset);
|
||||
break;
|
||||
|
|
|
@ -6,10 +6,8 @@
|
|||
|
||||
#include <immintrin.h>
|
||||
|
||||
DoubleDraw setup_tfrag_shader(const TfragRenderSettings& /*settings*/,
|
||||
SharedRenderState* render_state,
|
||||
DrawMode mode) {
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
DoubleDraw setup_opengl_from_draw_mode(DrawMode mode, u32 tex_unit, bool mipmap) {
|
||||
glActiveTexture(tex_unit);
|
||||
|
||||
if (mode.get_zt_enable()) {
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
@ -72,7 +70,8 @@ DoubleDraw setup_tfrag_shader(const TfragRenderSettings& /*settings*/,
|
|||
}
|
||||
|
||||
if (mode.get_filt_enable()) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
||||
mipmap ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
} else {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
@ -97,7 +96,7 @@ DoubleDraw setup_tfrag_shader(const TfragRenderSettings& /*settings*/,
|
|||
case GsTest::AlphaFail::FB_ONLY:
|
||||
// darn, we need to draw twice
|
||||
double_draw.kind = DoubleDrawKind::AFAIL_NO_DEPTH_WRITE;
|
||||
double_draw.aref = alpha_min;
|
||||
double_draw.aref_second = alpha_min;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
|
@ -120,13 +119,17 @@ DoubleDraw setup_tfrag_shader(const TfragRenderSettings& /*settings*/,
|
|||
} else {
|
||||
glDepthMask(GL_FALSE);
|
||||
}
|
||||
double_draw.aref_first = alpha_min;
|
||||
return double_draw;
|
||||
}
|
||||
|
||||
DoubleDraw setup_tfrag_shader(SharedRenderState* render_state, DrawMode mode) {
|
||||
auto draw_settings = setup_opengl_from_draw_mode(mode, GL_TEXTURE0, true);
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_min"),
|
||||
alpha_min);
|
||||
draw_settings.aref_first);
|
||||
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_max"),
|
||||
10.f);
|
||||
|
||||
return double_draw;
|
||||
return draw_settings;
|
||||
}
|
||||
|
||||
void first_tfrag_draw_setup(const TfragRenderSettings& settings, SharedRenderState* render_state) {
|
||||
|
|
|
@ -19,12 +19,13 @@ enum class DoubleDrawKind { NONE, AFAIL_NO_DEPTH_WRITE };
|
|||
|
||||
struct DoubleDraw {
|
||||
DoubleDrawKind kind = DoubleDrawKind::NONE;
|
||||
float aref = 0.;
|
||||
float aref_first = 0.;
|
||||
float aref_second = 0.;
|
||||
};
|
||||
|
||||
DoubleDraw setup_tfrag_shader(const TfragRenderSettings& /*settings*/,
|
||||
SharedRenderState* render_state,
|
||||
DrawMode mode);
|
||||
DoubleDraw setup_tfrag_shader(SharedRenderState* render_state, DrawMode mode);
|
||||
DoubleDraw setup_opengl_from_draw_mode(DrawMode mode, u32 tex_unit, bool mipmap);
|
||||
|
||||
void first_tfrag_draw_setup(const TfragRenderSettings& settings, SharedRenderState* render_state);
|
||||
void interp_time_of_day_slow(const float weights[8],
|
||||
const std::vector<tfrag3::TimeOfDayColor>& in,
|
||||
|
|
|
@ -307,14 +307,18 @@ void LoadDGOTest() {
|
|||
void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size) {
|
||||
auto name = Ptr<char>(name_gstr + 4).c();
|
||||
auto heap = Ptr<kheapinfo>(heap_info);
|
||||
load_and_link_dgo_from_c(name, heap, flag, buffer_size);
|
||||
load_and_link_dgo_from_c(name, heap, flag, buffer_size, false);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Load and link a DGO file.
|
||||
* This does not use the mutli-threaded linker and will block until the entire file is done.e
|
||||
*/
|
||||
void load_and_link_dgo_from_c(const char* name, Ptr<kheapinfo> heap, u32 linkFlag, s32 bufferSize) {
|
||||
void load_and_link_dgo_from_c(const char* name,
|
||||
Ptr<kheapinfo> heap,
|
||||
u32 linkFlag,
|
||||
s32 bufferSize,
|
||||
bool jump_from_c_to_goal) {
|
||||
lg::debug("[Load and Link DGO From C] {}", name);
|
||||
u32 oldShowStall = sShowStallMsg;
|
||||
|
||||
|
@ -365,8 +369,8 @@ void load_and_link_dgo_from_c(const char* name, Ptr<kheapinfo> heap, u32 linkFla
|
|||
char objName[64];
|
||||
strcpy(objName, (dgoObj + 4).cast<char>().c()); // name from dgo object header
|
||||
lg::debug("[link and exec] {:18s} {} {:6d} heap-use {:8d} {:8d}", objName, lastObjectLoaded,
|
||||
objSize, kheapused(kglobalheap), kheapused(kdebugheap));
|
||||
link_and_exec(obj, objName, objSize, heap, linkFlag); // link now!
|
||||
objSize, kheapused(kglobalheap), kdebugheap.offset ? kheapused(kdebugheap) : 0);
|
||||
link_and_exec(obj, objName, objSize, heap, linkFlag, jump_from_c_to_goal); // link now!
|
||||
|
||||
// inform IOP we are done
|
||||
if (!lastObjectLoaded) {
|
||||
|
|
|
@ -12,7 +12,11 @@
|
|||
|
||||
void kdgo_init_globals();
|
||||
u32 InitRPC();
|
||||
void load_and_link_dgo_from_c(const char* name, Ptr<kheapinfo> heap, u32 linkFlag, s32 bufferSize);
|
||||
void load_and_link_dgo_from_c(const char* name,
|
||||
Ptr<kheapinfo> heap,
|
||||
u32 linkFlag,
|
||||
s32 bufferSize,
|
||||
bool jump_from_c_to_goal);
|
||||
void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size);
|
||||
void StopIOP();
|
||||
|
||||
|
|
|
@ -770,7 +770,7 @@ uint32_t link_control::work_v2() {
|
|||
/*!
|
||||
* Complete linking. This will execute the top-level code for v3 object files, if requested.
|
||||
*/
|
||||
void link_control::finish() {
|
||||
void link_control::finish(bool jump_from_c_to_goal) {
|
||||
CacheFlush(m_code_start.c(), m_code_size);
|
||||
auto old_debug_segment = DebugSegment;
|
||||
if (m_keep_debug) {
|
||||
|
@ -793,7 +793,12 @@ void link_control::finish() {
|
|||
|
||||
// execute top level!
|
||||
if (m_entry.offset && (m_flags & LINK_FLAG_EXECUTE)) {
|
||||
call_goal(m_entry.cast<Function>(), 0, 0, 0, s7.offset, g_ee_main_mem);
|
||||
if (jump_from_c_to_goal) {
|
||||
u64 goal_stack = u64(g_ee_main_mem) + EE_MAIN_MEM_SIZE - 8;
|
||||
call_goal_on_stack(m_entry.cast<Function>(), goal_stack, s7.offset, g_ee_main_mem);
|
||||
} else {
|
||||
call_goal(m_entry.cast<Function>(), 0, 0, 0, s7.offset, g_ee_main_mem);
|
||||
}
|
||||
}
|
||||
|
||||
// inform compiler that we loaded.
|
||||
|
@ -825,14 +830,15 @@ Ptr<uint8_t> link_and_exec(Ptr<uint8_t> data,
|
|||
const char* name,
|
||||
int32_t size,
|
||||
Ptr<kheapinfo> heap,
|
||||
uint32_t flags) {
|
||||
uint32_t flags,
|
||||
bool jump_from_c_to_goal) {
|
||||
link_control lc;
|
||||
lc.begin(data, name, size, heap, flags);
|
||||
uint32_t done;
|
||||
do {
|
||||
done = lc.work();
|
||||
} while (!done);
|
||||
lc.finish();
|
||||
lc.finish(jump_from_c_to_goal);
|
||||
return lc.m_entry;
|
||||
}
|
||||
|
||||
|
@ -842,7 +848,7 @@ Ptr<uint8_t> link_and_exec(Ptr<uint8_t> data,
|
|||
u64 link_and_exec_wrapper(u64* args) {
|
||||
// data, name, size, heap, flags
|
||||
return link_and_exec(Ptr<u8>(args[0]), Ptr<char>(args[1]).c(), args[2], Ptr<kheapinfo>(args[3]),
|
||||
args[4])
|
||||
args[4], false)
|
||||
.offset;
|
||||
}
|
||||
|
||||
|
@ -858,7 +864,8 @@ uint64_t link_begin(u64* args) {
|
|||
auto work_result = saved_link_control.work();
|
||||
// if we managed to finish in one shot, take care of calling finish
|
||||
if (work_result) {
|
||||
saved_link_control.finish();
|
||||
// called from goal
|
||||
saved_link_control.finish(false);
|
||||
}
|
||||
|
||||
return work_result != 0;
|
||||
|
@ -870,7 +877,8 @@ uint64_t link_begin(u64* args) {
|
|||
uint64_t link_resume() {
|
||||
auto work_result = saved_link_control.work();
|
||||
if (work_result) {
|
||||
saved_link_control.finish();
|
||||
// called from goal
|
||||
saved_link_control.finish(false);
|
||||
}
|
||||
return work_result != 0;
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ struct link_control {
|
|||
uint32_t work();
|
||||
uint32_t work_v3();
|
||||
uint32_t work_v2();
|
||||
void finish();
|
||||
void finish(bool jump_from_c_to_goal);
|
||||
|
||||
void reset() {
|
||||
m_object_data.offset = 0;
|
||||
|
@ -97,7 +97,8 @@ Ptr<uint8_t> link_and_exec(Ptr<uint8_t> data,
|
|||
const char* name,
|
||||
int32_t size,
|
||||
Ptr<kheapinfo> heap,
|
||||
uint32_t flags);
|
||||
uint32_t flags,
|
||||
bool jump_from_c_to_goal);
|
||||
|
||||
uint64_t link_begin(u64* args);
|
||||
|
||||
|
|
|
@ -151,8 +151,9 @@ void ProcessListenerMessage(Ptr<char> msg) {
|
|||
// this setup allows listener function execution to clean up after itself.
|
||||
|
||||
// we have added the LINK_FLAG_OUTPUT_LOAD
|
||||
// jump from c to goal because this is called from the C++ stack.
|
||||
ListenerFunction->value = link_and_exec(buffer, "*listener*", 0, kdebugheap,
|
||||
LINK_FLAG_FORCE_DEBUG | LINK_FLAG_OUTPUT_LOAD)
|
||||
LINK_FLAG_FORCE_DEBUG | LINK_FLAG_OUTPUT_LOAD, true)
|
||||
.offset;
|
||||
return; // don't ack yet, this will happen after the function runs.
|
||||
} break;
|
||||
|
|
|
@ -857,7 +857,7 @@ void InitMachineScheme() {
|
|||
*EnableMethodSet = (*EnableMethodSet) + 1;
|
||||
load_and_link_dgo_from_c("game", kglobalheap,
|
||||
LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN,
|
||||
0x400000);
|
||||
0x400000, true);
|
||||
*EnableMethodSet = (*EnableMethodSet) - 1;
|
||||
|
||||
kernel_packages->value =
|
||||
|
|
|
@ -2013,7 +2013,7 @@ s32 InitHeapAndSymbol() {
|
|||
method_set_symbol->value++;
|
||||
load_and_link_dgo_from_c("kernel", kglobalheap,
|
||||
LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN,
|
||||
0x400000);
|
||||
0x400000, true);
|
||||
method_set_symbol->value--;
|
||||
|
||||
// check the kernel version!
|
||||
|
@ -2134,7 +2134,7 @@ s64 load_and_link(const char* filename, char* decode_name, kheapinfo* heap, u32
|
|||
s32 sz;
|
||||
auto rv = FileLoad(decode_name, make_ptr(heap), Ptr<u8>(0), KMALLOC_ALIGN_64, &sz);
|
||||
if (((s32)rv.offset) > -1) {
|
||||
return (s32)link_and_exec(rv, decode_name, sz, make_ptr(heap), flags).offset;
|
||||
return (s32)link_and_exec(rv, decode_name, sz, make_ptr(heap), flags, false).offset;
|
||||
}
|
||||
return (s32)rv.offset;
|
||||
}
|
||||
|
|
|
@ -289,6 +289,12 @@ u32 exec_runtime(int argc, char** argv) {
|
|||
}
|
||||
}
|
||||
|
||||
// initialize graphics first - the EE code will upload textures during boot and we
|
||||
// want the graphics system to catch them.
|
||||
if (enable_display) {
|
||||
Gfx::Init();
|
||||
}
|
||||
|
||||
// step 1: sce library prep
|
||||
iop::LIBRARY_INIT();
|
||||
ee::LIBRARY_INIT_sceCd();
|
||||
|
@ -317,7 +323,6 @@ u32 exec_runtime(int argc, char** argv) {
|
|||
// TODO relegate this to its own function
|
||||
// TODO also sync this up with how the game actually renders things (this is just a placeholder)
|
||||
if (enable_display) {
|
||||
Gfx::Init();
|
||||
Gfx::Loop([]() { return !MasterExit; });
|
||||
Gfx::Exit();
|
||||
}
|
||||
|
|
|
@ -791,70 +791,52 @@
|
|||
|
||||
;; special case for blue eco magnet effect
|
||||
(when (= kind (pickup-type eco-blue))
|
||||
(when (= eco-lev 0.0) ;; old level was 0, we just got our first piece of eco
|
||||
(when (= eco-lev 0.0)
|
||||
(let ((s5-1 (-> obj process)))
|
||||
(let* ((s3-5 (get-process *default-dead-pool* touch-tracker #x4000))
|
||||
(s4-3 (when s3-5
|
||||
;; interestingly, this uses the activate method of touch-tracker, not process.
|
||||
(let ((t9-28 (method-of-type touch-tracker activate)))
|
||||
(t9-28 (the-as touch-tracker s3-5) s5-1 'touch-tracker (the-as pointer #x70004000))
|
||||
)
|
||||
(run-now-in-process s3-5 touch-tracker-init (-> s5-1 root trans) (-> *FACT-bank* suck-bounce-dist) 300)
|
||||
(-> s3-5 ppointer)
|
||||
)
|
||||
(s4-3
|
||||
(when s3-5
|
||||
(let ((t9-28 (method-of-type touch-tracker activate)))
|
||||
(t9-28 (the-as touch-tracker s3-5) s5-1 'touch-tracker (the-as pointer #x70004000))
|
||||
)
|
||||
(run-now-in-process s3-5 touch-tracker-init (-> s5-1 root trans) (-> *FACT-bank* suck-bounce-dist) 300)
|
||||
(-> s3-5 ppointer)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;; send the touch tracker the target
|
||||
(send-event (ppointer->process s4-3) 'target s5-1)
|
||||
;; tell it we have blue eco.
|
||||
(send-event (ppointer->process s4-3) 'event 'eco-blue)
|
||||
;; give it a function to call to see if it's time to exit
|
||||
(send-event (ppointer->process s4-3)
|
||||
'exit
|
||||
(lambda ()
|
||||
(send-event *target* 'query 'powerup 3)
|
||||
)
|
||||
;; set up some collision thing.
|
||||
(send-event (ppointer->process s4-3)
|
||||
'eval
|
||||
(lambda :behavior process-drawable
|
||||
()
|
||||
(set! (-> (the-as collide-shape (-> self root)) root-prim collide-with)
|
||||
(collide-kind cak-1 cak-2 cak-3 blue-eco-suck)
|
||||
)
|
||||
(none)
|
||||
)
|
||||
)
|
||||
)
|
||||
(send-event (ppointer->process s4-3) 'exit (lambda () (send-event *target* 'query 'powerup 3)))
|
||||
(send-event
|
||||
(ppointer->process s4-3)
|
||||
'eval
|
||||
(lambda :behavior process-drawable
|
||||
()
|
||||
(set! (-> (the-as collide-shape (-> self root)) root-prim collide-with)
|
||||
(collide-kind cak-1 cak-2 cak-3 blue-eco-suck)
|
||||
)
|
||||
(none)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;; create a process that just keeps sending 'effect 'eco-blue
|
||||
(let ((s4-4 (get-process *4k-dead-pool* process #x4000)))
|
||||
(when s4-4
|
||||
(let ((t9-35 (method-of-type process activate)))
|
||||
(t9-35 s4-4 s5-1 'process (the-as pointer #x70004000))
|
||||
)
|
||||
(run-next-time-in-process s4-4
|
||||
(lambda ((arg0 process-drawable))
|
||||
(with-pp
|
||||
(let ((start-time (-> *display* base-frame-counter)))
|
||||
(until (>= (the-as int (- (-> *display* base-frame-counter) start-time)) 180)
|
||||
(let ((a1-0 (new 'stack-no-clear 'event-message-block)))
|
||||
(set! (-> a1-0 from) pp)
|
||||
(set! (-> a1-0 num-params) 1)
|
||||
(set! (-> a1-0 message) 'effect)
|
||||
(set! (-> a1-0 param 0) (the-as uint 'eco-blue))
|
||||
(send-event-function arg0 a1-0)
|
||||
)
|
||||
(suspend)
|
||||
)
|
||||
)
|
||||
(none)
|
||||
)
|
||||
)
|
||||
s5-1
|
||||
)
|
||||
(run-next-time-in-process
|
||||
s4-4
|
||||
(lambda ((arg0 process-drawable))
|
||||
(let ((s5-0 (-> *display* base-frame-counter)))
|
||||
(until (>= (- (-> *display* base-frame-counter) s5-0) 180)
|
||||
(send-event arg0 'effect 'eco-blue)
|
||||
(suspend)
|
||||
)
|
||||
)
|
||||
(none)
|
||||
)
|
||||
s5-1
|
||||
)
|
||||
(-> s4-4 ppointer)
|
||||
)
|
||||
)
|
||||
|
@ -865,12 +847,7 @@
|
|||
(-> obj eco-level)
|
||||
)
|
||||
(else
|
||||
((method-of-type fact-info pickup-collectable!)
|
||||
obj
|
||||
kind
|
||||
amount
|
||||
source-handle
|
||||
)
|
||||
((method-of-type fact-info pickup-collectable!) obj kind amount source-handle)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -99,6 +99,11 @@
|
|||
;; vector types (integer)
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defmacro init-vf0-vector ()
|
||||
"Initializes the VF0 vector which is a constant vector in the VU set to <0,0,0,1>"
|
||||
`(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
|
||||
)
|
||||
|
||||
;; the GOAL vector types are structures, storing values in memory.
|
||||
|
||||
;; Vector of 4 unsigned bytes.
|
||||
|
@ -647,11 +652,10 @@
|
|||
;; load vectors
|
||||
(.lvf vf2 a)
|
||||
(.lvf vf3 b)
|
||||
;; set vf0 to zero
|
||||
(.xor.vf vf0 vf0 vf0)
|
||||
(init-vf0-vector)
|
||||
;; add
|
||||
(.add.vf vf1 vf2 vf3)
|
||||
;; set w = 0
|
||||
;; set w = 1
|
||||
(.blend.vf vf1 vf1 vf0 :mask #b1000)
|
||||
;; store
|
||||
(.svf dst vf1)
|
||||
|
@ -669,11 +673,10 @@
|
|||
;; load vectors
|
||||
(.lvf vf2 a)
|
||||
(.lvf vf3 b)
|
||||
;; set vf0 to zero
|
||||
(.xor.vf vf0 vf0 vf0)
|
||||
(init-vf0-vector)
|
||||
;; subtract
|
||||
(.sub.vf vf1 vf2 vf3)
|
||||
;; set w = 0
|
||||
;; set w = 1
|
||||
(.blend.vf vf1 vf1 vf0 :mask #b1000)
|
||||
;; store
|
||||
(.svf dst vf1)
|
||||
|
|
|
@ -187,10 +187,6 @@
|
|||
|
||||
(define *transform-regs* (new 'static 'transform-regs))
|
||||
|
||||
(defmacro init-vf0-vector ()
|
||||
"Initializes the VF0 vector which is a constant vector in the VU set to <0,0,0,1>"
|
||||
`(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
|
||||
)
|
||||
|
||||
(defmacro with-vf0 (&rest body)
|
||||
"Macro for using the ps2-style vf0 register."
|
||||
|
|
|
@ -510,6 +510,7 @@
|
|||
:size-assert #x48
|
||||
:method-count-assert 9
|
||||
:flag-assert #x900000048
|
||||
:always-stack-singleton
|
||||
)
|
||||
|
||||
(defmacro process-stack-used (proc)
|
||||
|
|
|
@ -232,7 +232,8 @@ class Compiler {
|
|||
const goos::Object& type,
|
||||
const goos::Object* rest,
|
||||
Env* env,
|
||||
bool call_constructor);
|
||||
bool call_constructor,
|
||||
bool use_singleton);
|
||||
|
||||
StaticResult fill_static_array(const goos::Object& form,
|
||||
const goos::Object& rest,
|
||||
|
|
|
@ -263,21 +263,8 @@ RegVal* FunctionEnv::lexical_lookup(goos::Object sym) {
|
|||
return kv->second;
|
||||
}
|
||||
|
||||
StackVarAddrVal* FunctionEnv::allocate_stack_variable(const TypeSpec& ts, int size_bytes) {
|
||||
FunctionEnv::StackSpace FunctionEnv::allocate_aligned_stack_space(int size_bytes, int align_bytes) {
|
||||
require_aligned_stack();
|
||||
int slots_used = (size_bytes + emitter::GPR_SIZE - 1) / emitter::GPR_SIZE;
|
||||
auto result = alloc_val<StackVarAddrVal>(ts, m_stack_var_slots_used, slots_used);
|
||||
m_stack_var_slots_used += slots_used;
|
||||
return result;
|
||||
}
|
||||
|
||||
StackVarAddrVal* FunctionEnv::allocate_aligned_stack_variable(const TypeSpec& ts,
|
||||
int size_bytes,
|
||||
int align_bytes) {
|
||||
require_aligned_stack();
|
||||
if (align_bytes > 16) {
|
||||
fmt::print("\n\n\nBad stack align: {} bytes for {}\n\n\n\n", align_bytes, ts.print());
|
||||
}
|
||||
assert(align_bytes <= 16);
|
||||
int align_slots = (align_bytes + emitter::GPR_SIZE - 1) / emitter::GPR_SIZE;
|
||||
while (m_stack_var_slots_used % align_slots) {
|
||||
|
@ -292,16 +279,41 @@ StackVarAddrVal* FunctionEnv::allocate_aligned_stack_variable(const TypeSpec& ts
|
|||
}
|
||||
|
||||
int slots_used = (size_bytes + emitter::GPR_SIZE - 1) / emitter::GPR_SIZE;
|
||||
auto result = alloc_val<StackVarAddrVal>(ts, m_stack_var_slots_used, slots_used);
|
||||
StackSpace result;
|
||||
result.slot_count = slots_used;
|
||||
result.start_slot = m_stack_var_slots_used;
|
||||
m_stack_var_slots_used += slots_used;
|
||||
return result;
|
||||
}
|
||||
|
||||
StackVarAddrVal* FunctionEnv::allocate_aligned_stack_variable(const TypeSpec& ts,
|
||||
int size_bytes,
|
||||
int align_bytes) {
|
||||
if (align_bytes > 16) {
|
||||
fmt::print("\n\n\nBad stack align: {} bytes for {}\n\n\n\n", align_bytes, ts.print());
|
||||
}
|
||||
auto space = allocate_aligned_stack_space(size_bytes, align_bytes);
|
||||
return alloc_val<StackVarAddrVal>(ts, space.start_slot, space.slot_count);
|
||||
}
|
||||
|
||||
RegVal* FunctionEnv::push_reg_val(std::unique_ptr<RegVal> in) {
|
||||
m_iregs.push_back(std::move(in));
|
||||
return m_iregs.back().get();
|
||||
}
|
||||
|
||||
StackVarAddrVal* FunctionEnv::allocate_stack_singleton(const TypeSpec& ts,
|
||||
int size_bytes,
|
||||
int align_bytes) {
|
||||
const auto& existing = m_stack_singleton_slots.find(ts.print());
|
||||
if (existing == m_stack_singleton_slots.end()) {
|
||||
auto space = allocate_aligned_stack_space(size_bytes, align_bytes);
|
||||
m_stack_singleton_slots[ts.print()] = space;
|
||||
return alloc_val<StackVarAddrVal>(ts, space.start_slot, space.slot_count);
|
||||
} else {
|
||||
return alloc_val<StackVarAddrVal>(ts, existing->second.start_slot, existing->second.slot_count);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// LexicalEnv
|
||||
///////////////////
|
||||
|
|
|
@ -190,10 +190,15 @@ class FunctionEnv : public DeclareEnv {
|
|||
}
|
||||
const std::string& name() const { return m_name; }
|
||||
|
||||
StackVarAddrVal* allocate_stack_variable(const TypeSpec& ts, int size_bytes);
|
||||
struct StackSpace {
|
||||
int start_slot;
|
||||
int slot_count;
|
||||
};
|
||||
StackSpace allocate_aligned_stack_space(int size_bytes, int align_bytes);
|
||||
StackVarAddrVal* allocate_aligned_stack_variable(const TypeSpec& ts,
|
||||
int size_bytes,
|
||||
int align_bytes);
|
||||
StackVarAddrVal* allocate_stack_singleton(const TypeSpec& ts, int size_bytes, int align_bytes);
|
||||
int stack_slots_used_for_stack_vars() const { return m_stack_var_slots_used; }
|
||||
|
||||
int segment_for_static_data() {
|
||||
|
@ -250,6 +255,8 @@ class FunctionEnv : public DeclareEnv {
|
|||
int m_stack_var_slots_used = 0;
|
||||
std::unordered_map<std::string, Label> m_labels;
|
||||
std::vector<std::unique_ptr<Label>> m_unnamed_labels;
|
||||
std::unordered_map<std::string, StackSpace> m_stack_singleton_slots;
|
||||
|
||||
const goos::Reader* m_reader = nullptr;
|
||||
};
|
||||
|
||||
|
|
|
@ -1077,13 +1077,25 @@ Val* Compiler::compile_stack_new(const goos::Object& form,
|
|||
const goos::Object& type,
|
||||
const goos::Object* rest,
|
||||
Env* env,
|
||||
bool call_constructor) {
|
||||
bool call_constructor,
|
||||
bool use_singleton) {
|
||||
auto type_of_object = parse_typespec(unquote(type));
|
||||
auto fe = env->function_env();
|
||||
auto st_type_info = dynamic_cast<StructureType*>(m_ts.lookup_type(type_of_object));
|
||||
if (st_type_info && st_type_info->is_always_stack_singleton()) {
|
||||
use_singleton = true;
|
||||
if (call_constructor) {
|
||||
throw_compiler_error(
|
||||
form, "Stack-singleton types must be created on the stack with stack-no-clear");
|
||||
}
|
||||
}
|
||||
if (type_of_object == TypeSpec("inline-array") || type_of_object == TypeSpec("array")) {
|
||||
if (call_constructor) {
|
||||
throw_compiler_error(form, "Constructing stack arrays is not yet supported");
|
||||
}
|
||||
if (use_singleton) {
|
||||
throw_compiler_error(form, "Singleton stack arrays are not yet supported");
|
||||
}
|
||||
bool is_inline = type_of_object == TypeSpec("inline-array");
|
||||
auto elt_type = quoted_sym_as_string(pair_car(*rest));
|
||||
rest = &pair_cdr(*rest);
|
||||
|
@ -1144,9 +1156,19 @@ Val* Compiler::compile_stack_new(const goos::Object& form,
|
|||
}
|
||||
std::vector<RegVal*> args;
|
||||
// allocation
|
||||
auto mem = fe->allocate_aligned_stack_variable(type_of_object, ti->get_size_in_memory(), 16)
|
||||
->to_gpr(form, env);
|
||||
RegVal* mem;
|
||||
if (use_singleton) {
|
||||
mem = fe->allocate_stack_singleton(type_of_object, ti->get_size_in_memory(), 16)
|
||||
->to_gpr(form, env);
|
||||
} else {
|
||||
mem = fe->allocate_aligned_stack_variable(type_of_object, ti->get_size_in_memory(), 16)
|
||||
->to_gpr(form, env);
|
||||
}
|
||||
|
||||
if (call_constructor) {
|
||||
if (use_singleton) {
|
||||
throw_compiler_error(form, "Constructing stack singletons is not yet supported");
|
||||
}
|
||||
// the new method actual takes a "symbol" according the type system. So we have to cheat it.
|
||||
mem->set_type(TypeSpec("symbol"));
|
||||
args.push_back(mem);
|
||||
|
@ -1189,9 +1211,11 @@ Val* Compiler::compile_new(const goos::Object& form, const goos::Object& _rest,
|
|||
// put in code.
|
||||
return compile_static_new(form, type, rest, env);
|
||||
} else if (allocation == "stack") {
|
||||
return compile_stack_new(form, type, rest, env, true);
|
||||
return compile_stack_new(form, type, rest, env, true, false);
|
||||
} else if (allocation == "stack-no-clear") {
|
||||
return compile_stack_new(form, type, rest, env, false);
|
||||
return compile_stack_new(form, type, rest, env, false, false);
|
||||
} else if (allocation == "stack-singleton-no-clear") {
|
||||
return compile_stack_new(form, type, rest, env, false, true);
|
||||
}
|
||||
|
||||
throw_compiler_error(form, "Unsupported new form");
|
||||
|
|
2
test/decompiler/reference/decompiler-macros.gc
generated
vendored
2
test/decompiler/reference/decompiler-macros.gc
generated
vendored
|
@ -837,7 +837,7 @@
|
|||
"Send an event to a process. This should be used over send-event-function"
|
||||
|
||||
`(with-pp
|
||||
(let ((event-data (new 'stack-no-clear 'event-message-block)))
|
||||
(let ((event-data (new 'stack-singleton-no-clear 'event-message-block)))
|
||||
(set! (-> event-data from) pp)
|
||||
(set! (-> event-data num-params) ,(length params))
|
||||
(set! (-> event-data message) ,msg)
|
||||
|
|
1
test/decompiler/reference/kernel/gkernel-h_REF.gc
generated
vendored
1
test/decompiler/reference/kernel/gkernel-h_REF.gc
generated
vendored
|
@ -327,6 +327,7 @@
|
|||
(message symbol :offset-assert 12)
|
||||
(param uint64 7 :offset-assert 16)
|
||||
)
|
||||
:always-stack-singleton
|
||||
:method-count-assert 9
|
||||
:size-assert #x48
|
||||
:flag-assert #x900000048
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
(.nop.vf)
|
||||
(vector-! vector-2 vector-1 vector-0)
|
||||
(.nop.vf)
|
||||
; 9 + 18 + 27 = 54.0000
|
||||
; 9 + 18 + 27 + 1 = 55.0000
|
||||
(format #t "~f~%" (+ (-> vector-2 x) (-> vector-2 y) (-> vector-2 z) (-> vector-2 w)))
|
||||
)
|
||||
)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
(deftype ss-test-type (structure)
|
||||
((data uint64)
|
||||
(foo uint64))
|
||||
:always-stack-singleton
|
||||
)
|
||||
|
||||
|
||||
(defun ss-test ()
|
||||
(let ((v1 (new 'stack-no-clear 'ss-test-type))
|
||||
(v2 (new 'stack-no-clear 'ss-test-type)))
|
||||
(format #t "~A~%" (= v1 v2))
|
||||
)
|
||||
)
|
||||
|
||||
(ss-test)
|
|
@ -0,0 +1,18 @@
|
|||
(defun stack-addr-test ()
|
||||
(let ((vec1 (new 'stack-no-clear 'vector))
|
||||
(vec2 (new 'stack-no-clear 'vector))
|
||||
(vec3 (new 'stack-singleton-no-clear 'vector))
|
||||
(vec4 (new 'stack 'vector))
|
||||
(vec5 (new 'stack-singleton-no-clear 'vector))
|
||||
)
|
||||
(format #t "~A ~A ~A ~A ~A~%"
|
||||
(= vec1 vec2)
|
||||
(= vec2 vec3)
|
||||
(= vec3 vec4)
|
||||
(= vec4 vec5)
|
||||
(= vec3 vec5)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(stack-addr-test)
|
|
@ -607,7 +607,7 @@ TEST_F(WithGameTests, VFLoadAndStore) {
|
|||
|
||||
TEST_F(WithGameTests, VFSimpleMath) {
|
||||
shared_compiler->runner.run_static_test(env, testCategory, "test-basic-vector-math.gc",
|
||||
{"54.0000\n0\n"});
|
||||
{"55.0000\n0\n"});
|
||||
}
|
||||
|
||||
TEST_F(WithGameTests, VFLoadStatic) {
|
||||
|
@ -900,6 +900,16 @@ TEST_F(WithGameTests, PointerInStatic) {
|
|||
{"#f\n0\n"});
|
||||
}
|
||||
|
||||
TEST_F(WithGameTests, StackSingleton) {
|
||||
shared_compiler->runner.run_static_test(env, testCategory, "test-stack-singleton.gc",
|
||||
{"#f #f #f #f #t\n0\n"});
|
||||
}
|
||||
|
||||
TEST_F(WithGameTests, StackSingletonType) {
|
||||
shared_compiler->runner.run_static_test(env, testCategory, "test-stack-singleton-type.gc",
|
||||
{"#t\n0\n"});
|
||||
}
|
||||
|
||||
namespace Mips2C {
|
||||
namespace test_func {
|
||||
extern u64 execute(void*);
|
||||
|
|
Loading…
Reference in a new issue