diff --git a/common/dma/gs.h b/common/dma/gs.h index acc464e31..00468774f 100644 --- a/common/dma/gs.h +++ b/common/dma/gs.h @@ -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); } diff --git a/common/type_system/Type.cpp b/common/type_system/Type.cpp index ee330803c..6b395a3be 100644 --- a/common/type_system/Type.cpp +++ b/common/type_system/Type.cpp @@ -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 } diff --git a/common/type_system/Type.h b/common/type_system/Type.h index 800ecbcb4..364c11ae9 100644 --- a/common/type_system/Type.h +++ b/common/type_system/Type.h @@ -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; }; diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index 1d738a40c..53bb35429 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -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()) { diff --git a/common/type_system/deftype.cpp b/common/type_system/deftype.cpp index c0b632b7f..173523da1 100644 --- a/common/type_system/deftype.cpp +++ b/common/type_system/deftype.cpp @@ -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)); diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index cc2afe938..c1bbd7060 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -1469,6 +1469,7 @@ :method-count-assert 9 :size-assert #x48 :flag-assert #x900000048 + :always-stack-singleton ) ;; - Symbols diff --git a/docs/markdown/progress-notes/changelog.md b/docs/markdown/progress-notes/changelog.md index 7c5cd5f54..87599dd70 100644 --- a/docs/markdown/progress-notes/changelog.md +++ b/docs/markdown/progress-notes/changelog.md @@ -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`. \ No newline at end of file +- 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. \ No newline at end of file diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 57d98ca14..1e9c5203c 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -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 diff --git a/game/graphics/opengl_renderer/BucketRenderer.cpp b/game/graphics/opengl_renderer/BucketRenderer.cpp index 158ce1005..6de77c719 100644 --- a/game/graphics/opengl_renderer/BucketRenderer.cpp +++ b/game/graphics/opengl_renderer/BucketRenderer.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> 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(); } \ No newline at end of file diff --git a/game/graphics/opengl_renderer/BucketRenderer.h b/game/graphics/opengl_renderer/BucketRenderer.h index 1bb2c5b82..796c89788 100644 --- a/game/graphics/opengl_renderer/BucketRenderer.h +++ b/game/graphics/opengl_renderer/BucketRenderer.h @@ -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> renderers); + void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override; + void draw_debug_window() override; + void serialize(Serializer& ser) override; + + private: + std::vector> m_renderers; + int m_render_idx = 0; + std::vector m_name_strs; + std::vector m_name_str_ptrs; +}; + /*! * Renderer that makes sure the bucket is empty and ignores it. */ diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index 9d28c7ae8..e4c6612c5 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -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("water-tex-0", BucketId::WATER_TEX_LEVEL0); init_bucket_renderer("water-tex-1", BucketId::WATER_TEX_LEVEL1); init_bucket_renderer("pre-sprite-tex", BucketId::PRE_SPRITE_TEX); - init_bucket_renderer("sprite", BucketId::SPRITE); + std::vector> sprite_renderers; + sprite_renderers.push_back(std::make_unique("sprite-renderer", BucketId::SPRITE)); + sprite_renderers.push_back(std::make_unique("sprite-3", BucketId::SPRITE)); + + init_bucket_renderer("sprite", BucketId::SPRITE, std::move(sprite_renderers)); init_bucket_renderer("debug-draw-0", BucketId::DEBUG_DRAW_0, 0x8000, DirectRenderer::Mode::NORMAL); init_bucket_renderer("debug-draw-1", BucketId::DEBUG_DRAW_1, 0x8000, diff --git a/game/graphics/opengl_renderer/Shader.cpp b/game/graphics/opengl_renderer/Shader.cpp index 6e14cf26c..a24b37571 100644 --- a/game/graphics/opengl_renderer/Shader.cpp +++ b/game/graphics/opengl_renderer/Shader.cpp @@ -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"}; } diff --git a/game/graphics/opengl_renderer/Shader.h b/game/graphics/opengl_renderer/Shader.h index e986883d9..e26d05d57 100644 --- a/game/graphics/opengl_renderer/Shader.h +++ b/game/graphics/opengl_renderer/Shader.h @@ -37,6 +37,7 @@ enum class ShaderId { TFRAG3 = 12, TFRAG3_NO_TEX = 13, SPRITE = 14, + SPRITE3 = 15, MAX_SHADERS }; diff --git a/game/graphics/opengl_renderer/Sprite3.cpp b/game/graphics/opengl_renderer/Sprite3.cpp new file mode 100644 index 000000000..e9e272c29 --- /dev/null +++ b/game/graphics/opengl_renderer/Sprite3.cpp @@ -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; + } +} diff --git a/game/graphics/opengl_renderer/Sprite3.h b/game/graphics/opengl_renderer/Sprite3.h new file mode 100644 index 000000000..5cdf20c09 --- /dev/null +++ b/game/graphics/opengl_renderer/Sprite3.h @@ -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 + +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 flags_matrix; // flags + matrix... split + math::Vector info; + math::Vector pad; + }; + static_assert(sizeof(SpriteVertex3D) == 64); + + std::vector 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 ids; + u32 offset_in_idx_buffer = 0; + }; + + std::map m_sprite_buckets; + + u64 m_last_bucket_key = UINT64_MAX; + Bucket* m_last_bucket = nullptr; + + u64 m_sprite_idx = 0; + + std::vector m_index_buffer_data; +}; \ No newline at end of file diff --git a/game/graphics/opengl_renderer/SpriteRenderer.h b/game/graphics/opengl_renderer/SpriteRenderer.h index e95a5834b..dd0199b5a 100644 --- a/game/graphics/opengl_renderer/SpriteRenderer.h +++ b/game/graphics/opengl_renderer/SpriteRenderer.h @@ -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 color; - Vector4f st0; - math::Vector xy0; - Vector4f st1; - math::Vector xy1; - Vector4f st2; - math::Vector xy2; - Vector4f st3; - math::Vector 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: diff --git a/game/graphics/opengl_renderer/shaders/sprite3_3d.frag b/game/graphics/opengl_renderer/shaders/sprite3_3d.frag new file mode 100644 index 000000000..2cc5a4254 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/sprite3_3d.frag @@ -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; +//} diff --git a/game/graphics/opengl_renderer/shaders/sprite3_3d.vert b/game/graphics/opengl_renderer/shaders/sprite3_3d.vert new file mode 100644 index 000000000..0e245cc72 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/sprite3_3d.vert @@ -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; +} diff --git a/game/graphics/opengl_renderer/sprite_common.h b/game/graphics/opengl_renderer/sprite_common.h new file mode 100644 index 000000000..936b3a10d --- /dev/null +++ b/game/graphics/opengl_renderer/sprite_common.h @@ -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 color; + Vector4f st0; + math::Vector xy0; + Vector4f st1; + math::Vector xy1; + Vector4f st2; + math::Vector xy2; + Vector4f st3; + math::Vector 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"); \ No newline at end of file diff --git a/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp b/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp index 29539cd8e..c1b42b201 100644 --- a/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp +++ b/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp @@ -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; diff --git a/game/graphics/opengl_renderer/tfrag/Tie3.cpp b/game/graphics/opengl_renderer/tfrag/Tie3.cpp index 0297b48be..680196ba1 100644 --- a/game/graphics/opengl_renderer/tfrag/Tie3.cpp +++ b/game/graphics/opengl_renderer/tfrag/Tie3.cpp @@ -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; diff --git a/game/graphics/opengl_renderer/tfrag/tfrag_common.cpp b/game/graphics/opengl_renderer/tfrag/tfrag_common.cpp index 89b399346..b6d666fb3 100644 --- a/game/graphics/opengl_renderer/tfrag/tfrag_common.cpp +++ b/game/graphics/opengl_renderer/tfrag/tfrag_common.cpp @@ -6,10 +6,8 @@ #include -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) { diff --git a/game/graphics/opengl_renderer/tfrag/tfrag_common.h b/game/graphics/opengl_renderer/tfrag/tfrag_common.h index 7babbe1b3..ab87ec00d 100644 --- a/game/graphics/opengl_renderer/tfrag/tfrag_common.h +++ b/game/graphics/opengl_renderer/tfrag/tfrag_common.h @@ -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& in, diff --git a/game/kernel/kdgo.cpp b/game/kernel/kdgo.cpp index 11e13d741..568f7b8d0 100644 --- a/game/kernel/kdgo.cpp +++ b/game/kernel/kdgo.cpp @@ -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(name_gstr + 4).c(); auto heap = Ptr(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 heap, u32 linkFlag, s32 bufferSize) { +void load_and_link_dgo_from_c(const char* name, + Ptr 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 heap, u32 linkFla char objName[64]; strcpy(objName, (dgoObj + 4).cast().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) { diff --git a/game/kernel/kdgo.h b/game/kernel/kdgo.h index 449d713d9..30eaebd36 100644 --- a/game/kernel/kdgo.h +++ b/game/kernel/kdgo.h @@ -12,7 +12,11 @@ void kdgo_init_globals(); u32 InitRPC(); -void load_and_link_dgo_from_c(const char* name, Ptr heap, u32 linkFlag, s32 bufferSize); +void load_and_link_dgo_from_c(const char* name, + Ptr 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(); diff --git a/game/kernel/klink.cpp b/game/kernel/klink.cpp index 765e59cc5..4306b84d2 100644 --- a/game/kernel/klink.cpp +++ b/game/kernel/klink.cpp @@ -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(), 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(), goal_stack, s7.offset, g_ee_main_mem); + } else { + call_goal(m_entry.cast(), 0, 0, 0, s7.offset, g_ee_main_mem); + } } // inform compiler that we loaded. @@ -825,14 +830,15 @@ Ptr link_and_exec(Ptr data, const char* name, int32_t size, Ptr 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 link_and_exec(Ptr data, u64 link_and_exec_wrapper(u64* args) { // data, name, size, heap, flags return link_and_exec(Ptr(args[0]), Ptr(args[1]).c(), args[2], Ptr(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; } diff --git a/game/kernel/klink.h b/game/kernel/klink.h index 001a83d88..bc45781fb 100644 --- a/game/kernel/klink.h +++ b/game/kernel/klink.h @@ -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 link_and_exec(Ptr data, const char* name, int32_t size, Ptr heap, - uint32_t flags); + uint32_t flags, + bool jump_from_c_to_goal); uint64_t link_begin(u64* args); diff --git a/game/kernel/klisten.cpp b/game/kernel/klisten.cpp index f5825d6e5..1aa2199d4 100644 --- a/game/kernel/klisten.cpp +++ b/game/kernel/klisten.cpp @@ -151,8 +151,9 @@ void ProcessListenerMessage(Ptr 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; diff --git a/game/kernel/kmachine.cpp b/game/kernel/kmachine.cpp index eb3e93c4a..7ddb9f09b 100644 --- a/game/kernel/kmachine.cpp +++ b/game/kernel/kmachine.cpp @@ -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 = diff --git a/game/kernel/kscheme.cpp b/game/kernel/kscheme.cpp index 48a9dc450..029f6f647 100644 --- a/game/kernel/kscheme.cpp +++ b/game/kernel/kscheme.cpp @@ -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(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; } diff --git a/game/runtime.cpp b/game/runtime.cpp index 9e093fcbd..2d8829642 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -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(); } diff --git a/goal_src/engine/game/game-info.gc b/goal_src/engine/game/game-info.gc index 62fdea0c8..2aeb16066 100644 --- a/goal_src/engine/game/game-info.gc +++ b/goal_src/engine/game/game-info.gc @@ -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) ) ) ) diff --git a/goal_src/engine/math/vector-h.gc b/goal_src/engine/math/vector-h.gc index 3ec6ff0f4..d9afb1e3e 100644 --- a/goal_src/engine/math/vector-h.gc +++ b/goal_src/engine/math/vector-h.gc @@ -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) diff --git a/goal_src/engine/ps2/vu1-macros.gc b/goal_src/engine/ps2/vu1-macros.gc index c7f6de59b..51633b016 100644 --- a/goal_src/engine/ps2/vu1-macros.gc +++ b/goal_src/engine/ps2/vu1-macros.gc @@ -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." diff --git a/goal_src/kernel/gkernel-h.gc b/goal_src/kernel/gkernel-h.gc index 13c1f83fa..cd9c88c70 100644 --- a/goal_src/kernel/gkernel-h.gc +++ b/goal_src/kernel/gkernel-h.gc @@ -510,6 +510,7 @@ :size-assert #x48 :method-count-assert 9 :flag-assert #x900000048 + :always-stack-singleton ) (defmacro process-stack-used (proc) diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index f10acd901..aa0189531 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -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, diff --git a/goalc/compiler/Env.cpp b/goalc/compiler/Env.cpp index 65438008b..35935f474 100644 --- a/goalc/compiler/Env.cpp +++ b/goalc/compiler/Env.cpp @@ -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(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(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(ts, space.start_slot, space.slot_count); +} + RegVal* FunctionEnv::push_reg_val(std::unique_ptr 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(ts, space.start_slot, space.slot_count); + } else { + return alloc_val(ts, existing->second.start_slot, existing->second.slot_count); + } +} + /////////////////// // LexicalEnv /////////////////// diff --git a/goalc/compiler/Env.h b/goalc/compiler/Env.h index 0f85af8c8..9311bf081 100644 --- a/goalc/compiler/Env.h +++ b/goalc/compiler/Env.h @@ -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 m_labels; std::vector> m_unnamed_labels; + std::unordered_map m_stack_singleton_slots; + const goos::Reader* m_reader = nullptr; }; diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index e79af5f9d..3c372ac72 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -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(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 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"); diff --git a/test/decompiler/reference/decompiler-macros.gc b/test/decompiler/reference/decompiler-macros.gc index e2742a75d..5e25f5821 100644 --- a/test/decompiler/reference/decompiler-macros.gc +++ b/test/decompiler/reference/decompiler-macros.gc @@ -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) diff --git a/test/decompiler/reference/kernel/gkernel-h_REF.gc b/test/decompiler/reference/kernel/gkernel-h_REF.gc index e3e2c1c36..8ffeea697 100644 --- a/test/decompiler/reference/kernel/gkernel-h_REF.gc +++ b/test/decompiler/reference/kernel/gkernel-h_REF.gc @@ -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 diff --git a/test/goalc/source_templates/with_game/test-basic-vector-math.gc b/test/goalc/source_templates/with_game/test-basic-vector-math.gc index 44083d2c0..268e40365 100644 --- a/test/goalc/source_templates/with_game/test-basic-vector-math.gc +++ b/test/goalc/source_templates/with_game/test-basic-vector-math.gc @@ -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))) ) ) diff --git a/test/goalc/source_templates/with_game/test-stack-singleton-type.gc b/test/goalc/source_templates/with_game/test-stack-singleton-type.gc new file mode 100644 index 000000000..81f898a50 --- /dev/null +++ b/test/goalc/source_templates/with_game/test-stack-singleton-type.gc @@ -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) \ No newline at end of file diff --git a/test/goalc/source_templates/with_game/test-stack-singleton.gc b/test/goalc/source_templates/with_game/test-stack-singleton.gc new file mode 100644 index 000000000..7492abc7d --- /dev/null +++ b/test/goalc/source_templates/with_game/test-stack-singleton.gc @@ -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) \ No newline at end of file diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index 5a6cc5a7a..04b338cb7 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -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*);