diff --git a/RENDERTEXTURE_DESIGN.md b/RENDERTEXTURE_DESIGN.md new file mode 100644 index 0000000..fe03e90 --- /dev/null +++ b/RENDERTEXTURE_DESIGN.md @@ -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 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. \ No newline at end of file diff --git a/tests/test_grid_background.py b/tests/test_grid_background.py new file mode 100644 index 0000000..c79cf8e --- /dev/null +++ b/tests/test_grid_background.py @@ -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() \ No newline at end of file