1 /* 2 * Copyright (C) 2015-2020, by Laszlo Szeremi under the Boost license. 3 * 4 * Pixel Perfect Engine, graphics.layers.trnstilelayer module 5 */ 6 7 module PixelPerfectEngine.graphics.layers.trnstilelayer; 8 9 public import PixelPerfectEngine.graphics.layers.base; 10 import PixelPerfectEngine.system.etc; 11 import PixelPerfectEngine.system.exc; 12 import collections.treemap; 13 import inteli.emmintrin; 14 15 /** 16 * Implements a modified TileLayer with transformability with capabilities similar to MODE7. 17 * <br/> 18 * Transform function: 19 * [x',y'] = ([A,B,C,D] * ([x,y] + [sX,sY] - [x_0,y_0])>>>8 + [x_0,y_0] 20 * <br/> 21 * All basic transform values are integer based, 256 equals with 1.0 22 * <br/> 23 * Restrictions compared to standard TileLayer: 24 * <ul> 25 * <li>Tiles must have any of the following sizes: 8, 16, 32, 64; since this layer needs to do modulo computations for each pixel.</li> 26 * <li>In future versions, map sizes for this layer will be restricted to power of two sizes to make things faster</li> 27 * <li>Maximum layer size in pixels are restricted to 65536*65536 due to architectural limitations. Accelerated versions might raise 28 * this limitation.</li> 29 * </ul> 30 * HDMA emulation supported through delegate hBlankInterrupt. 31 */ 32 public class TransformableTileLayer(BMPType = Bitmap16Bit, int TileX = 8, int TileY = 8) : Layer, ITileLayer{ 33 /*if(isPowerOf2(TileX) && isPowerOf2(TileY))*/ 34 protected struct DisplayListItem { 35 BMPType tile; ///For reference counting 36 void* pixelSrc; ///Used for quicker access to the Data 37 wchar ID; ///ID, mainly used as a padding and secondary identification 38 /** 39 * Sets the maximum accessable color amount by the bitmap. 40 * By default, for 4 bit bitmaps, it's 4, and it enables 256 * 16 color palettes. 41 * This limitation is due to the way how the MappingElement struct works. 42 * 8 bit bitmaps can assess the full 256 * 256 palette space. 43 * Lower values can be described to avoid wasting palettes space in cases when the 44 * bitmaps wouldn't use their full capability. 45 * Not used with 16 bit indexed and 32 bit direct color bitmaps. 46 */ 47 ubyte palShift; 48 ubyte reserved; ///Padding for 32 bit 49 this (wchar ID, BMPType tile, ubyte paletteSh = 0) pure @trusted @nogc nothrow { 50 void _systemWrapper() pure @system @nogc nothrow { 51 pixelSrc = cast(void*)tile.getPtr(); 52 } 53 this.ID = ID; 54 this.tile = tile; 55 static if (BMPType.mangleof == Bitmap4Bit.mangleof) this.palShift = paletteSh ? paletteSh : 4; 56 else static if (BMPType.mangleof == Bitmap8Bit.mangleof) this.palShift = paletteSh ? paletteSh : 8; 57 _systemWrapper; 58 } 59 string toString() const { 60 import std.conv : to; 61 string result = to!string(cast(ushort)ID) ~ " ; " ~ to!string(pixelSrc); 62 return result; 63 } 64 } 65 alias DisplayList = TreeMap!(wchar, DisplayListItem, true); 66 protected DisplayList displayList; 67 protected short[4] transformPoints; /** Defines how the layer is being transformed */ 68 protected short[2] tpOrigin; /** Transform point */ 69 protected Bitmap32Bit backbuffer; ///used to store current screen output 70 /*static if(BMPType.mangleof == Bitmap8Bit.mangleof){ 71 protected ubyte[] src; 72 protected Color* palettePtr; ///Shared palette 73 }else static if(BMPType.mangleof == Bitmap16Bit.mangleof){ 74 protected ushort[] src;*/ 75 static if(is(BMPType == Bitmap4Bit) || is(BMPType == Bitmap8Bit) || is(BMPType == Bitmap16Bit)){ 76 protected ushort[] src; 77 }else static if(is(BMPType == Bitmap32Bit)){ 78 79 }else static assert(false,"Template parameter " ~ BMPType.mangleof ~ " not supported by TransformableTileLayer!"); 80 //TO DO: Replace these with a single 32 bit value 81 protected bool needsUpdate; ///Set to true if backbuffer needs an update (might be replaced with flags) 82 public WarpMode warpMode; ///Repeats the whole layer if set to true 83 public ubyte masterVal; ///Sets the master alpha value for the layer 84 public ushort paletteOffset; ///Offsets the palette by the given amount 85 protected int mX, mY; ///"Inherited" from TileLayer 86 static if(TileX == 8) 87 protected immutable int shiftX = 3; 88 else static if(TileX == 16) 89 protected immutable int shiftX = 4; 90 else static if(TileX == 32) 91 protected immutable int shiftX = 5; 92 else static if(TileX == 64) 93 protected immutable int shiftX = 6; 94 else static assert(false,"Unsupported horizontal tile size!"); 95 static if(TileY == 8) 96 protected immutable int shiftY = 3; 97 else static if(TileY == 16) 98 protected immutable int shiftY = 4; 99 else static if(TileY == 32) 100 protected immutable int shiftY = 5; 101 else static if(TileY == 64) 102 protected immutable int shiftY = 6; 103 else static assert(false,"Unsupported vertical tile size!"); 104 protected int totalX, totalY; 105 protected MappingElement[] mapping; 106 107 protected int4 _tileAmpersand; ///Used for quick modulo by power of two to calculate tile positions. 108 protected short8 _increment; 109 protected __m128i _mapAmpersand; ///Used for quick modulo by power of two to read maps 110 //protected __m128i _mapShift; ///Used for quick divide by power of two to read maps 111 112 alias HBIDelegate = @nogc nothrow void delegate(ref short[4] localABCD, ref short[2] localsXsY, ref short[2] localx0y0, short y); 113 /** 114 * Called before each line being redrawn. Can modify global values for each lines. 115 */ 116 public HBIDelegate hBlankInterrupt; 117 118 this (RenderingMode renderMode = RenderingMode.AlphaBlend) { 119 A = 256; 120 B = 0; 121 C = 0; 122 D = 256; 123 x_0 = 0; 124 y_0 = 0; 125 _tileAmpersand = [TileX - 1, TileY - 1, TileX - 1, TileY - 1]; 126 //_mapShift = [shiftX, shiftY, shiftX, shiftY]; 127 masterVal = ubyte.max; 128 setRenderingMode(renderMode); 129 needsUpdate = true; 130 //static if (BMPType.mangleof == Bitmap4Bit.mangleof) _paletteOffset = 4; 131 //else static if (BMPType.mangleof == Bitmap8Bit.mangleof) _paletteOffset = 8; 132 for(int i ; i < 8 ; i+=2) 133 _increment[i] = 2; 134 } 135 136 137 138 override public void setRasterizer(int rX,int rY) { 139 super.setRasterizer(rX,rY); 140 backbuffer = new Bitmap32Bit(rX, rY); 141 static if(BMPType.mangleof == Bitmap8Bit.mangleof || BMPType.mangleof == Bitmap16Bit.mangleof){ 142 src.length = rX; 143 } 144 } 145 146 override public @nogc void updateRaster(void* workpad,int pitch,Color* palette) { 147 //import core.stdc.stdio; 148 if(needsUpdate){ 149 needsUpdate = false; 150 //clear buffer 151 //backbuffer.clear(); 152 Color* dest = backbuffer.getPtr(); 153 short[2] sXsY = [cast(short)sX,cast(short)sY]; 154 short[4] localTP = transformPoints; 155 short[2] localTPO = tpOrigin; 156 //write new data into it 157 for(short y; y < rasterY; y++){ 158 if(hBlankInterrupt !is null){ 159 hBlankInterrupt(localTP, sXsY, localTPO, y); 160 } 161 short8 _sXsY, _localTP, _localTPO; 162 for(int i; i < 8; i++){ 163 _sXsY[i] = sXsY[i & 1]; 164 _localTP[i] = localTP[i & 3]; 165 _localTPO[i] = localTPO[i & 1]; 166 } 167 short8 xy_in; 168 for(int i = 1; i < 8; i += 2){ 169 xy_in[i] = y; 170 } 171 xy_in[4] = 1; 172 xy_in[6] = 1; 173 int4 _localTPO_0; 174 for(int i; i < 4; i++){ 175 _localTPO_0[i] = localTPO[i & 1]; 176 } 177 for(short x; x < rasterX; x++){ 178 int4 xy = _mm_srai_epi32(_mm_madd_epi16(cast(int4)_localTP, cast(int4)(xy_in + _sXsY - _localTPO)),8) 179 + _localTPO_0; 180 /+MappingElement currentTile0 = tileByPixelWithoutTransform(xy[0],xy[1]), 181 currentTile[1] = tileByPixelWithoutTransform(xy[2],xy[3]);+/ 182 MappingElement[2] currentTile = tileByPixelWithoutTransform(xy); 183 xy &= _tileAmpersand; 184 if(currentTile[0].tileID != 0xFFFF){ 185 const DisplayListItem d = displayList[currentTile[0].tileID]; 186 static if(is(BMPType == Bitmap4Bit)){ 187 ubyte* tsrc = cast(ubyte*)d.pixelSrc; 188 }else static if(is(BMPType == Bitmap8Bit)){ 189 ubyte* tsrc = cast(ubyte*)d.pixelSrc; 190 }else static if(is(BMPType == Bitmap16Bit)){ 191 ushort* tsrc = cast(ushort*)d.pixelSrc; 192 }else static if(is(BMPType == Bitmap32Bit)){ 193 Color* tsrc = cast(Color*)d.pixelSrc; 194 } 195 xy[0] = xy[0] & (TileX - 1); 196 xy[1] = xy[1] & (TileY - 1); 197 const int totalOffset = xy[0] + xy[1] * TileX; 198 static if(BMPType.mangleof == Bitmap4Bit.mangleof){ 199 src[x] = cast(ushort)((totalOffset & 1 ? tsrc[totalOffset>>1]>>4 : tsrc[totalOffset>>1] & 0x0F) | 200 currentTile[0].paletteSel<<d.palShift); 201 }else static if(BMPType.mangleof == Bitmap8Bit.mangleof ){ 202 src[x] = cast(ushort)(tsrc[totalOffset] | currentTile[0].paletteSel<<d.palShift); 203 }else static if(BMPType.mangleof == Bitmap16Bit.mangleof){ 204 src[x] = tsrc[totalOffset]; 205 }else{ 206 *dest = *tsrc; 207 dest++; 208 } 209 }else{ 210 static if(BMPType.mangleof == Bitmap8Bit.mangleof || BMPType.mangleof == Bitmap16Bit.mangleof || 211 BMPType.mangleof == Bitmap4Bit.mangleof){ 212 src[x] = 0; 213 }else{ 214 (*dest).raw = 0; 215 } 216 } 217 x++; 218 if(currentTile[1].tileID != 0xFFFF){ 219 const DisplayListItem d = displayList[currentTile[1].tileID]; 220 static if(is(BMPType == Bitmap4Bit)){ 221 ubyte* tsrc = cast(ubyte*)d.pixelSrc; 222 }else static if(is(BMPType == Bitmap8Bit)){ 223 ubyte* tsrc = cast(ubyte*)d.pixelSrc; 224 }else static if(is(BMPType == Bitmap16Bit)){ 225 ushort* tsrc = cast(ushort*)d.pixelSrc; 226 }else static if(is(BMPType == Bitmap32Bit)){ 227 Color* tsrc = cast(Color*)d.pixelSrc; 228 } 229 xy[2] = xy[2] & (TileX - 1); 230 xy[3] = xy[3] & (TileY - 1); 231 const int totalOffset = xy[2] + xy[3] * TileX; 232 static if(BMPType.mangleof == Bitmap4Bit.mangleof){ 233 src[x] = cast(ushort)((totalOffset & 1 ? tsrc[totalOffset>>1]>>4 : tsrc[totalOffset>>1] & 0x0F) | 234 currentTile[1].paletteSel<<d.palShift); 235 } else static if(BMPType.mangleof == Bitmap8Bit.mangleof ) { 236 src[x] = cast(ushort)(tsrc[totalOffset] | currentTile[1].paletteSel<<d.palShift); 237 } else static if(BMPType.mangleof == Bitmap16Bit.mangleof) { 238 src[x] = tsrc[totalOffset]; 239 } else { 240 *dest = *tsrc; 241 dest++; 242 } 243 } else { 244 static if(BMPType.mangleof == Bitmap8Bit.mangleof || BMPType.mangleof == Bitmap16Bit.mangleof || 245 BMPType.mangleof == Bitmap4Bit.mangleof){ 246 src[x] = 0; 247 } else { 248 (*dest).raw = 0; 249 } 250 } 251 xy_in += _increment; 252 253 } 254 /+else { 255 for(short x; x < rasterX; x++){ 256 int[2] xy = transformFunctionInt([x,y], localTP, localTPO, sXsY); 257 //printf("[%i,%i]",xy[0],xy[1]); 258 MappingElement currentTile = tileByPixelWithoutTransform(xy[0],xy[1]); 259 if(currentTile.tileID != 0xFFFF){ 260 const DisplayListItem d = displayList[currentTile.tileID]; 261 static if(BMPType.mangleof == Bitmap4Bit.mangleof){ 262 ubyte* tsrc = cast(ubyte*)d.pixelSrc; 263 }else static if(BMPType.mangleof == Bitmap8Bit.mangleof){ 264 ubyte* tsrc = cast(ubyte*)d.pixelSrc; 265 }else static if(BMPType.mangleof == Bitmap16Bit.mangleof){ 266 ushort* tsrc = cast(ushort*)d.pixelSrc; 267 }else static if(BMPType.mangleof == Bitmap32Bit.mangleof){ 268 Color* tsrc = cast(Color*)d.pixelSrc; 269 } 270 xy[0] = xy[0] & (TileX - 1); 271 xy[1] = xy[1] & (TileY - 1); 272 const int totalOffset = xy[0] + xy[1] * TileX; 273 static if(BMPType.mangleof == Bitmap4Bit.mangleof){ 274 src[x] = (totalOffset & 1 ? tsrc[totalOffset>>1]>>4 : tsrc[totalOffset>>1] & 0x0F) | currentTile.paletteSel<<_paletteOffset; 275 }else static if(BMPType.mangleof == Bitmap8Bit.mangleof ){ 276 src[x] = tsrc[totalOffset] | currentTile.paletteSel<<_paletteOffset; 277 }else static if(BMPType.mangleof == Bitmap16Bit.mangleof){ 278 src[x] = tsrc[totalOffset]; 279 }else{ 280 *dest = *tsrc; 281 dest++; 282 } 283 }else{ 284 static if(BMPType.mangleof == Bitmap8Bit.mangleof || BMPType.mangleof == Bitmap16Bit.mangleof || 285 BMPType.mangleof == Bitmap4Bit.mangleof){ 286 src[x] = 0; 287 }else{ 288 (*dest).raw = 0; 289 } 290 } 291 } 292 }+/ 293 static if(BMPType.mangleof == Bitmap4Bit.mangleof || BMPType.mangleof == Bitmap8Bit.mangleof || 294 BMPType.mangleof == Bitmap16Bit.mangleof){ 295 mainColorLookupFunction(src.ptr, cast(uint*)dest, cast(uint*)palette + paletteOffset, rasterX); 296 dest += rasterX; 297 } 298 } 299 } 300 //render surface onto the raster 301 void* p0 = workpad; 302 Color* c = backbuffer.getPtr(); 303 for(int y; y < rasterY; y++){ 304 mainRenderingFunction(cast(uint*)c, cast(uint*)p0, rasterX, masterVal); 305 c += rasterX; 306 p0 += pitch; 307 } 308 309 } 310 ///Returns which tile is at the given pixel 311 public MappingElement tileByPixel(int x, int y) @nogc @safe pure nothrow const { 312 //static if (USE_INTEL_INTRINSICS) { 313 // return MappingElement.init; 314 //} else { 315 int[2] xy = transformFunctionInt([cast(short)x,cast(short)y],transformPoints,tpOrigin,[cast(short)sX,cast(short)sY]); 316 return tileByPixelWithoutTransform(xy[0],xy[1]); 317 //} 318 319 } 320 ///Returns which tile is at the given pixel. 321 final protected MappingElement tileByPixelWithoutTransform(int x, int y) @nogc @safe pure nothrow const { 322 x >>>= shiftX; 323 y >>>= shiftY; 324 final switch (warpMode) with (WarpMode) { 325 case Off: 326 if (x >= mX || y >= mY || x < 0 || y < 0) return MappingElement(0xFFFF); 327 break; 328 case MapRepeat: 329 x &= _mapAmpersand[0]; 330 y &= _mapAmpersand[1]; 331 break; 332 case TileRepeat: 333 if (x >= mX || y >= mY || x < 0 || y < 0) return MappingElement(0x0000); 334 break; 335 } 336 return mapping[x + y * mX]; 337 } 338 ///Returns two tiles to speed up rendering. 339 final protected MappingElement[2] tileByPixelWithoutTransform(__m128i params) @nogc @safe pure nothrow const { 340 //params >>>= mapShift; 341 params[0] >>= shiftX; 342 params[2] >>= shiftX; 343 params[1] >>= shiftY; 344 params[3] >>= shiftY; 345 MappingElement[2] result; 346 final switch (warpMode) with (WarpMode) { 347 case Off: 348 if (params[0] >= mX || params[1] >= mY || params[0] < 0 || params[1] < 0) result[0] = MappingElement(0xFFFF); 349 else result[0] = mapping[params[0] + params[1] * mX]; 350 if (params[2] >= mX || params[3] >= mY || params[2] < 0 || params[3] < 0) result[1] = MappingElement(0xFFFF); 351 else result[1] = mapping[params[2] + params[3] * mX]; 352 return result; 353 case MapRepeat: 354 params &= _mapAmpersand; 355 result[0] = mapping[params[0] + params[1] * mX]; 356 result[1] = mapping[params[2] + params[3] * mX]; 357 return result; 358 case TileRepeat: 359 if (params[0] >= mX || params[1] >= mY || params[0] < 0 || params[1] < 0) result[0] = MappingElement(0x0000); 360 else result[0] = mapping[params[0] + params[1] * mX]; 361 if (params[2] >= mX || params[3] >= mY || params[2] < 0 || params[3] < 0) result[1] = MappingElement(0x0000); 362 else result[1] = mapping[params[2] + params[3] * mX]; 363 return result; 364 } 365 } 366 /** 367 * Horizontal scaling. Greater than 256 means zooming in, less than 256 means zooming out. 368 */ 369 public @nogc @property pure @safe short A(){ 370 return transformPoints[0]; 371 } 372 /** 373 * Horizontal shearing. 374 */ 375 public @nogc @property pure @safe short B(){ 376 return transformPoints[1]; 377 } 378 /** 379 * Vertical shearing. 380 */ 381 public @nogc @property pure @safe short C(){ 382 return transformPoints[2]; 383 } 384 /** 385 * Vertical scaling. Greater than 256 means zooming in, less than 256 means zooming out. 386 */ 387 public @nogc @property pure @safe short D(){ 388 return transformPoints[3]; 389 } 390 /** 391 * Horizontal transformation offset. 392 */ 393 public @nogc @property pure @safe short x_0(){ 394 return tpOrigin[0]; 395 } 396 /** 397 * Vertical transformation offset. 398 */ 399 public @nogc @property pure @safe short y_0(){ 400 return tpOrigin[1]; 401 } 402 /** 403 * Horizontal scaling. Greater than 256 means zooming in, less than 256 means zooming out. 404 */ 405 public @nogc @property pure @safe short A(short newval){ 406 transformPoints[0] = newval; 407 needsUpdate = true; 408 return transformPoints[0]; 409 } 410 /** 411 * Horizontal shearing. 412 */ 413 public @nogc @property pure @safe short B(short newval){ 414 transformPoints[1] = newval; 415 needsUpdate = true; 416 return transformPoints[1]; 417 } 418 /** 419 * Vertical shearing. 420 */ 421 public @nogc @property pure @safe short C(short newval){ 422 transformPoints[2] = newval; 423 needsUpdate = true; 424 return transformPoints[2]; 425 } 426 /** 427 * Vertical scaling. Greater than 256 means zooming in, less than 256 means zooming out. 428 */ 429 public @nogc @property pure @safe short D(short newval){ 430 transformPoints[3] = newval; 431 needsUpdate = true; 432 return transformPoints[3]; 433 } 434 /** 435 * Horizontal transformation offset. 436 */ 437 public @nogc @property pure @safe short x_0(short newval){ 438 tpOrigin[0] = newval; 439 //tpOrigin[2] = newval; 440 needsUpdate = true; 441 return tpOrigin[0]; 442 } 443 /** 444 * Vertical transformation offset. 445 */ 446 public @nogc @property pure @safe short y_0(short newval){ 447 tpOrigin[1] = newval; 448 //tpOrigin[3] = newval; 449 needsUpdate = true; 450 return tpOrigin[1]; 451 } 452 override public void scroll(int x,int y) { 453 super.scroll(x,y); 454 needsUpdate = true; 455 } 456 override public void relScroll(int x,int y) { 457 super.relScroll(x,y); 458 needsUpdate = true; 459 } 460 public MappingElement[] getMapping() @nogc @safe pure nothrow { 461 return mapping; 462 } 463 /+public MappingElement readMapping(int x, int y) @nogc @safe pure nothrow const { 464 return mapping[(y * mX) + x]; 465 }+/ 466 public int getTileWidth() @nogc @safe pure nothrow const { 467 return TileX; 468 } 469 public int getTileHeight() @nogc @safe pure nothrow const { 470 return TileY; 471 } 472 public int getMX() @nogc @safe pure nothrow const { 473 return mX; 474 } 475 public int getMY() @nogc @safe pure nothrow const { 476 return mY; 477 } 478 public size_t getTX() @nogc @safe pure nothrow const { 479 return totalX; 480 } 481 public size_t getTY() @nogc @safe pure nothrow const { 482 return totalY; 483 } 484 /// Sets the warp mode. 485 /// Returns the new warp mode that is being used. 486 public WarpMode setWarpMode(WarpMode mode) @nogc @safe pure nothrow { 487 return warpMode = mode; 488 } 489 /// Returns the currently used warp mode. 490 public WarpMode getWarpMode() @nogc @safe pure nothrow const { 491 return warpMode; 492 } 493 ///Gets the the ID of the given element from the mapping. x , y : Position. 494 public MappingElement readMapping(int x, int y) @nogc @safe pure nothrow const { 495 if(!warpMode){ 496 if(x < 0 || y < 0 || x >= mX || y >= mY){ 497 return MappingElement(0xFFFF); 498 } 499 }else{ 500 x = x % mX; 501 y = y % mY; 502 } 503 return mapping[x+(mX*y)]; 504 } 505 ///Writes to the map. x , y : Position. w : ID of the tile. 506 @nogc public void writeMapping(int x, int y, MappingElement w) { 507 mapping[x+(mX*y)]=w; 508 } 509 public void addTile(ABitmap tile, wchar id, ubyte paletteSh = 0) { 510 if(typeid(tile) !is typeid(BMPType)){ 511 throw new TileFormatException("Incorrect type of tile!"); 512 } 513 if(tile.width == TileX && tile.height == TileY){ 514 displayList[id] = DisplayListItem(id, cast(BMPType)tile, paletteSh); 515 }else{ 516 throw new TileFormatException("Incorrect tile size!", __FILE__, __LINE__, null); 517 } 518 } 519 ///Returns a tile from the displaylist 520 public ABitmap getTile(wchar id) { 521 return displayList[id].tile; 522 } 523 ///Removes the tile with the ID from the set. 524 public void removeTile(wchar id){ 525 displayList.remove(id); 526 } 527 ///Loads a mapping from an array. x , y : Sizes of the mapping. map : an array representing the elements of the map. 528 ///x*y=map.length 529 public void loadMapping(int x, int y, MappingElement[] mapping) { 530 if (!isPowerOf2(x) || !isPowerOf2(y)) 531 throw new MapFormatException("Map sizes are not power of two!"); 532 if (x * y != mapping.length) 533 throw new MapFormatException("Incorrect map sizes!"); 534 mX=x; 535 mY=y; 536 _mapAmpersand[0] = x - 1; 537 _mapAmpersand[1] = y - 1; 538 _mapAmpersand[2] = x - 1; 539 _mapAmpersand[3] = y - 1; 540 this.mapping = mapping; 541 totalX=mX*TileX; 542 totalY=mY*TileY; 543 } 544 }