| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| #include "../../SDL_internal.h" |
| #include "../SDL_sysvideo.h" |
| #define INCL_DOSERRORS |
| #define INCL_DOSPROCESS |
| #define INCL_DOSMODULEMGR |
| #define INCL_WIN |
| #define INCL_GPI |
| #define INCL_GPIBITMAPS /* GPI bit map functions */ |
| #include <os2.h> |
| #include <gradd.h> |
| #if 0 /* not used */ |
| #define INCL_GRE_DEVICE |
| #define INCL_GRE_DEVMISC |
| #include <pmddi.h> |
| #endif |
| #include "SDL_os2output.h" |
| #include "SDL_os2video.h" |
| |
| typedef struct _VODATA { |
| PVOID pBuffer; |
| HRGN hrgnVisible; |
| ULONG ulBPP; |
| ULONG ulScanLineSize; |
| ULONG ulWidth; |
| ULONG ulHeight; |
| ULONG ulScreenHeight; |
| ULONG ulScreenBytesPerLine; |
| RECTL rectlWin; |
| |
| PRECTL pRectl; |
| ULONG cRectl; |
| PBLTRECT pBltRect; |
| ULONG cBltRect; |
| } VODATA; |
| |
| static BOOL voQueryInfo(PVIDEOOUTPUTINFO pInfo); |
| static PVODATA voOpen(); |
| static VOID voClose(PVODATA pVOData); |
| static BOOL voSetVisibleRegion(PVODATA pVOData, HWND hwnd, |
| SDL_DisplayMode *pSDLDisplayMode, |
| HRGN hrgnShape, BOOL fVisible); |
| static PVOID voVideoBufAlloc(PVODATA pVOData, ULONG ulWidth, ULONG ulHeight, |
| ULONG ulBPP, ULONG fccColorEncoding, |
| PULONG pulScanLineSize); |
| static VOID voVideoBufFree(PVODATA pVOData); |
| static BOOL voUpdate(PVODATA pVOData, HWND hwnd, SDL_Rect *pSDLRects, |
| ULONG cSDLRects); |
| |
| OS2VIDEOOUTPUT voVMan = { |
| voQueryInfo, |
| voOpen, |
| voClose, |
| voSetVisibleRegion, |
| voVideoBufAlloc, |
| voVideoBufFree, |
| voUpdate |
| }; |
| |
| |
| static HMODULE hmodVMan = NULLHANDLE; |
| static FNVMIENTRY *pfnVMIEntry = NULL; |
| static ULONG ulVRAMAddress = 0; |
| |
| VOID APIENTRY ExitVMan(VOID) |
| { |
| if ( ( ulVRAMAddress != 0 ) && ( hmodVMan != NULLHANDLE ) ) |
| { |
| pfnVMIEntry( 0, VMI_CMD_TERMPROC, NULL, NULL ); |
| DosFreeModule( hmodVMan ); |
| } |
| |
| DosExitList( EXLST_EXIT, (PFNEXITLIST)NULL ); |
| } |
| |
| static BOOL _vmanInit() |
| { |
| ULONG ulRC; |
| CHAR acBuf[255]; |
| INITPROCOUT stInitProcOut; |
| |
| if ( hmodVMan != NULLHANDLE ) |
| // Already was initialized. |
| return TRUE; |
| |
| // Load vman.dll |
| ulRC = DosLoadModule( acBuf, sizeof(acBuf), "VMAN", &hmodVMan ); |
| if ( ulRC != NO_ERROR ) |
| { |
| debug( "Could not load VMAN.DLL, rc = %u : %s", ulRC, &acBuf ); |
| hmodVMan = NULLHANDLE; |
| return FALSE; |
| } |
| |
| // Get VMIEntry. |
| ulRC = DosQueryProcAddr( hmodVMan, 0L, "VMIEntry", (PFN *)&pfnVMIEntry ); |
| if ( ulRC != NO_ERROR ) |
| { |
| debug( "Could not query address of pfnVMIEntry func. of VMAN.DLL, " |
| "rc = %u", ulRC ); |
| DosFreeModule( hmodVMan ); |
| hmodVMan = NULLHANDLE; |
| return FALSE; |
| } |
| |
| // VMAN initialization. |
| stInitProcOut.ulLength = sizeof(stInitProcOut); |
| ulRC = pfnVMIEntry( 0, VMI_CMD_INITPROC, NULL, &stInitProcOut ); |
| if ( ulRC != RC_SUCCESS ) |
| { |
| debug( "Could not initialize VMAN for this process" ); |
| pfnVMIEntry = NULL; |
| DosFreeModule( hmodVMan ); |
| hmodVMan = NULLHANDLE; |
| return FALSE; |
| } |
| |
| // Store video memory virtual address. |
| ulVRAMAddress = stInitProcOut.ulVRAMVirt; |
| // We use exit list for VMI_CMD_TERMPROC. |
| if ( DosExitList( EXLST_ADD | 0x00001000, (PFNEXITLIST)ExitVMan ) |
| != NO_ERROR ) |
| debug( "DosExitList() failed" ); |
| |
| return TRUE; |
| } |
| |
| static PRECTL _getRectlArray(PVODATA pVOData, ULONG cRects) |
| { |
| PRECTL pRectl; |
| |
| if ( pVOData->cRectl >= cRects ) |
| return pVOData->pRectl; |
| |
| pRectl = SDL_realloc( pVOData->pRectl, cRects * sizeof(RECTL) ); |
| if ( pRectl == NULL ) |
| return NULL; |
| |
| pVOData->pRectl = pRectl; |
| pVOData->cRectl = cRects; |
| return pRectl; |
| } |
| |
| static PBLTRECT _getBltRectArray(PVODATA pVOData, ULONG cRects) |
| { |
| PBLTRECT pBltRect; |
| |
| if ( pVOData->cBltRect >= cRects ) |
| return pVOData->pBltRect; |
| |
| pBltRect = SDL_realloc( pVOData->pBltRect, cRects * sizeof(BLTRECT) ); |
| if ( pBltRect == NULL ) |
| return NULL; |
| |
| pVOData->pBltRect = pBltRect; |
| pVOData->cBltRect = cRects; |
| return pBltRect; |
| } |
| |
| |
| static BOOL voQueryInfo(PVIDEOOUTPUTINFO pInfo) |
| { |
| ULONG ulRC; |
| GDDMODEINFO sCurModeInfo; |
| |
| if ( !_vmanInit() ) |
| return FALSE; |
| |
| // Query current (desktop) mode. |
| ulRC = pfnVMIEntry( 0, VMI_CMD_QUERYCURRENTMODE, NULL, &sCurModeInfo ); |
| if ( ulRC != RC_SUCCESS ) |
| { |
| debug( "Could not query desktop video mode." ); |
| return FALSE; |
| } |
| |
| pInfo->ulBPP = sCurModeInfo.ulBpp; |
| pInfo->ulHorizResolution = sCurModeInfo.ulHorizResolution; |
| pInfo->ulVertResolution = sCurModeInfo.ulVertResolution; |
| pInfo->ulScanLineSize = sCurModeInfo.ulScanLineSize; |
| pInfo->fccColorEncoding = sCurModeInfo.fccColorEncoding; |
| |
| return TRUE; |
| } |
| |
| static PVODATA voOpen() |
| { |
| PVODATA pVOData; |
| |
| if ( !_vmanInit() ) |
| return NULL; |
| |
| pVOData = SDL_calloc( 1, sizeof(VODATA) ); |
| if ( pVOData == NULL ) |
| { |
| SDL_OutOfMemory(); |
| return NULL; |
| } |
| |
| return pVOData; |
| } |
| |
| static VOID voClose(PVODATA pVOData) |
| { |
| if ( pVOData->pRectl != NULL ) |
| SDL_free( pVOData->pRectl ); |
| |
| if ( pVOData->pBltRect != NULL ) |
| SDL_free( pVOData->pBltRect ); |
| |
| voVideoBufFree( pVOData ); |
| } |
| |
| static BOOL voSetVisibleRegion(PVODATA pVOData, HWND hwnd, |
| SDL_DisplayMode *pSDLDisplayMode, |
| HRGN hrgnShape, BOOL fVisible) |
| { |
| HPS hps; |
| BOOL fSuccess = FALSE; |
| |
| hps = WinGetPS( hwnd ); |
| |
| if ( pVOData->hrgnVisible != NULLHANDLE ) |
| { |
| GpiDestroyRegion( hps, pVOData->hrgnVisible ); |
| pVOData->hrgnVisible = NULLHANDLE; |
| } |
| |
| if ( fVisible ) |
| { |
| // Query visible rectangles |
| |
| pVOData->hrgnVisible = GpiCreateRegion( hps, 0, NULL ); |
| if ( pVOData->hrgnVisible == NULLHANDLE ) |
| { |
| SDL_SetError( "GpiCreateRegion() failed" ); |
| } |
| else |
| { |
| if ( WinQueryVisibleRegion( hwnd, pVOData->hrgnVisible ) == RGN_ERROR ) |
| { |
| GpiDestroyRegion( hps, pVOData->hrgnVisible ); |
| pVOData->hrgnVisible = NULLHANDLE; |
| } |
| else |
| { |
| if ( hrgnShape != NULLHANDLE ) |
| GpiCombineRegion( hps, pVOData->hrgnVisible, pVOData->hrgnVisible, |
| hrgnShape, CRGN_AND ); |
| fSuccess = TRUE; |
| } |
| } |
| |
| WinQueryWindowRect( hwnd, &pVOData->rectlWin ); |
| WinMapWindowPoints( hwnd, HWND_DESKTOP, (PPOINTL)&pVOData->rectlWin, 2 ); |
| |
| if ( pSDLDisplayMode != NULL ) |
| { |
| pVOData->ulScreenHeight = pSDLDisplayMode->h; |
| pVOData->ulScreenBytesPerLine = |
| ((PMODEDATA)pSDLDisplayMode->driverdata)->ulScanLineBytes; |
| } |
| } |
| |
| WinReleasePS( hps ); |
| |
| return fSuccess; |
| } |
| |
| static PVOID voVideoBufAlloc(PVODATA pVOData, ULONG ulWidth, ULONG ulHeight, |
| ULONG ulBPP, ULONG fccColorEncoding, |
| PULONG pulScanLineSize) |
| { |
| ULONG ulRC; |
| ULONG ulScanLineSize = ulWidth * (ulBPP >> 3); |
| |
| // Destroy previous buffer. |
| voVideoBufFree( pVOData ); |
| |
| if ( ( ulWidth == 0 ) || ( ulHeight == 0 ) || ( ulBPP == 0 ) ) |
| return NULL; |
| |
| // Bytes per line. |
| ulScanLineSize = ( ulScanLineSize + 3 ) & ~3; /* 4-byte aligning */ |
| *pulScanLineSize = ulScanLineSize; |
| |
| ulRC = DosAllocMem( &pVOData->pBuffer, |
| (ulHeight * ulScanLineSize) + sizeof(ULONG), |
| PAG_COMMIT | PAG_EXECUTE | PAG_READ | PAG_WRITE ); |
| if ( ulRC != NO_ERROR ) |
| { |
| debug( "DosAllocMem(), rc = %u", ulRC ); |
| return NULL; |
| } |
| |
| pVOData->ulBPP = ulBPP; |
| pVOData->ulScanLineSize = ulScanLineSize; |
| pVOData->ulWidth = ulWidth; |
| pVOData->ulHeight = ulHeight; |
| |
| return pVOData->pBuffer; |
| } |
| |
| static VOID voVideoBufFree(PVODATA pVOData) |
| { |
| ULONG ulRC; |
| |
| if ( pVOData->pBuffer == NULL ) |
| return; |
| |
| ulRC = DosFreeMem( pVOData->pBuffer ); |
| if ( ulRC != NO_ERROR ) |
| debug( "DosFreeMem(), rc = %u", ulRC ); |
| else |
| pVOData->pBuffer = NULL; |
| } |
| |
| static BOOL voUpdate(PVODATA pVOData, HWND hwnd, SDL_Rect *pSDLRects, |
| ULONG cSDLRects) |
| { |
| PRECTL prectlDst, prectlScan; |
| HPS hps; |
| HRGN hrgnUpdate; |
| RGNRECT rgnCtl; |
| SDL_Rect stSDLRectDef; |
| BMAPINFO bmiSrc; |
| BMAPINFO bmiDst; |
| PPOINTL pptlSrcOrg; |
| PBLTRECT pbrDst; |
| HWREQIN sHWReqIn; |
| BITBLTINFO sBitbltInfo = { 0 }; |
| ULONG ulIdx; |
| // RECTL rectlScreenUpdate; |
| |
| if ( pVOData->pBuffer == NULL ) |
| return FALSE; |
| |
| if ( pVOData->hrgnVisible == NULLHANDLE ) |
| return TRUE; |
| |
| bmiSrc.ulLength = sizeof(BMAPINFO); |
| bmiSrc.ulType = BMAP_MEMORY; |
| bmiSrc.ulWidth = pVOData->ulWidth; |
| bmiSrc.ulHeight = pVOData->ulHeight; |
| bmiSrc.ulBpp = pVOData->ulBPP; |
| bmiSrc.ulBytesPerLine = pVOData->ulScanLineSize; |
| bmiSrc.pBits = (PBYTE)pVOData->pBuffer; |
| |
| bmiDst.ulLength = sizeof(BMAPINFO); |
| bmiDst.ulType = BMAP_VRAM; |
| bmiDst.pBits = (PBYTE)ulVRAMAddress; |
| bmiDst.ulWidth = bmiSrc.ulWidth; |
| bmiDst.ulHeight = bmiSrc.ulHeight; |
| bmiDst.ulBpp = bmiSrc.ulBpp; |
| bmiDst.ulBytesPerLine = pVOData->ulScreenBytesPerLine; |
| |
| // List of update rectangles. This is the intersection of requested |
| // rectangles and visible rectangles. |
| |
| if ( cSDLRects == 0 ) |
| { |
| // Full update requested. |
| stSDLRectDef.x = 0; |
| stSDLRectDef.y = 0; |
| stSDLRectDef.w = bmiSrc.ulWidth; |
| stSDLRectDef.h = bmiSrc.ulHeight; |
| pSDLRects = &stSDLRectDef; |
| cSDLRects = 1; |
| } |
| |
| // Make list of destionation rectangles (prectlDst) list from the source |
| // list (prectl). |
| prectlDst = _getRectlArray( pVOData, cSDLRects ); |
| if ( prectlDst == NULL ) |
| { |
| debug( "Not enough memory" ); |
| return FALSE; |
| } |
| prectlScan = prectlDst; |
| for( ulIdx = 0; ulIdx < cSDLRects; ulIdx++, pSDLRects++, prectlScan++ ) |
| { |
| prectlScan->xLeft = pSDLRects->x; |
| prectlScan->yTop = pVOData->ulHeight - pSDLRects->y; |
| prectlScan->xRight = prectlScan->xLeft + pSDLRects->w; |
| prectlScan->yBottom = prectlScan->yTop - pSDLRects->h; |
| } |
| |
| hps = WinGetPS( hwnd ); |
| if ( hps == NULLHANDLE ) |
| return FALSE; |
| |
| // Make destination region to update. |
| hrgnUpdate = GpiCreateRegion( hps, cSDLRects, prectlDst ); |
| // "AND" on visible and destination regions, result is region to update. |
| GpiCombineRegion( hps, hrgnUpdate, hrgnUpdate, pVOData->hrgnVisible, |
| CRGN_AND ); |
| |
| // Get rectangles of the region to update. |
| rgnCtl.ircStart = 1; |
| rgnCtl.crc = 0; |
| rgnCtl.ulDirection = 1; |
| rgnCtl.crcReturned = 0; |
| GpiQueryRegionRects( hps, hrgnUpdate, NULL, &rgnCtl, NULL ); |
| if ( rgnCtl.crcReturned == 0 ) |
| { |
| GpiDestroyRegion( hps, hrgnUpdate ); |
| WinReleasePS( hps ); |
| return TRUE; |
| } |
| // We don't need prectlDst, use it again to store update regions. |
| prectlDst = _getRectlArray( pVOData, rgnCtl.crcReturned ); |
| if ( prectlDst == NULL ) |
| { |
| debug( "Not enough memory" ); |
| GpiDestroyRegion( hps, hrgnUpdate ); |
| WinReleasePS( hps ); |
| return FALSE; |
| } |
| rgnCtl.ircStart = 1; |
| rgnCtl.crc = rgnCtl.crcReturned; |
| rgnCtl.ulDirection = 1; |
| GpiQueryRegionRects( hps, hrgnUpdate, NULL, &rgnCtl, prectlDst ); |
| GpiDestroyRegion( hps, hrgnUpdate ); |
| WinReleasePS( hps ); |
| cSDLRects = rgnCtl.crcReturned; |
| // Now cRect/prectlDst is a list of regions in window (update && visible). |
| |
| // Make lists for blitting from update regions. |
| |
| pbrDst = _getBltRectArray( pVOData, cSDLRects ); |
| if ( pbrDst == NULL ) |
| { |
| debug( "Not enough memory" ); |
| return FALSE; |
| } |
| |
| prectlScan = prectlDst; |
| pptlSrcOrg = (PPOINTL)prectlDst; // Yes, this memory block will be used again. |
| for( ulIdx = 0; ulIdx < cSDLRects; ulIdx++, prectlScan++, pptlSrcOrg++ ) |
| { |
| pbrDst[ulIdx].ulXOrg = pVOData->rectlWin.xLeft + prectlScan->xLeft; |
| pbrDst[ulIdx].ulYOrg = pVOData->ulScreenHeight - |
| ( pVOData->rectlWin.yBottom + prectlScan->yTop ); |
| pbrDst[ulIdx].ulXExt = prectlScan->xRight - prectlScan->xLeft; |
| pbrDst[ulIdx].ulYExt = prectlScan->yTop - prectlScan->yBottom; |
| pptlSrcOrg->x = prectlScan->xLeft; |
| pptlSrcOrg->y = bmiSrc.ulHeight - prectlScan->yTop; |
| } |
| pptlSrcOrg = (PPOINTL)prectlDst; |
| |
| // Request HW |
| sHWReqIn.ulLength = sizeof(HWREQIN); |
| sHWReqIn.ulFlags = REQUEST_HW; |
| sHWReqIn.cScrChangeRects = 1; |
| sHWReqIn.arectlScreen = &pVOData->rectlWin; |
| if ( pfnVMIEntry( 0, VMI_CMD_REQUESTHW, &sHWReqIn, NULL ) != RC_SUCCESS ) |
| { |
| debug( "pfnVMIEntry(,VMI_CMD_REQUESTHW,,) failed" ); |
| sHWReqIn.cScrChangeRects = 0; // for fail signal only. |
| } |
| else |
| { |
| RECTL rclSrcBounds; |
| |
| rclSrcBounds.xLeft = 0; |
| rclSrcBounds.yBottom = 0; |
| rclSrcBounds.xRight = bmiSrc.ulWidth; |
| rclSrcBounds.yTop = bmiSrc.ulHeight; |
| |
| sBitbltInfo.ulLength = sizeof(BITBLTINFO); |
| sBitbltInfo.ulBltFlags = BF_DEFAULT_STATE | BF_ROP_INCL_SRC | BF_PAT_HOLLOW; |
| sBitbltInfo.cBlits = cSDLRects; |
| sBitbltInfo.ulROP = ROP_SRCCOPY; |
| sBitbltInfo.pSrcBmapInfo = &bmiSrc; |
| sBitbltInfo.pDstBmapInfo = &bmiDst; |
| sBitbltInfo.prclSrcBounds = &rclSrcBounds; |
| sBitbltInfo.prclDstBounds = &pVOData->rectlWin; |
| sBitbltInfo.aptlSrcOrg = pptlSrcOrg; |
| sBitbltInfo.abrDst = pbrDst; |
| |
| // Screen update. |
| if ( pfnVMIEntry( 0, VMI_CMD_BITBLT, &sBitbltInfo, NULL ) != RC_SUCCESS ) |
| { |
| debug( "pfnVMIEntry(,VMI_CMD_BITBLT,,) failed" ); |
| sHWReqIn.cScrChangeRects = 0; // for fail signal only. |
| } |
| |
| // Release HW. |
| sHWReqIn.ulFlags = 0; |
| if ( pfnVMIEntry( 0, VMI_CMD_REQUESTHW, &sHWReqIn, NULL ) != RC_SUCCESS ) |
| debug( "pfnVMIEntry(,VMI_CMD_REQUESTHW,,) failed" ); |
| } |
| |
| return sHWReqIn.cScrChangeRects != 0; |
| } |