-
Notifications
You must be signed in to change notification settings - Fork 0
Using Textures
NtinosTheGamer2324 edited this page Mar 7, 2026
·
1 revision
Textures are images stored in memory that you can draw on screen. They're essential for sprites, backgrounds, and UI elements!
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
#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;
}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)
// 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);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);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;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);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++);
}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;
}
}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
}
}
}- Create textures once - Don't create them every frame!
- Use backbuffer for pixel drawing - Faster than many small FillRect calls
- Batch draws - Draw all sprites, then present once
-
Release textures - Always call
NodGL_ReleaseResourcewhen done
#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!