docs: create RenderTexture overhaul design document
- Comprehensive design for Issue #6 implementation - Opt-in architecture to maintain backward compatibility - Phased implementation plan with clear milestones - Performance considerations and risk mitigation - API design for clipping and future effects Also includes Grid background color test
This commit is contained in:
parent
ff7cf25806
commit
5e4224a4f8
|
@ -0,0 +1,167 @@
|
|||
# RenderTexture Overhaul Design Document
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the design for implementing RenderTexture support across all UIDrawable classes in McRogueFace. This is Issue #6 and represents a major architectural change to the rendering system.
|
||||
|
||||
## Goals
|
||||
|
||||
1. **Automatic Clipping**: Children rendered outside parent bounds should be clipped
|
||||
2. **Off-screen Rendering**: Enable post-processing effects and complex compositing
|
||||
3. **Performance**: Cache static content, only re-render when changed
|
||||
4. **Backward Compatibility**: Existing code should continue to work
|
||||
|
||||
## Current State
|
||||
|
||||
### Classes Already Using RenderTexture:
|
||||
- **UIGrid**: Uses a 1920x1080 RenderTexture for compositing grid view
|
||||
- **SceneTransition**: Uses two 1024x768 RenderTextures for transitions
|
||||
- **HeadlessRenderer**: Uses RenderTexture for headless mode
|
||||
|
||||
### Classes Using Direct Rendering:
|
||||
- **UIFrame**: Renders box and children directly
|
||||
- **UICaption**: Renders text directly
|
||||
- **UISprite**: Renders sprite directly
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### 1. Opt-in Architecture
|
||||
|
||||
Not all UIDrawables need RenderTextures. We'll use an opt-in approach:
|
||||
|
||||
```cpp
|
||||
class UIDrawable {
|
||||
protected:
|
||||
// RenderTexture support (opt-in)
|
||||
std::unique_ptr<sf::RenderTexture> render_texture;
|
||||
sf::Sprite render_sprite;
|
||||
bool use_render_texture = false;
|
||||
bool render_dirty = true;
|
||||
|
||||
// Enable RenderTexture for this drawable
|
||||
void enableRenderTexture(unsigned int width, unsigned int height);
|
||||
void updateRenderTexture();
|
||||
};
|
||||
```
|
||||
|
||||
### 2. When to Use RenderTexture
|
||||
|
||||
RenderTextures will be enabled for:
|
||||
1. **UIFrame with clipping enabled** (new property: `clip_children = true`)
|
||||
2. **UIDrawables with effects** (future: shaders, blend modes)
|
||||
3. **Complex composites** (many children that rarely change)
|
||||
|
||||
### 3. Render Flow
|
||||
|
||||
```
|
||||
Standard Flow:
|
||||
render() → render directly to target
|
||||
|
||||
RenderTexture Flow:
|
||||
render() → if dirty → clear RT → render to RT → dirty = false
|
||||
→ draw RT sprite to target
|
||||
```
|
||||
|
||||
### 4. Dirty Flag Management
|
||||
|
||||
Mark as dirty when:
|
||||
- Properties change (position, size, color, etc.)
|
||||
- Children added/removed
|
||||
- Child marked as dirty (propagate up)
|
||||
- Animation frame
|
||||
|
||||
### 5. Size Management
|
||||
|
||||
RenderTexture size options:
|
||||
1. **Fixed Size**: Set at creation (current UIGrid approach)
|
||||
2. **Dynamic Size**: Match bounds, recreate on resize
|
||||
3. **Pooled Sizes**: Use standard sizes from pool
|
||||
|
||||
We'll use **Dynamic Size** with lazy creation.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Base Infrastructure (This PR)
|
||||
1. Add RenderTexture members to UIDrawable
|
||||
2. Add `enableRenderTexture()` method
|
||||
3. Implement dirty flag system
|
||||
4. Add `clip_children` property to UIFrame
|
||||
|
||||
### Phase 2: UIFrame Implementation
|
||||
1. Update UIFrame::render() to use RenderTexture when clipping
|
||||
2. Test with nested frames
|
||||
3. Verify clipping works correctly
|
||||
|
||||
### Phase 3: Performance Optimization
|
||||
1. Implement texture pooling
|
||||
2. Add dirty flag propagation
|
||||
3. Profile and optimize
|
||||
|
||||
### Phase 4: Extended Features
|
||||
1. Blur/glow effects using RenderTexture
|
||||
2. Viewport-based rendering (#8)
|
||||
3. Screenshot improvements
|
||||
|
||||
## API Changes
|
||||
|
||||
### Python API:
|
||||
```python
|
||||
# Enable clipping on frames
|
||||
frame.clip_children = True # New property
|
||||
|
||||
# Future: effects
|
||||
frame.blur_amount = 5.0
|
||||
sprite.glow_color = Color(255, 200, 100)
|
||||
```
|
||||
|
||||
### C++ API:
|
||||
```cpp
|
||||
// Enable RenderTexture
|
||||
frame->enableRenderTexture(width, height);
|
||||
frame->setClipChildren(true);
|
||||
|
||||
// Mark dirty
|
||||
frame->markDirty();
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
1. **Memory**: Each RenderTexture uses GPU memory (width * height * 4 bytes)
|
||||
2. **Creation Cost**: Creating RenderTextures is expensive, use pooling
|
||||
3. **Clear Cost**: Clearing large RenderTextures each frame is costly
|
||||
4. **Bandwidth**: Drawing to RenderTexture then to screen doubles bandwidth
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
1. All existing code continues to work (direct rendering by default)
|
||||
2. Gradually enable RenderTexture for specific use cases
|
||||
3. Profile before/after to ensure performance gains
|
||||
4. Document best practices
|
||||
|
||||
## Risks and Mitigation
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| Performance regression | Opt-in design, profile extensively |
|
||||
| Memory usage increase | Texture pooling, size limits |
|
||||
| Complexity increase | Clear documentation, examples |
|
||||
| Integration issues | Extensive testing with SceneTransition |
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. ✓ Frames can clip children to bounds
|
||||
2. ✓ No performance regression for direct rendering
|
||||
3. ✓ Scene transitions continue to work
|
||||
4. ✓ Memory usage is reasonable
|
||||
5. ✓ API is intuitive and documented
|
||||
|
||||
## Future Extensions
|
||||
|
||||
1. **Shader Support** (#106): RenderTextures enable post-processing shaders
|
||||
2. **Particle Systems** (#107): Render particles to texture for effects
|
||||
3. **Caching**: Static UI elements cached in RenderTextures
|
||||
4. **Resolution Independence**: RenderTextures for DPI scaling
|
||||
|
||||
## Conclusion
|
||||
|
||||
This design provides a foundation for professional rendering capabilities while maintaining backward compatibility and performance. The opt-in approach allows gradual adoption and testing.
|
|
@ -0,0 +1,126 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test Grid background color functionality"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_grid_background():
|
||||
"""Test Grid background color property"""
|
||||
print("Testing Grid Background Color...")
|
||||
|
||||
# Create a test scene
|
||||
mcrfpy.createScene("test")
|
||||
ui = mcrfpy.sceneUI("test")
|
||||
|
||||
# Create a grid with default background
|
||||
grid = mcrfpy.Grid(20, 15, grid_size=(20, 15))
|
||||
grid.x = 50
|
||||
grid.y = 50
|
||||
grid.w = 400
|
||||
grid.h = 300
|
||||
ui.append(grid)
|
||||
|
||||
# Add some tiles to see the background better
|
||||
for x in range(5, 15):
|
||||
for y in range(5, 10):
|
||||
point = grid.at(x, y)
|
||||
point.color = mcrfpy.Color(100, 150, 100)
|
||||
|
||||
# Add UI to show current background color
|
||||
info_frame = mcrfpy.Frame(500, 50, 200, 150,
|
||||
fill_color=mcrfpy.Color(40, 40, 40),
|
||||
outline_color=mcrfpy.Color(200, 200, 200),
|
||||
outline=2)
|
||||
ui.append(info_frame)
|
||||
|
||||
color_caption = mcrfpy.Caption(510, 60, "Background Color:")
|
||||
color_caption.font_size = 14
|
||||
color_caption.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
info_frame.children.append(color_caption)
|
||||
|
||||
color_display = mcrfpy.Caption(510, 80, "")
|
||||
color_display.font_size = 12
|
||||
color_display.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
info_frame.children.append(color_display)
|
||||
|
||||
# Activate the scene
|
||||
mcrfpy.setScene("test")
|
||||
|
||||
def run_tests(dt):
|
||||
"""Run background color tests"""
|
||||
mcrfpy.delTimer("run_tests")
|
||||
|
||||
print("\nTest 1: Default background color")
|
||||
default_color = grid.background_color
|
||||
print(f"Default: R={default_color.r}, G={default_color.g}, B={default_color.b}, A={default_color.a}")
|
||||
color_display.text = f"R:{default_color.r} G:{default_color.g} B:{default_color.b}"
|
||||
|
||||
def test_set_color(dt):
|
||||
mcrfpy.delTimer("test_set")
|
||||
print("\nTest 2: Set background to blue")
|
||||
grid.background_color = mcrfpy.Color(20, 40, 100)
|
||||
new_color = grid.background_color
|
||||
print(f"✓ Set to: R={new_color.r}, G={new_color.g}, B={new_color.b}")
|
||||
color_display.text = f"R:{new_color.r} G:{new_color.g} B:{new_color.b}"
|
||||
|
||||
def test_animation(dt):
|
||||
mcrfpy.delTimer("test_anim")
|
||||
print("\nTest 3: Manual color cycling")
|
||||
# Manually change color to test property is working
|
||||
colors = [
|
||||
mcrfpy.Color(200, 20, 20), # Red
|
||||
mcrfpy.Color(20, 200, 20), # Green
|
||||
mcrfpy.Color(20, 20, 200), # Blue
|
||||
]
|
||||
|
||||
color_index = [0] # Use list to allow modification in nested function
|
||||
|
||||
def cycle_red(dt):
|
||||
mcrfpy.delTimer("cycle_0")
|
||||
grid.background_color = colors[0]
|
||||
c = grid.background_color
|
||||
color_display.text = f"R:{c.r} G:{c.g} B:{c.b}"
|
||||
print(f"✓ Set to Red: R={c.r}, G={c.g}, B={c.b}")
|
||||
|
||||
def cycle_green(dt):
|
||||
mcrfpy.delTimer("cycle_1")
|
||||
grid.background_color = colors[1]
|
||||
c = grid.background_color
|
||||
color_display.text = f"R:{c.r} G:{c.g} B:{c.b}"
|
||||
print(f"✓ Set to Green: R={c.r}, G={c.g}, B={c.b}")
|
||||
|
||||
def cycle_blue(dt):
|
||||
mcrfpy.delTimer("cycle_2")
|
||||
grid.background_color = colors[2]
|
||||
c = grid.background_color
|
||||
color_display.text = f"R:{c.r} G:{c.g} B:{c.b}"
|
||||
print(f"✓ Set to Blue: R={c.r}, G={c.g}, B={c.b}")
|
||||
|
||||
# Cycle through colors
|
||||
mcrfpy.setTimer("cycle_0", cycle_red, 100)
|
||||
mcrfpy.setTimer("cycle_1", cycle_green, 400)
|
||||
mcrfpy.setTimer("cycle_2", cycle_blue, 700)
|
||||
|
||||
def test_complete(dt):
|
||||
mcrfpy.delTimer("complete")
|
||||
print("\nTest 4: Final color check")
|
||||
final_color = grid.background_color
|
||||
print(f"Final: R={final_color.r}, G={final_color.g}, B={final_color.b}")
|
||||
|
||||
print("\n✓ Grid background color tests completed!")
|
||||
print("- Default background color works")
|
||||
print("- Setting background color works")
|
||||
print("- Color cycling works")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# Schedule tests
|
||||
mcrfpy.setTimer("test_set", test_set_color, 1000)
|
||||
mcrfpy.setTimer("test_anim", test_animation, 2000)
|
||||
mcrfpy.setTimer("complete", test_complete, 4500)
|
||||
|
||||
# Start tests
|
||||
mcrfpy.setTimer("run_tests", run_tests, 100)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_grid_background()
|
Loading…
Reference in New Issue