mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
GBA: Fixed/improved window left/right behavior
Fixes issue in speech bubbles in Megaman & Bass
This commit is contained in:
parent
7f4ba93e88
commit
f862b0ca37
2 changed files with 121 additions and 54 deletions
|
@ -83,6 +83,7 @@ void GbaPpu::ProcessHBlank()
|
|||
void GbaPpu::ProcessEndOfScanline()
|
||||
{
|
||||
ProcessSprites();
|
||||
ProcessWindow();
|
||||
|
||||
if(!_skipRender && _emu->IsDebugging()) {
|
||||
DebugProcessMemoryAccessView();
|
||||
|
@ -93,8 +94,9 @@ void GbaPpu::ProcessEndOfScanline()
|
|||
_state.Cycle = 0;
|
||||
_state.Scanline++;
|
||||
|
||||
//Reset renderer data for next scale
|
||||
//Reset renderer data for next scanline
|
||||
_lastRenderCycle = -1;
|
||||
_lastWindowCycle = -1;
|
||||
_oamLastCycle = -1;
|
||||
std::fill(_layerOutput[0], _layerOutput[0] + 240, GbaPixelData {});
|
||||
std::fill(_layerOutput[1], _layerOutput[1] + 240, GbaPixelData {});
|
||||
|
@ -165,23 +167,7 @@ void GbaPpu::ProcessEndOfScanline()
|
|||
_console->GetMemoryManager()->SetIrqSource(GbaIrqSource::LcdScanlineMatch);
|
||||
}
|
||||
|
||||
//Windows are enabled/disabled when the scanline reaches the start/end scanlines
|
||||
//See window_midframe test - unsure about behavior when top==bottom
|
||||
if(_state.Scanline == _state.Window[0].TopY) {
|
||||
_window0Active = _state.Window0Enabled;
|
||||
}
|
||||
if(_state.Scanline == _state.Window[0].BottomY) {
|
||||
_window0Active = false;
|
||||
}
|
||||
|
||||
if(_state.Scanline == _state.Window[1].TopY) {
|
||||
_window1Active = _state.Window1Enabled;
|
||||
}
|
||||
if(_state.Scanline == _state.Window[1].BottomY) {
|
||||
_window1Active = false;
|
||||
}
|
||||
|
||||
InitializeActiveWindows();
|
||||
InitializeWindows();
|
||||
}
|
||||
|
||||
void GbaPpu::SendFrame()
|
||||
|
@ -226,6 +212,7 @@ void GbaPpu::RenderScanline(bool forceRender)
|
|||
}
|
||||
|
||||
ProcessSprites();
|
||||
ProcessWindow();
|
||||
|
||||
if(_state.Scanline >= 160 || _lastRenderCycle >= 1006) {
|
||||
return;
|
||||
|
@ -294,15 +281,16 @@ void GbaPpu::ProcessColorMath()
|
|||
GbaPixelData sprPixel = {};
|
||||
|
||||
for(int x = start; x <= end; x++) {
|
||||
wnd = _activeWindow[x];
|
||||
|
||||
if constexpr(windowEnabled) {
|
||||
if(_state.WindowActiveLayers[_activeWindow[x]][GbaPpu::SpriteLayerIndex]) {
|
||||
wnd = _activeWindow[x];
|
||||
if(_state.WindowActiveLayers[wnd][GbaPpu::SpriteLayerIndex]) {
|
||||
main = _oamReadOutput[x];
|
||||
} else {
|
||||
main = {};
|
||||
}
|
||||
} else {
|
||||
wnd = GbaPpu::NoWindow;
|
||||
main = _oamReadOutput[x];
|
||||
}
|
||||
|
||||
|
@ -391,35 +379,100 @@ void GbaPpu::BlendColors(uint16_t* dst, int x, uint16_t main, uint8_t aCoeff, ui
|
|||
dst[x] = r | (g << 5) | (b << 10);
|
||||
}
|
||||
|
||||
void GbaPpu::ApplyWindowBounds(uint8_t window)
|
||||
void GbaPpu::InitializeWindows()
|
||||
{
|
||||
uint8_t x1 = _state.Window[window].LeftX;
|
||||
uint8_t x2 = _state.Window[window].RightX;
|
||||
if(x2 < x1 || x2 > 240) {
|
||||
x2 = 240;
|
||||
//Windows are enabled/disabled when the scanline reaches the start/end scanlines
|
||||
//See window_midframe test - unsure about behavior when top==bottom
|
||||
if(_state.Scanline == _state.Window[0].TopY) {
|
||||
_window0ActiveY = _state.Window0Enabled;
|
||||
}
|
||||
if(_state.Scanline == _state.Window[0].BottomY) {
|
||||
_window0ActiveY = false;
|
||||
}
|
||||
|
||||
if(x1 < 240) {
|
||||
memset(_activeWindow + x1, window, x2 - x1);
|
||||
if(_state.Scanline == _state.Window[1].TopY) {
|
||||
_window1ActiveY = _state.Window1Enabled;
|
||||
}
|
||||
if(_state.Scanline == _state.Window[1].BottomY) {
|
||||
_window1ActiveY = false;
|
||||
}
|
||||
|
||||
memset(_activeWindow, GbaPpu::OutsideWindow, sizeof(_activeWindow));
|
||||
}
|
||||
|
||||
void GbaPpu::ProcessWindow()
|
||||
{
|
||||
//Using the same logic as for window Y above, enable/disable windows when
|
||||
//the current pixel matches the left/right X value for the window
|
||||
//Some games set left > right, essentially making the window
|
||||
//wrap around to the beginning of the next scanline.
|
||||
//(MM&B does this for speech bubbles)
|
||||
//TODOGBA is there any test rom for this (window x)?
|
||||
int x = (_lastWindowCycle + 1) / 4;
|
||||
int end = std::min(_state.Cycle / 4, 255) + 1;
|
||||
if(x >= end) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_state.Window0Enabled || _state.Window1Enabled) {
|
||||
for(int i = -1; i < 4; i++) {
|
||||
uint16_t changePos = i < 0 ? 0 : _windowChangePos[i];
|
||||
uint16_t nextChange = i < 3 ? _windowChangePos[i + 1] : 256;
|
||||
if(nextChange == changePos || (changePos < x && nextChange < x)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(x == _state.Window[0].LeftX) {
|
||||
_window0ActiveX = _state.Window0Enabled;
|
||||
}
|
||||
if(x == _state.Window[0].RightX) {
|
||||
_window0ActiveX = false;
|
||||
}
|
||||
|
||||
if(x == _state.Window[1].LeftX) {
|
||||
_window1ActiveX = _state.Window1Enabled;
|
||||
}
|
||||
if(x == _state.Window[1].RightX) {
|
||||
_window1ActiveX = false;
|
||||
}
|
||||
|
||||
int length = std::min(nextChange - x, end - x);
|
||||
if(_window0ActiveX && _window0ActiveY) {
|
||||
memset(_activeWindow + x, GbaPpu::Window0, length);
|
||||
} else if(_window1ActiveX && _window1ActiveY) {
|
||||
memset(_activeWindow + x, GbaPpu::Window1, length);
|
||||
}
|
||||
|
||||
x += length;
|
||||
if(x >= end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_window0ActiveX = false;
|
||||
_window1ActiveX = false;
|
||||
}
|
||||
|
||||
_lastWindowCycle = _state.Cycle;
|
||||
}
|
||||
|
||||
void GbaPpu::SetWindowX(uint8_t& regValue, uint8_t newValue)
|
||||
{
|
||||
if(regValue != newValue) {
|
||||
regValue = newValue;
|
||||
UpdateWindowChangePoints();
|
||||
}
|
||||
}
|
||||
|
||||
void GbaPpu::InitializeActiveWindows()
|
||||
void GbaPpu::UpdateWindowChangePoints()
|
||||
{
|
||||
//TODOGBA currently doesn't update mid-scanline, is this latched on the GBA?
|
||||
if(!_state.Window0Enabled && !_state.Window1Enabled && !_state.ObjWindowEnabled) {
|
||||
memset(_activeWindow, GbaPpu::NoWindow, sizeof(_activeWindow));
|
||||
} else {
|
||||
memset(_activeWindow, GbaPpu::OutsideWindow, sizeof(_activeWindow));
|
||||
|
||||
if(_window1Active) {
|
||||
ApplyWindowBounds(GbaPpu::Window1);
|
||||
}
|
||||
|
||||
if(_window0Active) {
|
||||
ApplyWindowBounds(GbaPpu::Window0);
|
||||
}
|
||||
}
|
||||
//Generate a sorted list of the positions where the windows start/end
|
||||
//Used in ProcessWindow to process the changes in chunks
|
||||
_windowChangePos[0] = _state.Window[0].LeftX;
|
||||
_windowChangePos[1] = _state.Window[0].RightX;
|
||||
_windowChangePos[2] = _state.Window[1].LeftX;
|
||||
_windowChangePos[3] = _state.Window[1].RightX;
|
||||
std::sort(_windowChangePos, _windowChangePos + 4);
|
||||
}
|
||||
|
||||
void GbaPpu::SetPixelData(GbaPixelData& pixel, uint16_t color, uint8_t priority, uint8_t layer)
|
||||
|
@ -1247,10 +1300,10 @@ void GbaPpu::WriteRegister(uint32_t addr, uint8_t value)
|
|||
case 0x2E: case 0x3E: SetTransformOrigin<16>((addr & 0x10) >> 4, value, true); break;
|
||||
case 0x2F: case 0x3F: SetTransformOrigin<24>((addr & 0x10) >> 4, value & 0x0F, true); break;
|
||||
|
||||
case 0x40: _state.Window[0].RightX = value; break;
|
||||
case 0x41: _state.Window[0].LeftX = value; break;
|
||||
case 0x42: _state.Window[1].RightX = value; break;
|
||||
case 0x43: _state.Window[1].LeftX = value; break;
|
||||
case 0x40: SetWindowX(_state.Window[0].RightX, value); break;
|
||||
case 0x41: SetWindowX(_state.Window[0].LeftX, value); break;
|
||||
case 0x42: SetWindowX(_state.Window[1].RightX, value); break;
|
||||
case 0x43: SetWindowX(_state.Window[1].LeftX, value); break;
|
||||
|
||||
case 0x44: _state.Window[0].BottomY = value; break;
|
||||
case 0x45: _state.Window[0].TopY = value; break;
|
||||
|
@ -1479,8 +1532,12 @@ void GbaPpu::Serialize(Serializer& s)
|
|||
SVArray(_memoryAccess, 308 * 4);
|
||||
|
||||
SV(_triggerSpecialDma);
|
||||
SV(_window0Active);
|
||||
SV(_window1Active);
|
||||
|
||||
SV(_lastWindowCycle);
|
||||
SV(_window0ActiveY);
|
||||
SV(_window1ActiveY);
|
||||
SV(_window0ActiveX);
|
||||
SV(_window1ActiveX);
|
||||
|
||||
SV(_lastRenderCycle);
|
||||
SV(_evalOamIndex);
|
||||
|
@ -1549,4 +1606,8 @@ void GbaPpu::Serialize(Serializer& s)
|
|||
SVI(_objData[i].Height);
|
||||
}
|
||||
}
|
||||
|
||||
if(!s.IsSaving()) {
|
||||
UpdateWindowChangePoints();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,14 +116,19 @@ private:
|
|||
GbaPixelData _layerOutput[4][GbaConstants::ScreenWidth] = {};
|
||||
|
||||
uint8_t _oamWindow[GbaConstants::ScreenWidth] = {};
|
||||
uint8_t _activeWindow[GbaConstants::ScreenWidth] = {};
|
||||
uint8_t _activeWindow[256] = {};
|
||||
|
||||
Timer _frameSkipTimer;
|
||||
bool _skipRender = false;
|
||||
|
||||
bool _triggerSpecialDma = false;
|
||||
bool _window0Active = false;
|
||||
bool _window1Active = false;
|
||||
|
||||
int16_t _lastWindowCycle = -1;
|
||||
bool _window0ActiveY = false;
|
||||
bool _window1ActiveY = false;
|
||||
bool _window0ActiveX = false;
|
||||
bool _window1ActiveX = false;
|
||||
uint8_t _windowChangePos[4] = {};
|
||||
|
||||
int16_t _lastRenderCycle = -1;
|
||||
GbaLayerRendererData _layerData[4] = {};
|
||||
|
@ -166,11 +171,12 @@ private:
|
|||
template<GbaPpuBlendEffect effect, bool bg0Enabled, bool bg1Enabled, bool bg2Enabled, bool bg3Enabled, bool windowEnabled> void ProcessColorMath();
|
||||
|
||||
void BlendColors(uint16_t* dst, int x, uint16_t a, uint8_t aCoeff, uint16_t b, uint8_t bCoeff);
|
||||
void ApplyWindowBounds(uint8_t window);
|
||||
void InitializeActiveWindows();
|
||||
|
||||
template<bool isSubColor> uint16_t ReadColor(int x, uint16_t addr);
|
||||
|
||||
void InitializeWindows();
|
||||
void ProcessWindow();
|
||||
void SetWindowX(uint8_t& regValue, uint8_t newValue);
|
||||
void UpdateWindowChangePoints();
|
||||
void SetWindowActiveLayers(int window, uint8_t cfg);
|
||||
|
||||
void SendFrame();
|
||||
|
|
Loading…
Add table
Reference in a new issue