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) @nogc nothrow @safe {
190 		setRenderingMode(renderMode);
191 		//src[0].length = 1024;
192 	}
193 	/**
194 	 * Checks all sprites for whether they're on screen or not.
195 	 * Called every time the layer is being scrolled.
196 	 */
197 	public void checkAllSprites() @safe pure nothrow {
198 		foreach (key; allSprites) {
199 			checkSprite(key);
200 		}
201 	}
202 	/**
203 	 * Checks whether a sprite would be displayed on the screen, then updates the display list.
204 	 * Returns true if it's on screen.
205 	 */
206 	public bool checkSprite(int n) @safe pure nothrow {
207 		return checkSprite(allSprites[n]);
208 	}
209 	///Ditto.
210 	protected bool checkSprite(DisplayListItem sprt) @safe pure nothrow {
211 		//assert(sprt.bmpType != BitmapTypes.Undefined && sprt.pixelData, "DisplayList error!");
212 		if(sprt.slice.width && sprt.slice.height 
213 				&& (sprt.position.right > sX && sprt.position.bottom > sY && 
214 				sprt.position.left < sX + rasterX && sprt.position.top < sY + rasterY)) {
215 			displayedSprites.put(sprt.priority);
216 			return true;
217 		} else {
218 			displayedSprites.removeByElem(sprt.priority);
219 			return false;
220 		}
221 	}
222 	/**
223 	 * Searches the DisplayListItem by priority and returns it.
224 	 * Can be used for external use without any safety issues.
225 	 */
226 	public DisplayListItem getDisplayListItem(int n) @nogc pure @safe nothrow {
227 		return allSprites[n];
228 	}
229 	/**
230 	 * Searches the DisplayListItem by priority and returns it.
231 	 * Intended for internal use, as it returns it as a reference value.
232 	 */
233 	protected DisplayListItem* getDisplayListItem_internal(int n) @nogc pure @safe nothrow {
234 		return allSprites.ptrOf(n);
235 	}
236 	override public void setRasterizer(int rX,int rY) {
237 		super.setRasterizer(rX,rY);
238 	}
239 	///Returns the displayed portion of the sprite.
240 	public Coordinate getSlice(int n) @nogc pure @safe nothrow {
241 		return getDisplayListItem(n).slice;
242 	}
243 	///Writes the displayed portion of the sprite.
244 	///Returns the new slice, if invalid (greater than the bitmap, etc.) returns Coordinate.init.
245 	public Coordinate setSlice(int n, Coordinate slice) @safe pure nothrow {
246 		DisplayListItem* sprt = allSprites.ptrOf(n);
247 		if(sprt) {
248 			sprt.slice = slice;
249 			checkSprite(*sprt);
250 			return sprt.slice;
251 		} else {
252 			return Coordinate.init;
253 		}
254 	}
255 	///Returns the selected paletteID of the sprite.
256 	public ushort getPaletteID(int n) @nogc pure @safe nothrow {
257 		return getDisplayListItem(n).paletteSel;
258 	}
259 	///Sets the paletteID of the sprite. Returns the new ID, which is truncated to the possible values with a simple binary and operation
260 	///Palette must exist in the parent Raster, otherwise AccessError might happen
261 	public ushort setPaletteID(int n, ushort paletteID) @nogc pure @safe nothrow {
262 		return getDisplayListItem_internal(n).paletteSel = paletteID;
263 	}
264 	/**
265 	 * Adds a sprite to the layer.
266 	 */
267 	public void addSprite(ABitmap s, int n, Box c, ushort paletteSel = 0, int scaleHoriz = 1024, 
268 				int scaleVert = 1024) @safe nothrow {
269 		DisplayListItem d = DisplayListItem(c, s, n, paletteSel, scaleHoriz, scaleVert);
270 		d.renderFunc = mainRenderingFunction;
271 		synchronized
272 			allSprites[n] = d;
273 		checkSprite(d);
274 	}
275 	///Ditto
276 	public void addSprite(ABitmap s, int n, int x, int y, ushort paletteSel = 0, int scaleHoriz = 1024, 
277 				int scaleVert = 1024) @safe nothrow {
278 		DisplayListItem d = DisplayListItem(Box(x, y, s.width + x, s.height + y), s, n, paletteSel, scaleHoriz, scaleVert);
279 		d.renderFunc = mainRenderingFunction;
280 		synchronized
281 			allSprites[n] = d;
282 		checkSprite(d);
283 	}
284 	/**
285 	 * Replaces the bitmap of the given sprite.
286 	 */
287 	public void replaceSprite(ABitmap s, int n) @safe nothrow {
288 		DisplayListItem* sprt = getDisplayListItem_internal(n);
289 		sprt.replaceSprite(s);
290 		checkSprite(*sprt);
291 	}
292 	///Ditto with move
293 	public void replaceSprite(ABitmap s, int n, int x, int y) @safe nothrow {
294 		DisplayListItem* sprt = getDisplayListItem_internal(n);
295 		sprt.replaceSprite(s);
296 		sprt.position.move(x, y);
297 		checkSprite(*sprt);
298 	}
299 	///Ditto with move
300 	public void replaceSprite(ABitmap s, int n, Coordinate c) @safe nothrow {
301 		DisplayListItem* sprt = allSprites.ptrOf(n);
302 		sprt.replaceSprite(s);
303 		sprt.position = c;
304 		checkSprite(*sprt);
305 	}
306 	/**
307 	 * Removes a sprite from both displaylists by priority.
308 	 */
309 	public void removeSprite(int n) @safe nothrow {
310 		synchronized {
311 			displayedSprites.removeByElem(n);
312 			allSprites.remove(n);
313 		}
314 	}
315 	///Clears all sprite from the layer.
316 	public void clear() @safe nothrow {
317 		displayedSprites = OnScreenList.init;
318 		allSprites = DisplayList.init;
319 	}
320 	/**
321 	 * Moves a sprite to the given position.
322 	 */
323 	public void moveSprite(int n, int x, int y) @safe nothrow {
324 		DisplayListItem* sprt = allSprites.ptrOf(n);
325 		sprt.position.move(x, y);
326 		checkSprite(*sprt);
327 	}
328 	/**
329 	 * Moves a sprite by the given amount.
330 	 */
331 	public void relMoveSprite(int n, int x, int y) @safe nothrow {
332 		DisplayListItem* sprt = allSprites.ptrOf(n);
333 		sprt.position.relMove(x, y);
334 		checkSprite(*sprt);
335 	}
336 	///Sets the rendering function for the sprite (defaults to the layer's rendering function)
337 	public void setSpriteRenderingMode(int n, RenderingMode mode) @safe nothrow {
338 		DisplayListItem* sprt = allSprites.ptrOf(n);
339 		sprt.renderFunc = getRenderingFunc(mode);
340 	}
341 	public @nogc Coordinate getSpriteCoordinate(int n) @safe nothrow {
342 		return allSprites[n].position;
343 	}
344 	///Scales sprite horizontally. Returns the new size, or -1 if the scaling value is invalid, or -2 if spriteID not found.
345 	public int scaleSpriteHoriz(int n, int hScl) @trusted nothrow { 
346 		DisplayListItem* sprt = allSprites.ptrOf(n);
347 		if(!sprt) return -2;
348 		else if(!hScl) return -1;
349 		else {
350 			sprt.scaleHoriz = hScl;
351 			const int newWidth = cast(int)scaleNearestLength(sprt.width, hScl);
352 			sprt.slice.right = newWidth;
353 			sprt.position.right = sprt.position.left + newWidth;
354 			checkSprite(*sprt);
355 			return newWidth;
356 		}
357 	}
358 	///Scales sprite vertically. Returns the new size, or -1 if the scaling value is invalid, or -2 if spriteID not found.
359 	public int scaleSpriteVert(int n, int vScl) @trusted nothrow {
360 		DisplayListItem* sprt = allSprites.ptrOf(n);
361 		if(!sprt) return -2;
362 		else if(!vScl) return -1;
363 		else {
364 			sprt.scaleVert = vScl;
365 			const int newHeight = cast(int)scaleNearestLength(sprt.height, vScl);
366 			sprt.slice.bottom = newHeight;
367 			sprt.position.bottom = sprt.position.top + newHeight;
368 			checkSprite(*sprt);
369 			return newHeight;
370 		}
371 		/+if (!vScl) return -1;
372 		for(int i; i < displayList.length ; i++){
373 			if(displayList[i].priority == n){
374 				displayList[i].scaleVert = vScl;
375 				const int newHeight = cast(int)scaleNearestLength(displayList[i].height, vScl);
376 				displayList[i].slice.bottom = newHeight;
377 				return displayList[i].position.bottom = displayList[i].position.top + newHeight;
378 			}
379 		}
380 		return -2;+/
381 	}
382 	///Gets the sprite's current horizontal scale value
383 	public int getScaleSpriteHoriz(int n) @nogc @trusted nothrow {
384 		return allSprites[n].scaleHoriz;
385 	}
386 	///Gets the sprite's current vertical scale value
387 	public int getScaleSpriteVert(int n) @nogc @trusted nothrow {
388 		return allSprites[n].scaleVert;
389 	}
390 	public override @nogc void updateRaster(void* workpad, int pitch, Color* palette) {
391 		/*
392 		 * BUG 1: If sprite is wider than 2048 pixels, it'll cause issues (mostly memory leaks) due to a hack.
393 		 * BUG 2: Obscuring the top part of a sprite when scaleVert is not 1024 will cause glitches.
394 		 */
395 		foreach (priority ; displayedSprites) {
396 		//foreach(i ; displayList){
397 			DisplayListItem i = allSprites[priority];
398 			const int left = i.position.left + i.slice.left;
399 			const int top = i.position.top + i.slice.top;
400 			const int right = i.position.left + i.slice.right;
401 			const int bottom = i.position.top + i.slice.bottom;
402 			/+if((i.position.right > sX && i.position.bottom > sY) && (i.position.left < sX + rasterX && i.position.top < sY +
403 					rasterY)){+/
404 			//if((right > sX && left < sX + rasterX) && (bottom > sY && top < sY + rasterY) && i.slice.width && i.slice.height){
405 			int offsetXA = sX > left ? sX - left : 0;//Left hand side offset, zero if not obscured
406 			const int offsetXB = sX + rasterX < right ? right - (sX + rasterX) : 0; //Right hand side offset, zero if not obscured
407 			const int offsetYA = sY > top ? sY - top : 0;		//top offset of sprite, zero if not obscured
408 			const int offsetYB = sY + rasterY < bottom ? bottom - (sY + rasterY) + 1 : 1;	//bottom offset of sprite, zero if not obscured
409 			//const int offsetYB0 = cast(int)scaleNearestLength(offsetYB, i.scaleVert);
410 			const int sizeX = i.slice.width();		//total displayed width
411 			const int offsetX = left - sX;
412 			const int length = sizeX - offsetXA - offsetXB - 1;
413 			//int lengthY = i.slice.height - offsetYA - offsetYB;
414 			//const int lfour = length * 4;
415 			const int offsetY = sY < top ? (top-sY)*pitch : 0;	//used if top portion of the sprite is off-screen
416 			//offset = i.scaleVert % 1024;
417 			const int scaleVertAbs = i.scaleVert * (i.scaleVert < 0 ? -1 : 1);	//absolute value of vertical scaling, used in various calculations
418 			//int offset, prevOffset;
419 			const int offsetAmount = scaleVertAbs <= 1024 ? 1024 : scaleVertAbs;	//used to limit the amount of re-rendering every line
420 			//offset = offsetYA<<10;
421 			const int offsetYA0 = cast(int)(cast(double)offsetYA / (1024.0 / cast(double)scaleVertAbs));	//amount of skipped lines (I think) TODO: remove floating-point arithmetic
422 			const int sizeXOffset = i.width * (i.scaleVert < 0 ? -1 : 1);
423 			int prevOffset = offsetYA0 * offsetAmount;		//
424 			int offset = offsetYA0 * scaleVertAbs;
425 			const size_t p0offset = (i.scaleHoriz > 0 ? offsetXA : offsetXB); //determines offset based on mirroring
426 			// HACK: as I couldn't figure out a better method yet I decided to scale a whole line, which has a lot of problems
427 			const int scalelength = i.position.width < 2048 ? i.width : 2048;	//limit width to 2048, the minimum required for this scaling method to work
428 			void* dest = workpad + (offsetX + offsetXA)*4 + offsetY;
429 			final switch (i.bmpType) with (BitmapTypes) {
430 				case Bmp4Bit:
431 					ubyte* p0 = cast(ubyte*)i.pixelData + i.width * ((i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0)>>1);
432 					for(int y = offsetYA ; y < i.slice.height - offsetYB ; ){
433 						horizontalScaleNearest4BitAndCLU(p0, src.ptr, palette + (i.paletteSel<<i.paletteSh), scalelength, offsetXA & 1,
434 								i.scaleHoriz);
435 						prevOffset += offsetAmount;
436 						for(; offset < prevOffset; offset += scaleVertAbs){
437 							y++;
438 							i.renderFunc(cast(uint*)src.ptr + p0offset, cast(uint*)dest, length, i.masterAlpha);
439 							dest += pitch;
440 						}
441 						p0 += sizeXOffset >> 1;
442 					}
443 					//}
444 					break;
445 				case Bmp8Bit:
446 					ubyte* p0 = cast(ubyte*)i.pixelData + i.width * (i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0);
447 					for(int y = offsetYA ; y < i.slice.height - offsetYB ; ){
448 						horizontalScaleNearestAndCLU(p0, src.ptr, palette + (i.paletteSel<<i.paletteSh), scalelength, i.scaleHoriz);
449 						prevOffset += 1024;
450 						for(; offset < prevOffset; offset += scaleVertAbs){
451 							y++;
452 							i.renderFunc(cast(uint*)src.ptr + p0offset, cast(uint*)dest, length, i.masterAlpha);
453 							dest += pitch;
454 						}
455 						p0 += sizeXOffset;
456 					}
457 					break;
458 				case Bmp16Bit:
459 					ushort* p0 = cast(ushort*)i.pixelData + i.width * (i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0);
460 					for(int y = offsetYA ; y < i.slice.height - offsetYB ; ){
461 						horizontalScaleNearestAndCLU(p0, src.ptr, palette, scalelength, i.scaleHoriz);
462 						prevOffset += 1024;
463 						for(; offset < prevOffset; offset += scaleVertAbs){
464 							y++;
465 							i.renderFunc(cast(uint*)src.ptr + p0offset, cast(uint*)dest, length, i.masterAlpha);
466 							dest += pitch;
467 						}
468 						p0 += sizeXOffset;
469 					}
470 					break;
471 				case Bmp32Bit:
472 					Color* p0 = cast(Color*)i.pixelData + i.width * (i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0);
473 					for(int y = offsetYA ; y < i.slice.height - offsetYB ; ){
474 						horizontalScaleNearest(p0, src.ptr, scalelength, i.scaleHoriz);
475 						prevOffset += 1024;
476 						for(; offset < prevOffset; offset += scaleVertAbs){
477 							y++;
478 							i.renderFunc(cast(uint*)src.ptr + p0offset, cast(uint*)dest, length, i.masterAlpha);
479 							dest += pitch;
480 						}
481 						p0 += sizeXOffset;
482 					}
483 					//}
484 					break;
485 				case Undefined, Bmp1Bit, Bmp2Bit, Planar:
486 					break;
487 			}
488 
489 			//}
490 		}
491 		//foreach(int threadOffset; threads.parallel)
492 			//free(src[threadOffset]);
493 	}
494 	///Absolute scrolling.
495 	public override void scroll(int x, int y) @safe pure nothrow {
496 		sX = x;
497 		sY = y;
498 		checkAllSprites;
499 	}
500 	///Relative scrolling. Positive values scrolls the layer left and up, negative values scrolls the layer down and right.
501 	public override void relScroll(int x, int y) @safe pure nothrow {
502 		sX += x;
503 		sY += y;
504 		checkAllSprites;
505 	}
506 }