Instantly share code, notes, and snippets.

Embed
What would you like to do?
//=============================================================================
//
// Adventure Game Studio (AGS)
//
// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others
// The full list of copyright holders can be found in the Copyright.txt
// file, which is part of this source code distribution.
//
// The AGS source code is provided under the Artistic License 2.0.
// A copy of this license can be found in the file License.txt and at
// http://www.opensource.org/licenses/artistic-license-2.0.php
//
//=============================================================================
#if defined(WINDOWS_VERSION) || defined(ANDROID_VERSION) || defined(IOS_VERSION)
#include <algorithm>
#include "gfx/ali3dexception.h"
#include "gfx/ali3dogl.h"
#include "gfx/gfxfilter_ogl.h"
#include "gfx/gfxfilter_aaogl.h"
#include "main/main_allegro.h"
#include "platform/base/agsplatformdriver.h"
//
// NOTE: following external variables are used by the mobile ports:
// TODO: parse them during config
//
// psp_gfx_scaling - scaling style:
// * 0 - no scaling
// * 1 - stretch and preserve aspect ratio
// * 2 - stretch to whole screen
//
// psp_gfx_smoothing - scaling filter:
// * 0 - nearest-neighbour
// * 1 - linear
//
// psp_gfx_renderer - rendering mode
// * 1 - render directly to screen
// * 2 - render to texture first and then to screen
//
// psp_gfx_super_sampling - enable super sampling
//
#if defined(WINDOWS_VERSION)
int device_screen_initialized = 1;
const char* fbo_extension_string = "GL_EXT_framebuffer_object";
const char* vsync_extension_string = "WGL_EXT_swap_control";
// TODO: linking to glew32 library might be a better option
PFNGLGENFRAMEBUFFERSEXTPROC glGenFramebuffersEXT = 0;
PFNGLDELETEFRAMEBUFFERSEXTPROC glDeleteFramebuffersEXT = 0;
PFNGLBINDFRAMEBUFFEREXTPROC glBindFramebufferEXT = 0;
PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC glCheckFramebufferStatusEXT = 0;
PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC glGetFramebufferAttachmentParameterivEXT = 0;
PFNGLGENERATEMIPMAPEXTPROC glGenerateMipmapEXT = 0;
PFNGLFRAMEBUFFERTEXTURE2DEXTPROC glFramebufferTexture2DEXT = 0;
PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC glFramebufferRenderbufferEXT = 0;
PFNWGLSWAPINTERVALEXTPROC glSwapIntervalEXT = 0;
// Shaders stuff
PFNGLCREATESHADERPROC glCreateShader = 0;
PFNGLSHADERSOURCEPROC glShaderSource = 0;
PFNGLCOMPILESHADERPROC glCompileShader = 0;
PFNGLGETSHADERIVPROC glGetShaderiv = 0;
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = 0;
PFNGLATTACHSHADERPROC glAttachShader = 0;
PFNGLDETACHSHADERPROC glDetachShader = 0;
PFNGLDELETESHADERPROC glDeleteShader = 0;
PFNGLCREATEPROGRAMPROC glCreateProgram = 0;
PFNGLLINKPROGRAMPROC glLinkProgram = 0;
PFNGLGETPROGRAMIVPROC glGetProgramiv = 0;
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = 0;
PFNGLUSEPROGRAMPROC glUseProgram = 0;
PFNGLDELETEPROGRAMPROC glDeleteProgram = 0;
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = 0;
PFNGLUNIFORM1IPROC glUniform1i = 0;
PFNGLUNIFORM1FPROC glUniform1f = 0;
PFNGLUNIFORM3FPROC glUniform3f = 0;
#elif defined(ANDROID_VERSION)
#define glOrtho glOrthof
#define GL_CLAMP GL_CLAMP_TO_EDGE
// Defined in Allegro
extern "C"
{
void android_swap_buffers();
void android_create_screen(int width, int height, int color_depth);
void android_mouse_setup(int left, int right, int top, int bottom, float scaling_x, float scaling_y);
}
extern "C" void android_debug_printf(char* format, ...);
extern int psp_gfx_smoothing;
extern int psp_gfx_scaling;
extern int psp_gfx_renderer;
extern int psp_gfx_super_sampling;
extern unsigned int android_screen_physical_width;
extern unsigned int android_screen_physical_height;
extern int android_screen_initialized;
#define device_screen_initialized android_screen_initialized
#define device_mouse_setup android_mouse_setup
#define device_swap_buffers android_swap_buffers
const char* fbo_extension_string = "GL_OES_framebuffer_object";
#define glGenFramebuffersEXT glGenFramebuffersOES
#define glDeleteFramebuffersEXT glDeleteFramebuffersOES
#define glBindFramebufferEXT glBindFramebufferOES
#define glCheckFramebufferStatusEXT glCheckFramebufferStatusOES
#define glGetFramebufferAttachmentParameterivEXT glGetFramebufferAttachmentParameterivOES
#define glGenerateMipmapEXT glGenerateMipmapOES
#define glFramebufferTexture2DEXT glFramebufferTexture2DOES
#define glFramebufferRenderbufferEXT glFramebufferRenderbufferOES
#define GL_FRAMEBUFFER_EXT GL_FRAMEBUFFER_OES
#define GL_COLOR_ATTACHMENT0_EXT GL_COLOR_ATTACHMENT0_OES
#elif defined(IOS_VERSION)
extern "C"
{
void ios_swap_buffers();
void ios_select_buffer();
void ios_create_screen();
void ios_mouse_setup(int left, int right, int top, int bottom, float scaling_x, float scaling_y);
}
#define glOrtho glOrthof
#define GL_CLAMP GL_CLAMP_TO_EDGE
extern int psp_gfx_smoothing;
extern int psp_gfx_scaling;
extern int psp_gfx_renderer;
extern int psp_gfx_super_sampling;
extern unsigned int ios_screen_physical_width;
extern unsigned int ios_screen_physical_height;
extern int ios_screen_initialized;
#define device_screen_initialized ios_screen_initialized
#define device_mouse_setup ios_mouse_setup
#define device_swap_buffers ios_swap_buffers
const char* fbo_extension_string = "GL_OES_framebuffer_object";
#define glGenFramebuffersEXT glGenFramebuffersOES
#define glDeleteFramebuffersEXT glDeleteFramebuffersOES
#define glBindFramebufferEXT glBindFramebufferOES
#define glCheckFramebufferStatusEXT glCheckFramebufferStatusOES
#define glGetFramebufferAttachmentParameterivEXT glGetFramebufferAttachmentParameterivOES
#define glGenerateMipmapEXT glGenerateMipmapOES
#define glFramebufferTexture2DEXT glFramebufferTexture2DOES
#define glFramebufferRenderbufferEXT glFramebufferRenderbufferOES
#define GL_FRAMEBUFFER_EXT GL_FRAMEBUFFER_OES
#define GL_COLOR_ATTACHMENT0_EXT GL_COLOR_ATTACHMENT0_OES
#endif
namespace AGS
{
namespace Engine
{
namespace OGL
{
using namespace AGS::Common;
void ogl_dummy_vsync() { }
#define algetr32(xx) ((xx >> _rgb_r_shift_32) & 0xFF)
#define algetg32(xx) ((xx >> _rgb_g_shift_32) & 0xFF)
#define algetb32(xx) ((xx >> _rgb_b_shift_32) & 0xFF)
#define algeta32(xx) ((xx >> _rgb_a_shift_32) & 0xFF)
#define algetr15(xx) ((xx >> _rgb_r_shift_15) & 0x1F)
#define algetg15(xx) ((xx >> _rgb_g_shift_15) & 0x1F)
#define algetb15(xx) ((xx >> _rgb_b_shift_15) & 0x1F)
#define GFX_OPENGL AL_ID('O','G','L',' ')
GFX_DRIVER gfx_opengl =
{
GFX_OPENGL,
empty_string,
empty_string,
"OpenGL",
NULL, // init
NULL, // exit
NULL, // AL_METHOD(int, scroll, (int x, int y));
ogl_dummy_vsync, // vsync
NULL, // setpalette
NULL, // AL_METHOD(int, request_scroll, (int x, int y));
NULL, // AL_METHOD(int, poll_scroll, (void));
NULL, // AL_METHOD(void, enable_triple_buffer, (void));
NULL, //create_video_bitmap
NULL, //destroy_video_bitmap
NULL, //show_video_bitmap
NULL,
NULL, //gfx_directx_create_system_bitmap,
NULL, //gfx_directx_destroy_system_bitmap,
NULL, //gfx_directx_set_mouse_sprite,
NULL, //gfx_directx_show_mouse,
NULL, //gfx_directx_hide_mouse,
NULL, //gfx_directx_move_mouse,
NULL, // AL_METHOD(void, drawing_mode, (void));
NULL, // AL_METHOD(void, save_video_state, (void*));
NULL, // AL_METHOD(void, restore_video_state, (void*));
NULL, // AL_METHOD(void, set_blender_mode, (int mode, int r, int g, int b, int a));
NULL, // AL_METHOD(int, fetch_mode_list, (void));
0, 0, // int w, h;
FALSE, // int linear;
0, // long bank_size;
0, // long bank_gran;
0, // long vid_mem;
0, // long vid_phys_base;
TRUE // int windowed;
};
void OGLBitmap::Dispose()
{
if (_tiles != NULL)
{
for (int i = 0; i < _numTiles; i++)
glDeleteTextures(1, &(_tiles[i].texture));
free(_tiles);
_tiles = NULL;
_numTiles = 0;
}
if (_vertex != NULL)
{
free(_vertex);
_vertex = NULL;
}
}
OGLGraphicsDriver::ShaderProgram::ShaderProgram() : Program(0), SamplerVar(0), ColorVar(0), AuxVar(0) {}
OGLGraphicsDriver::OGLGraphicsDriver()
{
#if defined (WINDOWS_VERSION)
_hDC = NULL;
_hRC = NULL;
_hWnd = NULL;
_hInstance = NULL;
device_screen_physical_width = 0;
device_screen_physical_height = 0;
#elif defined (ANRDOID_VERSION)
device_screen_physical_width = android_screen_physical_width
device_screen_physical_height = android_screen_physical_height
#elif defined (IOS_VERSION)
device_screen_physical_width = ios_screen_physical_width
device_screen_physical_height = ios_screen_physical_height
#endif
_firstTimeInit = false;
_backbuffer = 0;
_fbo = 0;
_tint_red = 0;
_tint_green = 0;
_tint_blue = 0;
_screenTintLayer = NULL;
_screenTintLayerDDB = NULL;
_screenTintSprite.skip = true;
_screenTintSprite.x = 0;
_screenTintSprite.y = 0;
_legacyPixelShader = false;
flipTypeLastTime = kFlip_None;
_can_render_to_texture = false;
_do_render_to_texture = false;
_super_sampling = 1;
set_up_default_vertices();
}
void OGLGraphicsDriver::set_up_default_vertices()
{
std::fill(_backbuffer_vertices, _backbuffer_vertices + sizeof(_backbuffer_vertices) / sizeof(GLfloat), 0.0f);
std::fill(_backbuffer_texture_coordinates, _backbuffer_texture_coordinates + sizeof(_backbuffer_texture_coordinates) / sizeof(GLfloat), 0.0f);
defaultVertices[0].position.x = 0.0f;
defaultVertices[0].position.y = 0.0f;
defaultVertices[0].tu=0.0;
defaultVertices[0].tv=0.0;
defaultVertices[1].position.x = 1.0f;
defaultVertices[1].position.y = 0.0f;
defaultVertices[1].tu=1.0;
defaultVertices[1].tv=0.0;
defaultVertices[2].position.x = 0.0f;
defaultVertices[2].position.y = -1.0f;
defaultVertices[2].tu=0.0;
defaultVertices[2].tv=1.0;
defaultVertices[3].position.x = 1.0f;
defaultVertices[3].position.y = -1.0f;
defaultVertices[3].tu=1.0;
defaultVertices[3].tv=1.0;
}
#if defined (WINDOWS_VERSION)
void OGLGraphicsDriver::create_desktop_screen(int width, int height, int depth)
{
device_screen_physical_width = width;
device_screen_physical_height = height;
}
#endif
void OGLGraphicsDriver::Vsync()
{
// do nothing on OpenGL
}
void OGLGraphicsDriver::RenderSpritesAtScreenResolution(bool enabled)
{
if (_can_render_to_texture)
_do_render_to_texture = !enabled;
if (_do_render_to_texture)
glDisable(GL_SCISSOR_TEST);
}
bool OGLGraphicsDriver::IsModeSupported(const DisplayMode &mode)
{
if (mode.Width <= 0 || mode.Height <= 0 || mode.ColorDepth <= 0)
{
set_allegro_error("Invalid resolution parameters: %d x %d x %d", mode.Width, mode.Height, mode.ColorDepth);
return false;
}
return true;
}
bool OGLGraphicsDriver::SupportsGammaControl()
{
return false;
}
void OGLGraphicsDriver::SetGamma(int newGamma)
{
}
void OGLGraphicsDriver::SetGraphicsFilter(POGLFilter filter)
{
_filter = filter;
OnSetFilter();
// Creating ddbs references filter properties at some point,
// so we have to redo this part of initialization here.
create_screen_tint_bitmap();
}
void OGLGraphicsDriver::SetTintMethod(TintMethod method)
{
_legacyPixelShader = (method == TintReColourise);
}
void OGLGraphicsDriver::FirstTimeInit()
{
// It was told that GL_VERSION is supposed to begin with digits and may have
// custom string after, but mobile version of OpenGL seem to have different
// version string format.
String ogl_v_str = (const char*)glGetString(GL_VERSION);
String digits = "1234567890";
const char *digits_ptr = std::find_first_of(ogl_v_str.GetCStr(), ogl_v_str.GetCStr() + ogl_v_str.GetLength(),
digits.GetCStr(), digits.GetCStr() + digits.GetLength());
if (digits_ptr < ogl_v_str.GetCStr() + ogl_v_str.GetLength())
_oglVersion.SetFromString(digits_ptr);
Debug::Printf(kDbgMsg_Init, "Running OpenGL: %s", ogl_v_str.GetCStr());
TestVSync();
TestRenderToTexture();
CreateShaders();
_firstTimeInit = true;
}
bool OGLGraphicsDriver::InitGlScreen(const DisplayMode &mode)
{
#if defined(ANDROID_VERSION)
android_create_screen(mode.Width, mode.Height, mode.ColorDepth);
#elif defined(IOS_VERSION)
ios_create_screen();
ios_select_buffer();
#elif defined (WINDOWS_VERSION)
if (!mode.Windowed)
{
if (platform->EnterFullscreenMode(mode))
platform->AdjustWindowStyleForFullscreen();
}
// NOTE: adjust_window may leave task bar visible, so we do not use it for fullscreen mode
if (mode.Windowed && adjust_window(mode.Width, mode.Height) != 0)
{
set_allegro_error("Window size not supported");
return false;
}
_hWnd = win_get_window();
if (!(_hDC = GetDC(_hWnd)))
return false;
// First check if we need to recreate GL context, this will only be
// required if different color depth is requested.
if (_hRC)
{
GLuint pixel_fmt = GetPixelFormat(_hDC);
PIXELFORMATDESCRIPTOR pfd;
DescribePixelFormat(_hDC, pixel_fmt, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
if (pfd.cColorBits != mode.ColorDepth)
{
DeleteGlContext();
}
}
if (!_hRC)
{
if (!CreateGlContext(mode))
return false;
}
create_desktop_screen(mode.Width, mode.Height, mode.ColorDepth);
win_grab_input();
#endif
gfx_driver = &gfx_opengl;
return true;
}
void OGLGraphicsDriver::InitGlParams(const DisplayMode &mode)
{
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glShadeModel(GL_FLAT);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, device_screen_physical_width, device_screen_physical_height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, device_screen_physical_width, 0, device_screen_physical_height, 0, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
#if !defined (ANDROID_VERSION)
if (glSwapIntervalEXT)
{
if(mode.Vsync)
glSwapIntervalEXT(1);
else
glSwapIntervalEXT(0);
}
#endif
}
bool OGLGraphicsDriver::CreateGlContext(const DisplayMode &mode)
{
#if defined (WINDOWS_VERSION)
PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
mode.ColorDepth,
0, 0, 0, 0, 0, 0,
0,
0,
0,
0, 0, 0, 0,
0,
0,
0,
PFD_MAIN_PLANE,
0,
0, 0, 0
};
_oldPixelFormat = GetPixelFormat(_hDC);
DescribePixelFormat(_hDC, _oldPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &_oldPixelFormatDesc);
GLuint pixel_fmt;
if (!(pixel_fmt = ChoosePixelFormat(_hDC, &pfd)))
return false;
if (!SetPixelFormat(_hDC, pixel_fmt, &pfd))
return false;
if (!(_hRC = wglCreateContext(_hDC)))
return false;
if(!wglMakeCurrent(_hDC, _hRC))
return false;
#endif // WINDOWS_VERSION
return true;
}
void OGLGraphicsDriver::DeleteGlContext()
{
#if defined (WINDOWS_VERSION)
if (_hRC)
{
wglMakeCurrent(NULL, NULL);
wglDeleteContext(_hRC);
_hRC = NULL;
}
if (_oldPixelFormat > 0)
SetPixelFormat(_hDC, _oldPixelFormat, &_oldPixelFormatDesc);
#endif
}
void OGLGraphicsDriver::TestVSync()
{
const char* extensions = (const char*)glGetString(GL_EXTENSIONS);
const char* extensionsARB = NULL;
#if defined(WINDOWS_VERSION)
PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB");
extensionsARB = wglGetExtensionsStringARB ? (const char*)wglGetExtensionsStringARB(_hDC) : NULL;
#endif
#if !defined(ANDROID_VERSION)
if (extensions && strstr(extensions, vsync_extension_string) != NULL ||
extensionsARB && strstr(extensionsARB, vsync_extension_string) != NULL)
{
#if defined(WINDOWS_VERSION)
glSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
#endif
}
if (!glSwapIntervalEXT)
Debug::Printf(kDbgMsg_Warn, "WARNING: OpenGL extension '%s' not supported, vertical sync will be kept at driver default.", vsync_extension_string);
#endif
}
void OGLGraphicsDriver::TestRenderToTexture()
{
const char* extensions = (const char*)glGetString(GL_EXTENSIONS);
if (extensions && strstr(extensions, fbo_extension_string) != NULL)
{
#if defined(WINDOWS_VERSION)
glGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC)wglGetProcAddress("glGenFramebuffersEXT");
glDeleteFramebuffersEXT = (PFNGLDELETEFRAMEBUFFERSEXTPROC)wglGetProcAddress("glDeleteFramebuffersEXT");
glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC)wglGetProcAddress("glBindFramebufferEXT");
glCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)wglGetProcAddress("glCheckFramebufferStatusEXT");
glGetFramebufferAttachmentParameterivEXT = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC)wglGetProcAddress("glGetFramebufferAttachmentParameterivEXT");
glGenerateMipmapEXT = (PFNGLGENERATEMIPMAPEXTPROC)wglGetProcAddress("glGenerateMipmapEXT");
glFramebufferTexture2DEXT = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)wglGetProcAddress("glFramebufferTexture2DEXT");
glFramebufferRenderbufferEXT = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)wglGetProcAddress("glFramebufferRenderbufferEXT");
#endif
_can_render_to_texture = true;
// Disable super-sampling if it would cause a too large texture size
if (_super_sampling > 1)
{
int max = 1024;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
if ((max < _srcRect.GetWidth() * 2) || (max < _srcRect.GetHeight() * 2))
_super_sampling = 1;
}
}
else
{
_can_render_to_texture = false;
Debug::Printf(kDbgMsg_Warn, "WARNING: OpenGL extension '%s' not supported, rendering to texture mode will be disabled.", fbo_extension_string);
}
if (!_can_render_to_texture)
_do_render_to_texture = false;
}
void OGLGraphicsDriver::CreateShaders()
{
// We need at least OpenGL 3.0 to run our tinting shader
if (_oglVersion.Major < 3)
Debug::Printf(kDbgMsg_Warn, "WARNING: OpenGL 3.0 or higher is required for tinting shader.");
// TODO: linking with GLEW library might be a better idea
#if defined(WINDOWS_VERSION)
glCreateShader = (PFNGLCREATESHADERPROC)wglGetProcAddress("glCreateShader");
glShaderSource = (PFNGLSHADERSOURCEPROC)wglGetProcAddress("glShaderSource");
glCompileShader = (PFNGLCOMPILESHADERPROC)wglGetProcAddress("glCompileShader");
glGetShaderiv = (PFNGLGETSHADERIVPROC)wglGetProcAddress("glGetShaderiv");
glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)wglGetProcAddress("glGetShaderInfoLog");
glAttachShader = (PFNGLATTACHSHADERPROC)wglGetProcAddress("glAttachShader");
glDetachShader = (PFNGLDETACHSHADERPROC)wglGetProcAddress("glDetachShader");
glDeleteShader = (PFNGLDELETESHADERPROC)wglGetProcAddress("glDeleteShader");
glCreateProgram = (PFNGLCREATEPROGRAMPROC)wglGetProcAddress("glCreateProgram");
glLinkProgram = (PFNGLLINKPROGRAMPROC)wglGetProcAddress("glLinkProgram");
glGetProgramiv = (PFNGLGETPROGRAMIVPROC)wglGetProcAddress("glGetProgramiv");
glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)wglGetProcAddress("glGetProgramInfoLog");
glUseProgram = (PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram");
glDeleteProgram = (PFNGLDELETEPROGRAMPROC)wglGetProcAddress("glDeleteProgram");
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)wglGetProcAddress("glGetUniformLocation");
glUniform1i = (PFNGLUNIFORM1IPROC)wglGetProcAddress("glUniform1i");
glUniform1f = (PFNGLUNIFORM1FPROC)wglGetProcAddress("glUniform1f");
glUniform3f = (PFNGLUNIFORM3FPROC)wglGetProcAddress("glUniform3f");
#endif
if (!glCreateShader)
{
Debug::Printf(kDbgMsg_Error, "ERROR: OpenGL shader functions could not be linked.");
return;
}
CreateTintShader();
CreateLightShader();
}
void OGLGraphicsDriver::CreateTintShader()
{
const char *fragment_shader_src =
// NOTE: this shader emulates "historical" AGS software tinting; it is not
// necessarily "proper" tinting in modern terms.
// The RGB-HSV-RGB conversion found in the Internet (copyright unknown);
// Color processing is replicated from Direct3D shader by Chris Jones
// (Engine/resource/tintshaderLegacy.fx).
"\
#version 130\n\
out vec4 gl_FragColor;\n\
uniform sampler2D textID;\n\
uniform vec3 tintHSV;\n\
uniform vec3 tintAmnTrsLum;\n\
\
vec3 rgb2hsv(vec3 c)\n\
{\n\
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n\
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));\n\
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));\n\
\
float d = q.x - min(q.w, q.y);\n\
float e = 1.0e-10;\n\
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n\
}\n\
\
vec3 hsv2rgb(vec3 c)\n\
{\n\
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n\
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\n\
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\n\
}\n\
\
float getValue(vec3 color)\n\
{\n\
float colorMax = max (color[0], color[1]);\n\
colorMax = max (colorMax, color[2]);\n\
return colorMax;\n\
}\n\
\
void main()\n\
{\n\
vec2 coords = gl_TexCoord[0].xy;\n\
vec4 src_col = texture2D(textID, coords);\n\
float amount = tintAmnTrsLum[0];\n\
float lum = getValue(src_col.xyz);\n\
lum = max(lum - (1.0 - tintAmnTrsLum[2]), 0.0);\n\
vec3 new_col = (hsv2rgb(vec3(tintHSV[0],tintHSV[1],lum)) * amount + src_col.xyz*(1-amount));\n\
gl_FragColor = vec4(new_col, src_col.w * tintAmnTrsLum[1]);\n\
}\n\
";
CreateShaderProgram(_tintShader, "Tinting", fragment_shader_src, "textID", "tintHSV", "tintAmnTrsLum");
}
void OGLGraphicsDriver::CreateLightShader()
{
const char *fragment_shader_src =
// NOTE: due to how the lighting works in AGS, this is combined MODULATE / ADD shader.
// if the light is < 0, then MODULATE operation is used, otherwise ADD is used.
// NOTE: it's been said that using branching in shaders produces inefficient code.
// If that will ever become a real problem, we can easily split this shader in two.
"\
#version 130\n\
out vec4 gl_FragColor;\n\
uniform sampler2D textID;\n\
uniform float light;\n\
uniform float alpha;\n\
\
void main()\n\
{\n\
vec2 coords = gl_TexCoord[0].xy;\n\
gl_FragColor = texture2D(textID, coords);\n\
if (light >= 0.0)\n\
gl_FragColor = vec4(gl_FragColor.xyz + vec3(light, light, light), gl_FragColor.w * alpha);\n\
else\n\
gl_FragColor = vec4(gl_FragColor.xyz * abs(light), gl_FragColor.w * alpha);\n\
}\n\
";
CreateShaderProgram(_lightShader, "Lighting", fragment_shader_src, "textID", "light", "alpha");
}
void OGLGraphicsDriver::CreateShaderProgram(ShaderProgram &prg, const char *name, const char *fragment_shader_src,
const char *sampler_var, const char *color_var, const char *aux_var)
{
GLint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_shader_src, NULL);
glCompileShader(fragment_shader);
GLint compile_result;
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &compile_result);
if (compile_result == GL_FALSE)
{
OutputShaderError(fragment_shader, String::FromFormat("%s program's fragment shader", name), true);
glDeleteShader(fragment_shader);
return;
}
GLuint program = glCreateProgram();
glAttachShader(program, fragment_shader);
glLinkProgram(program);
GLint link_result = 0;
glGetProgramiv(program, GL_LINK_STATUS, &link_result);
if(link_result == GL_FALSE)
{
OutputShaderError(program, String::FromFormat("%s program", name), false);
glDeleteProgram(program);
glDeleteShader(fragment_shader);
return;
}
glDetachShader(program, fragment_shader);
glDeleteShader(fragment_shader);
prg.Program = program;
prg.SamplerVar = glGetUniformLocation(program, sampler_var);
prg.ColorVar = glGetUniformLocation(program, color_var);
prg.AuxVar = glGetUniformLocation(program, aux_var);
}
void OGLGraphicsDriver::DeleteShaderProgram(ShaderProgram &prg)
{
if (prg.Program)
glDeleteProgram(prg.Program);
prg.Program = 0;
}
void OGLGraphicsDriver::OutputShaderError(GLuint obj_id, const String &obj_name, bool is_shader)
{
GLint log_len = 0;
if (is_shader)
glGetShaderiv(obj_id, GL_INFO_LOG_LENGTH, &log_len);
else
glGetProgramiv(obj_id, GL_INFO_LOG_LENGTH, &log_len);
std::vector<GLchar> errorLog(log_len);
if (is_shader)
glGetShaderInfoLog(obj_id, log_len, &log_len, &errorLog[0]);
else
glGetProgramInfoLog(obj_id, log_len, &log_len, &errorLog[0]);
Debug::Printf(kDbgMsg_Error, "ERROR: OpenGL: %s %s:", obj_name.GetCStr(), is_shader ? "failed to compile" : "failed to link");
Debug::Printf(kDbgMsg_Error, "----------------------------------------");
Debug::Printf(kDbgMsg_Error, "%s", &errorLog[0]);
Debug::Printf(kDbgMsg_Error, "----------------------------------------");
}
void OGLGraphicsDriver::SetupBackbufferTexture()
{
// NOTE: ability to render to texture depends on OGL context, which is
// created in SetDisplayMode, therefore creation of textures require
// both native size set and context capabilities test passed.
if (!IsNativeSizeValid() || !_can_render_to_texture)
return;
DeleteBackbufferTexture();
// _backbuffer_texture_coordinates defines translation from wanted texture size to actual supported texture size
_backRenderSize = _srcRect.GetSize() * _super_sampling;
_backTextureSize = _backRenderSize;
AdjustSizeToNearestSupportedByCard(&_backTextureSize.Width, &_backTextureSize.Height);
const float back_ratio_w = (float)_backRenderSize.Width / (float)_backTextureSize.Width;
const float back_ratio_h = (float)_backRenderSize.Height / (float)_backTextureSize.Height;
std::fill(_backbuffer_texture_coordinates, _backbuffer_texture_coordinates + sizeof(_backbuffer_texture_coordinates) / sizeof(GLfloat), 0.0f);
_backbuffer_texture_coordinates[2] = _backbuffer_texture_coordinates[6] = back_ratio_w;
_backbuffer_texture_coordinates[5] = _backbuffer_texture_coordinates[7] = back_ratio_h;
glGenTextures(1, &_backbuffer);
glBindTexture(GL_TEXTURE_2D, _backbuffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _backTextureSize.Width, _backTextureSize.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glGenFramebuffersEXT(1, &_fbo);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, _backbuffer, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
// Assign vertices of the backbuffer texture position in the scene
_backbuffer_vertices[0] = _backbuffer_vertices[4] = 0;
_backbuffer_vertices[2] = _backbuffer_vertices[6] = _srcRect.GetWidth();
_backbuffer_vertices[5] = _backbuffer_vertices[7] = _srcRect.GetHeight();
_backbuffer_vertices[1] = _backbuffer_vertices[3] = 0;
}
void OGLGraphicsDriver::DeleteBackbufferTexture()
{
if (_backbuffer)
glDeleteTextures(1, &_backbuffer);
if (_fbo)
glDeleteFramebuffersEXT(1, &_fbo);
_backbuffer = 0;
_fbo = 0;
}
void OGLGraphicsDriver::SetupViewport()
{
if (!IsModeSet() || !IsRenderFrameValid())
return;
// Setup viewport rect and scissor; notice that OpenGL viewport has Y axis inverted
_viewportRect = RectWH(_dstRect.Left, device_screen_physical_height - 1 - _dstRect.Bottom, _dstRect.GetWidth(), _dstRect.GetHeight());
glScissor(_viewportRect.Left, _viewportRect.Top, _viewportRect.GetWidth(), _viewportRect.GetHeight());
#if defined(ANDROID_VERSION) || defined(IOS_VERSION)
device_mouse_setup(
(device_screen_physical_width - _dstRect.GetWidth()) / 2,
device_screen_physical_width - ((device_screen_physical_width - _dstRect.GetWidth()) / 2),
(device_screen_physical_height - _dstRect.GetHeight()) / 2,
device_screen_physical_height - ((device_screen_physical_height - _dstRect.GetHeight()) / 2),
1.0f,
1.0f);
#endif
}
bool OGLGraphicsDriver::SetDisplayMode(const DisplayMode &mode, volatile int *loopTimer)
{
ReleaseDisplayMode();
if (mode.ColorDepth < 15)
{
set_allegro_error("OpenGL driver does not support 256-color display mode");
return false;
}
try
{
if (!InitGlScreen(mode))
return false;
if (!_firstTimeInit)
FirstTimeInit();
InitGlParams(mode);
}
catch (Ali3DException exception)
{
if (exception._message != get_allegro_error())
set_allegro_error(exception._message);
return false;
}
OnInit(loopTimer);
// On certain platforms OpenGL renderer ignores requested screen sizes
// and uses values imposed by the operating system (device).
DisplayMode final_mode = mode;
final_mode.Width = device_screen_physical_width;
final_mode.Height = device_screen_physical_height;
OnModeSet(final_mode);
// TODO: move these options parsing into config instead
#if defined(ANDROID_VERSION) || defined (IOS_VERSION)
if (psp_gfx_renderer == 2 && _can_render_to_texture)
{
_super_sampling = ((psp_gfx_super_sampling > 0) ? 2 : 1);
_do_render_to_texture = true;
}
else
{
_super_sampling = 1;
_do_render_to_texture = false;
}
#endif
create_screen_tint_bitmap();
// If we already have a native size set, then update virtual screen and setup backbuffer texture immediately
CreateVirtualScreen();
SetupBackbufferTexture();
// If we already have a render frame configured, then setup viewport and backbuffer mappings immediately
SetupViewport();
return true;
}
void OGLGraphicsDriver::CreateVirtualScreen()
{
if (!IsModeSet() || !IsNativeSizeValid())
return;
CreateStageScreen();
}
bool OGLGraphicsDriver::SetNativeSize(const Size &src_size)
{
OnSetNativeSize(src_size);
SetupBackbufferTexture();
// If we already have a gfx mode set, then update virtual screen immediately
CreateVirtualScreen();
return !_srcRect.IsEmpty();
}
bool OGLGraphicsDriver::SetRenderFrame(const Rect &dst_rect)
{
if (!IsNativeSizeValid())
return false;
OnSetRenderFrame(dst_rect);
// Also make sure viewport and backbuffer mappings are updated using new native & destination rectangles
SetupViewport();
return !_dstRect.IsEmpty();
}
int OGLGraphicsDriver::GetDisplayDepthForNativeDepth(int native_color_depth) const
{
// TODO: check for device caps to know which depth is supported?
return 32;
}
IGfxModeList *OGLGraphicsDriver::GetSupportedModeList(int color_depth)
{
std::vector<DisplayMode> modes;
platform->GetSystemDisplayModes(modes);
return new OGLDisplayModeList(modes);
}
PGfxFilter OGLGraphicsDriver::GetGraphicsFilter() const
{
return _filter;
}
void OGLGraphicsDriver::ReleaseDisplayMode()
{
if (!IsModeSet())
return;
OnModeReleased();
DeleteBackbufferTexture();
if (_screenTintLayerDDB != NULL)
{
this->DestroyDDB(_screenTintLayerDDB);
_screenTintLayerDDB = NULL;
_screenTintSprite.bitmap = NULL;
}
delete _screenTintLayer;
_screenTintLayer = NULL;
DestroyStageScreen();
gfx_driver = NULL;
if (platform->ExitFullscreenMode())
platform->RestoreWindowStyle();
}
void OGLGraphicsDriver::UnInit()
{
OnUnInit();
ReleaseDisplayMode();
DeleteGlContext();
#if defined (WINDOWS_VERSION)
_hWnd = NULL;
_hDC = NULL;
#endif
DeleteShaderProgram(_tintShader);
DeleteShaderProgram(_lightShader);
}
OGLGraphicsDriver::~OGLGraphicsDriver()
{
UnInit();
}
void OGLGraphicsDriver::ClearRectangle(int x1, int y1, int x2, int y2, RGB *colorToUse)
{
}
void OGLGraphicsDriver::GetCopyOfScreenIntoBitmap(Bitmap *destination, bool at_native_res)
{
(void)at_native_res; // TODO: support this at some point
Rect retr_rect;
if (_do_render_to_texture)
{
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fbo);
retr_rect = RectWH(0, 0, _backRenderSize.Width, _backRenderSize.Height);
}
else
{
#if defined(IOS_VERSION)
ios_select_buffer();
#elif defined(WINDOWS_VERSION)
glReadBuffer(GL_FRONT);
#endif
retr_rect = _dstRect;
}
int bpp = _mode.ColorDepth / 8;
int bufferSize = retr_rect.GetWidth() * retr_rect.GetHeight() * bpp;
unsigned char* buffer = new unsigned char[bufferSize];
if (buffer)
{
glReadPixels(retr_rect.Left, retr_rect.Top, retr_rect.GetWidth(), retr_rect.GetHeight(), GL_RGBA, GL_UNSIGNED_BYTE, buffer);
unsigned char* surfaceData = buffer;
unsigned char* sourcePtr;
unsigned char* destPtr;
Bitmap* retrieveInto = BitmapHelper::CreateBitmap(retr_rect.GetWidth(), retr_rect.GetHeight(), _mode.ColorDepth);
if (retrieveInto)
{
for (int y = retrieveInto->GetHeight() - 1; y >= 0; y--)
{
sourcePtr = surfaceData;
destPtr = &retrieveInto->GetScanLineForWriting(y)[0];
for (int x = 0; x < retrieveInto->GetWidth() * bpp; x += bpp)
{
// TODO: find out if it's possible to retrieve pixels in the matching format
destPtr[x] = sourcePtr[x + 2];
destPtr[x + 1] = sourcePtr[x + 1];
destPtr[x + 2] = sourcePtr[x];
destPtr[x + 3] = sourcePtr[x + 3];
}
surfaceData += retr_rect.GetWidth() * bpp;
}
destination->StretchBlt(retrieveInto, RectWH(0, 0, retrieveInto->GetWidth(), retrieveInto->GetHeight()),
RectWH(0, 0, destination->GetWidth(), destination->GetHeight()));
delete retrieveInto;
}
delete [] buffer;
}
}
void OGLGraphicsDriver::RenderToBackBuffer()
{
throw Ali3DException("D3D driver does not have a back buffer");
}
void OGLGraphicsDriver::Render()
{
Render(kFlip_None);
}
void OGLGraphicsDriver::Render(GlobalFlipType flip)
{
_render(flip, true);
}
void OGLGraphicsDriver::_reDrawLastFrame()
{
drawList = drawListLastTime;
}
void OGLGraphicsDriver::_renderSprite(OGLDrawListEntry *drawListEntry, bool globalLeftRightFlip, bool globalTopBottomFlip)
{
OGLBitmap *bmpToDraw = drawListEntry->bitmap;
if (bmpToDraw->_transparency >= 255)
return;
if (bmpToDraw->_tintSaturation > 0)
{
// Use tinting shader
if (_tintShader.Program)
{
glUseProgram(_tintShader.Program);
float rgb[3];
float sat_trs_lum[3]; // saturation / transparency / luminance
if (_legacyPixelShader)
{
rgb_to_hsv(bmpToDraw->_red, bmpToDraw->_green, bmpToDraw->_blue, &rgb[0], &rgb[1], &rgb[2]);
rgb[0] /= 360.0; // In HSV, Hue is 0-360
}
else
{
rgb[0] = (float)bmpToDraw->_red / 255.0;
rgb[1] = (float)bmpToDraw->_green / 255.0;
rgb[2] = (float)bmpToDraw->_blue / 255.0;
}
sat_trs_lum[0] = (float)bmpToDraw->_tintSaturation / 255.0;
if (bmpToDraw->_transparency > 0)
sat_trs_lum[1] = (float)bmpToDraw->_transparency / 255.0;
else
sat_trs_lum[1] = 1.0f;
if (bmpToDraw->_lightLevel > 0)
sat_trs_lum[2] = (float)bmpToDraw->_lightLevel / 255.0;
else
sat_trs_lum[2] = 1.0f;
glUniform1i(_tintShader.SamplerVar, 0);
glUniform3f(_tintShader.ColorVar, rgb[0], rgb[1], rgb[2]);
glUniform3f(_tintShader.AuxVar, sat_trs_lum[0], sat_trs_lum[1], sat_trs_lum[2]);
}
}
else if (bmpToDraw->_lightLevel > 0)
{
// Use light shader
glUseProgram(_lightShader.Program);
float light_lev = 1.0f;
float alpha = 1.0f;
// Light level parameter in DDB is weird, it is measured in units of
// 1/255 (although effectively 1/250, see draw.cpp), but contains two
// ranges: 1-255 is darker range and 256-511 is brighter range.
// (light level of 0 means "default color")
if ((bmpToDraw->_lightLevel > 0) && (bmpToDraw->_lightLevel < 256))
{
// darkening the sprite... this stupid calculation is for
// consistency with the allegro software-mode code that does
// a trans blend with a (8,8,8) sprite
light_lev = -((bmpToDraw->_lightLevel * 192) / 256 + 64) / 255.f; // darker, uses MODULATE op
}
else if (bmpToDraw->_lightLevel > 256)
{
light_lev = ((bmpToDraw->_lightLevel - 256) / 2) / 255.f; // brighter, uses ADD op
}
if (bmpToDraw->_transparency > 0)
alpha = bmpToDraw->_transparency / 255.f;
glUniform1i(_lightShader.SamplerVar, 0);
glUniform1f(_lightShader.ColorVar, light_lev);
glUniform1f(_lightShader.AuxVar, alpha);
}
else
{
// Use default processing
if (bmpToDraw->_transparency == 0)
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
else
glColor4f(1.0f, 1.0f, 1.0f, bmpToDraw->_transparency / 255.0f);
}
float width = bmpToDraw->GetWidthToRender();
float height = bmpToDraw->GetHeightToRender();
float xProportion = (float)width / (float)bmpToDraw->_width;
float yProportion = (float)height / (float)bmpToDraw->_height;
bool flipLeftToRight = globalLeftRightFlip ^ bmpToDraw->_flipped;
int drawAtX = drawListEntry->x + _global_x_offset;
int drawAtY = drawListEntry->y + _global_y_offset;
for (int ti = 0; ti < bmpToDraw->_numTiles; ti++)
{
width = bmpToDraw->_tiles[ti].width * xProportion;
height = bmpToDraw->_tiles[ti].height * yProportion;
float xOffs;
float yOffs = bmpToDraw->_tiles[ti].y * yProportion;
if (flipLeftToRight != globalLeftRightFlip)
{
xOffs = (bmpToDraw->_width - (bmpToDraw->_tiles[ti].x + bmpToDraw->_tiles[ti].width)) * xProportion;
}
else
{
xOffs = bmpToDraw->_tiles[ti].x * xProportion;
}
int thisX = drawAtX + xOffs;
int thisY = drawAtY + yOffs;
if (globalLeftRightFlip)
{
thisX = (_srcRect.GetWidth() - thisX) - width;
}
if (globalTopBottomFlip)
{
thisY = (_srcRect.GetHeight() - thisY) - height;
}
thisX = (-(_srcRect.GetWidth() / 2)) + thisX;
thisY = (_srcRect.GetHeight() / 2) - thisY;
//Setup translation and scaling matrices
float widthToScale = (float)width;
float heightToScale = (float)height;
if (flipLeftToRight)
{
// The usual transform changes 0..1 into 0..width
// So first negate it (which changes 0..w into -w..0)
widthToScale = -widthToScale;
// and now shift it over to make it 0..w again
thisX += width;
}
if (globalTopBottomFlip)
{
heightToScale = -heightToScale;
thisY -= height;
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
if (_do_render_to_texture)
glTranslatef(_backRenderSize.Width / 2.0f, _backRenderSize.Height / 2.0f, 0.0f);
else
glTranslatef(_srcRect.GetWidth() / 2.0f, _srcRect.GetHeight() / 2.0f, 0.0f);
glTranslatef((float)thisX, (float)thisY, 0.0f);
glScalef(widthToScale, heightToScale, 1.0f);
glBindTexture(GL_TEXTURE_2D, bmpToDraw->_tiles[ti].texture);
if ((_smoothScaling) && bmpToDraw->_useResampler && (bmpToDraw->_stretchToHeight > 0) &&
((bmpToDraw->_stretchToHeight != bmpToDraw->_height) ||
(bmpToDraw->_stretchToWidth != bmpToDraw->_width)))
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
else if (_do_render_to_texture)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
else
{
_filter->SetFilteringForStandardSprite();
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
if (bmpToDraw->_vertex != NULL)
{
glTexCoordPointer(2, GL_FLOAT, sizeof(OGLCUSTOMVERTEX), &(bmpToDraw->_vertex[ti * 4].tu));
glVertexPointer(2, GL_FLOAT, sizeof(OGLCUSTOMVERTEX), &(bmpToDraw->_vertex[ti * 4].position));
}
else
{
glTexCoordPointer(2, GL_FLOAT, sizeof(OGLCUSTOMVERTEX), &defaultVertices[0].tu);
glVertexPointer(2, GL_FLOAT, sizeof(OGLCUSTOMVERTEX), &defaultVertices[0].position);
}
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
glUseProgram(0);
}
void OGLGraphicsDriver::_render(GlobalFlipType flip, bool clearDrawListAfterwards)
{
#if defined(IOS_VERSION)
ios_select_buffer();
#endif
if (!device_screen_initialized)
{// TODO: find out if this is really necessary here and why
if (!_firstTimeInit)
FirstTimeInit();
InitGlParams(_mode);
device_screen_initialized = 1;
}
std::vector<OGLDrawListEntry> &listToDraw = drawList;
size_t listSize = drawList.size();
bool globalLeftRightFlip = (flip == kFlip_Vertical) || (flip == kFlip_Both);
bool globalTopBottomFlip = (flip == kFlip_Horizontal) || (flip == kFlip_Both);
if (_do_render_to_texture)
{
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fbo);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, _backRenderSize.Width, _backRenderSize.Height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, _backRenderSize.Width, 0, _backRenderSize.Height, 0, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
else
{
glDisable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_SCISSOR_TEST);
glViewport(_viewportRect.Left, _viewportRect.Top, _viewportRect.GetWidth(), _viewportRect.GetHeight());
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, _srcRect.GetWidth(), 0, _srcRect.GetHeight(), 0, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
for (size_t i = 0; i < listSize; i++)
{
if (listToDraw[i].skip)
continue;
if (listToDraw[i].bitmap == NULL)
{
if (DoNullSpriteCallback(listToDraw[i].x, listToDraw[i].y))
listToDraw[i] = OGLDrawListEntry((OGLBitmap*)_stageVirtualScreenDDB);
else
continue;
}
this->_renderSprite(&listToDraw[i], globalLeftRightFlip, globalTopBottomFlip);
}
if (!_screenTintSprite.skip)
{
this->_renderSprite(&_screenTintSprite, false, false);
}
if (_do_render_to_texture)
{
// Texture is ready, now create rectangle in the world space and draw texture upon it
#if defined(IOS_VERSION)
ios_select_buffer();
#else
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
#endif
glViewport(_viewportRect.Left, _viewportRect.Top, _viewportRect.GetWidth(), _viewportRect.GetHeight());
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, _srcRect.GetWidth(), 0, _srcRect.GetHeight(), 0, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_BLEND);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
// use correct sampling method when stretching buffer to the final rect
_filter->SetFilteringForStandardSprite();
glBindTexture(GL_TEXTURE_2D, _backbuffer);
glTexCoordPointer(2, GL_FLOAT, 0, _backbuffer_texture_coordinates);
glVertexPointer(2, GL_FLOAT, 0, _backbuffer_vertices);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glEnable(GL_BLEND);
}
glFinish();
#if defined(WINDOWS_VERSION)
SwapBuffers(_hDC);
#elif defined(ANDROID_VERSION) || defined(IOS_VERSION)
device_swap_buffers();
#endif
if (clearDrawListAfterwards)
{
drawListLastTime = drawList;
flipTypeLastTime = flip;
ClearDrawList();
}
}
void OGLGraphicsDriver::ClearDrawList()
{
drawList.clear();
}
void OGLGraphicsDriver::DrawSprite(int x, int y, IDriverDependantBitmap* bitmap)
{
drawList.push_back(OGLDrawListEntry((OGLBitmap*)bitmap, x, y));
}
void OGLGraphicsDriver::DestroyDDB(IDriverDependantBitmap* bitmap)
{
for (size_t i = 0; i < drawListLastTime.size(); i++)
{
if (drawListLastTime[i].bitmap == bitmap)
{
drawListLastTime[i].skip = true;
}
}
delete bitmap;
}
__inline void get_pixel_if_not_transparent15(unsigned short *pixel, unsigned short *red, unsigned short *green, unsigned short *blue, unsigned short *divisor)
{
if (pixel[0] != MASK_COLOR_15)
{
*red += algetr15(pixel[0]);
*green += algetg15(pixel[0]);
*blue += algetb15(pixel[0]);
divisor[0]++;
}
}
__inline void get_pixel_if_not_transparent32(unsigned int *pixel, unsigned int *red, unsigned int *green, unsigned int *blue, unsigned int *divisor)
{
if (pixel[0] != MASK_COLOR_32)
{
*red += algetr32(pixel[0]);
*green += algetg32(pixel[0]);
*blue += algetb32(pixel[0]);
divisor[0]++;
}
}
#define D3DCOLOR_RGBA(r,g,b,a) \
(((((a)&0xff)<<24)|(((b)&0xff)<<16)|(((g)&0xff)<<8)|((r)&0xff)))
void OGLGraphicsDriver::UpdateTextureRegion(TextureTile *tile, Bitmap *bitmap, OGLBitmap *target, bool hasAlpha)
{
int textureHeight = tile->height;
int textureWidth = tile->width;
AdjustSizeToNearestSupportedByCard(&textureWidth, &textureHeight);
int tileWidth = (textureWidth > tile->width) ? tile->width + 1 : tile->width;
int tileHeight = (textureHeight > tile->height) ? tile->height + 1 : tile->height;
bool usingLinearFiltering = _filter->UseLinearFiltering();
bool lastPixelWasTransparent = false;
char *origPtr = (char*)malloc(4 * tileWidth * tileHeight);
char *memPtr = origPtr;
for (int y = 0; y < tileHeight; y++)
{
// Mimic the behaviour of GL_CLAMP_EDGE for the bottom line
if (y == tile->height)
{
unsigned int* memPtrLong = (unsigned int*)memPtr;
unsigned int* memPtrLong_previous = (unsigned int*)(memPtr - tileWidth * 4);
for (int x = 0; x < tileWidth; x++)
memPtrLong[x] = memPtrLong_previous[x] & 0x00FFFFFF;
continue;
}
lastPixelWasTransparent = false;
const uint8_t *scanline_before = bitmap->GetScanLine(y + tile->y - 1);
const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
const uint8_t *scanline_after = bitmap->GetScanLine(y + tile->y + 1);
for (int x = 0; x < tileWidth; x++)
{
/* if (target->_colDepth == 15)
{
unsigned short* memPtrShort = (unsigned short*)memPtr;
unsigned short* srcData = (unsigned short*)&bitmap->GetScanLine[y + tile->y][(x + tile->x) * 2];
if (*srcData == MASK_COLOR_15)
{
if (target->_opaque) // set to black if opaque
memPtrShort[x] = 0x8000;
else if (!usingLinearFiltering)
memPtrShort[x] = 0;
// set to transparent, but use the colour from the neighbouring
// pixel to stop the linear filter doing black outlines
else
{
unsigned short red = 0, green = 0, blue = 0, divisor = 0;
if (x > 0)
get_pixel_if_not_transparent15(&srcData[-1], &red, &green, &blue, &divisor);
if (x < tile->width - 1)
get_pixel_if_not_transparent15(&srcData[1], &red, &green, &blue, &divisor);
if (y > 0)
get_pixel_if_not_transparent15((unsigned short*)&bitmap->GetScanLine[y + tile->y - 1][(x + tile->x) * 2], &red, &green, &blue, &divisor);
if (y < tile->height - 1)
get_pixel_if_not_transparent15((unsigned short*)&bitmap->GetScanLine[y + tile->y + 1][(x + tile->x) * 2], &red, &green, &blue, &divisor);
if (divisor > 0)
memPtrShort[x] = ((red / divisor) << 10) | ((green / divisor) << 5) | (blue / divisor);
else
memPtrShort[x] = 0;
}
lastPixelWasTransparent = true;
}
else
{
memPtrShort[x] = 0x8000 | (algetr15(*srcData) << 10) | (algetg15(*srcData) << 5) | algetb15(*srcData);
if (lastPixelWasTransparent)
{
// update the colour of the previous tranparent pixel, to
// stop black outlines when linear filtering
memPtrShort[x - 1] = memPtrShort[x] & 0x7FFF;
lastPixelWasTransparent = false;
}
}
}
else if (target->_colDepth == 32)
*/
{
unsigned int* memPtrLong = (unsigned int*)memPtr;
if (x == tile->width)
{
memPtrLong[x] = memPtrLong[x - 1] & 0x00FFFFFF;
continue;
}
unsigned int* srcData = (unsigned int*)&scanline_at[(x + tile->x) << 2];
if (*srcData == MASK_COLOR_32)
{
if (target->_opaque) // set to black if opaque
memPtrLong[x] = 0xFF000000;
else if (!usingLinearFiltering)
memPtrLong[x] = 0;
// set to transparent, but use the colour from the neighbouring
// pixel to stop the linear filter doing black outlines
else
{
unsigned int red = 0, green = 0, blue = 0, divisor = 0;
if (x > 0)
get_pixel_if_not_transparent32(&srcData[-1], &red, &green, &blue, &divisor);
if (x < tile->width - 1)
get_pixel_if_not_transparent32(&srcData[1], &red, &green, &blue, &divisor);
if (y > 0)
get_pixel_if_not_transparent32((unsigned int*)&scanline_before[(x + tile->x) << 2], &red, &green, &blue, &divisor);
if (y < tile->height - 1)
get_pixel_if_not_transparent32((unsigned int*)&scanline_after[(x + tile->x) << 2], &red, &green, &blue, &divisor);
if (divisor > 0)
memPtrLong[x] = ((red / divisor) << 16) | ((green / divisor) << 8) | (blue / divisor);
else
memPtrLong[x] = 0;
}
lastPixelWasTransparent = true;
}
else if (hasAlpha)
{
memPtrLong[x] = D3DCOLOR_RGBA(algetr32(*srcData), algetg32(*srcData), algetb32(*srcData), algeta32(*srcData));
}
else
{
memPtrLong[x] = D3DCOLOR_RGBA(algetr32(*srcData), algetg32(*srcData), algetb32(*srcData), 0xff);
if (lastPixelWasTransparent)
{
// update the colour of the previous tranparent pixel, to
// stop black outlines when linear filtering
memPtrLong[x - 1] = memPtrLong[x] & 0x00FFFFFF;
lastPixelWasTransparent = false;
}
}
}
}
memPtr += tileWidth * 4;
}
unsigned int newTexture = tile->texture;
glBindTexture(GL_TEXTURE_2D, tile->texture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tileWidth, tileHeight, GL_RGBA, GL_UNSIGNED_BYTE, origPtr);
free(origPtr);
}
void OGLGraphicsDriver::UpdateDDBFromBitmap(IDriverDependantBitmap* bitmapToUpdate, Bitmap *bitmap, bool hasAlpha)
{
OGLBitmap *target = (OGLBitmap*)bitmapToUpdate;
Bitmap *source = bitmap;
if ((target->_width == bitmap->GetWidth()) &&
(target->_height == bitmap->GetHeight()))
{
if (bitmap->GetColorDepth() != target->_colDepth)
{
//throw Ali3DException("Mismatched colour depths");
source = BitmapHelper::CreateBitmapCopy(bitmap, 32);
}
target->_hasAlpha = hasAlpha;
for (int i = 0; i < target->_numTiles; i++)
{
UpdateTextureRegion(&target->_tiles[i], source, target, hasAlpha);
}
if (source != bitmap)
delete source;
}
}
Bitmap *OGLGraphicsDriver::ConvertBitmapToSupportedColourDepth(Bitmap *bitmap)
{
int colourDepth = bitmap->GetColorDepth();
if (colourDepth != 32)
{
int old_conv = get_color_conversion();
set_color_conversion(COLORCONV_KEEP_TRANS | COLORCONV_TOTAL);
// we need 32-bit colour
Bitmap *tempBmp = BitmapHelper::CreateBitmapCopy(bitmap, 32);
set_color_conversion(old_conv);
return tempBmp;
}
return bitmap;
}
void OGLGraphicsDriver::AdjustSizeToNearestSupportedByCard(int *width, int *height)
{
int allocatedWidth = *width, allocatedHeight = *height;
bool foundWidth = false, foundHeight = false;
int tryThis = 2;
while ((!foundWidth) || (!foundHeight))
{
if ((tryThis >= allocatedWidth) && (!foundWidth))
{
allocatedWidth = tryThis;
foundWidth = true;
}
if ((tryThis >= allocatedHeight) && (!foundHeight))
{
allocatedHeight = tryThis;
foundHeight = true;
}
tryThis = tryThis << 1;
}
*width = allocatedWidth;
*height = allocatedHeight;
}
IDriverDependantBitmap* OGLGraphicsDriver::CreateDDBFromBitmap(Bitmap *bitmap, bool hasAlpha, bool opaque)
{
int allocatedWidth = bitmap->GetWidth();
int allocatedHeight = bitmap->GetHeight();
// NOTE: original bitmap object is not modified in this function
Bitmap *tempBmp = ConvertBitmapToSupportedColourDepth(bitmap);
bitmap = tempBmp;
int colourDepth = bitmap->GetColorDepth();
OGLBitmap *ddb = new OGLBitmap(bitmap->GetWidth(), bitmap->GetHeight(), colourDepth, opaque);
AdjustSizeToNearestSupportedByCard(&allocatedWidth, &allocatedHeight);
int tilesAcross = 1, tilesDown = 1;
// *************** REMOVE THESE LINES *************
//direct3ddevicecaps.MaxTextureWidth = 64;
//direct3ddevicecaps.MaxTextureHeight = 256;
// *************** END REMOVE THESE LINES *************
// Calculate how many textures will be necessary to
// store this image
int MaxTextureWidth = 512;
int MaxTextureHeight = 512;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &MaxTextureWidth);
MaxTextureHeight = MaxTextureWidth;
tilesAcross = (allocatedWidth + MaxTextureWidth - 1) / MaxTextureWidth;
tilesDown = (allocatedHeight + MaxTextureHeight - 1) / MaxTextureHeight;
int tileWidth = bitmap->GetWidth() / tilesAcross;
int lastTileExtraWidth = bitmap->GetWidth() % tilesAcross;
int tileHeight = bitmap->GetHeight() / tilesDown;
int lastTileExtraHeight = bitmap->GetHeight() % tilesDown;
int tileAllocatedWidth = tileWidth;
int tileAllocatedHeight = tileHeight;
AdjustSizeToNearestSupportedByCard(&tileAllocatedWidth, &tileAllocatedHeight);
int numTiles = tilesAcross * tilesDown;
TextureTile *tiles = (TextureTile*)malloc(sizeof(TextureTile) * numTiles);
memset(tiles, 0, sizeof(TextureTile) * numTiles);
OGLCUSTOMVERTEX *vertices = NULL;
if ((numTiles == 1) &&
(allocatedWidth == bitmap->GetWidth()) &&
(allocatedHeight == bitmap->GetHeight()))
{
// use default whole-image vertices
}
else
{
// The texture is not the same as the bitmap, so create some custom vertices
// so that only the relevant portion of the texture is rendered
int vertexBufferSize = numTiles * 4 * sizeof(OGLCUSTOMVERTEX);
ddb->_vertex = vertices = (OGLCUSTOMVERTEX*)malloc(vertexBufferSize);
}
for (int x = 0; x < tilesAcross; x++)
{
for (int y = 0; y < tilesDown; y++)
{
TextureTile *thisTile = &tiles[y * tilesAcross + x];
int thisAllocatedWidth = tileAllocatedWidth;
int thisAllocatedHeight = tileAllocatedHeight;
thisTile->x = x * tileWidth;
thisTile->y = y * tileHeight;
thisTile->width = tileWidth;
thisTile->height = tileHeight;
if (x == tilesAcross - 1)
{
thisTile->width += lastTileExtraWidth;
thisAllocatedWidth = thisTile->width;
AdjustSizeToNearestSupportedByCard(&thisAllocatedWidth, &thisAllocatedHeight);
}
if (y == tilesDown - 1)
{
thisTile->height += lastTileExtraHeight;
thisAllocatedHeight = thisTile->height;
AdjustSizeToNearestSupportedByCard(&thisAllocatedWidth, &thisAllocatedHeight);
}
if (vertices != NULL)
{
for (int vidx = 0; vidx < 4; vidx++)
{
int i = (y * tilesAcross + x) * 4 + vidx;
vertices[i] = defaultVertices[vidx];
if (vertices[i].tu > 0.0)
{
vertices[i].tu = (float)thisTile->width / (float)thisAllocatedWidth;
}
if (vertices[i].tv > 0.0)
{
vertices[i].tv = (float)thisTile->height / (float)thisAllocatedHeight;
}
}
}
glGenTextures(1, &thisTile->texture);
glBindTexture(GL_TEXTURE_2D, thisTile->texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, thisAllocatedWidth, thisAllocatedHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
}
}
ddb->_numTiles = numTiles;
ddb->_tiles = tiles;
UpdateDDBFromBitmap(ddb, bitmap, hasAlpha);
if (tempBmp != bitmap)
delete tempBmp;
return ddb;
}
void OGLGraphicsDriver::do_fade(bool fadingOut, int speed, int targetColourRed, int targetColourGreen, int targetColourBlue)
{
if (fadingOut)
{
this->_reDrawLastFrame();
}
else if (_drawScreenCallback != NULL)
_drawScreenCallback();
Bitmap *blackSquare = BitmapHelper::CreateBitmap(16, 16, 32);
blackSquare->Clear(makecol32(targetColourRed, targetColourGreen, targetColourBlue));
IDriverDependantBitmap *d3db = this->CreateDDBFromBitmap(blackSquare, false, false);
delete blackSquare;
d3db->SetStretch(_srcRect.GetWidth(), _srcRect.GetHeight(), false);
this->DrawSprite(-_global_x_offset, -_global_y_offset, d3db);
if (speed <= 0) speed = 16;
speed *= 2; // harmonise speeds with software driver which is faster
for (int a = 1; a < 255; a += speed)
{
int timerValue = *_loopTimer;
d3db->SetTransparency(fadingOut ? a : (255 - a));
this->_render(flipTypeLastTime, false);
do
{
if (_pollingCallback)
_pollingCallback();
platform->YieldCPU();
}
while (timerValue == *_loopTimer);
}
if (fadingOut)
{
d3db->SetTransparency(0);
this->_render(flipTypeLastTime, false);
}
this->DestroyDDB(d3db);
this->ClearDrawList();
}
void OGLGraphicsDriver::FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue)
{
do_fade(true, speed, targetColourRed, targetColourGreen, targetColourBlue);
}
void OGLGraphicsDriver::FadeIn(int speed, PALLETE p, int targetColourRed, int targetColourGreen, int targetColourBlue)
{
do_fade(false, speed, targetColourRed, targetColourGreen, targetColourBlue);
}
void OGLGraphicsDriver::BoxOutEffect(bool blackingOut, int speed, int delay)
{
if (blackingOut)
{
this->_reDrawLastFrame();
}
else if (_drawScreenCallback != NULL)
_drawScreenCallback();
Bitmap *blackSquare = BitmapHelper::CreateBitmap(16, 16, 32);
blackSquare->Clear();
IDriverDependantBitmap *d3db = this->CreateDDBFromBitmap(blackSquare, false, false);
delete blackSquare;
d3db->SetStretch(_srcRect.GetWidth(), _srcRect.GetHeight(), false);
this->DrawSprite(0, 0, d3db);
if (!blackingOut)
{
// when fading in, draw four black boxes, one
// across each side of the screen
this->DrawSprite(0, 0, d3db);
this->DrawSprite(0, 0, d3db);
this->DrawSprite(0, 0, d3db);
}
int yspeed = _srcRect.GetHeight() / (_srcRect.GetWidth() / speed);
int boxWidth = speed;
int boxHeight = yspeed;
while (boxWidth < _srcRect.GetWidth())
{
boxWidth += speed;
boxHeight += yspeed;
size_t last = drawList.size() - 1;
if (blackingOut)
{
drawList[last].x = _srcRect.GetWidth() / 2- boxWidth / 2;
drawList[last].y = _srcRect.GetHeight() / 2 - boxHeight / 2;
d3db->SetStretch(boxWidth, boxHeight, false);
}
else
{
drawList[last - 3].x = _srcRect.GetWidth() / 2 - boxWidth / 2 - _srcRect.GetWidth();
drawList[last - 2].y = _srcRect.GetHeight() / 2 - boxHeight / 2 - _srcRect.GetHeight();
drawList[last - 1].x = _srcRect.GetWidth() / 2 + boxWidth / 2;
drawList[last ].y = _srcRect.GetHeight() / 2 + boxHeight / 2;
d3db->SetStretch(_srcRect.GetWidth(), _srcRect.GetHeight(), false);
}
this->_render(flipTypeLastTime, false);
if (_pollingCallback)
_pollingCallback();
platform->Delay(delay);
}
this->DestroyDDB(d3db);
this->ClearDrawList();
}
bool OGLGraphicsDriver::PlayVideo(const char *filename, bool useAVISound, VideoSkipType skipType, bool stretchToFullScreen)
{
return true;
}
void OGLGraphicsDriver::create_screen_tint_bitmap()
{
// Some work on textures depends on current scaling filter, sadly
// TODO: find out if there is a workaround for that
if (!IsModeSet() || !_filter)
return;
_screenTintLayer = BitmapHelper::CreateBitmap(16, 16, _mode.ColorDepth);
_screenTintLayer = ReplaceBitmapWithSupportedFormat(_screenTintLayer);
_screenTintLayerDDB = (OGLBitmap*)this->CreateDDBFromBitmap(_screenTintLayer, false, false);
_screenTintSprite.bitmap = _screenTintLayerDDB;
}
void OGLGraphicsDriver::SetScreenTint(int red, int green, int blue)
{
if ((red != _tint_red) || (green != _tint_green) || (blue != _tint_blue))
{
_tint_red = red;
_tint_green = green;
_tint_blue = blue;
_screenTintLayer->Clear(makecol_depth(_screenTintLayer->GetColorDepth(), red, green, blue));
this->UpdateDDBFromBitmap(_screenTintLayerDDB, _screenTintLayer, false);
_screenTintLayerDDB->SetStretch(_srcRect.GetWidth(), _srcRect.GetHeight(), false);
_screenTintLayerDDB->SetTransparency(128);
_screenTintSprite.skip = ((red == 0) && (green == 0) && (blue == 0));
}
}
OGLGraphicsFactory *OGLGraphicsFactory::_factory = NULL;
OGLGraphicsFactory::~OGLGraphicsFactory()
{
_factory = NULL;
}
size_t OGLGraphicsFactory::GetFilterCount() const
{
return 2;
}
const GfxFilterInfo *OGLGraphicsFactory::GetFilterInfo(size_t index) const
{
switch (index)
{
case 0:
return &OGLGfxFilter::FilterInfo;
case 1:
return &AAOGLGfxFilter::FilterInfo;
default:
return NULL;
}
}
String OGLGraphicsFactory::GetDefaultFilterID() const
{
return OGLGfxFilter::FilterInfo.Id;
}
/* static */ OGLGraphicsFactory *OGLGraphicsFactory::GetFactory()
{
if (!_factory)
_factory = new OGLGraphicsFactory();
return _factory;
}
OGLGraphicsDriver *OGLGraphicsFactory::EnsureDriverCreated()
{
if (!_driver)
_driver = new OGLGraphicsDriver();
return _driver;
}
OGLGfxFilter *OGLGraphicsFactory::CreateFilter(const String &id)
{
if (OGLGfxFilter::FilterInfo.Id.CompareNoCase(id) == 0)
return new OGLGfxFilter();
else if (AAOGLGfxFilter::FilterInfo.Id.CompareNoCase(id) == 0)
return new AAOGLGfxFilter();
return NULL;
}
} // namespace OGL
} // namespace Engine
} // namespace AGS
#endif // only on Windows, Android and iOS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment