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.
 
 

388 lines
12 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 NUMVERTEXNORMALS 162
static int anorms[NUMVERTEXNORMALS][3] = {
#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][32768]; // Primitive buffer, just a raw buffer of bytes to use as a pool for primitives
char *nextpri;
extern u_long tim_player_f[];
extern u_long tim_player_b[];
extern u_long tim_player16bpp[];
extern u_long tim_shambler[];
extern u_long mdl_player[];
// The player texture is divided into two pieces, so that each part is aligned to their own texture page
TIM_IMAGE playerFrontTex, playerBackTex;
TIM_IMAGE shamblerTex;
ps1mdl_t model;
void loadTexture(const u_long* tim, 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);
}
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;
}
// 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);
// Define display environments, first on top and second on bottom
SetDefDispEnv(&disp[0], 0, 0, 320, 240);
SetDefDispEnv(&disp[1], 0, 240, 320, 240);
// Define drawing environments, first on bottom and second on top
SetDefDrawEnv(&draw[0], 0, 240, 320, 240);
SetDefDrawEnv(&draw[1], 0, 0, 320, 240);
// Set and enable clear color
setRGB0(&draw[0], 96, 0, 96);
setRGB0(&draw[1], 96, 0, 96);
draw[0].isbg = 1;
draw[1].isbg = 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];
loadTexture(tim_player_f, &playerFrontTex);
loadTexture(tim_player_b, &playerBackTex);
//loadTexture(tim_player16bpp, &playerFrontTex);
loadTexture(tim_shambler, &shamblerTex);
loadModel(mdl_player, &model);
// 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 << 16 | lit << 8 | lit;
}
void drawStuff(int counter)
{
ClearOTagR(ot[db], OTLEN);
// Print the obligatory hello world and counter to show that the
// program isn't locking up to the last created text stream
FntPrint(-1, "COUNTER=%d, SIN=%d\n", counter, isin(counter));
FntPrint(-1, "Image x %d y %d w %d h %d mode 0x%x\n", playerFrontTex.prect->x, playerFrontTex.prect->y, playerFrontTex.prect->w, playerFrontTex.prect->h, playerFrontTex.mode);
FntPrint(-1, "Model: %d tris, %d verts\n", model.header->triangleCount, model.header->vertexCount);
// Draw the last created text stream
FntFlush(-1);
int frameNum = (counter >> 2) % model.header->frameCount;
int vertOffs = frameNum * model.header->vertexCount;
for (int triIdx = 0; triIdx < model.header->triangleCount; ++triIdx)
{
ps1mdl_triangle_t *tri = &model.triangles[triIdx];
ps1mdl_vertex_t *v0 = &model.vertices[tri->vertexIndex[0] + vertOffs];
ps1mdl_vertex_t *v1 = &model.vertices[tri->vertexIndex[1] + vertOffs];
ps1mdl_vertex_t *v2 = &model.vertices[tri->vertexIndex[2] + vertOffs];
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]];
// Use normal indices to generate a greyscale value. Good for just giving the polygons some gradients.
unsigned int n0 = v0->normalIndex, n1 = v1->normalIndex, n2 = v2->normalIndex;
unsigned int rgb0 = (n0 << 16) | (n0 << 8) | n0;
unsigned int rgb1 = (n1 << 16) | (n1 << 8) | n1;
unsigned int rgb2 = (n2 << 16) | (n2 << 8) | n2;
// Visualize normals as RGB
rgb0 = normToRGB(anorms[v0->normalIndex]);
rgb1 = normToRGB(anorms[v1->normalIndex]);
rgb2 = normToRGB(anorms[v2->normalIndex]);
// Calculate some actual Lambert shading based on a static light vector
rgb0 = fakeLight(anorms[v0->normalIndex]);
rgb1 = fakeLight(anorms[v1->normalIndex]);
rgb2 = fakeLight(anorms[v2->normalIndex]);
// Normally you'd have GTE do this but we're just going for a quick hack now
unsigned short depth = ((unsigned short)v0->position[1] + (unsigned short)v1->position[1] + (unsigned short)v2->position[1]) / 3;
// 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
short u0 = !tri->frontFace && !tc0->onSeam ? tc0->u - (model.header->skinWidth >> 1) : tc0->u;
short u1 = !tri->frontFace && !tc1->onSeam ? tc1->u - (model.header->skinWidth >> 1) : tc1->u;
short u2 = !tri->frontFace && !tc2->onSeam ? tc2->u - (model.header->skinWidth >> 1) : tc2->u;
addGouraudTexturedTriangle(
v0->position[0], 240 - v0->position[2],
v1->position[0], 240 - v1->position[2],
v2->position[0], 240 - v2->position[2],
(unsigned char)depth,
rgb0, rgb1, rgb2,
u0, tc0->v, u1, tc1->v, u2, tc2->v, tri->frontFace ? &playerFrontTex : &playerBackTex);
}
return;
int r, g, b;
lerpcol(255, 255, 0, 255, 0, 0, (icos(counter * 32) + 4096) >> 1, &r, &g, &b);
addTile(32, 32, 64, 64, r, g, b);
lerpcol(0, 255, 255, 0, 255, 0, (icos(counter * 24 + 512) + 4096) >> 1, &r, &g, &b);
addTile(128, 160, 48, 48, r, g, b);
addColoredTriangle(260, 140, 220, 220, 300, 220, 0xFF0000, 0x00FF00, 0x0000FF, 0);
addTexturedTriangle(260, 40, 220, 120, 300, 120, 0, 94, 74, 124, 58, 1, &playerFrontTex);
addTexturedSprite(20, 140, 80, 80, &playerFrontTex);
addTexturedSprite(80, 40, 154, 114, &shamblerTex);
}
// Main function, program entrypoint
int main(int argc, const char *argv[])
{
int counter;
// Init stuff
init();
// Main loop
counter = 0;
while(1)
{
drawStuff(counter);
// Update display
display();
// Increment the counter
counter++;
}
return 0;
}