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