1 module pixelperfectengine.graphics.layers.spritelayer; 2 3 public import pixelperfectengine.graphics.layers.base; 4 5 import collections.treemap; 6 import collections.sortedlist; 7 import std.bitmanip : bitfields; 8 9 /** 10 * General-purpose sprite controller and renderer. 11 */ 12 public class SpriteLayer : Layer, ISpriteLayer { 13 /** 14 * Helps to determine the displaying properties and order of sprites. 15 */ 16 public struct DisplayListItem { 17 Box position; /// Stores the position relative to the origin point. Actual display position is determined by the scroll positions. 18 Box slice; /// To compensate for the lack of scanline interrupt capabilities, this enables chopping off parts of a sprite. 19 void* pixelData; /// Points to the pixel data. 20 /** 21 * From version 0.10.0 onwards, each sprites can have their own rendering function set up to 22 * allow different effect on a single layer. 23 * If not specified otherwise, the layer's main rendering function will be used instead. 24 * Custom rendering functions can be written by the user, it requires knowledge of writing 25 * pixel shader-like functions using fixed-point arithmetics. Use of vector optimizatons 26 * techniques (SSE2, AVX, NEON, etc) are needed for optimal performance. 27 */ 28 @nogc pure nothrow void function(uint* src, uint* dest, size_t length, ubyte value) renderFunc; 29 int width; /// Width of the sprite 30 int height; /// Height of the sprite 31 int scaleHoriz; /// Horizontal scaling 32 int scaleVert; /// Vertical scaling 33 int priority; /// Used for automatic sorting and identification. 34 /** 35 * Selects the palette of the sprite. 36 * Amount of accessable color depends on the palette access shifting value. A value of 8 enables 37 * 256 * 256 color palettes, and a value of 4 enables 4096 * 16 color palettes. 38 * `paletteSh` can be set lower than what the bitmap is capable of storing at its maximum, this 39 * can enable the packing of more palettes within the main one, e.g. a `paletteSh` value of 7 40 * means 512 * 128 color palettes, while the bitmaps are still stored in the 8 bit "chunky" mode 41 * instead of 7 bit planar that would require way more processing power. However this doesn't 42 * limit the bitmap's ability to access 256 colors, and this can result in memory leakage if 43 * the end developer isn't careful enough. 44 */ 45 ushort paletteSel; 46 //ubyte flags; /// Flags packed into a single byte (bitmapType, paletteSh) 47 mixin(bitfields!( 48 ubyte, "paletteSh", 4, 49 ubyte, "bmpType", 4, 50 )); 51 ubyte masterAlpha = ubyte.max;/// Sets the master alpha value of the sprite, e.g. opacity 52 //ubyte wordLength; /// Determines the word length of a sprite in a much quicker way than getting classinfo. 53 //ubyte paletteSh; /// Palette shifting value. 8 is default for 8 bit, and 4 for 4 bit bitmaps. (see paletteSel for more info) 54 //static enum ubyte PALETTESH_MASK = 0x0F; /// Mask for paletteSh 55 //static enum ubyte BMPTYPE_MASK = 0x80; /// Mask for bmpType 56 /** 57 * Creates a display list item with palette selector. 58 */ 59 this(Coordinate position, ABitmap sprite, int priority, ushort paletteSel = 0, int scaleHoriz = 1024, 60 int scaleVert = 1024) pure @trusted nothrow { 61 this.position = position; 62 this.width = sprite.width; 63 this.height = sprite.height; 64 this.priority = priority; 65 this.paletteSel = paletteSel; 66 this.scaleVert = scaleVert; 67 this.scaleHoriz = scaleHoriz; 68 slice = Coordinate(0,0,sprite.width,sprite.height); 69 if (typeid(sprite) is typeid(Bitmap4Bit)) { 70 bmpType = BitmapTypes.Bmp4Bit; 71 paletteSh = 4; 72 pixelData = (cast(Bitmap4Bit)(sprite)).getPtr; 73 } else if (typeid(sprite) is typeid(Bitmap8Bit)) { 74 bmpType = BitmapTypes.Bmp8Bit; 75 paletteSh = 8; 76 pixelData = (cast(Bitmap8Bit)(sprite)).getPtr; 77 } else if (typeid(sprite) is typeid(Bitmap16Bit)) { 78 bmpType = BitmapTypes.Bmp16Bit; 79 pixelData = (cast(Bitmap16Bit)(sprite)).getPtr; 80 } else if (typeid(sprite) is typeid(Bitmap32Bit)) { 81 bmpType = BitmapTypes.Bmp32Bit; 82 pixelData = (cast(Bitmap32Bit)(sprite)).getPtr; 83 } 84 } 85 /** 86 * Creates a display list item without palette selector. 87 */ 88 this(Coordinate position, Coordinate slice, ABitmap sprite, int priority, int scaleHoriz = 1024, 89 int scaleVert = 1024) pure @trusted nothrow { 90 if(slice.top < 0) 91 slice.top = 0; 92 if(slice.left < 0) 93 slice.left = 0; 94 if(slice.right >= sprite.width) 95 slice.right = sprite.width - 1; 96 if(slice.bottom >= sprite.height) 97 slice.bottom = sprite.height - 1; 98 this.slice = slice; 99 this(position, sprite, priority, paletteSel, scaleHoriz, scaleVert); 100 } 101 /+ 102 /// Palette shifting value. 8 is default for 8 bit, and 4 for 4 bit bitmaps. (see paletteSel for more info) 103 @property ubyte paletteSh() @safe @nogc pure nothrow const { 104 return cast(ubyte)flags & PALETTESH_MASK; 105 } 106 /// Palette shifting value. 8 is default for 8 bit, and 4 for 4 bit bitmaps. (see paletteSel for more info) 107 @property ubyte paletteSh(ubyte val) @safe @nogc pure nothrow { 108 flags &= ~PALETTESH_MASK; 109 flags |= val; 110 return cast(ubyte)flags & PALETTESH_MASK; 111 } 112 /// Defines the type of bitmap the sprite is using. This method is much faster and simpler than checking the class type of the bitmap. 113 @property BitmapTypes bmpType() @safe @nogc pure nothrow const { 114 return cast(BitmapTypes)((flags & BMPTYPE_MASK) >>> 4); 115 } 116 /// Defines the type of bitmap the sprite is using. This method is much faster and simpler than checking the class type of the bitmap. 117 @property BitmapTypes bmpType(BitmapTypes val) @safe @nogc pure nothrow { 118 flags &= ~BMPTYPE_MASK; 119 flags |= cast(ubyte)val << 4; 120 return bmpType; 121 }+/ 122 /** 123 * Resets the slice to its original position. 124 */ 125 void resetSlice() pure @nogc @safe nothrow { 126 slice.left = 0; 127 slice.top = 0; 128 slice.right = position.width - 1; 129 slice.bottom = position.height - 1; 130 } 131 /** 132 * Replaces the sprite with a new one. 133 * If the sizes are mismatching, the top-left coordinates are left as is, but the slicing is reset. 134 */ 135 void replaceSprite(ABitmap sprite) @trusted pure nothrow { 136 //this.sprite = sprite; 137 //palette = sprite.getPalettePtr(); 138 if(this.width != sprite.width || this.height != sprite.height){ 139 this.width = sprite.width; 140 this.height = sprite.height; 141 position.right = position.left + cast(int)scaleNearestLength(width, scaleHoriz); 142 position.bottom = position.top + cast(int)scaleNearestLength(height, scaleVert); 143 resetSlice(); 144 } 145 if (typeid(sprite) is typeid(Bitmap4Bit)) { 146 bmpType = BitmapTypes.Bmp4Bit; 147 paletteSh = 4; 148 pixelData = (cast(Bitmap4Bit)(sprite)).getPtr; 149 } else if (typeid(sprite) is typeid(Bitmap8Bit)) { 150 bmpType = BitmapTypes.Bmp8Bit; 151 paletteSh = 8; 152 pixelData = (cast(Bitmap8Bit)(sprite)).getPtr; 153 } else if (typeid(sprite) is typeid(Bitmap16Bit)) { 154 bmpType = BitmapTypes.Bmp16Bit; 155 pixelData = (cast(Bitmap16Bit)(sprite)).getPtr; 156 } else if (typeid(sprite) is typeid(Bitmap32Bit)) { 157 bmpType = BitmapTypes.Bmp32Bit; 158 pixelData = (cast(Bitmap32Bit)(sprite)).getPtr; 159 } 160 } 161 @nogc int opCmp(in DisplayListItem d) const pure @safe nothrow { 162 return priority - d.priority; 163 } 164 @nogc bool opEquals(in DisplayListItem d) const pure @safe nothrow { 165 return priority == d.priority; 166 } 167 @nogc int opCmp(in int pri) const pure @safe nothrow { 168 return priority - pri; 169 } 170 @nogc bool opEquals(in int pri) const pure @safe nothrow { 171 return priority == pri; 172 } 173 174 string toString() const { 175 import std.conv : to; 176 return "{Position: " ~ position.toString ~ ";\nDisplayed portion: " ~ slice.toString ~";\nPriority: " ~ 177 to!string(priority) ~ "; PixelData: " ~ to!string(pixelData) ~ 178 "; PaletteSel: " ~ to!string(paletteSel) ~ "; bmpType: " ~ to!string(bmpType) ~ "}"; 179 } 180 } 181 alias DisplayList = TreeMap!(int, DisplayListItem); 182 alias OnScreenList = SortedList!(int, "a < b", false); 183 //protected DisplayListItem[] displayList; ///Stores the display data 184 protected DisplayList allSprites; ///All sprites of this layer 185 protected OnScreenList displayedSprites; ///Sprites that are being displayed 186 protected Color[2048] src; ///Local buffer for scaling 187 //size_t[8] prevSize; 188 ///Default ctor 189 public this(RenderingMode renderMode = RenderingMode.AlphaBlend) nothrow @safe { 190 setRenderingMode(renderMode); 191 //Bug workaround: Sometimes when attempting to append an element to a zero-length array, it causes an exception 192 //to be thrown, due to access errors. This bug is unstable, and as such hard to debug for (memory leakage issue?) 193 displayedSprites.reserve(128); 194 //src[0].length = 1024; 195 } 196 /** 197 * Checks all sprites for whether they're on screen or not. 198 * Called every time the layer is being scrolled. 199 */ 200 public void checkAllSprites() @safe pure nothrow { 201 foreach (key; allSprites) { 202 checkSprite(key); 203 } 204 } 205 /** 206 * Checks whether a sprite would be displayed on the screen, then updates the display list. 207 * Returns true if it's on screen. 208 */ 209 public bool checkSprite(int n) @safe pure nothrow { 210 return checkSprite(allSprites[n]); 211 } 212 ///Ditto. 213 protected bool checkSprite(DisplayListItem sprt) @safe pure nothrow { 214 //assert(sprt.bmpType != BitmapTypes.Undefined && sprt.pixelData, "DisplayList error!"); 215 if(sprt.slice.width && sprt.slice.height 216 && (sprt.position.right > sX && sprt.position.bottom > sY && 217 sprt.position.left < sX + rasterX && sprt.position.top < sY + rasterY)) { 218 displayedSprites.put(sprt.priority); 219 return true; 220 } else { 221 displayedSprites.removeByElem(sprt.priority); 222 return false; 223 } 224 } 225 /** 226 * Searches the DisplayListItem by priority and returns it. 227 * Can be used for external use without any safety issues. 228 */ 229 public DisplayListItem getDisplayListItem(int n) @nogc pure @safe nothrow { 230 return allSprites[n]; 231 } 232 /** 233 * Searches the DisplayListItem by priority and returns it. 234 * Intended for internal use, as it returns it as a reference value. 235 */ 236 protected DisplayListItem* getDisplayListItem_internal(int n) @nogc pure @safe nothrow { 237 return allSprites.ptrOf(n); 238 } 239 override public void setRasterizer(int rX,int rY) { 240 super.setRasterizer(rX,rY); 241 } 242 ///Returns the displayed portion of the sprite. 243 public Coordinate getSlice(int n) @nogc pure @safe nothrow { 244 return getDisplayListItem(n).slice; 245 } 246 ///Writes the displayed portion of the sprite. 247 ///Returns the new slice, if invalid (greater than the bitmap, etc.) returns Coordinate.init. 248 public Coordinate setSlice(int n, Coordinate slice) @safe pure nothrow { 249 DisplayListItem* sprt = allSprites.ptrOf(n); 250 if(sprt) { 251 sprt.slice = slice; 252 checkSprite(*sprt); 253 return sprt.slice; 254 } else { 255 return Coordinate.init; 256 } 257 } 258 ///Returns the selected paletteID of the sprite. 259 public ushort getPaletteID(int n) @nogc pure @safe nothrow { 260 return getDisplayListItem(n).paletteSel; 261 } 262 ///Sets the paletteID of the sprite. Returns the new ID, which is truncated to the possible values with a simple binary and operation 263 ///Palette must exist in the parent Raster, otherwise AccessError might happen 264 public ushort setPaletteID(int n, ushort paletteID) @nogc pure @safe nothrow { 265 return getDisplayListItem_internal(n).paletteSel = paletteID; 266 } 267 /** 268 * Adds a sprite to the layer. 269 */ 270 public void addSprite(ABitmap s, int n, Box c, ushort paletteSel = 0, int scaleHoriz = 1024, 271 int scaleVert = 1024) @safe nothrow { 272 DisplayListItem d = DisplayListItem(c, s, n, paletteSel, scaleHoriz, scaleVert); 273 d.renderFunc = mainRenderingFunction; 274 synchronized 275 allSprites[n] = d; 276 checkSprite(d); 277 } 278 ///Ditto 279 public void addSprite(ABitmap s, int n, int x, int y, ushort paletteSel = 0, int scaleHoriz = 1024, 280 int scaleVert = 1024) @safe nothrow { 281 DisplayListItem d = DisplayListItem(Box(x, y, x + s.width - 1, y + s.height - 1), s, n, paletteSel, scaleHoriz, 282 scaleVert); 283 d.renderFunc = mainRenderingFunction; 284 synchronized 285 allSprites[n] = d; 286 checkSprite(d); 287 } 288 /** 289 * Replaces the bitmap of the given sprite. 290 */ 291 public void replaceSprite(ABitmap s, int n) @safe nothrow { 292 DisplayListItem* sprt = getDisplayListItem_internal(n); 293 sprt.replaceSprite(s); 294 checkSprite(*sprt); 295 } 296 ///Ditto with move 297 public void replaceSprite(ABitmap s, int n, int x, int y) @safe nothrow { 298 DisplayListItem* sprt = getDisplayListItem_internal(n); 299 sprt.replaceSprite(s); 300 sprt.position.move(x, y); 301 checkSprite(*sprt); 302 } 303 ///Ditto with move 304 public void replaceSprite(ABitmap s, int n, Coordinate c) @safe nothrow { 305 DisplayListItem* sprt = allSprites.ptrOf(n); 306 sprt.replaceSprite(s); 307 sprt.position = c; 308 checkSprite(*sprt); 309 } 310 /** 311 * Removes a sprite from both displaylists by priority. 312 */ 313 public void removeSprite(int n) @safe nothrow { 314 synchronized { 315 displayedSprites.removeByElem(n); 316 allSprites.remove(n); 317 } 318 } 319 ///Clears all sprite from the layer. 320 public void clear() @safe nothrow { 321 displayedSprites = OnScreenList.init; 322 allSprites = DisplayList.init; 323 } 324 /** 325 * Moves a sprite to the given position. 326 */ 327 public void moveSprite(int n, int x, int y) @safe nothrow { 328 DisplayListItem* sprt = allSprites.ptrOf(n); 329 sprt.position.move(x, y); 330 checkSprite(*sprt); 331 } 332 /** 333 * Moves a sprite by the given amount. 334 */ 335 public void relMoveSprite(int n, int x, int y) @safe nothrow { 336 DisplayListItem* sprt = allSprites.ptrOf(n); 337 sprt.position.relMove(x, y); 338 checkSprite(*sprt); 339 } 340 ///Sets the rendering function for the sprite (defaults to the layer's rendering function) 341 public void setSpriteRenderingMode(int n, RenderingMode mode) @safe nothrow { 342 DisplayListItem* sprt = allSprites.ptrOf(n); 343 sprt.renderFunc = getRenderingFunc(mode); 344 } 345 public @nogc Coordinate getSpriteCoordinate(int n) @safe nothrow { 346 return allSprites[n].position; 347 } 348 ///Scales sprite horizontally. Returns the new size, or -1 if the scaling value is invalid, or -2 if spriteID not found. 349 public int scaleSpriteHoriz(int n, int hScl) @trusted nothrow { 350 DisplayListItem* sprt = allSprites.ptrOf(n); 351 if(!sprt) return -2; 352 else if(!hScl) return -1; 353 else { 354 sprt.scaleHoriz = hScl; 355 const int newWidth = cast(int)scaleNearestLength(sprt.width, hScl); 356 sprt.slice.right = newWidth; 357 sprt.position.right = sprt.position.left + newWidth; 358 checkSprite(*sprt); 359 return newWidth; 360 } 361 } 362 ///Scales sprite vertically. Returns the new size, or -1 if the scaling value is invalid, or -2 if spriteID not found. 363 public int scaleSpriteVert(int n, int vScl) @trusted nothrow { 364 DisplayListItem* sprt = allSprites.ptrOf(n); 365 if(!sprt) return -2; 366 else if(!vScl) return -1; 367 else { 368 sprt.scaleVert = vScl; 369 const int newHeight = cast(int)scaleNearestLength(sprt.height, vScl); 370 sprt.slice.bottom = newHeight; 371 sprt.position.bottom = sprt.position.top + newHeight; 372 checkSprite(*sprt); 373 return newHeight; 374 } 375 /+if (!vScl) return -1; 376 for(int i; i < displayList.length ; i++){ 377 if(displayList[i].priority == n){ 378 displayList[i].scaleVert = vScl; 379 const int newHeight = cast(int)scaleNearestLength(displayList[i].height, vScl); 380 displayList[i].slice.bottom = newHeight; 381 return displayList[i].position.bottom = displayList[i].position.top + newHeight; 382 } 383 } 384 return -2;+/ 385 } 386 ///Gets the sprite's current horizontal scale value 387 public int getScaleSpriteHoriz(int n) @nogc @trusted nothrow { 388 return allSprites[n].scaleHoriz; 389 } 390 ///Gets the sprite's current vertical scale value 391 public int getScaleSpriteVert(int n) @nogc @trusted nothrow { 392 return allSprites[n].scaleVert; 393 } 394 public override @nogc void updateRaster(void* workpad, int pitch, Color* palette) { 395 /* 396 * BUG 1: If sprite is wider than 2048 pixels, it'll cause issues (mostly memory leaks) due to a hack. 397 * BUG 2: Obscuring the top part of a sprite when scaleVert is not 1024 will cause glitches. 398 */ 399 foreach (priority ; displayedSprites) { 400 //foreach(i ; displayList){ 401 DisplayListItem i = allSprites[priority]; 402 const int left = i.position.left + i.slice.left; 403 const int top = i.position.top + i.slice.top; 404 const int right = i.position.left + i.slice.right; 405 const int bottom = i.position.top + i.slice.bottom; 406 /+if((i.position.right > sX && i.position.bottom > sY) && (i.position.left < sX + rasterX && i.position.top < sY + 407 rasterY)){+/ 408 //if((right > sX && left < sX + rasterX) && (bottom > sY && top < sY + rasterY) && i.slice.width && i.slice.height){ 409 int offsetXA = sX > left ? sX - left : 0;//Left hand side offset, zero if not obscured 410 const int offsetXB = sX + rasterX < right ? right - (sX + rasterX) : 0; //Right hand side offset, zero if not obscured 411 const int offsetYA = sY > top ? sY - top : 0; //top offset of sprite, zero if not obscured 412 const int offsetYB = sY + rasterY < bottom ? bottom - (sY + rasterY) + 1 : 1; //bottom offset of sprite, zero if not obscured 413 //const int offsetYB0 = cast(int)scaleNearestLength(offsetYB, i.scaleVert); 414 const int sizeX = i.slice.width(); //total displayed width 415 const int offsetX = left - sX; 416 const int length = sizeX - offsetXA - offsetXB - 1; 417 //int lengthY = i.slice.height - offsetYA - offsetYB; 418 //const int lfour = length * 4; 419 const int offsetY = sY < top ? (top-sY)*pitch : 0; //used if top portion of the sprite is off-screen 420 //offset = i.scaleVert % 1024; 421 const int scaleVertAbs = i.scaleVert * (i.scaleVert < 0 ? -1 : 1); //absolute value of vertical scaling, used in various calculations 422 //int offset, prevOffset; 423 const int offsetAmount = scaleVertAbs <= 1024 ? 1024 : scaleVertAbs; //used to limit the amount of re-rendering every line 424 //offset = offsetYA<<10; 425 const int offsetYA0 = cast(int)(cast(double)offsetYA / (1024.0 / cast(double)scaleVertAbs)); //amount of skipped lines (I think) TODO: remove floating-point arithmetic 426 const int sizeXOffset = i.width * (i.scaleVert < 0 ? -1 : 1); 427 int prevOffset = offsetYA0 * offsetAmount; // 428 int offset = offsetYA0 * scaleVertAbs; 429 const size_t p0offset = (i.scaleHoriz > 0 ? offsetXA : offsetXB); //determines offset based on mirroring 430 // HACK: as I couldn't figure out a better method yet I decided to scale a whole line, which has a lot of problems 431 const int scalelength = i.position.width < 2048 ? i.width : 2048; //limit width to 2048, the minimum required for this scaling method to work 432 void* dest = workpad + (offsetX + offsetXA)*4 + offsetY; 433 final switch (i.bmpType) with (BitmapTypes) { 434 case Bmp4Bit: 435 ubyte* p0 = cast(ubyte*)i.pixelData + i.width * ((i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0)>>1); 436 for(int y = offsetYA ; y < i.slice.height - offsetYB ; ){ 437 horizontalScaleNearest4BitAndCLU(p0, src.ptr, palette + (i.paletteSel<<i.paletteSh), scalelength, offsetXA & 1, 438 i.scaleHoriz); 439 prevOffset += offsetAmount; 440 for(; offset < prevOffset; offset += scaleVertAbs){ 441 y++; 442 i.renderFunc(cast(uint*)src.ptr + p0offset, cast(uint*)dest, length, i.masterAlpha); 443 dest += pitch; 444 } 445 p0 += sizeXOffset >> 1; 446 } 447 //} 448 break; 449 case Bmp8Bit: 450 ubyte* p0 = cast(ubyte*)i.pixelData + i.width * (i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0); 451 for(int y = offsetYA ; y < i.slice.height - offsetYB ; ){ 452 horizontalScaleNearestAndCLU(p0, src.ptr, palette + (i.paletteSel<<i.paletteSh), scalelength, i.scaleHoriz); 453 prevOffset += 1024; 454 for(; offset < prevOffset; offset += scaleVertAbs){ 455 y++; 456 i.renderFunc(cast(uint*)src.ptr + p0offset, cast(uint*)dest, length, i.masterAlpha); 457 dest += pitch; 458 } 459 p0 += sizeXOffset; 460 } 461 break; 462 case Bmp16Bit: 463 ushort* p0 = cast(ushort*)i.pixelData + i.width * (i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0); 464 for(int y = offsetYA ; y < i.slice.height - offsetYB ; ){ 465 horizontalScaleNearestAndCLU(p0, src.ptr, palette, scalelength, i.scaleHoriz); 466 prevOffset += 1024; 467 for(; offset < prevOffset; offset += scaleVertAbs){ 468 y++; 469 i.renderFunc(cast(uint*)src.ptr + p0offset, cast(uint*)dest, length, i.masterAlpha); 470 dest += pitch; 471 } 472 p0 += sizeXOffset; 473 } 474 break; 475 case Bmp32Bit: 476 Color* p0 = cast(Color*)i.pixelData + i.width * (i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0); 477 for(int y = offsetYA ; y < i.slice.height - offsetYB ; ){ 478 horizontalScaleNearest(p0, src.ptr, scalelength, i.scaleHoriz); 479 prevOffset += 1024; 480 for(; offset < prevOffset; offset += scaleVertAbs){ 481 y++; 482 i.renderFunc(cast(uint*)src.ptr + p0offset, cast(uint*)dest, length, i.masterAlpha); 483 dest += pitch; 484 } 485 p0 += sizeXOffset; 486 } 487 //} 488 break; 489 case Undefined, Bmp1Bit, Bmp2Bit, Planar: 490 break; 491 } 492 493 //} 494 } 495 //foreach(int threadOffset; threads.parallel) 496 //free(src[threadOffset]); 497 } 498 ///Absolute scrolling. 499 public override void scroll(int x, int y) @safe pure nothrow { 500 sX = x; 501 sY = y; 502 checkAllSprites; 503 } 504 ///Relative scrolling. Positive values scrolls the layer left and up, negative values scrolls the layer down and right. 505 public override void relScroll(int x, int y) @safe pure nothrow { 506 sX += x; 507 sY += y; 508 checkAllSprites; 509 } 510 }