/** * Copyright (c) 2025 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "display/render.h" #include "assert/assert.h" #include "util/memory.h" uint8_t RENDER_BACKGROUND_X = 0; uint8_t RENDER_BACKGROUND_Y = 0; uint8_t RENDER_STATUS = 0; bool_t RENDER_DISPLAY_ON = false; uint8_t RENDER_TILES[RENDER_TILE_COUNT] = { 0 }; uint8_t RENDER_BACKGROUND_TILEMAP[RENDER_BACKGROUND_TILE_COUNT] = { 0 }; Color RENDER_PALETTE[RENDER_PALETTE_COLOR_COUNT] = { { 102, 191, 47, 255 }, { 78, 146, 35, 255 }, { 48, 89, 22, 255 }, { 18, 33, 8, 255 } }; RenderTexture2D RENDER_BACKBUFFER; RenderTexture2D RENDER_TILES_TEXTURE; void renderInit(void) { InitWindow(RENDER_WIDTH_PIXELS * 4, RENDER_HEIGHT_PIXELS * 4, "Dusk"); SetTargetFPS(60); SetWindowState( FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_VSYNC_HINT ); // Create back buffer for rendering. RENDER_BACKBUFFER = LoadRenderTexture( RENDER_WIDTH_PIXELS, RENDER_HEIGHT_PIXELS ); // Create texture to hold the tile data. RENDER_TILES_TEXTURE = LoadRenderTexture( RENDER_TILE_COUNT * RENDER_TILE_WIDTH, RENDER_TILE_HEIGHT ); } void renderVsync() { int32_t x, y, i; BeginDrawing(); if(RENDER_DISPLAY_ON) { // Update the texture with the new tile data BeginTextureMode(RENDER_TILES_TEXTURE); i = 0; for(i = 0; i < RENDER_TILE_COUNT; i++) { uint8_t *tile = RENDER_TILES + (i * RENDER_TILE_BYTES_PER_TILE); // For each pixel in the tile... for(y = 0; y < RENDER_TILE_HEIGHT; y++) { uint8_t low = tile[y * RENDER_TILE_BYTES_PER_ROW]; uint8_t high = tile[y * RENDER_TILE_BYTES_PER_ROW + 1]; for(x = 0; x < RENDER_TILE_WIDTH; x++) { uint8_t loBit = (low >> (7 - x)) & 1; uint8_t hiBit = (high >> (7 - x)) & 1; uint8_t paletteIndex = (hiBit << 1) | loBit; // Draw the pixel to the texture DrawPixel( (i * RENDER_TILE_WIDTH) + x, y, RENDER_PALETTE[paletteIndex] ); } } } EndTextureMode(); // Clear the back buffer BeginTextureMode(RENDER_BACKBUFFER); ClearBackground(RENDER_PALETTE[0]); // Render background tiles i = 0; for(y = 0; y < RENDER_BACKGROUND_ROWS; y++) { for(x = 0; x < RENDER_BACKGROUND_COLUMNS; x++) { // Get the tile index from the tilemap uint8_t tileIndex = RENDER_BACKGROUND_TILEMAP[i++]; DrawTexturePro( RENDER_TILES_TEXTURE.texture, (Rectangle){ .x = ((int32_t)tileIndex) * RENDER_TILE_WIDTH, .y = 0, .width = RENDER_TILE_WIDTH, .height = -RENDER_TILE_HEIGHT }, (Rectangle){ ((int32_t)x * RENDER_TILE_WIDTH) - RENDER_BACKGROUND_X, ((int32_t)y * RENDER_TILE_HEIGHT) - RENDER_BACKGROUND_Y, RENDER_TILE_WIDTH, RENDER_TILE_HEIGHT }, (Vector2){ 0, 0 }, 0.0f, WHITE ); } } // Render the back buffer to the screen EndTextureMode(); ClearBackground(WHITE); // Keep aspect and center the render int32_t renderWidth, renderHeight, renderX, renderY; const int32_t width = GetScreenWidth(); const int32_t height = GetScreenHeight(); if (RENDER_WIDTH_PIXELS * height > RENDER_HEIGHT_PIXELS * width) { renderWidth = width; renderHeight = (RENDER_HEIGHT_PIXELS * width) / RENDER_WIDTH_PIXELS; renderX = 0; renderY = (height - renderHeight) / 2; } else { renderWidth = (RENDER_WIDTH_PIXELS * height) / RENDER_HEIGHT_PIXELS; renderHeight = height; renderX = (width - renderWidth) / 2; renderY = 0; } DrawTexturePro( RENDER_BACKBUFFER.texture, (Rectangle) { 0, 0, RENDER_WIDTH_PIXELS, -RENDER_HEIGHT_PIXELS }, (Rectangle) { renderX, renderY, renderWidth, renderHeight }, (Vector2) { 0, 0 }, 0.0f, WHITE ); } EndDrawing(); if(WindowShouldClose()) RENDER_STATUS |= RENDER_SHOULD_EXIT; } void renderDispose() { UnloadRenderTexture(RENDER_TILES_TEXTURE); CloseWindow(); } void renderDisplayOn(void) { RENDER_DISPLAY_ON = true; } void renderDisplayOff(void) { RENDER_DISPLAY_ON = false; } void renderTilesBuffer( const uint8_t index, const uint8_t count, const uint8_t *tiles ) { assertTrue(count > 0, "Count must be greater than zero"); assertNotNull(tiles, "Tiles pointer must not be null"); // Copy data to fake vram memoryCopy( &RENDER_TILES[index * RENDER_TILE_BYTES_PER_TILE], tiles, count * RENDER_TILE_BYTES_PER_TILE ); } void renderBackgroundTilesBufferRectangle( const uint8_t xPos, const uint8_t yPos, const uint8_t width, const uint8_t height, const uint8_t *tiles ) { uint8_t w = width, h = height, x = xPos, y = yPos; assertNotNull(tiles, "Tiles pointer must not be null"); // Clamp x and y to tilemap bounds if (x >= RENDER_BACKGROUND_COLUMNS) x = RENDER_BACKGROUND_COLUMNS - 1; if (y >= RENDER_BACKGROUND_ROWS) y = RENDER_BACKGROUND_ROWS - 1; // Clamp width and height so we don't overflow past edges if (x + w > RENDER_BACKGROUND_COLUMNS) { w = RENDER_BACKGROUND_COLUMNS - x; } if (y + h > RENDER_BACKGROUND_ROWS) { h = RENDER_BACKGROUND_ROWS - y; } if (w == 0 || h == 0) return; for (uint8_t row = 0; row < h; row++) { memoryCopy( &RENDER_BACKGROUND_TILEMAP[(y + row) * RENDER_BACKGROUND_COLUMNS + x], &tiles[row * width], w ); } }