First little test project for PS1 programming using PSn00bSDK
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

567 lines
17 KiB

/*
* LibPSn00b Example Programs
*
* Hello World Example
* 2019-2020 Meido-Tek Productions / PSn00bSDK Project
*
* The obligatory hello world example normally included in nearly every
* SDK package. This example should also get you started in how to manage
* the display using psxgpu.
*
* Example by Lameguy64
*
*
* Changelog:
*
* January 1, 2020 - Initial version
*/
#include <stdio.h>
#include <sys/types.h>
#include <psxetc.h>
#include <psxgte.h>
#include <psxgpu.h>
#include <inline_c.h>
#include "ps1mdl.h"
#define OTLEN 256
#define SCREENWIDTH 512
#define SCREENHEIGHT 240
#define NUMVERTEXNORMALS 162
static short anorms[NUMVERTEXNORMALS][4] = {
#include "ps1anorms.h"
};
// Define display/draw environments for double buffering
DISPENV disp[2];
DRAWENV draw[2];
int db;
u_long ot[2][OTLEN]; // Ordering tables, two arrays for double buffering. These are basically the buckets for bucket sorting of polygons.
// You can also see them as "layers" in the modern 3D graphics sense, but they serve a more immediate role for polygon ordering.
char primbuff[2][65536]; // Primitive buffer, just a raw buffer of bytes to use as a pool for primitives
char *nextpri;
extern u_long mdl_player[];
extern u_long tim_player_f[];
extern u_long tim_player_b[];
extern u_long mdl_shambler[];
extern u_long tim_shambler_f[];
extern u_long tim_shambler_b[];
extern u_long mdl_ogre[];
extern u_long tim_ogre_f[];
extern u_long tim_ogre_b[];
extern u_long mdl_quaddama[];
extern u_long tim_quaddama_f[];
extern u_long tim_quaddama_b[];
typedef struct
{
ps1mdl_header_t* header;
ps1mdl_texcoord_t* texCoords;
ps1mdl_triangle_t* triangles;
ps1mdl_vertex_t* vertices;
VECTOR position;
SVECTOR rotation;
} ps1mdl_t;
typedef struct
{
RECT prect, crect;
int mode;
int uoffs, voffs;
} ps1texture_t;
typedef struct
{
// Skin textures are divided into two pieces, so that each part is aligned to their own texture page
ps1texture_t front, back;
} ps1skin_t;
ps1mdl_t playerModel = { .position = { -180, 0, 0 }, .rotation = { 0 } };
ps1mdl_t shamblerModel = { .position = { 180, 0, 0 }, .rotation = { 0, 0, ONE >> 1, 0 } };
ps1mdl_t ogreModel = { .position = { 0, 180, 0 }, .rotation = { 0, 0, (ONE * 3) >> 2, 0 } };
ps1mdl_t quadModel = { .position = { -32, 0, 0 }, .rotation = { 0 } };
ps1skin_t playerSkin, shamblerSkin, ogreSkin, quadSkin;
const MATRIX identity = {
ONE, 0, 0,
0, ONE, 0,
0, 0, ONE,
0, 0, 0,
};
const MATRIX quake_swizzle = {
ONE, 0, 0,
0, 0, -ONE,
0, ONE, 0,
0, 0, 0,
};
MATRIX light_cols = {
ONE, 0, 0,
ONE, 0, 0,
ONE, 0, 0
};
MATRIX light_dirs = {
ONE, 0, 0,
0, 0, 0,
0, 0, 0
};
// Scale X coordinates to correct the aspect ratio for the chosen resolution
VECTOR aspect_scale = { SCREENWIDTH * ONE / 320 , ONE, ONE };
VECTOR cam_pos = { 0, 0, 0 };
SVECTOR cam_rot = { 0 };
void loadModel(const u_long* data, ps1mdl_t *mdl)
{
const char *bytes = (const char*)data;
mdl->header = (ps1mdl_header_t*)bytes;
bytes += sizeof(ps1mdl_header_t);
mdl->texCoords = (ps1mdl_texcoord_t*)bytes;
bytes += sizeof(ps1mdl_texcoord_t) * mdl->header->vertexCount;
mdl->triangles = (ps1mdl_triangle_t*)bytes;
bytes += sizeof(ps1mdl_triangle_t) * mdl->header->triangleCount;
mdl->vertices = (ps1mdl_vertex_t*)bytes;
}
void loadTexture(const u_long* tim, ps1texture_t *texture)
{
TIM_IMAGE* tparam;
GetTimInfo(tim, tparam);
// This freezes up if the TIM image has weird dimensions, such as the ones from Quake...
LoadImage(tparam->prect, tparam->paddr);
// Upload CLUT for palettized images
if (tparam->mode & 0x8)
{
LoadImage(tparam->crect, tparam->caddr);
}
DrawSync(0);
texture->prect = *tparam->prect;
texture->crect = *tparam->crect;
texture->mode = tparam->mode;
texture->uoffs = (texture->prect.x % 64) << (2 - (texture->mode & 0x3));
texture->voffs = (texture->prect.y & 0xFF);
}
void loadSkin(const u_long* frontTim, const u_long* backTim, ps1skin_t *skin)
{
loadTexture(frontTim, &skin->front);
loadTexture(backTim, &skin->back);
}
// Init function
void init(void)
{
// This not only resets the GPU but it also installs the library's
// ISR subsystem to the kernel
ResetGraph(0);
SetVideoMode(MODE_NTSC);
// Define display environments, first on top and second on bottom
SetDefDispEnv(&disp[0], 0, 0, SCREENWIDTH, SCREENHEIGHT);
SetDefDispEnv(&disp[1], 0, SCREENHEIGHT, SCREENWIDTH, SCREENHEIGHT);
// Define drawing environments, first on bottom and second on top
SetDefDrawEnv(&draw[0], 0, SCREENHEIGHT, SCREENWIDTH, SCREENHEIGHT);
SetDefDrawEnv(&draw[1], 0, 0, SCREENWIDTH, SCREENHEIGHT);
// Set and enable clear color
setRGB0(&draw[0], 49, 77, 121);
setRGB0(&draw[1], 49, 77, 121);
draw[0].isbg = 1;
draw[0].dtd = 1;
draw[1].isbg = 1;
draw[1].dtd = 1;
// Clear double buffer counter
db = 0;
// Apply the GPU environments
PutDispEnv(&disp[db]);
PutDrawEnv(&draw[db]);
// Load test font
FntLoad(960, 0);
// Open up a test font text stream of 100 characters
FntOpen(0, 8, 320, 224, 0, 100);
nextpri = primbuff[0];
loadModel(mdl_player, &playerModel);
loadSkin(tim_player_f, tim_player_b, &playerSkin);
loadModel(mdl_shambler, &shamblerModel);
loadSkin(tim_shambler_f, tim_shambler_b, &shamblerSkin);
loadModel(mdl_ogre, &ogreModel);
loadSkin(tim_ogre_f, tim_ogre_b, &ogreSkin);
loadModel(mdl_quaddama, &quadModel);
loadSkin(tim_quaddama_f, tim_quaddama_b, &quadSkin);
InitGeom();
gte_SetGeomOffset(SCREENWIDTH >> 1, SCREENHEIGHT >> 1);
gte_SetGeomScreen(180); // Screen depth for FOV control. Essentially the Z position of the vanishing point.
// Set texture page for the entire drawing environment. Nice in some cases perhaps, but not what we need.
//draw[0].tpage = getTPage(playerFrontTex.mode & 0x3, 0, playerFrontTex.prect->x, playerFrontTex.prect->y);
//draw[1].tpage = getTPage(playerFrontTex.mode & 0x3, 0, playerFrontTex.prect->x, playerFrontTex.prect->y);
}
// Display function
void display(void)
{
// Wait for all drawing to complete
// TODO: this should be interleaved with the double buffered ordering table draw commands, to minimize CPU idle time while waiting for the GPU
DrawSync(0);
// Wait for vertical sync to cap the logic to 60fps (or 50 in PAL mode)
// and prevent screen tearing
VSync(0);
// Switch pages
PutDispEnv(&disp[db]);
PutDrawEnv(&draw[db]);
// Enable display output, ResetGraph() disables it by default
SetDispMask(1);
DrawOTag(ot[db] + OTLEN - 1); // This performs a DMA transfer to quickly send all the primitives off to the GPU
// Flip buffer index
db = !db;
nextpri = primbuff[db];
}
static void lerpcol(int r0, int g0, int b0, int r1, int g1, int b1, int lerp, int* rout, int* gout, int* bout) // lerp = 0-4096
{
int invlerp = 4096 - lerp;
*rout = (r0 * invlerp + r1 * lerp) >> 12;
*gout = (g0 * invlerp + g1 * lerp) >> 12;
*bout = (b0 * invlerp + b1 * lerp) >> 12;
}
void addTile(int x, int y, int w, int h, int r, int g, int b)
{
// Initialize tile primitive
TILE *tile = (TILE*)nextpri;
setTile(tile);
setXY0(tile, x, y);
setWH(tile, w, h);
setRGB0(tile, r, g, b);
addPrim(ot[db], tile);
nextpri += sizeof(TILE);
}
void addColoredTriangle(int x0, int y0, int x1, int y1, int x2, int y2, unsigned int rgb0, unsigned int rgb1, unsigned int rgb2, unsigned char depth)
{
POLY_G3 *poly = (POLY_G3*)nextpri;
setPolyG3(poly);
setXY3(poly, x0, y0, x1, y1, x2, y2);
setRGB0(poly, (rgb0 >> 16) & 0xFF, (rgb0 >> 8) & 0xFF, rgb0 & 0xFF);
setRGB1(poly, (rgb1 >> 16) & 0xFF, (rgb1 >> 8) & 0xFF, rgb1 & 0xFF);
setRGB2(poly, (rgb2 >> 16) & 0xFF, (rgb2 >> 8) & 0xFF, rgb2 & 0xFF);
addPrim(ot[db] + depth, poly);
nextpri += sizeof(POLY_G3);
}
void addTexturedTriangle(int x0, int y0, int x1, int y1, int x2, int y2, short u0, short v0, short u1, short v1, short u2, short v2, TIM_IMAGE *img)
{
short uoffs = (img->prect->x % 64) << (2 - (img->mode & 0x3));
short voffs = (img->prect->y & 0xFF);
POLY_FT3 *poly = (POLY_FT3*)nextpri;
setPolyFT3(poly);
setXY3(poly, x0, y0, x1, y1, x2, y2);
setUV3(poly, uoffs + u0, voffs + v0, uoffs + u1, voffs + v1, uoffs + u2, voffs + v2);
setRGB0(poly, 128, 128, 128);
setClut(poly, img->crect->x, img->crect->y);
setTPage(poly, img->mode & 0x3, 0, img->prect->x, img->prect->y);
addPrim(ot[db], poly);
nextpri += sizeof(POLY_FT3);
}
void addGouraudTexturedTriangle(
int x0, int y0, int x1, int y1, int x2, int y2, unsigned char depth,
unsigned int rgb0, unsigned int rgb1, unsigned int rgb2,
short u0, short v0, short u1, short v1, short u2, short v2, TIM_IMAGE *img)
{
if (img == NULL)
{
addColoredTriangle(x0, y0, x1, y1, x2, y2, rgb0, rgb1, rgb2, depth);
return;
}
short uoffs = (img->prect->x % 64) << (2 - (img->mode & 0x3));
short voffs = (img->prect->y & 0xFF);
POLY_GT3 *poly = (POLY_GT3*)nextpri;
setPolyGT3(poly);
setXY3(poly, x0, y0, x1, y1, x2, y2);
setUV3(poly, uoffs + u0, voffs + v0, uoffs + u1, voffs + v1, uoffs + u2, voffs + v2);
setRGB0(poly, (rgb0 >> 16) & 0xFF, (rgb0 >> 8) & 0xFF, rgb0 & 0xFF);
setRGB1(poly, (rgb1 >> 16) & 0xFF, (rgb1 >> 8) & 0xFF, rgb1 & 0xFF);
setRGB2(poly, (rgb2 >> 16) & 0xFF, (rgb2 >> 8) & 0xFF, rgb2 & 0xFF);
setClut(poly, img->crect->x, img->crect->y);
setTPage(poly, img->mode & 0x3, 0, img->prect->x, img->prect->y);
addPrim(ot[db] + depth, poly);
nextpri += sizeof(POLY_GT3);
}
void addTexturedSprite(int x, int y, int w, int h, TIM_IMAGE *img)
{
short uoffs = (img->prect->x % 64) << (2 - (img->mode & 0x3));
short voffs = (img->prect->y & 0xFF);
SPRT *sprite = (SPRT*)nextpri;
setSprt(sprite);
setXY0(sprite, x, y);
setWH(sprite, w, h);
setUV0(sprite, uoffs, voffs);
setClut(sprite, img->crect->x, img->crect->y);
setRGB0(sprite, 128, 128, 128);
addPrim(ot[db], sprite);
nextpri += sizeof(SPRT);
DR_TPAGE *tpage = (DR_TPAGE*)nextpri;
setDrawTPage(tpage, 0, 1, getTPage(img->mode & 0x3, 0, img->prect->x, img->prect->y));
addPrim(ot[db], tpage);
nextpri += sizeof(DR_TPAGE);
}
static int normToRGB(const int *norm)
{
int r = ((norm[0] + 4096) >> 5) & 0xFF;
int g = ((norm[1] + 4096) >> 5) & 0xFF;
int b = ((norm[2] + 4096) >> 5) & 0xFF;
return r << 16 | g << 8 | b;
}
static int dot(const int *a, const int *b)
{
int c0 = (a[0] * b[0]) >> 12;
int c1 = (a[1] * b[1]) >> 12;
int c2 = (a[2] * b[2]) >> 12;
return c0 + c1 + c2;
}
static int fakeLight(const int *norm)
{
const int light[3] = { ONE, 0, 0 };
int lit = dot(norm, light) >> 4; // Calculate light intensity and normalize to [-256..255]
lit = lit < 0 ? 0 : lit; // Clamp to [0..255]
lit += 48; // Add some ambient light
lit = lit > 255 ? 255 : lit; // Clamp to [0..255] again
return lit;
}
SVECTOR outPos;
#define MAXVERTEXBUF 512
SVECTOR vertexBuffer[MAXVERTEXBUF] = { 0 }; // Temporary buffer for precalculating vertex data
#define SCTR(v, i) (short)((scale[i]*(v)->position[i]+translate[i])>>10)
void drawModel(MATRIX *view_matrix, ps1mdl_t *model, ps1skin_t *skin, int frameCounter)
{
ps1texture_t *tex;
short u0, u1, u2, uoffs, voffs;
int p;
short halfSkinWidth = model->header->skinWidth >> 1;
unsigned short vertexCount = model->header->vertexCount;
unsigned short triangleCount = model->header->triangleCount;
int frameNum = frameCounter % model->header->frameCount;
ps1mdl_vertex_t *baseVertex = &model->vertices[frameNum * vertexCount];
int *scale = model->header->scale;
int *translate = model->header->translate;
// Model world position and rotation
MATRIX model_mtx;
RotMatrix(&model->rotation, &model_mtx);
TransMatrix(&model_mtx, &model->position);
// Adjust light direction by the model's rotation relative to the world
MATRIX light_mtx;
MulMatrix0(&light_dirs, &model_mtx, &light_mtx);
gte_SetLightMatrix(&light_mtx);
// Compose model matrix with view-projection matrix to obtain the final model-view-projection matrix
CompMatrixLV(view_matrix, &model_mtx, &model_mtx);
// TODO: this idea is good but it's missing the >> 12 fraction divide after transformation. Hmmmm...
// MATRIX mesh_mtx = identity;
// ScaleMatrixL(&mesh_mtx, (VECTOR*)scale);
// TransMatrix(&mesh_mtx, (VECTOR*)translate);
// CompMatrixLV(&model_mtx, &mesh_mtx, &mesh_mtx);
gte_SetRotMatrix(&model_mtx);
gte_SetTransMatrix(&model_mtx);
// Scale, translate and convert vertex data to SVECTOR ahead of time for all vertices.
// This eliminates duplicate calculations on vertices shared by multiple triangles.
SVECTOR *v = vertexBuffer;
for (unsigned short vertIdx = 0; vertIdx < vertexCount; ++vertIdx, ++baseVertex, ++v)
{
setVector(v, SCTR(baseVertex,0), SCTR(baseVertex,1), SCTR(baseVertex,2));
v->pad = baseVertex->normalIndex;
}
for (unsigned short triIdx = 0; triIdx < triangleCount; ++triIdx)
{
ps1mdl_triangle_t *tri = &model->triangles[triIdx];
SVECTOR *v0 = &vertexBuffer[tri->vertexIndex[0]];
SVECTOR *v1 = &vertexBuffer[tri->vertexIndex[1]];
SVECTOR *v2 = &vertexBuffer[tri->vertexIndex[2]];
gte_ldv3(v0, v1, v2);
gte_rtpt(); // Rotation, Translation and Perspective triplet (all three vertices at once)
// Normal clipping for backface culling
gte_nclip();
gte_stopz(&p);
if (p < 0)
continue;
// Average Z for depth sorting
gte_avsz3();
gte_stotz(&p);
unsigned short depth = p;//p >> 2;
if (depth < 0 || depth >= OTLEN)
continue;
ps1mdl_texcoord_t *tc0 = &model->texCoords[tri->vertexIndex[0]];
ps1mdl_texcoord_t *tc1 = &model->texCoords[tri->vertexIndex[1]];
ps1mdl_texcoord_t *tc2 = &model->texCoords[tri->vertexIndex[2]];
if (tri->frontFace)
{
u0 = tc0->u, u1 = tc1->u, u2 = tc2->u;
tex = &skin->front;
}
else
{
// Since we have the texture split into two parts, we need to correct the UVs that are *not* on the seam, instead of the other way round
u0 = tc0->u - !tc0->onSeam * halfSkinWidth;
u1 = tc1->u - !tc1->onSeam * halfSkinWidth;
u2 = tc2->u - !tc2->onSeam * halfSkinWidth;
tex = &skin->back;
}
// TODO: we could get rid of these silly offsets if we just ensure that textures are always page-aligned
uoffs = tex->uoffs, voffs = tex->voffs;
POLY_GT3 *poly = (POLY_GT3*)nextpri;
setPolyGT3(poly);
// Store transformed vertex coordinates in screen space
gte_stsxy3_gt3(poly);
// Copy some values for on-screen debugging
outPos.vx = poly->x0;
outPos.vy = poly->y0;
outPos.vz = p;
// Calculate vertex color based on normal
// TODO: we could probably speed this up by precalculating the lighting for each normal, if we need to draw more than 162 vertices
gte_ldrgb(&poly->r0);
gte_ldv3(anorms[v0->pad], anorms[v1->pad], anorms[v2->pad]);
gte_nct();
gte_strgb3(&poly->r0, &poly->r1, &poly->r2);
// Set texture parameters
setUV3(poly, uoffs + u0, voffs + tc0->v, uoffs + u1, voffs + tc1->v, uoffs + u2, voffs + tc2->v);
setClut(poly, tex->crect.x, tex->crect.y);
setTPage(poly, tex->mode & 0x3, 0, tex->prect.x, tex->prect.y);
addPrim(ot[db] + depth, poly);
nextpri += sizeof(POLY_GT3);
}
}
void drawStuff(int counter)
{
ClearOTagR(ot[db], OTLEN);
FntPrint(-1, "Image x %d y %d w %d h %d mode 0x%x\n", playerSkin.front.prect.x, playerSkin.front.prect.y, playerSkin.front.prect.w, playerSkin.front.prect.h, playerSkin.front.mode);
FntPrint(-1, "Model: %d tris, %d verts\n", playerModel.header->triangleCount, playerModel.header->vertexCount);
FntPrint(-1, "Trsf: (%d, %d, %d)\n", outPos.vx, outPos.vy, outPos.vz);
// Draw the last created text stream
FntFlush(-1);
gte_SetBackColor(48, 48, 48); // Ambient light color
gte_SetColorMatrix(&light_cols); // Light color (up to three different lights)
MATRIX proj_matrix = quake_swizzle; // Swizzle coordinates so that everything is in Quake coordinate system (Z up)
ScaleMatrixL(&proj_matrix, &aspect_scale); // Apply aspect ratio correction for the current resolution
MATRIX view_matrix;
SVECTOR trot = { 0, 0, 0, 0 }; // TODO: inverse camera rotation (in Quake coordinates)
VECTOR tpos = { 0, 400, -100 }; // TODO: inverse camera position (in Quake coordinates)
RotMatrix(&trot, &view_matrix); // Set camera rotation part of the view matrix
ApplyMatrixLV(&view_matrix, &tpos, &tpos); // Apply camera rotation to camera position
TransMatrix(&view_matrix, &tpos); // Apply transformed position to the translation part of the view matrix
// Compose view and projection matrices to obtain a combined view-projection matrix
CompMatrixLV(&proj_matrix, &view_matrix, &view_matrix);
drawModel(&view_matrix, &playerModel, &playerSkin, counter >> 2);
drawModel(&view_matrix, &shamblerModel, &shamblerSkin, counter >> 2);
//drawModel(&view_matrix, &ogreModel, &ogreSkin, counter >> 2);
drawModel(&view_matrix, &quadModel, &quadSkin, 0);
}
// Main function, program entrypoint
int main(int argc, const char *argv[])
{
int counter;
// Init stuff
init();
// Main loop
counter = 0;
while(1)
{
quadModel.rotation.vz = (counter << 5) % ONE;
quadModel.position.vz = (isin(counter << 6) * 32) >> 12;
drawStuff(counter);
// Update display
display();
// Increment the counter
counter++;
}
return 0;
}