1 /** 2 * Retrograde Engine 3 * 4 * Authors: 5 * Mike Bierlee, m.bierlee@lostmoment.com 6 * Copyright: 2014-2021 Mike Bierlee 7 * License: 8 * This software is licensed under the terms of the MIT license. 9 * The full terms of the license can be found in the LICENSE.txt file. 10 */ 11 12 module retrograde.graphics.twodee.sdl2.rendering; 13 14 version(Have_derelict_sdl2) { 15 16 import retrograde.entity; 17 import retrograde.graphics.core; 18 import retrograde.math; 19 import retrograde.file; 20 import retrograde.sdl2.window; 21 import retrograde.derelict; 22 import retrograde.messaging; 23 import retrograde.stringid; 24 25 import poodinis; 26 27 import std.string; 28 import std.range; 29 import std.math; 30 import std.algorithm.sorting; 31 import std.algorithm.iteration; 32 33 import derelict.sdl2.sdl; 34 import derelict.sdl2.image; 35 36 class Sdl2RenderSystemInitException : Exception { 37 this(string reason) { 38 super("Could not initialize SDL2 render system: " ~ reason); 39 } 40 } 41 42 public class Sdl2RenderSystem : EntityProcessor { 43 private SDL_Renderer* renderer; 44 private SDL_Window* window; 45 46 public ColorRgba clearColor = ColorRgba(0, 0, 0, 255); 47 48 @Autowire 49 @OptionalDependency 50 private SubRenderer[] subRenderers; 51 52 @Autowire 53 private Sdl2WindowCreator windowCreator; 54 55 public override void initialize() { 56 if (!DerelictLibrary.loadedAndInitialized()) { 57 throw new Sdl2RenderSystemInitException("SDL2 is not initialized. See retrograde.derelict module and load and initialize SDL2 before the renderer."); 58 } 59 60 window = windowCreator.createWindow(); 61 if (!window) { 62 throw new Sdl2RenderSystemInitException("Could not create window"); 63 } 64 65 renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); 66 if (!renderer) { 67 throw new Sdl2RenderSystemInitException("Could not create renderer"); 68 } 69 70 initializeSubRenderers(); 71 } 72 73 public void initializeSubRenderers() { 74 if (subRenderers.length == 0) { 75 throw new Sdl2RenderSystemInitException("No SDL2 subrenderers are registered in the dependency container."); 76 } 77 78 foreach (subRenderer; subRenderers) { 79 subRenderer.renderer = renderer; 80 } 81 } 82 83 public override void cleanup() { 84 SDL_DestroyWindow(window); 85 SDL_DestroyRenderer(renderer); 86 } 87 88 public override bool acceptsEntity(Entity entity) { 89 foreach(subRenderer ; subRenderers) { 90 if (subRenderer.acceptsEntity(entity)) { 91 return true; 92 } 93 } 94 return false; 95 } 96 97 protected override void processAcceptedEntity(Entity entity) { 98 foreach(subRenderer ; subRenderers) { 99 if (subRenderer.acceptsEntity(entity)) { 100 subRenderer.addEntity(entity); 101 } 102 } 103 } 104 105 protected override void processRemovedEntity(Entity entity) { 106 foreach(subRenderer ; subRenderers) { 107 if (subRenderer.hasEntity(entity)) { 108 subRenderer.removeEntity(entity.id); 109 } 110 } 111 } 112 113 public override void update() { 114 foreach(subRenderer ; subRenderers) { 115 subRenderer.update(); 116 } 117 } 118 119 public override void draw() { 120 if (renderer) { 121 SDL_SetRenderDrawColor(renderer, clearColor.r, clearColor.g, clearColor.b, clearColor.a); 122 SDL_RenderClear(renderer); 123 foreach(subRenderer ; subRenderers) { 124 subRenderer.draw(); 125 } 126 SDL_RenderPresent(renderer); 127 } 128 } 129 130 public SDL_Texture* createTextureFromSurface(SDL_Surface* surface) { 131 return SDL_CreateTextureFromSurface(renderer, surface); 132 } 133 } 134 135 public class SubRenderer : EntityProcessor { 136 public SDL_Renderer* renderer; 137 } 138 139 public class SpriteSubRenderer : SubRenderer { 140 private bool reorderEntities = false; 141 private Entity[][int] orderedEntities; 142 private SortedRange!(int[]) renderOrderKeys; 143 144 public override bool acceptsEntity(Entity entity) { 145 return (entity.hasComponent!RenderableSpriteComponent 146 || entity.hasComponent!RenderableTextComponent) 147 && entity.hasComponent!TextureComponent; 148 } 149 150 public override void update() { 151 if (reorderEntities) { 152 orderedEntities.destroy(); 153 _entities.getAll().each!(e => addToOrderedMap(e)); 154 createRenderOrderKeys(); 155 reorderEntities = false; 156 } 157 } 158 159 private void createRenderOrderKeys() { 160 renderOrderKeys = orderedEntities.keys.sort(); 161 } 162 163 private void addToOrderedMap(Entity entity) { 164 int renderOrder = getEntityDrawOrder(entity); 165 orderedEntities[renderOrder] ~= entity; 166 } 167 168 private int getEntityDrawOrder(Entity entity) { 169 return entity.getFromComponent!RenderOrderComponent(component => component.order, 0); 170 } 171 172 private void removeFromOrderedMap(uint entityId) { 173 auto entity = _entities[entityId]; 174 auto renderOrder = getEntityDrawOrder(entity); 175 orderedEntities[renderOrder] = orderedEntities[renderOrder].filter!(ent => ent.id != entityId)().array(); 176 } 177 178 public override void addEntity(Entity entity) { 179 if (acceptsEntity(entity)) { 180 addToOrderedMap(entity); 181 createRenderOrderKeys(); 182 super.addEntity(entity); 183 } 184 } 185 186 public override void removeEntity(uint entityId) { 187 removeFromOrderedMap(entityId); 188 super.removeEntity(entityId); 189 } 190 191 public override void draw() { 192 foreach(orderIndex; renderOrderKeys) { 193 foreach(entity; orderedEntities[orderIndex]) { 194 auto isHidden = entity.getFromComponent!HideableComponent(component => component.isHidden, false); 195 if (isHidden) { 196 continue; 197 } 198 199 drawEntitySprite(entity); 200 } 201 } 202 } 203 204 private void drawEntitySprite(Entity entity) { 205 auto texture = cast(Sdl2Texture) entity.getFromComponent!TextureComponent(c => c.texture); 206 if (!texture) return; 207 auto sdlTexture = texture.getSdlTexture(); 208 if (!sdlTexture) return; 209 210 RectangleU currentSpriteSize = entity.getFromComponent!SpriteSheetComponent((component) { 211 auto currentFrame = entity.getFromComponent!SpriteAnimationComponent(component => component.currentFrame, 1L); 212 return cast(RectangleU) component.getNthSprite(currentFrame); 213 }, texture.getTextureSize()); 214 215 Vector2D position = entity.getFromComponent!Position2DComponent(component => component.position, Vector2D(0)); 216 217 entity.maybeWithComponent!DrawingOffset2DComponent((component) { 218 position = position + component.offset; 219 }); 220 221 int renderFlip = SDL_FLIP_NONE; 222 if (entity.hasComponent!VerticalSpriteFlipComponent) { 223 renderFlip = renderFlip | SDL_FLIP_VERTICAL; 224 } 225 226 if (entity.hasComponent!HorizontalSpriteFlipComponent) { 227 renderFlip = renderFlip | SDL_FLIP_HORIZONTAL; 228 } 229 230 double rotation = radiansToDegrees(entity.getFromComponent!OrientationR2Component(component => component.angle, 0)); 231 232 Vector2D offset = entity.getFromComponent!DrawingOffset2DComponent(component => component.offset, Vector2D(0)) * -1; 233 SDL_Point center = SDL_Point(cast(int) offset.x, cast(int) offset.y); 234 235 auto textureColor = entity.getFromComponent!TextureColorComponent(c => c.color, ColorRgb(255, 255, 255)); 236 SDL_SetTextureColorMod(sdlTexture, textureColor.r, textureColor.g, textureColor.b); 237 238 SDL_Rect source; 239 source.x = currentSpriteSize.x; 240 source.y = currentSpriteSize.y; 241 source.w = currentSpriteSize.width; 242 source.h = currentSpriteSize.height; 243 SDL_Rect destination; 244 destination.x = cast(int) round(position.x); 245 destination.y = cast(int) round(position.y); 246 destination.w = currentSpriteSize.width; 247 destination.h = currentSpriteSize.height; 248 249 SDL_RenderCopyEx(renderer, sdlTexture, &source, &destination, rotation, ¢er, renderFlip); 250 } 251 } 252 253 class Sdl2Texture : Texture { 254 private SDL_Texture* texture; 255 private SDL_Rect textureSize; 256 private string name; 257 258 public this() {} 259 260 public this(SDL_Texture* texture, string name) { 261 this.texture = texture; 262 this.name = name; 263 setTextureSize(texture); 264 } 265 266 public override RectangleU getTextureSize() { 267 return RectangleU(textureSize.x, textureSize.y, textureSize.w, textureSize.h); 268 } 269 270 public override string getName() { 271 return name; 272 } 273 274 public SDL_Rect getSdlTextureSize() { 275 return textureSize; 276 } 277 278 public SDL_Texture* getSdlTexture() { 279 return texture; 280 } 281 282 public void setSdlTexture(SDL_Texture* texture) { 283 this.texture = texture; 284 setTextureSize(texture); 285 } 286 287 private void setTextureSize(SDL_Texture* texture) { 288 textureSize.x = 0; 289 textureSize.y = 0; 290 SDL_QueryTexture(texture, null, null, &textureSize.w, &textureSize.h); 291 } 292 } 293 294 class Sdl2TextureComponentFactory : TextureComponentFactory { 295 296 @Autowire 297 private Sdl2RenderSystem renderer; 298 299 private TextureCache textures = new TextureCache(); 300 301 public TextureComponent loadTexture(File textureFile) { 302 auto texture = textures.getOrAdd(textureFile.fileName, { 303 SDL_Surface* textureSurface = IMG_Load(textureFile.fileName.toStringz()); 304 SDL_Texture* texture = renderer.createTextureFromSurface(textureSurface); 305 SDL_FreeSurface(textureSurface); 306 return new Sdl2Texture(texture, textureFile.fileName); 307 }); 308 309 return new TextureComponent(texture); 310 } 311 312 public TextureComponent createNullComponent() { 313 return new TextureComponent(null); 314 } 315 } 316 317 }