BizHawk/waterbox/bsnescore/bsnes/sfc/ppu-fast/mode7hd.cpp

254 lines
9.9 KiB
C++

//determine mode 7 line groups for perspective correction
auto PPU::Line::cacheMode7HD() -> void {
ppu.mode7LineGroups.count = 0;
if(ppu.hdPerspective()) {
#define isLineMode7(line) (line.io.bg1.tileMode == TileMode::Mode7 && !line.io.displayDisable && ( \
(line.io.bg1.aboveEnable || line.io.bg1.belowEnable) \
))
bool state = false;
uint y;
//find the moe 7 groups
for(y = 0; y < Line::count; y++) {
if(state != isLineMode7(ppu.lines[Line::start + y])) {
state = !state;
if(state) {
ppu.mode7LineGroups.startLine[ppu.mode7LineGroups.count] = ppu.lines[Line::start + y].y;
} else {
ppu.mode7LineGroups.endLine[ppu.mode7LineGroups.count] = ppu.lines[Line::start + y].y - 1;
//the lines at the edges of mode 7 groups may be erroneous, so start and end lines for interpolation are moved inside
int offset = (ppu.mode7LineGroups.endLine[ppu.mode7LineGroups.count] - ppu.mode7LineGroups.startLine[ppu.mode7LineGroups.count]) / 8;
ppu.mode7LineGroups.startLerpLine[ppu.mode7LineGroups.count] = ppu.mode7LineGroups.startLine[ppu.mode7LineGroups.count] + offset;
ppu.mode7LineGroups.endLerpLine[ppu.mode7LineGroups.count] = ppu.mode7LineGroups.endLine[ppu.mode7LineGroups.count] - offset;
ppu.mode7LineGroups.count++;
}
}
}
#undef isLineMode7
if(state) {
//close the last group if necessary
ppu.mode7LineGroups.endLine[ppu.mode7LineGroups.count] = ppu.lines[Line::start + y].y - 1;
int offset = (ppu.mode7LineGroups.endLine[ppu.mode7LineGroups.count] - ppu.mode7LineGroups.startLine[ppu.mode7LineGroups.count]) / 8;
ppu.mode7LineGroups.startLerpLine[ppu.mode7LineGroups.count] = ppu.mode7LineGroups.startLine[ppu.mode7LineGroups.count] + offset;
ppu.mode7LineGroups.endLerpLine[ppu.mode7LineGroups.count] = ppu.mode7LineGroups.endLine[ppu.mode7LineGroups.count] - offset;
ppu.mode7LineGroups.count++;
}
//detect groups that do not have perspective
for(int i : range(ppu.mode7LineGroups.count)) {
int a = -1, b = -1, c = -1, d = -1; //the mode 7 scale factors of the current line
int aPrev = -1, bPrev = -1, cPrev = -1, dPrev = -1; //the mode 7 scale factors of the previous line
bool aVar = false, bVar = false, cVar = false, dVar = false; //has a varying value been found for the factors?
bool aInc = false, bInc = false, cInc = false, dInc = false; //has the variation been an increase or decrease?
for(y = ppu.mode7LineGroups.startLerpLine[i]; y <= ppu.mode7LineGroups.endLerpLine[i]; y++) {
a = ((int)((int16)(ppu.lines[y].io.mode7.a)));
b = ((int)((int16)(ppu.lines[y].io.mode7.b)));
c = ((int)((int16)(ppu.lines[y].io.mode7.c)));
d = ((int)((int16)(ppu.lines[y].io.mode7.d)));
//has the value of 'a' changed compared to the last line?
//(and is the factor larger than zero, which happens sometimes and seems to be game-specific, mostly at the edges of the screen)
if(aPrev > 0 && a > 0 && a != aPrev) {
if(!aVar) {
//if there has been no variation yet, store that there is one and store if it is an increase or decrease
aVar = true;
aInc = a > aPrev;
} else if(aInc != a > aPrev) {
//if there has been an increase and now we have a decrease, or vice versa, set the interpolation lines to -1
//to deactivate perspective correction for this group and stop analyzing it further
ppu.mode7LineGroups.startLerpLine[i] = -1;
ppu.mode7LineGroups.endLerpLine[i] = -1;
break;
}
}
if(bPrev > 0 && b > 0 && b != bPrev) {
if(!bVar) {
bVar = true;
bInc = b > bPrev;
} else if(bInc != b > bPrev) {
ppu.mode7LineGroups.startLerpLine[i] = -1;
ppu.mode7LineGroups.endLerpLine[i] = -1;
break;
}
}
if(cPrev > 0 && c > 0 && c != cPrev) {
if(!cVar) {
cVar = true;
cInc = c > cPrev;
} else if(cInc != c > cPrev) {
ppu.mode7LineGroups.startLerpLine[i] = -1;
ppu.mode7LineGroups.endLerpLine[i] = -1;
break;
}
}
if(dPrev > 0 && d > 0 && d != bPrev) {
if(!dVar) {
dVar = true;
dInc = d > dPrev;
} else if(dInc != d > dPrev) {
ppu.mode7LineGroups.startLerpLine[i] = -1;
ppu.mode7LineGroups.endLerpLine[i] = -1;
break;
}
}
aPrev = a, bPrev = b, cPrev = c, dPrev = d;
}
}
}
}
auto PPU::Line::renderMode7HD(PPU::IO::Background& self, uint8 source) -> void {
const bool extbg = source == Source::BG2;
const uint scale = ppu.hdScale();
Pixel pixel;
Pixel* above = &this->above[-1];
Pixel* below = &this->below[-1];
//find the first and last scanline for interpolation
int y_a = -1;
int y_b = -1;
#define isLineMode7(n) (ppu.lines[n].io.bg1.tileMode == TileMode::Mode7 && !ppu.lines[n].io.displayDisable && ( \
(ppu.lines[n].io.bg1.aboveEnable || ppu.lines[n].io.bg1.belowEnable) \
))
if(ppu.hdPerspective()) {
//find the mode 7 line group this line is in and use its interpolation lines
for(int i : range(ppu.mode7LineGroups.count)) {
if(y >= ppu.mode7LineGroups.startLine[i] && y <= ppu.mode7LineGroups.endLine[i]) {
y_a = ppu.mode7LineGroups.startLerpLine[i];
y_b = ppu.mode7LineGroups.endLerpLine[i];
break;
}
}
}
if(y_a == -1 || y_b == -1) {
//if perspective correction is disabled or the group was detected as non-perspective, use the neighboring lines
y_a = y;
y_b = y;
if(y_a > 1 && isLineMode7(y_a)) y_a--;
if(y_b < 239 && isLineMode7(y_b)) y_b++;
}
#undef isLineMode7
Line line_a = ppu.lines[y_a];
float a_a = (int16)line_a.io.mode7.a;
float b_a = (int16)line_a.io.mode7.b;
float c_a = (int16)line_a.io.mode7.c;
float d_a = (int16)line_a.io.mode7.d;
Line line_b = ppu.lines[y_b];
float a_b = (int16)line_b.io.mode7.a;
float b_b = (int16)line_b.io.mode7.b;
float c_b = (int16)line_b.io.mode7.c;
float d_b = (int16)line_b.io.mode7.d;
int hcenter = (int13)io.mode7.x;
int vcenter = (int13)io.mode7.y;
int hoffset = (int13)io.mode7.hoffset;
int voffset = (int13)io.mode7.voffset;
if(io.mode7.vflip) {
y_a = 255 - y_a;
y_b = 255 - y_b;
}
bool windowAbove[256];
bool windowBelow[256];
renderWindow(self.window, self.window.aboveEnable, windowAbove);
renderWindow(self.window, self.window.belowEnable, windowBelow);
int pixelYp = INT_MIN;
for(int ys : range(scale)) {
float yf = y + ys * 1.0 / scale - 0.5;
if(io.mode7.vflip) yf = 255 - yf;
float a = 1.0 / lerp(y_a, 1.0 / a_a, y_b, 1.0 / a_b, yf);
float b = 1.0 / lerp(y_a, 1.0 / b_a, y_b, 1.0 / b_b, yf);
float c = 1.0 / lerp(y_a, 1.0 / c_a, y_b, 1.0 / c_b, yf);
float d = 1.0 / lerp(y_a, 1.0 / d_a, y_b, 1.0 / d_b, yf);
int ht = (hoffset - hcenter) % 1024;
float vty = ((voffset - vcenter) % 1024) + yf;
float originX = (a * ht) + (b * vty) + (hcenter << 8);
float originY = (c * ht) + (d * vty) + (vcenter << 8);
int pixelXp = INT_MIN;
for(int x : range(256)) {
bool doAbove = self.aboveEnable && !windowAbove[x];
bool doBelow = self.belowEnable && !windowBelow[x];
for(int xs : range(scale)) {
float xf = x + xs * 1.0 / scale - 0.5;
if(io.mode7.hflip) xf = 255 - xf;
int pixelX = (originX + a * xf) / 256;
int pixelY = (originY + c * xf) / 256;
above++;
below++;
//only compute color again when coordinates have changed
if(pixelX != pixelXp || pixelY != pixelYp) {
uint tile = io.mode7.repeat == 3 && ((pixelX | pixelY) & ~1023) ? 0 : (ppu.vram[(pixelY >> 3 & 127) * 128 + (pixelX >> 3 & 127)] & 0xff);
uint palette = io.mode7.repeat == 2 && ((pixelX | pixelY) & ~1023) ? 0 : (ppu.vram[(((pixelY & 7) << 3) + (pixelX & 7)) + (tile << 6)] >> 8);
uint8 priority;
if(!extbg) {
priority = self.priority_enabled[0] ? self.priority[0] : 0;
} else {
priority = self.priority_enabled[palette >> 7] ? self.priority[palette >> 7] : 0;
palette &= 0x7f;
}
if(!palette) continue;
uint16 color;
if(io.col.directColor && !extbg) {
color = directColor(0, palette);
} else {
color = cgram[palette];
}
pixel = {source, priority, color};
pixelXp = pixelX;
pixelYp = pixelY;
}
if(doAbove && (!extbg || pixel.priority > above->priority)) *above = pixel;
if(doBelow && (!extbg || pixel.priority > below->priority)) *below = pixel;
}
}
}
if(ppu.ss()) {
uint divisor = scale * scale;
for(uint p : range(256)) {
uint ab = 0, bb = 0;
uint ag = 0, bg = 0;
uint ar = 0, br = 0;
for(uint y : range(scale)) {
auto above = &this->above[p * scale];
auto below = &this->below[p * scale];
for(uint x : range(scale)) {
uint a = above[x].color;
uint b = below[x].color;
ab += a >> 0 & 31;
ag += a >> 5 & 31;
ar += a >> 10 & 31;
bb += b >> 0 & 31;
bg += b >> 5 & 31;
br += b >> 10 & 31;
}
}
uint16 aboveColor = ab / divisor << 0 | ag / divisor << 5 | ar / divisor << 10;
uint16 belowColor = bb / divisor << 0 | bg / divisor << 5 | br / divisor << 10;
this->above[p] = {source, this->above[p * scale].priority, aboveColor};
this->below[p] = {source, this->below[p * scale].priority, belowColor};
}
}
}
//interpolation and extrapolation
auto PPU::Line::lerp(float pa, float va, float pb, float vb, float pr) -> float {
if(va == vb || pr == pa) return va;
if(pr == pb) return vb;
return va + (vb - va) / (pb - pa) * (pr - pa);
}