# 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 ```c #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: ```c 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 ```c // 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": ```c 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. ```c // 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: ```c 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: ```c 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 ```c 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 ```c 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 ```c #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](Handling-Input.md) - Make your programs interactive!