Skip to content

Using Textures

NtinosTheGamer2324 edited this page Mar 7, 2026 · 1 revision

Using Textures in NodGL

Textures are images stored in memory that you can draw on screen. They're essential for sprites, backgrounds, and UI elements!

What is a Texture?

A texture is like a digital picture that lives in your computer's memory (or GPU memory). You can:

  • Create textures from raw pixel data
  • Draw them anywhere on screen
  • Scale and rotate them
  • Use them as sprites in games

Creating a Simple Texture

Step 1: Allocate Pixel Data

#include "NodGL.h"
#include "libc.h"

int md_main(long argc, char **argv) {
    NodGL_Device device;
    NodGL_Context ctx;
    NodGL_CreateDevice(NodGL_FEATURE_LEVEL_1_0, &device, &ctx, NULL);
    
    // Create a 64x64 pixel array
    uint32_t *pixels = malloc(64 * 64 * sizeof(uint32_t));
    
    // Fill it with a gradient
    for (int y = 0; y < 64; y++) {
        for (int x = 0; x < 64; x++) {
            uint8_t red = (x * 255) / 63;
            uint8_t green = (y * 255) / 63;
            pixels[y * 64 + x] = NodGL_ColorARGB(255, red, green, 0);
        }
    }
    
    // Create texture description
    NodGL_TextureDesc tex_desc = {0};
    tex_desc.width = 64;
    tex_desc.height = 64;
    tex_desc.format = NodGL_FORMAT_R8G8B8A8_UNORM;
    tex_desc.mip_levels = 1;
    tex_desc.initial_data = pixels;
    tex_desc.initial_data_size = 64 * 64 * 4;
    
    // Create the texture
    NodGL_Texture texture;
    if (NodGL_CreateTexture(device, &tex_desc, &texture) != NodGL_OK) {
        printf("Failed to create texture!\n");
        free(pixels);
        return 1;
    }
    
    // Draw it on screen at position (100, 100)
    NodGL_ClearContext(ctx, NodGL_CLEAR_COLOR, 0xFF000000, 1.0f, 0);
    NodGL_DrawTexture(ctx, texture, 0, 0, 100, 100, 64, 64);
    NodGL_PresentContext(ctx, 1);
    
    // Wait
    for (volatile int i = 0; i < 5000000; i++);
    
    // Cleanup
    NodGL_ReleaseResource(device, texture);
    free(pixels);
    NodGL_ReleaseDevice(device);
    return 0;
}

Drawing Textures

The NodGL_DrawTexture function copies a texture to the screen:

NodGL_DrawTexture(ctx, texture, src_x, src_y, dst_x, dst_y, width, height);

Parameters:

  • texture - The texture to draw
  • src_x, src_y - Which part of the texture to use (usually 0, 0 for the whole thing)
  • dst_x, dst_y - Where to draw it on screen
  • width, height - Size to draw (same as texture size = no scaling)

Example: Draw Multiple Copies

// Draw the same texture in different places
NodGL_DrawTexture(ctx, texture, 0, 0, 50, 50, 64, 64);
NodGL_DrawTexture(ctx, texture, 0, 0, 200, 100, 64, 64);
NodGL_DrawTexture(ctx, texture, 0, 0, 350, 150, 64, 64);

Using a Backbuffer (For Drawing Pixels)

Sometimes you want to draw individual pixels. Use a mappable texture as a "backbuffer":

NodGL_Device device;
NodGL_Context ctx;
NodGL_CreateDevice(NodGL_FEATURE_LEVEL_1_0, &device, &ctx, NULL);

// Get screen size
uint32_t screen_w, screen_h;
NodGL_GetScreenResolution(device, &screen_w, &screen_h);

// Create a texture the size of the screen
NodGL_TextureDesc tex_desc = {0};
tex_desc.width = screen_w;
tex_desc.height = screen_h;
tex_desc.format = NodGL_FORMAT_R8G8B8A8_UNORM;
tex_desc.mip_levels = 1;

NodGL_Texture backbuffer;
NodGL_CreateTexture(device, &tex_desc, &backbuffer);

// Map it to get a pointer to the pixels
uint32_t *pixels;
uint32_t pitch;
NodGL_MapResource(ctx, backbuffer, (void**)&pixels, &pitch);

// Now you can write pixels directly!
for (int y = 0; y < screen_h; y++) {
    for (int x = 0; x < screen_w; x++) {
        // Calculate position (pitch is in bytes, we need pixels)
        int pixel_index = y * (pitch / 4) + x;
        
        // Draw a simple pattern
        uint8_t r = (x * 255) / screen_w;
        uint8_t g = (y * 255) / screen_h;
        pixels[pixel_index] = NodGL_ColorARGB(255, r, g, 0);
    }
}

// Display the backbuffer
NodGL_DrawTexture(ctx, backbuffer, 0, 0, 0, 0, screen_w, screen_h);
NodGL_PresentContext(ctx, 1);

Understanding Pitch

Pitch is the number of bytes per row of pixels. It's not always the same as width * 4 because the GPU might add padding.

// WRONG - Assumes no padding
int index = y * width + x;  

// CORRECT - Use pitch
int index = y * (pitch / 4) + x;

Sprite Drawing Helper

Here's a useful function for drawing sprites:

typedef struct {
    NodGL_Texture texture;
    int width;
    int height;
} Sprite;

void draw_sprite(NodGL_Context ctx, Sprite *sprite, int x, int y) {
    NodGL_DrawTexture(ctx, sprite->texture, 
        0, 0,                          // Source: whole sprite
        x, y,                          // Destination
        sprite->width, sprite->height  // Size
    );
}

// Usage:
Sprite player_sprite;
// ... create texture ...
draw_sprite(ctx, &player_sprite, 100, 200);

Sprite Sheets

A sprite sheet is one big texture containing multiple sprites:

void draw_sprite_from_sheet(NodGL_Context ctx, NodGL_Texture sheet,
                            int sprite_x, int sprite_y,      // Which sprite in the sheet
                            int sprite_w, int sprite_h,      // Size of each sprite
                            int screen_x, int screen_y)      // Where to draw
{
    NodGL_DrawTexture(ctx, sheet, 
        sprite_x, sprite_y,           // Source position in sheet
        screen_x, screen_y,           // Destination on screen
        sprite_w, sprite_h            // Size
    );
}

// Example: Draw character animation frames
// Assuming sprites are 32x32 in a horizontal row
for (int frame = 0; frame < 4; frame++) {
    NodGL_ClearContext(ctx, NodGL_CLEAR_COLOR, 0xFF000000, 1.0f, 0);
    
    // Draw frame from sprite sheet
    draw_sprite_from_sheet(ctx, sprite_sheet,
        frame * 32, 0,    // Each frame is 32 pixels apart
        32, 32,           // Sprite size
        200, 200);        // Draw at center
    
    NodGL_PresentContext(ctx, 1);
    
    // Delay between frames
    for (volatile int i = 0; i < 500000; i++);
}

Creating Common Patterns

Checkerboard Texture

uint32_t *pixels = malloc(64 * 64 * sizeof(uint32_t));

for (int y = 0; y < 64; y++) {
    for (int x = 0; x < 64; x++) {
        int square = ((x / 8) + (y / 8)) % 2;
        pixels[y * 64 + x] = square ? 0xFFFFFFFF : 0xFF000000;
    }
}

Circle Sprite

uint32_t *pixels = malloc(32 * 32 * sizeof(uint32_t));

for (int y = 0; y < 32; y++) {
    for (int x = 0; x < 32; x++) {
        int dx = x - 16;
        int dy = y - 16;
        
        if (dx * dx + dy * dy <= 16 * 16) {
            pixels[y * 32 + x] = 0xFFFF0000;  // Red circle
        } else {
            pixels[y * 32 + x] = 0x00000000;  // Transparent
        }
    }
}

Performance Tips

  1. Create textures once - Don't create them every frame!
  2. Use backbuffer for pixel drawing - Faster than many small FillRect calls
  3. Batch draws - Draw all sprites, then present once
  4. Release textures - Always call NodGL_ReleaseResource when done

Complete Example: Bouncing Ball

#include "NodGL.h"
#include "libc.h"

int md_main(long argc, char **argv) {
    NodGL_Device device;
    NodGL_Context ctx;
    NodGL_CreateDevice(NodGL_FEATURE_LEVEL_1_0, &device, &ctx, NULL);
    
    uint32_t screen_w, screen_h;
    NodGL_GetScreenResolution(device, &screen_w, &screen_h);
    
    // Create ball sprite
    uint32_t *ball_pixels = malloc(32 * 32 * sizeof(uint32_t));
    for (int y = 0; y < 32; y++) {
        for (int x = 0; x < 32; x++) {
            int dx = x - 16, dy = y - 16;
            if (dx * dx + dy * dy <= 16 * 16) {
                ball_pixels[y * 32 + x] = 0xFFFF0000;
            } else {
                ball_pixels[y * 32 + x] = 0x00000000;
            }
        }
    }
    
    NodGL_TextureDesc desc = {32, 32, NodGL_FORMAT_R8G8B8A8_UNORM, 1, ball_pixels, 32*32*4};
    NodGL_Texture ball_tex;
    NodGL_CreateTexture(device, &desc, &ball_tex);
    free(ball_pixels);
    
    // Ball physics
    int x = 100, y = 100;
    int dx = 3, dy = 2;
    
    for (int i = 0; i < 500; i++) {
        // Update position
        x += dx;
        y += dy;
        
        // Bounce off edges
        if (x <= 0 || x >= screen_w - 32) dx = -dx;
        if (y <= 0 || y >= screen_h - 32) dy = -dy;
        
        // Draw
        NodGL_ClearContext(ctx, NodGL_CLEAR_COLOR, 0xFF000020, 1.0f, 0);
        NodGL_DrawTexture(ctx, ball_tex, 0, 0, x, y, 32, 32);
        NodGL_PresentContext(ctx, 1);
        
        for (volatile int j = 0; j < 50000; j++);
    }
    
    NodGL_ReleaseResource(device, ball_tex);
    NodGL_ReleaseDevice(device);
    return 0;
}

Next: Handling Input - Make your programs interactive!

Clone this wiki locally