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.
391 lines
12 KiB
391 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 SCREENWIDTH 320
|
|
#define SCREENHEIGHT 240
|
|
|
|
#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, 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], 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;
|
|
}
|