Resolution Scaling¶
PixelRoot32 features a powerful Independent Resolution Scaling system. This allows the engine to render internally at a lower resolution (Logical Resolution) and then scale the final image to the display's actual hardware resolution (Physical Resolution).
Why use Resolution Scaling?¶
On microcontrollers like the ESP32, memory and processing power are limited. Rendering at a full 240x240 resolution consumes significant RAM and CPU cycles for every pixel drawn.
By using a lower logical resolution (e.g., 128x128): 1. Memory Savings: A 128x128 8bpp buffer uses ~16KB, while 240x240 uses ~57KB (72% reduction). 2. Performance Boost: Fewer pixels to process means more complex scenes and higher FPS. 3. Retro Aesthetic: Nearest-neighbor scaling preserves the pixel-art look perfectly.
Logical vs Physical Resolution¶
- Logical Resolution: The virtual canvas where your game logic, sprites, and UI are drawn.
- Physical Resolution: The actual pixel dimensions of your hardware display.
flowchart LR
subgraph Logical [Logical Resolution 128x128]
A[Game Logic] --> B[Renderer API]
B --> C[Internal Framebuffer]
end
subgraph Scaling [Hardware Scaling]
C --> D[Nearest Neighbor Scaler]
end
subgraph Physical [Physical Display 240x240]
D --> E[SPI/DMA Transfer]
E --> F[LCD Hardware]
end Configuration¶
Using Presets¶
The easiest way to configure scaling is using the ResolutionPresets helper.
#include <graphics/ResolutionPresets.h>
// Create a config for 128x128 logical resolution scaled to 240x240 physical
auto config = pixelroot32::graphics::ResolutionPresets::create(
pixelroot32::graphics::RES_128x128,
pixelroot32::graphics::ST7789
);
Manual Configuration¶
You can also specify custom dimensions in the DisplayConfig constructor.
pixelroot32::graphics::DisplayConfig config(
pixelroot32::graphics::ST7789, // Driver
0, // Rotation
240, 240, // Physical Width, Physical Height
160, 160 // Logical Width, Logical Height
);
Performance Impact¶
The following table shows estimated savings on an ESP32 for a standard 240x240 display:
| Logical Resolution | Memory (8bpp) | RAM Savings | FPS Gain (est.) |
|---|---|---|---|
| 240x240 (Full) | 57.6 KB | 0% | Baseline |
| 160x160 | 25.6 KB | ~55% | +30% |
| 128x128 | 16.4 KB | ~72% | +60% |
| 96x96 | 9.2 KB | ~84% | +100% |
Final FPS Analysis (v1.0.0)
At 240x240 physical pixels, the baseline limit was ~14 FPS due to SPI overhead. However, in v1.0.0, the engine achieves ~43 FPS stable at this resolution via:
- DMA Pipelining: No CPU stalls while waiting for the bus.
- Fast-Path Scaling: Direct 32-bit row copying without individual pixel processing.
To exceed 43 FPS, you must either: 1. Use a smaller physical display (128x128 physical = 60+ FPS). 2. Use a faster SPI clock (Experimental 80MHz = 60+ FPS, but may be unstable). 3. Reduce the rendering area using Logical Offsets.
Implementation Details¶
Nearest Neighbor Scaling¶
The engine uses a Nearest Neighbor algorithm optimized for ESP32. It avoids floating-point math by using pre-calculated Lookup Tables (LUTs).
On-the-fly Scaling¶
To save even more RAM, the engine does not maintain a physical-sized buffer. Instead, it scales the image line-by-line during the SPI DMA transfer. This means the only large buffer in memory is the small logical one.
Profiling¶
You can measure the performance of the scaling system by enabling the Debug Statistics Overlay. This provides real-time data on FPS, CPU load, and RAM usage directly on the screen.
See Engine - Debug Overlay for instructions on how to enable it.
Alternatively, you can enable low-level profiling in EngineConfig.h:
This will output the time taken for scaling and transfer to the Serial monitor: [PROFILING] Scaled Transfer: 12450 us (80 FPS max)
Best Practices¶
- Aspect Ratio: Keep the logical aspect ratio the same as the physical one to avoid stretching.
- Integer Multiples: For the sharpest results, try to use logical resolutions that are simple fractions of the physical resolution (e.g., 120x120 for a 240x240 screen).
- Hardware Recommendation: For high-action games requiring 30+ FPS (like the Metroidvania sample), the engine now supports up to ~43 FPS on 240x240 displays at 40MHz. While 128x128 physical displays can still reach 60+ FPS, the v1.0.0 optimizations (DMA Pipelining) make 240x240 displays perfectly viable for most games.
- UI Positioning: Use UIAnchorLayout to ensure your UI elements stay correctly positioned regardless of the logical resolution chosen.