void InitBackground(const BACKGND *pBgnd) { int i; // playfield counter PLAYFIELD *pPlayfield; // pointer to current playfield // set current background pCurBgnd = pBgnd; // init background sky colour SetBgndColour(pBgnd->rgbSkyColour); // start of playfield array pPlayfield = pBgnd->fieldArray; // for each background playfield for (i = 0; i < pBgnd->numPlayfields; i++, pPlayfield++) { // init playfield pos pPlayfield->fieldX = intToFrac(pBgnd->ptInitWorld.x); pPlayfield->fieldY = intToFrac(pBgnd->ptInitWorld.y); // no scrolling pPlayfield->fieldXvel = intToFrac(0); pPlayfield->fieldYvel = intToFrac(0); // clear playfield display list pPlayfield->pDispList = NULL; // clear playfield moved flag pPlayfield->bMoved = false; } }
void OpenGLGraphicsManager::setActualScreenSize(uint width, uint height) { _outputScreenWidth = width; _outputScreenHeight = height; // Setup backbuffer size. _backBuffer.setDimensions(width, height); uint overlayWidth = width; uint overlayHeight = height; // WORKAROUND: We can only support surfaces up to the maximum supported // texture size. Thus, in case we encounter a physical size bigger than // this maximum texture size we will simply use an overlay as big as // possible and then scale it to the physical display size. This sounds // bad but actually all recent chips should support full HD resolution // anyway. Thus, it should not be a real issue for modern hardware. if ( overlayWidth > (uint)g_context.maxTextureSize || overlayHeight > (uint)g_context.maxTextureSize) { const frac_t outputAspect = intToFrac(_outputScreenWidth) / _outputScreenHeight; if (outputAspect > (frac_t)FRAC_ONE) { overlayWidth = g_context.maxTextureSize; overlayHeight = intToFrac(overlayWidth) / outputAspect; } else { overlayHeight = g_context.maxTextureSize; overlayWidth = fracToInt(overlayHeight * outputAspect); } } // HACK: We limit the minimal overlay size to 256x200, which is the // minimum of the dimensions of the two resolutions 256x240 (NES) and // 320x200 (many DOS games use this). This hopefully assure that our // GUI has working layouts. overlayWidth = MAX<uint>(overlayWidth, 256); overlayHeight = MAX<uint>(overlayHeight, 200); if (!_overlay || _overlay->getFormat() != _defaultFormatAlpha) { delete _overlay; _overlay = nullptr; _overlay = createSurface(_defaultFormatAlpha); assert(_overlay); // We always filter the overlay with GL_LINEAR. This assures it's // readable in case it needs to be scaled and does not affect it // otherwise. _overlay->enableLinearFiltering(true); } _overlay->allocate(overlayWidth, overlayHeight); _overlay->fill(0); // Re-setup the scaling for the screen and cursor recalculateDisplayArea(); recalculateCursorScaling(); // Something changed, so update the screen change ID. ++_screenChangeID; }
frac_t OpenGLGraphicsManager::getDesiredGameScreenAspect() const { const uint width = _currentState.gameWidth; const uint height = _currentState.gameHeight; if (_currentState.aspectRatioCorrection) { // In case we enable aspect ratio correction we force a 4/3 ratio. // But just for 320x200 and 640x400 games, since other games do not need // this. if ((width == 320 && height == 200) || (width == 640 && height == 400)) { return intToFrac(4) / 3; } } return intToFrac(width) / height; }
/** * Give a object a new image and new orientation flags. * @param pAniObj Object to be updated * @param newflags Objects new flags * @param hNewImg Objects new image */ void AnimateObjectFlags(OBJECT *pAniObj, int newflags, SCNHANDLE hNewImg) { // validate object pointer assert(isValidObject(pAniObj)); if (pAniObj->hImg != hNewImg || (pAniObj->flags & DMA_HARDFLAGS) != (newflags & DMA_HARDFLAGS)) { // something has changed int oldAniX, oldAniY; // objects old animation offsets int newAniX, newAniY; // objects new animation offsets // get objects old animation offsets GetAniOffset(pAniObj->hImg, pAniObj->flags, &oldAniX, &oldAniY); // get objects new animation offsets GetAniOffset(hNewImg, newflags, &newAniX, &newAniY); if (hNewImg) { // get pointer to image const IMAGE *pNewImg = (IMAGE *)LockMem(hNewImg); // setup new shape pAniObj->width = FROM_LE_16(pNewImg->imgWidth); pAniObj->height = FROM_LE_16(pNewImg->imgHeight) & ~C16_FLAG_MASK; newflags &= ~C16_FLAG_MASK; newflags |= FROM_LE_16(pNewImg->imgHeight) & C16_FLAG_MASK; // set objects bitmap definition pAniObj->hBits = FROM_LE_32(pNewImg->hImgBits); } else { // null image pAniObj->width = 0; pAniObj->height = 0; pAniObj->hBits = 0; } // set objects flags and signal a change pAniObj->flags = newflags | DMA_CHANGED; // set objects image pAniObj->hImg = hNewImg; // adjust objects position - subtract new from old for difference pAniObj->xPos += intToFrac(oldAniX - newAniX); pAniObj->yPos += intToFrac(oldAniY - newAniY); } }
void PlayfieldSetPos(int which, int newXpos, int newYpos) { PLAYFIELD *pPlayfield; // pointer to relavent playfield // make sure there is a background assert(pCurBgnd != NULL); // make sure the playfield number is in range assert(which >= 0 && which < pCurBgnd->numPlayfields); // get playfield pointer pPlayfield = pCurBgnd->fieldArray + which; // set new integer position pPlayfield->fieldX = intToFrac(newXpos); pPlayfield->fieldY = intToFrac(newYpos); // set moved flag pPlayfield->bMoved = true; }
void ListWidget::reflowLayout() { Widget::reflowLayout(); _leftPadding = g_gui.xmlEval()->getVar("Globals.ListWidget.Padding.Left", 0); _rightPadding = g_gui.xmlEval()->getVar("Globals.ListWidget.Padding.Right", 0); _topPadding = g_gui.xmlEval()->getVar("Globals.ListWidget.Padding.Top", 0); _bottomPadding = g_gui.xmlEval()->getVar("Globals.ListWidget.Padding.Bottom", 0); _hlLeftPadding = g_gui.xmlEval()->getVar("Globals.ListWidget.hlLeftPadding", 0); _hlRightPadding = g_gui.xmlEval()->getVar("Globals.ListWidget.hlRightPadding", 0); _scrollBarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0); // HACK: Once we take padding into account, there are times where // integer rounding leaves a big chunk of white space in the bottom // of the list. // We do a rough rounding on the decimal places of Entries Per Page, // to add another entry even if it goes a tad over the padding. frac_t entriesPerPage = intToFrac(_h - _topPadding - _bottomPadding) / kLineHeight; // Our threshold before we add another entry is 0.9375 (0xF000 with FRAC_BITS being 16). const frac_t threshold = intToFrac(15) / 16; if ((frac_t)(entriesPerPage & FRAC_LO_MASK) >= threshold) entriesPerPage += FRAC_ONE; _entriesPerPage = fracToInt(entriesPerPage); assert(_entriesPerPage > 0); delete[] _textWidth; _textWidth = new int[_entriesPerPage]; for (int i = 0; i < _entriesPerPage; i++) _textWidth[i] = 0; if (_scrollBar) { _scrollBar->resize(_w - _scrollBarWidth + 1, 0, _scrollBarWidth, _h); scrollBarRecalc(); scrollToCurrent(); } }
void OpenGLGraphicsManager::recalculateDisplayArea() { if (!_gameScreen || _outputScreenHeight == 0) { return; } const frac_t outputAspect = intToFrac(_outputScreenWidth) / _outputScreenHeight; const frac_t desiredAspect = getDesiredGameScreenAspect(); _displayWidth = _outputScreenWidth; _displayHeight = _outputScreenHeight; // Adjust one dimension for mantaining the aspect ratio. if (outputAspect < desiredAspect) { _displayHeight = intToFrac(_displayWidth) / desiredAspect; } else if (outputAspect > desiredAspect) { _displayWidth = fracToInt(_displayHeight * desiredAspect); } // We center the screen in the middle for now. _displayX = (_outputScreenWidth - _displayWidth ) / 2; _displayY = (_outputScreenHeight - _displayHeight) / 2; }
void MultiMoveRelXY(OBJECT *pMultiObj, int deltaX, int deltaY) { // validate object pointer assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); if (deltaX == 0 && deltaY == 0) return; // ignore no change // for all the objects that make up this multi-part do { // signal a change in the object pMultiObj->flags |= DMA_CHANGED; // adjust the x position pMultiObj->xPos += intToFrac(deltaX); // adjust the y position pMultiObj->yPos += intToFrac(deltaY); // next obj in list pMultiObj = pMultiObj->pSlave; } while (pMultiObj != NULL); }
void MultiAdjustXY(OBJECT *pMultiObj, int deltaX, int deltaY) { // validate object pointer assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); if (deltaX == 0 && deltaY == 0) return; // ignore no change if (!TinselV2) { // *** This may be wrong!!! if (pMultiObj->flags & DMA_FLIPH) { // image is flipped horizontally - flip the x direction deltaX = -deltaX; } if (pMultiObj->flags & DMA_FLIPV) { // image is flipped vertically - flip the y direction deltaY = -deltaY; } } // for all the objects that make up this multi-part do { // signal a change in the object pMultiObj->flags |= DMA_CHANGED; // adjust the x position pMultiObj->xPos += intToFrac(deltaX); // adjust the y position pMultiObj->yPos += intToFrac(deltaY); // next obj in list pMultiObj = pMultiObj->pSlave; } while (pMultiObj != NULL); }
/** * Sort the specified object list in Z Y order. * @param pObjList List to sort */ void SortObjectList(OBJECT *pObjList) { OBJECT *pPrev, *pObj; // object list traversal pointers OBJECT head; // temporary head of list - because pObjList is not usually a OBJECT // put at head of list head.pNext = pObjList->pNext; // set head of list dummy OBJ Z Y values to lowest possible head.yPos = intToFrac(MIN_INT16); head.zPos = MIN_INT; for (pPrev = &head, pObj = head.pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) { // check Z order if (pObj->zPos < pPrev->zPos) { // object Z is lower than previous Z // remove object from list pPrev->pNext = pObj->pNext; // re-insert object on list InsertObject(pObjList, pObj); // back to beginning of list pPrev = &head; pObj = head.pNext; } else if (pObj->zPos == pPrev->zPos) { // Z values are the same - sort on Y if (fracToDouble(pObj->yPos) < fracToDouble(pPrev->yPos)) { // object Y is lower than previous Y // remove object from list pPrev->pNext = pObj->pNext; // re-insert object on list InsertObject(pObjList, pObj); // back to beginning of list pPrev = &head; pObj = head.pNext; } } } }
bool OpenGLGraphicsManager::getGLPixelFormat(const Graphics::PixelFormat &pixelFormat, GLenum &glIntFormat, GLenum &glFormat, GLenum &glType) const { #ifdef SCUMM_LITTLE_ENDIAN if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24)) { // ABGR8888 #else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)) { // RGBA8888 #endif glIntFormat = GL_RGBA; glFormat = GL_RGBA; glType = GL_UNSIGNED_BYTE; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)) { // RGB565 glIntFormat = GL_RGB; glFormat = GL_RGB; glType = GL_UNSIGNED_SHORT_5_6_5; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0)) { // RGBA5551 glIntFormat = GL_RGBA; glFormat = GL_RGBA; glType = GL_UNSIGNED_SHORT_5_5_5_1; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0)) { // RGBA4444 glIntFormat = GL_RGBA; glFormat = GL_RGBA; glType = GL_UNSIGNED_SHORT_4_4_4_4; return true; #if !USE_FORCED_GLES && !USE_FORCED_GLES2 // The formats below are not supported by every GLES implementation. // Thus, we do not mark them as supported when a GLES context is setup. } else if (isGLESContext()) { return false; #ifdef SCUMM_LITTLE_ENDIAN } else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)) { // RGBA8888 glIntFormat = GL_RGBA; glFormat = GL_RGBA; glType = GL_UNSIGNED_INT_8_8_8_8; return true; #endif } else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)) { // RGB555 glIntFormat = GL_RGB; glFormat = GL_BGRA; glType = GL_UNSIGNED_SHORT_1_5_5_5_REV; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 8, 4, 0, 12)) { // ARGB4444 glIntFormat = GL_RGBA; glFormat = GL_BGRA; glType = GL_UNSIGNED_SHORT_4_4_4_4_REV; return true; #ifdef SCUMM_BIG_ENDIAN } else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24)) { // ABGR8888 glIntFormat = GL_RGBA; glFormat = GL_RGBA; glType = GL_UNSIGNED_INT_8_8_8_8_REV; return true; #endif } else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 8, 16, 24, 0)) { // BGRA8888 glIntFormat = GL_RGBA; glFormat = GL_BGRA; glType = GL_UNSIGNED_INT_8_8_8_8; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 5, 6, 5, 0, 0, 5, 11, 0)) { // BGR565 glIntFormat = GL_RGB; glFormat = GL_RGB; glType = GL_UNSIGNED_SHORT_5_6_5_REV; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 1, 1, 6, 11, 0)) { // BGRA5551 glIntFormat = GL_RGBA; glFormat = GL_BGRA; glType = GL_UNSIGNED_SHORT_5_5_5_1; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 0, 4, 8, 12)) { // ABGR4444 glIntFormat = GL_RGBA; glFormat = GL_RGBA; glType = GL_UNSIGNED_SHORT_4_4_4_4_REV; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 4, 8, 12, 0)) { // BGRA4444 glIntFormat = GL_RGBA; glFormat = GL_BGRA; glType = GL_UNSIGNED_SHORT_4_4_4_4; return true; #endif // !USE_FORCED_GLES && !USE_FORCED_GLES2 } else { return false; } } frac_t OpenGLGraphicsManager::getDesiredGameScreenAspect() const { const uint width = _currentState.gameWidth; const uint height = _currentState.gameHeight; if (_currentState.aspectRatioCorrection) { // In case we enable aspect ratio correction we force a 4/3 ratio. // But just for 320x200 and 640x400 games, since other games do not need // this. if ((width == 320 && height == 200) || (width == 640 && height == 400)) { return intToFrac(4) / 3; } } return intToFrac(width) / height; } void OpenGLGraphicsManager::recalculateDisplayArea() { if (!_gameScreen || _outputScreenHeight == 0) { return; } const frac_t outputAspect = intToFrac(_outputScreenWidth) / _outputScreenHeight; const frac_t desiredAspect = getDesiredGameScreenAspect(); _displayWidth = _outputScreenWidth; _displayHeight = _outputScreenHeight; // Adjust one dimension for mantaining the aspect ratio. if (outputAspect < desiredAspect) { _displayHeight = intToFrac(_displayWidth) / desiredAspect; } else if (outputAspect > desiredAspect) { _displayWidth = fracToInt(_displayHeight * desiredAspect); } // We center the screen in the middle for now. _displayX = (_outputScreenWidth - _displayWidth ) / 2; _displayY = (_outputScreenHeight - _displayHeight) / 2; // Setup drawing limitation for game graphics. // This invovles some trickery because OpenGL's viewport coordinate system // is upside down compared to ours. _backBuffer.setScissorBox(_displayX, _outputScreenHeight - _displayHeight - _displayY, _displayWidth, _displayHeight); // Clear the whole screen for the first three frames to remove leftovers. _scissorOverride = 3; // Update the cursor position to adjust for new display area. setMousePosition(_cursorX, _cursorY); // Force a redraw to assure screen is properly redrawn. _forceRedraw = true; } void OpenGLGraphicsManager::updateCursorPalette() { if (!_cursor || !_cursor->hasPalette()) { return; } if (_cursorPaletteEnabled) { _cursor->setPalette(0, 256, _cursorPalette); } else { _cursor->setPalette(0, 256, _gamePalette); } _cursor->setColorKey(_cursorKeyColor); } void OpenGLGraphicsManager::recalculateCursorScaling() { if (!_cursor || !_gameScreen) { return; } // By default we use the unscaled versions. _cursorHotspotXScaled = _cursorHotspotX; _cursorHotspotYScaled = _cursorHotspotY; _cursorWidthScaled = _cursor->getWidth(); _cursorHeightScaled = _cursor->getHeight(); // In case scaling is actually enabled we will scale the cursor according // to the game screen. if (!_cursorDontScale) { const frac_t screenScaleFactorX = intToFrac(_displayWidth) / _gameScreen->getWidth(); const frac_t screenScaleFactorY = intToFrac(_displayHeight) / _gameScreen->getHeight(); _cursorHotspotXScaled = fracToInt(_cursorHotspotXScaled * screenScaleFactorX); _cursorWidthScaled = fracToInt(_cursorWidthScaled * screenScaleFactorX); _cursorHotspotYScaled = fracToInt(_cursorHotspotYScaled * screenScaleFactorY); _cursorHeightScaled = fracToInt(_cursorHeightScaled * screenScaleFactorY); } } #ifdef USE_OSD const Graphics::Font *OpenGLGraphicsManager::getFontOSD() { return FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont); } #endif void OpenGLGraphicsManager::saveScreenshot(const Common::String &filename) const { const uint width = _outputScreenWidth; const uint height = _outputScreenHeight; // A line of a BMP image must have a size divisible by 4. // We calculate the padding bytes needed here. // Since we use a 3 byte per pixel mode, we can use width % 4 here, since // it is equal to 4 - (width * 3) % 4. (4 - (width * Bpp) % 4, is the // usual way of computing the padding bytes required). const uint linePaddingSize = width % 4; const uint lineSize = width * 3 + linePaddingSize; // Allocate memory for screenshot uint8 *pixels = new uint8[lineSize * height]; // Get pixel data from OpenGL buffer GL_CALL(glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels)); // BMP stores as BGR. Since we can't assume that GL_BGR is supported we // will swap the components from the RGB we read to BGR on our own. for (uint y = height; y-- > 0;) { uint8 *line = pixels + y * lineSize; for (uint x = width; x > 0; --x, line += 3) { SWAP(line[0], line[2]); } } // Open file Common::DumpFile out; out.open(filename); // Write BMP header out.writeByte('B'); out.writeByte('M'); out.writeUint32LE(height * lineSize + 54); out.writeUint32LE(0); out.writeUint32LE(54); out.writeUint32LE(40); out.writeUint32LE(width); out.writeUint32LE(height); out.writeUint16LE(1); out.writeUint16LE(24); out.writeUint32LE(0); out.writeUint32LE(0); out.writeUint32LE(0); out.writeUint32LE(0); out.writeUint32LE(0); out.writeUint32LE(0); // Write pixel data to BMP out.write(pixels, lineSize * height); // Free allocated memory delete[] pixels; } } // End of namespace OpenGL
/** * Initialise a object using a OBJ_INIT structure to supply parameters. * @param pInitTbl Pointer to object initialisation table */ OBJECT *InitObject(const OBJ_INIT *pInitTbl) { // allocate a new object OBJECT *pObj = AllocObject(); // make sure object created assert(pObj != NULL); // set objects shape pObj->hImg = pInitTbl->hObjImg; // set objects ID pObj->oid = pInitTbl->objID; // set objects flags pObj->flags = DMA_CHANGED | pInitTbl->objFlags; // set objects Z position pObj->zPos = pInitTbl->objZ; // get pointer to image if (pInitTbl->hObjImg) { int aniX, aniY; // objects animation offsets PALQ *pPalQ = NULL; // palette queue pointer const IMAGE *pImg = (const IMAGE *)LockMem(pInitTbl->hObjImg); // handle to image if (pImg->hImgPal) { // allocate a palette for this object pPalQ = AllocPalette(FROM_LE_32(pImg->hImgPal)); // make sure palette allocated assert(pPalQ != NULL); } // assign palette to object pObj->pPal = pPalQ; // set objects size pObj->width = FROM_LE_16(pImg->imgWidth); pObj->height = FROM_LE_16(pImg->imgHeight) & ~C16_FLAG_MASK; pObj->flags &= ~C16_FLAG_MASK; pObj->flags |= FROM_LE_16(pImg->imgHeight) & C16_FLAG_MASK; // set objects bitmap definition pObj->hBits = FROM_LE_32(pImg->hImgBits); // get animation offset of object GetAniOffset(pObj->hImg, pInitTbl->objFlags, &aniX, &aniY); // set objects X position - subtract ani offset pObj->xPos = intToFrac(pInitTbl->objX - aniX); // set objects Y position - subtract ani offset pObj->yPos = intToFrac(pInitTbl->objY - aniY); } else { // no image handle - null image // set objects X position pObj->xPos = intToFrac(pInitTbl->objX); // set objects Y position pObj->yPos = intToFrac(pInitTbl->objY); } // return new object return pObj; }
bool OpenGLGraphicsManager::getGLPixelFormat(const Graphics::PixelFormat &pixelFormat, GLenum &glIntFormat, GLenum &glFormat, GLenum &glType) const { #ifdef SCUMM_LITTLE_ENDIAN if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24)) { // ABGR8888 #else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)) { // RGBA8888 #endif glIntFormat = GL_RGBA; glFormat = GL_RGBA; glType = GL_UNSIGNED_BYTE; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)) { // RGB565 glIntFormat = GL_RGB; glFormat = GL_RGB; glType = GL_UNSIGNED_SHORT_5_6_5; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0)) { // RGBA5551 glIntFormat = GL_RGBA; glFormat = GL_RGBA; glType = GL_UNSIGNED_SHORT_5_5_5_1; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0)) { // RGBA4444 glIntFormat = GL_RGBA; glFormat = GL_RGBA; glType = GL_UNSIGNED_SHORT_4_4_4_4; return true; #ifndef USE_GLES #ifdef SCUMM_LITTLE_ENDIAN } else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)) { // RGBA8888 glIntFormat = GL_RGBA; glFormat = GL_RGBA; glType = GL_UNSIGNED_INT_8_8_8_8; return true; #endif } else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)) { // RGB555 // GL_BGRA does not exist in every GLES implementation so should not be configured if // USE_GLES is set. glIntFormat = GL_RGB; glFormat = GL_BGRA; glType = GL_UNSIGNED_SHORT_1_5_5_5_REV; return true; } else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24)) { // ARGB8888 glIntFormat = GL_RGBA; glFormat = GL_BGRA; glType = GL_UNSIGNED_INT_8_8_8_8_REV; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 8, 4, 0, 12)) { // ARGB4444 glIntFormat = GL_RGBA; glFormat = GL_BGRA; glType = GL_UNSIGNED_SHORT_4_4_4_4_REV; return true; #ifdef SCUMM_BIG_ENDIAN } else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24)) { // ABGR8888 glIntFormat = GL_RGBA; glFormat = GL_RGBA; glType = GL_UNSIGNED_INT_8_8_8_8_REV; return true; #endif } else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 8, 16, 24, 0)) { // BGRA8888 glIntFormat = GL_RGBA; glFormat = GL_BGRA; glType = GL_UNSIGNED_INT_8_8_8_8; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 5, 6, 5, 0, 0, 5, 11, 0)) { // BGR565 glIntFormat = GL_RGB; glFormat = GL_BGR; glType = GL_UNSIGNED_SHORT_5_6_5; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 1, 1, 6, 11, 0)) { // BGRA5551 glIntFormat = GL_RGBA; glFormat = GL_BGRA; glType = GL_UNSIGNED_SHORT_5_5_5_1; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 0, 4, 8, 12)) { // ABGR4444 glIntFormat = GL_RGBA; glFormat = GL_RGBA; glType = GL_UNSIGNED_SHORT_4_4_4_4_REV; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 4, 8, 12, 0)) { // BGRA4444 glIntFormat = GL_RGBA; glFormat = GL_BGRA; glType = GL_UNSIGNED_SHORT_4_4_4_4; return true; #endif } else { return false; } } frac_t OpenGLGraphicsManager::getDesiredGameScreenAspect() const { const uint width = _currentState.gameWidth; const uint height = _currentState.gameHeight; if (_currentState.aspectRatioCorrection) { // In case we enable aspect ratio correction we force a 4/3 ratio. // But just for 320x200 and 640x400 games, since other games do not need // this. if ((width == 320 && height == 200) || (width == 640 && height == 400)) { return intToFrac(4) / 3; } } return intToFrac(width) / height; } void OpenGLGraphicsManager::recalculateDisplayArea() { if (!_gameScreen || _outputScreenHeight == 0) { return; } const frac_t outputAspect = intToFrac(_outputScreenWidth) / _outputScreenHeight; const frac_t desiredAspect = getDesiredGameScreenAspect(); _displayWidth = _outputScreenWidth; _displayHeight = _outputScreenHeight; // Adjust one dimension for mantaining the aspect ratio. if (outputAspect < desiredAspect) { _displayHeight = intToFrac(_displayWidth) / desiredAspect; } else if (outputAspect > desiredAspect) { _displayWidth = fracToInt(_displayHeight * desiredAspect); } // We center the screen in the middle for now. _displayX = (_outputScreenWidth - _displayWidth ) / 2; _displayY = (_outputScreenHeight - _displayHeight) / 2; } void OpenGLGraphicsManager::updateCursorPalette() { if (!_cursor || !_cursor->hasPalette()) { return; } if (_cursorPaletteEnabled) { _cursor->setPalette(0, 256, _cursorPalette); } else { _cursor->setPalette(0, 256, _gamePalette); } // We remove all alpha bits from the palette entry of the color key. // This makes sure its properly handled as color key. const Graphics::PixelFormat &hardwareFormat = _cursor->getHardwareFormat(); const uint32 aMask = (0xFF >> hardwareFormat.aLoss) << hardwareFormat.aShift; if (hardwareFormat.bytesPerPixel == 2) { uint16 *palette = (uint16 *)_cursor->getPalette() + _cursorKeyColor; *palette &= ~aMask; } else if (hardwareFormat.bytesPerPixel == 4) { uint32 *palette = (uint32 *)_cursor->getPalette() + _cursorKeyColor; *palette &= ~aMask; } else { warning("OpenGLGraphicsManager::updateCursorPalette: Unsupported pixel depth %d", hardwareFormat.bytesPerPixel); } } void OpenGLGraphicsManager::recalculateCursorScaling() { if (!_cursor || !_gameScreen) { return; } // By default we use the unscaled versions. _cursorHotspotXScaled = _cursorHotspotX; _cursorHotspotYScaled = _cursorHotspotY; _cursorWidthScaled = _cursor->getWidth(); _cursorHeightScaled = _cursor->getHeight(); // In case scaling is actually enabled we will scale the cursor according // to the game screen. if (!_cursorDontScale) { const frac_t screenScaleFactorX = intToFrac(_displayWidth) / _gameScreen->getWidth(); const frac_t screenScaleFactorY = intToFrac(_displayHeight) / _gameScreen->getHeight(); _cursorHotspotXScaled = fracToInt(_cursorHotspotXScaled * screenScaleFactorX); _cursorWidthScaled = fracToInt(_cursorWidthScaled * screenScaleFactorX); _cursorHotspotYScaled = fracToInt(_cursorHotspotYScaled * screenScaleFactorY); _cursorHeightScaled = fracToInt(_cursorHeightScaled * screenScaleFactorY); } } #ifdef USE_OSD const Graphics::Font *OpenGLGraphicsManager::getFontOSD() { return FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont); } #endif void OpenGLGraphicsManager::saveScreenshot(const Common::String &filename) const { const uint width = _outputScreenWidth; const uint height = _outputScreenHeight; // A line of a BMP image must have a size divisible by 4. // We calculate the padding bytes needed here. // Since we use a 3 byte per pixel mode, we can use width % 4 here, since // it is equal to 4 - (width * 3) % 4. (4 - (width * Bpp) % 4, is the // usual way of computing the padding bytes required). const uint linePaddingSize = width % 4; const uint lineSize = width * 3 + linePaddingSize; // Allocate memory for screenshot uint8 *pixels = new uint8[lineSize * height]; // Get pixel data from OpenGL buffer GLCALL(glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels)); // BMP stores as BGR. Since we can't assume that GL_BGR is supported we // will swap the components from the RGB we read to BGR on our own. for (uint y = height; y-- > 0;) { uint8 *line = pixels + y * lineSize; for (uint x = width; x > 0; --x, line += 3) { SWAP(line[0], line[2]); } } // Open file Common::DumpFile out; out.open(filename); // Write BMP header out.writeByte('B'); out.writeByte('M'); out.writeUint32LE(height * lineSize + 54); out.writeUint32LE(0); out.writeUint32LE(54); out.writeUint32LE(40); out.writeUint32LE(width); out.writeUint32LE(height); out.writeUint16LE(1); out.writeUint16LE(24); out.writeUint32LE(0); out.writeUint32LE(0); out.writeUint32LE(0); out.writeUint32LE(0); out.writeUint32LE(0); out.writeUint32LE(0); // Write pixel data to BMP out.write(pixels, lineSize * height); // Free allocated memory delete[] pixels; } } // End of namespace OpenGL
void OpenGLGraphicsManager::setActualScreenSize(uint width, uint height) { _outputScreenWidth = width; _outputScreenHeight = height; // Setup coordinates system. GLCALL(glViewport(0, 0, _outputScreenWidth, _outputScreenHeight)); GLCALL(glMatrixMode(GL_PROJECTION)); GLCALL(glLoadIdentity()); #ifdef USE_GLES GLCALL(glOrthof(0, _outputScreenWidth, _outputScreenHeight, 0, -1, 1)); #else GLCALL(glOrtho(0, _outputScreenWidth, _outputScreenHeight, 0, -1, 1)); #endif GLCALL(glMatrixMode(GL_MODELVIEW)); GLCALL(glLoadIdentity()); uint overlayWidth = width; uint overlayHeight = height; // WORKAROUND: We can only support surfaces up to the maximum supported // texture size. Thus, in case we encounter a physical size bigger than // this maximum texture size we will simply use an overlay as big as // possible and then scale it to the physical display size. This sounds // bad but actually all recent chips should support full HD resolution // anyway. Thus, it should not be a real issue for modern hardware. if ( overlayWidth > (uint)Texture::getMaximumTextureSize() || overlayHeight > (uint)Texture::getMaximumTextureSize()) { const frac_t outputAspect = intToFrac(_outputScreenWidth) / _outputScreenHeight; if (outputAspect > (frac_t)FRAC_ONE) { overlayWidth = Texture::getMaximumTextureSize(); overlayHeight = intToFrac(overlayWidth) / outputAspect; } else { overlayHeight = Texture::getMaximumTextureSize(); overlayWidth = fracToInt(overlayHeight * outputAspect); } } // HACK: We limit the minimal overlay size to 256x200, which is the // minimum of the dimensions of the two resolutions 256x240 (NES) and // 320x200 (many DOS games use this). This hopefully assure that our // GUI has working layouts. overlayWidth = MAX<uint>(overlayWidth, 256); overlayHeight = MAX<uint>(overlayHeight, 200); if (!_overlay || _overlay->getFormat() != _defaultFormatAlpha) { delete _overlay; _overlay = nullptr; _overlay = createTexture(_defaultFormatAlpha); assert(_overlay); // We always filter the overlay with GL_LINEAR. This assures it's // readable in case it needs to be scaled and does not affect it // otherwise. _overlay->enableLinearFiltering(true); } _overlay->allocate(overlayWidth, overlayHeight); _overlay->fill(0); #ifdef USE_OSD if (!_osd || _osd->getFormat() != _defaultFormatAlpha) { delete _osd; _osd = nullptr; _osd = createTexture(_defaultFormatAlpha); assert(_osd); // We always filter the osd with GL_LINEAR. This assures it's // readable in case it needs to be scaled and does not affect it // otherwise. _osd->enableLinearFiltering(true); } _osd->allocate(_overlay->getWidth(), _overlay->getHeight()); _osd->fill(0); #endif // Re-setup the scaling for the screen and cursor recalculateDisplayArea(); recalculateCursorScaling(); // Something changed, so update the screen change ID. ++_screenChangeID; }
OSystem::TransactionError OpenGLGraphicsManager::endGFXTransaction() { assert(_transactionMode == kTransactionActive); uint transactionError = OSystem::kTransactionSuccess; bool setupNewGameScreen = false; if ( _oldState.gameWidth != _currentState.gameWidth || _oldState.gameHeight != _currentState.gameHeight) { setupNewGameScreen = true; } #ifdef USE_RGB_COLOR if (_oldState.gameFormat != _currentState.gameFormat) { setupNewGameScreen = true; } // Check whether the requested format can actually be used. Common::List<Graphics::PixelFormat> supportedFormats = getSupportedFormats(); // In case the requested format is not usable we will fall back to CLUT8. if (Common::find(supportedFormats.begin(), supportedFormats.end(), _currentState.gameFormat) == supportedFormats.end()) { _currentState.gameFormat = Graphics::PixelFormat::createFormatCLUT8(); transactionError |= OSystem::kTransactionFormatNotSupported; } #endif do { uint requestedWidth = _currentState.gameWidth; uint requestedHeight = _currentState.gameHeight; const uint desiredAspect = getDesiredGameScreenAspect(); requestedHeight = intToFrac(requestedWidth) / desiredAspect; if (!loadVideoMode(requestedWidth, requestedHeight, #ifdef USE_RGB_COLOR _currentState.gameFormat #else Graphics::PixelFormat::createFormatCLUT8() #endif ) // HACK: This is really nasty but we don't have any guarantees of // a context existing before, which means we don't know the maximum // supported texture size before this. Thus, we check whether the // requested game resolution is supported over here. || ( _currentState.gameWidth > (uint)Texture::getMaximumTextureSize() || _currentState.gameHeight > (uint)Texture::getMaximumTextureSize())) { if (_transactionMode == kTransactionActive) { // Try to setup the old state in case its valid and is // actually different from the new one. if (_oldState.valid && _oldState != _currentState) { // Give some hints on what failed to set up. if ( _oldState.gameWidth != _currentState.gameWidth || _oldState.gameHeight != _currentState.gameHeight) { transactionError |= OSystem::kTransactionSizeChangeFailed; } #ifdef USE_RGB_COLOR if (_oldState.gameFormat != _currentState.gameFormat) { transactionError |= OSystem::kTransactionFormatNotSupported; } #endif if (_oldState.aspectRatioCorrection != _currentState.aspectRatioCorrection) { transactionError |= OSystem::kTransactionAspectRatioFailed; } if (_oldState.graphicsMode != _currentState.graphicsMode) { transactionError |= OSystem::kTransactionModeSwitchFailed; } // Roll back to the old state. _currentState = _oldState; _transactionMode = kTransactionRollback; // Try to set up the old state. continue; } } // DON'T use error(), as this tries to bring up the debug // console, which WON'T WORK now that we might no have a // proper screen. warning("OpenGLGraphicsManager::endGFXTransaction: Could not load any graphics mode!"); g_system->quit(); } // In case we reach this we have a valid state, yay. _transactionMode = kTransactionNone; _currentState.valid = true; } while (_transactionMode == kTransactionRollback); if (setupNewGameScreen) { delete _gameScreen; _gameScreen = nullptr; #ifdef USE_RGB_COLOR _gameScreen = createTexture(_currentState.gameFormat); #else _gameScreen = createTexture(Graphics::PixelFormat::createFormatCLUT8()); #endif assert(_gameScreen); if (_gameScreen->hasPalette()) { _gameScreen->setPalette(0, 256, _gamePalette); } _gameScreen->allocate(_currentState.gameWidth, _currentState.gameHeight); _gameScreen->enableLinearFiltering(_currentState.graphicsMode == GFX_LINEAR); // We fill the screen to all black or index 0 for CLUT8. if (_currentState.gameFormat.bytesPerPixel == 1) { _gameScreen->fill(0); } else { _gameScreen->fill(_gameScreen->getSurface()->format.RGBToColor(0, 0, 0)); } } // Update our display area and cursor scaling. This makes sure we pick up // aspect ratio correction and game screen changes correctly. recalculateDisplayArea(); recalculateCursorScaling(); // Something changed, so update the screen change ID. ++_screenChangeID; // Since transactionError is a ORd list of TransactionErrors this is // clearly wrong. But our API is simply broken. return (OSystem::TransactionError)transactionError; }
/** * Main text outputting routine. If a object list is specified a * multi-object is created for the whole text and a pointer to the head * of the list is returned. * @param pList Object list to add text to * @param szStr String to output * @param color Color for monochrome text * @param xPos X position of string * @param yPos Y position of string * @param hFont Which font to use * @param mode Mode flags for the string * @param sleepTime Sleep time between each character (if non-zero) */ OBJECT *ObjectTextOut(OBJECT **pList, char *szStr, int color, int xPos, int yPos, SCNHANDLE hFont, int mode, int sleepTime) { int xJustify; // x position of text after justification int yOffset; // offset to next line of text OBJECT *pFirst; // head of multi-object text list OBJECT *pChar = 0; // object ptr for the character byte c; SCNHANDLE hImg; const IMAGE *pImg; // make sure there is a linked list to add text to assert(pList); // get font pointer const FONT *pFont = (const FONT *)LockMem(hFont); // init head of text list pFirst = NULL; // get image for capital W assert(pFont->fontDef[(int)'W']); pImg = (const IMAGE *)LockMem(FROM_32(pFont->fontDef[(int)'W'])); // get height of capital W for offset to next line yOffset = FROM_16(pImg->imgHeight) & ~C16_FLAG_MASK; while (*szStr) { // x justify the text according to the mode flags xJustify = JustifyText(szStr, xPos, pFont, mode); // repeat until end of string or end of line while ((c = *szStr) != EOS_CHAR && c != LF_CHAR) { if (g_bMultiByte) { if (c & 0x80) c = ((c & ~0x80) << 8) + *++szStr; } hImg = FROM_32(pFont->fontDef[c]); if (hImg == 0) { // no image for this character // add font spacing for a space character xJustify += FROM_32(pFont->spaceSize); } else { // printable character int aniX, aniY; // char image animation offsets OBJ_INIT oi; oi.hObjImg = FROM_32(pFont->fontInit.hObjImg); oi.objFlags = FROM_32(pFont->fontInit.objFlags); oi.objID = FROM_32(pFont->fontInit.objID); oi.objX = FROM_32(pFont->fontInit.objX); oi.objY = FROM_32(pFont->fontInit.objY); oi.objZ = FROM_32(pFont->fontInit.objZ); // allocate and init a character object if (pFirst == NULL) // first time - init head of list pFirst = pChar = InitObject(&oi); // FIXME: endian issue using fontInit!!! else // chain to multi-char list pChar = pChar->pSlave = InitObject(&oi); // FIXME: endian issue using fontInit!!! // convert image handle to pointer pImg = (const IMAGE *)LockMem(hImg); // fill in character object pChar->hImg = hImg; // image def pChar->width = FROM_16(pImg->imgWidth); // width of chars bitmap pChar->height = FROM_16(pImg->imgHeight) & ~C16_FLAG_MASK; // height of chars bitmap pChar->hBits = FROM_32(pImg->hImgBits); // bitmap // check for absolute positioning if (mode & TXT_ABSOLUTE) pChar->flags |= DMA_ABS; // set characters color - only effective for mono fonts pChar->constant = color; // get Y animation offset GetAniOffset(hImg, pChar->flags, &aniX, &aniY); // set x position - ignore animation point pChar->xPos = intToFrac(xJustify); // set y position - adjust for animation point pChar->yPos = intToFrac(yPos - aniY); if (mode & TXT_SHADOW) { // we want to shadow the character OBJECT *pShad; // allocate a object for the shadow and chain to multi-char list pShad = pChar->pSlave = AllocObject(); // copy the character for a shadow CopyObject(pShad, pChar); // add shadow offsets to characters position pShad->xPos += intToFrac(FROM_32(pFont->xShadow)); pShad->yPos += intToFrac(FROM_32(pFont->yShadow)); // shadow is behind the character pShad->zPos--; // shadow is always mono pShad->flags = DMA_CNZ | DMA_CHANGED; // check for absolute positioning if (mode & TXT_ABSOLUTE) pShad->flags |= DMA_ABS; // shadow always uses first palette entry // should really alloc a palette here also ???? pShad->constant = 1; // add shadow to object list InsertObject(pList, pShad); } // add character to object list InsertObject(pList, pChar); // move to end of list if (pChar->pSlave) pChar = pChar->pSlave; // add character spacing xJustify += FROM_16(pImg->imgWidth); } // finally add the inter-character spacing xJustify += FROM_32(pFont->xSpacing); // next character in string ++szStr; } // adjust the text y position and add the inter-line spacing yPos += yOffset + FROM_32(pFont->ySpacing); // check for newline if (c == LF_CHAR) // next character in string ++szStr; } // return head of list return pFirst; }
uint32 SoundGen2GS::mixSound() { int i, b; memset(_sndBuffer, 0, BUFFER_SIZE << 1); if (!_playing || _playingSound == -1) return BUFFER_SIZE; // Handle Apple IIGS sound mixing here // TODO: Implement playing both waves in an oscillator // TODO: Implement swap-mode in an oscillator for (uint midiChan = 0; midiChan < _midiChannels.size(); midiChan++) { for (uint gsChan = 0; gsChan < _midiChannels[midiChan]._gsChannels.size(); gsChan++) { IIgsChannelInfo &channel = _midiChannels[midiChan]._gsChannels[gsChan]; if (channel.playing()) { // Only mix in actively playing channels // Frequency multiplier was 1076.0 based on tests made with MESS 0.117. // Tests made with KEGS32 averaged the multiplier to around 1045. // So this is a guess but maybe it's 1046.5... i.e. C6's frequency? double hertz = C6_FREQ * pow(SEMITONE, fracToDouble(channel.note)); channel.posAdd = doubleToFrac(hertz / getRate()); channel.vol = doubleToFrac(fracToDouble(channel.envVol) * fracToDouble(channel.chanVol) / 127.0); double tempVol = fracToDouble(channel.vol)/127.0; for (i = 0; i < IIGS_BUFFER_SIZE; i++) { b = channel.relocatedSample[fracToInt(channel.pos)]; // TODO: Find out what volume/amplification setting is loud enough // but still doesn't clip when playing many channels on it. _sndBuffer[i] += (int16) (b * tempVol * 256/4); channel.pos += channel.posAdd; if (channel.pos >= intToFrac(channel.size)) { if (channel.loop) { // Don't divide by zero on zero length samples channel.pos %= intToFrac(channel.size + (channel.size == 0)); // Probably we should loop the envelope too channel.envSeg = 0; channel.envVol = channel.startEnvVol; } else { channel.pos = channel.chanVol = 0; channel.end = true; break; } } } if (channel.envSeg < ENVELOPE_SEGMENT_COUNT) { const IIgsEnvelopeSegment &seg = channel.ins->env.seg[channel.envSeg]; // I currently assume enveloping works with the same speed as the MIDI // (i.e. with 1/60ths of a second ticks). // TODO: Check if enveloping really works with the same speed as MIDI frac_t envVolDelta = doubleToFrac(seg.inc/256.0); if (intToFrac(seg.bp) >= channel.envVol) { channel.envVol += envVolDelta; if (channel.envVol >= intToFrac(seg.bp)) { channel.envVol = intToFrac(seg.bp); channel.envSeg += 1; } } else { channel.envVol -= envVolDelta; if (channel.envVol <= intToFrac(seg.bp)) { channel.envVol = intToFrac(seg.bp); channel.envSeg += 1; } } } } } } removeStoppedSounds(); return IIGS_BUFFER_SIZE; }
/** * Fill output buffer by advancing the generators for a 1/60th of a second. * @return Number of generated samples */ uint SoundGen2GS::generateOutput() { memset(_out, 0, _outSize * 2 * 2); if (!_playing || _playingSound == -1) return _outSize * 2; int16 *p = _out; int n = _outSize; while (n--) { int outl = 0; int outr = 0; for (int k = 0; k < MAX_GENERATORS; k++) { IIgsGenerator *g = &_generators[k]; if (!g->ins) continue; const IIgsInstrumentHeader *i = g->ins; // Advance envelope int vol = fracToInt(g->a); if (g->a <= i->env[g->seg].bp) { g->a += i->env[g->seg].inc * ENVELOPE_COEF; if (g->a > i->env[g->seg].bp) { g->a = i->env[g->seg].bp; g->seg++; } } else { g->a -= i->env[g->seg].inc * ENVELOPE_COEF; if (g->a < i->env[g->seg].bp) { g->a = i->env[g->seg].bp; g->seg++; } } // TODO: Advance vibrato here. The Apple IIGS uses a LFO with // triangle wave to modulate the frequency of both oscillators. // In Apple IIGS the vibrato and the envelope are updated at the // same time, so the vibrato speed depends on ENVELOPE_COEF. // Advance oscillators int s0 = 0; int s1 = 0; if (!g->osc[0].halt) { s0 = g->osc[0].base[fracToInt(g->osc[0].p)]; g->osc[0].p += g->osc[0].pd; if ((uint)fracToInt(g->osc[0].p) >= g->osc[0].size) { g->osc[0].p -= intToFrac(g->osc[0].size); if (!g->osc[0].loop) g->osc[0].halt = 1; if (g->osc[0].swap) { g->osc[0].halt = 1; g->osc[1].halt = 0; } } } if (!g->osc[1].halt) { s1 = g->osc[1].base[fracToInt(g->osc[1].p)]; g->osc[1].p += g->osc[1].pd; if ((uint)fracToInt(g->osc[1].p) >= g->osc[1].size) { g->osc[1].p -= intToFrac(g->osc[1].size); if (!g->osc[1].loop) g->osc[1].halt = 1; if (g->osc[1].swap) { g->osc[0].halt = 0; g->osc[1].halt = 1; } } } // Take envelope and MIDI volume information into account. // Also amplify. s0 *= vol * g->vel / 127 * 80 / 256; s1 *= vol * g->vel / 127 * 80 / 256; // Select output channel. if (g->osc[0].chn) outl += s0; else outr += s0; if (g->osc[1].chn) outl += s1; else outr += s1; } if (outl > 32768) outl = 32768; if (outl < -32767) outl = -32767; if (outr > 32768) outr = 32768; if (outr < -32767) outr = -32767; *p++ = outl; *p++ = outr; } return _outSize * 2; }