/* * 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_player128[]; extern u_long tim_player16bpp[]; extern u_long tim_shambler[]; extern u_long mdl_player[]; TIM_IMAGE playerTex; 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_player128, &playerTex); loadTexture(tim_player16bpp, &playerTex); 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(playerTex.mode & 0x3, 0, playerTex.prect->x, playerTex.prect->y); //draw[1].tpage = getTPage(playerTex.mode & 0x3, 0, playerTex.prect->x, playerTex.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 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 += 32; // 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", playerTex.prect->x, playerTex.prect->y, playerTex.prect->w, playerTex.prect->h, playerTex.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]; // 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; addColoredTriangle(v0->position[0], 240 - v0->position[2], v1->position[0], 240 - v1->position[2], v2->position[0], 240 - v2->position[2], rgb0, rgb1, rgb2, (unsigned char)depth); } 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, &playerTex); addTexturedSprite(20, 140, 80, 80, &playerTex); 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; }