#include "renderer.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include "EASTL/array.h" #include "gtemath.hh" #include "splashpack.hh" using namespace psyqo::fixed_point_literals; using namespace psyqo::trig_literals; using namespace psyqo::GTE; psxsplash::Renderer *psxsplash::Renderer::instance = nullptr; void psxsplash::Renderer::Init(psyqo::GPU &gpuInstance) { psyqo::Kernel::assert(instance == nullptr, "A second intialization of Renderer was tried"); clear(); clear(); clear(); write(psyqo::FixedPoint<16>(160.0).raw()); write(psyqo::FixedPoint<16>(120.0).raw()); write(120); write(ORDERING_TABLE_SIZE / 3); write(ORDERING_TABLE_SIZE / 4); if (!instance) { instance = new Renderer(gpuInstance); } } void psxsplash::Renderer::SetCamera(psxsplash::Camera &camera) { m_currentCamera = &camera; } void psxsplash::Renderer::Render(eastl::vector &objects) { psyqo::Kernel::assert(m_currentCamera != nullptr, "PSXSPLASH: Tried to render without an active camera"); uint8_t parity = m_gpu.getParity(); auto &ot = m_ots[parity]; auto &clear = m_clear[parity]; auto &balloc = m_ballocs[parity]; balloc.reset(); eastl::array projected; for (auto &obj : objects) { psyqo::Vec3 cameraPosition, objectPosition; psyqo::Matrix33 finalMatrix; ::clear(); ::clear(); ::clear(); // Rotate the camera Translation vector by the camera rotation writeSafe(m_currentCamera->GetRotation()); writeSafe(-m_currentCamera->GetPosition()); Kernels::mvmva(); cameraPosition = readSafe(); // Rotate the object Translation vector by the camera rotation writeSafe(obj->position); Kernels::mvmva(); objectPosition = readSafe(); objectPosition.x += cameraPosition.x; objectPosition.y += cameraPosition.y; objectPosition.z += cameraPosition.z; // Combine object and camera rotations MatrixMultiplyGTE(m_currentCamera->GetRotation(), obj->rotation, &finalMatrix); psyqo::GTE::writeSafe(objectPosition); psyqo::GTE::writeSafe(finalMatrix); for (int i = 0; i < obj->polyCount; i++) { Tri &tri = obj->polygons[i]; psyqo::Vec3 result; writeSafe(tri.v0); writeSafe(tri.v1); writeSafe(tri.v2); Kernels::rtpt(); Kernels::nclip(); int32_t mac0 = 0; read(reinterpret_cast(&mac0)); if (mac0 <= 0) continue; int32_t zIndex = 0; uint32_t u0, u1, u2; read(&u0); read(&u1); read(&u2); int32_t sz0 = (int32_t)u0; int32_t sz1 = (int32_t)u1; int32_t sz2 = (int32_t)u2; if ((sz0 < 1 && sz1 < 1 && sz2 < 1)) { continue; }; zIndex = eastl::max(eastl::max(sz0, sz1), sz2); if (zIndex < 0 || zIndex >= ORDERING_TABLE_SIZE) continue; read(&projected[0].packed); read(&projected[1].packed); read(&projected[2].packed); recursiveSubdivideAndRender(tri, projected, zIndex, 1); } } m_gpu.getNextClear(clear.primitive, m_clearcolor); m_gpu.chain(clear); m_gpu.chain(ot); } void psxsplash::Renderer::RenderNavmeshPreview(psxsplash::Navmesh navmesh, bool isOnMesh) { uint8_t parity = m_gpu.getParity(); eastl::array projected; auto &ot = m_ots[parity]; auto &clear = m_clear[parity]; auto &balloc = m_ballocs[parity]; balloc.reset(); psyqo::Vec3 cameraPosition; ::clear(); ::clear(); ::clear(); // Rotate the camera Translation vector by the camera rotation writeSafe(m_currentCamera->GetRotation()); writeSafe(m_currentCamera->GetPosition()); Kernels::mvmva(); cameraPosition = readSafe(); write(-cameraPosition.x.raw()); write(-cameraPosition.y.raw()); write(-cameraPosition.z.raw()); psyqo::GTE::writeSafe(m_currentCamera->GetRotation()); for (int i = 0; i < navmesh.triangleCount; i++) { NavMeshTri &tri = navmesh.polygons[i]; psyqo::Vec3 result; writeSafe(tri.v0); writeSafe(tri.v1); writeSafe(tri.v2); Kernels::rtpt(); Kernels::nclip(); int32_t mac0 = 0; read(reinterpret_cast(&mac0)); if (mac0 <= 0) continue; int32_t zIndex = 0; uint32_t u0, u1, u2; read(&u0); read(&u1); read(&u2); int32_t sz0 = *reinterpret_cast(&u0); int32_t sz1 = *reinterpret_cast(&u1); int32_t sz2 = *reinterpret_cast(&u2); zIndex = eastl::max(eastl::max(sz0, sz1), sz2); if (zIndex < 0 || zIndex >= ORDERING_TABLE_SIZE) continue; auto &prim = balloc.allocateFragment(); prim.primitive.pointA = projected[0]; prim.primitive.pointB = projected[1]; prim.primitive.pointC = projected[2]; psyqo::Color heightColor; if (isOnMesh) { heightColor.r = 0; heightColor.g = ((tri.v0.y.raw() + tri.v1.y.raw() + tri.v2.y.raw()) / 3) * 100 % 256; heightColor.b = 0; } else { heightColor.r = ((tri.v0.y.raw() + tri.v1.y.raw() + tri.v2.y.raw()) / 3) * 100 % 256; heightColor.g = 0; heightColor.b = 0; } prim.primitive.setColor(heightColor); prim.primitive.setOpaque(); ot.insert(prim, zIndex); } m_gpu.getNextClear(clear.primitive, m_clearcolor); m_gpu.chain(clear); m_gpu.chain(ot); } void psxsplash::Renderer::VramUpload(const uint16_t *imageData, int16_t posX, int16_t posY, int16_t width, int16_t height) { psyqo::Rect uploadRect{.a = {.x = posX, .y = posY}, .b = {width, height}}; m_gpu.uploadToVRAM(imageData, uploadRect); } psyqo::Color averageColor(const psyqo::Color &a, const psyqo::Color &b) { return psyqo::Color{static_cast((a.r + b.r) >> 1), static_cast((a.g + b.g) >> 1), static_cast((a.b + b.b) >> 1)}; } void psxsplash::Renderer::recursiveSubdivideAndRender(Tri &tri, eastl::array &projected, int zIndex, int maxIterations) { uint16_t minX = eastl::min({projected[0].x, projected[1].x, projected[2].x}); uint16_t maxX = eastl::max({projected[0].x, projected[1].x, projected[2].x}); uint16_t minY = eastl::min({projected[0].y, projected[1].y, projected[2].y}); uint16_t maxY = eastl::max({projected[0].y, projected[1].y, projected[2].y}); uint16_t width = maxX - minX; uint16_t height = maxY - minY; bool leavingScreenSpace = false; if (projected[0].x < -100 || projected[0].y < -100 || projected[1].x < -100 || projected[1].y < -100 || projected[2].x < -100 || projected[2].y < -100 || width > 420 || height > 356) { leavingScreenSpace = true; } if (maxIterations == 0 || ((width < 512 && height < 256 && !leavingScreenSpace))) { auto &balloc = m_ballocs[m_gpu.getParity()]; auto &prim = balloc.allocateFragment(); prim.primitive.pointA = projected[0]; prim.primitive.pointB = projected[1]; prim.primitive.pointC = projected[2]; prim.primitive.uvA = tri.uvA; prim.primitive.uvB = tri.uvB; prim.primitive.uvC = tri.uvC; // uvC remains UVCoordsPadded. prim.primitive.tpage = tri.tpage; psyqo::PrimPieces::ClutIndex clut(tri.clutX, tri.clutY); prim.primitive.clutIndex = clut; prim.primitive.setColorA(tri.colorA); prim.primitive.setColorB(tri.colorB); prim.primitive.setColorC(tri.colorC); prim.primitive.setOpaque(); m_ots[m_gpu.getParity()].insert(prim, zIndex); return; } // Subdivide the triangle auto distanceSq = [](const psyqo::Vertex &a, const psyqo::Vertex &b) -> uint32_t { int dx = a.x - b.x; int dy = a.y - b.y; return dx * dx + dy * dy; }; uint32_t d0 = distanceSq(projected[0], projected[1]); uint32_t d1 = distanceSq(projected[1], projected[2]); uint32_t d2 = distanceSq(projected[2], projected[0]); int i, j, k; if (d0 >= d1 && d0 >= d2) { i = 0; j = 1; k = 2; } else if (d1 >= d0 && d1 >= d2) { i = 1; j = 2; k = 0; } else { i = 2; j = 0; k = 1; } auto getUVu = [&](int idx) -> uint8_t { if (idx == 0) return tri.uvA.u; if (idx == 1) return tri.uvB.u; return tri.uvC.u; }; auto getUVv = [&](int idx) -> uint8_t { if (idx == 0) return tri.uvA.v; if (idx == 1) return tri.uvB.v; return tri.uvC.v; }; auto getColor = [&](int idx) -> psyqo::Color { if (idx == 0) return tri.colorA; if (idx == 1) return tri.colorB; return tri.colorC; }; psyqo::Vertex mid; mid.x = (projected[i].x + projected[j].x) >> 1; mid.y = (projected[i].y + projected[j].y) >> 1; uint8_t newU = (getUVu(i) + getUVu(j)) / 2; uint8_t newV = (getUVv(i) + getUVv(j)) / 2; psyqo::Color newColor = averageColor(getColor(i), getColor(j)); eastl::array projA, projB; projA[0] = projected[i]; projA[1] = mid; projA[2] = projected[k]; projB[0] = mid; projB[1] = projected[j]; projB[2] = projected[k]; Tri triA, triB; triA.uvA = {getUVu(i), getUVv(i)}; triA.uvB = {newU, newV}; triA.uvC = {getUVu(k), getUVv(k)}; triA.colorA = getColor(i); triA.colorB = newColor; triA.colorC = getColor(k); /*triA.colorA = {.r = 255}; triA.colorB = {.r = 255}; triA.colorC = {.r = 255};*/ triA.tpage = tri.tpage; triA.clutX = tri.clutX; triA.clutY = tri.clutY; triA.normal = tri.normal; triB.uvA = {newU, newV}; triB.uvB = {getUVu(j), getUVv(j)}; triB.uvC = {getUVu(k), getUVv(k)}; triB.colorA = newColor; triB.colorB = getColor(j); triB.colorC = getColor(k); /*triB.colorA = {.g = 255}; triB.colorB = {.g = 255}; triB.colorC = {.g = 255};*/ triB.tpage = tri.tpage; triB.clutX = tri.clutX; triB.clutY = tri.clutY; triB.normal = tri.normal; recursiveSubdivideAndRender(triA, projA, zIndex, maxIterations - 1); recursiveSubdivideAndRender(triB, projB, zIndex, maxIterations - 1); }