tutorial:context

Basics

In this tutorial we will be walking through the setup for the PSP's sceGu boilerplate code. In the future, tutorials will use GU2GL which will handle the code we setup today. The series of graphics tutorials will use C as the primary programming language. This is not due to favoritism, but the code for sceGu will be nearly identical for all of the toolchains.

This tutorial will create an application that properly initializes sceGu and renders a blank green screen. This code references callbacks which were set up in previous tutorials. The source for this tutorial can be found here.

#include "../common/callbacks.h"
 
// Include Graphics Libraries
#include <pspdisplay.h>
#include <pspgu.h>
#include <pspgum.h>
 
 
// PSP Module Info
PSP_MODULE_INFO("Context Sample", 0, 1, 1);
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU);
 
// Global variables
int running = 1;
 
int main() {
    // Boilerplate
    SetupCallbacks();
 
    //Main program loop
    while(running){
         //TODO: Add code
    }
 
    // Exit Game
    sceKernelExitGame();
    return 0;
}

First things first, let's go ahead and define some variables in global scope.

// Define PSP Width / Height
#define PSP_BUF_WIDTH (512)
#define PSP_SCR_WIDTH (480)
#define PSP_SCR_HEIGHT (272)
 
// PSP GE List
static unsigned int __attribute__((aligned(16))) list[262144];

First, the PSP Buffer Width is 512 instead of 480. This is because buffers need to be in terms of the nearest power of 2. The closest power of 2 to 480 that fully fits it is 512. This is required for the GE's rendering functions. The screen width and height are pretty self-explanatory as the PSP's screen is 480 x 272. The other variable we've defined here is the GE List. This list is basically a big buffer where all the GPU commands are queued internally by sceGu using sendCommandi / sendCommandf. This list uses the compiler extension to guarantee its alignment, as the list is required to be 16 aligned by the GE. If you don't do this, your code may fail to run properly. We'll be passing in the list into the sceGu library to use it later.

While we're at it, let's also setup some basic VRAM functions. The PSP VRAM is a separate area of memory (2MB or 4MB depending on your model). By default it is 2MB and you can request a larger size on PSP-2000+ models with `sceGeEdramSetSize`

// Get Memory Size
static unsigned int getMemorySize(unsigned int width, unsigned int height, unsigned int psm){
    unsigned int size = width * height;
    switch (psm){
        case GU_PSM_T4:
            return size / 2;
        case GU_PSM_T8:
            return size;
 
        case GU_PSM_5650:
	case GU_PSM_5551:
	case GU_PSM_4444:
	case GU_PSM_T16:
            return size * 2;
 
        case GU_PSM_8888:
        case GU_PSM_T32:
            return size * 4;
 
        default:
            return 0;
    }
}
 
// Vram Buffer Request
void* getStaticVramBuffer(unsigned int width, unsigned int height, unsigned int psm) {
    static unsigned int staticOffset = 0;
 
    unsigned int memSize = getMemorySize(width,height,psm);
    void* result = (void*)staticOffset;
    staticOffset += memSize;
 
    return result;
}
 
// Vram Texture Request
void* getStaticVramTexture(unsigned int width, unsigned int height, unsigned int psm){
    void* result = getStaticVramBuffer(width,height,psm);
    return (void*)(((unsigned int)result) + ((unsigned int)sceGeEdramGetAddr()));
}

While this code may look daunting, there's actually fairly little going on. The getMemorySize() method computes the total byte requirements for a texture given the format. The result is equal to width * height * bytesPerPixel. The bytes per pixel is then determined by the format and the result is returned.

The second function is getStaticVramBuffer() which returns a void* starting from 0 – this is useful for some functions in sceGu which need the VRAM to be relative to 0. getStaticVramTexture() translates this value to the physical hardware VRAM address using sceGeEdramGetAddr(). getStaticVramBuffer() has a statically managed counter that starts from 0 and tracks VRAM allocations. The counter is incremented based on the size of the allocation. It returns the pointer as needed.

These functions aren't immmediately clear why they're useful, but the next snippet will make it clear. This next snippet initializes sceGu

// Initialize Graphics
void initGraphics() {
        void* fbp0 = getStaticVramBuffer(PSP_BUF_WIDTH,PSP_SCR_HEIGHT,GU_PSM_8888);
	void* fbp1 = getStaticVramBuffer(PSP_BUF_WIDTH,PSP_SCR_HEIGHT,GU_PSM_8888);
	void* zbp = getStaticVramBuffer(PSP_BUF_WIDTH,PSP_SCR_HEIGHT,GU_PSM_4444);
 
	sceGuInit();
 
	sceGuStart(GU_DIRECT,list);
	sceGuDrawBuffer(GU_PSM_8888,fbp0,PSP_BUF_WIDTH);
	sceGuDispBuffer(PSP_SCR_WIDTH,PSP_SCR_HEIGHT,fbp1,PSP_BUF_WIDTH);
	sceGuDepthBuffer(zbp,PSP_BUF_WIDTH);
	sceGuOffset(2048 - (PSP_SCR_WIDTH/2),2048 - (PSP_SCR_HEIGHT/2));
	sceGuViewport(2048,2048,PSP_SCR_WIDTH,PSP_SCR_HEIGHT);
	sceGuDepthRange(65535,0);
	sceGuScissor(0,0,PSP_SCR_WIDTH,PSP_SCR_HEIGHT);
	sceGuEnable(GU_SCISSOR_TEST);
	sceGuDepthFunc(GU_GEQUAL);
	sceGuEnable(GU_DEPTH_TEST);
	sceGuFrontFace(GU_CW);
	sceGuShadeModel(GU_SMOOTH);
	sceGuEnable(GU_CULL_FACE);
	sceGuEnable(GU_TEXTURE_2D);
	sceGuEnable(GU_CLIP_PLANES);
	sceGuFinish();
	sceGuSync(0,0);
 
	sceDisplayWaitVblankStart();
	sceGuDisplay(GU_TRUE);
}

There's a ton to break down in this section. First things first, we request 3 buffers. 2 for our Double Buffered rendering and one for the Depth Buffer (Z Buffer). These buffers use the PSP's buffer width + the height. The depth buffer does not need full color mode (8-bits per RGBA).

Next, we call the GU initialization function and then start the display list. This needs to be done to set an initial state to the GE. The next couple of commands sceGuDrawBuffer sceGuDispBuffer and sceGuDepthBuffer use the buffers we made earlier to setup our rendering. The draw buffer is the buffer which is currently being drawn to. The display buffer is the buffer currently being displayed to the screen, and the depth buffer is a constant buffer used for depth testing in the Draw Buffer. The draw buffer is in 8-bit RGBA mode and we pass the buffer and the width. The display buffer is width x height in size to be shown, uses the second buffer, and needs the buffer width. The depth buffer just needs our depth buffer and the Buffer width.

The next part is a little difficult to explain – we offset the render coordinates to 2048 2048 and center the screen there and then setup the viewport about those coordinates. This is standard procedure – I'd love to have a better explanation than that, so if you know, please comment. The next part is the depth range. This sets up the absolute near and far values that we can use. This helps with the precision of the depth range. We use the max first and the minimum second because the PSP has an inverted depth buffer – it's reversed order. We also use Scissor to make sure the only part that will be rendered is the PSP screen itself (we don't waste GPU on filling data in the unseen portion of the buffer). We also enable Scissor Testing because of this.

Since the depthbuffer is inverted, instead of LEQUAL which is often used on PC, we use GEQUAL as the standard depth testing function. We also enable depth testing here. Some applications and some objects should not be depth tested, but that's a bigger consideration depending on your application. Depth testing for example may not be particularly relevant for 2D games especially.

The next couple of items are just settings changes. We set the shading model to smooth which enables Gouraud shading instead of Flat shading. This allows faces to be shaded per vertex and partially allows things like color blending. The Front Face is set to be Clockwise here, and this is to set our winding order for backface culling. Both of these topics have further explanation beyond the scope of tutorial. We then enable Textures, Clipping, and Backface Culling.

Finally we finish out the list recording with sceGuFinish(), call sceGuSync to wait until the GE (GPU) is done processing these changes. We also then wait for the start of the next VBlank frame and enable the display output.

Below are a few functions you'd probably be interested in using. They wrap useful functionality that you would need to work with the sceGu library.

// End Graphics
void termGraphics() {
    sceGuTerm();
}
// Start Frame
void startFrame() {
    sceGuStart(GU_DIRECT, list);
}
// End Frame
void endFrame() {
    sceGuFinish();
    sceGuSync(0, 0);
    sceDisplayWaitVblankStart();
    sceGuSwapBuffers();
}

Terminating graphics is pretty straightforward, unlike the setup, and you call this at the end of an application.

Starting the frame follows a similar logic we saw earlier in our program by starting the GE Display List, and endFrame compliments that with the finish and synchronize alongside waiting for VSync and most importantly swapping the display buffers. The swapping of the display and draw buffers presents the actual content you rendered to the screen.

int main() {
    // Boilerplate
    SetupCallbacks();
 
    // Initialize Graphics
    initGraphics();
 
 
    //Main program loop
    while(running){
        startFrame();
 
        //Clear background to Green
        sceGuClearColor(0xFF00FF00);
        sceGuClear(GU_COLOR_BUFFER_BIT);
 
        endFrame();
    }
 
    // Terminate Graphics
    termGraphics();
 
    // Exit Game
    sceKernelExitGame();
    return 0;
}

The new main function has both the initialization and termination of graphics library, starting and ending frames in the main program loop, and some code which clears the background. In this code we set the clear color to Green 0xFF00FF00. This follows the order 0xAABBGGRR for your RGBA values. There exists multiple utility functions like GU_RGBA and GU_COLOR to convert from more human readable color values to this format. The format is actually RGBA, but due to the endianness of the system it presents itself in reverse order. We then call sceGuClear which clears specific buffers based on the flags you present. In this case we do GU_COLOR_BUFFER_BIT but others existed such as STENCIL_BUFFER_BIT and DEPTH_BUFFER_BIT. With this code, we have now made our first program using sceGu!

For programmers experienced in some degree of OpenGL programming, the PSP's sceGu acts a lot like OpenGL 1.2 – in fact, if you change sceGu to gl, it works nearly identically with some exceptions that will be covered later. This is why I [Iridescence] created a wrapper called GU2GL which makes this leap. There is no overhead to the GU2GL translation as it uses the `#define` C macro to convert the names. It also implements all of the aforementioned utility functions in this tutorial. This code is pretty simple to understand and I'll provide a sample below. The rest of the tutorial will use GU2GL for simplicity's sake, but you can still write code in sceGu by un-translating the names and using the functions in this tutorial.

// Important definition for the single file header. Do this ONLY ONE TIME in your code base.
#define GUGL_IMPLEMENTATION
#include <gu2gl.h>
 
// Remove VRAM functions, GU2GL implements them with the same interface
 
int main() {
    // Boilerplate
    SetupCallbacks();
 
    // Initialize Graphics
    guglInit(list);
 
 
    //Main program loop
    while(running){
        guglStartFrame(list, GL_FALSE);
 
        //Clear background to Green
        glClearColor(0xFF00FF00);
        glClear(GL_COLOR_BUFFER_BIT);
 
        guglSwapBuffers(GL_TRUE, GL_FALSE);
    }
 
    // Terminate Graphics
    guglTerm();
 
 
    // Exit Game
    sceKernelExitGame();
    return 0;
}

The only notable differences here are from StartFrame and SwapBuffers. StartFrame() has two parameters, the display list, and whether or not you're in dialog mode. Unless you're using the sceUtilityDialogs or the OSK you will not need to worry about the dialogs. Use GL_FALSE here. SwapBuffers() takes two parameters, VSYNC, and dialog mode. It is recommended for released applications and stability that you use VSync, but for performance testing that VSync is disabled.

Congratulations! You've written your first Graphics API program on PSP! In the next tutorial we'll cover drawing objects to the screen.

  • tutorial/context.txt
  • Last modified: 2022/09/11 20:53
  • by iridescence