cleanup
This commit is contained in:
@@ -1 +0,0 @@
|
||||
ˆ¨platform¦native§release¯pcsx-redux@head«environmentªproduction¥level¥error£sdk„¤namesentry.native§version¥0.6.1¨packages‘‚¤name¾github:getsentry/sentry-native§version¥0.6.1¬integrations‘¨crashpad¤tags€¥extra€¨contexts<EFBFBD>¢os„¤name§Windows®kernel_version¯10.0.26100.8036§versionª10.0.26200¥build¤8037
|
||||
Binary file not shown.
1
Makefile
1
Makefile
@@ -13,7 +13,6 @@ src/triclip.cpp \
|
||||
src/lua.cpp \
|
||||
src/luaapi.cpp \
|
||||
src/scenemanager.cpp \
|
||||
src/sceneloader.cpp \
|
||||
src/fileloader.cpp \
|
||||
src/audiomanager.cpp \
|
||||
src/controls.cpp \
|
||||
|
||||
BIN
build_output.txt
BIN
build_output.txt
Binary file not shown.
BIN
output.bin
BIN
output.bin
Binary file not shown.
50
src/bvh.cpp
50
src/bvh.cpp
@@ -81,32 +81,32 @@ int BVHManager::traverseRegion(int nodeIndex, int32_t qMinX, int32_t qMinY,
|
||||
|
||||
if (!aabbOverlap(node, qMinX, qMinY, qMinZ, qMaxX, qMaxY, qMaxZ)) {
|
||||
return currentCount;
|
||||
|
||||
if (node.isLeaf()) {
|
||||
int count = node.triangleCount;
|
||||
int available = maxRefs - currentCount;
|
||||
if (count > available)
|
||||
count = available;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
outRefs[currentCount + i] = m_triangleRefs[node.firstTriangle + i];
|
||||
}
|
||||
return currentCount + count;
|
||||
}
|
||||
|
||||
if (node.leftChild != 0xFFFF) {
|
||||
currentCount =
|
||||
traverseRegion(node.leftChild, qMinX, qMinY, qMinZ, qMaxX, qMaxY,
|
||||
qMaxZ, outRefs, currentCount, maxRefs);
|
||||
}
|
||||
if (node.rightChild != 0xFFFF) {
|
||||
currentCount =
|
||||
traverseRegion(node.rightChild, qMinX, qMinY, qMinZ, qMaxX, qMaxY,
|
||||
qMaxZ, outRefs, currentCount, maxRefs);
|
||||
}
|
||||
|
||||
return currentCount;
|
||||
}
|
||||
|
||||
if (node.isLeaf()) {
|
||||
int count = node.triangleCount;
|
||||
int available = maxRefs - currentCount;
|
||||
if (count > available)
|
||||
count = available;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
outRefs[currentCount + i] = m_triangleRefs[node.firstTriangle + i];
|
||||
}
|
||||
return currentCount + count;
|
||||
}
|
||||
|
||||
if (node.leftChild != 0xFFFF) {
|
||||
currentCount =
|
||||
traverseRegion(node.leftChild, qMinX, qMinY, qMinZ, qMaxX, qMaxY,
|
||||
qMaxZ, outRefs, currentCount, maxRefs);
|
||||
}
|
||||
if (node.rightChild != 0xFFFF) {
|
||||
currentCount =
|
||||
traverseRegion(node.rightChild, qMinX, qMinY, qMinZ, qMaxX, qMaxY,
|
||||
qMaxZ, outRefs, currentCount, maxRefs);
|
||||
}
|
||||
|
||||
return currentCount;
|
||||
}
|
||||
|
||||
bool BVHManager::aabbOverlap(const BVHNode &node, int32_t qMinX, int32_t qMinY,
|
||||
|
||||
@@ -28,12 +28,6 @@ struct BVHNode {
|
||||
return leftChild == 0xFFFF && rightChild == 0xFFFF;
|
||||
}
|
||||
|
||||
bool containsPoint(const psyqo::Vec3& point) const {
|
||||
return point.x.raw() >= minX && point.x.raw() <= maxX &&
|
||||
point.y.raw() >= minY && point.y.raw() <= maxY &&
|
||||
point.z.raw() >= minZ && point.z.raw() <= maxZ;
|
||||
}
|
||||
|
||||
bool testPlane(int32_t nx, int32_t ny, int32_t nz, int32_t d) const {
|
||||
int32_t px = (nx >= 0) ? maxX : minX;
|
||||
int32_t py = (ny >= 0) ? maxY : minY;
|
||||
|
||||
@@ -14,7 +14,7 @@ class Camera {
|
||||
|
||||
void MoveX(psyqo::FixedPoint<12> x);
|
||||
void MoveY(psyqo::FixedPoint<12> y);
|
||||
void MoveZ(psyqo::FixedPoint<12> y);
|
||||
void MoveZ(psyqo::FixedPoint<12> z);
|
||||
|
||||
void SetPosition(psyqo::FixedPoint<12> x, psyqo::FixedPoint<12> y, psyqo::FixedPoint<12> z);
|
||||
psyqo::Vec3& GetPosition() { return m_position; }
|
||||
|
||||
@@ -55,12 +55,6 @@ void SpatialGrid::worldToGrid(const psyqo::Vec3& pos, int& gx, int& gy, int& gz)
|
||||
if (gz >= GRID_SIZE) gz = GRID_SIZE - 1;
|
||||
}
|
||||
|
||||
int SpatialGrid::getCellIndex(const psyqo::Vec3& pos) const {
|
||||
int gx, gy, gz;
|
||||
worldToGrid(pos, gx, gy, gz);
|
||||
return gx + gy * GRID_SIZE + gz * GRID_SIZE * GRID_SIZE;
|
||||
}
|
||||
|
||||
void SpatialGrid::insert(uint16_t objectIndex, const AABB& bounds) {
|
||||
int minGx, minGy, minGz;
|
||||
int maxGx, maxGy, maxGz;
|
||||
@@ -308,98 +302,4 @@ bool CollisionSystem::testAABB(const AABB& a, const AABB& b,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CollisionSystem::areColliding(uint16_t indexA, uint16_t indexB) const {
|
||||
for (int i = 0; i < m_resultCount; i++) {
|
||||
if ((m_results[i].objectA == indexA && m_results[i].objectB == indexB) ||
|
||||
(m_results[i].objectA == indexB && m_results[i].objectB == indexA)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CollisionSystem::raycast(const psyqo::Vec3& origin, const psyqo::Vec3& direction,
|
||||
psyqo::FixedPoint<12> maxDistance,
|
||||
psyqo::Vec3& hitPoint, psyqo::Vec3& hitNormal,
|
||||
uint16_t& hitObjectIndex) const {
|
||||
auto closestT = maxDistance;
|
||||
bool hit = false;
|
||||
|
||||
const FP zero(0);
|
||||
const FP one(1);
|
||||
const FP negOne(-1);
|
||||
const FP largeVal(1000);
|
||||
const FP negLargeVal(-1000);
|
||||
FP epsilon;
|
||||
epsilon.value = 4;
|
||||
|
||||
for (int i = 0; i < m_colliderCount; i++) {
|
||||
const CollisionData& collider = m_colliders[i];
|
||||
if (collider.type == CollisionType::None) continue;
|
||||
|
||||
const AABB& box = collider.bounds;
|
||||
|
||||
auto tMin = negLargeVal;
|
||||
auto tMax = largeVal;
|
||||
|
||||
if (direction.x != zero) {
|
||||
auto invD = one / direction.x;
|
||||
auto t1 = (box.min.x - origin.x) * invD;
|
||||
auto t2 = (box.max.x - origin.x) * invD;
|
||||
if (t1 > t2) { auto tmp = t1; t1 = t2; t2 = tmp; }
|
||||
if (t1 > tMin) tMin = t1;
|
||||
if (t2 < tMax) tMax = t2;
|
||||
} else if (origin.x < box.min.x || origin.x > box.max.x) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (direction.y != zero) {
|
||||
auto invD = one / direction.y;
|
||||
auto t1 = (box.min.y - origin.y) * invD;
|
||||
auto t2 = (box.max.y - origin.y) * invD;
|
||||
if (t1 > t2) { auto tmp = t1; t1 = t2; t2 = tmp; }
|
||||
if (t1 > tMin) tMin = t1;
|
||||
if (t2 < tMax) tMax = t2;
|
||||
} else if (origin.y < box.min.y || origin.y > box.max.y) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (direction.z != zero) {
|
||||
auto invD = one / direction.z;
|
||||
auto t1 = (box.min.z - origin.z) * invD;
|
||||
auto t2 = (box.max.z - origin.z) * invD;
|
||||
if (t1 > t2) { auto tmp = t1; t1 = t2; t2 = tmp; }
|
||||
if (t1 > tMin) tMin = t1;
|
||||
if (t2 < tMax) tMax = t2;
|
||||
} else if (origin.z < box.min.z || origin.z > box.max.z) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tMin > tMax || tMax < zero) continue;
|
||||
|
||||
auto t = (tMin >= zero) ? tMin : tMax;
|
||||
|
||||
if (t < closestT && t >= zero) {
|
||||
closestT = t;
|
||||
hitObjectIndex = collider.gameObjectIndex;
|
||||
hit = true;
|
||||
|
||||
hitPoint = psyqo::Vec3{
|
||||
origin.x + direction.x * t,
|
||||
origin.y + direction.y * t,
|
||||
origin.z + direction.z * t
|
||||
};
|
||||
|
||||
if ((hitPoint.x - box.min.x).abs() < epsilon) hitNormal = psyqo::Vec3{negOne, zero, zero};
|
||||
else if ((hitPoint.x - box.max.x).abs() < epsilon) hitNormal = psyqo::Vec3{one, zero, zero};
|
||||
else if ((hitPoint.y - box.min.y).abs() < epsilon) hitNormal = psyqo::Vec3{zero, negOne, zero};
|
||||
else if ((hitPoint.y - box.max.y).abs() < epsilon) hitNormal = psyqo::Vec3{zero, one, zero};
|
||||
else if ((hitPoint.z - box.min.z).abs() < epsilon) hitNormal = psyqo::Vec3{zero, zero, negOne};
|
||||
else hitNormal = psyqo::Vec3{zero, zero, one};
|
||||
}
|
||||
}
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
} // namespace psxsplash
|
||||
|
||||
@@ -33,22 +33,6 @@ struct AABB {
|
||||
(point.z >= min.z && point.z <= max.z);
|
||||
}
|
||||
|
||||
psyqo::Vec3 center() const {
|
||||
return psyqo::Vec3{
|
||||
(min.x + max.x) / 2,
|
||||
(min.y + max.y) / 2,
|
||||
(min.z + max.z) / 2
|
||||
};
|
||||
}
|
||||
|
||||
psyqo::Vec3 halfExtents() const {
|
||||
return psyqo::Vec3{
|
||||
(max.x - min.x) / 2,
|
||||
(max.y - min.y) / 2,
|
||||
(max.z - min.z) / 2
|
||||
};
|
||||
}
|
||||
|
||||
void expand(const psyqo::Vec3& delta);
|
||||
};
|
||||
static_assert(sizeof(AABB) == 24);
|
||||
@@ -101,7 +85,6 @@ public:
|
||||
void clear();
|
||||
void insert(uint16_t objectIndex, const AABB& bounds);
|
||||
int queryAABB(const AABB& bounds, uint16_t* output, int maxResults) const;
|
||||
int getCellIndex(const psyqo::Vec3& pos) const;
|
||||
|
||||
private:
|
||||
Cell m_cells[CELL_COUNT];
|
||||
@@ -134,13 +117,6 @@ public:
|
||||
const CollisionResult* getResults() const { return m_results; }
|
||||
int getResultCount() const { return m_resultCount; }
|
||||
|
||||
bool areColliding(uint16_t indexA, uint16_t indexB) const;
|
||||
|
||||
bool raycast(const psyqo::Vec3& origin, const psyqo::Vec3& direction,
|
||||
psyqo::FixedPoint<12> maxDistance,
|
||||
psyqo::Vec3& hitPoint, psyqo::Vec3& hitNormal,
|
||||
uint16_t& hitObjectIndex) const;
|
||||
|
||||
int getColliderCount() const { return m_colliderCount; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -12,7 +12,7 @@ using namespace psyqo::trig_literals;
|
||||
|
||||
class Controls {
|
||||
public:
|
||||
/// Force DualShock into analog mode (LED=Red) via raw SIO commands.
|
||||
/// Force DualShock into analog mode
|
||||
/// Must be called BEFORE Init() since Init() hands SIO control to AdvancedPad.
|
||||
void forceAnalogMode();
|
||||
|
||||
@@ -87,4 +87,4 @@ class Controls {
|
||||
void getDpadAxes(int16_t &outX, int16_t &outY) const;
|
||||
};
|
||||
|
||||
}; // namespace psxsplash
|
||||
} // namespace psxsplash
|
||||
@@ -3,18 +3,11 @@
|
||||
#include <psyqo/fixed-point.hh>
|
||||
#include <psyqo/soft-math.hh>
|
||||
#include <psyqo/trigonometry.hh>
|
||||
#include "streq.hh"
|
||||
#include "uisystem.hh"
|
||||
|
||||
namespace psxsplash {
|
||||
|
||||
// Bare-metal string compare (avoids linking libc)
|
||||
static bool cs_streq(const char* a, const char* b) {
|
||||
while (*a && *b) {
|
||||
if (*a++ != *b++) return false;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
void CutscenePlayer::init(Cutscene* cutscenes, int count, Camera* camera, AudioManager* audio,
|
||||
UISystem* uiSystem) {
|
||||
m_cutscenes = cutscenes;
|
||||
@@ -31,7 +24,7 @@ bool CutscenePlayer::play(const char* name) {
|
||||
if (!name || !m_cutscenes) return false;
|
||||
|
||||
for (int i = 0; i < m_count; i++) {
|
||||
if (m_cutscenes[i].name && cs_streq(m_cutscenes[i].name, name)) {
|
||||
if (m_cutscenes[i].name && streq(m_cutscenes[i].name, name)) {
|
||||
m_active = &m_cutscenes[i];
|
||||
m_frame = 0;
|
||||
m_nextAudio = 0;
|
||||
@@ -64,7 +57,7 @@ bool CutscenePlayer::play(const char* name) {
|
||||
}
|
||||
break;
|
||||
case TrackType::ObjectRotationY:
|
||||
// Can't easily recover angle from matrix — default to 0
|
||||
// TODO: I added this. Then realized that it only stores rotation matrix. That sucks. To anyone who finds this Pull requests are open :P
|
||||
break;
|
||||
case TrackType::ObjectActive:
|
||||
if (track.target) {
|
||||
@@ -119,12 +112,10 @@ void CutscenePlayer::stop() {
|
||||
void CutscenePlayer::tick() {
|
||||
if (!m_active) return;
|
||||
|
||||
// Apply all tracks at the current frame
|
||||
for (uint8_t i = 0; i < m_active->trackCount; i++) {
|
||||
applyTrack(m_active->tracks[i]);
|
||||
}
|
||||
|
||||
// Fire audio events whose frame has been reached
|
||||
while (m_nextAudio < m_active->audioEventCount) {
|
||||
CutsceneAudioEvent& evt = m_active->audioEvents[m_nextAudio];
|
||||
if (evt.frame <= m_frame) {
|
||||
@@ -137,39 +128,31 @@ void CutscenePlayer::tick() {
|
||||
}
|
||||
}
|
||||
|
||||
// Advance frame
|
||||
m_frame++;
|
||||
if (m_frame > m_active->totalFrames) {
|
||||
m_active = nullptr; // Cutscene finished
|
||||
}
|
||||
}
|
||||
|
||||
// Apply easing curve to fixed-point t ∈ [0, 4096].
|
||||
static int32_t applyCurve(int32_t t, InterpMode mode) {
|
||||
switch (mode) {
|
||||
default:
|
||||
case InterpMode::Linear:
|
||||
return t;
|
||||
case InterpMode::Step:
|
||||
return 0; // snaps to 'a' value until next keyframe
|
||||
return 0;
|
||||
case InterpMode::EaseIn:
|
||||
// t² / 4096
|
||||
return (int32_t)((int64_t)t * t >> 12);
|
||||
case InterpMode::EaseOut:
|
||||
// 1 − (1−t)² = t*(2 − t) / 4096
|
||||
return (int32_t)(((int64_t)t * (8192 - t)) >> 12);
|
||||
case InterpMode::EaseInOut: {
|
||||
// Smoothstep: 3t² − 2t³ (all in 12-bit fixed point)
|
||||
int64_t t2 = (int64_t)t * t; // 24-bit
|
||||
int64_t t3 = t2 * t; // 36-bit
|
||||
int64_t t2 = (int64_t)t * t;
|
||||
int64_t t3 = t2 * t;
|
||||
return (int32_t)((3 * t2 - 2 * (t3 >> 12)) >> 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Common helper: find surrounding keyframes and compute fixed-point t (0..4096).
|
||||
// Returns false if result was clamped (no interpolation needed, out[] already set).
|
||||
// When true, t already has the easing curve applied based on the *destination* keyframe's InterpMode.
|
||||
static bool findKfPair(CutsceneKeyframe* kf, uint8_t count, uint16_t frame,
|
||||
uint8_t& a, uint8_t& b, int32_t& t, int16_t out[3]) {
|
||||
if (count == 0) {
|
||||
@@ -208,7 +191,6 @@ void CutscenePlayer::lerpKeyframes(CutsceneKeyframe* kf, uint8_t count, const in
|
||||
uint8_t a, b;
|
||||
int32_t t;
|
||||
if (!findKfPair(kf, count, m_frame, a, b, t, out)) {
|
||||
// If clamped to first keyframe and frame < first kf frame, blend from initial
|
||||
if (count > 0 && kf[0].getFrame() > 0 && m_frame < kf[0].getFrame()) {
|
||||
uint16_t span = kf[0].getFrame();
|
||||
uint32_t num = (uint32_t)m_frame << 12;
|
||||
@@ -228,8 +210,6 @@ void CutscenePlayer::lerpKeyframes(CutsceneKeyframe* kf, uint8_t count, const in
|
||||
}
|
||||
}
|
||||
|
||||
// Shortest-path angle interpolation.
|
||||
// Angles are in psyqo units where 2048 = full circle (2π).
|
||||
static constexpr int32_t ANGLE_FULL_CIRCLE = 2048;
|
||||
static constexpr int32_t ANGLE_HALF_CIRCLE = 1024;
|
||||
|
||||
@@ -237,7 +217,6 @@ void CutscenePlayer::lerpAngles(CutsceneKeyframe* kf, uint8_t count, const int16
|
||||
uint8_t a, b;
|
||||
int32_t t;
|
||||
if (!findKfPair(kf, count, m_frame, a, b, t, out)) {
|
||||
// If clamped to first keyframe and frame < first kf frame, blend from initial
|
||||
if (count > 0 && kf[0].getFrame() > 0 && m_frame < kf[0].getFrame()) {
|
||||
uint16_t span = kf[0].getFrame();
|
||||
uint32_t num = (uint32_t)m_frame << 12;
|
||||
@@ -257,9 +236,7 @@ void CutscenePlayer::lerpAngles(CutsceneKeyframe* kf, uint8_t count, const int16
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int32_t from = (int32_t)kf[a].values[i];
|
||||
int32_t to = (int32_t)kf[b].values[i];
|
||||
// Shortest-path: wrap delta into [-1024, +1024)
|
||||
int32_t delta = to - from;
|
||||
// Modulo into [-2048, +2048) then clamp to half-circle
|
||||
delta = ((delta + ANGLE_HALF_CIRCLE) % ANGLE_FULL_CIRCLE + ANGLE_FULL_CIRCLE) % ANGLE_FULL_CIRCLE - ANGLE_HALF_CIRCLE;
|
||||
out[i] = (int16_t)(from + ((delta * t) >> 12));
|
||||
}
|
||||
@@ -314,10 +291,8 @@ void CutscenePlayer::applyTrack(CutsceneTrack& track) {
|
||||
|
||||
case TrackType::ObjectActive: {
|
||||
if (!track.target) return;
|
||||
// Step interpolation: find the last keyframe at or before m_frame
|
||||
CutsceneKeyframe* kf = track.keyframes;
|
||||
uint8_t count = track.keyframeCount;
|
||||
// Use initial state if we're before the first keyframe
|
||||
int16_t activeVal = (count > 0 && m_frame < kf[0].getFrame())
|
||||
? track.initialValues[0]
|
||||
: kf[0].values[0];
|
||||
|
||||
@@ -24,22 +24,19 @@ enum class TrackType : uint8_t {
|
||||
ObjectPosition = 2,
|
||||
ObjectRotationY = 3,
|
||||
ObjectActive = 4,
|
||||
// UI track types (v13+)
|
||||
UICanvasVisible = 5, // Step: values[0] = 0/1. target unused, uiHandle = canvas index.
|
||||
UIElementVisible= 6, // Step: values[0] = 0/1. uiHandle = element handle.
|
||||
UIProgress = 7, // Linear: values[0] = 0-100. uiHandle = element handle.
|
||||
UIPosition = 8, // Linear: values[0] = x, values[1] = y. uiHandle = element handle.
|
||||
UIColor = 9, // Linear: values[0] = r, values[1] = g, values[2] = b. uiHandle = element handle.
|
||||
UICanvasVisible = 5,
|
||||
UIElementVisible= 6,
|
||||
UIProgress = 7,
|
||||
UIPosition = 8,
|
||||
UIColor = 9,
|
||||
};
|
||||
|
||||
/// Per-keyframe interpolation mode.
|
||||
/// Packed into upper 3 bits of the frame field in CutsceneKeyframe.
|
||||
enum class InterpMode : uint8_t {
|
||||
Linear = 0, // Default linear interpolation
|
||||
Step = 1, // Instant jump (no interpolation)
|
||||
EaseIn = 2, // Slow start, fast end (quadratic)
|
||||
EaseOut = 3, // Fast start, slow end (quadratic)
|
||||
EaseInOut = 4, // Smooth start and end (smoothstep)
|
||||
Linear = 0,
|
||||
Step = 1,
|
||||
EaseIn = 2,
|
||||
EaseOut = 3,
|
||||
EaseInOut = 4,
|
||||
};
|
||||
|
||||
struct CutsceneKeyframe {
|
||||
@@ -66,16 +63,9 @@ struct CutsceneTrack {
|
||||
TrackType trackType;
|
||||
uint8_t keyframeCount;
|
||||
uint8_t pad[2];
|
||||
CutsceneKeyframe* keyframes; // Points into splashpack data (resolved at load time)
|
||||
GameObject* target; // nullptr = camera track or UI track
|
||||
|
||||
/// For UI tracks: flat handle into UISystem (canvas index or element handle).
|
||||
/// Set during cutscene load by resolving canvas/element names.
|
||||
CutsceneKeyframe* keyframes;
|
||||
GameObject* target;
|
||||
int16_t uiHandle;
|
||||
|
||||
/// Initial values captured at play() time for pre-first-keyframe blending.
|
||||
/// For position tracks: fp12 x,y,z. For rotation tracks: raw angle values.
|
||||
/// For ObjectActive: values[0] = 1 (active) or 0 (inactive).
|
||||
int16_t initialValues[3];
|
||||
};
|
||||
|
||||
@@ -88,8 +78,7 @@ struct Cutscene {
|
||||
CutsceneAudioEvent* audioEvents; // Points into splashpack data
|
||||
};
|
||||
|
||||
/// Zero-allocation cutscene player. Call init() once after splashpack is loaded,
|
||||
/// then tick() once per frame from the scene loop.
|
||||
|
||||
class CutscenePlayer {
|
||||
public:
|
||||
/// Initialize with loaded cutscene data. Safe to pass nullptr/0 if no cutscenes.
|
||||
|
||||
@@ -19,9 +19,8 @@ namespace psxsplash {
|
||||
* 4. ISO9660Parser::scheduleGetDirentry — look up a file by path
|
||||
* 5. ISO9660Parser::scheduleReadRequest — read the file sectors
|
||||
*
|
||||
* The BIOS has already read SYSTEM.CNF and launched the executable by the
|
||||
* time we get here, so we don't touch SYSTEM.CNF at all.
|
||||
*/
|
||||
|
||||
class FileLoaderCDRom final : public FileLoader {
|
||||
public:
|
||||
FileLoaderCDRom() : m_isoParser(&m_cdrom) {}
|
||||
@@ -99,7 +98,7 @@ class FileLoaderCDRom final : public FileLoader {
|
||||
// ── LoadFileSyncWithProgress ───────────────────────────────
|
||||
// Reads the file in 32-sector (64 KB) chunks, calling the
|
||||
// progress callback between each chunk so the loading bar
|
||||
// animates during the (slow) CD-ROM transfer.
|
||||
// animates during the CD-ROM transfer.
|
||||
uint8_t* LoadFileSyncWithProgress(
|
||||
const char* filename, int& outSize,
|
||||
const LoadProgressInfo* progress) override
|
||||
|
||||
@@ -12,12 +12,6 @@ namespace psxsplash {
|
||||
* - Emulator mode: break instructions intercepted by PCSX-Redux
|
||||
* - Real hardware mode: SIO1 serial protocol (break handler installed
|
||||
* by pcdrv_sio1_init, then pcdrv_init detects which path to use)
|
||||
*
|
||||
* Both modes are preserved — we call pcdrv_sio1_init() first (installs
|
||||
* the SIO1 break handler + redirects printf), then pcdrv_init() which
|
||||
* checks for the PCSX-Redux emulator and falls back to SIO1 if absent.
|
||||
*
|
||||
* All tasks resolve synchronously since PCdrv I/O is blocking.
|
||||
*/
|
||||
class FileLoaderPCdrv final : public FileLoader {
|
||||
public:
|
||||
@@ -27,11 +21,9 @@ class FileLoaderPCdrv final : public FileLoader {
|
||||
// ── scheduleInit ──────────────────────────────────────────────
|
||||
psyqo::TaskQueue::Task scheduleInit() override {
|
||||
return psyqo::TaskQueue::Task([this](psyqo::TaskQueue::Task* task) {
|
||||
// Set up SIO1 break handler + printf redirect (real-hardware path).
|
||||
// On emulator this is harmless — pcsx-redux ignores SIO1 setup.
|
||||
|
||||
pcdrv_sio1_init();
|
||||
|
||||
// Detect emulator vs SIO1 and initialise the active transport.
|
||||
m_available = (pcdrv_init() == 0);
|
||||
task->resolve();
|
||||
});
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
|
||||
namespace psxsplash {
|
||||
void MatrixMultiplyGTE(const psyqo::Matrix33 &matA, const psyqo::Matrix33 &matB, psyqo::Matrix33 *result);
|
||||
};
|
||||
} // namespace psxsplash
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "loadingscreen.hh"
|
||||
#include "fileloader.hh"
|
||||
#include <psyqo/kernel.hh>
|
||||
extern "C" int ramsyscall_printf(const char*, ...);
|
||||
#include "renderer.hh"
|
||||
|
||||
#include <psyqo/primitives/rectangles.hh>
|
||||
@@ -12,23 +11,9 @@ extern "C" int ramsyscall_printf(const char*, ...);
|
||||
|
||||
namespace psxsplash {
|
||||
|
||||
// psyqo places the second framebuffer at VRAM Y=256 regardless of the
|
||||
// actual display height (240 for NTSC, 256 for PAL). All double-buffer
|
||||
// rendering must use this constant, NOT m_resH, to match the display
|
||||
// registers set by GPU::flip().
|
||||
static constexpr int16_t kBuffer1Y = 256;
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Bare-metal string helpers (no libc)
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
static void int_to_str(int val, char* buf) {
|
||||
if (val < 0) { *buf++ = '-'; val = -val; }
|
||||
char tmp[12];
|
||||
int len = 0;
|
||||
do { tmp[len++] = '0' + (val % 10); val /= 10; } while (val > 0);
|
||||
for (int i = len - 1; i >= 0; i--) *buf++ = tmp[i];
|
||||
*buf = '\0';
|
||||
}
|
||||
// This file has duplicate code from UISystem... This is terrible...
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Load
|
||||
@@ -37,22 +22,15 @@ bool LoadingScreen::load(psyqo::GPU& gpu, psyqo::Font<>& systemFont, int sceneIn
|
||||
// Build filename using the active backend's naming convention
|
||||
char filename[32];
|
||||
FileLoader::BuildLoadingFilename(sceneIndex, filename, sizeof(filename));
|
||||
ramsyscall_printf("LoadingScreen: loading '%s'\n", filename);
|
||||
|
||||
int fileSize = 0;
|
||||
uint8_t* data = FileLoader::Get().LoadFileSync(filename, fileSize);
|
||||
if (!data || fileSize < (int)sizeof(LoaderPackHeader)) {
|
||||
ramsyscall_printf("LoadingScreen: FAILED to load (data=%p size=%d)\n", data, fileSize);
|
||||
if (data) FileLoader::Get().FreeFile(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* header = reinterpret_cast<const LoaderPackHeader*>(data);
|
||||
ramsyscall_printf("LoadingScreen: magic='%c%c' ver=%d canvases=%d fonts=%d atlases=%d cluts=%d tableOff=%u\n",
|
||||
header->magic[0], header->magic[1], header->version,
|
||||
header->canvasCount, header->fontCount, header->atlasCount, header->clutCount, header->tableOffset);
|
||||
if (header->magic[0] != 'L' || header->magic[1] != 'P') {
|
||||
ramsyscall_printf("LoadingScreen: bad magic, aborting\n");
|
||||
FileLoader::Get().FreeFile(data);
|
||||
return false;
|
||||
}
|
||||
@@ -73,17 +51,12 @@ bool LoadingScreen::load(psyqo::GPU& gpu, psyqo::Font<>& systemFont, int sceneIn
|
||||
m_ui.uploadFonts(gpu);
|
||||
|
||||
// Ensure canvas 0 is visible
|
||||
ramsyscall_printf("LoadingScreen: canvasCount=%d\n", m_ui.getCanvasCount());
|
||||
if (m_ui.getCanvasCount() > 0) {
|
||||
m_ui.setCanvasVisible(0, true);
|
||||
int elemCount = m_ui.getCanvasElementCount(0);
|
||||
ramsyscall_printf("LoadingScreen: canvas 0 has %d elements\n", elemCount);
|
||||
}
|
||||
|
||||
// Find the progress bar named "loading"
|
||||
findProgressBar();
|
||||
ramsyscall_printf("LoadingScreen: hasProgressBar=%d barXY=(%d,%d) barWH=(%d,%d)\n",
|
||||
m_hasProgressBar, m_barX, m_barY, m_barW, m_barH);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -170,7 +143,7 @@ void LoadingScreen::drawImage(psyqo::GPU& gpu, int handle,
|
||||
const UIImageData* img = m_ui.getImageData(handle);
|
||||
if (!img) return;
|
||||
|
||||
// Build TPage attribute (same as UISystem::makeTPage)
|
||||
// Build TPage attribute
|
||||
psyqo::PrimPieces::TPageAttr tpage;
|
||||
tpage.setPageX(img->texpageX);
|
||||
tpage.setPageY(img->texpageY);
|
||||
@@ -372,8 +345,6 @@ void LoadingScreen::renderInitialAndFree(psyqo::GPU& gpu) {
|
||||
FileLoader::Get().FreeFile(m_data);
|
||||
m_data = nullptr;
|
||||
m_dataSize = 0;
|
||||
// m_ui now points into freed memory — don't use it again.
|
||||
// m_active remains true so updateProgress keeps working.
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
namespace psxsplash {
|
||||
|
||||
/// Loader pack header — matches the binary written by PSXLoaderPackWriter.cs.
|
||||
struct LoaderPackHeader {
|
||||
char magic[2]; // "LP"
|
||||
uint16_t version; // 2
|
||||
@@ -22,7 +21,6 @@ struct LoaderPackHeader {
|
||||
};
|
||||
static_assert(sizeof(LoaderPackHeader) == 16, "LoaderPackHeader must be 16 bytes");
|
||||
|
||||
/// Atlas entry in the loader pack (matches SPLASHPACKTextureAtlas layout).
|
||||
struct LoaderPackAtlas {
|
||||
uint32_t pixelDataOffset; // absolute offset in file
|
||||
uint16_t width, height;
|
||||
@@ -30,7 +28,6 @@ struct LoaderPackAtlas {
|
||||
};
|
||||
static_assert(sizeof(LoaderPackAtlas) == 12, "LoaderPackAtlas must be 12 bytes");
|
||||
|
||||
/// CLUT entry in the loader pack (matches SPLASHPACKClut layout).
|
||||
struct LoaderPackClut {
|
||||
uint32_t clutDataOffset; // absolute offset in file
|
||||
uint16_t clutX; // VRAM X (in 16-pixel units × 16)
|
||||
@@ -40,25 +37,7 @@ struct LoaderPackClut {
|
||||
};
|
||||
static_assert(sizeof(LoaderPackClut) == 12, "LoaderPackClut must be 12 bytes");
|
||||
|
||||
/// Loading screen controller.
|
||||
///
|
||||
/// Loads a .loading file (tiny UI-only binary), renders the screen,
|
||||
/// and provides progress updates during the main splashpack load.
|
||||
///
|
||||
/// Strategy:
|
||||
/// 1. Blank the screen immediately (user sees black, not frozen frame).
|
||||
/// 2. Load the .loading file (small, fast).
|
||||
/// 3. Upload texture atlases, CLUTs, and custom font textures to VRAM.
|
||||
/// 4. Render ALL elements to BOTH framebuffers using direct GPU commands:
|
||||
/// - Images via sendPrimitive(GouraudTexturedTriangle)
|
||||
/// - Custom font text via sendPrimitive(TPage) + sendPrimitive(Sprite)
|
||||
/// - System font text via font.print()
|
||||
/// - Boxes and progress bars via sendPrimitive(Rectangle)
|
||||
/// 5. FREE all loaded data (loader pack) so the splashpack has room.
|
||||
/// 6. During splashpack loading, call updateProgress() to redraw ONLY the
|
||||
/// progress bar (simple rectangles in both framebuffers — no VRAM needed).
|
||||
///
|
||||
/// The progress bar is found by looking for a PSXUIProgressBar element named "loading".
|
||||
|
||||
class LoadingScreen {
|
||||
public:
|
||||
/// Try to load a loader pack from a file.
|
||||
|
||||
39
src/lua.cpp
39
src/lua.cpp
@@ -92,26 +92,23 @@ static int gameobjectSetPosition(psyqo::Lua L) {
|
||||
|
||||
}
|
||||
|
||||
static int gamobjectGetActive(psyqo::Lua L) {
|
||||
static int gameobjectGetActive(psyqo::Lua L) {
|
||||
auto go = L.toUserdata<psxsplash::GameObject>(1);
|
||||
L.push(go->isActive());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int gamobjectSetActive(psyqo::Lua L) {
|
||||
static int gameobjectSetActive(psyqo::Lua L) {
|
||||
auto go = L.toUserdata<psxsplash::GameObject>(1);
|
||||
bool active = L.toBoolean(2);
|
||||
go->setActive(active);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Angle constants: psyqo::Angle is FixedPoint<10>, so 1.0_pi = raw 1024
|
||||
static constexpr lua_Number kAngleScale = 1024;
|
||||
static psyqo::Trig<> s_trig;
|
||||
|
||||
// Fast integer atan2 approximation → psyqo::Angle (pi-fraction units)
|
||||
// Uses linear approximation in first octant then folds to full circle.
|
||||
// Max error ~4° (acceptable for PS1 game objects).
|
||||
|
||||
static psyqo::Angle fastAtan2(int32_t sinVal, int32_t cosVal) {
|
||||
psyqo::Angle result;
|
||||
if (cosVal == 0 && sinVal == 0) { result.value = 0; return result; }
|
||||
@@ -121,17 +118,10 @@ static psyqo::Angle fastAtan2(int32_t sinVal, int32_t cosVal) {
|
||||
|
||||
int32_t minV = abs_s < abs_c ? abs_s : abs_c;
|
||||
int32_t maxV = abs_s > abs_c ? abs_s : abs_c;
|
||||
|
||||
// Compute angle in first octant [0, π/4 = 256 Angle units]
|
||||
// angle = (minV/maxV) * 256, using only 32-bit math.
|
||||
// Max minV for normalized sin/cos ≈ 4096, so minV * 256 ≈ 1M — fits int32.
|
||||
int32_t angle = (minV * 256) / maxV;
|
||||
|
||||
// Past 45°: use complement
|
||||
if (abs_s > abs_c) angle = 512 - angle; // π/2 - angle
|
||||
// Quadrant 2/3: cos < 0
|
||||
if (cosVal < 0) angle = 1024 - angle; // π - angle
|
||||
// Quadrant 3/4: sin < 0
|
||||
if (abs_s > abs_c) angle = 512 - angle;
|
||||
if (cosVal < 0) angle = 1024 - angle;
|
||||
if (sinVal < 0) angle = -angle;
|
||||
|
||||
result.value = angle;
|
||||
@@ -140,11 +130,9 @@ static psyqo::Angle fastAtan2(int32_t sinVal, int32_t cosVal) {
|
||||
|
||||
static int gameobjectGetRotationY(psyqo::Lua L) {
|
||||
auto go = L.toUserdata<psxsplash::GameObject>(1);
|
||||
// Y rotation matrix: vs[0].x = cos(θ), vs[0].z = sin(θ)
|
||||
int32_t sinRaw = go->rotation.vs[0].z.raw();
|
||||
int32_t cosRaw = go->rotation.vs[0].x.raw();
|
||||
psyqo::Angle angle = fastAtan2(sinRaw, cosRaw);
|
||||
// Return in pi-units: 0.5 = π/2 = 90°
|
||||
L.pushNumber(static_cast<lua_Number>(angle.value) / kAngleScale);
|
||||
return 1;
|
||||
}
|
||||
@@ -172,10 +160,10 @@ void psxsplash::Lua::Init() {
|
||||
L.push(gameobjectSetPosition);
|
||||
L.setField(-2, "set_position");
|
||||
|
||||
L.push(gamobjectGetActive);
|
||||
L.push(gameobjectGetActive);
|
||||
L.setField(-2, "get_active");
|
||||
|
||||
L.push(gamobjectSetActive);
|
||||
L.push(gameobjectSetActive);
|
||||
L.setField(-2, "set_active");
|
||||
|
||||
L.push(gameobjectGetRotationY);
|
||||
@@ -212,8 +200,7 @@ void psxsplash::Lua::Init() {
|
||||
}
|
||||
|
||||
void psxsplash::Lua::Shutdown() {
|
||||
// Close the Lua VM if it's still open.
|
||||
// Safe to call multiple times or on an already-closed VM.
|
||||
|
||||
if (m_state.getState()) {
|
||||
m_state.close();
|
||||
}
|
||||
@@ -223,9 +210,8 @@ void psxsplash::Lua::Shutdown() {
|
||||
}
|
||||
|
||||
void psxsplash::Lua::Reset() {
|
||||
// Nuclear reset: destroy the entire Lua VM and create a fresh one.
|
||||
Shutdown();
|
||||
m_state = psyqo::Lua(); // fresh state (luaL_newstate + openlibs)
|
||||
m_state = psyqo::Lua();
|
||||
Init();
|
||||
}
|
||||
|
||||
@@ -298,8 +284,6 @@ void psxsplash::Lua::RegisterSceneScripts(int index) {
|
||||
// empty stack
|
||||
}
|
||||
|
||||
// We're going to store the Lua table for the object at the address of the object,
|
||||
// and the table for its methods at the address of the object + 1 byte.
|
||||
void psxsplash::Lua::RegisterGameObject(GameObject* go) {
|
||||
uint8_t* ptr = reinterpret_cast<uint8_t*>(go);
|
||||
auto L = m_state;
|
||||
@@ -431,7 +415,6 @@ void psxsplash::Lua::OnTriggerExitScript(int luaFileIndex, int triggerIndex) {
|
||||
void psxsplash::Lua::OnDestroy(GameObject* go) {
|
||||
if (!hasEvent(go, EVENT_ON_DESTROY)) return;
|
||||
onDestroyMethodWrapper.callMethod(*this, go);
|
||||
// Clear the event mask when object is destroyed
|
||||
go->eventMask = EVENT_NONE;
|
||||
}
|
||||
|
||||
@@ -463,8 +446,6 @@ void psxsplash::Lua::OnUpdate(GameObject* go, int deltaFrames) {
|
||||
void psxsplash::Lua::RelocateGameObjects(GameObject** objects, size_t count, intptr_t delta) {
|
||||
auto L = m_state;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
// objects[i] is already the NEW pointer (relocated by shrinkBuffer).
|
||||
// Compute the OLD pointer by subtracting delta.
|
||||
uint8_t* newPtr = reinterpret_cast<uint8_t*>(objects[i]);
|
||||
uint8_t* oldPtr = newPtr - delta;
|
||||
|
||||
@@ -507,7 +488,7 @@ void psxsplash::Lua::PushGameObject(GameObject* go) {
|
||||
L.rawGet(LUA_REGISTRYINDEX);
|
||||
|
||||
if (!L.isTable(-1)) {
|
||||
printf("Warning: GameObject not found in Lua registry\n");
|
||||
L.pop();
|
||||
L.push(); // push nil so the caller always gets a value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ class Lua {
|
||||
[[no_unique_address]] FunctionWrapper<1, typestring_is("onSceneCreationStart")> onSceneCreationStartFunctionWrapper;
|
||||
[[no_unique_address]] FunctionWrapper<2, typestring_is("onSceneCreationEnd")> onSceneCreationEndFunctionWrapper;
|
||||
|
||||
// Object-level events (methodId 100+, offset to avoid collision with scene events)
|
||||
// Object-level events
|
||||
[[no_unique_address]] FunctionWrapper<100, typestring_is("onCreate")> onCreateMethodWrapper;
|
||||
[[no_unique_address]] FunctionWrapper<101, typestring_is("onCollideWithPlayer")> onCollideWithPlayerMethodWrapper;
|
||||
[[no_unique_address]] FunctionWrapper<102, typestring_is("onInteract")> onInteractMethodWrapper;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <psyqo/soft-math.hh>
|
||||
#include <psyqo/trigonometry.hh>
|
||||
#include <psyqo/fixed-point.hh>
|
||||
#include <psyqo/xprintf.h>
|
||||
|
||||
|
||||
namespace psxsplash {
|
||||
|
||||
@@ -241,7 +241,7 @@ void LuaAPI::RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cut
|
||||
L.push(Math_Max);
|
||||
L.setField(-2, "Max");
|
||||
|
||||
L.setGlobal("Math");
|
||||
L.setGlobal("PSXMath");
|
||||
|
||||
// ========================================================================
|
||||
// SCENE API
|
||||
@@ -615,7 +615,8 @@ int LuaAPI::Entity_ForEach(lua_State* L) {
|
||||
lua.pop(2); // pop non-table + callback copy
|
||||
continue;
|
||||
}
|
||||
if (lua.pcall(1, 0) != LUA_OK) {
|
||||
lua.pushNumber(i); // push index as second argument
|
||||
if (lua.pcall(2, 0) != LUA_OK) {
|
||||
lua.pop(); // pop error message
|
||||
}
|
||||
}
|
||||
@@ -1179,28 +1180,27 @@ int LuaAPI::Camera_LookAt(lua_State* L) {
|
||||
// ============================================================================
|
||||
|
||||
int LuaAPI::Audio_Play(lua_State* L) {
|
||||
printf("[Audio_Play] ENTER top=%d\n", lua_gettop(L));
|
||||
psyqo::Lua lua(L);
|
||||
|
||||
if (!s_sceneManager) {
|
||||
lua.pushNumber(-1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int soundId = -1;
|
||||
|
||||
// Accept number (index) or string (name lookup) like Entity.Find
|
||||
// Check isNumber FIRST — in Lua, numbers pass isString too.
|
||||
// Check isNumber FIRST - in Lua, numbers pass isString too.
|
||||
if (lua.isNumber(1)) {
|
||||
soundId = static_cast<int>(lua.toNumber(1));
|
||||
printf("[Audio_Play] by index: %d\n", soundId);
|
||||
} else if (lua.isString(1)) {
|
||||
const char* name = lua.toString(1);
|
||||
printf("[Audio_Play] by name: '%s'\n", name ? name : "(null)");
|
||||
soundId = s_sceneManager->findAudioClipByName(name);
|
||||
if (soundId < 0) {
|
||||
printf("[Audio_Play] clip not found\n");
|
||||
lua.pushNumber(-1);
|
||||
return 1;
|
||||
}
|
||||
printf("[Audio_Play] found at index %d\n", soundId);
|
||||
} else {
|
||||
printf("[Audio_Play] invalid arg type\n");
|
||||
lua.pushNumber(-1);
|
||||
return 1;
|
||||
}
|
||||
@@ -1208,9 +1208,7 @@ int LuaAPI::Audio_Play(lua_State* L) {
|
||||
int volume = static_cast<int>(lua.optNumber(2, 100));
|
||||
int pan = static_cast<int>(lua.optNumber(3, 64));
|
||||
|
||||
printf("[Audio_Play] play(%d, vol=%d, pan=%d)\n", soundId, volume, pan);
|
||||
int voice = s_sceneManager->getAudio().play(soundId, volume, pan);
|
||||
printf("[Audio_Play] voice=%d OK\n", voice);
|
||||
lua.pushNumber(voice);
|
||||
return 1;
|
||||
}
|
||||
@@ -1236,6 +1234,7 @@ int LuaAPI::Audio_Find(lua_State* L) {
|
||||
|
||||
int LuaAPI::Audio_Stop(lua_State* L) {
|
||||
psyqo::Lua lua(L);
|
||||
if (!s_sceneManager) return 0;
|
||||
int channelId = static_cast<int>(lua.toNumber(1));
|
||||
s_sceneManager->getAudio().stopVoice(channelId);
|
||||
return 0;
|
||||
@@ -1243,6 +1242,7 @@ int LuaAPI::Audio_Stop(lua_State* L) {
|
||||
|
||||
int LuaAPI::Audio_SetVolume(lua_State* L) {
|
||||
psyqo::Lua lua(L);
|
||||
if (!s_sceneManager) return 0;
|
||||
int channelId = static_cast<int>(lua.toNumber(1));
|
||||
int volume = static_cast<int>(lua.toNumber(2));
|
||||
int pan = static_cast<int>(lua.optNumber(3, 64));
|
||||
@@ -1252,6 +1252,7 @@ int LuaAPI::Audio_SetVolume(lua_State* L) {
|
||||
|
||||
int LuaAPI::Audio_StopAll(lua_State* L) {
|
||||
psyqo::Lua lua(L);
|
||||
if (!s_sceneManager) return 0;
|
||||
s_sceneManager->getAudio().stopAll();
|
||||
return 0;
|
||||
}
|
||||
@@ -1263,9 +1264,8 @@ int LuaAPI::Audio_StopAll(lua_State* L) {
|
||||
int LuaAPI::Debug_Log(lua_State* L) {
|
||||
psyqo::Lua lua(L);
|
||||
|
||||
const char* msg = lua.optString(1, "");
|
||||
printf("[Lua] %s\n", msg);
|
||||
|
||||
// Debug.Log is a no-op on PS1 to avoid printf overhead.
|
||||
// Messages are only visible in emulator TTY builds.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1280,12 +1280,6 @@ int LuaAPI::Debug_DrawLine(lua_State* L) {
|
||||
}
|
||||
|
||||
// TODO: Queue LINE_G2 primitive through Renderer
|
||||
// For now, log to debug console (visible in emulator)
|
||||
#ifdef PSXSPLASH_DEBUG_DRAW
|
||||
printf("[DebugDraw] Line (%d,%d,%d)->(%d,%d,%d)\n",
|
||||
sx.raw() >> 12, sy.raw() >> 12, sz.raw() >> 12,
|
||||
ex.raw() >> 12, ey.raw() >> 12, ez.raw() >> 12);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1300,11 +1294,6 @@ int LuaAPI::Debug_DrawBox(lua_State* L) {
|
||||
}
|
||||
|
||||
// TODO: Queue 12 LINE_G2 primitives (box wireframe) through Renderer
|
||||
#ifdef PSXSPLASH_DEBUG_DRAW
|
||||
printf("[DebugDraw] Box center(%d,%d,%d) size(%d,%d,%d)\n",
|
||||
cx.raw() >> 12, cy.raw() >> 12, cz.raw() >> 12,
|
||||
hx.raw() >> 12, hy.raw() >> 12, hz.raw() >> 12);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace psxsplash {
|
||||
uint16_t padding;
|
||||
|
||||
/// Returns true if this triangle has no texture (vertex-color only).
|
||||
/// These should be rendered as POLY_G3 (GouraudTriangle) instead of POLY_GT3.
|
||||
bool isUntextured() const {
|
||||
return *reinterpret_cast<const uint16_t*>(&tpage) == UNTEXTURED_TPAGE;
|
||||
}
|
||||
|
||||
@@ -117,40 +117,6 @@ void NavRegionSystem::closestPointOnSegment(int32_t px, int32_t pz,
|
||||
outZ = az + fpmul(t, abz);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Segment crosses portal check (XZ)
|
||||
// ============================================================================
|
||||
|
||||
bool NavRegionSystem::segmentCrossesPortal(int32_t p0x, int32_t p0z,
|
||||
int32_t p1x, int32_t p1z,
|
||||
int32_t ax, int32_t az,
|
||||
int32_t bx, int32_t bz) {
|
||||
// Standard 2D segment intersection test using cross products.
|
||||
// Returns true if segment [p0,p1] crosses segment [a,b].
|
||||
|
||||
int32_t dx = p1x - p0x, dz = p1z - p0z;
|
||||
int32_t ex = bx - ax, ez = bz - az;
|
||||
|
||||
int64_t denom = (int64_t)dx * ez - (int64_t)dz * ex;
|
||||
if (denom == 0) return false; // Parallel
|
||||
|
||||
int32_t fx = ax - p0x, fz = az - p0z;
|
||||
|
||||
int64_t tNum = (int64_t)fx * ez - (int64_t)fz * ex;
|
||||
int64_t uNum = (int64_t)fx * dz - (int64_t)fz * dx;
|
||||
|
||||
// Check t in [0,1] and u in [0,1]
|
||||
if (denom > 0) {
|
||||
if (tNum < 0 || tNum > denom) return false;
|
||||
if (uNum < 0 || uNum > denom) return false;
|
||||
} else {
|
||||
if (tNum > 0 || tNum < denom) return false;
|
||||
if (uNum > 0 || uNum < denom) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Get floor Y at position (plane equation)
|
||||
// ============================================================================
|
||||
@@ -316,36 +282,10 @@ int32_t NavRegionSystem::resolvePosition(int32_t& newX, int32_t& newZ,
|
||||
bool NavRegionSystem::findPath(uint16_t startRegion, uint16_t endRegion,
|
||||
NavPath& path) const {
|
||||
// STUB: Returns false until NPC pathfinding is implemented.
|
||||
// When implemented, this will be A* over the region adjacency graph:
|
||||
// - Open set: priority queue by f-cost (g + heuristic)
|
||||
// - g-cost: sum of Euclidean distances between region centroids
|
||||
// - Heuristic: straight-line distance to goal centroid
|
||||
// - Neighbor iteration: via portal edges
|
||||
// - Max path length: NAV_MAX_PATH_STEPS
|
||||
path.stepCount = 0;
|
||||
(void)startRegion;
|
||||
(void)endRegion;
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Get portal between two regions
|
||||
// ============================================================================
|
||||
|
||||
const NavPortal* NavRegionSystem::getPortalBetween(uint16_t regionA, uint16_t regionB) const {
|
||||
if (regionA >= m_header.regionCount) return nullptr;
|
||||
|
||||
const auto& reg = m_regions[regionA];
|
||||
for (int i = 0; i < reg.portalCount; i++) {
|
||||
uint16_t portalIdx = reg.portalStart + i;
|
||||
if (portalIdx >= m_header.portalCount) break;
|
||||
|
||||
if (m_portals[portalIdx].neighborRegion == regionB) {
|
||||
return &m_portals[portalIdx];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace psxsplash
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
* - Player has a single current region index.
|
||||
* - Movement: point-in-convex-polygon test → portal crossing → neighbor update.
|
||||
* - Floor Y: project XZ onto region's floor plane.
|
||||
* - Pathfinding: A* over region adjacency graph (stub ready for NPC drop-in).
|
||||
*
|
||||
* All math is fixed-point 20.12. Zero floats.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
@@ -148,24 +145,10 @@ public:
|
||||
/// Returns the clamped position.
|
||||
void clampToRegion(int32_t& x, int32_t& z, uint16_t regionIndex) const;
|
||||
|
||||
// ========================================================================
|
||||
// Pathfinding stub — documented API for NPC drop-in
|
||||
// ========================================================================
|
||||
|
||||
/// Find a path from startRegion to endRegion.
|
||||
/// Writes region indices into path.regions[], sets path.stepCount.
|
||||
/// Returns true if a path was found.
|
||||
///
|
||||
/// Implementation: A* over the region adjacency graph.
|
||||
/// Cost heuristic: Euclidean distance between region centroids.
|
||||
/// This is a STUB — returns false until NPC pathfinding is implemented.
|
||||
// TODO: Implement this
|
||||
bool findPath(uint16_t startRegion, uint16_t endRegion,
|
||||
NavPath& path) const;
|
||||
|
||||
/// Get the portal edge between two adjacent regions.
|
||||
/// Returns nullptr if regions are not adjacent.
|
||||
const NavPortal* getPortalBetween(uint16_t regionA, uint16_t regionB) const;
|
||||
|
||||
private:
|
||||
NavDataHeader m_header = {};
|
||||
const NavRegion* m_regions = nullptr;
|
||||
@@ -183,11 +166,6 @@ private:
|
||||
int32_t bx, int32_t bz,
|
||||
int32_t& outX, int32_t& outZ);
|
||||
|
||||
/// Check if a line segment (player movement) crosses a portal edge
|
||||
static bool segmentCrossesPortal(int32_t p0x, int32_t p0z,
|
||||
int32_t p1x, int32_t p1z,
|
||||
int32_t ax, int32_t az,
|
||||
int32_t bx, int32_t bz);
|
||||
};
|
||||
|
||||
} // namespace psxsplash
|
||||
|
||||
@@ -1,26 +1,7 @@
|
||||
/*
|
||||
* pcdrv_handler.hh - Unified PCDRV API with runtime dispatch
|
||||
*
|
||||
* On pcsx-redux (emulator), uses pcdrv.h break instructions which are
|
||||
* intercepted at the CPU level natively.
|
||||
*
|
||||
* On real hardware, bypasses break instructions entirely and communicates
|
||||
* directly over SIO1 using the same protocol as PCdrvSerialHost.cs.
|
||||
* This avoids reliance on the exception save area, which is fragile
|
||||
* across different compiler versions and optimization levels.
|
||||
*
|
||||
* Additionally, redirects PSYQo's printf output to SIO1 on real hardware.
|
||||
*
|
||||
* Call pcdrv_sio1_init() once at startup, after PSYQo initialization.
|
||||
* Then use pcdrv_open/read/write/close/seek instead of PCopen/PCread/etc.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <psyqo/kernel.hh>
|
||||
#include <psyqo/xprintf.h>
|
||||
#include "common/hardware/pcsxhw.h"
|
||||
#include "common/kernel/pcdrv.h"
|
||||
|
||||
@@ -79,18 +60,6 @@ static inline void sio_pcdrv_escape(uint32_t funcCode) {
|
||||
sio_write32(funcCode);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Runtime detection - reads magic at 0x1F802080 each call.
|
||||
// NOT cached in a static, because this is a header-only file and each
|
||||
// translation unit would get its own copy of any static variable.
|
||||
// pcsx_present() is a single bus read - negligible cost.
|
||||
// =========================================================================
|
||||
|
||||
// =========================================================================
|
||||
// Direct SIO1 PCDRV implementations (real hardware path)
|
||||
// These call the host protocol directly with actual pointers/values,
|
||||
// bypassing break instructions and the exception save area entirely.
|
||||
// =========================================================================
|
||||
|
||||
static int sio_pcdrv_init() {
|
||||
sio_pcdrv_escape(0x101);
|
||||
@@ -114,19 +83,6 @@ static int sio_pcdrv_open(const char* name, int flags) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int sio_pcdrv_creat(const char* name) {
|
||||
sio_pcdrv_escape(0x102);
|
||||
if (!sio_check_okay()) return -1;
|
||||
const char* p = name;
|
||||
while (*p) sio_putc((uint8_t)*p++);
|
||||
sio_putc(0x00);
|
||||
sio_write32(0); // params
|
||||
if (sio_check_okay()) {
|
||||
return (int)sio_read32(); // handle
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int sio_pcdrv_close(int fd) {
|
||||
sio_pcdrv_escape(0x104);
|
||||
if (!sio_check_okay()) return -1;
|
||||
@@ -158,23 +114,6 @@ static int sio_pcdrv_read(int fd, void* buf, int len) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int sio_pcdrv_write(int fd, const void* buf, int len) {
|
||||
sio_pcdrv_escape(0x106);
|
||||
if (!sio_check_okay()) return -1;
|
||||
sio_write32((uint32_t)fd);
|
||||
sio_write32((uint32_t)len);
|
||||
sio_write32((uint32_t)(uintptr_t)buf); // memaddr for host debug
|
||||
if (!sio_check_okay()) return -1;
|
||||
const uint8_t* src = (const uint8_t*)buf;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sio_putc(src[i]);
|
||||
}
|
||||
if (sio_check_okay()) {
|
||||
return (int)sio_read32(); // bytes written
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int sio_pcdrv_seek(int fd, int offset, int whence) {
|
||||
sio_pcdrv_escape(0x107);
|
||||
if (!sio_check_okay()) return -1;
|
||||
@@ -202,11 +141,6 @@ static int pcdrv_open(const char* name, int flags, int perms) {
|
||||
return sio_pcdrv_open(name, flags);
|
||||
}
|
||||
|
||||
static int pcdrv_creat(const char* name, int perms) {
|
||||
if (pcsx_present()) return PCcreat(name, perms);
|
||||
return sio_pcdrv_creat(name);
|
||||
}
|
||||
|
||||
static int pcdrv_close(int fd) {
|
||||
if (pcsx_present()) return PCclose(fd);
|
||||
return sio_pcdrv_close(fd);
|
||||
@@ -217,11 +151,6 @@ static int pcdrv_read(int fd, void* buf, int len) {
|
||||
return sio_pcdrv_read(fd, buf, len);
|
||||
}
|
||||
|
||||
static int pcdrv_write(int fd, const void* buf, int len) {
|
||||
if (pcsx_present()) return PCwrite(fd, buf, len);
|
||||
return sio_pcdrv_write(fd, buf, len);
|
||||
}
|
||||
|
||||
static int pcdrv_seek(int fd, int offset, int whence) {
|
||||
if (pcsx_present()) return PClseek(fd, offset, whence);
|
||||
return sio_pcdrv_seek(fd, offset, whence);
|
||||
@@ -239,51 +168,6 @@ static void sio1Init() {
|
||||
for (int i = 0; i < 100; i++) { __asm__ volatile("" ::: "memory"); } // settle delay
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Printf redirect - replaces PSYQo's printfStub with SIO1 output
|
||||
//
|
||||
// PSYQo's kernel takeover (takeOverKernel) destroys the BIOS and replaces
|
||||
// the A0/B0/C0 jump handlers. Only A0[0x3F] (printf) is functional; all
|
||||
// other BIOS calls return immediately. PSYQo's printfStub calls
|
||||
// syscall_write(1,...) which goes to A0[0x03] - a dead no-op on real HW.
|
||||
//
|
||||
// Fix: replace the printf target address embedded in the A0 handler code
|
||||
// at addresses 0xa8 (lui $t0, hi) and 0xb4 (ori $t0, $t0, lo) with our
|
||||
// function that outputs directly to SIO1.
|
||||
// =========================================================================
|
||||
|
||||
// Printf replacement that sends output to SIO1
|
||||
static int sio1Printf(const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int r = vxprintf([](const char* data, int size, void*) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
while (!(SIO1_STAT & SIO1_TX_RDY)) {}
|
||||
SIO1_DATA = (uint8_t)data[i];
|
||||
}
|
||||
}, nullptr, fmt, args);
|
||||
va_end(args);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void redirectPrintfToSIO1() {
|
||||
uintptr_t addr = (uintptr_t)sio1Printf;
|
||||
uint16_t hi = (uint16_t)(addr >> 16);
|
||||
uint16_t lo = (uint16_t)(addr & 0xffff);
|
||||
if (lo >= 0x8000) hi++; // sign-extension compensation for ori
|
||||
|
||||
// Patch the A0 handler's embedded address:
|
||||
// 0xa8: lui $t0, hi (opcode 001111, rs=0, rt=$t0=$8)
|
||||
// 0xb4: ori $t0, $t0, lo (opcode 001101, rs=$t0, rt=$t0)
|
||||
*(volatile uint32_t*)0xa8 = 0x3C080000 | hi; // lui $t0, hi
|
||||
*(volatile uint32_t*)0xb4 = 0x35080000 | lo; // ori $t0, $t0, lo
|
||||
|
||||
psyqo::Kernel::flushCache();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Master init - call once at startup, after PSYQo initialization
|
||||
// =========================================================================
|
||||
|
||||
static void pcdrv_sio1_init() {
|
||||
if (pcsx_present()) return; // emulator handles PCDRV natively
|
||||
|
||||
@@ -57,12 +57,6 @@ void psxsplash::Renderer::SetFog(const FogConfig& fog) {
|
||||
}
|
||||
}
|
||||
|
||||
void psxsplash::Renderer::writeFogRegisters() {
|
||||
// Per-vertex fog is now computed manually in processTriangle (no DPCT).
|
||||
// DQA/DQB/RFC/GFC/BFC are no longer needed for fog.
|
||||
// The fog color is used directly via m_fog.color in the fogBlend function.
|
||||
}
|
||||
|
||||
psyqo::Vec3 psxsplash::Renderer::computeCameraViewPos() {
|
||||
::clear<Register::TRX, Safe>();
|
||||
::clear<Register::TRY, Safe>();
|
||||
@@ -270,7 +264,7 @@ void psxsplash::Renderer::Render(eastl::vector<GameObject*>& objects) {
|
||||
auto& ditherCmd = balloc.allocateFragment<psyqo::Prim::TPage>();
|
||||
ditherCmd.primitive.attr.setDithering(true);
|
||||
ot.insert(ditherCmd, ORDERING_TABLE_SIZE - 1);
|
||||
writeFogRegisters();
|
||||
|
||||
psyqo::Vec3 cameraPosition = computeCameraViewPos();
|
||||
int32_t fogFarSZ = m_fog.fogFarSZ;
|
||||
for (auto& obj : objects) {
|
||||
@@ -301,7 +295,7 @@ void psxsplash::Renderer::RenderWithBVH(eastl::vector<GameObject*>& objects, con
|
||||
auto& ditherCmd2 = balloc.allocateFragment<psyqo::Prim::TPage>();
|
||||
ditherCmd2.primitive.attr.setDithering(true);
|
||||
ot.insert(ditherCmd2, ORDERING_TABLE_SIZE - 1);
|
||||
writeFogRegisters();
|
||||
|
||||
Frustum frustum; m_currentCamera->ExtractFrustum(frustum);
|
||||
int visibleCount = bvh.cullFrustum(frustum, m_visibleRefs, MAX_VISIBLE_TRIANGLES);
|
||||
psyqo::Vec3 cameraPosition = computeCameraViewPos();
|
||||
@@ -510,7 +504,7 @@ void psxsplash::Renderer::RenderWithRooms(eastl::vector<GameObject*>& objects,
|
||||
auto& ditherCmd3 = balloc.allocateFragment<psyqo::Prim::TPage>();
|
||||
ditherCmd3.primitive.attr.setDithering(true);
|
||||
ot.insert(ditherCmd3, ORDERING_TABLE_SIZE - 1);
|
||||
writeFogRegisters();
|
||||
|
||||
psyqo::Vec3 cameraPosition = computeCameraViewPos();
|
||||
int32_t fogFarSZ = m_fog.fogFarSZ;
|
||||
int32_t camX = m_currentCamera->GetPosition().x.raw();
|
||||
|
||||
@@ -97,12 +97,9 @@ class Renderer final {
|
||||
TriangleRef m_visibleRefs[MAX_VISIBLE_TRIANGLES];
|
||||
int m_frameCount = 0;
|
||||
|
||||
void writeFogRegisters();
|
||||
psyqo::Vec3 computeCameraViewPos();
|
||||
void setupObjectTransform(GameObject* obj, const psyqo::Vec3& cameraPosition);
|
||||
|
||||
// Core triangle pipeline: rtpt -> nclip -> screen-space clip -> emit.
|
||||
// Uses Bandwidth's proven approach: nclip always, max-SZ depth, screen clip.
|
||||
void processTriangle(Tri& tri, int32_t fogFarSZ,
|
||||
psyqo::OrderingTable<ORDERING_TABLE_SIZE>& ot,
|
||||
psyqo::BumpAllocator<BUMP_ALLOCATOR_SIZE>& balloc);
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#include "sceneloader.hh"
|
||||
#include "fileloader.hh"
|
||||
|
||||
namespace psxsplash {
|
||||
|
||||
bool SceneLoader::s_initialised = false;
|
||||
|
||||
bool SceneLoader::Init() {
|
||||
// FileLoader::Get().scheduleInit() is task-based; for backward compat
|
||||
// with the old sync Init() we rely on main.cpp running the init task
|
||||
// before any LoadFile calls. This flag is set unconditionally so that
|
||||
// IsPCdrvAvailable() remains useful.
|
||||
s_initialised = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneLoader::IsPCdrvAvailable() {
|
||||
return s_initialised;
|
||||
}
|
||||
|
||||
uint8_t* SceneLoader::LoadFile(const char* filename, int& outSize) {
|
||||
return FileLoader::Get().LoadFileSync(filename, outSize);
|
||||
}
|
||||
|
||||
void SceneLoader::FreeFile(uint8_t* data) {
|
||||
FileLoader::Get().FreeFile(data);
|
||||
}
|
||||
|
||||
} // namespace psxsplash
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace psxsplash {
|
||||
|
||||
/**
|
||||
* SceneLoader — backward-compatibility façade.
|
||||
*
|
||||
* All calls now delegate to the FileLoader singleton (selected at compile
|
||||
* time by the LOADER build flag). New code should use FileLoader directly.
|
||||
*/
|
||||
class SceneLoader {
|
||||
public:
|
||||
/** Initialise the active FileLoader backend (PCdrv or CD-ROM). */
|
||||
static bool Init();
|
||||
|
||||
/** Load a file synchronously. Uses FileLoader::Get().LoadFileSync(). */
|
||||
static uint8_t* LoadFile(const char* filename, int& outSize);
|
||||
|
||||
/** Free previously loaded data. Uses FileLoader::Get().FreeFile(). */
|
||||
static void FreeFile(uint8_t* data);
|
||||
|
||||
/** Returns true after a successful Init(). */
|
||||
static bool IsPCdrvAvailable();
|
||||
|
||||
private:
|
||||
static bool s_initialised;
|
||||
};
|
||||
|
||||
} // namespace psxsplash
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "profiler.hh"
|
||||
#include "renderer.hh"
|
||||
#include "splashpack.hh"
|
||||
#include "streq.hh"
|
||||
#include "luaapi.hh"
|
||||
#include "loadingscreen.hh"
|
||||
|
||||
@@ -163,24 +164,21 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
|
||||
|
||||
m_playerHeight = sceneSetup.playerHeight;
|
||||
|
||||
// Load movement parameters from splashpack (v8+)
|
||||
m_controls.setMoveSpeed(sceneSetup.moveSpeed);
|
||||
m_controls.setSprintSpeed(sceneSetup.sprintSpeed);
|
||||
m_playerRadius = (int32_t)sceneSetup.playerRadius.value;
|
||||
if (m_playerRadius == 0) m_playerRadius = PLAYER_RADIUS; // fallback to default
|
||||
if (m_playerRadius == 0) m_playerRadius = PLAYER_RADIUS;
|
||||
m_jumpVelocityRaw = (int32_t)sceneSetup.jumpVelocity.value;
|
||||
int32_t gravityRaw = (int32_t)sceneSetup.gravity.value;
|
||||
m_gravityPerFrame = gravityRaw / 30; // Convert per-second² to per-frame velocity change
|
||||
if (m_gravityPerFrame == 0 && gravityRaw > 0) m_gravityPerFrame = 1; // Ensure nonzero
|
||||
m_gravityPerFrame = gravityRaw / 30;
|
||||
if (m_gravityPerFrame == 0 && gravityRaw > 0) m_gravityPerFrame = 1;
|
||||
m_velocityY = 0;
|
||||
m_isGrounded = true;
|
||||
m_lastFrameTime = 0;
|
||||
m_deltaFrames = 1;
|
||||
|
||||
// Initialize collision system
|
||||
m_collisionSystem.init();
|
||||
|
||||
// Register colliders from splashpack data
|
||||
for (size_t i = 0; i < sceneSetup.colliders.size(); i++) {
|
||||
SPLASHPACKCollider* collider = sceneSetup.colliders[i];
|
||||
if (collider == nullptr) continue;
|
||||
@@ -203,7 +201,6 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
|
||||
);
|
||||
}
|
||||
|
||||
// Register trigger boxes from splashpack data
|
||||
for (size_t i = 0; i < sceneSetup.triggerBoxes.size(); i++) {
|
||||
SPLASHPACKTriggerBox* tb = sceneSetup.triggerBoxes[i];
|
||||
if (tb == nullptr) continue;
|
||||
@@ -219,9 +216,7 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
|
||||
m_collisionSystem.registerTriggerBox(bounds, tb->luaFileIndex);
|
||||
}
|
||||
|
||||
// Load Lua files - order is important here. We need
|
||||
// to load the Lua files before we register the game objects,
|
||||
// as the game objects may reference Lua files by index.
|
||||
|
||||
for (int i = 0; i < m_luaFiles.size(); i++) {
|
||||
auto luaFile = m_luaFiles[i];
|
||||
L.LoadLuaFile(luaFile->luaCode, luaFile->length, i);
|
||||
@@ -233,7 +228,6 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
|
||||
|
||||
L.OnSceneCreationStart();
|
||||
|
||||
// Register game objects
|
||||
for (auto object : m_gameObjects) {
|
||||
L.RegisterGameObject(object);
|
||||
}
|
||||
@@ -255,34 +249,25 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
|
||||
void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
|
||||
LuaAPI::IncrementFrameCount();
|
||||
|
||||
// Tick cutscene player (advance frame and apply tracks before rendering)
|
||||
m_cutscenePlayer.tick();
|
||||
|
||||
// Delta-time measurement: count elapsed frames based on gpu timer
|
||||
// PS1 NTSC frame = ~33333 microseconds (30fps vsync)
|
||||
{
|
||||
uint32_t now = gpu.now();
|
||||
if (m_lastFrameTime != 0) {
|
||||
uint32_t elapsed = now - m_lastFrameTime;
|
||||
// 33333us per frame at 30fps. If >50000us, we dropped a frame.
|
||||
m_deltaFrames = (elapsed > 50000) ? 2 : 1;
|
||||
if (elapsed > 83000) m_deltaFrames = 3; // Two frames dropped
|
||||
if (elapsed > 83000) m_deltaFrames = 3;
|
||||
}
|
||||
m_lastFrameTime = now;
|
||||
}
|
||||
|
||||
uint32_t renderingStart = gpu.now();
|
||||
auto& renderer = psxsplash::Renderer::GetInstance();
|
||||
// Dispatch render path based on scene type.
|
||||
// Interior scenes (type 1) use room/portal occlusion; exterior scenes use BVH culling.
|
||||
if (m_sceneType == 1 && m_roomCount > 0 && m_rooms != nullptr) {
|
||||
// Determine which room the camera is in for portal culling.
|
||||
// During cutscene playback the camera may be elsewhere from the player,
|
||||
// so use the actual camera position to find the room.
|
||||
|
||||
int camRoom = -1;
|
||||
if (m_navRegions.isLoaded()) {
|
||||
if (m_cutscenePlayer.isPlaying()) {
|
||||
// Camera-driven: look up nav region from camera world position
|
||||
auto& camPos = m_currentCamera.GetPosition();
|
||||
uint16_t camRegion = m_navRegions.findRegion(camPos.x.value, camPos.z.value);
|
||||
if (camRegion != NAV_NO_REGION) {
|
||||
@@ -290,7 +275,6 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
|
||||
if (ri != 0xFF) camRoom = (int)ri;
|
||||
}
|
||||
} else if (m_playerNavRegion != NAV_NO_REGION) {
|
||||
// Normal gameplay: use cached player nav region
|
||||
uint8_t ri = m_navRegions.getRoomIndex(m_playerNavRegion);
|
||||
if (ri != 0xFF) camRoom = (int)ri;
|
||||
}
|
||||
@@ -308,10 +292,8 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
|
||||
psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_RENDERING, renderingTime);
|
||||
#endif
|
||||
|
||||
// Collision detection
|
||||
uint32_t collisionStart = gpu.now();
|
||||
|
||||
// Build player AABB from position + radius/height
|
||||
AABB playerAABB;
|
||||
{
|
||||
psyqo::FixedPoint<12> r;
|
||||
@@ -327,7 +309,6 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
|
||||
psyqo::Vec3 pushBack;
|
||||
int collisionCount = m_collisionSystem.detectCollisions(playerAABB, pushBack);
|
||||
|
||||
// Apply push-back to player position
|
||||
{
|
||||
psyqo::FixedPoint<12> zero;
|
||||
if (pushBack.x != zero || pushBack.z != zero) {
|
||||
@@ -426,9 +407,7 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
|
||||
|
||||
uint32_t navmeshStart = gpu.now();
|
||||
if (!freecam) {
|
||||
// Priority: WorldCollision + NavRegions (v7) > NavGrid (v5) > Legacy Navmesh
|
||||
if (m_worldCollision.isLoaded()) {
|
||||
// Move-and-slide against world geometry (XZ walls only)
|
||||
psyqo::Vec3 slid = m_worldCollision.moveAndSlide(
|
||||
oldPlayerPosition, m_playerPosition, m_playerRadius, 0xFF);
|
||||
|
||||
@@ -664,9 +643,6 @@ void psxsplash::SceneManager::loadScene(psyqo::GPU& gpu, int sceneIndex, bool is
|
||||
char filename[32];
|
||||
FileLoader::BuildSceneFilename(sceneIndex, filename, sizeof(filename));
|
||||
|
||||
// Blank BOTH framebuffers so the user doesn't see the BIOS screen
|
||||
// or a frozen frame. FastFill ignores the scissor/DrawingArea so we
|
||||
// can target any VRAM region directly.
|
||||
psyqo::Prim::FastFill ff(psyqo::Color{.r = 0, .g = 0, .b = 0});
|
||||
ff.rect = psyqo::Rect{0, 0, 320, 240};
|
||||
gpu.sendPrimitive(ff);
|
||||
@@ -674,7 +650,6 @@ void psxsplash::SceneManager::loadScene(psyqo::GPU& gpu, int sceneIndex, bool is
|
||||
gpu.sendPrimitive(ff);
|
||||
gpu.pumpCallbacks();
|
||||
|
||||
// Try to load a loading screen for the target scene
|
||||
LoadingScreen loading;
|
||||
if (s_font) {
|
||||
if (loading.load(gpu, *s_font, sceneIndex)) {
|
||||
@@ -742,9 +717,7 @@ void psxsplash::SceneManager::clearScene() {
|
||||
// (bytecode, strings, tables, registry) in one shot via lua_close.
|
||||
L.Shutdown();
|
||||
|
||||
// 2. Free vector BACKING STORAGE (not just contents).
|
||||
// clear() only sets size=0 but keeps the allocated capacity.
|
||||
// swap-with-empty releases the heap blocks so they can be coalesced.
|
||||
// 2. Clear all vectors to free their heap storage (game objects, Lua files, names, etc)
|
||||
{ eastl::vector<GameObject*> tmp; tmp.swap(m_gameObjects); }
|
||||
{ eastl::vector<LuaFile*> tmp; tmp.swap(m_luaFiles); }
|
||||
{ eastl::vector<const char*> tmp; tmp.swap(m_objectNames); }
|
||||
@@ -775,12 +748,10 @@ void psxsplash::SceneManager::shrinkBuffer() {
|
||||
if (m_liveDataSize == 0 || m_currentSceneData == nullptr) return;
|
||||
|
||||
uint8_t* oldBase = m_currentSceneData;
|
||||
// Allocate the shrunk buffer. The volatile cast prevents the compiler
|
||||
// from assuming operator new never returns NULL (it does with
|
||||
// -fno-exceptions), which would let it optimize away the null check.
|
||||
|
||||
uint8_t* volatile newBaseV = new uint8_t[m_liveDataSize];
|
||||
uint8_t* newBase = newBaseV;
|
||||
if (!newBase) return; // Heap exhausted — keep the full buffer
|
||||
if (!newBase) return;
|
||||
__builtin_memcpy(newBase, oldBase, m_liveDataSize);
|
||||
|
||||
intptr_t delta = reinterpret_cast<intptr_t>(newBase) - reinterpret_cast<intptr_t>(oldBase);
|
||||
@@ -823,11 +794,6 @@ void psxsplash::SceneManager::shrinkBuffer() {
|
||||
|
||||
m_uiSystem.relocate(delta);
|
||||
|
||||
// Re-key Lua registry entries for game objects. RegisterGameObject stored
|
||||
// lightuserdata keys using the OLD buffer addresses. After relocation, the
|
||||
// game object pointers changed but the registry keys are stale. Without
|
||||
// this, Entity.Find/FindByIndex return nil (wrong key), and in release
|
||||
// builds (-Os) the optimizer can turn this into a hard crash.
|
||||
if (!m_gameObjects.empty()) {
|
||||
L.RelocateGameObjects(
|
||||
reinterpret_cast<GameObject**>(m_gameObjects.data()),
|
||||
@@ -842,16 +808,11 @@ void psxsplash::SceneManager::shrinkBuffer() {
|
||||
// OBJECT NAME LOOKUP
|
||||
// ============================================================================
|
||||
|
||||
// Inline streq (no libc on bare-metal PS1)
|
||||
static bool name_eq(const char* a, const char* b) {
|
||||
while (*a && *b) { if (*a++ != *b++) return false; }
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
psxsplash::GameObject* psxsplash::SceneManager::findObjectByName(const char* name) const {
|
||||
if (!name || m_objectNames.empty()) return nullptr;
|
||||
for (size_t i = 0; i < m_objectNames.size() && i < m_gameObjects.size(); i++) {
|
||||
if (m_objectNames[i] && name_eq(m_objectNames[i], name)) {
|
||||
if (m_objectNames[i] && streq(m_objectNames[i], name)) {
|
||||
return m_gameObjects[i];
|
||||
}
|
||||
}
|
||||
@@ -861,7 +822,7 @@ psxsplash::GameObject* psxsplash::SceneManager::findObjectByName(const char* nam
|
||||
int psxsplash::SceneManager::findAudioClipByName(const char* name) const {
|
||||
if (!name || m_audioClipNames.empty()) return -1;
|
||||
for (size_t i = 0; i < m_audioClipNames.size(); i++) {
|
||||
if (m_audioClipNames[i] && name_eq(m_audioClipNames[i], name)) {
|
||||
if (m_audioClipNames[i] && streq(m_audioClipNames[i], name)) {
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,6 @@ class SceneManager {
|
||||
int m_cutsceneCount = 0;
|
||||
CutscenePlayer m_cutscenePlayer;
|
||||
|
||||
// UI system (v13+)
|
||||
UISystem m_uiSystem;
|
||||
#ifdef PSXSPLASH_MEMOVERLAY
|
||||
MemOverlay m_memOverlay;
|
||||
@@ -154,7 +153,6 @@ class SceneManager {
|
||||
|
||||
psyqo::FixedPoint<12, uint16_t> m_playerHeight;
|
||||
|
||||
// Movement physics (v8+)
|
||||
int32_t m_playerRadius; // Collision radius in fp12 (replaces hardcoded PLAYER_RADIUS)
|
||||
int32_t m_velocityY; // Vertical velocity in fp12 per second (negative = up)
|
||||
int32_t m_gravityPerFrame; // Gravity velocity change per frame (fp12)
|
||||
@@ -182,5 +180,4 @@ class SceneManager {
|
||||
void clearScene(); // Deallocate current scene objects
|
||||
void shrinkBuffer(); // Free pixel/audio bulk data after VRAM/SPU uploads
|
||||
};
|
||||
}; // namespace psxsplash
|
||||
// namespace psxsplash
|
||||
} // namespace psxsplash
|
||||
296
src/sio_pcdrv.h
296
src/sio_pcdrv.h
@@ -1,296 +0,0 @@
|
||||
/*
|
||||
* sio_pcdrv.h — SIO1-based PCDrv implementation for PSYQo applications
|
||||
*
|
||||
* Problem: PSYQo's kernel initialization overwrites the exception handler
|
||||
* at 0x80000080, destroying Unirom's DEBG hooks. The standard
|
||||
* pcdrv.h functions use MIPS `break` instructions that rely on
|
||||
* those hooks to translate into SIO escape sequences.
|
||||
*
|
||||
* Solution: Bypass the `break` instruction mechanism entirely. Instead,
|
||||
* talk directly to SIO1 hardware and send the exact same 0x00+'p'
|
||||
* escape protocol that the host (NOTPSXSerial / PCdrvSerialHost)
|
||||
* expects. This works regardless of what's at the exception vector.
|
||||
*
|
||||
* Protocol: Matches NOTPSXSerial's PCDrv.cs / Bridge.MonitorSerial():
|
||||
* PS1 → Host: 0x00 'p' funcCode(4 LE)
|
||||
* Host → PS1: "OKAY" ... (function-specific data)
|
||||
* or "NOPE" on error
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SIO1 hardware registers (UART serial port, 0x1F801050)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
#define SIO1_DATA (*(volatile uint8_t *)0x1F801050)
|
||||
#define SIO1_STAT (*(volatile uint32_t *)0x1F801054)
|
||||
#define SIO1_MODE (*(volatile uint16_t *)0x1F801058)
|
||||
#define SIO1_CTRL (*(volatile uint16_t *)0x1F80105A)
|
||||
#define SIO1_BAUD (*(volatile uint16_t *)0x1F80105E)
|
||||
|
||||
// Status register bits
|
||||
#define SIO1_STAT_TX_RDY (1 << 0) // TX FIFO not full
|
||||
#define SIO1_STAT_RX_RDY (1 << 1) // RX data available
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// Low-level SIO1 I/O — blocking, tight polling loops
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
static inline void sio_putc(uint8_t byte) {
|
||||
while (!(SIO1_STAT & SIO1_STAT_TX_RDY)) {}
|
||||
SIO1_DATA = byte;
|
||||
}
|
||||
|
||||
static inline uint8_t sio_getc() {
|
||||
while (!(SIO1_STAT & SIO1_STAT_RX_RDY)) {}
|
||||
return SIO1_DATA;
|
||||
}
|
||||
|
||||
static inline void sio_write32(uint32_t val) {
|
||||
sio_putc((uint8_t)(val));
|
||||
sio_putc((uint8_t)(val >> 8));
|
||||
sio_putc((uint8_t)(val >> 16));
|
||||
sio_putc((uint8_t)(val >> 24));
|
||||
}
|
||||
|
||||
static inline uint32_t sio_read32() {
|
||||
uint32_t v = (uint32_t)sio_getc();
|
||||
v |= (uint32_t)sio_getc() << 8;
|
||||
v |= (uint32_t)sio_getc() << 16;
|
||||
v |= (uint32_t)sio_getc() << 24;
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline void sio_send_str(const char *s) {
|
||||
while (*s) sio_putc((uint8_t)*s++);
|
||||
}
|
||||
|
||||
// Read 4 bytes and check if they are "OKAY"
|
||||
static inline int sio_check_okay() {
|
||||
uint8_t a = sio_getc();
|
||||
uint8_t b = sio_getc();
|
||||
uint8_t c = sio_getc();
|
||||
uint8_t d = sio_getc();
|
||||
return (a == 'O' && b == 'K' && c == 'A' && d == 'Y');
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// PCDrv escape protocol — send 0x00 + 'p' + function code
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
static inline void sio_pcdrv_escape(uint32_t funcCode) {
|
||||
sio_putc(0x00); // escape character
|
||||
sio_putc('p'); // PCDrv marker
|
||||
sio_write32(funcCode); // function code, little-endian
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// PCDrv API — drop-in replacements for common/kernel/pcdrv.h
|
||||
// Same names, same signatures, same return conventions.
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* sio1_ensure_init — (re-)initialize SIO1 for 115200 8N1
|
||||
* Safe to call multiple times. Uses the same register values
|
||||
* that Unirom/nugget use, so this is a no-op if SIO1 is already
|
||||
* configured. Ensures correct config even if PSYQo or BIOS
|
||||
* touched the SIO1 registers.
|
||||
*/
|
||||
static inline void sio1_ensure_init() {
|
||||
SIO1_CTRL = 0; // reset
|
||||
SIO1_MODE = 0x004e; // MUL16, 8 data bits, no parity, 1 stop bit
|
||||
SIO1_BAUD = (uint16_t)(2073600 / 115200); // = 18
|
||||
SIO1_CTRL = 0x0025; // TX enable, RX enable, RTS assert
|
||||
// Small delay for hardware to settle
|
||||
{
|
||||
int i = 0;
|
||||
while (i < 100) { __asm__ volatile("" ::: "memory"); i++; }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PCinit — initialize PCDrv connection
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
static inline int PCinit() {
|
||||
sio1_ensure_init(); // make sure SIO1 is properly configured
|
||||
sio_pcdrv_escape(0x101);
|
||||
|
||||
// Host responds: "OKAY" + 0x00
|
||||
if (!sio_check_okay()) return -1;
|
||||
sio_getc(); // consume trailing 0x00
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* PCopen — open a file on the host
|
||||
* Returns file handle (positive) on success, -1 on failure.
|
||||
*/
|
||||
static inline int PCopen(const char *name, int flags, int perms) {
|
||||
(void)perms; // unused in protocol
|
||||
sio_pcdrv_escape(0x103);
|
||||
|
||||
// Host responds: "OKAY" (first ACK, ready for filename)
|
||||
if (!sio_check_okay()) return -1;
|
||||
|
||||
// Send filename (null-terminated)
|
||||
const char *p = name;
|
||||
while (*p) sio_putc((uint8_t)*p++);
|
||||
sio_putc(0x00); // null terminator
|
||||
|
||||
// Send file mode as uint32 LE
|
||||
sio_write32((uint32_t)flags);
|
||||
|
||||
// Host responds: "OKAY" + handle(4) or "NOPE"
|
||||
uint8_t r0 = sio_getc();
|
||||
uint8_t r1 = sio_getc();
|
||||
uint8_t r2 = sio_getc();
|
||||
uint8_t r3 = sio_getc();
|
||||
|
||||
if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// "OKAY" — read handle
|
||||
int handle = (int)sio_read32();
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* PCclose — close a file handle
|
||||
* Returns 0 on success.
|
||||
*/
|
||||
static inline int PCclose(int fd) {
|
||||
sio_pcdrv_escape(0x104);
|
||||
|
||||
// Host responds: "OKAY" (ready for params)
|
||||
if (!sio_check_okay()) return -1;
|
||||
|
||||
// Send handle + 2 unused params (matches Unirom kernel convention)
|
||||
sio_write32((uint32_t)fd);
|
||||
sio_write32(0); // unused
|
||||
sio_write32(0); // unused
|
||||
|
||||
// Host responds: "OKAY" + handle(4) or "NOPE"
|
||||
uint8_t r0 = sio_getc();
|
||||
uint8_t r1 = sio_getc();
|
||||
uint8_t r2 = sio_getc();
|
||||
uint8_t r3 = sio_getc();
|
||||
|
||||
if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// "OKAY" — read handle back (v1, not used by caller)
|
||||
sio_read32();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* PCread — read data from a file into memory
|
||||
* Returns number of bytes read, or -1 on failure.
|
||||
*/
|
||||
static inline int PCread(int fd, void *buf, int len) {
|
||||
sio_pcdrv_escape(0x105);
|
||||
|
||||
// Host responds: "OKAY" (ready for params)
|
||||
if (!sio_check_okay()) return -1;
|
||||
|
||||
// Send handle + length + memaddr (memaddr is debug-only, send buf ptr)
|
||||
sio_write32((uint32_t)fd);
|
||||
sio_write32((uint32_t)len);
|
||||
sio_write32((uint32_t)(uintptr_t)buf);
|
||||
|
||||
// Host responds: "OKAY" + dataLength(4) + checksum(4) + raw data
|
||||
// or "NOPE"
|
||||
uint8_t r0 = sio_getc();
|
||||
uint8_t r1 = sio_getc();
|
||||
uint8_t r2 = sio_getc();
|
||||
uint8_t r3 = sio_getc();
|
||||
|
||||
if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// "OKAY" — read response
|
||||
uint32_t dataLength = sio_read32();
|
||||
uint32_t checksum = sio_read32(); // not verified, just consume
|
||||
(void)checksum;
|
||||
|
||||
// Read raw data bytes into buffer
|
||||
uint8_t *dst = (uint8_t *)buf;
|
||||
for (uint32_t i = 0; i < dataLength; i++) {
|
||||
dst[i] = sio_getc();
|
||||
}
|
||||
|
||||
return (int)dataLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* PCwrite — write data from memory to a file
|
||||
* Returns number of bytes written, or -1 on failure.
|
||||
*/
|
||||
static inline int PCwrite(int fd, const void *buf, int len) {
|
||||
sio_pcdrv_escape(0x106);
|
||||
|
||||
// Host responds: "OKAY" (ready for params)
|
||||
if (!sio_check_okay()) return -1;
|
||||
|
||||
// Send handle + length + memaddr
|
||||
sio_write32((uint32_t)fd);
|
||||
sio_write32((uint32_t)len);
|
||||
sio_write32((uint32_t)(uintptr_t)buf);
|
||||
|
||||
// Host responds: "OKAY" (ready for data) or "NOPE"
|
||||
uint8_t r0 = sio_getc();
|
||||
uint8_t r1 = sio_getc();
|
||||
uint8_t r2 = sio_getc();
|
||||
uint8_t r3 = sio_getc();
|
||||
if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Send raw data
|
||||
const uint8_t *src = (const uint8_t *)buf;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sio_putc(src[i]);
|
||||
}
|
||||
|
||||
// Host responds: "OKAY" + bytesWritten(4)
|
||||
if (!sio_check_okay()) return -1;
|
||||
int written = (int)sio_read32();
|
||||
return written;
|
||||
}
|
||||
|
||||
/**
|
||||
* PClseek — seek within a file
|
||||
* Returns new position, or -1 on failure.
|
||||
*/
|
||||
static inline int PClseek(int fd, int offset, int whence) {
|
||||
sio_pcdrv_escape(0x107);
|
||||
|
||||
// Host responds: "OKAY" (ready for params)
|
||||
if (!sio_check_okay()) return -1;
|
||||
|
||||
// Send handle + offset + whence (seek origin)
|
||||
sio_write32((uint32_t)fd);
|
||||
sio_write32((uint32_t)offset);
|
||||
sio_write32((uint32_t)whence);
|
||||
|
||||
// Host responds: "OKAY" + position(4) or "NOPE"
|
||||
uint8_t r0 = sio_getc();
|
||||
uint8_t r1 = sio_getc();
|
||||
uint8_t r2 = sio_getc();
|
||||
uint8_t r3 = sio_getc();
|
||||
|
||||
if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// "OKAY" — read new position
|
||||
int pos = (int)sio_read32();
|
||||
return pos;
|
||||
}
|
||||
@@ -12,20 +12,13 @@
|
||||
#include "cutscene.hh"
|
||||
#include "lua.h"
|
||||
#include "mesh.hh"
|
||||
#include "streq.hh"
|
||||
#include "worldcollision.hh"
|
||||
#include "navregion.hh"
|
||||
#include "renderer.hh"
|
||||
|
||||
namespace psxsplash {
|
||||
|
||||
// Bare-metal string compare (no libc)
|
||||
static bool sp_streq(const char* a, const char* b) {
|
||||
while (*a && *b) {
|
||||
if (*a++ != *b++) return false;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
struct SPLASHPACKFileHeader {
|
||||
char magic[2];
|
||||
uint16_t version;
|
||||
@@ -99,7 +92,6 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
|
||||
setup.playerStartRotation = header->playerStartRot;
|
||||
setup.playerHeight = header->playerHeight;
|
||||
|
||||
// Movement parameters (v8+)
|
||||
setup.moveSpeed.value = header->moveSpeed;
|
||||
setup.sprintSpeed.value = header->sprintSpeed;
|
||||
setup.jumpVelocity.value = header->jumpVelocity;
|
||||
@@ -120,7 +112,6 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
|
||||
cursor += sizeof(psxsplash::LuaFile);
|
||||
}
|
||||
|
||||
// sceneLuaFileIndex is stored as uint16_t in header; 0xFFFF means "no scene script" (-1)
|
||||
setup.sceneLuaFileIndex = (header->sceneLuaFileIndex == 0xFFFF) ? -1 : (int)header->sceneLuaFileIndex;
|
||||
|
||||
for (uint16_t i = 0; i < header->gameObjectCount; i++) {
|
||||
@@ -130,14 +121,12 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
|
||||
cursor += sizeof(psxsplash::GameObject);
|
||||
}
|
||||
|
||||
// Read collision data (after GameObjects)
|
||||
for (uint16_t i = 0; i < header->colliderCount; i++) {
|
||||
psxsplash::SPLASHPACKCollider *collider = reinterpret_cast<psxsplash::SPLASHPACKCollider *>(cursor);
|
||||
setup.colliders.push_back(collider);
|
||||
cursor += sizeof(psxsplash::SPLASHPACKCollider);
|
||||
}
|
||||
|
||||
// Read trigger boxes (after colliders)
|
||||
setup.triggerBoxes.reserve(header->triggerBoxCount);
|
||||
for (uint16_t i = 0; i < header->triggerBoxCount; i++) {
|
||||
psxsplash::SPLASHPACKTriggerBox *tb = reinterpret_cast<psxsplash::SPLASHPACKTriggerBox *>(cursor);
|
||||
@@ -145,7 +134,6 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
|
||||
cursor += sizeof(psxsplash::SPLASHPACKTriggerBox);
|
||||
}
|
||||
|
||||
// BVH data
|
||||
if (header->bvhNodeCount > 0) {
|
||||
BVHNode* bvhNodes = reinterpret_cast<BVHNode*>(cursor);
|
||||
cursor += header->bvhNodeCount * sizeof(BVHNode);
|
||||
@@ -157,28 +145,24 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
|
||||
triangleRefs, header->bvhTriangleRefCount);
|
||||
}
|
||||
|
||||
// Interactables
|
||||
for (uint16_t i = 0; i < header->interactableCount; i++) {
|
||||
psxsplash::Interactable *interactable = reinterpret_cast<psxsplash::Interactable *>(cursor);
|
||||
setup.interactables.push_back(interactable);
|
||||
cursor += sizeof(psxsplash::Interactable);
|
||||
}
|
||||
|
||||
// World collision soup
|
||||
if (header->worldCollisionMeshCount > 0) {
|
||||
uintptr_t addr = reinterpret_cast<uintptr_t>(cursor);
|
||||
cursor = reinterpret_cast<uint8_t*>((addr + 3) & ~3);
|
||||
cursor = const_cast<uint8_t*>(setup.worldCollision.initializeFromData(cursor));
|
||||
}
|
||||
|
||||
// Nav regions
|
||||
if (header->navRegionCount > 0) {
|
||||
uintptr_t addr = reinterpret_cast<uintptr_t>(cursor);
|
||||
cursor = reinterpret_cast<uint8_t*>((addr + 3) & ~3);
|
||||
cursor = const_cast<uint8_t*>(setup.navRegions.initializeFromData(cursor));
|
||||
}
|
||||
|
||||
// Room/portal data (interior scenes)
|
||||
if (header->roomCount > 0) {
|
||||
uintptr_t addr = reinterpret_cast<uintptr_t>(cursor);
|
||||
cursor = reinterpret_cast<uint8_t*>((addr + 3) & ~3);
|
||||
@@ -218,7 +202,6 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
|
||||
for (uint16_t i = 0; i < header->gameObjectCount; i++) {
|
||||
uint8_t nameLen = *nameData++;
|
||||
const char* nameStr = reinterpret_cast<const char*>(nameData);
|
||||
// Names are stored as length-prefixed, null-terminated strings
|
||||
setup.objectNames.push_back(nameStr);
|
||||
nameData += nameLen + 1; // +1 for null terminator
|
||||
}
|
||||
@@ -318,7 +301,7 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
|
||||
} else {
|
||||
for (size_t oi = 0; oi < setup.objectNames.size(); oi++) {
|
||||
if (setup.objectNames[oi] &&
|
||||
sp_streq(setup.objectNames[oi], objName)) {
|
||||
streq(setup.objectNames[oi], objName)) {
|
||||
track.target = setup.objects[oi];
|
||||
break;
|
||||
}
|
||||
@@ -343,7 +326,6 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
|
||||
}
|
||||
}
|
||||
|
||||
// Read UI canvas data (version 13+)
|
||||
if (header->version >= 13) {
|
||||
setup.uiCanvasCount = header->uiCanvasCount;
|
||||
setup.uiFontCount = header->uiFontCount;
|
||||
|
||||
@@ -51,7 +51,6 @@ struct SplashpackSceneSetup {
|
||||
// New component arrays
|
||||
eastl::vector<Interactable *> interactables;
|
||||
|
||||
// Object name table (v9+): parallel to objects, points into splashpack data
|
||||
eastl::vector<const char *> objectNames;
|
||||
|
||||
// Audio clips (v10+): ADPCM data with metadata
|
||||
@@ -64,12 +63,11 @@ struct SplashpackSceneSetup {
|
||||
};
|
||||
eastl::vector<AudioClipSetup> audioClips;
|
||||
|
||||
// Audio clip name table (v10+): parallel to audioClips, points into splashpack data
|
||||
eastl::vector<const char*> audioClipNames;
|
||||
|
||||
BVHManager bvh; // Spatial acceleration structure for culling
|
||||
WorldCollision worldCollision; // Triangle-level world collision (v7+)
|
||||
NavRegionSystem navRegions; // Convex region navigation (v7+)
|
||||
WorldCollision worldCollision;
|
||||
NavRegionSystem navRegions;
|
||||
psyqo::GTE::PackedVec3 playerStartPosition;
|
||||
psyqo::GTE::PackedVec3 playerStartRotation;
|
||||
psyqo::FixedPoint<12, uint16_t> playerHeight;
|
||||
@@ -82,7 +80,6 @@ struct SplashpackSceneSetup {
|
||||
uint8_t fogR = 0, fogG = 0, fogB = 0;
|
||||
uint8_t fogDensity = 5;
|
||||
|
||||
// Room/portal data (v11+, interior scenes only)
|
||||
const RoomData* rooms = nullptr;
|
||||
uint16_t roomCount = 0;
|
||||
const PortalData* portals = nullptr;
|
||||
@@ -90,25 +87,20 @@ struct SplashpackSceneSetup {
|
||||
const TriangleRef* roomTriRefs = nullptr;
|
||||
uint16_t roomTriRefCount = 0;
|
||||
|
||||
// Movement parameters (v8+)
|
||||
psyqo::FixedPoint<12, uint16_t> moveSpeed; // Per-frame speed constant (fp12)
|
||||
psyqo::FixedPoint<12, uint16_t> sprintSpeed; // Per-frame sprint constant (fp12)
|
||||
psyqo::FixedPoint<12, uint16_t> jumpVelocity; // Per-second initial velocity (fp12)
|
||||
psyqo::FixedPoint<12, uint16_t> gravity; // Per-second² acceleration (fp12)
|
||||
psyqo::FixedPoint<12, uint16_t> playerRadius; // Collision radius (fp12)
|
||||
|
||||
// Cutscenes (version 12+)
|
||||
Cutscene loadedCutscenes[MAX_CUTSCENES];
|
||||
int cutsceneCount = 0;
|
||||
|
||||
// UI system (v13+)
|
||||
uint16_t uiCanvasCount = 0;
|
||||
uint8_t uiFontCount = 0;
|
||||
uint32_t uiTableOffset = 0;
|
||||
|
||||
// Buffer management (v15+): byte offset where pixel/audio bulk data begins.
|
||||
// Everything before this offset is needed at runtime; everything after can
|
||||
// be freed once VRAM/SPU uploads are done.
|
||||
|
||||
uint32_t liveDataSize = 0;
|
||||
};
|
||||
|
||||
@@ -117,4 +109,4 @@ class SplashPackLoader {
|
||||
void LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup);
|
||||
};
|
||||
|
||||
}; // namespace psxsplash
|
||||
} // namespace psxsplash
|
||||
|
||||
10
src/streq.hh
Normal file
10
src/streq.hh
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace psxsplash {
|
||||
|
||||
inline bool streq(const char* a, const char* b) {
|
||||
while (*a && *a == *b) { a++; b++; }
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
} // namespace psxsplash
|
||||
@@ -6,17 +6,10 @@
|
||||
#include <psyqo/primitives/rectangles.hh>
|
||||
#include <psyqo/primitives/sprites.hh>
|
||||
#include <psyqo/primitives/triangles.hh>
|
||||
#include "streq.hh"
|
||||
|
||||
namespace psxsplash {
|
||||
|
||||
// Bare-metal string compare (no libc)
|
||||
static bool ui_streq(const char* a, const char* b) {
|
||||
while (*a && *b) {
|
||||
if (*a++ != *b++) return false;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Init
|
||||
// ============================================================================
|
||||
@@ -521,7 +514,7 @@ void UISystem::renderProportionalText(int fontIdx, int16_t x, int16_t y,
|
||||
int UISystem::findCanvas(const char* name) const {
|
||||
if (!name) return -1;
|
||||
for (int i = 0; i < m_canvasCount; i++) {
|
||||
if (m_canvases[i].name && ui_streq(m_canvases[i].name, name))
|
||||
if (m_canvases[i].name && streq(m_canvases[i].name, name))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
@@ -546,7 +539,7 @@ int UISystem::findElement(int canvasIdx, const char* name) const {
|
||||
if (canvasIdx < 0 || canvasIdx >= m_canvasCount || !name) return -1;
|
||||
const UICanvas& cv = m_canvases[canvasIdx];
|
||||
for (int i = 0; i < cv.elementCount; i++) {
|
||||
if (cv.elements[i].name && ui_streq(cv.elements[i].name, name)) {
|
||||
if (cv.elements[i].name && streq(cv.elements[i].name, name)) {
|
||||
// Return flat handle: index into m_elements
|
||||
int handle = (int)(cv.elements + i - m_elements);
|
||||
return handle;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Auto-generated by SplashEdit - do not edit manually.
|
||||
// LEGACY -> used by loading screen
|
||||
|
||||
#pragma once
|
||||
|
||||
// GPU resolution
|
||||
|
||||
@@ -3,16 +3,6 @@
|
||||
#include <psyqo/fixed-point.hh>
|
||||
#include <psyqo/vector.hh>
|
||||
|
||||
// One-shot collision diagnostics
|
||||
|
||||
/**
|
||||
* worldcollision.cpp - Player-vs-World Triangle Collision
|
||||
*
|
||||
* ALL math is 20.12 fixed-point. Intermediate products use int64_t
|
||||
* to avoid overflow (20.12 * 20.12 = 40.24, shift >>12 back to 20.12).
|
||||
*
|
||||
* Performance budget: ~256 triangle tests per frame on 33MHz MIPS.
|
||||
*/
|
||||
|
||||
namespace psxsplash {
|
||||
|
||||
|
||||
BIN
test_literal.cpp
BIN
test_literal.cpp
Binary file not shown.
Reference in New Issue
Block a user