tutorial:drawing

Result

This tutorial will create an application that draws colored triangles and squares to a black screen and utilizes indices. This code references callbacks and functions which were set up in previous tutorials. The source for this tutorial can be found here. There is also a pure sceGu implementation there.

#include "../../common/callbacks.h"
#define GUGL_IMPLEMENTATION
#include <gu2gl.h>
 
// PSP Module Info
PSP_MODULE_INFO("Triangle Sample", 0, 1, 1);
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU);
 
// Global variables
int running = 1;
static unsigned int __attribute__((aligned(16))) list[262144];
 
int main() {
    // Boilerplate
    SetupCallbacks();
 
    // Initialize Graphics
    guglInit(list);
 
    //Main program loop
    while(running){
        guglStartFrame(list, GL_FALSE);
 
        //Clear background to Black
        glClearColor(0xFF000000);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
 
        guglSwapBuffers(GL_TRUE, GL_FALSE);
    }
 
    // Terminate Graphics
    guglTerm();
 
    // Exit Game
    sceKernelExitGame();
    return 0;
}

First things first, let's go ahead and make sure we have some default matrices set for our transformations. We will cover in more depth what matrices do later. In short, the Projection Matrix handles whether or not we are in 2D Orthographic mode or 3D Perspective mode. The View Matrix handles camera rotations and translation. The Model Matrix handles individual objects and how they are transformed. To make sure these have valid values, and customize it, we should set this up.

int main() {
    // ...
    guglInit(list);
 
    // Initialize Matrices
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-16.0f / 9.0f, 16.0f / 9.0f, -1.0f, 1.0f, -10.0f, 10.0f);
 
    glMatrixMode(GL_VIEW);
    glLoadIdentity();
 
    glMatrixMode(GL_MODEL);
    glLoadIdentity();
    // ...
}

First things first, we have to set the matrix mode for the matrix that we wish to modify using `glMatrixMode()` (`sceGumMatrixMode()`). This lets us make sure we are modifying the proper matrix. We then use `glLoadIdentity()` (`sceGumLoadIdentity()`) to set a matrix to its default value (known as the identity matrix, which remains neutral in matrix multiplication). We first start with the Projection matrix which is the only one we wish to ensure has the proper values. We set up an orthographic (2D) projection using the `glOrtho()` (`sceGumOrtho()`) to set up the projection. The parameters in order is left, right, bottom, top, near, and far. These are the minimum and maximum values on the X, Y, and Z axes in respective order. This defines your coordinate system for 2D. For example, if you wanted the origin in the bottom left (as is standard), you would use `glOrtho(0, 480, 0, 272, -10, 10)` where the Z is (mostly) irrelevant. In our case we set it to be [-16/9, 16/9] for X axis [-1, 1] for Y axis, [-10, 10] for Z axis.

In our application, we're only going to be drawing 2D objects today. Therefore, depth testing isn't a desired feature, as well as a texture since well, we have none. To disable this, add the following code into your loop.

        //...
        guglStartFrame(list, GL_FALSE);
 
        // We're doing a 2D, Non-Textured render 
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_TEXTURE_2D);
        //...

First thing's first, let's take a look at vertices, the individual points that make up any geometric shape. In computer graphics, almost everything is always drawn using triangles, because they're simple and any other shape can be made with them. For practical terms, at minimum we must define a position of a vertex in 3D space. There are other properties such as normals, colors, and textures as well. The PSP requires us to submit our vertices in a specific format for the hardware. See Here.

For our application, we just want to draw a colored rectangle, so to do so, we'll define a struct.

struct Vertex
{
	unsigned int color;
	float x, y, z;
};

To then make an object, we can use these vertices.

struct Vertex __attribute__((aligned(16))) triangle[3] = {
    {0xFF00FF00, 0.35f, 0.0, -1.0f},
    {0xFF0000FF, -0.35f, 0.0, -1.0f},
    {0xFFFF0000, 0.0, 0.5f, -1.0f},
};

These coordinates are the 3 vertices of our triangle. They are specified in clockwise order from the original point. If you reverse the order, you'll find that nothing draws because of backface culling. But now to actually draw the triangle. In your program loop, insert this after clear and before swap buffers.

//...
glDrawElements(GL_TRIANGLES, GL_COLOR_8888 | GL_VERTEX_32BITF | GL_TRANSFORM_3D, 3, NULL, triangle);
//...

This call draws the elements of an indexed array. Since there's no indices provided, it just draws all the vertices in order. We specify that we are drawing with the triangle mode. Then, we specify our vertex type. We are using 8-bit RGBA color so we use COLOR_8888, and add VERTEX_32BITF for 32 bit floating point vertices. We also specify that we would like 3D transformations to be applied – this can, and realistically always should be specified. The next is the total number of indices we have. Given that we don't have indices, it's just the count of vertices. We then specify the index array which is NULL because we don't use them, and the vertex array, which is our triangle vertex array. And with that, you should have a triangle drawing in the middle of the screen!

Earlier, we mentioned indexed arrays. To see what they are and why they're useful, let's try changing our triangle to a square. To draw a square we need two right triangles. Below is the vertex data you would need.

struct Vertex __attribute__((aligned(16))) square[6] = {
    {0xFF00FF00, -0.25f, -0.25f, -1.0f},  // 0
    {0xFF0000FF, -0.25f, 0.25f, -1.0f},   // 1
    {0xFFFF0000, 0.25f, 0.25f, -1.0f},    // 2
 
    {0xFFFF0000, 0.25f, 0.25f, -1.0f},    // 2
    {0xFFFFFFFF, 0.25f, -0.25f, -1.0f},   // 3
    {0xFF00FF00, -0.25f, -0.25f, -1.0f},  // 0
};

The vertices are labeled here to show how they needed to be put together in order to form two validly-wound right triangles. This means we have a total of 6 vertices in our square array which we can draw by modifying the draw command.

    glDrawElements(GL_TRIANGLES, GL_COLOR_8888 | GL_VERTEX_32BITF | GL_TRANSFORM_3D, 6, NULL, square);

This now uses all 6 vertices and you should be presented with a square in the middle of your screen. This great, but we end up having 2 duplicated vertices. In this example, these 2 duplicated vertices cost us 16 bytes. Imagine what it would be like if you had a thousand-faced mesh – you'd have 50% more data than you actually need! To stop this, we can use indices which is an additional data array that specifies the order of vertices to draw. This can cut down the duplicated vertex problem.

For example, let's change the square to only just the vertices we need.

struct Vertex __attribute__((aligned(16))) square_indexed[4] = {
    {0xFF00FFFF, -0.25f, -0.25f, -1.0f}, //0
    {0xFFFF00FF, -0.25f, 0.25f, -1.0f},  //1
    {0xFFFFFF00, 0.25f, 0.25f, -1.0f},   //2
    {0xFF000000, 0.25f, -0.25f, -1.0f},  //3
};

This is good, but now if you try to compile and run, only one triangle will show up. To get indexed drawing to work, let's create the index array.

unsigned short __attribute__((aligned(16))) indices[6] = {
    0, 1, 2, 2, 3, 0
};

This is where we specify the order used earlier in the vertex sample to “redo” those vertices. If we change our `glDrawElements()` (`sceGumDrawArray()`) command, we get the following.

    glDrawElements(GL_TRIANGLES, GL_INDEX_16BIT | GL_COLOR_8888 | GL_VERTEX_32BITF | GL_TRANSFORM_3D, 6, indices, square_indexed);

We had to add one extra member to our vertex information which is that we were using INDEX_16BIT (unsigned short) for the array, and we had to specify the indices array itself. For only this case, we had 6 vertices (96 bytes) with no indices, with indices we had 4 vertices (64 bytes) with 6 unsigned short indices (12 bytes) for a total of (80 bytes). This only gets better the more we have. At 12 vertices (192 bytes) we save to (160 bytes) which is a 16% save on squares. On others like Cubes, cubes have 8 vertices, but for triangles would require 6 faces * 6 vertices = 36 vertices. In this case, indexed rendering saves 376 bytes, or 65%.

Congratulations! You've drawn a triangle, a square, and used indices to draw a square to the screen! Soon, you'll be able to do anything you want with PSP Graphics!

  • tutorial/drawing.txt
  • Last modified: 2022/09/11 22:28
  • by iridescence