/* * 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 #include #include #include #include #include #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; }