1705 lines
46 KiB
JavaScript
1705 lines
46 KiB
JavaScript
function MemoryAligned16(size) {
|
|
this.buffer = new Uint16Array(size >> 1);
|
|
};
|
|
|
|
MemoryAligned16.prototype.load8 = function(offset) {
|
|
return (this.loadU8(offset) << 24) >> 24;
|
|
};
|
|
|
|
MemoryAligned16.prototype.load16 = function(offset) {
|
|
return (this.loadU16(offset) << 16) >> 16;
|
|
};
|
|
|
|
MemoryAligned16.prototype.loadU8 = function(offset) {
|
|
var index = offset >> 1;
|
|
if (offset & 1) {
|
|
return (this.buffer[index] & 0xFF00) >>> 8;
|
|
} else {
|
|
return this.buffer[index] & 0x00FF;
|
|
}
|
|
};
|
|
|
|
MemoryAligned16.prototype.loadU16 = function(offset) {
|
|
return this.buffer[offset >> 1];
|
|
};
|
|
|
|
MemoryAligned16.prototype.load32 = function(offset) {
|
|
return this.buffer[(offset >> 1) & ~1] | (this.buffer[(offset >> 1) | 1] << 16);
|
|
};
|
|
|
|
MemoryAligned16.prototype.store8 = function(offset, value) {
|
|
var index = offset >> 1;
|
|
this.store16(offset, (value << 8) | value);
|
|
};
|
|
|
|
MemoryAligned16.prototype.store16 = function(offset, value) {
|
|
this.buffer[offset >> 1] = value;
|
|
};
|
|
|
|
MemoryAligned16.prototype.store32 = function(offset, value) {
|
|
var index = offset >> 1;
|
|
this.store16(offset, this.buffer[index] = value & 0xFFFF);
|
|
this.store16(offset + 2, this.buffer[index + 1] = value >>> 16);
|
|
};
|
|
|
|
MemoryAligned16.prototype.insert = function(start, data) {
|
|
this.buffer.set(data, start);
|
|
};
|
|
|
|
MemoryAligned16.prototype.invalidatePage = function(address) {};
|
|
|
|
function GameBoyAdvanceVRAM(size) {
|
|
MemoryAligned16.call(this, size);
|
|
this.vram = this.buffer;
|
|
};
|
|
|
|
GameBoyAdvanceVRAM.prototype = Object.create(MemoryAligned16.prototype);
|
|
|
|
function GameBoyAdvanceOAM(size) {
|
|
MemoryAligned16.call(this, size);
|
|
this.oam = this.buffer;
|
|
this.objs = new Array(128);
|
|
for (var i = 0; i < 128; ++i) {
|
|
this.objs[i] = new GameBoyAdvanceOBJ(this, i);
|
|
}
|
|
this.scalerot = new Array(32);
|
|
for (var i = 0; i < 32; ++i) {
|
|
this.scalerot[i] = {
|
|
a: 1,
|
|
b: 0,
|
|
c: 0,
|
|
d: 1
|
|
};
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceOAM.prototype = Object.create(MemoryAligned16.prototype);
|
|
|
|
GameBoyAdvanceOAM.prototype.overwrite = function(memory) {
|
|
for (var i = 0; i < (this.buffer.byteLength >> 1); ++i) {
|
|
this.store16(i << 1, memory[i]);
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceOAM.prototype.store16 = function(offset, value) {
|
|
var index = (offset & 0x3F8) >> 3;
|
|
var obj = this.objs[index];
|
|
var scalerot = this.scalerot[index >> 2];
|
|
var layer = obj.priority;
|
|
var disable = obj.disable;
|
|
var y = obj.y;
|
|
switch (offset & 0x00000006) {
|
|
case 0:
|
|
// Attribute 0
|
|
obj.y = value & 0x00FF;
|
|
var wasScalerot = obj.scalerot;
|
|
obj.scalerot = value & 0x0100;
|
|
if (obj.scalerot) {
|
|
obj.scalerotOam = this.scalerot[obj.scalerotParam];
|
|
obj.doublesize = !!(value & 0x0200);
|
|
obj.disable = 0;
|
|
obj.hflip = 0;
|
|
obj.vflip = 0;
|
|
} else {
|
|
obj.doublesize = false;
|
|
obj.disable = value & 0x0200;
|
|
if (wasScalerot) {
|
|
obj.hflip = obj.scalerotParam & 0x0008;
|
|
obj.vflip = obj.scalerotParam & 0x0010;
|
|
}
|
|
}
|
|
obj.mode = (value & 0x0C00) >> 6; // This lines up with the stencil format
|
|
obj.mosaic = value & 0x1000;
|
|
obj.multipalette = value & 0x2000;
|
|
obj.shape = (value & 0xC000) >> 14;
|
|
|
|
obj.recalcSize();
|
|
break;
|
|
case 2:
|
|
// Attribute 1
|
|
obj.x = value & 0x01FF;
|
|
if (obj.scalerot) {
|
|
obj.scalerotParam = (value & 0x3E00) >> 9;
|
|
obj.scalerotOam = this.scalerot[obj.scalerotParam];
|
|
obj.hflip = 0;
|
|
obj.vflip = 0;
|
|
obj.drawScanline = obj.drawScanlineAffine;
|
|
} else {
|
|
obj.hflip = value & 0x1000;
|
|
obj.vflip = value & 0x2000;
|
|
obj.drawScanline = obj.drawScanlineNormal;
|
|
}
|
|
obj.size = (value & 0xC000) >> 14;
|
|
|
|
obj.recalcSize();
|
|
break;
|
|
case 4:
|
|
// Attribute 2
|
|
obj.tileBase = value & 0x03FF;
|
|
obj.priority = (value & 0x0C00) >> 10;
|
|
obj.palette = (value & 0xF000) >> 8; // This is shifted up 4 to make pushPixel faster
|
|
break;
|
|
case 6:
|
|
// Scaling/rotation parameter
|
|
switch (index & 0x3) {
|
|
case 0:
|
|
scalerot.a = (value << 16) / 0x1000000;
|
|
break;
|
|
case 1:
|
|
scalerot.b = (value << 16) / 0x1000000;
|
|
break;
|
|
case 2:
|
|
scalerot.c = (value << 16) / 0x1000000;
|
|
break;
|
|
case 3:
|
|
scalerot.d = (value << 16) / 0x1000000;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
MemoryAligned16.prototype.store16.call(this, offset, value);
|
|
};
|
|
|
|
function GameBoyAdvancePalette() {
|
|
this.colors = [ new Array(0x100), new Array(0x100) ];
|
|
this.adjustedColors = [ new Array(0x100), new Array(0x100) ];
|
|
this.passthroughColors = [
|
|
this.colors[0], // BG0
|
|
this.colors[0], // BG1
|
|
this.colors[0], // BG2
|
|
this.colors[0], // BG3
|
|
this.colors[1], // OBJ
|
|
this.colors[0] // Backdrop
|
|
];
|
|
this.blendY = 1;
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.overwrite = function(memory) {
|
|
for (var i = 0; i < 512; ++i) {
|
|
this.store16(i << 1, memory[i]);
|
|
}
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.loadU8 = function(offset) {
|
|
return (this.loadU16(offset) >> (8 * (offset & 1))) & 0xFF;
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.loadU16 = function(offset) {
|
|
return this.colors[(offset & 0x200) >> 9][(offset & 0x1FF) >> 1];
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.load16 = function(offset) {
|
|
return (this.loadU16(offset) << 16) >> 16;
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.load32 = function(offset) {
|
|
return this.loadU16(offset) | (this.loadU16(offset + 2) << 16);
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.store16 = function(offset, value) {
|
|
var type = (offset & 0x200) >> 9;
|
|
var index = (offset & 0x1FF) >> 1;
|
|
this.colors[type][index] = value;
|
|
this.adjustedColors[type][index] = this.adjustColor(value);
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.store32 = function(offset, value) {
|
|
this.store16(offset, value & 0xFFFF);
|
|
this.store16(offset + 2, value >> 16);
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.invalidatePage = function(address) {};
|
|
|
|
GameBoyAdvancePalette.prototype.convert16To32 = function(value, input) {
|
|
var r = (value & 0x001F) << 3;
|
|
var g = (value & 0x03E0) >> 2;
|
|
var b = (value & 0x7C00) >> 7;
|
|
|
|
input[0] = r;
|
|
input[1] = g;
|
|
input[2] = b;
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.mix = function(aWeight, aColor, bWeight, bColor) {
|
|
var ar = (aColor & 0x001F);
|
|
var ag = (aColor & 0x03E0) >> 5;
|
|
var ab = (aColor & 0x7C00) >> 10;
|
|
|
|
var br = (bColor & 0x001F);
|
|
var bg = (bColor & 0x03E0) >> 5;
|
|
var bb = (bColor & 0x7C00) >> 10;
|
|
|
|
var r = Math.min(aWeight * ar + bWeight * br, 0x1F);
|
|
var g = Math.min(aWeight * ag + bWeight * bg, 0x1F);
|
|
var b = Math.min(aWeight * ab + bWeight * bb, 0x1F);
|
|
|
|
return r | (g << 5) | (b << 10);
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.makeDarkPalettes = function(layers) {
|
|
if (this.adjustColor != this.adjustColorDark) {
|
|
this.adjustColor = this.adjustColorDark;
|
|
this.resetPalettes();
|
|
}
|
|
this.resetPaletteLayers(layers);
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.makeBrightPalettes = function(layers) {
|
|
if (this.adjustColor != this.adjustColorBright) {
|
|
this.adjustColor = this.adjustColorBright;
|
|
this.resetPalettes();
|
|
}
|
|
this.resetPaletteLayers(layers);
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.makeNormalPalettes = function() {
|
|
this.passthroughColors[0] = this.colors[0];
|
|
this.passthroughColors[1] = this.colors[0];
|
|
this.passthroughColors[2] = this.colors[0];
|
|
this.passthroughColors[3] = this.colors[0];
|
|
this.passthroughColors[4] = this.colors[1];
|
|
this.passthroughColors[5] = this.colors[0];
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.makeSpecialPalette = function(layer) {
|
|
this.passthroughColors[layer] = this.adjustedColors[layer == 4 ? 1 : 0];
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.makeNormalPalette = function(layer) {
|
|
this.passthroughColors[layer] = this.colors[layer == 4 ? 1 : 0];
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.resetPaletteLayers = function(layers) {
|
|
if (layers & 0x01) {
|
|
this.passthroughColors[0] = this.adjustedColors[0];
|
|
} else {
|
|
this.passthroughColors[0] = this.colors[0];
|
|
}
|
|
if (layers & 0x02) {
|
|
this.passthroughColors[1] = this.adjustedColors[0];
|
|
} else {
|
|
this.passthroughColors[1] = this.colors[0];
|
|
}
|
|
if (layers & 0x04) {
|
|
this.passthroughColors[2] = this.adjustedColors[0];
|
|
} else {
|
|
this.passthroughColors[2] = this.colors[0];
|
|
}
|
|
if (layers & 0x08) {
|
|
this.passthroughColors[3] = this.adjustedColors[0];
|
|
} else {
|
|
this.passthroughColors[3] = this.colors[0];
|
|
}
|
|
if (layers & 0x10) {
|
|
this.passthroughColors[4] = this.adjustedColors[1];
|
|
} else {
|
|
this.passthroughColors[4] = this.colors[1];
|
|
}
|
|
if (layers & 0x20) {
|
|
this.passthroughColors[5] = this.adjustedColors[0];
|
|
} else {
|
|
this.passthroughColors[5] = this.colors[0];
|
|
}
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.resetPalettes = function() {
|
|
var i;
|
|
var outPalette = this.adjustedColors[0];
|
|
var inPalette = this.colors[0];
|
|
for (i = 0; i < 256; ++i) {
|
|
outPalette[i] = this.adjustColor(inPalette[i]);
|
|
}
|
|
|
|
outPalette = this.adjustedColors[1];
|
|
inPalette = this.colors[1];
|
|
for (i = 0; i < 256; ++i) {
|
|
outPalette[i] = this.adjustColor(inPalette[i]);
|
|
}
|
|
}
|
|
|
|
GameBoyAdvancePalette.prototype.accessColor = function(layer, index) {
|
|
return this.passthroughColors[layer][index];
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.adjustColorDark = function(color) {
|
|
var r = (color & 0x001F);
|
|
var g = (color & 0x03E0) >> 5;
|
|
var b = (color & 0x7C00) >> 10;
|
|
|
|
r = r - (r * this.blendY);
|
|
g = g - (g * this.blendY);
|
|
b = b - (b * this.blendY);
|
|
|
|
return r | (g << 5) | (b << 10);
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.adjustColorBright = function(color) {
|
|
var r = (color & 0x001F);
|
|
var g = (color & 0x03E0) >> 5;
|
|
var b = (color & 0x7C00) >> 10;
|
|
|
|
r = r + ((31 - r) * this.blendY);
|
|
g = g + ((31 - g) * this.blendY);
|
|
b = b + ((31 - b) * this.blendY);
|
|
|
|
return r | (g << 5) | (b << 10);
|
|
};
|
|
|
|
GameBoyAdvancePalette.prototype.adjustColor = GameBoyAdvancePalette.prototype.adjustColorBright;
|
|
|
|
GameBoyAdvancePalette.prototype.setBlendY = function(y) {
|
|
if (this.blendY != y) {
|
|
this.blendY = y;
|
|
this.resetPalettes();
|
|
}
|
|
};
|
|
|
|
function GameBoyAdvanceOBJ(oam, index) {
|
|
this.TILE_OFFSET = 0x10000;
|
|
this.oam = oam;
|
|
|
|
this.index = index;
|
|
this.x = 0;
|
|
this.y = 0;
|
|
this.scalerot = 0;
|
|
this.doublesize = false;
|
|
this.disable = 1;
|
|
this.mode = 0;
|
|
this.mosaic = false;
|
|
this.multipalette = false;
|
|
this.shape = 0;
|
|
this.scalerotParam = 0;
|
|
this.hflip = 0;
|
|
this.vflip = 0;
|
|
this.tileBase = 0;
|
|
this.priority = 0;
|
|
this.palette = 0;
|
|
this.drawScanline = this.drawScanlineNormal;
|
|
this.pushPixel = GameBoyAdvanceSoftwareRenderer.pushPixel;
|
|
this.cachedWidth = 8;
|
|
this.cachedHeight = 8;
|
|
};
|
|
|
|
GameBoyAdvanceOBJ.prototype.drawScanlineNormal = function(backing, y, yOff, start, end) {
|
|
var video = this.oam.video;
|
|
var x;
|
|
var underflow;
|
|
var offset;
|
|
var mask = this.mode | video.target2[video.LAYER_OBJ] | (this.priority << 1);
|
|
if (this.mode == 0x10) {
|
|
mask |= video.TARGET1_MASK;
|
|
}
|
|
if (video.blendMode == 1 && video.alphaEnabled) {
|
|
mask |= video.target1[video.LAYER_OBJ];
|
|
}
|
|
|
|
var totalWidth = this.cachedWidth;
|
|
if (this.x < video.HORIZONTAL_PIXELS) {
|
|
if (this.x < start) {
|
|
underflow = start - this.x;
|
|
offset = start;
|
|
} else {
|
|
underflow = 0;
|
|
offset = this.x;
|
|
}
|
|
if (end < this.cachedWidth + this.x) {
|
|
totalWidth = end - this.x;
|
|
}
|
|
} else {
|
|
underflow = start + 512 - this.x;
|
|
offset = start;
|
|
if (end < this.cachedWidth - underflow) {
|
|
totalWidth = end;
|
|
}
|
|
}
|
|
|
|
var localX;
|
|
var localY;
|
|
if (!this.vflip) {
|
|
localY = y - yOff;
|
|
} else {
|
|
localY = this.cachedHeight - y + yOff - 1;
|
|
}
|
|
var localYLo = localY & 0x7;
|
|
var mosaicX;
|
|
var tileOffset;
|
|
|
|
var paletteShift = this.multipalette ? 1 : 0;
|
|
|
|
if (video.objCharacterMapping) {
|
|
tileOffset = ((localY & 0x01F8) * this.cachedWidth) >> 6;
|
|
} else {
|
|
tileOffset = (localY & 0x01F8) << (2 - paletteShift);
|
|
}
|
|
|
|
if (this.mosaic) {
|
|
mosaicX = video.objMosaicX - 1 - (video.objMosaicX + offset - 1) % video.objMosaicX;
|
|
offset += mosaicX;
|
|
underflow += mosaicX;
|
|
}
|
|
if (!this.hflip) {
|
|
localX = underflow;
|
|
} else {
|
|
localX = this.cachedWidth - underflow - 1;
|
|
}
|
|
|
|
var tileRow = video.accessTile(this.TILE_OFFSET + (x & 0x4) * paletteShift, this.tileBase + (tileOffset << paletteShift) + ((localX & 0x01F8) >> (3 - paletteShift)), localYLo << paletteShift);
|
|
for (x = underflow; x < totalWidth; ++x) {
|
|
mosaicX = this.mosaic ? offset % video.objMosaicX : 0;
|
|
if (!this.hflip) {
|
|
localX = x - mosaicX;
|
|
} else {
|
|
localX = this.cachedWidth - (x - mosaicX) - 1;
|
|
}
|
|
if (!paletteShift) {
|
|
if (!(x & 0x7) || (this.mosaic && !mosaicX)) {
|
|
tileRow = video.accessTile(this.TILE_OFFSET, this.tileBase + tileOffset + (localX >> 3), localYLo);
|
|
}
|
|
} else {
|
|
if (!(x & 0x3) || (this.mosaic && !mosaicX)) {
|
|
tileRow = video.accessTile(this.TILE_OFFSET + (localX & 0x4), this.tileBase + (tileOffset << 1) + ((localX & 0x01F8) >> 2), localYLo << 1);
|
|
}
|
|
}
|
|
this.pushPixel(video.LAYER_OBJ, this, video, tileRow, localX & 0x7, offset, backing, mask, false);
|
|
offset++;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceOBJ.prototype.drawScanlineAffine = function(backing, y, yOff, start, end) {
|
|
var video = this.oam.video;
|
|
var x;
|
|
var underflow;
|
|
var offset;
|
|
var mask = this.mode | video.target2[video.LAYER_OBJ] | (this.priority << 1);
|
|
if (this.mode == 0x10) {
|
|
mask |= video.TARGET1_MASK;
|
|
}
|
|
if (video.blendMode == 1 && video.alphaEnabled) {
|
|
mask |= video.target1[video.LAYER_OBJ];
|
|
}
|
|
|
|
var localX;
|
|
var localY;
|
|
var yDiff = y - yOff;
|
|
var tileOffset;
|
|
|
|
var paletteShift = this.multipalette ? 1 : 0;
|
|
var totalWidth = this.cachedWidth << this.doublesize;
|
|
var totalHeight = this.cachedHeight << this.doublesize;
|
|
var drawWidth = totalWidth;
|
|
if (drawWidth > video.HORIZONTAL_PIXELS) {
|
|
totalWidth = video.HORIZONTAL_PIXELS;
|
|
}
|
|
|
|
if (this.x < video.HORIZONTAL_PIXELS) {
|
|
if (this.x < start) {
|
|
underflow = start - this.x;
|
|
offset = start;
|
|
} else {
|
|
underflow = 0;
|
|
offset = this.x;
|
|
}
|
|
if (end < drawWidth + this.x) {
|
|
drawWidth = end - this.x;
|
|
}
|
|
} else {
|
|
underflow = start + 512 - this.x;
|
|
offset = start;
|
|
if (end < drawWidth - underflow) {
|
|
drawWidth = end;
|
|
}
|
|
}
|
|
|
|
for (x = underflow; x < drawWidth; ++x) {
|
|
localX = this.scalerotOam.a * (x - (totalWidth >> 1)) + this.scalerotOam.b * (yDiff - (totalHeight >> 1)) + (this.cachedWidth >> 1);
|
|
localY = this.scalerotOam.c * (x - (totalWidth >> 1)) + this.scalerotOam.d * (yDiff - (totalHeight >> 1)) + (this.cachedHeight >> 1);
|
|
if (this.mosaic) {
|
|
localX -= (x % video.objMosaicX) * this.scalerotOam.a + (y % video.objMosaicY) * this.scalerotOam.b;
|
|
localY -= (x % video.objMosaicX) * this.scalerotOam.c + (y % video.objMosaicY) * this.scalerotOam.d;
|
|
}
|
|
|
|
if (localX < 0 || localX >= this.cachedWidth || localY < 0 || localY >= this.cachedHeight) {
|
|
offset++;
|
|
continue;
|
|
}
|
|
|
|
if (video.objCharacterMapping) {
|
|
tileOffset = ((localY & 0x01F8) * this.cachedWidth) >> 6;
|
|
} else {
|
|
tileOffset = (localY & 0x01F8) << (2 - paletteShift);
|
|
}
|
|
tileRow = video.accessTile(this.TILE_OFFSET + (localX & 0x4) * paletteShift, this.tileBase + (tileOffset << paletteShift) + ((localX & 0x01F8) >> (3 - paletteShift)), (localY & 0x7) << paletteShift);
|
|
this.pushPixel(video.LAYER_OBJ, this, video, tileRow, localX & 0x7, offset, backing, mask, false);
|
|
offset++;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceOBJ.prototype.recalcSize = function() {
|
|
switch (this.shape) {
|
|
case 0:
|
|
// Square
|
|
this.cachedHeight = this.cachedWidth = 8 << this.size;
|
|
break;
|
|
case 1:
|
|
// Horizontal
|
|
switch (this.size) {
|
|
case 0:
|
|
this.cachedHeight = 8;
|
|
this.cachedWidth = 16;
|
|
break;
|
|
case 1:
|
|
this.cachedHeight = 8;
|
|
this.cachedWidth = 32;
|
|
break;
|
|
case 2:
|
|
this.cachedHeight = 16;
|
|
this.cachedWidth = 32;
|
|
break;
|
|
case 3:
|
|
this.cachedHeight = 32;
|
|
this.cachedWidth = 64;
|
|
break;
|
|
}
|
|
break;
|
|
case 2:
|
|
// Vertical
|
|
switch (this.size) {
|
|
case 0:
|
|
this.cachedHeight = 16;
|
|
this.cachedWidth = 8;
|
|
break;
|
|
case 1:
|
|
this.cachedHeight = 32;
|
|
this.cachedWidth = 8;
|
|
break;
|
|
case 2:
|
|
this.cachedHeight = 32;
|
|
this.cachedWidth = 16;
|
|
break;
|
|
case 3:
|
|
this.cachedHeight = 64;
|
|
this.cachedWidth = 32;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
// Bad!
|
|
}
|
|
};
|
|
|
|
function GameBoyAdvanceOBJLayer(video, index) {
|
|
this.video = video;
|
|
this.bg = false;
|
|
this.index = video.LAYER_OBJ;
|
|
this.priority = index;
|
|
this.enabled = false;
|
|
this.objwin = 0;
|
|
};
|
|
|
|
GameBoyAdvanceOBJLayer.prototype.drawScanline = function(backing, layer, start, end) {
|
|
var y = this.video.vcount;
|
|
var wrappedY;
|
|
var mosaicY;
|
|
var obj;
|
|
if (start >= end) {
|
|
return;
|
|
}
|
|
var objs = this.video.oam.objs;
|
|
for (var i = 0; i < objs.length; ++i) {
|
|
obj = objs[i];
|
|
if (obj.disable) {
|
|
continue;
|
|
}
|
|
if ((obj.mode & this.video.OBJWIN_MASK) != this.objwin) {
|
|
continue;
|
|
}
|
|
if (!(obj.mode & this.video.OBJWIN_MASK) && this.priority != obj.priority) {
|
|
continue;
|
|
}
|
|
if (obj.y < this.video.VERTICAL_PIXELS) {
|
|
wrappedY = obj.y;
|
|
} else {
|
|
wrappedY = obj.y - 256;
|
|
}
|
|
var totalHeight;
|
|
if (!obj.scalerot) {
|
|
totalHeight = obj.cachedHeight;
|
|
} else {
|
|
totalHeight = obj.cachedHeight << obj.doublesize;
|
|
}
|
|
if (!obj.mosaic) {
|
|
mosaicY = y;
|
|
} else {
|
|
mosaicY = y - y % this.video.objMosaicY;
|
|
}
|
|
if (wrappedY <= y && (wrappedY + totalHeight) > y) {
|
|
obj.drawScanline(backing, mosaicY, wrappedY, start, end);
|
|
}
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceOBJLayer.prototype.objComparator = function(a, b) {
|
|
return a.index - b.index;
|
|
};
|
|
|
|
function GameBoyAdvanceSoftwareRenderer() {
|
|
this.LAYER_BG0 = 0;
|
|
this.LAYER_BG1 = 1;
|
|
this.LAYER_BG2 = 2;
|
|
this.LAYER_BG3 = 3;
|
|
this.LAYER_OBJ = 4;
|
|
this.LAYER_BACKDROP = 5;
|
|
|
|
this.HORIZONTAL_PIXELS = 240;
|
|
this.VERTICAL_PIXELS = 160;
|
|
|
|
this.LAYER_MASK = 0x06;
|
|
this.BACKGROUND_MASK = 0x01;
|
|
this.TARGET2_MASK = 0x08;
|
|
this.TARGET1_MASK = 0x10;
|
|
this.OBJWIN_MASK = 0x20;
|
|
this.WRITTEN_MASK = 0x80;
|
|
|
|
this.PRIORITY_MASK = this.LAYER_MASK | this.BACKGROUND_MASK;
|
|
|
|
this.drawBackdrop = new (function(video) {
|
|
this.bg = true;
|
|
this.priority = -1;
|
|
this.index = video.LAYER_BACKDROP;
|
|
this.enabled = true;
|
|
|
|
this.drawScanline = function(backing, layer, start, end) {
|
|
// TODO: interactions with blend modes and OBJWIN
|
|
for (var x = start; x < end; ++x) {
|
|
if (!(backing.stencil[x] & video.WRITTEN_MASK)) {
|
|
backing.color[x] = video.palette.accessColor(this.index, 0);
|
|
backing.stencil[x] = video.WRITTEN_MASK;
|
|
} else if (backing.stencil[x] & video.TARGET1_MASK) {
|
|
backing.color[x] = video.palette.mix(video.blendB, video.palette.accessColor(this.index, 0), video.blendA, backing.color[x]);
|
|
backing.stencil[x] = video.WRITTEN_MASK;
|
|
}
|
|
}
|
|
}
|
|
})(this);
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.clear = function(mmu) {
|
|
this.palette = new GameBoyAdvancePalette();
|
|
this.vram = new GameBoyAdvanceVRAM(mmu.SIZE_VRAM);
|
|
this.oam = new GameBoyAdvanceOAM(mmu.SIZE_OAM);
|
|
this.oam.video = this;
|
|
this.objLayers = [
|
|
new GameBoyAdvanceOBJLayer(this, 0),
|
|
new GameBoyAdvanceOBJLayer(this, 1),
|
|
new GameBoyAdvanceOBJLayer(this, 2),
|
|
new GameBoyAdvanceOBJLayer(this, 3)
|
|
];
|
|
this.objwinLayer = new GameBoyAdvanceOBJLayer(this, 4);
|
|
this.objwinLayer.objwin = this.OBJWIN_MASK;
|
|
|
|
// DISPCNT
|
|
this.backgroundMode = 0;
|
|
this.displayFrameSelect = 0;
|
|
this.hblankIntervalFree = 0;
|
|
this.objCharacterMapping = 0;
|
|
this.forcedBlank = 1;
|
|
this.win0 = 0;
|
|
this.win1 = 0;
|
|
this.objwin = 0;
|
|
|
|
// VCOUNT
|
|
this.vcount = -1;
|
|
|
|
// WIN0H
|
|
this.win0Left = 0;
|
|
this.win0Right = 240;
|
|
|
|
// WIN1H
|
|
this.win1Left = 0;
|
|
this.win1Right = 240;
|
|
|
|
// WIN0V
|
|
this.win0Top = 0;
|
|
this.win0Bottom = 160;
|
|
|
|
// WIN1V
|
|
this.win1Top = 0;
|
|
this.win1Bottom = 160;
|
|
|
|
// WININ/WINOUT
|
|
this.windows = new Array();
|
|
for (var i = 0; i < 4; ++i) {
|
|
this.windows.push({
|
|
enabled: [ false, false, false, false, false, true ],
|
|
special: 0
|
|
});
|
|
};
|
|
|
|
// BLDCNT
|
|
this.target1 = new Array(5);
|
|
this.target2 = new Array(5);
|
|
this.blendMode = 0;
|
|
|
|
// BLDALPHA
|
|
this.blendA = 0;
|
|
this.blendB = 0;
|
|
|
|
// BLDY
|
|
this.blendY = 0;
|
|
|
|
// MOSAIC
|
|
this.bgMosaicX = 1;
|
|
this.bgMosaicY = 1;
|
|
this.objMosaicX = 1;
|
|
this.objMosaicY = 1;
|
|
|
|
this.lastHblank = 0;
|
|
this.nextHblank = this.HDRAW_LENGTH;
|
|
this.nextEvent = this.nextHblank;
|
|
|
|
this.nextHblankIRQ = 0;
|
|
this.nextVblankIRQ = 0;
|
|
this.nextVcounterIRQ = 0;
|
|
|
|
this.bg = new Array();
|
|
for (var i = 0; i < 4; ++i) {
|
|
this.bg.push({
|
|
bg: true,
|
|
index: i,
|
|
enabled: false,
|
|
video: this,
|
|
vram: this.vram,
|
|
priority: 0,
|
|
charBase: 0,
|
|
mosaic: false,
|
|
multipalette: false,
|
|
screenBase: 0,
|
|
overflow: 0,
|
|
size: 0,
|
|
x: 0,
|
|
y: 0,
|
|
refx: 0,
|
|
refy: 0,
|
|
dx: 1,
|
|
dmx: 0,
|
|
dy: 0,
|
|
dmy: 1,
|
|
sx: 0,
|
|
sy: 0,
|
|
pushPixel: GameBoyAdvanceSoftwareRenderer.pushPixel,
|
|
drawScanline: this.drawScanlineBGMode0
|
|
});
|
|
}
|
|
|
|
this.bgModes = [
|
|
this.drawScanlineBGMode0,
|
|
this.drawScanlineBGMode2, // Modes 1 and 2 are identical for layers 2 and 3
|
|
this.drawScanlineBGMode2,
|
|
this.drawScanlineBGMode3,
|
|
this.drawScanlineBGMode4,
|
|
this.drawScanlineBGMode5
|
|
];
|
|
|
|
this.drawLayers = [
|
|
this.bg[0],
|
|
this.bg[1],
|
|
this.bg[2],
|
|
this.bg[3],
|
|
this.objLayers[0],
|
|
this.objLayers[1],
|
|
this.objLayers[2],
|
|
this.objLayers[3],
|
|
this.objwinLayer,
|
|
this.drawBackdrop
|
|
];
|
|
|
|
this,objwinActive = false;
|
|
this.alphaEnabled = false;
|
|
|
|
this.scanline = {
|
|
color: new Uint16Array(this.HORIZONTAL_PIXELS),
|
|
// Stencil format:
|
|
// Bits 0-1: Layer
|
|
// Bit 2: Is background
|
|
// Bit 3: Is Target 2
|
|
// Bit 4: Is Target 1
|
|
// Bit 5: Is OBJ Window
|
|
// Bit 6: Reserved
|
|
// Bit 7: Has been written
|
|
stencil: new Uint8Array(this.HORIZONTAL_PIXELS)
|
|
};
|
|
this.sharedColor = [ 0, 0, 0 ];
|
|
this.sharedMap = {
|
|
tile: 0,
|
|
hflip: false,
|
|
vflip: false,
|
|
palette: 0
|
|
};
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.clearSubsets = function(mmu, regions) {
|
|
if (regions & 0x04) {
|
|
this.palette.overwrite(new Uint16Array(mmu.SIZE_PALETTE >> 1));
|
|
}
|
|
|
|
if (regions & 0x08) {
|
|
this.vram.insert(0, new Uint16Array(mmu.SIZE_VRAM >> 1));
|
|
}
|
|
|
|
if (regions & 0x10) {
|
|
this.oam.overwrite(new Uint16Array(mmu.SIZE_OAM >> 1));
|
|
this.oam.video = this;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.freeze = function() {
|
|
console.log('GameBoyAdvanceSoftwareRenderer.freeze', this);
|
|
return {
|
|
palette: this.palette.freeze(),
|
|
// oam: this.oam.freeze(),
|
|
vram: this.vram.freeze(),
|
|
};
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.defrost = function(f) {
|
|
// 游戏人物
|
|
this.palette.defrost(f.palette);
|
|
// this.oam.defrost(f.oam);
|
|
// 恢复子弹, 血条等数据的显示
|
|
this.vram.defrost(f.vram);
|
|
};
|
|
|
|
// GameBoyAdvanceOAM.prototype.freeze = function() {
|
|
// return {
|
|
// buffer: Serializer.prefix(this.buffer.buffer),
|
|
// oam: Serializer.prefix(this.oam.buffer),
|
|
// }
|
|
// }
|
|
//
|
|
// GameBoyAdvanceOAM.prototype.defrost = function(frost) {
|
|
// this.buffer = new Uint16Array(frost.buffer);
|
|
// this.oam = new Uint16Array(frost.oam);
|
|
// }
|
|
|
|
GameBoyAdvancePalette.prototype.freeze = function() {
|
|
return {
|
|
colors: this.colors,
|
|
adjustedColors: this.adjustedColors,
|
|
passthroughColors: this.passthroughColors
|
|
}
|
|
};
|
|
GameBoyAdvancePalette.prototype.defrost = function(frost) {
|
|
this.colors = frost.colors;
|
|
this.adjustedColors = frost.adjustedColors;
|
|
this.passthroughColors = frost.passthroughColors;
|
|
};
|
|
|
|
|
|
GameBoyAdvanceVRAM.prototype.freeze = function() {
|
|
return {
|
|
buffer: Serializer.prefix(this.buffer.buffer),
|
|
vram: Serializer.prefix(this.vram.buffer),
|
|
}
|
|
}
|
|
|
|
GameBoyAdvanceVRAM.prototype.defrost = function(frost) {
|
|
this.buffer = new Uint16Array(frost.buffer);
|
|
this.vram = new Uint16Array(frost.vram);
|
|
}
|
|
|
|
// GameBoyAdvanceOBJ.prototype.freeze = function() {
|
|
// return {
|
|
// cachedHeight: this.cachedHeight,
|
|
// cachedWidth: this.cachedWidth,
|
|
// disable: this.disable,
|
|
// doublesize: this.doublesize,
|
|
// hflip: this.hflip,
|
|
// index: this.index,
|
|
// mode: this.mode,
|
|
// mosaic: this.mosaic,
|
|
// multipalette: this.multipalette,
|
|
// palette: this.palette,
|
|
// priority: this.priority,
|
|
// scalerot: this.scalerot,
|
|
// scalerotOam: this.scalerotOam,
|
|
// scalerotParam: this.scalerotParam,
|
|
// shape: this.shape,
|
|
// size: this.size,
|
|
// tileBase: this.tileBase,
|
|
// vflip: this.vflip,
|
|
// x: this.x,
|
|
// y: this.y,
|
|
// }
|
|
// }
|
|
//
|
|
// GameBoyAdvanceOBJ.prototype.defrost = function(f) {
|
|
// this.cachedHeight = f.cachedHeight;
|
|
// this.cachedWidth = f.cachedWidth;
|
|
// this.disable = f.disable;
|
|
// this.doublesize = f.doublesize;
|
|
// this.hflip = f.hflip;
|
|
// this.index = f.index;
|
|
// this.mode = f.mode;
|
|
// this.mosaic = f.mosaic;
|
|
// this.multipalette = f.multipalette;
|
|
// this.palette = f.palette;
|
|
// this.priority = f.priority;
|
|
// this.scalerot = f.scalerot;
|
|
// this.scalerotOam = f.scalerotOam;
|
|
// this.scalerotParam = f.scalerotParam;
|
|
// this.shape = f.shape;
|
|
// this.size = f.size;
|
|
// this.tileBase = f.tileBase;
|
|
// this.vflip = f.vflip;
|
|
// this.x = f.x;
|
|
// this.y = f.y;
|
|
// }
|
|
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.setBacking = function(backing) {
|
|
this.pixelData = backing;
|
|
|
|
// Clear backing first
|
|
for (var offset = 0; offset < this.HORIZONTAL_PIXELS * this.VERTICAL_PIXELS * 4;) {
|
|
this.pixelData.data[offset++] = 0xFF;
|
|
this.pixelData.data[offset++] = 0xFF;
|
|
this.pixelData.data[offset++] = 0xFF;
|
|
this.pixelData.data[offset++] = 0xFF;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeDisplayControl = function(value) {
|
|
this.backgroundMode = value & 0x0007;
|
|
this.displayFrameSelect = value & 0x0010;
|
|
this.hblankIntervalFree = value & 0x0020;
|
|
this.objCharacterMapping = value & 0x0040;
|
|
this.forcedBlank = value & 0x0080;
|
|
this.bg[0].enabled = value & 0x0100;
|
|
this.bg[1].enabled = value & 0x0200;
|
|
this.bg[2].enabled = value & 0x0400;
|
|
this.bg[3].enabled = value & 0x0800;
|
|
this.objLayers[0].enabled = value & 0x1000;
|
|
this.objLayers[1].enabled = value & 0x1000;
|
|
this.objLayers[2].enabled = value & 0x1000;
|
|
this.objLayers[3].enabled = value & 0x1000;
|
|
this.win0 = value & 0x2000;
|
|
this.win1 = value & 0x4000;
|
|
this.objwin = value & 0x8000;
|
|
this.objwinLayer.enabled = value & 0x1000 && value & 0x8000;
|
|
|
|
// Total hack so we can store both things that would set it to 256-color mode in the same variable
|
|
this.bg[2].multipalette &= ~0x0001;
|
|
this.bg[3].multipalette &= ~0x0001;
|
|
if (this.backgroundMode > 0) {
|
|
this.bg[2].multipalette |= 0x0001;
|
|
}
|
|
if (this.backgroundMode == 2) {
|
|
this.bg[3].multipalette |= 0x0001;
|
|
}
|
|
|
|
this.resetLayers();
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeBackgroundControl = function(bg, value) {
|
|
var bgData = this.bg[bg];
|
|
bgData.priority = value & 0x0003;
|
|
bgData.charBase = (value & 0x000C) << 12;
|
|
bgData.mosaic = value & 0x0040;
|
|
bgData.multipalette &= ~0x0080;
|
|
if (bg < 2 || this.backgroundMode == 0) {
|
|
bgData.multipalette |= value & 0x0080;
|
|
}
|
|
bgData.screenBase = (value & 0x1F00) << 3;
|
|
bgData.overflow = value & 0x2000;
|
|
bgData.size = (value & 0xC000) >> 14;
|
|
|
|
this.drawLayers.sort(this.layerComparator);
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeBackgroundHOffset = function(bg, value) {
|
|
this.bg[bg].x = value & 0x1FF;
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeBackgroundVOffset = function(bg, value) {
|
|
this.bg[bg].y = value & 0x1FF;
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeBackgroundRefX = function(bg, value) {
|
|
this.bg[bg].refx = (value << 4) / 0x1000;
|
|
this.bg[bg].sx = this.bg[bg].refx;
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeBackgroundRefY = function(bg, value) {
|
|
this.bg[bg].refy = (value << 4) / 0x1000;
|
|
this.bg[bg].sy = this.bg[bg].refy;
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeBackgroundParamA = function(bg, value) {
|
|
this.bg[bg].dx = (value << 16) / 0x1000000;
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeBackgroundParamB = function(bg, value) {
|
|
this.bg[bg].dmx = (value << 16) / 0x1000000;
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeBackgroundParamC = function(bg, value) {
|
|
this.bg[bg].dy = (value << 16) / 0x1000000;
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeBackgroundParamD = function(bg, value) {
|
|
this.bg[bg].dmy = (value << 16) / 0x1000000;
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeWin0H = function(value) {
|
|
this.win0Left = (value & 0xFF00) >> 8;
|
|
this.win0Right = Math.min(this.HORIZONTAL_PIXELS, value & 0x00FF);
|
|
if (this.win0Left > this.win0Right) {
|
|
this.win0Right = this.HORIZONTAL_PIXELS;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeWin1H = function(value) {
|
|
this.win1Left = (value & 0xFF00) >> 8;
|
|
this.win1Right = Math.min(this.HORIZONTAL_PIXELS, value & 0x00FF);
|
|
if (this.win1Left > this.win1Right) {
|
|
this.win1Right = this.HORIZONTAL_PIXELS;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeWin0V = function(value) {
|
|
this.win0Top = (value & 0xFF00) >> 8;
|
|
this.win0Bottom = Math.min(this.VERTICAL_PIXELS, value & 0x00FF);
|
|
if (this.win0Top > this.win0Bottom) {
|
|
this.win0Bottom = this.VERTICAL_PIXELS;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeWin1V = function(value) {
|
|
this.win1Top = (value & 0xFF00) >> 8;
|
|
this.win1Bottom = Math.min(this.VERTICAL_PIXELS, value & 0x00FF);
|
|
if (this.win1Top > this.win1Bottom) {
|
|
this.win1Bottom = this.VERTICAL_PIXELS;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeWindow = function(index, value) {
|
|
var window = this.windows[index];
|
|
window.enabled[0] = value & 0x01;
|
|
window.enabled[1] = value & 0x02;
|
|
window.enabled[2] = value & 0x04;
|
|
window.enabled[3] = value & 0x08;
|
|
window.enabled[4] = value & 0x10;
|
|
window.special = value & 0x20;
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeWinIn = function(value) {
|
|
this.writeWindow(0, value);
|
|
this.writeWindow(1, value >> 8);
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeWinOut = function(value) {
|
|
this.writeWindow(2, value);
|
|
this.writeWindow(3, value >> 8);
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeBlendControl = function(value) {
|
|
this.target1[0] = !!(value & 0x0001) * this.TARGET1_MASK;
|
|
this.target1[1] = !!(value & 0x0002) * this.TARGET1_MASK;
|
|
this.target1[2] = !!(value & 0x0004) * this.TARGET1_MASK;
|
|
this.target1[3] = !!(value & 0x0008) * this.TARGET1_MASK;
|
|
this.target1[4] = !!(value & 0x0010) * this.TARGET1_MASK;
|
|
this.target1[5] = !!(value & 0x0020) * this.TARGET1_MASK;
|
|
this.target2[0] = !!(value & 0x0100) * this.TARGET2_MASK;
|
|
this.target2[1] = !!(value & 0x0200) * this.TARGET2_MASK;
|
|
this.target2[2] = !!(value & 0x0400) * this.TARGET2_MASK;
|
|
this.target2[3] = !!(value & 0x0800) * this.TARGET2_MASK;
|
|
this.target2[4] = !!(value & 0x1000) * this.TARGET2_MASK;
|
|
this.target2[5] = !!(value & 0x2000) * this.TARGET2_MASK;
|
|
this.blendMode = (value & 0x00C0) >> 6;
|
|
|
|
switch (this.blendMode) {
|
|
case 1:
|
|
// Alpha
|
|
// Fall through
|
|
case 0:
|
|
// Normal
|
|
this.palette.makeNormalPalettes();
|
|
break;
|
|
case 2:
|
|
// Brighter
|
|
this.palette.makeBrightPalettes(value & 0x3F);
|
|
break;
|
|
case 3:
|
|
// Darker
|
|
this.palette.makeDarkPalettes(value & 0x3F);
|
|
break;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.setBlendEnabled = function(layer, enabled, override) {
|
|
this.alphaEnabled = enabled && override == 1;
|
|
if (enabled) {
|
|
switch (override) {
|
|
case 1:
|
|
// Alpha
|
|
// Fall through
|
|
case 0:
|
|
// Normal
|
|
this.palette.makeNormalPalette(layer);
|
|
break;
|
|
case 2:
|
|
// Brighter
|
|
case 3:
|
|
// Darker
|
|
this.palette.makeSpecialPalette(layer);
|
|
break;
|
|
}
|
|
} else {
|
|
this.palette.makeNormalPalette(layer);
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeBlendAlpha = function(value) {
|
|
this.blendA = (value & 0x001F) / 16;
|
|
if (this.blendA > 1) {
|
|
this.blendA = 1;
|
|
}
|
|
this.blendB = ((value & 0x1F00) >> 8) / 16;
|
|
if (this.blendB > 1) {
|
|
this.blendB = 1;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeBlendY = function(value) {
|
|
this.blendY = value;
|
|
this.palette.setBlendY(value >= 16 ? 1 : (value / 16));
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.writeMosaic = function(value) {
|
|
this.bgMosaicX = (value & 0xF) + 1;
|
|
this.bgMosaicY = ((value >> 4) & 0xF) + 1;
|
|
this.objMosaicX = ((value >> 8) & 0xF) + 1;
|
|
this.objMosaicY = ((value >> 12) & 0xF) + 1;
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.resetLayers = function() {
|
|
if (this.backgroundMode > 1) {
|
|
this.bg[0].enabled = false;
|
|
this.bg[1].enabled = false;
|
|
}
|
|
if (this.bg[2].enabled) {
|
|
this.bg[2].drawScanline = this.bgModes[this.backgroundMode];
|
|
}
|
|
if ((this.backgroundMode == 0 || this.backgroundMode == 2)) {
|
|
if (this.bg[3].enabled) {
|
|
this.bg[3].drawScanline = this.bgModes[this.backgroundMode];
|
|
}
|
|
} else {
|
|
this.bg[3].enabled = false;
|
|
}
|
|
this.drawLayers.sort(this.layerComparator);
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.layerComparator = function(a, b) {
|
|
var diff = b.priority - a.priority;
|
|
if (!diff) {
|
|
if (a.bg && !b.bg) {
|
|
return -1;
|
|
} else if (!a.bg && b.bg) {
|
|
return 1;
|
|
}
|
|
|
|
return b.index - a.index;
|
|
}
|
|
return diff;
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.accessMapMode0 = function(base, size, x, yBase, out) {
|
|
var offset = base + ((x >> 2) & 0x3E) + yBase;
|
|
|
|
if (size & 1) {
|
|
offset += (x & 0x100) << 3;
|
|
}
|
|
|
|
var mem = this.vram.loadU16(offset);
|
|
out.tile = mem & 0x03FF;
|
|
out.hflip = mem & 0x0400;
|
|
out.vflip = mem & 0x0800;
|
|
out.palette = (mem & 0xF000) >> 8 // This is shifted up 4 to make pushPixel faster
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.accessMapMode1 = function(base, size, x, yBase, out) {
|
|
var offset = base + (x >> 3) + yBase;
|
|
|
|
out.tile = this.vram.loadU8(offset);
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.accessTile = function(base, tile, y) {
|
|
var offset = base + (tile << 5);
|
|
offset |= y << 2;
|
|
|
|
return this.vram.load32(offset);
|
|
}
|
|
|
|
GameBoyAdvanceSoftwareRenderer.pushPixel = function(layer, map, video, row, x, offset, backing, mask, raw) {
|
|
var index;
|
|
if (!raw) {
|
|
if (this.multipalette) {
|
|
index = (row >> (x << 3)) & 0xFF;
|
|
} else {
|
|
index = (row >> (x << 2)) & 0xF;
|
|
}
|
|
// Index 0 is transparent
|
|
if (!index) {
|
|
return;
|
|
} else if (!this.multipalette) {
|
|
index |= map.palette;
|
|
}
|
|
}
|
|
|
|
var stencil = video.WRITTEN_MASK;
|
|
var oldStencil = backing.stencil[offset];
|
|
var blend = video.blendMode;
|
|
if (video.objwinActive) {
|
|
if (oldStencil & video.OBJWIN_MASK) {
|
|
if (video.windows[3].enabled[layer]) {
|
|
video.setBlendEnabled(layer, video.windows[3].special && video.target1[layer], blend);
|
|
if (video.windows[3].special && video.alphaEnabled) {
|
|
mask |= video.target1[layer];
|
|
}
|
|
stencil |= video.OBJWIN_MASK;
|
|
} else {
|
|
return;
|
|
}
|
|
} else if (video.windows[2].enabled[layer]) {
|
|
video.setBlendEnabled(layer, video.windows[2].special && video.target1[layer], blend);
|
|
if (video.windows[2].special && video.alphaEnabled) {
|
|
mask |= video.target1[layer];
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((mask & video.TARGET1_MASK) && (oldStencil & video.TARGET2_MASK)) {
|
|
video.setBlendEnabled(layer, true, 1);
|
|
}
|
|
|
|
var pixel = raw ? row : video.palette.accessColor(layer, index);
|
|
|
|
if (mask & video.TARGET1_MASK) {
|
|
video.setBlendEnabled(layer, !!blend, blend);
|
|
}
|
|
var highPriority = (mask & video.PRIORITY_MASK) < (oldStencil & video.PRIORITY_MASK);
|
|
// Backgrounds can draw over each other, too.
|
|
if ((mask & video.PRIORITY_MASK) == (oldStencil & video.PRIORITY_MASK)) {
|
|
highPriority = mask & video.BACKGROUND_MASK;
|
|
}
|
|
|
|
if (!(oldStencil & video.WRITTEN_MASK)) {
|
|
// Nothing here yet, just continue
|
|
stencil |= mask;
|
|
} else if (highPriority) {
|
|
// We are higher priority
|
|
if (mask & video.TARGET1_MASK && oldStencil & video.TARGET2_MASK) {
|
|
pixel = video.palette.mix(video.blendA, pixel, video.blendB, backing.color[offset]);
|
|
}
|
|
// We just drew over something, so it doesn't make sense for us to be a TARGET1 anymore...
|
|
stencil |= mask & ~video.TARGET1_MASK;
|
|
} else if ((mask & video.PRIORITY_MASK) > (oldStencil & video.PRIORITY_MASK)) {
|
|
// We're below another layer, but might be the blend target for it
|
|
stencil = oldStencil & ~(video.TARGET1_MASK | video.TARGET2_MASK);
|
|
if (mask & video.TARGET2_MASK && oldStencil & video.TARGET1_MASK) {
|
|
pixel = video.palette.mix(video.blendB, pixel, video.blendA, backing.color[offset]);
|
|
} else {
|
|
return;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if (mask & video.OBJWIN_MASK) {
|
|
// We ARE the object window, don't draw pixels!
|
|
backing.stencil[offset] |= video.OBJWIN_MASK;
|
|
return;
|
|
}
|
|
backing.color[offset] = pixel;
|
|
backing.stencil[offset] = stencil;
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.identity = function(x) {
|
|
return x;
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.drawScanlineBlank = function(backing) {
|
|
for (var x = 0; x < this.HORIZONTAL_PIXELS; ++x) {
|
|
backing.color[x] = 0xFFFF;
|
|
backing.stencil[x] = 0;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.prepareScanline = function(backing) {
|
|
for (var x = 0; x < this.HORIZONTAL_PIXELS; ++x) {
|
|
backing.stencil[x] = this.target2[this.LAYER_BACKDROP];
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.drawScanlineBGMode0 = function(backing, bg, start, end) {
|
|
var video = this.video;
|
|
var x;
|
|
var y = video.vcount;
|
|
var offset = start;
|
|
var xOff = bg.x;
|
|
var yOff = bg.y;
|
|
var localX;
|
|
var localXLo;
|
|
var localY = y + yOff;
|
|
if (this.mosaic) {
|
|
localY -= y % video.bgMosaicY;
|
|
}
|
|
var localYLo = localY & 0x7;
|
|
var mosaicX;
|
|
var screenBase = bg.screenBase;
|
|
var charBase = bg.charBase;
|
|
var size = bg.size;
|
|
var index = bg.index;
|
|
var map = video.sharedMap;
|
|
var paletteShift = bg.multipalette ? 1 : 0;
|
|
var mask = video.target2[index] | (bg.priority << 1) | video.BACKGROUND_MASK;
|
|
if (video.blendMode == 1 && video.alphaEnabled) {
|
|
mask |= video.target1[index];
|
|
}
|
|
|
|
var yBase = (localY << 3) & 0x7C0;
|
|
if (size == 2) {
|
|
yBase += (localY << 3) & 0x800;
|
|
} else if (size == 3) {
|
|
yBase += (localY << 4) & 0x1000;
|
|
}
|
|
|
|
var xMask;
|
|
if (size & 1) {
|
|
xMask = 0x1FF;
|
|
} else {
|
|
xMask = 0xFF;
|
|
}
|
|
|
|
video.accessMapMode0(screenBase, size, (start + xOff) & xMask, yBase, map);
|
|
var tileRow = video.accessTile(charBase, map.tile << paletteShift, (!map.vflip ? localYLo : 7 - localYLo) << paletteShift);
|
|
for (x = start; x < end; ++x) {
|
|
localX = (x + xOff) & xMask;
|
|
mosaicX = this.mosaic ? offset % video.bgMosaicX : 0;
|
|
localX -= mosaicX;
|
|
localXLo = localX & 0x7;
|
|
if (!paletteShift) {
|
|
if (!localXLo || (this.mosaic && !mosaicX)) {
|
|
video.accessMapMode0(screenBase, size, localX, yBase, map);
|
|
tileRow = video.accessTile(charBase, map.tile, !map.vflip ? localYLo : 7 - localYLo);
|
|
if (!tileRow && !localXLo) {
|
|
x += 7;
|
|
offset += 8;
|
|
continue;
|
|
}
|
|
}
|
|
} else {
|
|
if (!localXLo || (this.mosaic && !mosaicX)) {
|
|
video.accessMapMode0(screenBase, size, localX, yBase, map);
|
|
}
|
|
if (!(localXLo & 0x3) || (this.mosaic && !mosaicX)) {
|
|
tileRow = video.accessTile(charBase + (!!(localX & 0x4) == !map.hflip ? 4 : 0), map.tile << 1, (!map.vflip ? localYLo : 7 - localYLo) << 1);
|
|
if (!tileRow && !(localXLo & 0x3)) {
|
|
x += 3;
|
|
offset += 4;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (map.hflip) {
|
|
localXLo = 7 - localXLo;
|
|
}
|
|
bg.pushPixel(index, map, video, tileRow, localXLo, offset, backing, mask, false);
|
|
offset++;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.drawScanlineBGMode2 = function(backing, bg, start, end) {
|
|
var video = this.video;
|
|
var x;
|
|
var y = video.vcount;
|
|
var offset = start;
|
|
var localX;
|
|
var localY;
|
|
var screenBase = bg.screenBase;
|
|
var charBase = bg.charBase;
|
|
var size = bg.size;
|
|
var sizeAdjusted = 128 << size;
|
|
var index = bg.index;
|
|
var map = video.sharedMap;
|
|
var color;
|
|
var mask = video.target2[index] | (bg.priority << 1) | video.BACKGROUND_MASK;
|
|
if (video.blendMode == 1 && video.alphaEnabled) {
|
|
mask |= video.target1[index];
|
|
}
|
|
|
|
var yBase;
|
|
|
|
for (x = start; x < end; ++x) {
|
|
localX = bg.dx * x + bg.sx;
|
|
localY = bg.dy * x + bg.sy;
|
|
if (this.mosaic) {
|
|
localX -= (x % video.bgMosaicX) * bg.dx + (y % video.bgMosaicY) * bg.dmx;
|
|
localY -= (x % video.bgMosaicX) * bg.dy + (y % video.bgMosaicY) * bg.dmy;
|
|
}
|
|
if (bg.overflow) {
|
|
localX &= sizeAdjusted - 1;
|
|
if (localX < 0) {
|
|
localX += sizeAdjusted;
|
|
}
|
|
localY &= sizeAdjusted - 1;
|
|
if (localY < 0) {
|
|
localY += sizeAdjusted;
|
|
}
|
|
} else if (localX < 0 || localY < 0 || localX >= sizeAdjusted || localY >= sizeAdjusted) {
|
|
offset++;
|
|
continue;
|
|
}
|
|
yBase = ((localY << 1) & 0x7F0) << size;
|
|
video.accessMapMode1(screenBase, size, localX, yBase, map);
|
|
color = this.vram.loadU8(charBase + (map.tile << 6) + ((localY & 0x7) << 3) + (localX & 0x7));
|
|
bg.pushPixel(index, map, video, color, 0, offset, backing, mask, false);
|
|
offset++;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.drawScanlineBGMode3 = function(backing, bg, start, end) {
|
|
var video = this.video;
|
|
var x;
|
|
var y = video.vcount;
|
|
var offset = start;
|
|
var localX;
|
|
var localY;
|
|
var index = bg.index;
|
|
var map = video.sharedMap;
|
|
var color;
|
|
var mask = video.target2[index] | (bg.priority << 1) | video.BACKGROUND_MASK;
|
|
if (video.blendMode == 1 && video.alphaEnabled) {
|
|
mask |= video.target1[index];
|
|
}
|
|
|
|
var yBase;
|
|
|
|
for (x = start; x < end; ++x) {
|
|
localX = bg.dx * x + bg.sx;
|
|
localY = bg.dy * x + bg.sy;
|
|
if (this.mosaic) {
|
|
localX -= (x % video.bgMosaicX) * bg.dx + (y % video.bgMosaicY) * bg.dmx;
|
|
localY -= (x % video.bgMosaicX) * bg.dy + (y % video.bgMosaicY) * bg.dmy;
|
|
}
|
|
if (localX < 0 || localY < 0 || localX >= video.HORIZONTAL_PIXELS || localY >= video.VERTICAL_PIXELS) {
|
|
offset++;
|
|
continue;
|
|
}
|
|
color = this.vram.loadU16(((localY * video.HORIZONTAL_PIXELS) + localX) << 1);
|
|
bg.pushPixel(index, map, video, color, 0, offset, backing, mask, true);
|
|
offset++;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.drawScanlineBGMode4 = function(backing, bg, start, end) {
|
|
var video = this.video;
|
|
var x;
|
|
var y = video.vcount;
|
|
var offset = start;
|
|
var localX;
|
|
var localY;
|
|
var charBase = 0;
|
|
if (video.displayFrameSelect) {
|
|
charBase += 0xA000;
|
|
}
|
|
var size = bg.size;
|
|
var index = bg.index;
|
|
var map = video.sharedMap;
|
|
var color;
|
|
var mask = video.target2[index] | (bg.priority << 1) | video.BACKGROUND_MASK;
|
|
if (video.blendMode == 1 && video.alphaEnabled) {
|
|
mask |= video.target1[index];
|
|
}
|
|
|
|
var yBase;
|
|
|
|
for (x = start; x < end; ++x) {
|
|
localX = bg.dx * x + bg.sx;
|
|
localY = 0 | bg.dy * x + bg.sy;
|
|
if (this.mosaic) {
|
|
localX -= (x % video.bgMosaicX) * bg.dx + (y % video.bgMosaicY) * bg.dmx;
|
|
localY -= (x % video.bgMosaicX) * bg.dy + (y % video.bgMosaicY) * bg.dmy;
|
|
}
|
|
yBase = (localY << 2) & 0x7E0;
|
|
if (localX < 0 || localY < 0 || localX >= video.HORIZONTAL_PIXELS || localY >= video.VERTICAL_PIXELS) {
|
|
offset++;
|
|
continue;
|
|
}
|
|
color = this.vram.loadU8(charBase + (localY * video.HORIZONTAL_PIXELS) + localX);
|
|
bg.pushPixel(index, map, video, color, 0, offset, backing, mask, false);
|
|
offset++;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.drawScanlineBGMode5 = function(backing, bg, start, end) {
|
|
var video = this.video;
|
|
var x;
|
|
var y = video.vcount;
|
|
var offset = start;
|
|
var localX;
|
|
var localY;
|
|
var charBase = 0;
|
|
if (video.displayFrameSelect) {
|
|
charBase += 0xA000;
|
|
}
|
|
var index = bg.index;
|
|
var map = video.sharedMap;
|
|
var color;
|
|
var mask = video.target2[index] | (bg.priority << 1) | video.BACKGROUND_MASK;
|
|
if (video.blendMode == 1 && video.alphaEnabled) {
|
|
mask |= video.target1[index];
|
|
}
|
|
|
|
var yBase;
|
|
|
|
for (x = start; x < end; ++x) {
|
|
localX = bg.dx * x + bg.sx;
|
|
localY = bg.dy * x + bg.sy;
|
|
if (this.mosaic) {
|
|
localX -= (x % video.bgMosaicX) * bg.dx + (y % video.bgMosaicY) * bg.dmx;
|
|
localY -= (x % video.bgMosaicX) * bg.dy + (y % video.bgMosaicY) * bg.dmy;
|
|
}
|
|
if (localX < 0 || localY < 0 || localX >= 160 || localY >= 128) {
|
|
offset++;
|
|
continue;
|
|
}
|
|
color = this.vram.loadU16(charBase + ((localY * 160) + localX) << 1);
|
|
bg.pushPixel(index, map, video, color, 0, offset, backing, mask, true);
|
|
offset++;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.drawScanline = function(y) {
|
|
var backing = this.scanline;
|
|
if (this.forcedBlank) {
|
|
this.drawScanlineBlank(backing);
|
|
return;
|
|
}
|
|
this.prepareScanline(backing);
|
|
var layer;
|
|
var firstStart;
|
|
var firstEnd;
|
|
var lastStart;
|
|
var lastEnd;
|
|
this.vcount = y;
|
|
// Draw lower priority first and then draw over them
|
|
for (var i = 0; i < this.drawLayers.length; ++i) {
|
|
layer = this.drawLayers[i];
|
|
if (!layer.enabled) {
|
|
continue;
|
|
}
|
|
this.objwinActive = false;
|
|
if (!(this.win0 || this.win1 || this.objwin)) {
|
|
this.setBlendEnabled(layer.index, this.target1[layer.index], this.blendMode);
|
|
layer.drawScanline(backing, layer, 0, this.HORIZONTAL_PIXELS);
|
|
} else {
|
|
firstStart = 0;
|
|
firstEnd = this.HORIZONTAL_PIXELS;
|
|
lastStart = 0;
|
|
lastEnd = this.HORIZONTAL_PIXELS;
|
|
if (this.win0 && y >= this.win0Top && y < this.win0Bottom) {
|
|
if (this.windows[0].enabled[layer.index]) {
|
|
this.setBlendEnabled(layer.index, this.windows[0].special && this.target1[layer.index], this.blendMode);
|
|
layer.drawScanline(backing, layer, this.win0Left, this.win0Right);
|
|
}
|
|
firstStart = Math.max(firstStart, this.win0Left);
|
|
firstEnd = Math.min(firstEnd, this.win0Left);
|
|
lastStart = Math.max(lastStart, this.win0Right);
|
|
lastEnd = Math.min(lastEnd, this.win0Right);
|
|
}
|
|
if (this.win1 && y >= this.win1Top && y < this.win1Bottom) {
|
|
if (this.windows[1].enabled[layer.index]) {
|
|
this.setBlendEnabled(layer.index, this.windows[1].special && this.target1[layer.index], this.blendMode);
|
|
if (!this.windows[0].enabled[layer.index] && (this.win1Left < firstStart || this.win1Right < lastStart)) {
|
|
// We've been cut in two by window 0!
|
|
layer.drawScanline(backing, layer, this.win1Left, firstStart);
|
|
layer.drawScanline(backing, layer, lastEnd, this.win1Right);
|
|
} else {
|
|
layer.drawScanline(backing, layer, this.win1Left, this.win1Right);
|
|
}
|
|
}
|
|
firstStart = Math.max(firstStart, this.win1Left);
|
|
firstEnd = Math.min(firstEnd, this.win1Left);
|
|
lastStart = Math.max(lastStart, this.win1Right);
|
|
lastEnd = Math.min(lastEnd, this.win1Right);
|
|
}
|
|
// Do last two
|
|
if (this.windows[2].enabled[layer.index] || (this.objwin && this.windows[3].enabled[layer.index])) {
|
|
// WINOUT/OBJWIN
|
|
this.objwinActive = this.objwin;
|
|
this.setBlendEnabled(layer.index, this.windows[2].special && this.target1[layer.index], this.blendMode); // Window 3 handled in pushPixel
|
|
if (firstEnd > lastStart) {
|
|
layer.drawScanline(backing, layer, 0, this.HORIZONTAL_PIXELS);
|
|
} else {
|
|
if (firstEnd) {
|
|
layer.drawScanline(backing, layer, 0, firstEnd);
|
|
}
|
|
if (lastStart < this.HORIZONTAL_PIXELS) {
|
|
layer.drawScanline(backing, layer, lastStart, this.HORIZONTAL_PIXELS);
|
|
}
|
|
if (lastEnd < firstStart) {
|
|
layer.drawScanline(backing, layer, lastEnd, firstStart);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.setBlendEnabled(this.LAYER_BACKDROP, this.target1[this.LAYER_BACKDROP] && this.windows[2].special, this.blendMode);
|
|
}
|
|
if (layer.bg) {
|
|
layer.sx += layer.dmx;
|
|
layer.sy += layer.dmy;
|
|
}
|
|
}
|
|
|
|
this.finishScanline(backing);
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.finishScanline = function(backing) {
|
|
var color;
|
|
var bd = this.palette.accessColor(this.LAYER_BACKDROP, 0);
|
|
var xx = this.vcount * this.HORIZONTAL_PIXELS * 4;
|
|
var isTarget2 = this.target2[this.LAYER_BACKDROP];
|
|
for (var x = 0; x < this.HORIZONTAL_PIXELS; ++x) {
|
|
if (backing.stencil[x] & this.WRITTEN_MASK) {
|
|
color = backing.color[x];
|
|
if (isTarget2 && backing.stencil[x] & this.TARGET1_MASK) {
|
|
color = this.palette.mix(this.blendA, color, this.blendB, bd);
|
|
}
|
|
this.palette.convert16To32(color, this.sharedColor);
|
|
} else {
|
|
this.palette.convert16To32(bd, this.sharedColor);
|
|
}
|
|
this.pixelData.data[xx++] = this.sharedColor[0];
|
|
this.pixelData.data[xx++] = this.sharedColor[1];
|
|
this.pixelData.data[xx++] = this.sharedColor[2];
|
|
xx++;
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.startDraw = function() {
|
|
// Nothing to do
|
|
};
|
|
|
|
GameBoyAdvanceSoftwareRenderer.prototype.finishDraw = function(caller) {
|
|
this.bg[2].sx = this.bg[2].refx;
|
|
this.bg[2].sy = this.bg[2].refy;
|
|
this.bg[3].sx = this.bg[3].refx;
|
|
this.bg[3].sy = this.bg[3].refy;
|
|
caller.finishDraw(this.pixelData);
|
|
};
|