emulator/gba3/js/video/software.js
2019-06-14 13:47:15 +08:00

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);
};