// Meteor - A Nintendo Gameboy Advance emulator // Copyright (C) 2009-2011 Philippe Daouadi // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "ameteor/graphics/screen.hpp" #include "../globals.hpp" #include "../debug.hpp" #include "ameteor.hpp" #include namespace AMeteor { namespace Graphics { BgLayer Screen::* const Screen::BgLayers [4] = { &Screen::m_bgLayer0, &Screen::m_bgLayer1, &Screen::m_bgLayer2, &Screen::m_bgLayer3 }; // TODO there is no more need to pass theses references Screen::Screen (Memory& memory, Io& io) : m_io(io), m_surface(new uint16_t[WIDTH*HEIGHT]), m_renderer(m_surface), m_frameskip(0), m_curframe(0), m_dispcnt(0), m_refX2(0), m_refY2(0), m_refX3(0), m_refY3(0), m_pPalette((uint16_t*)memory.GetRealAddress(0x05000000)), m_bgLayer0(0, memory, io, m_pPalette), m_bgLayer1(1, memory, io, m_pPalette), m_bgLayer2(2, memory, io, m_pPalette), m_bgLayer3(3, memory, io, m_pPalette), m_objs(memory, io, m_pPalette + 256) { } Screen::~Screen () { delete [] m_surface; } void Screen::DrawLine (uint8_t line) { if (m_curframe < m_frameskip) { // we skip this frame // VBlank if (line == 159) // we don't update screen since we haven't drawn anything on it, it // would show up a buffer with the previous image or maybe only // garbage (we use double buffering) m_curframe = (m_curframe + 1) % FRMSKIP_TOTAL; return; } uint16_t* lineBg = new uint16_t[4*WIDTH]; // layers may draw transparent pixels, so we need to clear them memset(lineBg, 0, 4*WIDTH*sizeof(uint16_t)); uint32_t* lineObj = new uint32_t[WIDTH]; for (uint32_t* p = lineObj + WIDTH - 1; p >= lineObj; --p) *p = 0x00030000; uint8_t prio[4]; prio[0] = m_bgLayer0.GetPriority(); prio[1] = m_bgLayer1.GetPriority(); prio[2] = m_bgLayer2.GetPriority(); prio[3] = m_bgLayer3.GetPriority(); uint8_t layersOn = (m_dispcnt >> 8) & 0x1F; switch (m_dispcnt & 0x7) { case 0: // all in mode 0 // if the bg is enabled draw it if (layersOn & (0x1 )) m_bgLayer0.DrawLine0(line, lineBg); if (layersOn & (0x1 << 1)) m_bgLayer1.DrawLine0(line, lineBg+WIDTH); if (layersOn & (0x1 << 2)) m_bgLayer2.DrawLine0(line, lineBg+2*WIDTH); if (layersOn & (0x1 << 3)) m_bgLayer3.DrawLine0(line, lineBg+3*WIDTH); // if objects are enabled draw them if (layersOn & (0x1 << 4)) m_objs.DrawLine(line, lineObj); break; case 1: // bg0 and bg1 in mode 0 and bg2 in mode 2, no bg3 // disable layer 3 layersOn &= 0xF7; // if the bg is enabled draw it if (layersOn & (0x1 )) m_bgLayer0.DrawLine0(line, lineBg); if (layersOn & (0x1 << 1)) m_bgLayer1.DrawLine0(line, lineBg+WIDTH); if (layersOn & (0x1 << 2)) m_bgLayer2.DrawLine2(lineBg+2*WIDTH, m_refX2, m_refY2, m_io.DRead16(Io::BG2PA), m_io.DRead16(Io::BG2PC)); // if objects are enabled draw them if (layersOn & (0x1 << 4)) m_objs.DrawLine(line, lineObj); break; case 2: // bg2 and bg3 in mode 2, no bg0 and bg1 // disable layers 0 and 1 layersOn &= 0xFC; // if the bg is enabled draw it if (layersOn & (0x1 << 2)) m_bgLayer2.DrawLine2(lineBg+2*WIDTH, m_refX2, m_refY2, m_io.DRead16(Io::BG2PA), m_io.DRead16(Io::BG2PC)); if (layersOn & (0x1 << 3)) m_bgLayer3.DrawLine2(lineBg+3*WIDTH, m_refX3, m_refY3, m_io.DRead16(Io::BG3PA), m_io.DRead16(Io::BG3PC)); // if objects are enabled draw them if (layersOn & (0x1 << 4)) m_objs.DrawLine(line, lineObj); break; case 3: // bg2 only 15 bit direct color 240x160 layersOn &= 0xF4; if (layersOn & (0x1 << 2)) m_bgLayer2.DrawLine3(lineBg+2*WIDTH, m_refX2, m_refY2, m_io.DRead16(Io::BG2PA), m_io.DRead16(Io::BG2PC)); if (layersOn & (0x1 << 4)) m_objs.DrawLineHighOnly(line, lineObj); break; // TODO (remember, HIGH ONLY for objs, don't make shitty copy paste) case 4: // bg2 only in mode 4 (bitmap 256) layersOn &= 0xF4; // if bg2 is enabled if (layersOn & (0x1 << 2)) // draw it m_bgLayer2.DrawLine4( line, lineBg+2*WIDTH, m_refX2, m_refY2, m_io.DRead16(Io::BG2PA), m_io.DRead16(Io::BG2PB), m_io.DRead16(Io::BG2PC), m_io.DRead16(Io::BG2PD), m_dispcnt & (0x1 << 4)); // if objs are enabled if (layersOn & (0x1 << 4)) // all objs with the current priority m_objs.DrawLineHighOnly(line, lineObj); break; case 5: // bg2 only 15 bit direct color 160x128 2 frames layersOn &= 0xF4; if (layersOn & (0x1 << 2)) m_bgLayer2.DrawLine5(lineBg+2*WIDTH, m_refX2, m_refY2, m_io.DRead16(Io::BG2PA), m_io.DRead16(Io::BG2PC), m_dispcnt & (0x1 << 4)); if (layersOn & (0x1 << 4)) m_objs.DrawLineHighOnly(line, lineObj); break; default : met_abort("not supported : " << (m_dispcnt & 0x7)); break; } // windows /* I got very little information for this, it may not be accurate. All * the sources don't say the same thing */ uint8_t* window = NULL; if (m_dispcnt >> 13) { window = new uint8_t[WIDTH]; // Outside window memset(window, m_io.DRead16(Io::WINOUT) & 0x3F, WIDTH*sizeof(uint8_t)); // OBJ window if (m_dispcnt & (0x1 << 15)) m_objs.DrawWindow(line, window); // Window 1 if (m_dispcnt & (0x1 << 14)) DrawWindow(line, window, m_io.DRead16(Io::WIN1V), m_io.DRead16(Io::WIN1H), (m_io.DRead16(Io::WININ) >> 8) & 0x3F); // Window 0 if (m_dispcnt & (0x1 << 13)) DrawWindow(line, window, m_io.DRead16(Io::WIN0V), m_io.DRead16(Io::WIN0H), m_io.DRead16(Io::WININ) & 0x3F); } // color effects uint16_t bldcnt = m_io.DRead16(Io::BLDCNT); uint8_t colorEffect = (bldcnt >> 6) & 0x3; uint8_t eva = std::min(m_io.DRead8(Io::BLDALPHA) & 0x1F, 16); uint8_t evb = std::min(m_io.DRead8(Io::BLDALPHA+1) & 0x1F, 16); uint8_t evy = std::min(m_io.DRead8(Io::BLDY) & 0x1F, 16); // blending uint16_t* surface = m_surface + line*WIDTH; uint16_t out, bout; // top and back are formated as follow : // 4 bits | 4 bits // priority | layer uint8_t top, back; uint8_t curprio; uint32_t* pObj = lineObj; uint16_t* pBg = lineBg; uint8_t* pWin = window; uint8_t winmask; // if window are disabled, we draw everything which is enabled by // layersOn if (!window) winmask = 0xFF; for (uint8_t x = 0; x < WIDTH; ++x, ++pBg, ++pObj, ++pWin) { if (window) winmask = *pWin; // backdrop bout = out = m_pPalette[0]; back = top = 0xF5; // for each layer for (uint8_t l = 0; l < 4; ++l) // if layer is enabled and if ((layersOn & (0x1 << l)) && // pixel to draw is not transparent (pBg[l*WIDTH] & 0x8000)) { curprio = ((prio[l] << 4) | l); if (curprio < back && curprio > top) { bout = pBg[l*WIDTH]; back = curprio; } else if ( // priority is lower than current top pixel and curprio < top && // this layer should be drawn in current window (winmask & (0x1 << l))) { bout = out; out = pBg[l*WIDTH]; back = top; top = curprio; } } // now objects // if objects are enabled if ((layersOn & (0x1 << 4)) && // pixel to draw is not transparent (*pObj & 0x8000)) { curprio = ((*pObj >> (16 - 4)) & (0x3 << 4)); if (curprio <= (back & 0xF0) && curprio > (top & 0xF0)) { bout = *pObj; back = curprio | 4; } else if (// priority is lower than current top pixel and // NOTE : objects are OVER bg with same priority curprio <= (top & 0xF0) && // objects should be drawn in current window (winmask & (0x1 << 4))) { bout = out; out = *pObj; back = top; top = curprio | 4; } } // if we have an object on top and it has semi transparency if ((top & 0xF) == 4 && (*pObj & (0x1 << 18))) { // if second target is just behind if (bldcnt & ((0x1 << 8) << (back & 0xF))) // apply alpha blend out = std::min(((bout & 0x001F) * evb + (out & 0x001F) * eva) / 16, 0x001F) | std::min((((bout & 0x03E0) * evb + (out & 0x03E0) * eva) / 16) & 0xFFE0, 0x03E0) | // no need to take care of over flow since u16 & s32 = s32 std::min((((bout & 0x7C00) * evb + (out & 0x7C00) * eva) / 16) & 0xFC00, 0x7C00); } // else if no window or window and effects are enabled in window // and we have a first target on top else if ((!window || (*pWin & (0x1 << 5))) && (bldcnt & (0x1 << (top & 0xF)))) switch (colorEffect) { case 1: // alpha blend // if second target is just behind // TODO optimization : special cases for eva = 0 or evb = 0 if (bldcnt & ((0x1 << 8) << (back & 0xF))) // apply alpha blend out = std::min(((bout & 0x001F) * evb + (out & 0x001F) * eva) / 16, 0x001F) | std::min((((bout & 0x03E0) * evb + (out & 0x03E0) * eva) / 16) & 0xFFE0, 0x03E0) | // no need to take care of over flow since u16 & s32 = s32 std::min((((bout & 0x7C00) * evb + (out & 0x7C00) * eva) / 16) & 0xFC00, 0x7C00); break; case 2: // brightness increase // we don't need saturation since the formula makes it so it never // goes above 0x1F out = (((out & 0x001F) + ((0x001F - (out & 0x001F)) * evy) / 16) & 0x001F) | (((out & 0x03E0) + ((0x03E0 - (out & 0x03E0)) * evy) / 16) & 0x03E0) | (((out & 0x7C00) + ((0x7C00 - (out & 0x7C00)) * evy) / 16) & 0x7C00); break; case 3: // brightness decrease // we don't need saturation since the formula makes it so it never // goes below 0 out = ((((out & 0x001F) * (16-evy)) / 16) & 0x001F) | ((((out & 0x03E0) * (16-evy)) / 16) & 0x03E0) | ((((out & 0x7C00) * (16-evy)) / 16) & 0x7C00); break; } *surface = out; ++surface; } m_refX2 += (int16_t)m_io.DRead16(Io::BG2PB); m_refY2 += (int16_t)m_io.DRead16(Io::BG2PD); m_refX3 += (int16_t)m_io.DRead16(Io::BG3PB); m_refY3 += (int16_t)m_io.DRead16(Io::BG3PD); if (window) delete [] window; delete [] lineBg; delete [] lineObj; // VBlank if (line == 159) { m_curframe = (m_curframe + 1) % FRMSKIP_TOTAL; m_renderer.VBlank(); } } void Screen::DrawWindow (uint8_t line, uint8_t* surface, uint16_t win0v, uint16_t win0h, uint8_t mask) { // the variables are called win0, but this function works also for win1 uint8_t win0t = win0v >> 8, win0b = win0v & 0xFF; // VBA says that if t == b and they are greater than 228, we are in the // window // This is from Tonc documentation if (win0t >= 227) return; else if (win0b > win0t && line >= win0t && line < win0b // the above is the normal behaviour || win0b < win0t && (line >= win0t || line < win0b) // the above is the "inverted" behaviour ) { uint8_t win0l, win0r; uint8_t* ptr; win0l = win0h >> 8; win0r = win0h & 0xFF; // this seems wrong //if (win0l > 240) // win0l = 240; //if (win0r > 240) // win0r = 240; // if this is the normal behaviour if (win0l <= win0r) { ptr = surface + win0l; for (uint8_t i = win0l; i < win0r && i < 240; ++i, ++ptr) *ptr = mask; } // else, this is the inverted behaviour else { ptr = surface; for (uint8_t i = 0; i < win0r && i < 240; ++i, ++ptr) *ptr = mask; ptr = surface + win0l; for (uint8_t i = win0l; i < 240; ++i, ++ptr) *ptr = mask; } } } bool Screen::SaveState (std::ostream& stream) { SS_WRITE_VAR(m_refX2); SS_WRITE_VAR(m_refY2); SS_WRITE_VAR(m_refX3); SS_WRITE_VAR(m_refY3); return true; } bool Screen::LoadState (std::istream& stream) { SS_READ_VAR(m_refX2); SS_READ_VAR(m_refY2); SS_READ_VAR(m_refX3); SS_READ_VAR(m_refY3); return true; } } }