#include "config.h" #include "screen.h" #include "render_direct3d.h" #include #include #include extern "C" HWND hWndMain; extern "C" { #include "colours.h" #include "log.h" } typedef D3DXMATRIX* (WINAPI* D3DXMatrixLookAtLHFunc)(D3DXMATRIX*, CONST D3DXVECTOR3*, CONST D3DXVECTOR3*, CONST D3DXVECTOR3*); typedef D3DXMATRIX* (WINAPI* D3DXMatrixPerspectiveFovLHFunc)(D3DXMATRIX*, FLOAT, FLOAT, FLOAT, FLOAT); typedef D3DXMATRIX* (WINAPI* D3DXMatrixRotationYFunc)(D3DXMATRIX*, FLOAT); // D3D declarations static LPDIRECT3D9 d3d; static LPDIRECT3DDEVICE9 d3d_device; static LPDIRECT3DVERTEXBUFFER9 vertex_buffer = NULL; static LPDIRECT3DTEXTURE9 texture_buffer = NULL; static D3DPRESENT_PARAMETERS d3dpp; struct CUSTOMVERTEX {FLOAT X, Y, Z; DWORD COLOR; FLOAT U, V;}; #define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1) static float const pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164062862f; static float texturehorizclip = 0.0f; static float texturevertclip = 0.0f; static HMODULE hD3DX = NULL; static D3DXMatrixLookAtLHFunc D3DXMatrixLookAtLHPtr = NULL; static D3DXMatrixPerspectiveFovLHFunc D3DXMatrixPerspectiveFovLHPtr = NULL; static D3DXMatrixRotationYFunc D3DXMatrixRotationYPtr = NULL; extern "C" void startupdirect3d(int screenwidth, int screenheight, BOOL windowed, FRAMEPARAMS *fp) { // look for the newest, oldest, then generic versions if ((hD3DX = LoadLibrary("d3dx9_42.dll")) != NULL || (hD3DX = LoadLibrary("d3dx9_24.dll")) != NULL || (hD3DX = LoadLibrary("d3dx9.dll")) != NULL) { D3DXMatrixLookAtLHPtr = (D3DXMatrixLookAtLHFunc) GetProcAddress(hD3DX, "D3DXMatrixLookAtLH"); D3DXMatrixPerspectiveFovLHPtr = (D3DXMatrixPerspectiveFovLHFunc) GetProcAddress(hD3DX, "D3DXMatrixPerspectiveFovLH"); D3DXMatrixRotationYPtr = (D3DXMatrixRotationYFunc) GetProcAddress(hD3DX, "D3DXMatrixRotationY"); // unlikely failure to load function pointers if (D3DXMatrixLookAtLHPtr == NULL || D3DXMatrixPerspectiveFovLHPtr == NULL || D3DXMatrixRotationYPtr == NULL) { FreeLibrary(hD3DX); hD3DX = NULL; Log_print("Extended Direct3D functions disabled - load failure"); } } else { Log_print("Extended Direct3D functions disabled - updated DirectX runtime needed"); } d3d = Direct3DCreate9(D3D_SDK_VERSION); initpresentparams(screenwidth, screenheight, windowed); // create the D3D device d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWndMain, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3d_device); initdevice(fp); return; } // Function used when we need to change present params void resetdevice(int screenwidth, int screenheight, BOOL windowed, FRAMEPARAMS *fp) { texture_buffer->Release(); // close and release the texture initpresentparams(screenwidth, screenheight, windowed); d3d_device->Reset(&d3dpp); initdevice(fp); return; } void initpresentparams(int screenwidth, int screenheight, BOOL windowed) { ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.Windowed = windowed; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.hDeviceWindow = hWndMain; d3dpp.BackBufferWidth = screenwidth; d3dpp.BackBufferHeight = screenheight; d3dpp.EnableAutoDepthStencil = TRUE; d3dpp.AutoDepthStencilFormat = D3DFMT_D16; // Only need this in fullscreen mode. if (!windowed) { d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8; } return; } void initdevice(FRAMEPARAMS *fp) { UINT texturesize; D3DTEXTUREFILTERTYPE filtertype; // compute a horizontal & vertical offset // with compensation for possible cropping & stretching int clip_width = fp->view.right - fp->view.left; int clip_height = fp->view.bottom - fp->view.top; // set the type of interpolation filtering selected by the user // only bilinear or none are available. if (fp->filter == BILINEAR) filtertype = D3DTEXF_LINEAR; else filtertype = D3DTEXF_POINT; d3d_device->SetRenderState(D3DRS_LIGHTING, FALSE); // turn off the 3D lighting d3d_device->SetRenderState(D3DRS_ZENABLE, TRUE); // turn on the z-buffer d3d_device->SetSamplerState( 0, D3DSAMP_MAGFILTER, filtertype); d3d_device->SetFVF(CUSTOMFVF); // In Direct3D we paint the Atari screen onto a texture that overlays a vertex buffer polygon. // Although most newer graphics cards support rectangular textures of variable size, // for compatibility and performance reasons, it is better that the texture be square with // sides that are a power of two. Since the size of the bitmap changes depending // on the scanline mode, we set that here. We also set the clipping parameters here // that we'll use in the vertex buffer init to only map the drawn portion of the // texture to the vertex buffer. switch (fp->scanlinemode) { case NONE: // we are just mapping the standard 320x240 Atari screen bitmap. texturesize = 512; texturehorizclip = clip_width / (float)texturesize; texturevertclip = clip_height / (float)texturesize; break; case LOW: // LOW res scanlines produces a 320x480 pixel bitmap texturesize = 512; texturehorizclip = clip_width / (float)texturesize; texturevertclip = (clip_height * 2 + 1) / (float)texturesize; break; case MEDIUM: // MEDIUM res scanlines produces a 320x720 pixel bitmap texturesize = 1024; texturehorizclip = clip_width / (float)texturesize; texturevertclip = (clip_height * 3 + 2) / (float)texturesize; break; case HIGH: // HIGH res canlines produces a 320x960 pixel bitmap texturesize = 1024; texturehorizclip = clip_width / (float)texturesize; texturevertclip = (clip_height * 4 + 3) / (float)texturesize; break; } // now lets go ahead and create a blank texture of the appropriate dimensions d3d_device->CreateTexture(texturesize, texturesize, 0, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &texture_buffer, NULL ); return; } // this function creates the 2D vertex buffer polygon and attaches the texture to it void init_vertices(float x, float y, float z) { // Create the vertices. // What we're actually doing here is specifying two right triangles // attached to one another to form a wireframe rectangle. We also specify // that our texture (the screen) should be mapped to extend to the edges // of this rectangle. This is basically how we create a 2D surface with // a 3D toolset. struct CUSTOMVERTEX vertex_def[] = { {-x, y, z, 0xffffffff, 0, 0,}, {x, y, z, 0xffffffff, texturehorizclip, 0,}, {-x, -y, -z, 0xffffffff, 0, texturevertclip,}, {x, -y, -z, 0xffffffff, texturehorizclip, texturevertclip,}, }; d3d_device->CreateVertexBuffer(4*sizeof(CUSTOMVERTEX), 0, CUSTOMFVF, D3DPOOL_MANAGED, &vertex_buffer, NULL); VOID* pVoid; // a void pointer // lock vertex_buffer and load the vertices into it vertex_buffer->Lock(0, 0, (void**)&pVoid, 0); memcpy(pVoid, vertex_def, sizeof(vertex_def)); vertex_buffer->Unlock(); return; } // this is the function used to render each frame void refreshv_direct3d(UBYTE *scr_ptr, FRAMEPARAMS *fp) { // clear the contents of the vertex buffer polygon and make it black d3d_device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); d3d_device->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); // If the caller has set fp->d3dRefresh to true it means that the vertex // buffer has changed (usually because the window or texture size has changed). // In these cases we will re-init the vertices prior to building the frame. if (fp->d3dRefresh) { if (hD3DX != NULL) { init_vertices(fp->d3dWidth, 0, fp->d3dHeight); } else { init_vertices(fp->d3dWidth, fp->d3dHeight, 0); } fp->d3dRefresh = FALSE; } // We can now unlock the backbuffer and start to draw our frame d3d_device->BeginScene(); // refresh the texture buffer for the frame // this function call actually draws the contents of the Atari screen buffer // to the Direct3D backbuffer. refresh_frame(scr_ptr, fp); // set the texture d3d_device->SetTexture(0, texture_buffer); // select the vertex buffer to display d3d_device->SetStreamSource(0, vertex_buffer, 0, sizeof(CUSTOMVERTEX)); //*********************************************************************** // This next section supports the 3D screensaver and tilt functions by // using some simple matrix tranformations on the vertex buffer polygon. // Note: If you want to remove this 3D screensaver effect and operate // in a more purely 2D view, change the init_vertices statement above to // init_vertices(fp->d3dWidth, fp->d3dHeight, 0); // and remove all statements from here to the end of this // 3D Matrix Transformations section. //*********************************************************************** if (hD3DX != NULL) { D3DXMATRIX matView; static float index = pi; // rotation amount static float cy = 2.41f; // amount of zoom static float cz = 0.00001f; // amount of tilt static float by = 0.0; if (fp->screensaver) { fp->tiltlevel = TILTLEVEL0; // make sure tiltlevel is disabled // this transformation creates and animates the 3D screensaver if (cy > 1.5f) cy -= 0.002f; // zoom if (cz < 2.2) cz += 0.005f; // tilt index+=0.005f; // rotation index } else if (fp->tiltlevel != TILTLEVEL0) { index = pi; switch (fp->tiltlevel) { case TILTLEVEL1: if (cz < 1.0) cz += 0.012f; // tilt if (by > -0.2f) by -= 0.003f; // slide up break; case TILTLEVEL2: if (cz < 1.5) cz += 0.016f; // tilt if (cy > 2.3f) cy -= 0.004f; // zoom break; case TILTLEVEL3: if (cz < 1.8) cz += 0.012f; // tilt if (cy > 2.1f) cy -= 0.008f; // zoom if (by > -0.25f) by -= 0.002f; // slide up break; } } else { // this transformation creates a standard 2D view index = pi; cy = 2.41f; cz = 0.00001f; by = 0.0; } // Create the transform matView D3DXMatrixLookAtLHPtr(&matView, &D3DXVECTOR3 (0.0f, cy, cz), // tilt & zoom the camera &D3DXVECTOR3 (0.0f, by, 0.0f), // raise & lower the camera &D3DXVECTOR3 (0.0f, 1.0, 0.0f)); // flip the camera d3d_device->SetTransform(D3DTS_VIEW, &matView); // set the view transform to matView // set the projection transform D3DXMATRIX matProjection; // the projection transform matrix D3DXMatrixPerspectiveFovLHPtr(&matProjection, D3DXToRadian(45), // the horizontal field of view 1.0f, // aspect ratio 1.0f, // the near view-plane 100.0f); // the far view-plane d3d_device->SetTransform(D3DTS_PROJECTION, &matProjection); // set the projection // set the world transform D3DXMATRIX matRotateY; // a matrix to store the rotation for each triangle D3DXMatrixRotationYPtr(&matRotateY, index); // the rotation matrix d3d_device->SetTransform(D3DTS_WORLD, &(matRotateY)); // set the world transform } //*********************************************************************** // End 3D transformations //*********************************************************************** // draw the fully rendered and transformed surface to the backbuffer d3d_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); // We now lock the backbuffer d3d_device->EndScene(); // Now tell Direct3D to flip the image to the screen. d3d_device->Present(NULL, NULL, NULL, NULL); return; } // shutdown the Direct3D engine extern "C" void shutdowndirect3d(void) { vertex_buffer->Release(); // close and release the vertex buffer texture_buffer->Release(); // close and release the texture d3d_device->Release(); // close and release the 3D device d3d->Release(); // close and release Direct3D if (hD3DX != NULL) { FreeLibrary(hD3DX); hD3DX = NULL; } return; } // Copy the raw screen buffer data to the texture buffer void refresh_frame(UBYTE *scr_ptr, FRAMEPARAMS *fp) { int pixel = 0; int i, x, y; int texturewidth; // calculate texture width int viewwidth = fp->view.right - fp->view.left; D3DLOCKED_RECT d3dlr; texture_buffer->LockRect(0, &d3dlr, 0, 0); DWORD* pixels = (DWORD*)d3dlr.pBits; texturewidth = d3dlr.Pitch / 4; // copy screen buffer to the texture scr_ptr += fp->view.top * Screen_WIDTH + fp->view.left; for (y = fp->view.top; y < fp->view.bottom; y++) { for (x = fp->view.left; x < fp->view.right; x++) { if (y < 0 || y >= Screen_HEIGHT || x < 0 || x >= Screen_WIDTH) pixels[pixel] = Colours_table[0]; else pixels[pixel] = Colours_table[*scr_ptr]; for (i = 0; i < fp->scanlinemode; i++) { pixels[pixel + i * texturewidth] = pixels[pixel]; } scr_ptr++; pixel++; } scr_ptr += Screen_WIDTH - viewwidth; pixel += texturewidth - viewwidth; pixel += texturewidth * fp->scanlinemode; } texture_buffer->UnlockRect(0); }