#include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Graphics/CDrawable.hpp" #include "Runtime/Graphics/CMetroidModelInstance.hpp" #include "Runtime/Graphics/CDrawablePlaneObject.hpp" #include "Runtime/Graphics/CLight.hpp" #include "Runtime/Graphics/CModel.hpp" #include "Runtime/Particle/CParticleGen.hpp" #include "Runtime/Graphics/CCubeModel.hpp" #include "Runtime/Graphics/CCubeSurface.hpp" #include "Runtime/Graphics/CCubeMaterial.hpp" namespace metaforce { static logvisor::Module Log("CCubeRenderer"); static rstl::reserved_vector sDataHolder; static rstl::reserved_vector, 50> sBucketsHolder; static rstl::reserved_vector sPlaneObjectDataHolder; static rstl::reserved_vector sPlaneObjectBucketHolder; class Buckets { friend class CCubeRenderer; static inline rstl::reserved_vector sBucketIndex; static inline rstl::reserved_vector* sData = nullptr; static inline rstl::reserved_vector, 50>* sBuckets = nullptr; static inline rstl::reserved_vector* sPlaneObjectData = nullptr; static inline rstl::reserved_vector* sPlaneObjectBucket = nullptr; static constexpr std::array skWorstMinMaxDistance{99999.0f, -99999.0f}; static inline std::array sMinMaxDistance{0.0f, 0.0f}; public: static void Clear(); static void Sort(); static void InsertPlaneObject(float closeDist, float farDist, const zeus::CAABox& aabb, bool invertTest, const zeus::CPlane& plane, bool zOnly, EDrawableType dtype, void* data); static void Insert(const zeus::CVector3f& pos, const zeus::CAABox& aabb, EDrawableType dtype, void* data, const zeus::CPlane& plane, u16 extraSort); static void Shutdown(); static void Init(); }; void Buckets::Clear() { sData->clear(); sBucketIndex.clear(); sPlaneObjectData->clear(); sPlaneObjectBucket->clear(); for (rstl::reserved_vector& bucket : *sBuckets) { bucket.clear(); } sMinMaxDistance = skWorstMinMaxDistance; } void Buckets::Sort() { float delta = std::max(1.f, sMinMaxDistance[1] - sMinMaxDistance[0]); float pitch = 49.f / delta; for (auto it = sPlaneObjectData->begin(); it != sPlaneObjectData->end(); ++it) if (sPlaneObjectBucket->size() != sPlaneObjectBucket->capacity()) sPlaneObjectBucket->push_back(s16(it - sPlaneObjectData->begin())); u32 precision = 50; if (sPlaneObjectBucket->size()) { std::sort(sPlaneObjectBucket->begin(), sPlaneObjectBucket->end(), [](u16 a, u16 b) { return (*sPlaneObjectData)[a].GetDistance() < (*sPlaneObjectData)[b].GetDistance(); }); precision = 50 / u32(sPlaneObjectBucket->size() + 1); pitch = 1.f / (delta / float(precision - 2)); int accum = 0; for (u16 idx : *sPlaneObjectBucket) { ++accum; CDrawablePlaneObject& planeObj = (*sPlaneObjectData)[idx]; planeObj.x24_targetBucket = u16(precision * accum); } } for (CDrawable& drawable : *sData) { int slot; float relDist = drawable.GetDistance() - sMinMaxDistance[0]; if (sPlaneObjectBucket->empty()) { slot = zeus::clamp(1, int(relDist * pitch), 49); } else { slot = zeus::clamp(0, int(relDist * pitch), int(precision) - 2); for (u16 idx : *sPlaneObjectBucket) { CDrawablePlaneObject& planeObj = (*sPlaneObjectData)[idx]; bool partial, full; if (planeObj.x3c_25_zOnly) { partial = drawable.GetBounds().max.z() > planeObj.GetPlane().d(); full = drawable.GetBounds().min.z() > planeObj.GetPlane().d(); } else { partial = planeObj.GetPlane().pointToPlaneDist( drawable.GetBounds().closestPointAlongVector(planeObj.GetPlane().normal())) > 0.f; full = planeObj.GetPlane().pointToPlaneDist( drawable.GetBounds().furthestPointAlongVector(planeObj.GetPlane().normal())) > 0.f; } bool cont; if (drawable.GetType() == EDrawableType::Particle) cont = planeObj.x3c_24_invertTest ? !partial : full; else cont = planeObj.x3c_24_invertTest ? (!partial || !full) : (partial || full); if (!cont) break; slot += precision; } } if (slot == -1) slot = 49; rstl::reserved_vector& bucket = (*sBuckets)[slot]; if (bucket.size() < bucket.capacity()) bucket.push_back(&drawable); // else // Log.report(logvisor::Fatal, FMT_STRING("Full bucket!!!")); } u16 bucketIdx = u16(sBuckets->size()); for (auto it = sBuckets->rbegin(); it != sBuckets->rend(); ++it) { --bucketIdx; sBucketIndex.push_back(bucketIdx); rstl::reserved_vector& bucket = *it; if (bucket.size()) { std::sort(bucket.begin(), bucket.end(), [](CDrawable* a, CDrawable* b) { if (a->GetDistance() == b->GetDistance()) return a->GetExtraSort() > b->GetExtraSort(); return a->GetDistance() > b->GetDistance(); }); } } for (auto it = sPlaneObjectBucket->rbegin(); it != sPlaneObjectBucket->rend(); ++it) { CDrawablePlaneObject& planeObj = (*sPlaneObjectData)[*it]; rstl::reserved_vector& bucket = (*sBuckets)[planeObj.x24_targetBucket]; bucket.push_back(&planeObj); } } void Buckets::InsertPlaneObject(float closeDist, float farDist, const zeus::CAABox& aabb, bool invertTest, const zeus::CPlane& plane, bool zOnly, EDrawableType dtype, void* data) { if (sPlaneObjectData->size() == sPlaneObjectData->capacity()) { return; } sPlaneObjectData->emplace_back(dtype, closeDist, farDist, aabb, invertTest, plane, zOnly, data); } void Buckets::Insert(const zeus::CVector3f& pos, const zeus::CAABox& aabb, EDrawableType dtype, void* data, const zeus::CPlane& plane, u16 extraSort) { if (sData->size() == sData->capacity()) { Log.report(logvisor::Fatal, FMT_STRING("Rendering buckets filled to capacity")); return; } const float dist = plane.pointToPlaneDist(pos); sData->emplace_back(dtype, extraSort, dist, aabb, data); sMinMaxDistance[0] = std::min(sMinMaxDistance[0], dist); sMinMaxDistance[1] = std::max(sMinMaxDistance[1], dist); } void Buckets::Shutdown() { sData = nullptr; sBuckets = nullptr; sPlaneObjectData = nullptr; sPlaneObjectBucket = nullptr; } void Buckets::Init() { sData = &sDataHolder; sBuckets = &sBucketsHolder; sBuckets->resize(50); sPlaneObjectData = &sPlaneObjectDataHolder; sPlaneObjectBucket = &sPlaneObjectBucketHolder; sMinMaxDistance = skWorstMinMaxDistance; } CCubeRenderer::CAreaListItem::CAreaListItem(const std::vector* geom, const CAreaRenderOctTree* octTree, std::unique_ptr>>&& textures, std::unique_ptr>>&& models, int areaIdx) : x0_geometry(geom) , x4_octTree(octTree) , x8_textures(std::move(textures)) , x10_models(std::move(models)) , x18_areaIdx(areaIdx) {} CCubeRenderer::CCubeRenderer(IObjectStore& store, IFactory& resFac) : x8_factory(resFac), xc_store(store) { void* data = xe4_blackTex.Lock(); memset(data, 0, 32); xe4_blackTex.UnLock(); GenerateReflectionTex(); GenerateFogVolumeRampTex(); GenerateSphereRampTex(); LoadThermoPalette(); g_Renderer = this; Buckets::Init(); // GX draw sync } CCubeRenderer::~CCubeRenderer() { g_Renderer = nullptr; } void CCubeRenderer::GenerateReflectionTex() {} void CCubeRenderer::GenerateFogVolumeRampTex() {} void CCubeRenderer::GenerateSphereRampTex() {} void CCubeRenderer::LoadThermoPalette() {} void CCubeRenderer::ReallyDrawPhazonSuitIndirectEffect(const zeus::CColor& vertColor, const CTexture& maskTex, const CTexture& indTex, const zeus::CColor& modColor, float scale, float offX, float offY) {} void CCubeRenderer::ReallyDrawPhazonSuitEffect(const zeus::CColor& modColor, const CTexture& maskTex) {} void CCubeRenderer::DoPhazonSuitIndirectAlphaBlur(float blurRadius, float f2, const TLockedToken& indTex) {} void CCubeRenderer::AddWorldSurfaces(CCubeModel& model) { for (auto* it = model.GetFirstSortedSurface(); it != nullptr; it = it->GetNextSurface()) { auto mat = model.GetMaterialByIndex(it->GetMaterialIndex()); auto blend = mat.GetCompressedBlend(); auto bounds = it->GetBounds(); auto pos = bounds.closestPointAlongVector(xb0_viewPlane.normal()); Buckets::Insert(pos, bounds, EDrawableType::WorldSurface, it, xb0_viewPlane, static_cast(blend == 0x50004)); } } void CCubeRenderer::AddStaticGeometry(const std::vector* geometry, const CAreaRenderOctTree* octTree, int areaIdx) { auto search = FindStaticGeometry(geometry); if (search == x1c_areaListItems.end()) { auto textures = std::make_unique>>(); auto models = std::make_unique>>(); if (!geometry->empty()) { CCubeModel::MakeTexturesFromMats((*geometry)[0].GetMaterialPointer(), *textures.get(), &xc_store, false); models->reserve(geometry->size()); int instIdx = 0; for (const CMetroidModelInstance& inst : *geometry) { models->emplace_back( std::make_unique(const_cast*>(inst.GetSurfaces()), textures.get(), const_cast(inst.GetMaterialPointer()), const_cast*>(inst.GetVertexPointer()), const_cast*>(inst.GetColorPointer()), const_cast*>(inst.GetNormalPointer()), const_cast*>(inst.GetTCPointer()), const_cast*>(inst.GetPackedTCPointer()), inst.GetBoundingBox(), inst.GetFlags(), false, instIdx)); ++instIdx; } } x1c_areaListItems.emplace_back(geometry, octTree, std::move(textures), std::move(models), areaIdx); } } void CCubeRenderer::EnablePVS(const CPVSVisSet& set, u32 areaIdx) { if (!xdc_) { xc8_pvs.emplace(set); xdc_ = true; } else { xc8_pvs.emplace(set); } xe0_pvsAreaIdx = areaIdx; } void CCubeRenderer::DisablePVS() { xc8_pvs.reset(); } void CCubeRenderer::RemoveStaticGeometry(const std::vector* geometry) {} void CCubeRenderer::DrawUnsortedGeometry(int areaIdx, int mask, int targetMask, bool shadowRender) {} void CCubeRenderer::DrawSortedGeometry(int areaIdx, int mask, int targetMask) { SetupRendererStates(true); const CAreaListItem* item = nullptr; for (const auto& areaListItem : x1c_areaListItems) { if (areaIdx == -1 || areaIdx == areaListItem.x18_areaIdx) { if (areaListItem.x4_octTree != nullptr) { item = &areaListItem; } for (const auto& model : *areaListItem.x10_models) { if (model->IsVisible()) { AddWorldSurfaces(*model); } } } } Buckets::Sort(); RenderBucketItems(item); SetupCGraphicsState(); DrawRenderBucketsDebug(); Buckets::Clear(); } void CCubeRenderer::DrawStaticGeometry(int areaIdx, int mask, int targetMask) {} void CCubeRenderer::DrawAreaGeometry(int areaIdx, int mask, int targetMask) {} void CCubeRenderer::RenderBucketItems(const CAreaListItem* lights) {} void CCubeRenderer::PostRenderFogs() {} void CCubeRenderer::SetModelMatrix(const zeus::CTransform& xf) { CGraphics::SetModelMatrix(xf); } void CCubeRenderer::AddParticleGen(CParticleGen& gen) { auto bounds = gen.GetBounds(); if (bounds) { auto closestPoint = bounds->closestPointAlongVector(xb0_viewPlane.normal()); Buckets::Insert(closestPoint, *bounds, EDrawableType::Particle, reinterpret_cast(&gen), xb0_viewPlane, 0); } } void CCubeRenderer::AddParticleGen(CParticleGen& gen, const zeus::CVector3f& pos, const zeus::CAABox& bounds) { Buckets::Insert(pos, bounds, EDrawableType::Particle, reinterpret_cast(&gen), xb0_viewPlane, 0); } void CCubeRenderer::AddPlaneObject(void* obj, const zeus::CAABox& aabb, const zeus::CPlane& plane, int type) { auto closestPoint = aabb.closestPointAlongVector(xb0_viewPlane.normal()); auto closestDist = xb0_viewPlane.pointToPlaneDist(closestPoint); auto furthestPoint = aabb.furthestPointAlongVector(xb0_viewPlane.normal()); auto furthestDist = xb0_viewPlane.pointToPlaneDist(furthestPoint); if (closestDist >= 0.f || furthestDist >= 0.f) { bool zOnly = false; if (plane.normal() == zeus::skUp) { zOnly = true; } bool invertTest = false; if (zOnly) { invertTest = CGraphics::g_GXModelView.origin.z() >= plane.d(); } else if (plane.pointToPlaneDist(CGraphics::g_GXModelView.origin) < 0.f) { invertTest = false; } else { invertTest = true; } Buckets::InsertPlaneObject(closestDist, furthestDist, aabb, invertTest, plane, zOnly, EDrawableType(type + 2), obj); } } void CCubeRenderer::AddDrawable(void* obj, const zeus::CVector3f& pos, const zeus::CAABox& aabb, int mode, IRenderer::EDrawableSorting sorting) { if (sorting == EDrawableSorting::UnsortedCallback) { xa8_drawableCallback(obj, xac_drawableCallbackUserData, mode); } else { Buckets::Insert(pos, aabb, EDrawableType(mode + 2), obj, xb0_viewPlane, 0); } } void CCubeRenderer::SetDrawableCallback(IRenderer::TDrawableCallback cb, void* ctx) { xa8_drawableCallback = cb; xac_drawableCallbackUserData = ctx; } void CCubeRenderer::SetWorldViewpoint(const zeus::CTransform& xf) { CGraphics::SetViewPointMatrix(xf); auto front = xf.frontVector(); xb0_viewPlane = zeus::CPlane(front, front.dot(xf.origin)); } void CCubeRenderer::SetPerspective(float fovy, float aspect, float znear, float zfar) { CGraphics::SetPerspective(fovy, aspect, znear, zfar); } void CCubeRenderer::SetPerspective(float fovy, float width, float height, float znear, float zfar) { CGraphics::SetPerspective(fovy, width / height, znear, zfar); } std::pair CCubeRenderer::SetViewportOrtho(bool centered, float znear, float zfar) { auto left = static_cast(centered ? CGraphics::GetViewportLeft() - CGraphics::GetViewportWidth() / 2 : CGraphics::GetViewportLeft()); auto top = static_cast(centered ? CGraphics::GetViewportTop() - CGraphics::GetViewportHeight() / 2 : CGraphics::GetViewportHeight()); auto right = static_cast(CGraphics::GetViewportLeft() + (centered ? CGraphics::GetViewportWidth() / 2 : CGraphics::GetViewportWidth())); auto bottom = static_cast(CGraphics::GetViewportTop() + (centered ? CGraphics::GetViewportHeight() / 2 : CGraphics::GetViewportHeight())); CGraphics::SetOrtho(left, right, top, bottom, znear, zfar); CGraphics::SetViewPointMatrix({}); CGraphics::SetModelMatrix({}); return {{left, top}, {right, bottom}}; } void CCubeRenderer::SetClippingPlanes(const zeus::CFrustum& frustum) { x44_frustumPlanes = frustum; } void CCubeRenderer::SetViewport(int left, int bottom, int width, int height) { CGraphics::SetViewport(left, bottom, width, height); CGraphics::SetScissor(left, bottom, width, height); } void CCubeRenderer::BeginScene() { CGraphics::SetUseVideoFilter(true); CGraphics::SetViewport(0, 0, CGraphics::g_Viewport.x8_width, CGraphics::g_Viewport.xc_height); CGraphics::SetClearColor(zeus::skClear); CGraphics::SetCullMode(ERglCullMode::Front); CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true); CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha, ERglLogicOp::Clear); CGraphics::SetPerspective(75.f, CGraphics::g_Viewport.x8_width / CGraphics::g_Viewport.xc_height, 1.f, 4096.f); CGraphics::SetModelMatrix(zeus::CTransform()); if (x310_phazonSuitMaskCountdown != 0) { --x310_phazonSuitMaskCountdown; if (x310_phazonSuitMaskCountdown == 0) { x314_phazonSuitMask.reset(); } } if (!x318_31_persistRGBA6) { x318_26_requestRGBA6 = false; } // GXSetPixelFmt(x318_26_requestRGBA6, GX_ZC_LINEAR); aurora::gfx::set_alpha_update(true); aurora::gfx::set_dst_alpha(true, 0.f); CGraphics::BeginScene(); } void CCubeRenderer::EndScene() { x318_31_persistRGBA6 = !CGraphics::g_IsBeginSceneClearFb; CGraphics::EndScene(); if (x2dc_reflectionAge < 2) { ++x2dc_reflectionAge; } else { x14c_reflectionTex.reset(); }; } void CCubeRenderer::SetDebugOption(IRenderer::EDebugOption option, int value) { if (option == EDebugOption::PVSState) { xc8_pvs->SetState(EPVSVisSetState(value)); } else if (option == EDebugOption::PVSMode) { xc0_pvsMode = EPVSMode(value); } else if (option == EDebugOption::FogDisabled) { x318_28_disableFog = true; } } void CCubeRenderer::BeginPrimitive(IRenderer::EPrimitiveType type, s32 nverts) { x18_primVertCount = nverts; CGraphics::StreamBegin(GX::Primitive(type)); } void CCubeRenderer::BeginLines(s32 nverts) { BeginPrimitive(EPrimitiveType::Lines, nverts); } void CCubeRenderer::BeginLineStrip(s32 nverts) { BeginPrimitive(EPrimitiveType::LineStrip, nverts); } void CCubeRenderer::BeginTriangles(s32 nverts) { BeginPrimitive(EPrimitiveType::Triangles, nverts); } void CCubeRenderer::BeginTriangleStrip(s32 nverts) { BeginPrimitive(EPrimitiveType::TriangleStrip, nverts); } void CCubeRenderer::BeginTriangleFan(s32 nverts) { BeginPrimitive(EPrimitiveType::TriangleFan, nverts); } void CCubeRenderer::PrimVertex(const zeus::CVector3f& vertex) { --x18_primVertCount; CGraphics::StreamColor(x2e0_primColor); CGraphics::StreamNormal(x2e4_primNormal); CGraphics::StreamVertex(vertex); } void CCubeRenderer::PrimNormal(const zeus::CVector3f& normal) { x2e4_primNormal = normal; } void CCubeRenderer::PrimColor(float r, float g, float b, float a) { PrimColor({r, g, b, a}); } void CCubeRenderer::PrimColor(const zeus::CColor& color) { x2e0_primColor = color; } void CCubeRenderer::EndPrimitive() { while (x18_primVertCount > 0) { PrimVertex(zeus::skZero3f); } CGraphics::StreamEnd(); } void CCubeRenderer::SetAmbientColor(const zeus::CColor& color) { CGraphics::SetAmbientColor(color); } void CCubeRenderer::DrawString(const char* string, int x, int y) { x10_font.DrawString(string, x, y, zeus::skWhite); } u32 CCubeRenderer::GetFPS() { return CGraphics::GetFPS(); } void CCubeRenderer::CacheReflection(IRenderer::TReflectionCallback cb, void* ctx, bool clearAfter) {} void CCubeRenderer::DrawSpaceWarp(const zeus::CVector3f& pt, float strength) {} void CCubeRenderer::DrawThermalModel(const CModel& model, const zeus::CColor& multCol, const zeus::CColor& addCol, TVectorRef positions, TVectorRef normals, const CModelFlags& flags) {} void CCubeRenderer::DrawModelDisintegrate(const CModel& model, const CTexture& tex, const zeus::CColor& color, TVectorRef positions, TVectorRef normals) {} void CCubeRenderer::DrawModelFlat(const CModel& model, const CModelFlags& flags, bool unsortedOnly) {} void CCubeRenderer::SetWireframeFlags(int flags) {} void CCubeRenderer::SetWorldFog(ERglFogMode mode, float startz, float endz, const zeus::CColor& color) {} void CCubeRenderer::RenderFogVolume(const zeus::CColor& color, const zeus::CAABox& aabb, const TLockedToken* model, const CSkinnedModel* sModel) {} void CCubeRenderer::SetThermal(bool thermal, float level, const zeus::CColor& color) {} void CCubeRenderer::SetThermalColdScale(float scale) {} void CCubeRenderer::DoThermalBlendCold() {} void CCubeRenderer::DoThermalBlendHot() {} u32 CCubeRenderer::GetStaticWorldDataSize() { return 0; } void CCubeRenderer::SetGXRegister1Color(const zeus::CColor& color) { aurora::gfx::set_tev_reg_color(GX::TevRegID::TEVREG1, color); } void CCubeRenderer::SetWorldLightFadeLevel(float level) { x2fc_tevReg1Color = zeus::CColor(level, level, level, 1.f); } void CCubeRenderer::PrepareDynamicLights(const std::vector& lights) {} void CCubeRenderer::AllocatePhazonSuitMaskTexture() {} void CCubeRenderer::DrawPhazonSuitIndirectEffect(const zeus::CColor& nonIndirectMod, const TLockedToken& indTex, const zeus::CColor& indirectMod, float blurRadius, float scale, float offX, float offY) {} void CCubeRenderer::DrawXRayOutline(const zeus::CAABox& aabb) {} std::list::iterator CCubeRenderer::FindStaticGeometry(const std::vector* geometry) { return std::find_if(x1c_areaListItems.begin(), x1c_areaListItems.end(), [&](const CAreaListItem& item) { return item.x0_geometry == geometry; }); } void CCubeRenderer::FindOverlappingWorldModels(std::vector& modelBits, const zeus::CAABox& aabb) const {} int CCubeRenderer::DrawOverlappingWorldModelIDs(int alphaVal, const std::vector& modelBits, const zeus::CAABox& aabb) { return 0; } void CCubeRenderer::DrawOverlappingWorldModelShadows(int alphaVal, const std::vector& modelBits, const zeus::CAABox& aabb, float alpha) {} void CCubeRenderer::SetupCGraphicsState() { CGraphics::DisableAllLights(); CGraphics::SetModelMatrix({}); CTevCombiners::ResetStates(); CGraphics::SetAmbientColor({0.4f}); // CGX::SetChanMatColor(EChannelId::Channel0, GX::Color{0xFFFFFFFF}); CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true); // CGX::SetChanCtrl(EChannelId::Channel1, false, GX::SRC_REG, GX::LIGHT_NULL, GX::DF_NONE, GX::AF_NONE); CCubeMaterial::EnsureTevsDirect(); } void CCubeRenderer::SetupRendererStates(bool depthWrite) { CGraphics::DisableAllLights(); CGraphics::SetModelMatrix({}); CGraphics::SetAmbientColor(zeus::skBlack); CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, depthWrite); CCubeMaterial::ResetCachedMaterials(); aurora::gfx::set_tev_reg_color(GX::TEVREG1, x2fc_tevReg1Color); } } // namespace metaforce