// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator. // Copyright (C) 1999-2003 Forgotten // Copyright (C) 2004 Forgotten and the VBA development team // 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 2, 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, write to the Free Software Foundation, // Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "stdafx.h" #include "vba.h" #define DIRECT3D_VERSION 0x0800 #include #include #include "MainWnd.h" #include "../System.h" #include "../GBA.h" #include "../Globals.h" #include "../Text.h" #include "../gb/gbGlobals.h" #include "Reg.h" #include "resource.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #ifdef MMX extern "C" bool cpu_mmx; extern bool detectMMX(); #endif extern int Init_2xSaI(u32); extern void winlog(const char *,...); extern int systemSpeed; typedef struct _D3DTLVERTEX { float sx; /* Screen coordinates */ float sy; float sz; float rhw; /* Reciprocal of homogeneous w */ D3DCOLOR color; /* Vertex color */ float tu; /* Texture coordinates */ float tv; _D3DTLVERTEX() { } _D3DTLVERTEX(const D3DVECTOR& v, float _rhw, D3DCOLOR _color, float _tu, float _tv) { sx = v.x; sy = v.y; sz = v.z; rhw = _rhw; color = _color; tu = _tu; tv = _tv; } } D3DTLVERTEX, *LPD3DTLVERTEX; #define D3DFVF_TLVERTEX D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1 class Direct3DDisplay : public IDisplay { private: HINSTANCE d3dDLL; LPDIRECT3D8 pD3D; LPDIRECT3DDEVICE8 pDevice; LPDIRECT3DTEXTURE8 pTexture; D3DSURFACE_DESC dsdBackBuffer; D3DPRESENT_PARAMETERS dpp; D3DFORMAT screenFormat; int width; int height; bool filterDisabled; ID3DXFont *pFont; bool failed; void restoreDeviceObjects(); void invalidateDeviceObjects(); bool initializeOffscreen(int w, int h); void updateFiltering(int); public: Direct3DDisplay(); virtual ~Direct3DDisplay(); virtual bool initialize(); virtual void cleanup(); virtual void render(); virtual void checkFullScreen(); virtual void renderMenu(); virtual void clear(); virtual bool changeRenderSize(int w, int h); virtual void resize(int w, int h); virtual DISPLAY_TYPE getType() { return DIRECT_3D; }; virtual void setOption(const char *, int); virtual int selectFullScreenMode(GUID **); }; Direct3DDisplay::Direct3DDisplay() { d3dDLL = NULL; pD3D = NULL; pDevice = NULL; pTexture = NULL; pFont = NULL; screenFormat = D3DFMT_R5G6B5; width = 0; height = 0; filterDisabled = false; failed = false; } Direct3DDisplay::~Direct3DDisplay() { cleanup(); } void Direct3DDisplay::cleanup() { if(pD3D != NULL) { if(pFont) { pFont->Release(); pFont = NULL; } if(pTexture) { pTexture->Release(); pTexture = NULL; } if(pDevice) { pDevice->Release(); pDevice = NULL; } pD3D->Release(); pD3D = NULL; if(d3dDLL != NULL) { FreeLibrary(d3dDLL); d3dDLL = NULL; } } } bool Direct3DDisplay::initialize() { theApp.sizeX = 240; theApp.sizeY = 160; switch(theApp.videoOption) { case VIDEO_1X: theApp.surfaceSizeX = theApp.sizeX; theApp.surfaceSizeY = theApp.sizeY; break; case VIDEO_2X: theApp.surfaceSizeX = theApp.sizeX * 2; theApp.surfaceSizeY = theApp.sizeY * 2; break; case VIDEO_3X: theApp.surfaceSizeX = theApp.sizeX * 3; theApp.surfaceSizeY = theApp.sizeY * 3; break; case VIDEO_4X: theApp.surfaceSizeX = theApp.sizeX * 4; theApp.surfaceSizeY = theApp.sizeY * 4; break; case VIDEO_320x240: case VIDEO_640x480: case VIDEO_800x600: case VIDEO_OTHER: { RECT r; ::GetWindowRect(GetDesktopWindow(), &r); theApp.fsWidth = r.right - r.left; theApp.fsHeight = r.bottom - r.top; /* Need to fix this code later. For now, Fullscreen takes the whole screen. int scaleX = (fsWidth / sizeX); int scaleY = (fsHeight / sizeY); int min = scaleX < scaleY ? scaleX : scaleY; surfaceSizeX = sizeX * min; surfaceSizeY = sizeY * min; if(fullScreenStretch) { */ theApp.surfaceSizeX = theApp.fsWidth; theApp.surfaceSizeY = theApp.fsHeight; // } } break; } theApp.rect.left = 0; theApp.rect.top = 0; theApp.rect.right = theApp.sizeX; theApp.rect.bottom = theApp.sizeY; theApp.dest.left = 0; theApp.dest.top = 0; theApp.dest.right = theApp.surfaceSizeX; theApp.dest.bottom = theApp.surfaceSizeY; DWORD style = WS_POPUP | WS_VISIBLE; DWORD styleEx = 0; if(theApp.videoOption <= VIDEO_4X) style |= WS_OVERLAPPEDWINDOW; else styleEx = 0; if(theApp.videoOption <= VIDEO_4X) AdjustWindowRectEx(&theApp.dest, style, TRUE, styleEx); else AdjustWindowRectEx(&theApp.dest, style, FALSE, styleEx); int winSizeX = theApp.dest.right-theApp.dest.left; int winSizeY = theApp.dest.bottom-theApp.dest.top; if(theApp.videoOption > VIDEO_4X) { winSizeX = theApp.fsWidth; winSizeY = theApp.fsHeight; } int x = 0; int y = 0; if(theApp.videoOption <= VIDEO_4X) { x = theApp.windowPositionX; y = theApp.windowPositionY; } // Create a window MainWnd *pWnd = new MainWnd; theApp.m_pMainWnd = pWnd; pWnd->CreateEx(styleEx, theApp.wndClass, "VisualBoyAdvance", style, x,y,winSizeX,winSizeY, NULL, 0); if (!(HWND)*pWnd) { winlog("Error creating Window %08x\n", GetLastError()); return FALSE; } theApp.updateMenuBar(); theApp.adjustDestRect(); d3dDLL = LoadLibrary("D3D8.DLL"); LPDIRECT3D8 (WINAPI *D3DCreate)(UINT); if(d3dDLL != NULL) { D3DCreate = (LPDIRECT3D8 (WINAPI *)(UINT)) GetProcAddress(d3dDLL, "Direct3DCreate8"); if(D3DCreate == NULL) { theApp.directXMessage("Direct3DCreate8"); return FALSE; } } else { theApp.directXMessage("D3D8.DLL"); return FALSE; } pD3D = D3DCreate(120); if(pD3D == NULL) { winlog("Error creating Direct3D object\n"); return FALSE; } theApp.mode320Available = FALSE; theApp.mode640Available = FALSE; theApp.mode800Available = FALSE; D3DDISPLAYMODE mode; pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &mode); switch(mode.Format) { case D3DFMT_R8G8B8: systemColorDepth = 24; systemRedShift = 19; systemGreenShift = 11; systemBlueShift = 3; break; case D3DFMT_X8R8G8B8: systemColorDepth = 32; systemRedShift = 19; systemGreenShift = 11; systemBlueShift = 3; Init_2xSaI(32); break; case D3DFMT_R5G6B5: systemColorDepth = 16; systemRedShift = 11; systemGreenShift = 6; systemBlueShift = 0; Init_2xSaI(565); break; case D3DFMT_X1R5G5B5: systemColorDepth = 16; systemRedShift = 10; systemGreenShift = 5; systemBlueShift = 0; Init_2xSaI(555); break; default: systemMessage(0,"Unsupport D3D format %d", mode.Format); return false; } theApp.fsColorDepth = systemColorDepth; #ifdef MMX if(!theApp.disableMMX) cpu_mmx = theApp.detectMMX(); else cpu_mmx = 0; #endif screenFormat = mode.Format; // check for available fullscreen modes ZeroMemory(&dpp, sizeof(dpp)); dpp.Windowed = TRUE; dpp.BackBufferFormat = mode.Format; dpp.BackBufferCount = 1; dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; dpp.BackBufferWidth = theApp.surfaceSizeX; dpp.BackBufferHeight = theApp.surfaceSizeY; HRESULT hret = pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, pWnd->GetSafeHwnd(), D3DCREATE_SOFTWARE_VERTEXPROCESSING, &dpp, &pDevice); if(!SUCCEEDED(hret)) { winlog("Error creating Direct3DDevice %08x\n", hret); return false; } restoreDeviceObjects(); switch(systemColorDepth) { case 16: { for(int i = 0; i < 0x10000; i++) { systemColorMap16[i] = ((i & 0x1f) << systemRedShift) | (((i & 0x3e0) >> 5) << systemGreenShift) | (((i & 0x7c00) >> 10) << systemBlueShift); } } break; case 24: case 32: { for(int i = 0; i < 0x10000; i++) { systemColorMap32[i] = ((i & 0x1f) << systemRedShift) | (((i & 0x3e0) >> 5) << systemGreenShift) | (((i & 0x7c00) >> 10) << systemBlueShift); } } break; } theApp.updateFilter(); theApp.updateIFB(); if(failed) return false; pWnd->DragAcceptFiles(TRUE); return TRUE; } bool Direct3DDisplay::initializeOffscreen(int w, int h) { int size = 256; if(w > 256 || h > 256) size = 512; UINT ww = size; UINT hh = size; D3DFORMAT format = screenFormat; if(SUCCEEDED(D3DXCheckTextureRequirements(pDevice, &ww, &hh, NULL, 0, &format, D3DPOOL_MANAGED))) { if((int)ww < w || (int)hh < h) { if(theApp.filterFunction) { filterDisabled = true; theApp.filterFunction = NULL; systemMessage(0, "3D card cannot support needed texture size for filter function. Disabling it"); } } else filterDisabled = false; if(SUCCEEDED(D3DXCreateTexture(pDevice, ww, hh, 0, 0, format, D3DPOOL_MANAGED, &pTexture))) { width = w; height = h; return true; } } return false; } void Direct3DDisplay::updateFiltering(int filter) { switch(filter) { default: case 0: // point filtering pDevice->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_POINT ); pDevice->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_POINT ); pDevice->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_POINT ); break; case 1: // bilinear pDevice->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); pDevice->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); pDevice->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_POINT ); break; } } void Direct3DDisplay::restoreDeviceObjects() { // Store render target surface desc LPDIRECT3DSURFACE8 pBackBuffer; HRESULT hr; if(SUCCEEDED(hr = pDevice->GetBackBuffer( 0, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer ))) { pBackBuffer->GetDesc( &dsdBackBuffer ); pBackBuffer->Release(); } else systemMessage(0, "Failed GetBackBuffer %08x", hr); // Set up the texture pDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE ); pDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); pDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); int filter = regQueryDwordValue("d3dFilter", 0); if(filter < 0 || filter > 3) filter = 0; updateFiltering(filter); // Set miscellaneous render states pDevice->SetRenderState( D3DRS_DITHERENABLE, TRUE ); pDevice->SetRenderState( D3DRS_ZENABLE, FALSE ); // Set the projection matrix D3DXMATRIX matProj; FLOAT fAspect = ((FLOAT)dsdBackBuffer.Width) / dsdBackBuffer.Height; D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, fAspect, 1.0f, 100.0f ); pDevice->SetTransform( D3DTS_PROJECTION, &matProj ); // turn off lighting pDevice->SetRenderState( D3DRS_LIGHTING, FALSE ); if(pFont) { pFont->Release(); pFont = NULL; } // Create a D3D font using D3DX HFONT hFont = CreateFont( 14, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE, "Arial" ); D3DXCreateFont( pDevice, hFont, &pFont ); } void Direct3DDisplay::clear() { } void Direct3DDisplay::renderMenu() { checkFullScreen(); if(theApp.m_pMainWnd) theApp.m_pMainWnd->DrawMenuBar(); } void Direct3DDisplay::checkFullScreen() { // if(tripleBuffering) // pDirect3D->FlipToGDISurface(); } static void BlitRect(LPDIRECT3DDEVICE8 lpDevice, LPDIRECT3DTEXTURE8 lpSrc, float left, float top, float right, float bottom, D3DCOLOR col,float z) { // calculate rhw float rhw=1.0f/(z*990.0f+10.0f); // set up rectangle D3DTLVERTEX verts[4]; verts[0]=D3DTLVERTEX(D3DXVECTOR3(left-0.5f, top-0.5f, z),rhw,col,0.0f,0.0f); verts[1]=D3DTLVERTEX(D3DXVECTOR3(right-0.5f, top-0.5f, z),rhw,col,1.0f,0.0f); verts[2]=D3DTLVERTEX(D3DXVECTOR3(right-0.5f, bottom-0.5f, z),rhw,col,1.0f,1.0f); verts[3]=D3DTLVERTEX(D3DXVECTOR3(left-0.5f, bottom-0.5f, z),rhw,col,0.0f,1.0f); // set the texture lpDevice->SetTexture(0,lpSrc); // configure shader for vertex type lpDevice->SetVertexShader(D3DFVF_TLVERTEX); // draw the rectangle lpDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN,2,verts,sizeof(D3DTLVERTEX)); } void Direct3DDisplay::render() { if(!pDevice) return; // Test the cooperative level to see if it's okay to render if( FAILED( pDevice->TestCooperativeLevel() ) ) { return; } pDevice->Clear( 0L, NULL, D3DCLEAR_TARGET, 0x000000ff, 1.0f, 0L ); if(SUCCEEDED(pDevice->BeginScene())) { D3DLOCKED_RECT locked; if(pTexture && SUCCEEDED(pTexture->LockRect(0, &locked, NULL, 0))) { if(theApp.filterFunction) { if(systemColorDepth == 16) theApp.filterFunction(pix+theApp.filterWidth*2+4, theApp.filterWidth*2+4, (u8*)theApp.delta, (u8*)locked.pBits, locked.Pitch, theApp.filterWidth, theApp.filterHeight); else theApp.filterFunction(pix+theApp.filterWidth*4+4, theApp.filterWidth*4+4, (u8*)theApp.delta, (u8*)locked.pBits, locked.Pitch, theApp.filterWidth, theApp.filterHeight); } else { int copyX = 240; int copyY = 160; if(theApp.cartridgeType == 1) { if(gbBorderOn) { copyX = 256; copyY = 224; } else { copyX = 160; copyY = 144; } } // MMX doesn't seem to be faster to copy the data __asm { mov eax, copyX; mov ebx, copyY; mov esi, pix; mov edi, locked.pBits; mov edx, locked.Pitch; cmp systemColorDepth, 16; jnz gbaOtherColor; sub edx, eax; sub edx, eax; lea esi,[esi+2*eax+4]; shr eax, 1; gbaLoop16bit: mov ecx, eax; repz movsd; inc esi; inc esi; inc esi; inc esi; add edi, edx; dec ebx; jnz gbaLoop16bit; jmp gbaLoopEnd; gbaOtherColor: cmp systemColorDepth, 32; jnz gbaOtherColor2; sub edx, eax; sub edx, eax; sub edx, eax; sub edx, eax; lea esi, [esi+4*eax+4]; gbaLoop32bit: mov ecx, eax; repz movsd; add esi, 4; add edi, edx; dec ebx; jnz gbaLoop32bit; jmp gbaLoopEnd; gbaOtherColor2: lea eax, [eax+2*eax]; sub edx, eax; gbaLoop24bit: mov ecx, eax; shr ecx, 2; repz movsd; add edi, edx; dec ebx; jnz gbaLoop24bit; gbaLoopEnd: } } if(theApp.videoOption > VIDEO_4X && theApp.showSpeed) { char buffer[30]; if(theApp.showSpeed == 1) sprintf(buffer, "%3d%%", systemSpeed); else sprintf(buffer, "%3d%%(%d, %d fps)", systemSpeed, systemFrameSkip, theApp.showRenderedFrames); if(theApp.showSpeedTransparent) drawTextTransp((u8*)locked.pBits, locked.Pitch, theApp.rect.left+10, theApp.rect.bottom-10, buffer); else drawText((u8*)locked.pBits, locked.Pitch, theApp.rect.left+10, theApp.rect.bottom-10, buffer); } pTexture->UnlockRect(0); float scaleX = (float)theApp.surfaceSizeX / theApp.sizeX; float scaleY = (float)theApp.surfaceSizeY / theApp.sizeY; BlitRect(pDevice, pTexture, 0, 0, scaleX*256, scaleY*256, 0xffffff, 0.1f); } if(theApp.screenMessage) { if(((GetTickCount() - theApp.screenMessageTime) < 3000) && !theApp.disableStatusMessage && pFont) { D3DCOLOR color = D3DCOLOR_ARGB(255, 255, 0, 0); pFont->Begin(); RECT r; r.left = 10; r.top = dpp.BackBufferHeight - 20; r.right = dpp.BackBufferWidth - 10; r.bottom = r.top + 20; pFont->DrawText(theApp.screenMessageBuffer, -1, &r, 0, color); pFont->End(); } else { theApp.screenMessage = false; } } pDevice->EndScene(); pDevice->Present( NULL, NULL, NULL, NULL ); } } void Direct3DDisplay::invalidateDeviceObjects() { if(pFont) pFont->Release(); pFont = NULL; } void Direct3DDisplay::resize(int w, int h) { if(pDevice) { dpp.BackBufferWidth = w; dpp.BackBufferHeight = h; HRESULT hr; invalidateDeviceObjects(); if(SUCCEEDED(hr = pDevice->Reset(&dpp))) { restoreDeviceObjects(); } else systemMessage(0, "Failed device reset %08x", hr); } } bool Direct3DDisplay::changeRenderSize(int w, int h) { if(w != width || h != height) { if(pTexture) { pTexture->Release(); pTexture = NULL; } if(!initializeOffscreen(w, h)) { failed = true; return false; } } if(filterDisabled && theApp.filterFunction) theApp.filterFunction = NULL; return true; } void Direct3DDisplay::setOption(const char *option, int value) { if(!strcmp(option, "d3dFilter")) updateFiltering(value); } int Direct3DDisplay::selectFullScreenMode(GUID **) { HWND wnd = GetDesktopWindow(); RECT r; GetWindowRect(wnd, &r); int w = (r.right - r.left) & 4095; int h = (r.bottom - r.top) & 4095; HDC dc = GetDC(wnd); int c = GetDeviceCaps(dc, BITSPIXEL); ReleaseDC(wnd, dc); return (c << 24) | (w << 12) | h; } IDisplay *newDirect3DDisplay() { return new Direct3DDisplay(); }