Rendering Pipeline Overview
Vertex Data → Vertex Shader → Rasterization → Fragment Shader → Framebuffer
│ │ │
│ │ │
Attributes Transformation Texturing
Indices Lighting Lighting
Per-vertex Per-pixel
Complete Rendering Setup
Initialize RSX
Set up the graphics context:
#include <rsx/rsx.h>
gcmContextData *context;
void *host_addr = memalign(1024*1024, 128*1024*1024);
rsxInit(&context, 0x80000, 128*1024*1024, host_addr);
Configure Display
Set up framebuffers and video output:
// Configure video mode
videoState state;
videoGetState(0, 0, &state);
// Allocate framebuffers
u32 display_width = 1920;
u32 display_height = 1080;
u32 color_pitch = display_width * 4;
for (int i = 0; i < 2; i++) {
color_buffer[i] = rsxMemalign(64, color_pitch * display_height);
rsxAddressToOffset(color_buffer[i], &color_offset[i]);
gcmSetDisplayBuffer(i, color_offset[i], color_pitch,
display_width, display_height);
}
gcmSetFlipMode(GCM_FLIP_VSYNC);
Load Shaders
Load and initialize vertex and fragment programs:
#include "my_shader_vpo.h"
#include "my_shader_fpo.h"
rsxVertexProgram *vpo = (rsxVertexProgram*)my_shader_vpo;
rsxFragmentProgram *fpo = (rsxFragmentProgram*)my_shader_fpo;
void *vp_ucode, *fp_ucode;
u32 vpsize, fpsize;
rsxVertexProgramGetUCode(vpo, &vp_ucode, &vpsize);
rsxFragmentProgramGetUCode(fpo, &fp_ucode, &fpsize);
// Fragment program must be in RSX memory
u32 *fp_buffer = (u32*)rsxMemalign(64, fpsize);
memcpy(fp_buffer, fp_ucode, fpsize);
u32 fp_offset;
rsxAddressToOffset(fp_buffer, &fp_offset);
Set Render State
Configure rendering state:
// Enable depth testing
rsxSetDepthTestEnable(context, GCM_TRUE);
rsxSetDepthFunc(context, GCM_LESS);
rsxSetDepthWriteEnable(context, 1);
// Set shading mode
rsxSetShadeModel(context, GCM_SHADE_MODEL_SMOOTH);
// Set culling
rsxSetCullFaceEnable(context, GCM_TRUE);
rsxSetCullFace(context, GCM_CULL_BACK);
rsxSetFrontFace(context, GCM_FRONTFACE_CCW);
// Set color mask
rsxSetColorMask(context, GCM_COLOR_MASK_R |
GCM_COLOR_MASK_G |
GCM_COLOR_MASK_B |
GCM_COLOR_MASK_A);
Setting Up the Viewport
Configure the viewport and scissor rectangle:void setupViewport(u16 width, u16 height) {
f32 scale[4], offset[4];
f32 min = 0.0f, max = 1.0f;
// Calculate viewport transform
scale[0] = width * 0.5f;
scale[1] = height * -0.5f; // Negative for Y-down
scale[2] = (max - min) * 0.5f;
scale[3] = 0.0f;
offset[0] = width * 0.5f;
offset[1] = height * 0.5f;
offset[2] = (max + min) * 0.5f;
offset[3] = 0.0f;
// Set viewport
rsxSetViewport(context, 0, 0, width, height, min, max,
scale, offset);
// Set scissor (clipping rectangle)
rsxSetScissor(context, 0, 0, width, height);
// Set viewport clip
for (int i = 0; i < 8; i++) {
rsxSetViewportClip(context, i, width, height);
}
}
Configuring the Surface
Set the render target before drawing:void setRenderTarget(u32 buffer_index) {
gcmSurface surface;
memset(&surface, 0, sizeof(gcmSurface));
// Color buffer
surface.type = GCM_SURFACE_TYPE_LINEAR;
surface.antiAlias = GCM_SURFACE_CENTER_1;
surface.colorFormat = GCM_SURFACE_A8R8G8B8;
surface.colorTarget = GCM_SURFACE_TARGET_0;
surface.colorLocation[0] = GCM_LOCATION_RSX;
surface.colorOffset[0] = color_offset[buffer_index];
surface.colorPitch[0] = color_pitch;
// Depth buffer
surface.depthFormat = GCM_SURFACE_ZETA_Z16;
surface.depthLocation = GCM_LOCATION_RSX;
surface.depthOffset = depth_offset;
surface.depthPitch = depth_pitch;
// Dimensions
surface.width = display_width;
surface.height = display_height;
surface.x = 0;
surface.y = 0;
rsxSetSurface(context, &surface);
}
Clearing Buffers
Clear color and depth buffers before rendering:void clearBuffers() {
// Set clear color (ARGB)
u32 color = 0xFF000000; // Black with full alpha
rsxSetClearColor(context, color);
// Set clear depth/stencil
rsxSetClearDepthStencil(context, 0xffffff00);
// Clear all buffers
rsxClearSurface(context, GCM_CLEAR_R | // Red
GCM_CLEAR_G | // Green
GCM_CLEAR_B | // Blue
GCM_CLEAR_A | // Alpha
GCM_CLEAR_Z | // Depth
GCM_CLEAR_S); // Stencil
}
Clear Flags
Fromrsx/gcm_sys.h:128-141:
GCM_CLEAR_Z // Clear depth buffer
GCM_CLEAR_S // Clear stencil buffer
GCM_CLEAR_R // Clear red channel
GCM_CLEAR_G // Clear green channel
GCM_CLEAR_B // Clear blue channel
GCM_CLEAR_A // Clear alpha channel
GCM_CLEAR_M // Clear all channels + depth + stencil
Loading Textures
Texture Structure
Fromrsx/gcm_sys.h:877-963:
typedef struct _gcmTexture {
u8 format; // Texture format
u8 mipmap; // Mipmap enable
u8 dimension; // 1D, 2D, or 3D
u8 cubemap; // Cubemap enable
u32 remap; // Color component remapping
u16 width; // Width in pixels
u16 height; // Height in pixels
u16 depth; // Depth (for 3D textures)
u8 location; // Memory location
u32 pitch; // Pitch in bytes
u32 offset; // Memory offset
} gcmTexture;
Loading a Texture
void loadTexture(u8 unit, void *data, u32 width, u32 height) {
gcmTexture texture;
u32 pitch = width * 4; // 4 bytes per pixel
// Allocate texture memory
u32 *tex_buffer = (u32*)rsxMemalign(128, pitch * height);
memcpy(tex_buffer, data, pitch * height);
u32 tex_offset;
rsxAddressToOffset(tex_buffer, &tex_offset);
// Configure texture
texture.format = GCM_TEXTURE_FORMAT_A8R8G8B8 |
GCM_TEXTURE_FORMAT_LIN;
texture.mipmap = 1;
texture.dimension = GCM_TEXTURE_DIMS_2D;
texture.cubemap = GCM_FALSE;
// Color remapping (ARGB -> ARGB)
texture.remap =
(GCM_TEXTURE_REMAP_TYPE_REMAP << GCM_TEXTURE_REMAP_TYPE_B_SHIFT) |
(GCM_TEXTURE_REMAP_TYPE_REMAP << GCM_TEXTURE_REMAP_TYPE_G_SHIFT) |
(GCM_TEXTURE_REMAP_TYPE_REMAP << GCM_TEXTURE_REMAP_TYPE_R_SHIFT) |
(GCM_TEXTURE_REMAP_TYPE_REMAP << GCM_TEXTURE_REMAP_TYPE_A_SHIFT) |
(GCM_TEXTURE_REMAP_COLOR_B << GCM_TEXTURE_REMAP_COLOR_B_SHIFT) |
(GCM_TEXTURE_REMAP_COLOR_G << GCM_TEXTURE_REMAP_COLOR_G_SHIFT) |
(GCM_TEXTURE_REMAP_COLOR_R << GCM_TEXTURE_REMAP_COLOR_R_SHIFT) |
(GCM_TEXTURE_REMAP_COLOR_A << GCM_TEXTURE_REMAP_COLOR_A_SHIFT);
texture.width = width;
texture.height = height;
texture.depth = 1;
texture.location = GCM_LOCATION_RSX;
texture.pitch = pitch;
texture.offset = tex_offset;
// Invalidate texture cache
rsxInvalidateTextureCache(context, GCM_INVALIDATE_TEXTURE);
// Load texture
rsxLoadTexture(context, unit, &texture);
// Set texture control
rsxTextureControl(context, unit, GCM_TRUE, 0<<8, 12<<8,
GCM_TEXTURE_MAX_ANISO_1);
// Set texture filter
rsxTextureFilter(context, unit, 0,
GCM_TEXTURE_LINEAR,
GCM_TEXTURE_LINEAR,
GCM_TEXTURE_CONVOLUTION_QUINCUNX);
// Set texture wrap mode
rsxTextureWrapMode(context, unit,
GCM_TEXTURE_CLAMP_TO_EDGE,
GCM_TEXTURE_CLAMP_TO_EDGE,
GCM_TEXTURE_CLAMP_TO_EDGE,
0, GCM_TEXTURE_ZFUNC_LESS, 0);
}
Texture Formats
Fromrsx/gcm_sys.h:251-304:
- Uncompressed
- Compressed
- Floating Point
- Depth
GCM_TEXTURE_FORMAT_B8 // 8-bit grayscale
GCM_TEXTURE_FORMAT_A1R5G5B5 // 16-bit 1-5-5-5
GCM_TEXTURE_FORMAT_A4R4G4B4 // 16-bit 4-4-4-4
GCM_TEXTURE_FORMAT_R5G6B5 // 16-bit 5-6-5
GCM_TEXTURE_FORMAT_A8R8G8B8 // 32-bit 8-8-8-8
GCM_TEXTURE_FORMAT_G8B8 // 16-bit two-channel
GCM_TEXTURE_FORMAT_DXT1 // 4x4 pixels to 8 bytes
GCM_TEXTURE_FORMAT_DXT23 // 4x4 pixels to 16 bytes (DXT3)
GCM_TEXTURE_FORMAT_DXT45 // 4x4 pixels to 16 bytes (DXT5)
GCM_TEXTURE_FORMAT_W16_Z16_Y16_X16_FLOAT // 16-bit float per channel
GCM_TEXTURE_FORMAT_W32_Z32_Y32_X32_FLOAT // 32-bit float per channel
GCM_TEXTURE_FORMAT_X32_FLOAT // Single 32-bit float
GCM_TEXTURE_FORMAT_Y16_X16_FLOAT // Two 16-bit floats
GCM_TEXTURE_FORMAT_DEPTH24_D8 // 24-bit fixed + 8-bit dummy
GCM_TEXTURE_FORMAT_DEPTH24_D8_FLOAT // 24-bit float + 8-bit dummy
GCM_TEXTURE_FORMAT_DEPTH16 // 16-bit fixed
GCM_TEXTURE_FORMAT_DEPTH16_FLOAT // 16-bit float
Texture Filtering
Filter modes fromrsx/gcm_sys.h:384-391:
GCM_TEXTURE_NEAREST // Point sampling
GCM_TEXTURE_LINEAR // Bilinear
GCM_TEXTURE_NEAREST_MIPMAP_NEAREST // Nearest mipmap
GCM_TEXTURE_LINEAR_MIPMAP_NEAREST // Bilinear, nearest mipmap
GCM_TEXTURE_NEAREST_MIPMAP_LINEAR // Nearest, lerp mipmaps
GCM_TEXTURE_LINEAR_MIPMAP_LINEAR // Trilinear
GCM_TEXTURE_CONVOLUTION_MIN // Convolution minify
GCM_TEXTURE_CONVOLUTION_MAG // Convolution magnify
Texture Wrap Modes
Fromrsx/gcm_sys.h:397-404:
GCM_TEXTURE_REPEAT // Repeat texture
GCM_TEXTURE_MIRRORED_REPEAT // Mirror repeat
GCM_TEXTURE_CLAMP_TO_EDGE // Clamp to edge
GCM_TEXTURE_BORDER // Border color
GCM_TEXTURE_CLAMP // Clamp to [0,1]
GCM_TEXTURE_MIRROR_ONCE_CLAMP_TO_EDGE // Mirror once then clamp
GCM_TEXTURE_MIRROR_ONCE_CLAMP_TO_BORDER// Mirror once then border
GCM_TEXTURE_MIRROR_ONCE_CLAMP // Mirror once then clamp
Rendering Geometry
Vertex Buffer Setup
Create and bind vertex buffers:typedef struct {
float pos[3];
float nrm[3];
float u, v;
} Vertex;
Vertex *vertices;
u16 *indices;
u32 vertex_count, index_count;
void setupGeometry() {
// Allocate in RSX memory
vertices = (Vertex*)rsxMemalign(128,
vertex_count * sizeof(Vertex));
indices = (u16*)rsxMemalign(128,
index_count * sizeof(u16));
// Fill with data...
// Get offsets
u32 voffset, ioffset;
rsxAddressToOffset(vertices, &voffset);
rsxAddressToOffset(indices, &ioffset);
}
Drawing Primitives
Draw geometry using index buffers:void drawMesh() {
u32 offset;
// Bind vertex attributes
rsxAddressToOffset(&vertices[0].pos, &offset);
rsxBindVertexArrayAttrib(context, GCM_VERTEX_ATTRIB_POS, 0,
offset, sizeof(Vertex), 3,
GCM_VERTEX_DATA_TYPE_F32,
GCM_LOCATION_RSX);
rsxAddressToOffset(&vertices[0].nrm, &offset);
rsxBindVertexArrayAttrib(context, GCM_VERTEX_ATTRIB_NORMAL, 0,
offset, sizeof(Vertex), 3,
GCM_VERTEX_DATA_TYPE_F32,
GCM_LOCATION_RSX);
rsxAddressToOffset(&vertices[0].u, &offset);
rsxBindVertexArrayAttrib(context, GCM_VERTEX_ATTRIB_TEX0, 0,
offset, sizeof(Vertex), 2,
GCM_VERTEX_DATA_TYPE_F32,
GCM_LOCATION_RSX);
// Draw indexed triangles
rsxAddressToOffset(indices, &offset);
rsxDrawIndexArray(context, GCM_TYPE_TRIANGLES,
offset, index_count,
GCM_INDEX_TYPE_16B,
GCM_LOCATION_RSX);
}
Primitive Types
Fromrsx/gcm_sys.h:209-228:
GCM_TYPE_POINTS // Individual points
GCM_TYPE_LINES // Line segments
GCM_TYPE_LINE_LOOP // Connected lines, closed
GCM_TYPE_LINE_STRIP // Connected lines, open
GCM_TYPE_TRIANGLES // Individual triangles
GCM_TYPE_TRIANGLE_STRIP // Triangle strip
GCM_TYPE_TRIANGLE_FAN // Triangle fan
GCM_TYPE_QUADS // Individual quads
GCM_TYPE_QUAD_STRIP // Quad strip
GCM_TYPE_POLYGON // Polygon
Fragment Shader Setup
Fragment shaders (pixel shaders) process each pixel.Fragment Shader Example
Fromsamples/graphics/rsxtest/shaders/diffuse_specular_shader.fcg:
void main
(
float4 position : TEXCOORD0,
float3 normal : TEXCOORD1,
float2 texcoord : TEXCOORD2,
uniform float3 globalAmbient,
uniform float3 lightPosition,
uniform float3 lightColor,
uniform float3 eyePosition,
uniform float3 Kd, // Diffuse coefficient
uniform float3 Ks, // Specular coefficient
uniform float shininess,
uniform sampler2D texture,
out float4 oColor : COLOR
)
{
float3 N = normalize(normal);
// Diffuse lighting
float3 L = normalize(lightPosition - position.xyz);
float diffuseLight = max(dot(N, L), 0.0f);
float3 diffuse = Kd * lightColor * diffuseLight;
// Specular lighting
float3 V = normalize(eyePosition - position.xyz);
float3 H = normalize(L + V);
float specularLight = pow(max(dot(H, N), 0.0f), shininess);
if (diffuseLight <= 0) specularLight = 0;
float3 specular = Ks * specularLight;
// Sample texture
float3 texColor = tex2D(texture, texcoord).xyz;
// Combine
float3 color = texColor * (diffuse + globalAmbient) + specular;
oColor = float4(color, 1.0f);
}
Loading Fragment Program
void setupFragmentShader() {
void *fp_ucode;
u32 fpsize;
// Get microcode
rsxFragmentProgramGetUCode(fpo, &fp_ucode, &fpsize);
// Fragment program MUST be in RSX memory
u32 *fp_buffer = (u32*)rsxMemalign(64, fpsize);
memcpy(fp_buffer, fp_ucode, fpsize);
u32 fp_offset;
rsxAddressToOffset(fp_buffer, &fp_offset);
// Get shader parameters
eyePosition = rsxFragmentProgramGetConst(fpo, "eyePosition");
globalAmbient = rsxFragmentProgramGetConst(fpo, "globalAmbient");
litPosition = rsxFragmentProgramGetConst(fpo, "lightPosition");
litColor = rsxFragmentProgramGetConst(fpo, "lightColor");
Kd = rsxFragmentProgramGetConst(fpo, "Kd");
Ks = rsxFragmentProgramGetConst(fpo, "Ks");
spec = rsxFragmentProgramGetConst(fpo, "shininess");
// Get texture unit
textureUnit = rsxFragmentProgramGetAttrib(fpo, "texture");
}
Setting Fragment Parameters
void setFragmentParameters() {
float eye[3] = {0.0f, 0.0f, 20.0f};
float ambient[3] = {0.1f, 0.1f, 0.1f};
float lightPos[3] = {250.0f, 150.0f, 150.0f};
float lightCol[3] = {0.95f, 0.95f, 0.95f};
float diffuse[3] = {0.5f, 0.0f, 0.0f};
float specular[3] = {0.7f, 0.6f, 0.6f};
float shine = 17.8954f;
// Set parameters (note: requires fp_offset and location)
rsxSetFragmentProgramParameter(context, fpo, eyePosition,
eye, fp_offset, GCM_LOCATION_RSX);
rsxSetFragmentProgramParameter(context, fpo, globalAmbient,
ambient, fp_offset, GCM_LOCATION_RSX);
rsxSetFragmentProgramParameter(context, fpo, litPosition,
lightPos, fp_offset, GCM_LOCATION_RSX);
rsxSetFragmentProgramParameter(context, fpo, litColor,
lightCol, fp_offset, GCM_LOCATION_RSX);
rsxSetFragmentProgramParameter(context, fpo, Kd,
diffuse, fp_offset, GCM_LOCATION_RSX);
rsxSetFragmentProgramParameter(context, fpo, Ks,
specular, fp_offset, GCM_LOCATION_RSX);
rsxSetFragmentProgramParameter(context, fpo, spec,
&shine, fp_offset, GCM_LOCATION_RSX);
// Load fragment program
rsxLoadFragmentProgramLocation(context, fpo, fp_offset,
GCM_LOCATION_RSX);
}
Complete Render Loop
Fromsamples/graphics/rsxtest/source/main.cpp:555-606:
u32 curr_fb = 0;
int main() {
// Initialize...
init_screen();
init_shader();
init_texture();
// Create geometry
sphere = createSphere(3.0f, 32, 32);
// Projection matrix
Matrix4 P = transpose(
Matrix4::perspective(DEGTORAD(45.0f),
aspect_ratio, 1.0f, 3000.0f)
);
while (running) {
// Wait for previous flip
waitflip();
// Set render target
setRenderTarget(curr_fb);
// Clear buffers
rsxSetClearColor(context, 0x00000000);
rsxSetClearDepthStencil(context, 0xffffff00);
rsxClearSurface(context, GCM_CLEAR_R | GCM_CLEAR_G |
GCM_CLEAR_B | GCM_CLEAR_A |
GCM_CLEAR_S | GCM_CLEAR_Z);
// Set viewport
setupViewport(display_width, display_height);
// Set depth control
rsxSetZMinMaxControl(context, 0, 1, 1);
// Calculate matrices
Matrix4 viewMatrix = Matrix4::lookAt(eye_pos, eye_dir, up_vec);
Matrix4 modelMatrix = rotX * rotY;
Matrix4 modelViewMatrix = transpose(viewMatrix * modelMatrix);
// Bind vertex data
bindVertexData(sphere);
// Load vertex program
rsxLoadVertexProgram(context, vpo, vp_ucode);
rsxSetVertexProgramParameter(context, vpo, projMatrix,
(float*)&P);
rsxSetVertexProgramParameter(context, vpo, mvMatrix,
(float*)&modelViewMatrix);
// Set fragment parameters
setFragmentParameters();
// Set texture
loadTexture(textureUnit->index, tex_data, 128, 128);
// Set user clip planes
rsxSetUserClipPlaneControl(context,
GCM_USER_CLIP_PLANE_DISABLE,
GCM_USER_CLIP_PLANE_DISABLE,
GCM_USER_CLIP_PLANE_DISABLE,
GCM_USER_CLIP_PLANE_DISABLE,
GCM_USER_CLIP_PLANE_DISABLE,
GCM_USER_CLIP_PLANE_DISABLE);
// Draw
u32 offset;
rsxAddressToOffset(&sphere->indices[0], &offset);
rsxDrawIndexArray(context, GCM_TYPE_TRIANGLES,
offset, sphere->cnt_indices,
GCM_INDEX_TYPE_16B, GCM_LOCATION_RSX);
// Flip
flip(curr_fb);
curr_fb = !curr_fb;
}
return 0;
}
Depth Testing
Configure depth buffer operations:// Enable depth test
rsxSetDepthTestEnable(context, GCM_TRUE);
// Set depth function
rsxSetDepthFunc(context, GCM_LESS);
// Enable depth writes
rsxSetDepthWriteEnable(context, 1);
// Set depth bounds
rsxSetZMinMaxControl(context, 0, 1, 1);
Depth Functions
Fromrsx/gcm_sys.h:143-158:
GCM_NEVER // Never pass
GCM_LESS // Pass if less
GCM_EQUAL // Pass if equal
GCM_LEQUAL // Pass if less or equal
GCM_GREATER // Pass if greater
GCM_NOTEQUAL // Pass if not equal
GCM_GEQUAL // Pass if greater or equal
GCM_ALWAYS // Always pass
Blending
Configure alpha blending:// Enable blending
rsxSetBlendEnable(context, GCM_TRUE);
// Set blend equation
rsxSetBlendEquation(context, GCM_FUNC_ADD, GCM_FUNC_ADD);
// Set blend function
rsxSetBlendFunc(context,
GCM_SRC_ALPHA, // Source factor
GCM_ONE_MINUS_SRC_ALPHA, // Dest factor
GCM_SRC_ALPHA, // Alpha source
GCM_ONE_MINUS_SRC_ALPHA); // Alpha dest
Blend Factors
Fromrsx/gcm_sys.h:523-552:
GCM_ZERO
GCM_ONE
GCM_SRC_COLOR
GCM_ONE_MINUS_SRC_COLOR
GCM_SRC_ALPHA
GCM_ONE_MINUS_SRC_ALPHA
GCM_DST_ALPHA
GCM_ONE_MINUS_DST_ALPHA
GCM_DST_COLOR
GCM_ONE_MINUS_DST_COLOR
GCM_SRC_ALPHA_SATURATE
GCM_CONSTANT_COLOR
GCM_ONE_MINUS_CONSTANT_COLOR
GCM_CONSTANT_ALPHA
GCM_ONE_MINUS_CONSTANT_ALPHA
Blend Equations
Fromrsx/gcm_sys.h:554-569:
GCM_FUNC_ADD // Src + Dst
GCM_FUNC_MIN // min(Src, Dst)
GCM_FUNC_MAX // max(Src, Dst)
GCM_FUNC_SUBTRACT // Src - Dst
GCM_FUNC_REVERSE_SUBTRACT // Dst - Src
GCM_FUNC_REVERSE_SUBTRACT_SIGNED // Dst - Src (signed)
GCM_FUNC_ADD_SIGNED // Src + Dst (signed)
GCM_FUNC_REVERSE_ADD_SIGNED // Dst + Src (signed)
Culling
Configure face culling:// Enable culling
rsxSetCullFaceEnable(context, GCM_TRUE);
// Set which face to cull
rsxSetCullFace(context, GCM_CULL_BACK);
// Set front face winding
rsxSetFrontFace(context, GCM_FRONTFACE_CCW);
rsx/gcm_sys.h:160-165:
GCM_CULL_FRONT // Cull front faces
GCM_CULL_BACK // Cull back faces
GCM_CULL_ALL // Cull all faces
rsx/gcm_sys.h:174-177:
GCM_FRONTFACE_CW // Clockwise
GCM_FRONTFACE_CCW // Counter-clockwise
Flip and Sync
Present the rendered frame:void flip(u32 buffer) {
// Queue flip command
gcmSetFlip(context, buffer);
// Flush command buffer
rsxFlushBuffer(context);
// Wait for flip to be queued
gcmSetWaitFlip(context);
}
void waitflip() {
// Wait for flip to complete
while (gcmGetFlipStatus() != 0) {
usleep(200);
}
gcmResetFlipStatus();
}
Performance Tips
Next Steps
Font Rendering
Render text and UI elements
Advanced Techniques
Shadows, post-processing, and advanced effects