//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 mode 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; bool mosSing = self.mosaicEnable && io.mosaic.size != 1 && ppu.hdMosaic() == 1; const uint sampScale = mosSing ? 1 : ppu.hdSupersample(); const uint scale = mosSing ? 1 : ppu.hdScale() * sampScale; int sampSize = sampScale < 2 ? 0 : (256+2*ppu.widescreen()) * 4 * scale/sampScale; uint *sampTmp = new uint[sampSize]; memory::fill(sampTmp, sampSize); Pixel pixel; Pixel* above = &this->above[0]; Pixel* below = &this->below[0]; //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[1024]; bool windowBelow[1024]; renderWindow(self.window, self.window.aboveEnable, windowAbove, ppufast.widescreen(), ppufast.strwin()); renderWindow(self.window, self.window.belowEnable, windowBelow, ppufast.widescreen(), ppufast.strwin()); auto luma = ppu.lightTable[io.displayBrightness]; 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+2*ppufast.widescreen())) { x -= ppufast.widescreen(); bool doAbove = self.aboveEnable && !windowAbove[ppufast.winXad(x, false)]; bool doBelow = self.belowEnable && !windowBelow[ppufast.winXad(x, true)]; 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; bool skip = false; //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[0]; } else { priority = self.priority[palette >> 7]; palette &= 0x7f; } skip = !palette; if(!skip) { uint32 color; if(io.col.directColor && !extbg) { color = directColor(0, palette); } else { color = cgram[palette]; } color = luma[color]; pixel = {source, priority, color}; pixelXp = pixelX; pixelYp = pixelY; } } if(mosSing) { if(self.aboveEnable && !windowAbove[ppufast.winXad(x, false)]) plotAbove(x, pixel.source, pixel.priority, pixel.color); if(self.belowEnable && !windowBelow[ppufast.winXad(x, true)]) plotBelow(x, pixel.source, pixel.priority, pixel.color); } else if(sampScale == 1) { if(!skip && doAbove && (!extbg || pixel.priority > above->priority)) *above = pixel; if(!skip && doBelow && (!extbg || pixel.priority > below->priority)) *below = pixel; above++; below++; } else { int p = ((((x+ppufast.widescreen())*(scale/sampScale)) + (xs/sampScale))) * 4; sampTmp[p] += pixel.priority; sampTmp[p+1] += (pixel.color >> 16) & 255; sampTmp[p+2] += (pixel.color >> 8) & 255; sampTmp[p+3] += (pixel.color >> 0) & 255; if((ys+1) % sampScale == 0 && (xs+1) % sampScale == 0) { uint8 priority = sampTmp[p] / sampScale / sampScale; uint32 color = ((sampTmp[p+1] / sampScale / sampScale) << 16) + ((sampTmp[p+2] / sampScale / sampScale) << 8) + ((sampTmp[p+3] / sampScale / sampScale) << 0); if(!skip && doAbove && (!extbg || priority > above->priority)) *above = {source, priority, color}; if(!skip && doBelow && (!extbg || priority > below->priority)) *below = {source, priority, color}; above++; below++; sampTmp[p] = 0; sampTmp[p+1] = 0; sampTmp[p+2] = 0; sampTmp[p+3] = 0; } } } } } delete[] sampTmp; } //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); }