mirror of
https://github.com/OpenDriver2/REDRIVER2.git
synced 2024-11-23 02:42:38 +01:00
2256 lines
54 KiB
C
2256 lines
54 KiB
C
#include "EMULATOR.H"
|
|
|
|
#include "EMULATOR_VERSION.H"
|
|
#include "EMULATOR_GLOBALS.H"
|
|
#include "EMULATOR_PRIVATE.H"
|
|
|
|
#include "CRASHHANDLER.H"
|
|
|
|
#include "LIBETC.H"
|
|
#include "LIBGPU.H"
|
|
|
|
//#include <stdio.h>
|
|
//#include <string.h>
|
|
#if !defined(__ANDROID__)
|
|
//#include <thread>
|
|
#endif
|
|
#include <assert.h>
|
|
|
|
#define FIXED_TIME_STEP_NTSC (1.0/60.0) // 60 FPS clock
|
|
#define FIXED_TIME_STEP_PAL (1.0/50.0) // 50 FPS clock
|
|
|
|
#define SWAP_INTERVAL 1
|
|
|
|
#define PSX_SCREEN_ASPECT (240.0f / 320.0f) // PSX screen is mapped always to this aspect
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <SDL.h>
|
|
|
|
#include "EMULATOR_TIMER.H"
|
|
|
|
SDL_Window* g_window = NULL;
|
|
|
|
SysCounter counters[3] = { 0 };
|
|
|
|
TextureID g_vramTextures[2];
|
|
TextureID g_curVramTexture;
|
|
int g_curVramTextureIdx = 0;
|
|
|
|
TextureID g_fbTexture;
|
|
|
|
TextureID g_whiteTexture;
|
|
TextureID g_lastBoundTexture;
|
|
|
|
int g_windowWidth = 0;
|
|
int g_windowHeight = 0;
|
|
|
|
timerCtx_t g_swapTimer;
|
|
int g_swapInterval = SWAP_INTERVAL;
|
|
int g_enableSwapInterval = 1;
|
|
|
|
int g_wireframeMode = 0;
|
|
int g_texturelessMode = 0;
|
|
int g_emulatorPaused = 0;
|
|
int g_polygonSelected = 0;
|
|
int g_pgxpTextureCorrection = 1;
|
|
int g_pgxpZBuffer = 1;
|
|
int g_bilinearFiltering = 0;
|
|
|
|
#if defined(RENDERER_OGL)
|
|
GLuint g_glVertexArray;
|
|
GLuint g_glVertexBuffer;
|
|
|
|
GLuint g_glFramebuffer;
|
|
#endif
|
|
|
|
KeyboardMapping g_keyboard_mapping;
|
|
|
|
// Remap a value in the range [A,B] to [C,D].
|
|
#define RemapVal( val, A, B, C, D) \
|
|
(C + (D - C) * (val - A) / (B - A))
|
|
|
|
// remaps screen coordinates to [0..1]
|
|
// without clamping
|
|
inline void ScreenCoordsToEmulator(Vertex* vertex, int count)
|
|
{
|
|
#ifdef USE_PGXP
|
|
while (count--)
|
|
{
|
|
float psxScreenW = activeDispEnv.disp.w;
|
|
float psxScreenH = activeDispEnv.disp.h;
|
|
|
|
vertex[count].x = RemapVal(vertex[count].x, 0.0f, psxScreenW, 0.0f, 1.0f);
|
|
vertex[count].y = RemapVal(vertex[count].y, 0.0f, psxScreenH, 0.0f, 1.0f);
|
|
// FIXME: what about Z?
|
|
|
|
// also center
|
|
vertex[count].x -= 0.5f;
|
|
vertex[count].y -= 0.5f;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Emulator_ResetDevice()
|
|
{
|
|
#if defined(RENDERER_OGL)
|
|
SDL_GL_SetSwapInterval(g_enableSwapInterval ? g_swapInterval : 0);
|
|
#endif
|
|
}
|
|
|
|
|
|
static int Emulator_InitialiseGLContext(char* windowName, int fullscreen)
|
|
{
|
|
int windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
|
|
|
|
if(fullscreen)
|
|
{
|
|
windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
|
}
|
|
|
|
g_window = SDL_CreateWindow(windowName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, g_windowWidth, g_windowHeight, windowFlags);
|
|
|
|
#if defined(RENDERER_OGL)
|
|
SDL_GL_CreateContext(g_window);
|
|
#endif
|
|
|
|
if (g_window == NULL)
|
|
{
|
|
eprinterr("Failed to initialise SDL window or GL context!\n");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#if defined(OGLES)
|
|
EGLint majorVersion = 0, minorVersion = 0;
|
|
EGLContext eglContext = NULL;
|
|
EGLSurface eglSurface = NULL;
|
|
EGLConfig eglConfig = NULL;
|
|
EGLDisplay eglDisplay = NULL;
|
|
int numConfigs = 0;
|
|
|
|
const EGLint config16bpp[] =
|
|
{
|
|
#if OGLES_VERSION == 2
|
|
EGL_RENDERABLE_TYPE,EGL_OPENGL_ES2_BIT,
|
|
#elif OGLES_VERSION == 3
|
|
EGL_RENDERABLE_TYPE,EGL_OPENGL_ES3_BIT,
|
|
#endif
|
|
EGL_BUFFER_SIZE,24,
|
|
EGL_RED_SIZE,8,
|
|
EGL_GREEN_SIZE,8,
|
|
EGL_BLUE_SIZE,8,
|
|
EGL_ALPHA_SIZE,0,
|
|
EGL_DEPTH_SIZE,24,
|
|
EGL_STENCIL_SIZE,0,
|
|
EGL_SAMPLE_BUFFERS,1,
|
|
EGL_SAMPLES,4,
|
|
EGL_NONE
|
|
};
|
|
|
|
static int Emulator_InitialiseGLESContext(char* windowName, int fullscreen)
|
|
{
|
|
unsigned int windowFlags = SDL_WINDOW_OPENGL;
|
|
|
|
#if defined(__ANDROID__)
|
|
windowFlags |= SDL_WINDOW_FULLSCREEN;
|
|
#endif
|
|
|
|
eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
g_window = SDL_CreateWindow(windowName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, windowWidth, windowHeight, windowFlags);
|
|
|
|
if(g_window == NULL)
|
|
{
|
|
eprinterr("Failed to create SDL window!\n");
|
|
}
|
|
|
|
if (!eglInitialize(eglDisplay, &majorVersion, &minorVersion))
|
|
{
|
|
eprinterr("eglInitialize failure! Error: %x\n", eglGetError());
|
|
return FALSE;
|
|
}
|
|
|
|
eglBindAPI(EGL_OPENGL_ES_API);
|
|
|
|
if (!eglChooseConfig(eglDisplay, config16bpp, &eglConfig, 1, &numConfigs))
|
|
{
|
|
printf("eglChooseConfig failed\n");
|
|
if (eglContext == 0)
|
|
{
|
|
printf("Error code: %d\n", eglGetError());
|
|
}
|
|
}
|
|
|
|
SDL_SysWMinfo systemInfo;
|
|
SDL_VERSION(&systemInfo.version);
|
|
SDL_GetWindowWMInfo(g_window, &systemInfo);
|
|
#if defined(__EMSCRIPTEN__)
|
|
EGLNativeWindowType dummyWindow;
|
|
eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, (EGLNativeWindowType)dummyWindow, NULL);
|
|
#elif defined(__ANDROID__)
|
|
eglSurface = systemInfo.info.android.surface;
|
|
#else
|
|
eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, (EGLNativeWindowType)systemInfo.info.win.window, NULL);
|
|
#endif
|
|
if (eglSurface == EGL_NO_SURFACE)
|
|
{
|
|
eprinterr("eglSurface failure! Error: %x\n", eglGetError());
|
|
return FALSE;
|
|
}
|
|
|
|
EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, OGLES_VERSION, EGL_NONE };
|
|
eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, contextAttribs);
|
|
|
|
if (eglContext == EGL_NO_CONTEXT) {
|
|
eprinterr("eglContext failure! Error: %x\n", eglGetError());
|
|
return FALSE;
|
|
}
|
|
|
|
eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#endif
|
|
|
|
static int Emulator_InitialiseSDL(char* windowName, int width, int height, int fullscreen)
|
|
{
|
|
g_windowWidth = width;
|
|
g_windowHeight = height;
|
|
|
|
//Initialise SDL2
|
|
if (SDL_Init(SDL_INIT_VIDEO) == 0)
|
|
{
|
|
#if !defined(RO_DOUBLE_BUFFERED)
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 0);
|
|
#endif
|
|
|
|
#if defined(OGLES)
|
|
|
|
#if defined(__ANDROID__)
|
|
//Override to full screen.
|
|
SDL_DisplayMode displayMode;
|
|
if(SDL_GetCurrentDisplayMode(0, &displayMode) == 0)
|
|
{
|
|
screenWidth = displayMode.w;
|
|
windowWidth = displayMode.w;
|
|
screenHeight = displayMode.h;
|
|
windowHeight = displayMode.h;
|
|
}
|
|
#endif
|
|
//SDL_GL_SetAttribute(SDL_GL_CONTEXT_EGL, 1);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, OGLES_VERSION);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
|
#elif defined(RENDERER_OGL)
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
|
|
#endif
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 1 );
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
eprinterr("Error: Failed to initialise SDL\n");
|
|
return FALSE;
|
|
}
|
|
|
|
#if defined(RENDERER_OGL)
|
|
if (Emulator_InitialiseGLContext(windowName, fullscreen) == FALSE)
|
|
{
|
|
eprinterr("Failed to Initialise GL Context!\n");
|
|
return FALSE;
|
|
}
|
|
#elif defined(OGLES)
|
|
if (Emulator_InitialiseGLESContext(windowName, fullscreen) == FALSE)
|
|
{
|
|
eprinterr("Failed to Initialise GLES Context!\n");
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int Emulator_InitialiseGLExt()
|
|
{
|
|
#if defined(GLEW)
|
|
GLenum err = gladLoadGL();
|
|
|
|
if (err == 0)
|
|
return FALSE;
|
|
|
|
const char* rend = (const char *) glGetString(GL_RENDERER);
|
|
const char* vendor = (const char *) glGetString(GL_VENDOR);
|
|
eprintf("*Video adapter: %s by %s\n", rend, vendor);
|
|
|
|
const char* versionStr = (const char *) glGetString(GL_VERSION);
|
|
eprintf("*OpenGL version: %s\n", versionStr);
|
|
|
|
const char* glslVersionStr = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
|
|
eprintf("*GLSL version: %s\n", glslVersionStr);
|
|
|
|
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
SDL_Thread* g_vblankThread = NULL;
|
|
SDL_mutex* g_vblankMutex = NULL;
|
|
volatile bool g_stopVblank = false;
|
|
volatile int g_vblanksDone = 0;
|
|
volatile int g_lastVblankCnt = 0;
|
|
timerCtx_t g_vblankTimer;
|
|
|
|
extern void(*vsync_callback)(void);
|
|
|
|
int Emulator_DoVSyncCallback()
|
|
{
|
|
SDL_LockMutex(g_vblankMutex);
|
|
|
|
#if 1
|
|
int vblcnt = g_vblanksDone - g_lastVblankCnt;
|
|
|
|
static bool canDoCb = true;
|
|
|
|
if (canDoCb && vsync_callback) // prevent recursive calls
|
|
{
|
|
canDoCb = false;
|
|
|
|
// do vblank callbacks
|
|
int i = vblcnt;
|
|
while (i)
|
|
{
|
|
vsync_callback();
|
|
i--;
|
|
}
|
|
|
|
canDoCb = true;
|
|
}
|
|
#endif
|
|
g_lastVblankCnt = g_vblanksDone;
|
|
|
|
if (g_swapInterval == 0)
|
|
{
|
|
// extra speedup.
|
|
// does not affect `vsync_callback` count
|
|
g_vblanksDone += 1;
|
|
g_lastVblankCnt += 1;
|
|
}
|
|
|
|
SDL_UnlockMutex(g_vblankMutex);
|
|
|
|
return g_vblanksDone;
|
|
}
|
|
|
|
int vblankThreadMain(void* data)
|
|
{
|
|
Emulator_InitHPCTimer(&g_vblankTimer);
|
|
|
|
do
|
|
{
|
|
const long vmode = GetVideoMode();
|
|
const double timestep = vmode == MODE_NTSC ? FIXED_TIME_STEP_NTSC : FIXED_TIME_STEP_PAL;
|
|
|
|
double delta = Emulator_GetHPCTime(&g_vblankTimer, 0);
|
|
|
|
if (delta > timestep)
|
|
{
|
|
// do vblank events
|
|
SDL_LockMutex(g_vblankMutex);
|
|
|
|
g_vblanksDone++;
|
|
|
|
Emulator_GetHPCTime(&g_vblankTimer, 1);
|
|
|
|
#if 0
|
|
if(vsync_callback)
|
|
vsync_callback();
|
|
#endif
|
|
SDL_UnlockMutex(g_vblankMutex);
|
|
}
|
|
} while (!g_stopVblank);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int Emulator_InitialiseCore()
|
|
{
|
|
Emulator_InitHPCTimer(&g_swapTimer);
|
|
|
|
g_vblankThread = SDL_CreateThread(vblankThreadMain, "vbl", NULL);
|
|
|
|
if (NULL == g_vblankThread)
|
|
{
|
|
eprintf("SDL_CreateThread failed: %s\n", SDL_GetError());
|
|
return FALSE;
|
|
}
|
|
|
|
g_vblankMutex = SDL_CreateMutex();
|
|
if (NULL == g_vblankMutex)
|
|
{
|
|
eprintf("SDL_CreateMutex failed: %s\n", SDL_GetError());
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void Emulator_InitialiseInput()
|
|
{
|
|
g_keyboard_mapping.kc_square = SDL_SCANCODE_X;
|
|
g_keyboard_mapping.kc_circle = SDL_SCANCODE_V;
|
|
g_keyboard_mapping.kc_triangle = SDL_SCANCODE_Z;
|
|
g_keyboard_mapping.kc_cross = SDL_SCANCODE_C;
|
|
|
|
g_keyboard_mapping.kc_l1 = SDL_SCANCODE_LSHIFT;
|
|
g_keyboard_mapping.kc_l2 = SDL_SCANCODE_LCTRL;
|
|
g_keyboard_mapping.kc_l3 = SDL_SCANCODE_LEFTBRACKET;
|
|
|
|
g_keyboard_mapping.kc_r1 = SDL_SCANCODE_RSHIFT;
|
|
g_keyboard_mapping.kc_r2 = SDL_SCANCODE_RCTRL;
|
|
g_keyboard_mapping.kc_r3 = SDL_SCANCODE_RIGHTBRACKET;
|
|
|
|
g_keyboard_mapping.kc_dpad_up = SDL_SCANCODE_UP;
|
|
g_keyboard_mapping.kc_dpad_down = SDL_SCANCODE_DOWN;
|
|
g_keyboard_mapping.kc_dpad_left = SDL_SCANCODE_LEFT;
|
|
g_keyboard_mapping.kc_dpad_right = SDL_SCANCODE_RIGHT;
|
|
|
|
g_keyboard_mapping.kc_select = SDL_SCANCODE_SPACE;
|
|
g_keyboard_mapping.kc_start = SDL_SCANCODE_RETURN;
|
|
}
|
|
|
|
void Emulator_Initialise(char* windowName, int width, int height, int fullscreen)
|
|
{
|
|
eprintf("Initialising Psy-X %d.%d\n", EMULATOR_MAJOR_VERSION, EMULATOR_MINOR_VERSION);
|
|
eprintf("Build date: %s:%s\n", EMULATOR_COMPILE_DATE, EMULATOR_COMPILE_TIME);
|
|
|
|
if (Emulator_InitialiseSDL(windowName, width, height, fullscreen) == FALSE)
|
|
{
|
|
eprinterr("Failed to Intialise SDL\n");
|
|
Emulator_ShutDown();
|
|
}
|
|
|
|
#if defined(GLEW)
|
|
if (Emulator_InitialiseGLExt() == FALSE)
|
|
{
|
|
eprinterr("Failed to Intialise GL extensions\n");
|
|
Emulator_ShutDown();
|
|
}
|
|
#endif
|
|
|
|
if (Emulator_InitialiseCore() == FALSE)
|
|
{
|
|
eprinterr("Failed to Intialise Psy-X Core.\n");
|
|
Emulator_ShutDown();
|
|
}
|
|
|
|
if (Emulator_Initialise() == FALSE)
|
|
{
|
|
eprinterr("Failed to Intialise GL.\n");
|
|
Emulator_ShutDown();
|
|
}
|
|
|
|
Emulator_InitialiseInput();
|
|
}
|
|
|
|
void Emulator_GetScreenSize(int& screenWidth, int& screenHeight)
|
|
{
|
|
SDL_GetWindowSize(g_window, &screenWidth, &screenHeight);
|
|
}
|
|
|
|
void Emulator_SetCursorPosition(int x, int y)
|
|
{
|
|
SDL_WarpMouseInWindow(g_window, x, y);
|
|
}
|
|
|
|
void Emulator_LineSwapSourceVerts(VERTTYPE* &p0, VERTTYPE* &p1, unsigned char* &c0, unsigned char* &c1)
|
|
{
|
|
// swap line coordinates for left-to-right and up-to-bottom direction
|
|
if ((p0[0] > p1[0]) ||
|
|
(p0[1] > p1[1] && p0[0] == p1[0]))
|
|
{
|
|
VERTTYPE *tmp = p0;
|
|
p0 = p1;
|
|
p1 = tmp;
|
|
|
|
unsigned char* tmpCol = c0;
|
|
c0 = c1;
|
|
c1 = tmpCol;
|
|
}
|
|
}
|
|
|
|
void Emulator_GenerateLineArray(struct Vertex* vertex, VERTTYPE* p0, VERTTYPE* p1, ushort gteidx)
|
|
{
|
|
VERTTYPE dx = p1[0] - p0[0];
|
|
VERTTYPE dy = p1[1] - p0[1];
|
|
|
|
float ofsX = activeDrawEnv.ofs[0] % activeDispEnv.disp.w;
|
|
float ofsY = activeDrawEnv.ofs[1] % activeDispEnv.disp.h;
|
|
|
|
if (dx > abs((short)dy)) { // horizontal
|
|
vertex[0].x = p0[0] + ofsX;
|
|
vertex[0].y = p0[1] + ofsY;
|
|
|
|
vertex[1].x = p1[0] + ofsX + 1;
|
|
vertex[1].y = p1[1] + ofsY;
|
|
|
|
vertex[2].x = vertex[1].x;
|
|
vertex[2].y = vertex[1].y + 1;
|
|
|
|
vertex[3].x = vertex[0].x;
|
|
vertex[3].y = vertex[0].y + 1;
|
|
} else { // vertical
|
|
vertex[0].x = p0[0] + ofsX;
|
|
vertex[0].y = p0[1] + ofsY;
|
|
|
|
vertex[1].x = p1[0] + ofsX;
|
|
vertex[1].y = p1[1] + ofsY + 1;
|
|
|
|
vertex[2].x = vertex[1].x + 1;
|
|
vertex[2].y = vertex[1].y;
|
|
|
|
vertex[3].x = vertex[0].x + 1;
|
|
vertex[3].y = vertex[0].y;
|
|
} // TODO diagonal line alignment
|
|
|
|
#ifdef USE_PGXP
|
|
vertex[0].scr_h = vertex[1].scr_h = vertex[2].scr_h = vertex[3].scr_h = 0.0f;
|
|
#endif
|
|
|
|
ScreenCoordsToEmulator(vertex, 4);
|
|
}
|
|
|
|
|
|
|
|
inline void Emulator_ApplyVertexPGXP(Vertex* v, VERTTYPE* p, float ofsX, float ofsY, ushort gteidx)
|
|
{
|
|
#ifdef USE_PGXP
|
|
uint lookup = PGXP_LOOKUP_VALUE(p[0], p[1]);
|
|
PGXPVData vd;
|
|
if(g_pgxpTextureCorrection && PGXP_GetCacheData(vd, lookup, gteidx))
|
|
{
|
|
float gteOfsX = vd.ofx;
|
|
float gteOfsY = vd.ofy;
|
|
|
|
// FIXME: it must be clamped strictly to the current draw buffer bounds!
|
|
// this is bad approach but it works for now
|
|
if (gteOfsX > activeDispEnv.disp.w)
|
|
gteOfsX -= activeDispEnv.disp.w;
|
|
gteOfsX -= activeDispEnv.disp.w / 2;
|
|
|
|
if (gteOfsY > activeDispEnv.disp.h)
|
|
gteOfsY -= activeDispEnv.disp.h;
|
|
gteOfsY -= activeDispEnv.disp.h / 2;
|
|
|
|
|
|
|
|
v->x = vd.px;
|
|
v->y = vd.py;
|
|
v->z = vd.pz;
|
|
v->ofsX = (ofsX + gteOfsX) / float(activeDispEnv.disp.w) * 2.0f;
|
|
v->ofsY = (ofsY + gteOfsY) / float(activeDispEnv.disp.h) * 2.0f;
|
|
|
|
v->scr_h = vd.scr_h;
|
|
}
|
|
else
|
|
{
|
|
v->scr_h = 0.0f;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
void Emulator_GenerateVertexArrayTriangle(struct Vertex* vertex, VERTTYPE* p0, VERTTYPE* p1, VERTTYPE* p2, ushort gteidx)
|
|
{
|
|
assert(p0);
|
|
assert(p1);
|
|
assert(p2);
|
|
|
|
float ofsX = activeDrawEnv.ofs[0] % activeDispEnv.disp.w;
|
|
float ofsY = activeDrawEnv.ofs[1] % activeDispEnv.disp.h;
|
|
|
|
vertex[0].x = p0[0] + ofsX;
|
|
vertex[0].y = p0[1] + ofsY;
|
|
|
|
vertex[1].x = p1[0] + ofsX;
|
|
vertex[1].y = p1[1] + ofsY;
|
|
|
|
vertex[2].x = p2[0] + ofsX;
|
|
vertex[2].y = p2[1] + ofsY;
|
|
|
|
Emulator_ApplyVertexPGXP(&vertex[0], p0, ofsX, ofsY, gteidx-2);
|
|
Emulator_ApplyVertexPGXP(&vertex[1], p1, ofsX, ofsY, gteidx-1);
|
|
Emulator_ApplyVertexPGXP(&vertex[2], p2, ofsX, ofsY, gteidx);
|
|
|
|
ScreenCoordsToEmulator(vertex, 3);
|
|
}
|
|
|
|
void Emulator_GenerateVertexArrayQuad(struct Vertex* vertex, VERTTYPE* p0, VERTTYPE* p1, VERTTYPE* p2, VERTTYPE* p3, ushort gteidx)
|
|
{
|
|
assert(p0);
|
|
assert(p1);
|
|
assert(p2);
|
|
assert(p3);
|
|
|
|
float ofsX = activeDrawEnv.ofs[0] % activeDispEnv.disp.w;
|
|
float ofsY = activeDrawEnv.ofs[1] % activeDispEnv.disp.h;
|
|
|
|
vertex[0].x = p0[0] + ofsX;
|
|
vertex[0].y = p0[1] + ofsY;
|
|
|
|
vertex[1].x = p1[0] + ofsX;
|
|
vertex[1].y = p1[1] + ofsY;
|
|
|
|
vertex[2].x = p2[0] + ofsX;
|
|
vertex[2].y = p2[1] + ofsY;
|
|
|
|
vertex[3].x = p3[0] + ofsX;
|
|
vertex[3].y = p3[1] + ofsY;
|
|
|
|
Emulator_ApplyVertexPGXP(&vertex[0], p0, ofsX, ofsY, gteidx-2);
|
|
Emulator_ApplyVertexPGXP(&vertex[1], p1, ofsX, ofsY, gteidx-2);
|
|
Emulator_ApplyVertexPGXP(&vertex[2], p2, ofsX, ofsY, gteidx-1);
|
|
Emulator_ApplyVertexPGXP(&vertex[3], p3, ofsX, ofsY, gteidx);
|
|
|
|
ScreenCoordsToEmulator(vertex, 4);
|
|
}
|
|
|
|
void Emulator_GenerateVertexArrayRect(struct Vertex* vertex, VERTTYPE* p0, short w, short h, ushort gteidx)
|
|
{
|
|
assert(p0);
|
|
|
|
float ofsX = activeDrawEnv.ofs[0] % activeDispEnv.disp.w;
|
|
float ofsY = activeDrawEnv.ofs[1] % activeDispEnv.disp.h;
|
|
|
|
vertex[0].x = p0[0] + ofsX;
|
|
vertex[0].y = p0[1] + ofsY;
|
|
|
|
vertex[1].x = vertex[0].x;
|
|
vertex[1].y = vertex[0].y + h;
|
|
|
|
vertex[2].x = vertex[0].x + w;
|
|
vertex[2].y = vertex[0].y + h;
|
|
|
|
vertex[3].x = vertex[0].x + w;
|
|
vertex[3].y = vertex[0].y;
|
|
|
|
#ifdef USE_PGXP
|
|
vertex[0].scr_h = vertex[1].scr_h = vertex[2].scr_h = vertex[3].scr_h = 0.0f;
|
|
#endif
|
|
|
|
ScreenCoordsToEmulator(vertex, 4);
|
|
}
|
|
|
|
void Emulator_GenerateTexcoordArrayQuad(struct Vertex* vertex, unsigned char* uv0, unsigned char* uv1, unsigned char* uv2, unsigned char* uv3, short page, short clut, unsigned char dither)
|
|
{
|
|
assert(uv0);
|
|
assert(uv1);
|
|
assert(uv2);
|
|
assert(uv3);
|
|
|
|
const unsigned char bright = 2;
|
|
|
|
vertex[0].u = uv0[0];
|
|
vertex[0].v = uv0[1];
|
|
vertex[0].bright = bright;
|
|
vertex[0].dither = dither;
|
|
vertex[0].page = page;
|
|
vertex[0].clut = clut;
|
|
|
|
vertex[1].u = uv1[0];
|
|
vertex[1].v = uv1[1];
|
|
vertex[1].bright = bright;
|
|
vertex[1].dither = dither;
|
|
vertex[1].page = page;
|
|
vertex[1].clut = clut;
|
|
|
|
vertex[2].u = uv2[0];
|
|
vertex[2].v = uv2[1];
|
|
vertex[2].bright = bright;
|
|
vertex[2].dither = dither;
|
|
vertex[2].page = page;
|
|
vertex[2].clut = clut;
|
|
|
|
vertex[3].u = uv3[0];
|
|
vertex[3].v = uv3[1];
|
|
vertex[3].bright = bright;
|
|
vertex[3].dither = dither;
|
|
vertex[3].page = page;
|
|
vertex[3].clut = clut;
|
|
}
|
|
|
|
void Emulator_GenerateTexcoordArrayTriangle(struct Vertex* vertex, unsigned char* uv0, unsigned char* uv1, unsigned char* uv2, short page, short clut, unsigned char dither)
|
|
{
|
|
assert(uv0);
|
|
assert(uv1);
|
|
assert(uv2);
|
|
|
|
const unsigned char bright = 2;
|
|
|
|
vertex[0].u = uv0[0];
|
|
vertex[0].v = uv0[1];
|
|
vertex[0].bright = bright;
|
|
vertex[0].dither = dither;
|
|
vertex[0].page = page;
|
|
vertex[0].clut = clut;
|
|
|
|
vertex[1].u = uv1[0];
|
|
vertex[1].v = uv1[1];
|
|
vertex[1].bright = bright;
|
|
vertex[1].dither = dither;
|
|
vertex[1].page = page;
|
|
vertex[1].clut = clut;
|
|
|
|
vertex[2].u = uv2[0];
|
|
vertex[2].v = uv2[1];
|
|
vertex[2].bright = bright;
|
|
vertex[2].dither = dither;
|
|
vertex[2].page = page;
|
|
vertex[2].clut = clut;
|
|
}
|
|
|
|
void Emulator_GenerateTexcoordArrayRect(struct Vertex* vertex, unsigned char* uv, short page, short clut, short w, short h)
|
|
{
|
|
assert(uv);
|
|
//assert(int(uv[0]) + w <= 255);
|
|
//assert(int(uv[1]) + h <= 255);
|
|
// TODO
|
|
if (int(uv[0]) + w > 255) w = 255 - uv[0];
|
|
if (int(uv[1]) + h > 255) h = 255 - uv[1];
|
|
|
|
const unsigned char bright = 2;
|
|
const unsigned char dither = 0;
|
|
|
|
vertex[0].u = uv[0];
|
|
vertex[0].v = uv[1];
|
|
vertex[0].bright = bright;
|
|
vertex[0].dither = dither;
|
|
vertex[0].page = page;
|
|
vertex[0].clut = clut;
|
|
|
|
vertex[1].u = uv[0];
|
|
vertex[1].v = uv[1] + h;
|
|
vertex[1].bright = bright;
|
|
vertex[1].dither = dither;
|
|
vertex[1].page = page;
|
|
vertex[1].clut = clut;
|
|
|
|
vertex[2].u = uv[0] + w;
|
|
vertex[2].v = uv[1] + h;
|
|
vertex[2].bright = bright;
|
|
vertex[2].dither = dither;
|
|
vertex[2].page = page;
|
|
vertex[2].clut = clut;
|
|
|
|
vertex[3].u = uv[0] + w;
|
|
vertex[3].v = uv[1];
|
|
vertex[3].bright = bright;
|
|
vertex[3].dither = dither;
|
|
vertex[3].page = page;
|
|
vertex[3].clut = clut;
|
|
}
|
|
|
|
void Emulator_GenerateTexcoordArrayLineZero(struct Vertex* vertex, unsigned char dither)
|
|
{
|
|
const unsigned char bright = 1;
|
|
|
|
vertex[0].u = 0;
|
|
vertex[0].v = 0;
|
|
vertex[0].bright = bright;
|
|
vertex[0].dither = dither;
|
|
vertex[0].page = 0;
|
|
vertex[0].clut = 0;
|
|
|
|
vertex[1].u = 0;
|
|
vertex[1].v = 0;
|
|
vertex[1].bright = bright;
|
|
vertex[1].dither = dither;
|
|
vertex[1].page = 0;
|
|
vertex[1].clut = 0;
|
|
|
|
vertex[2].u = 0;
|
|
vertex[2].v = 0;
|
|
vertex[2].bright = bright;
|
|
vertex[2].dither = dither;
|
|
vertex[2].page = 0;
|
|
vertex[2].clut = 0;
|
|
|
|
vertex[3].u = 0;
|
|
vertex[3].v = 0;
|
|
vertex[3].bright = bright;
|
|
vertex[3].dither = dither;
|
|
vertex[3].page = 0;
|
|
vertex[3].clut = 0;
|
|
}
|
|
|
|
void Emulator_GenerateTexcoordArrayTriangleZero(struct Vertex* vertex, unsigned char dither)
|
|
{
|
|
const unsigned char bright = 1;
|
|
|
|
vertex[0].u = 0;
|
|
vertex[0].v = 0;
|
|
vertex[0].bright = bright;
|
|
vertex[0].dither = dither;
|
|
vertex[0].page = 0;
|
|
vertex[0].clut = 0;
|
|
|
|
vertex[1].u = 0;
|
|
vertex[1].v = 0;
|
|
vertex[1].bright = bright;
|
|
vertex[1].dither = dither;
|
|
vertex[1].page = 0;
|
|
vertex[1].clut = 0;
|
|
|
|
vertex[2].u = 0;
|
|
vertex[2].v = 0;
|
|
vertex[2].bright = bright;
|
|
vertex[2].dither = dither;
|
|
vertex[2].page = 0;
|
|
vertex[2].clut = 0;
|
|
|
|
}
|
|
|
|
void Emulator_GenerateTexcoordArrayQuadZero(struct Vertex* vertex, unsigned char dither)
|
|
{
|
|
const unsigned char bright = 1;
|
|
|
|
vertex[0].u = 0;
|
|
vertex[0].v = 0;
|
|
vertex[0].bright = bright;
|
|
vertex[0].dither = dither;
|
|
vertex[0].page = 0;
|
|
vertex[0].clut = 0;
|
|
|
|
vertex[1].u = 0;
|
|
vertex[1].v = 0;
|
|
vertex[1].bright = bright;
|
|
vertex[1].dither = dither;
|
|
vertex[1].page = 0;
|
|
vertex[1].clut = 0;
|
|
|
|
vertex[2].u = 0;
|
|
vertex[2].v = 0;
|
|
vertex[2].bright = bright;
|
|
vertex[2].dither = dither;
|
|
vertex[2].page = 0;
|
|
vertex[2].clut = 0;
|
|
|
|
vertex[3].u = 0;
|
|
vertex[3].v = 0;
|
|
vertex[3].bright = bright;
|
|
vertex[3].dither = dither;
|
|
vertex[3].page = 0;
|
|
vertex[3].clut = 0;
|
|
}
|
|
|
|
void Emulator_GenerateColourArrayLine(struct Vertex* vertex, unsigned char* col0, unsigned char* col1)
|
|
{
|
|
assert(col0);
|
|
assert(col1);
|
|
|
|
vertex[0].r = col0[0];
|
|
vertex[0].g = col0[1];
|
|
vertex[0].b = col0[2];
|
|
vertex[0].a = 255;
|
|
|
|
vertex[1].r = col1[0];
|
|
vertex[1].g = col1[1];
|
|
vertex[1].b = col1[2];
|
|
vertex[1].a = 255;
|
|
|
|
vertex[2].r = col1[0];
|
|
vertex[2].g = col1[1];
|
|
vertex[2].b = col1[2];
|
|
vertex[2].a = 255;
|
|
|
|
vertex[3].r = col0[0];
|
|
vertex[3].g = col0[1];
|
|
vertex[3].b = col0[2];
|
|
vertex[3].a = 255;
|
|
}
|
|
|
|
void Emulator_GenerateColourArrayTriangle(struct Vertex* vertex, unsigned char* col0, unsigned char* col1, unsigned char* col2)
|
|
{
|
|
assert(col0);
|
|
assert(col1);
|
|
assert(col2);
|
|
|
|
vertex[0].r = col0[0];
|
|
vertex[0].g = col0[1];
|
|
vertex[0].b = col0[2];
|
|
vertex[0].a = 255;
|
|
|
|
vertex[1].r = col1[0];
|
|
vertex[1].g = col1[1];
|
|
vertex[1].b = col1[2];
|
|
vertex[1].a = 255;
|
|
|
|
vertex[2].r = col2[0];
|
|
vertex[2].g = col2[1];
|
|
vertex[2].b = col2[2];
|
|
vertex[2].a = 255;
|
|
}
|
|
|
|
void Emulator_GenerateColourArrayQuad(struct Vertex* vertex, unsigned char* col0, unsigned char* col1, unsigned char* col2, unsigned char* col3)
|
|
{
|
|
assert(col0);
|
|
assert(col1);
|
|
assert(col2);
|
|
assert(col3);
|
|
|
|
vertex[0].r = col0[0];
|
|
vertex[0].g = col0[1];
|
|
vertex[0].b = col0[2];
|
|
vertex[0].a = 255;
|
|
|
|
vertex[1].r = col1[0];
|
|
vertex[1].g = col1[1];
|
|
vertex[1].b = col1[2];
|
|
vertex[1].a = 255;
|
|
|
|
vertex[2].r = col2[0];
|
|
vertex[2].g = col2[1];
|
|
vertex[2].b = col2[2];
|
|
vertex[2].a = 255;
|
|
|
|
vertex[3].r = col3[0];
|
|
vertex[3].g = col3[1];
|
|
vertex[3].b = col3[2];
|
|
vertex[3].a = 255;
|
|
}
|
|
|
|
ShaderID g_gte_shader_4;
|
|
ShaderID g_gte_shader_8;
|
|
ShaderID g_gte_shader_16;
|
|
ShaderID g_blit_shader;
|
|
|
|
#if defined(OGLES) || defined(RENDERER_OGL)
|
|
GLint u_Projection;
|
|
GLint u_Projection3D;
|
|
|
|
#define GPU_PACK_RG\
|
|
" float color_16 = (color_rg.y * 256.0 + color_rg.x) * 255.0;\n"
|
|
|
|
#define GPU_DISCARD\
|
|
" if (color_16 == 0.0) { discard; }\n"
|
|
|
|
#define GPU_DECODE_RG\
|
|
" fragColor = fract(floor(color_16 / vec4(1.0, 32.0, 1024.0, 32768.0)) / 32.0);\n"
|
|
|
|
#define GPU_PACK_RG_FUNC\
|
|
" float packRG(vec2 rg) { return (rg.y * 256.0 + rg.x) * 255.0;}\n"
|
|
|
|
#define GPU_DECODE_RG_FUNC\
|
|
" vec4 decodeRG(float rg) { return fract(floor(rg / vec4(1.0, 32.0, 1024.0, 32768.0)) / 32.0); }\n"
|
|
|
|
#define GPU_DITHERING\
|
|
" fragColor *= v_color;\n"\
|
|
" mat4 dither = mat4(\n"\
|
|
" -4.0, +0.0, -3.0, +1.0,\n"\
|
|
" +2.0, -2.0, +3.0, -1.0,\n"\
|
|
" -3.0, +1.0, -4.0, +0.0,\n"\
|
|
" +3.0, -1.0, +2.0, -2.0) / 255.0;\n"\
|
|
" ivec2 dc = ivec2(fract(gl_FragCoord.xy / 4.0) * 4.0);\n"\
|
|
" fragColor.xyz += vec3(dither[dc.x][dc.y] * v_texcoord.w);\n"
|
|
|
|
#define GPU_SAMPLE_TEXTURE_4BIT_FUNC\
|
|
" // returns 16 bit colour\n"\
|
|
" float samplePSX(vec2 tc){\n"\
|
|
" vec2 uv = (tc * vec2(0.25, 1.0) + v_page_clut.xy) * vec2(1.0 / 1024.0, 1.0 / 512.0);\n"\
|
|
" vec2 comp = VRAM(uv);\n"\
|
|
" int index = int(fract(tc.x / 4.0 + 0.0001) * 4.0);\n"\
|
|
" float v = comp[index / 2] * (255.0 / 16.0);\n"\
|
|
" float f = floor(v);\n"\
|
|
" vec2 c = vec2( (v - f) * 16.0, f );\n"\
|
|
" vec2 clut_pos = v_page_clut.zw;\n"\
|
|
" clut_pos.x += mix(c[0], c[1], mod(index, 2)) / 1024.0;\n"\
|
|
" return packRG(VRAM(clut_pos));\n"\
|
|
" }\n"
|
|
|
|
#define GPU_SAMPLE_TEXTURE_8BIT_FUNC\
|
|
" // returns 16 bit colour\n"\
|
|
" float samplePSX(vec2 tc){\n"\
|
|
" vec2 uv = (tc * vec2(0.5, 1.0) + v_page_clut.xy) * vec2(1.0 / 1024.0, 1.0 / 512.0);\n"\
|
|
" vec2 comp = VRAM(uv);\n"\
|
|
" vec2 clut_pos = v_page_clut.zw;\n"\
|
|
" int index = int(mod(tc.x, 2.0));\n"\
|
|
" clut_pos.x += comp[index] * 255.0 / 1024.0;\n"\
|
|
" vec2 color_rg = VRAM(clut_pos);\n"\
|
|
" return packRG(VRAM(clut_pos));\n"\
|
|
" }\n"
|
|
|
|
#define GPU_SAMPLE_TEXTURE_16BIT_FUNC\
|
|
" float samplePSX(vec2 tc){\n"\
|
|
" vec2 uv = (tc + v_page_clut.xy) * vec2(1.0 / 1024.0, 1.0 / 512.0);\n"\
|
|
" vec2 color_rg = VRAM(uv);\n"\
|
|
" return packRG(color_rg);\n"\
|
|
" }\n"
|
|
|
|
|
|
#define GPU_BILINEAR_SAMPLE_FUNC \
|
|
" float c_textureSize = 1;\n"\
|
|
" float c_onePixel = 1;\n"\
|
|
" vec4 BilinearTextureSample(vec2 P) {\n"\
|
|
" vec2 pixel = P + vec2(1.0, 1);\n"\
|
|
" vec2 frac = fract(pixel);\n"\
|
|
" pixel = floor(pixel) - vec2(c_onePixel * 0.5);\n"\
|
|
" float C11 = samplePSX(pixel + vec2( 0.0 , 0.0));\n"\
|
|
" float C21 = samplePSX(pixel + vec2( c_onePixel, 0.0));\n"\
|
|
" float C12 = samplePSX(pixel + vec2( 0.0 , c_onePixel));\n"\
|
|
" float C22 = samplePSX(pixel + vec2( c_onePixel, c_onePixel));\n"\
|
|
" float ax1 = mix(float(C11 > 0), float(C21 > 0), frac.x);\n"\
|
|
" float ax2 = mix(float(C12 > 0), float(C22 > 0), frac.x);\n"\
|
|
" if(mix(ax1, ax2, frac.y) < 0.25) { discard; }\n"\
|
|
" vec4 x1 = mix(decodeRG(C11), decodeRG(C21), frac.x);\n"\
|
|
" vec4 x2 = mix(decodeRG(C12), decodeRG(C22), frac.x);\n"\
|
|
" return mix(x1, x2, frac.y);\n"\
|
|
" }\n"
|
|
|
|
#define GPU_NEAREST_SAMPLE_FUNC \
|
|
"vec4 NearestTextureSample(vec2 P) {\n"\
|
|
" float color_16 = samplePSX(P);\n"\
|
|
" if(color_16 == 0.0) {discard;}\n"\
|
|
" return decodeRG(color_16);\n"\
|
|
"}\n"
|
|
|
|
#if (VRAM_FORMAT == GL_LUMINANCE_ALPHA)
|
|
#define GTE_FETCH_VRAM_FUNC\
|
|
" uniform sampler2D s_texture;\n"\
|
|
" vec2 VRAM(vec2 uv) { return texture2D(s_texture, uv).ra; }\n"
|
|
#else
|
|
#define GPU_FETCH_VRAM_FUNC\
|
|
" uniform sampler2D s_texture;\n"\
|
|
" vec2 VRAM(vec2 uv) { return texture2D(s_texture, uv).rg; }\n"
|
|
#endif
|
|
|
|
#ifdef USE_PGXP
|
|
#define GTE_PERSPECTIVE_CORRECTION \
|
|
" mat4 ofsMat = mat4(\n"\
|
|
" vec4(1.0, 0.0, 0.0, 0.0),\n"\
|
|
" vec4(0.0, 1.0, 0.0, 0.0),\n"\
|
|
" vec4(0.0, 0.0, 1.0, 0.0),\n"\
|
|
" vec4(a_zw.z, -a_zw.w, 0.0, 1.0));\n"\
|
|
" vec2 geom_ofs = vec2(0.5, 0.5);\n"\
|
|
" vec4 fragPosition = (a_zw.y > 100 ? ofsMat * (Projection3D * vec4((a_position.xy + geom_ofs) * vec2(1,-1) * a_zw.y, a_zw.x, 1.0)) : (Projection * vec4(a_position.xy, 0.5, 1.0)));\n" \
|
|
" gl_Position = fragPosition;\n"
|
|
#else
|
|
#define GTE_PERSPECTIVE_CORRECTION \
|
|
" gl_Position = Projection * vec4(a_position.xy, 0.0, 1.0);\n"
|
|
#endif
|
|
|
|
#define GTE_VERTEX_SHADER \
|
|
" attribute vec4 a_position;\n"\
|
|
" attribute vec4 a_texcoord; // uv, color multiplier, dither\n"\
|
|
" attribute vec4 a_color;\n"\
|
|
" attribute vec4 a_zw;\n"\
|
|
" uniform mat4 Projection;\n"\
|
|
" uniform mat4 Projection3D;\n"\
|
|
" const vec2 c_UVFudge = vec2(0.00025, 0.00025);\n"\
|
|
" void main() {\n"\
|
|
" v_texcoord = a_texcoord;\n"\
|
|
" v_color = a_color;\n"\
|
|
" v_color.xyz *= a_texcoord.z;\n"\
|
|
" v_page_clut.x = fract(a_position.z / 16.0) * 1024.0;\n"\
|
|
" v_page_clut.y = floor(a_position.z / 16.0) * 256.0;\n"\
|
|
" v_page_clut.z = fract(a_position.w / 64.0);\n"\
|
|
" v_page_clut.w = floor(a_position.w / 64.0) / 512.0;\n"\
|
|
" v_page_clut.xy += c_UVFudge;\n"\
|
|
" v_page_clut.zw += c_UVFudge;\n"\
|
|
GTE_PERSPECTIVE_CORRECTION\
|
|
" v_z = (gl_Position.z - 40) * 0.005;\n"\
|
|
" }\n"
|
|
|
|
#define GPU_FRAGMENT_SAMPLE_SHADER(bit) \
|
|
GPU_PACK_RG_FUNC\
|
|
GPU_DECODE_RG_FUNC\
|
|
GPU_FETCH_VRAM_FUNC\
|
|
GPU_SAMPLE_TEXTURE_## bit ##BIT_FUNC\
|
|
"#ifdef BILINEAR_FILTER\n"\
|
|
GPU_BILINEAR_SAMPLE_FUNC\
|
|
"#else\n"\
|
|
GPU_NEAREST_SAMPLE_FUNC\
|
|
"#endif\n"\
|
|
" void main() {\n"\
|
|
"#ifdef BILINEAR_FILTER\n"\
|
|
" fragColor = BilinearTextureSample(v_texcoord.xy);\n"\
|
|
"#else\n"\
|
|
" fragColor = NearestTextureSample(v_texcoord.xy);\n"\
|
|
"#endif\n"\
|
|
GPU_DITHERING\
|
|
" }\n"
|
|
|
|
const char* gte_shader_4 =
|
|
"varying vec4 v_texcoord;\n"
|
|
"varying vec4 v_color;\n"
|
|
"varying vec4 v_page_clut;\n"
|
|
"varying float v_z;\n"
|
|
"#ifdef VERTEX\n"
|
|
GTE_VERTEX_SHADER
|
|
"#else\n"
|
|
GPU_FRAGMENT_SAMPLE_SHADER(4)
|
|
"#endif\n";
|
|
|
|
const char* gte_shader_8 =
|
|
"varying vec4 v_texcoord;\n"
|
|
"varying vec4 v_color;\n"
|
|
"varying vec4 v_page_clut;\n"
|
|
"varying float v_z;\n"
|
|
"#ifdef VERTEX\n"
|
|
GTE_VERTEX_SHADER
|
|
"#else\n"
|
|
GPU_FRAGMENT_SAMPLE_SHADER(8)
|
|
"#endif\n";
|
|
|
|
const char* gte_shader_16 =
|
|
"varying vec4 v_texcoord;\n"
|
|
"varying vec4 v_color;\n"
|
|
"varying vec4 v_page_clut;\n"
|
|
"varying float v_z;\n"
|
|
"#ifdef VERTEX\n"
|
|
GTE_VERTEX_SHADER
|
|
"#else\n"
|
|
GPU_FRAGMENT_SAMPLE_SHADER(16)
|
|
"#endif\n";
|
|
|
|
const char* blit_shader =
|
|
"varying vec4 v_texcoord;\n"
|
|
"#ifdef VERTEX\n"
|
|
" attribute vec4 a_position;\n"
|
|
" attribute vec4 a_texcoord;\n"
|
|
" void main() {\n"
|
|
" v_texcoord = a_texcoord * vec4(8.0 / 1024.0, 8.0 / 512.0, 0.0, 0.0);\n"
|
|
" gl_Position = vec4(a_position.xy, 0.0, 1.0);\n"
|
|
" }\n"
|
|
"#else\n"
|
|
GPU_FETCH_VRAM_FUNC
|
|
" void main() {\n"
|
|
" vec2 color_rg = VRAM(v_texcoord.xy);\n"
|
|
GPU_PACK_RG
|
|
GPU_DECODE_RG
|
|
" }\n"
|
|
"#endif\n";
|
|
|
|
void Shader_CheckShaderStatus(GLuint shader)
|
|
{
|
|
char info[1024];
|
|
glGetShaderInfoLog(shader, sizeof(info), NULL, info);
|
|
if (info[0] && strlen(info) > 8)
|
|
{
|
|
eprinterr("%s\n", info);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
void Shader_CheckProgramStatus(GLuint program)
|
|
{
|
|
char info[1024];
|
|
glGetProgramInfoLog(program, sizeof(info), NULL, info);
|
|
if (info[0] && strlen(info) > 8)
|
|
{
|
|
eprinterr("%s\n", info);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
ShaderID Shader_Compile(const char *source)
|
|
{
|
|
#if defined(ES2_SHADERS)
|
|
const char *GLSL_HEADER_VERT =
|
|
"#version 100\n"
|
|
"precision lowp int;\n"
|
|
"precision highp float;\n"
|
|
"#define VERTEX\n";
|
|
|
|
const char *GLSL_HEADER_FRAG =
|
|
"#version 100\n"
|
|
"precision lowp int;\n"
|
|
"precision highp float;\n"
|
|
"#define fragColor gl_FragColor\n";
|
|
#elif defined(ES3_SHADERS)
|
|
const char *GLSL_HEADER_VERT =
|
|
"#version 300 es\n"
|
|
"precision lowp int;\n"
|
|
"precision highp float;\n"
|
|
"#define VERTEX\n"
|
|
"#define varying out\n"
|
|
"#define attribute in\n"
|
|
"#define texture2D texture\n";
|
|
|
|
const char *GLSL_HEADER_FRAG =
|
|
"#version 300 es\n"
|
|
"precision lowp int;\n"
|
|
"precision highp float;\n"
|
|
"#define varying in\n"
|
|
"#define texture2D texture\n"
|
|
"out vec4 fragColor;\n";
|
|
#else
|
|
const char *GLSL_HEADER_VERT =
|
|
"#version 330\n"
|
|
"precision lowp int;\n"
|
|
"precision highp float;\n"
|
|
"#define VERTEX\n"
|
|
"#define varying out\n"
|
|
"#define attribute in\n"
|
|
"#define texture2D texture\n";
|
|
|
|
const char *GLSL_HEADER_FRAG =
|
|
"#version 330\n"
|
|
"precision lowp int;\n"
|
|
"precision highp float;\n"
|
|
"#define varying in\n"
|
|
"#define texture2D texture\n"
|
|
"out vec4 fragColor;\n";
|
|
#endif
|
|
|
|
char extra_vs_defines[1024];
|
|
char extra_fs_defines[1024];
|
|
extra_vs_defines[0] = 0;
|
|
extra_fs_defines[0] = 0;
|
|
|
|
if(g_bilinearFiltering)
|
|
{
|
|
strcat(extra_fs_defines, "#define BILINEAR_FILTER\n");
|
|
}
|
|
|
|
const char *vs_list[] = { GLSL_HEADER_VERT, extra_vs_defines, source };
|
|
const char *fs_list[] = { GLSL_HEADER_FRAG, extra_fs_defines, source };
|
|
|
|
GLuint program = glCreateProgram();
|
|
|
|
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
|
glShaderSource(vertexShader, 3, vs_list, NULL);
|
|
glCompileShader(vertexShader);
|
|
Shader_CheckShaderStatus(vertexShader);
|
|
glAttachShader(program, vertexShader);
|
|
glDeleteShader(vertexShader);
|
|
|
|
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
glShaderSource(fragmentShader, 3, fs_list, NULL);
|
|
glCompileShader(fragmentShader);
|
|
Shader_CheckShaderStatus(fragmentShader);
|
|
glAttachShader(program, fragmentShader);
|
|
glDeleteShader(fragmentShader);
|
|
|
|
glBindAttribLocation(program, a_position, "a_position");
|
|
glBindAttribLocation(program, a_texcoord, "a_texcoord");
|
|
glBindAttribLocation(program, a_color, "a_color");
|
|
|
|
|
|
|
|
#ifdef USE_PGXP
|
|
glBindAttribLocation(program, a_zw, "a_zw");
|
|
#endif
|
|
|
|
glLinkProgram(program);
|
|
Shader_CheckProgramStatus(program);
|
|
|
|
GLint sampler = 0;
|
|
glUseProgram(program);
|
|
glUniform1iv(glGetUniformLocation(program, "s_texture"), 1, &sampler);
|
|
glUseProgram(0);
|
|
|
|
return program;
|
|
}
|
|
#else
|
|
#error
|
|
#endif
|
|
|
|
void Emulator_CreateGlobalShaders()
|
|
{
|
|
g_gte_shader_4 = Shader_Compile(gte_shader_4);
|
|
g_gte_shader_8 = Shader_Compile(gte_shader_8);
|
|
g_gte_shader_16 = Shader_Compile(gte_shader_16);
|
|
g_blit_shader = Shader_Compile(blit_shader);
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
u_Projection = glGetUniformLocation(g_gte_shader_4, "Projection");
|
|
#ifdef USE_PGXP
|
|
u_Projection3D = glGetUniformLocation(g_gte_shader_4, "Projection3D");
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
unsigned short vram[VRAM_WIDTH * VRAM_HEIGHT];
|
|
|
|
void Emulator_GenerateCommonTextures()
|
|
{
|
|
unsigned int pixelData = 0xFFFFFFFF;
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glGenTextures(1, &g_whiteTexture);
|
|
glBindTexture(GL_TEXTURE_2D, g_whiteTexture);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &pixelData);
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
#endif
|
|
}
|
|
|
|
int Emulator_Initialise()
|
|
{
|
|
SDL_memset(vram, 0, VRAM_WIDTH * VRAM_HEIGHT * sizeof(unsigned short));
|
|
Emulator_GenerateCommonTextures();
|
|
Emulator_CreateGlobalShaders();
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glDepthFunc(GL_LEQUAL);
|
|
glEnable(GL_STENCIL_TEST);
|
|
glBlendColor(0.5f, 0.5f, 0.5f, 0.25f);
|
|
|
|
// gen framebuffer
|
|
{
|
|
// make a special texture
|
|
// it will be resized later
|
|
glGenTextures(1, &g_fbTexture);
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, g_fbTexture);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
|
|
// default to VRAM size
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VRAM_WIDTH, VRAM_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
|
|
glGenFramebuffers(1, &g_glFramebuffer);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, g_glFramebuffer);
|
|
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, g_fbTexture, 0);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
}
|
|
|
|
// gen VRAM textures.
|
|
// double-buffered
|
|
{
|
|
glGenTextures(2, g_vramTextures);
|
|
g_curVramTexture = g_vramTextures[0];
|
|
g_curVramTextureIdx = 0;
|
|
|
|
for(int i = 0; i < 2; i++)
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, g_vramTextures[i]);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
|
|
// set storage size
|
|
glTexImage2D(GL_TEXTURE_2D, 0, VRAM_INTERNAL_FORMAT, VRAM_WIDTH, VRAM_HEIGHT, 0, VRAM_FORMAT, GL_UNSIGNED_BYTE, NULL);
|
|
}
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
|
|
// gen vertex buffer and index buffer
|
|
{
|
|
glGenBuffers(1, &g_glVertexBuffer);
|
|
glGenVertexArrays(1, &g_glVertexArray);
|
|
|
|
glBindVertexArray(g_glVertexArray);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, g_glVertexBuffer);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * MAX_NUM_POLY_BUFFER_VERTICES, NULL, GL_DYNAMIC_DRAW);
|
|
|
|
glEnableVertexAttribArray(a_position);
|
|
glEnableVertexAttribArray(a_texcoord);
|
|
glEnableVertexAttribArray(a_color);
|
|
|
|
#if defined(USE_PGXP)
|
|
glVertexAttribPointer(a_position, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), &((Vertex*)NULL)->x);
|
|
glVertexAttribPointer(a_zw, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), &((Vertex*)NULL)->z);
|
|
|
|
glEnableVertexAttribArray(a_zw);
|
|
#else
|
|
glVertexAttribPointer(a_position, 4, GL_SHORT, GL_FALSE, sizeof(Vertex), &((Vertex*)NULL)->x);
|
|
#endif
|
|
|
|
glVertexAttribPointer(a_texcoord, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof(Vertex), &((Vertex*)NULL)->u);
|
|
glVertexAttribPointer(a_color, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), &((Vertex*)NULL)->r);
|
|
|
|
glBindVertexArray(0);
|
|
}
|
|
#else
|
|
#error
|
|
#endif
|
|
|
|
Emulator_ResetDevice();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void Emulator_Ortho2D(float left, float right, float bottom, float top, float znear, float zfar)
|
|
{
|
|
float a = 2.0f / (right - left);
|
|
float b = 2.0f / (top - bottom);
|
|
float c = 2.0f / (znear - zfar);
|
|
|
|
float x = (left + right) / (left - right);
|
|
float y = (bottom + top) / (bottom - top);
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES) // -1..1
|
|
float z = (znear + zfar) / (znear - zfar);
|
|
#endif
|
|
|
|
float ortho[16] = {
|
|
a, 0, 0, 0,
|
|
0, b, 0, 0,
|
|
0, 0, c, 0,
|
|
x, y, z, 1
|
|
};
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glUniformMatrix4fv(u_Projection, 1, GL_FALSE, ortho);
|
|
#endif
|
|
}
|
|
|
|
void Emulator_Perspective3D(const float fov, const float width, const float height, const float zNear, const float zFar)
|
|
{
|
|
float sinF, cosF;
|
|
sinF = sinf(0.5f * fov);
|
|
cosF = cosf(0.5f * fov);
|
|
|
|
float h = cosF / sinF;
|
|
float w = (h * height) / width;
|
|
|
|
float persp[16] = {
|
|
w, 0, 0, 0,
|
|
0, h, 0, 0,
|
|
0, 0, (zFar + zNear) / (zFar - zNear), -(2 * zFar * zNear) / (zFar - zNear),
|
|
0, 0, 1, 0
|
|
};
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glUniformMatrix4fv(u_Projection3D, 1, GL_TRUE, persp);
|
|
#elif defined(D3D9)
|
|
d3ddev->SetVertexShaderConstantF(u_Projection3D, persp, 4);
|
|
#endif
|
|
}
|
|
|
|
void Emulator_SetupClipMode(const RECT16& rect)
|
|
{
|
|
// [A] isinterlaced dirty hack for widescreen
|
|
bool enabled = activeDispEnv.isinter || (rect.x - activeDispEnv.disp.x > 0 ||
|
|
rect.y - activeDispEnv.disp.y > 0 ||
|
|
rect.w < activeDispEnv.disp.w - 1 ||
|
|
rect.h < activeDispEnv.disp.h - 1);
|
|
|
|
float psxScreenW = activeDispEnv.disp.w;
|
|
float psxScreenH = activeDispEnv.disp.h;
|
|
|
|
// first map to 0..1
|
|
float clipRectX = (float)(rect.x - activeDispEnv.disp.x) / psxScreenW;
|
|
float clipRectY = (float)(rect.y - activeDispEnv.disp.y) / psxScreenH;
|
|
float clipRectW = (float)(rect.w) / psxScreenW;
|
|
float clipRectH = (float)(rect.h) / psxScreenH;
|
|
|
|
// then map to screen
|
|
|
|
{
|
|
clipRectX -= 0.5f;
|
|
#ifdef USE_PGXP
|
|
float emuScreenAspect = float(g_windowWidth) / float(g_windowHeight);
|
|
#else
|
|
float emuScreenAspect = (320.0f / 240.0f);
|
|
#endif
|
|
|
|
clipRectX /= PSX_SCREEN_ASPECT * emuScreenAspect;
|
|
clipRectW /= emuScreenAspect * PSX_SCREEN_ASPECT;
|
|
|
|
clipRectX += 0.5f;
|
|
}
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
if (!enabled)
|
|
{
|
|
glDisable(GL_SCISSOR_TEST);
|
|
return;
|
|
}
|
|
|
|
float flipOffset = g_windowHeight - clipRectH * (float)g_windowHeight;
|
|
|
|
glEnable(GL_SCISSOR_TEST);
|
|
glScissor(clipRectX * (float)g_windowWidth,
|
|
flipOffset - clipRectY * (float)g_windowHeight,
|
|
clipRectW * (float)g_windowWidth,
|
|
clipRectH * (float)g_windowHeight);
|
|
#endif
|
|
}
|
|
|
|
void Emulator_GetPSXWidescreenMappedViewport(RECT16* rect)
|
|
{
|
|
#ifdef USE_PGXP
|
|
float emuScreenAspect = float(g_windowWidth) / float(g_windowHeight);
|
|
|
|
float psxScreenW = activeDispEnv.disp.w;
|
|
float psxScreenH = activeDispEnv.disp.h;
|
|
|
|
rect->x = activeDispEnv.screen.x;
|
|
rect->y = activeDispEnv.screen.y;
|
|
|
|
rect->w = psxScreenW * emuScreenAspect * PSX_SCREEN_ASPECT; // windowWidth;
|
|
rect->h = psxScreenH; // windowHeight;
|
|
|
|
rect->x -= (rect->w - activeDispEnv.disp.w) / 2;
|
|
|
|
rect->w += rect->x;
|
|
#else
|
|
rect->x = activeDispEnv.screen.x;
|
|
rect->y = activeDispEnv.screen.y;
|
|
rect->w = activeDispEnv.disp.w;
|
|
rect->h = activeDispEnv.disp.h;
|
|
#endif
|
|
}
|
|
|
|
void Emulator_SetShader(const ShaderID &shader)
|
|
{
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glUseProgram(shader);
|
|
#else
|
|
#error
|
|
#endif
|
|
|
|
#ifdef USE_PGXP
|
|
float emuScreenAspect = float(g_windowWidth) / float(g_windowHeight);
|
|
Emulator_Ortho2D(-0.5f * emuScreenAspect * PSX_SCREEN_ASPECT, 0.5f * emuScreenAspect * PSX_SCREEN_ASPECT, 0.5f, -0.5f, -1.0f, 1.0f);
|
|
Emulator_Perspective3D(0.9265f, 1.0f, 1.0f / (emuScreenAspect * PSX_SCREEN_ASPECT), 1.0f, 1000.0f);
|
|
#else
|
|
Emulator_Ortho2D(0, activeDispEnv.disp.w, activeDispEnv.disp.h, 0, -1.0f, 1.0f);
|
|
#endif
|
|
}
|
|
|
|
void Emulator_SetTexture(TextureID texture, TexFormat texFormat)
|
|
{
|
|
switch (texFormat)
|
|
{
|
|
case TF_4_BIT :
|
|
Emulator_SetShader(g_gte_shader_4);
|
|
break;
|
|
case TF_8_BIT :
|
|
Emulator_SetShader(g_gte_shader_8);
|
|
break;
|
|
case TF_16_BIT :
|
|
Emulator_SetShader(g_gte_shader_16);
|
|
break;
|
|
}
|
|
|
|
if (g_texturelessMode) {
|
|
texture = g_whiteTexture;
|
|
}
|
|
|
|
if (g_lastBoundTexture == texture) {
|
|
return;
|
|
}
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glBindTexture(GL_TEXTURE_2D, texture);
|
|
#endif
|
|
|
|
g_lastBoundTexture = texture;
|
|
}
|
|
|
|
void Emulator_DestroyTexture(TextureID texture)
|
|
{
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glDeleteTextures(1, &texture);
|
|
#else
|
|
#error
|
|
#endif
|
|
}
|
|
|
|
void Emulator_Clear(int x, int y, int w, int h, unsigned char r, unsigned char g, unsigned char b)
|
|
{
|
|
// TODO clear rect if it's necessary
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
|
|
glClearColor(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
#endif
|
|
}
|
|
|
|
#define NOFILE 0
|
|
|
|
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__)
|
|
|
|
void Emulator_SaveVRAM(const char* outputFileName, int x, int y, int width, int height, int bReadFromFrameBuffer)
|
|
{
|
|
#if NOFILE
|
|
return;
|
|
#endif
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
|
|
#define FLIP_Y (VRAM_HEIGHT - i - 1)
|
|
|
|
#endif
|
|
|
|
FILE* fp = fopen(outputFileName, "wb");
|
|
if (fp == NULL)
|
|
return;
|
|
|
|
unsigned char TGAheader[12] = { 0,0,2,0,0,0,0,0,0,0,0,0 };
|
|
unsigned char header[6];
|
|
header[0] = (width % 256);
|
|
header[1] = (width / 256);
|
|
header[2] = (height % 256);
|
|
header[3] = (height / 256);
|
|
header[4] = 16;
|
|
header[5] = 0;
|
|
|
|
fwrite(TGAheader, sizeof(unsigned char), 12, fp);
|
|
fwrite(header, sizeof(unsigned char), 6, fp);
|
|
|
|
for (int i = 0; i < VRAM_HEIGHT; i++)
|
|
{
|
|
fwrite(vram + VRAM_WIDTH * FLIP_Y, sizeof(short), VRAM_WIDTH, fp );
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
#undef FLIP_Y
|
|
}
|
|
#endif
|
|
|
|
bool vram_need_update = true;
|
|
bool framebuffer_need_update = false;
|
|
|
|
void Emulator_ReadFramebufferDataToVRAM(int x, int y, int w, int h)
|
|
{
|
|
if (!framebuffer_need_update)
|
|
return;
|
|
|
|
framebuffer_need_update = false;
|
|
|
|
// now we can read it back to VRAM texture
|
|
{
|
|
ushort* fb = (ushort*)malloc(w * h * sizeof(ushort));
|
|
uint* data = (uint*)malloc(w * h * sizeof(uint));
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
// reat the texture
|
|
glBindTexture(GL_TEXTURE_2D, g_fbTexture);
|
|
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
#endif
|
|
|
|
uint* data_src = (uint*)data;
|
|
ushort* data_dst = (ushort*)fb;
|
|
|
|
for (int i = 0; i < h; i++)
|
|
{
|
|
for (int j = 0; j < w; j++)
|
|
{
|
|
uint c = *data_src++;
|
|
|
|
u_char b = ((c >> 3) & 0x1F);
|
|
u_char g = ((c >> 11) & 0x1F);
|
|
u_char r = ((c >> 19) & 0x1F);
|
|
|
|
*data_dst++ = r | (g << 5) | (b << 10) | 0x8000;
|
|
}
|
|
}
|
|
|
|
ushort* ptr = (ushort*)vram + VRAM_WIDTH * y + x;
|
|
|
|
for (int fy = 0; fy < h; fy++)
|
|
{
|
|
ushort* fb_ptr = fb + (h * fy / h) * w;
|
|
|
|
for (int fx = 0; fx < w; fx++)
|
|
ptr[fx] = fb_ptr[w * fx / w];
|
|
|
|
ptr += VRAM_WIDTH;
|
|
}
|
|
|
|
free(fb);
|
|
free(data);
|
|
}
|
|
|
|
vram_need_update = true;
|
|
}
|
|
|
|
void Emulator_StoreFrameBuffer(int x, int y, int w, int h)
|
|
{
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
// set storage size first
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, g_fbTexture);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, g_glFramebuffer);
|
|
|
|
// before drawing set source and target
|
|
{
|
|
// setup draw and read framebuffers
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); // source is backbuffer
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, g_glFramebuffer);
|
|
|
|
glBlitFramebuffer(0, 0, g_windowWidth, g_windowHeight, x, y + h, x + w, y, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
|
|
|
// done, unbind
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
|
}
|
|
|
|
// after drawing
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
glFlush();
|
|
#endif
|
|
}
|
|
|
|
void Emulator_CopyVRAM(unsigned short *src, int x, int y, int w, int h, int dst_x, int dst_y)
|
|
{
|
|
vram_need_update = true;
|
|
|
|
int stride = w;
|
|
|
|
if (!src)
|
|
{
|
|
framebuffer_need_update = true;
|
|
|
|
src = vram;
|
|
stride = VRAM_WIDTH;
|
|
}
|
|
|
|
src += x + y * stride;
|
|
|
|
unsigned short *dst = vram + dst_x + dst_y * VRAM_WIDTH;
|
|
|
|
for (int i = 0; i < h; i++) {
|
|
SDL_memcpy(dst, src, w * sizeof(short));
|
|
dst += VRAM_WIDTH;
|
|
src += stride;
|
|
}
|
|
}
|
|
|
|
void Emulator_ReadVRAM(unsigned short *dst, int x, int y, int dst_w, int dst_h)
|
|
{
|
|
Emulator_ReadFramebufferDataToVRAM(activeDispEnv.disp.x, activeDispEnv.disp.y, activeDispEnv.disp.w, activeDispEnv.disp.h);
|
|
|
|
unsigned short *src = vram + x + VRAM_WIDTH * y;
|
|
|
|
for (int i = 0; i < dst_h; i++) {
|
|
SDL_memcpy(dst, src, dst_w * sizeof(short));
|
|
dst += dst_w;
|
|
src += VRAM_WIDTH;
|
|
}
|
|
}
|
|
|
|
void Emulator_UpdateVRAM()
|
|
{
|
|
if (!vram_need_update)
|
|
return;
|
|
|
|
vram_need_update = false;
|
|
|
|
Emulator_ReadFramebufferDataToVRAM(activeDispEnv.disp.x, activeDispEnv.disp.y, activeDispEnv.disp.w, activeDispEnv.disp.h);
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
g_curVramTexture = g_vramTextures[g_curVramTextureIdx];
|
|
|
|
glBindTexture(GL_TEXTURE_2D, g_curVramTexture);
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, VRAM_WIDTH, VRAM_HEIGHT, VRAM_FORMAT, GL_UNSIGNED_BYTE, vram);
|
|
|
|
g_curVramTextureIdx++;
|
|
g_curVramTextureIdx &= 1;
|
|
#endif
|
|
}
|
|
|
|
void Emulator_BlitVRAM()
|
|
{
|
|
#if 0
|
|
if (activeDispEnv.isinter)
|
|
{
|
|
//Emulator_StoreFrameBuffer(activeDispEnv.disp.x, activeDispEnv.disp.y, activeDispEnv.disp.w, activeDispEnv.disp.h);
|
|
return;
|
|
}
|
|
|
|
Emulator_SetTexture(vramTexture, (TexFormat)-1); // avoid shader setup
|
|
Emulator_SetShader(g_blit_shader);
|
|
|
|
u_char l = activeDispEnv.disp.x / 8;
|
|
u_char t = activeDispEnv.disp.y / 8;
|
|
u_char r = activeDispEnv.disp.w / 8 + l;
|
|
u_char b = activeDispEnv.disp.h / 8 + t;
|
|
|
|
Vertex blit_vertices[] =
|
|
{
|
|
{ +1, +1, 0, 0, r, t, 0, 0, 0, 0, 0, 0 },
|
|
{ -1, -1, 0, 0, l, b, 0, 0, 0, 0, 0, 0 },
|
|
{ -1, +1, 0, 0, l, t, 0, 0, 0, 0, 0, 0 },
|
|
|
|
{ +1, -1, 0, 0, r, b, 0, 0, 0, 0, 0, 0 },
|
|
{ -1, -1, 0, 0, l, b, 0, 0, 0, 0, 0, 0 },
|
|
{ +1, +1, 0, 0, r, t, 0, 0, 0, 0, 0, 0 },
|
|
};
|
|
|
|
Emulator_UpdateVertexBuffer(blit_vertices, 6);
|
|
Emulator_SetBlendMode(BM_NONE);
|
|
Emulator_DrawTriangles(0, 2);
|
|
#endif
|
|
}
|
|
|
|
void Emulator_DoDebugKeys(int nKey, bool down); // forward decl
|
|
void Emulator_DoDebugMouseMotion(int x, int y);
|
|
|
|
void Emulator_DoPollEvent()
|
|
{
|
|
SDL_Event event;
|
|
while (SDL_PollEvent(&event))
|
|
{
|
|
switch (event.type)
|
|
{
|
|
case SDL_CONTROLLERDEVICEADDED:
|
|
padHandle[event.jbutton.which] = SDL_GameControllerOpen(event.cdevice.which);
|
|
break;
|
|
case SDL_CONTROLLERDEVICEREMOVED:
|
|
SDL_GameControllerClose(padHandle[event.cdevice.which]);
|
|
break;
|
|
case SDL_QUIT:
|
|
Emulator_ShutDown();
|
|
break;
|
|
case SDL_WINDOWEVENT:
|
|
switch (event.window.event)
|
|
{
|
|
case SDL_WINDOWEVENT_RESIZED:
|
|
g_windowWidth = event.window.data1;
|
|
g_windowHeight = event.window.data2;
|
|
Emulator_ResetDevice();
|
|
break;
|
|
case SDL_WINDOWEVENT_CLOSE:
|
|
Emulator_ShutDown();
|
|
break;
|
|
}
|
|
break;
|
|
case SDL_MOUSEMOTION:
|
|
|
|
Emulator_DoDebugMouseMotion(event.motion.x, event.motion.y);
|
|
break;
|
|
case SDL_KEYDOWN:
|
|
case SDL_KEYUP:
|
|
{
|
|
int nKey = event.key.keysym.scancode;
|
|
|
|
// lshift/right shift
|
|
if (nKey == SDL_SCANCODE_RSHIFT)
|
|
nKey = SDL_SCANCODE_LSHIFT;
|
|
else if (nKey == SDL_SCANCODE_RCTRL)
|
|
nKey = SDL_SCANCODE_LCTRL;
|
|
else if (nKey == SDL_SCANCODE_RALT)
|
|
nKey = SDL_SCANCODE_LALT;
|
|
|
|
Emulator_DoDebugKeys(nKey, (event.type == SDL_KEYUP) ? false : true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool begin_scene_flag = false;
|
|
|
|
bool Emulator_BeginScene()
|
|
{
|
|
Emulator_DoPollEvent();
|
|
|
|
if (begin_scene_flag)
|
|
return false;
|
|
|
|
assert(!begin_scene_flag);
|
|
|
|
g_lastBoundTexture = NULL;
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glBindVertexArray(g_glVertexArray);
|
|
|
|
glClearDepth(1.0f);
|
|
glClear(GL_DEPTH_BUFFER_BIT);
|
|
glClear(GL_STENCIL_BUFFER_BIT);
|
|
#endif
|
|
|
|
Emulator_UpdateVRAM();
|
|
Emulator_SetViewPort(0, 0, g_windowWidth, g_windowHeight);
|
|
|
|
Emulator_SetShader(g_gte_shader_4);
|
|
Emulator_Ortho2D(0.0f, activeDispEnv.disp.w, activeDispEnv.disp.h, 0.0f, -1.0f, 1.0f);
|
|
|
|
begin_scene_flag = true;
|
|
|
|
if (g_wireframeMode)
|
|
{
|
|
Emulator_SetWireframe(true);
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
#endif
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__)
|
|
void Emulator_TakeScreenshot()
|
|
{
|
|
unsigned char* pixels = new unsigned char[g_windowWidth * g_windowHeight * 4];
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glReadPixels(0, 0, g_windowWidth, g_windowHeight, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
|
|
#endif
|
|
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(pixels, g_windowWidth, g_windowHeight, 8 * 4, g_windowWidth * 4, 0, 0, 0, 0);
|
|
|
|
SDL_SaveBMP(surface, "SCREENSHOT.BMP");
|
|
SDL_FreeSurface(surface);
|
|
|
|
delete[] pixels;
|
|
}
|
|
#endif
|
|
|
|
GameDebugKeysHandlerFunc gameDebugKeys = NULL;
|
|
GameDebugMouseHandlerFunc gameDebugMouse = NULL;
|
|
int activeControllers = 0x1;
|
|
|
|
void Emulator_DoDebugMouseMotion(int x, int y)
|
|
{
|
|
if (gameDebugMouse)
|
|
gameDebugMouse(x, y);
|
|
}
|
|
|
|
void Emulator_DoDebugKeys(int nKey, bool down)
|
|
{
|
|
if(gameDebugKeys)
|
|
gameDebugKeys(nKey, down);
|
|
|
|
if (nKey == SDL_SCANCODE_BACKSPACE)
|
|
{
|
|
if(down)
|
|
{
|
|
if (g_swapInterval != 0)
|
|
{
|
|
g_swapInterval = 0;
|
|
Emulator_ResetDevice();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (g_swapInterval != SWAP_INTERVAL)
|
|
{
|
|
g_swapInterval = SWAP_INTERVAL;
|
|
Emulator_ResetDevice();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!down)
|
|
{
|
|
switch (nKey)
|
|
{
|
|
#ifdef _DEBUG
|
|
case SDL_SCANCODE_F1:
|
|
g_wireframeMode ^= 1;
|
|
eprintf("wireframe mode: %d\n", g_wireframeMode);
|
|
break;
|
|
|
|
case SDL_SCANCODE_F2:
|
|
g_texturelessMode ^= 1;
|
|
eprintf("textureless mode: %d\n", g_texturelessMode);
|
|
break;
|
|
|
|
case SDL_SCANCODE_F3:
|
|
g_emulatorPaused ^= 1;
|
|
break;
|
|
|
|
case SDL_SCANCODE_UP:
|
|
case SDL_SCANCODE_DOWN:
|
|
if (g_emulatorPaused)
|
|
{
|
|
g_polygonSelected += (nKey == SDL_SCANCODE_UP) ? 3 : -3;
|
|
}
|
|
break;
|
|
case SDL_SCANCODE_F10:
|
|
eprintf("saving VRAM.TGA\n");
|
|
Emulator_SaveVRAM("VRAM.TGA", 0, 0, VRAM_WIDTH, VRAM_HEIGHT, TRUE);
|
|
break;
|
|
#endif
|
|
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__)
|
|
case SDL_SCANCODE_F12:
|
|
eprintf("Saving screenshot\n");
|
|
Emulator_TakeScreenshot();
|
|
break;
|
|
#endif
|
|
case SDL_SCANCODE_F4:
|
|
|
|
activeControllers++;
|
|
activeControllers = activeControllers % 4;
|
|
|
|
if (activeControllers == 0)
|
|
activeControllers++;
|
|
|
|
eprintf("Active keyboard controller: %d\n", activeControllers);
|
|
break;
|
|
case SDL_SCANCODE_F5:
|
|
g_pgxpTextureCorrection ^= 1;
|
|
break;
|
|
case SDL_SCANCODE_F6:
|
|
g_pgxpZBuffer ^= 1;
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void Emulator_UpdateInput()
|
|
{
|
|
// also poll events here
|
|
Emulator_DoPollEvent();
|
|
|
|
InternalPadUpdates();
|
|
}
|
|
|
|
unsigned int Emulator_GetFPS()
|
|
{
|
|
#define FPS_INTERVAL 1.0
|
|
|
|
static unsigned int lastTime = SDL_GetTicks();
|
|
static unsigned int currentFps = 0;
|
|
static unsigned int passedFrames = 0;
|
|
|
|
passedFrames++;
|
|
if (lastTime < SDL_GetTicks() - FPS_INTERVAL * 1000)
|
|
{
|
|
lastTime = SDL_GetTicks();
|
|
currentFps = passedFrames;
|
|
passedFrames = 0;
|
|
}
|
|
|
|
return currentFps;
|
|
}
|
|
|
|
void Emulator_SwapWindow()
|
|
{
|
|
//Emulator_WaitForTimestep(1);
|
|
|
|
#if defined(RO_DOUBLE_BUFFERED)
|
|
#if defined(RENDERER_OGL)
|
|
SDL_GL_SwapWindow(g_window);
|
|
#elif defined(OGLES)
|
|
eglSwapBuffers(eglDisplay, eglSurface);
|
|
#endif
|
|
#else
|
|
glFinish();
|
|
#endif
|
|
}
|
|
|
|
void Emulator_WaitForTimestep(int count)
|
|
{
|
|
const long vmode = GetVideoMode();
|
|
const double timestep = vmode == MODE_NTSC ? FIXED_TIME_STEP_NTSC : FIXED_TIME_STEP_PAL;
|
|
|
|
// additional wait for swap
|
|
if (g_swapInterval > 0)
|
|
{
|
|
double delta = 0;
|
|
do
|
|
{
|
|
SDL_Delay(0); // yield
|
|
delta += Emulator_GetHPCTime(&g_swapTimer, 0);
|
|
} while (delta < timestep * count);
|
|
|
|
Emulator_GetHPCTime(&g_swapTimer, 1);
|
|
}
|
|
}
|
|
|
|
void Emulator_EndScene()
|
|
{
|
|
if (!begin_scene_flag)
|
|
return;
|
|
|
|
assert(begin_scene_flag);
|
|
|
|
if (g_wireframeMode)
|
|
{
|
|
Emulator_SetWireframe(false);
|
|
}
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glBindVertexArray(0);
|
|
#endif
|
|
|
|
begin_scene_flag = false;
|
|
|
|
Emulator_SwapWindow();
|
|
|
|
Emulator_StoreFrameBuffer(activeDispEnv.disp.x, activeDispEnv.disp.y, activeDispEnv.disp.w, activeDispEnv.disp.h);
|
|
}
|
|
|
|
void Emulator_ShutDown()
|
|
{
|
|
// quit vblank thread
|
|
if (g_vblankThread)
|
|
{
|
|
g_stopVblank = true;
|
|
|
|
int returnValue;
|
|
SDL_WaitThread(g_vblankThread, &returnValue);
|
|
}
|
|
|
|
if(g_vblankMutex)
|
|
SDL_DestroyMutex(g_vblankMutex);
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
g_curVramTexture = 0;
|
|
Emulator_DestroyTexture(g_vramTextures[0]);
|
|
Emulator_DestroyTexture(g_vramTextures[1]);
|
|
Emulator_DestroyTexture(g_whiteTexture);
|
|
#endif
|
|
|
|
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
|
|
|
SDL_DestroyWindow(g_window);
|
|
SDL_Quit();
|
|
exit(0);
|
|
}
|
|
|
|
int g_PreviousBlendMode = BM_NONE;
|
|
|
|
void Emulator_EnableDepth(int enable)
|
|
{
|
|
if(enable && g_pgxpZBuffer)
|
|
{
|
|
glEnable(GL_DEPTH_TEST);
|
|
//glDepthMask(GL_TRUE);
|
|
}
|
|
else
|
|
{
|
|
glDisable(GL_DEPTH_TEST);
|
|
//glDepthMask(GL_FALSE);
|
|
}
|
|
}
|
|
|
|
void Emulator_SetStencilMode(int drawPrim)
|
|
{
|
|
if(drawPrim)
|
|
{
|
|
glStencilFunc( GL_ALWAYS, 1, 0x10 );
|
|
glStencilOp( GL_REPLACE, GL_REPLACE, GL_REPLACE );
|
|
}
|
|
else
|
|
{
|
|
glStencilFunc( GL_NOTEQUAL, 1, 0xFF );
|
|
glStencilOp( GL_REPLACE, GL_KEEP, GL_KEEP );
|
|
}
|
|
}
|
|
|
|
void Emulator_SetBlendMode(BlendMode blendMode)
|
|
{
|
|
if (g_PreviousBlendMode == blendMode)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
if (g_PreviousBlendMode == BM_NONE)
|
|
{
|
|
glEnable(GL_BLEND);
|
|
}
|
|
|
|
switch (blendMode)
|
|
{
|
|
case BM_NONE:
|
|
glDisable(GL_BLEND);
|
|
Emulator_EnableDepth(TRUE);
|
|
break;
|
|
case BM_AVERAGE:
|
|
glBlendFunc(GL_CONSTANT_COLOR, GL_CONSTANT_COLOR);
|
|
glBlendEquation(GL_FUNC_ADD);
|
|
Emulator_EnableDepth(FALSE);
|
|
break;
|
|
case BM_ADD:
|
|
glBlendFunc(GL_ONE, GL_ONE);
|
|
glBlendEquation(GL_FUNC_ADD);
|
|
Emulator_EnableDepth(FALSE);
|
|
break;
|
|
case BM_SUBTRACT:
|
|
glBlendFunc(GL_ONE, GL_ONE);
|
|
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
|
|
Emulator_EnableDepth(FALSE);
|
|
break;
|
|
case BM_ADD_QUATER_SOURCE:
|
|
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE);
|
|
glBlendEquation(GL_FUNC_ADD);
|
|
Emulator_EnableDepth(FALSE);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
g_PreviousBlendMode = blendMode;
|
|
}
|
|
|
|
void Emulator_SetPolygonOffset(float ofs)
|
|
{
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
if (ofs == 0.0f)
|
|
{
|
|
glDisable(GL_POLYGON_OFFSET_FILL);
|
|
}
|
|
else
|
|
{
|
|
glEnable(GL_POLYGON_OFFSET_FILL);
|
|
glPolygonOffset(0.0f, ofs);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Emulator_SetViewPort(int x, int y, int width, int height)
|
|
{
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glViewport(x, y, width, height);
|
|
#endif
|
|
}
|
|
|
|
void Emulator_SetRenderTarget(const RenderTargetID &frameBufferObject)
|
|
{
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glBindFramebuffer(GL_FRAMEBUFFER, frameBufferObject);
|
|
#else
|
|
#error
|
|
#endif
|
|
}
|
|
|
|
void Emulator_SetWireframe(bool enable)
|
|
{
|
|
#if defined(RENDERER_OGL)
|
|
glPolygonMode(GL_FRONT_AND_BACK, enable ? GL_LINE : GL_FILL);
|
|
#endif
|
|
}
|
|
|
|
void Emulator_UpdateVertexBuffer(const Vertex *vertices, int num_vertices)
|
|
{
|
|
assert(num_vertices <= MAX_NUM_POLY_BUFFER_VERTICES);
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glBufferSubData(GL_ARRAY_BUFFER, 0, num_vertices * sizeof(Vertex), vertices);
|
|
#else
|
|
#error
|
|
#endif
|
|
}
|
|
|
|
void Emulator_DrawTriangles(int start_vertex, int triangles)
|
|
{
|
|
#if defined(RENDERER_OGL) || defined(OGLES)
|
|
glDrawArrays(GL_TRIANGLES, start_vertex, triangles * 3);
|
|
#else
|
|
#error
|
|
#endif
|
|
|
|
//framebuffer_need_update = true;
|
|
}
|