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