Files
Aerofoil/AerofoilSDL/GpDisplayDriver_SDL_GL2.cpp
Phil Marell e098370249 Use Command key instead of Control as modifier on macOS
This provides an experience closer to the original classic Mac version.

This is supported by the earlier changes to gracefully quit the game.
2021-07-24 16:27:55 +10:00

3199 lines
91 KiB
C++

#include "IGpDisplayDriver.h"
#include "CoreDefs.h"
#include "GpApplicationName.h"
#include "GpComPtr.h"
#include "GpDisplayDriverProperties.h"
#include "GpVOSEvent.h"
#include "GpRingBuffer.h"
#include "GpInputDriver_SDL_Gamepad.h"
#include "GpSDL.h"
#include "GpUnicode.h"
#include "IGpCursor.h"
#include "IGpDisplayDriverSurface.h"
#include "IGpLogDriver.h"
#include "IGpPrefsHandler.h"
#include "IGpSystemServices.h"
#include "IGpVOSEventQueue.h"
#include "SDL_events.h"
#include "SDL_mouse.h"
#include "SDL_opengl.h"
#include "SDL_video.h"
#include <stdlib.h>
#include <new>
#include <assert.h>
#include <chrono>
#include <numeric>
#include <vector>
#include <algorithm>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#pragma push_macro("LoadCursor")
#ifdef LoadCursor
#undef LoadCursor
#endif
#define GP_GL_IS_OPENGL_4_CONTEXT 0
class GpDisplayDriver_SDL_GL2;
static GpDisplayDriverSurfaceEffects gs_defaultEffects;
static const char *kPrefsIdentifier = "GpDisplayDriverSDL_GL2";
static uint32_t kPrefsVersion = 1;
struct GpDisplayDriver_SDL_GL2_Prefs
{
bool m_isFullScreen;
};
namespace GpBinarizedShaders
{
extern const char *g_drawQuadV_GL2;
extern const char *g_drawQuadPalettePF_GL2;
extern const char *g_drawQuadPalettePNF_GL2;
extern const char *g_drawQuad32PF_GL2;
extern const char *g_drawQuad32PNF_GL2;
extern const char *g_drawQuadPaletteICCPF_GL2;
extern const char *g_drawQuadPaletteICCPNF_GL2;
extern const char *g_drawQuad32ICCPF_GL2;
extern const char *g_drawQuad32ICCPNF_GL2;
extern const char *g_copyQuadP_GL2;
extern const char *g_scaleQuadP_GL2;
}
struct GpGLFunctions
{
typedef void (GLAPIENTRYP PFNGLCLEARPROC)(GLbitfield mask);
typedef void (GLAPIENTRYP PFNGLCLEARCOLORPROC)(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
typedef void (GLAPIENTRYP PFNGLVIEWPORTPROC)(GLint x, GLint y, GLsizei width, GLsizei height);
typedef void (GLAPIENTRYP PFNGLDRAWELEMENTSPROC)(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices);
typedef void (GLAPIENTRYP PFNGLBINDTEXTUREPROC)(GLenum target, GLuint texture);
typedef void (GLAPIENTRYP PFNGLGENTEXTURESPROC)(GLsizei n, GLuint *textures);
typedef void (GLAPIENTRYP PFNGLDELETETEXTURESPROC)(GLsizei n, GLuint *textures);
typedef void (GLAPIENTRYP PFNGLTEXIMAGE2DPROC)(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
typedef void (GLAPIENTRYP PFNGLTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels);
typedef void (GLAPIENTRYP PFNGLPIXELSTOREIPROC)(GLenum pname, GLint param);
typedef void (GLAPIENTRYP PFNGLTEXPARAMETERIPROC)(GLenum target, GLenum pname, GLint param);
typedef GLenum (GLAPIENTRYP PFNGLGETERRORPROC)();
typedef void (GLAPIENTRYP PFNGLENABLEPROC)(GLenum cap);
typedef void (GLAPIENTRYP PFNGLDISABLEPROC)(GLenum cap);
PFNGLENABLEPROC Enable;
PFNGLDISABLEPROC Disable;
PFNGLCLEARPROC Clear;
PFNGLCLEARCOLORPROC ClearColor;
PFNGLVIEWPORTPROC Viewport;
PFNGLGENFRAMEBUFFERSPROC GenFramebuffers;
PFNGLBINDFRAMEBUFFERPROC BindFramebuffer;
PFNGLFRAMEBUFFERTEXTURE2DPROC FramebufferTexture2D;
PFNGLCHECKFRAMEBUFFERSTATUSPROC CheckFramebufferStatus;
PFNGLDELETEFRAMEBUFFERSPROC DeleteFramebuffers;
PFNGLGENBUFFERSPROC GenBuffers;
PFNGLBUFFERDATAPROC BufferData;
PFNGLBINDBUFFERPROC BindBuffer;
PFNGLDELETEBUFFERSPROC DeleteBuffers;
PFNGLCREATEPROGRAMPROC CreateProgram;
PFNGLDELETEPROGRAMPROC DeleteProgram;
PFNGLLINKPROGRAMPROC LinkProgram;
PFNGLUSEPROGRAMPROC UseProgram;
PFNGLGETPROGRAMIVPROC GetProgramiv;
PFNGLGETPROGRAMINFOLOGPROC GetProgramInfoLog;
PFNGLGETUNIFORMLOCATIONPROC GetUniformLocation;
PFNGLGETATTRIBLOCATIONPROC GetAttribLocation;
PFNGLUNIFORM4FVPROC Uniform4fv;
PFNGLUNIFORM2FVPROC Uniform2fv;
PFNGLUNIFORM1FVPROC Uniform1fv;
PFNGLVERTEXATTRIBPOINTERPROC VertexAttribPointer;
PFNGLENABLEVERTEXATTRIBARRAYPROC EnableVertexAttribArray;
PFNGLDISABLEVERTEXATTRIBARRAYPROC DisableVertexAttribArray;
#if GP_GL_IS_OPENGL_4_CONTEXT
PFNGLGENVERTEXARRAYSPROC GenVertexArrays;
PFNGLDELETEVERTEXARRAYSPROC DeleteVertexArrays;
PFNGLBINDVERTEXARRAYPROC BindVertexArray;
#endif
PFNGLCREATESHADERPROC CreateShader;
PFNGLCOMPILESHADERPROC CompileShader;
PFNGLGETSHADERIVPROC GetShaderiv;
PFNGLGETSHADERINFOLOGPROC GetShaderInfoLog;
PFNGLATTACHSHADERPROC AttachShader;
PFNGLSHADERSOURCEPROC ShaderSource;
PFNGLDELETESHADERPROC DeleteShader;
PFNGLDRAWELEMENTSPROC DrawElements;
PFNGLACTIVETEXTUREPROC ActiveTexture;
PFNGLBINDTEXTUREPROC BindTexture;
PFNGLTEXPARAMETERIPROC TexParameteri;
PFNGLTEXIMAGE2DPROC TexImage2D;
PFNGLTEXSUBIMAGE2DPROC TexSubImage2D;
PFNGLPIXELSTOREIPROC PixelStorei;
PFNGLUNIFORM1IPROC Uniform1i;
PFNGLGENTEXTURESPROC GenTextures;
PFNGLDELETETEXTURESPROC DeleteTextures;
PFNGLGETERRORPROC GetError;
bool LookUpFunctions();
};
static void CheckGLError(const GpGLFunctions &gl, IGpLogDriver *logger)
{
#if GP_DEBUG_CONFIG
GLenum errorCode = gl.GetError();
if (errorCode != 0)
{
if (logger)
logger->Printf(IGpLogDriver::Category_Error, "GL error reported: %x", static_cast<int>(errorCode));
}
assert(errorCode == 0);
#endif
}
class GpGLObject
{
public:
explicit GpGLObject();
void AddRef();
void Release();
protected:
void InitDriver(GpDisplayDriver_SDL_GL2 *driver, const GpGLFunctions *gl);
virtual void Destroy() = 0;
GpDisplayDriver_SDL_GL2 *m_driver;
const GpGLFunctions *m_gl;
unsigned int m_count;
};
GpGLObject::GpGLObject()
: m_count(0)
, m_driver(nullptr)
{
}
void GpGLObject::AddRef()
{
++m_count;
}
void GpGLObject::Release()
{
unsigned int count = m_count;
if (count == 1)
{
this->Destroy();
return;
}
else
m_count = count - 1;
}
void GpGLObject::InitDriver(GpDisplayDriver_SDL_GL2 *driver, const GpGLFunctions *gl)
{
m_driver = driver;
m_gl = gl;
}
template<class T>
class GpGLObjectImpl : public GpGLObject
{
public:
static T *Create(GpDisplayDriver_SDL_GL2 *driver);
protected:
virtual bool Init() = 0;
void Destroy() final override;
};
template<class T>
void GpGLObjectImpl<T>::Destroy()
{
T *self = static_cast<T*>(this);
self->~T();
free(self);
}
class GpGLRenderTargetView final : public GpGLObjectImpl<GpGLRenderTargetView>
{
public:
GpGLRenderTargetView();
~GpGLRenderTargetView();
bool Init() override;
GLuint GetID() const;
private:
GLuint m_id;
};
GpGLRenderTargetView::GpGLRenderTargetView()
: m_id(0)
{
}
GpGLRenderTargetView::~GpGLRenderTargetView()
{
if (m_id)
m_gl->DeleteFramebuffers(1, &m_id);
}
bool GpGLRenderTargetView::Init()
{
m_gl->GenFramebuffers(1, &m_id);
return m_id != 0;
}
GLuint GpGLRenderTargetView::GetID() const
{
return m_id;
}
class GpGLTexture final : public GpGLObjectImpl<GpGLTexture>
{
public:
GpGLTexture();
~GpGLTexture();
bool Init() override;
GLuint GetID() const;
private:
GLuint m_id;
};
GpGLTexture::GpGLTexture()
: m_id(0)
{
}
GpGLTexture::~GpGLTexture()
{
if (m_gl)
m_gl->DeleteTextures(1, &m_id);
}
bool GpGLTexture::Init()
{
m_gl->GenTextures(1, &m_id);
return m_id != 0;
}
GLuint GpGLTexture::GetID() const
{
return m_id;
}
class GpGLBuffer final : public GpGLObjectImpl<GpGLBuffer>
{
public:
GpGLBuffer();
~GpGLBuffer();
bool Init() override;
GLuint GetID() const;
private:
GLuint m_id;
};
GpGLBuffer::GpGLBuffer()
: m_id(0)
{
}
GpGLBuffer::~GpGLBuffer()
{
if (m_gl)
m_gl->DeleteBuffers(1, &m_id);
}
bool GpGLBuffer::Init()
{
m_gl->GenBuffers(1, &m_id);
return m_id != 0;
}
GLuint GpGLBuffer::GetID() const
{
return m_id;
}
struct GpGLVertexArraySpec
{
const GpGLBuffer *m_buffer;
GLuint m_index;
GLint m_size;
GLenum m_type;
GLboolean m_normalized;
GLsizei m_stride;
GLsizei m_offset;
};
#if GP_GL_IS_OPENGL_4_CONTEXT
class GpGLVertexArray final : public GpGLObjectImpl<GpGLVertexArray>
{
public:
GpGLVertexArray();
~GpGLVertexArray();
bool Init() override;
GLuint GetID() const;
bool InitWithSpecs(const GpGLVertexArraySpec *specs, size_t numSpecs);
void Activate(const GLint *locations);
void Deactivate(const GLint *locations);
private:
GLuint m_id;
};
GpGLVertexArray::GpGLVertexArray()
: m_id(0)
{
}
GpGLVertexArray::~GpGLVertexArray()
{
if (m_id)
m_gl->DeleteVertexArrays(1, &m_id);
}
bool GpGLVertexArray::Init()
{
m_gl->GenVertexArrays(1, &m_id);
return m_id != 0;
}
GLuint GpGLVertexArray::GetID() const
{
return m_id;
}
bool GpGLVertexArray::InitWithSpecs(const GpGLVertexArraySpec *specs, size_t numSpecs)
{
m_gl->BindVertexArray(m_id);
for (size_t i = 0; i < numSpecs; i++)
{
const GpGLVertexArraySpec &spec = specs[i];
m_gl->BindBuffer(GL_ARRAY_BUFFER, spec.m_buffer->GetID());
m_gl->VertexAttribPointer(spec.m_index, spec.m_size, spec.m_type, spec.m_normalized, spec.m_stride, static_cast<const char*>(nullptr) + spec.m_offset);
m_gl->EnableVertexAttribArray(spec.m_index);
m_gl->BindBuffer(GL_ARRAY_BUFFER, 0);
}
m_gl->BindVertexArray(0);
return true;
}
void GpGLVertexArray::Activate(const GLint *locations)
{
m_gl->BindVertexArray(m_id);
}
void GpGLVertexArray::Deactivate(const GLint *locations)
{
m_gl->BindVertexArray(0);
}
#else
class GpGLVertexArray final : public GpGLObjectImpl<GpGLVertexArray>
{
public:
GpGLVertexArray();
~GpGLVertexArray();
bool Init() override;
bool InitWithSpecs(const GpGLVertexArraySpec *specs, size_t numSpecs);
void Activate(const GLint *locations);
void Deactivate(const GLint *locations);
private:
GpGLVertexArraySpec *m_specs;
size_t m_numSpecs;
};
GpGLVertexArray::GpGLVertexArray()
: m_specs(nullptr)
, m_numSpecs(0)
{
}
GpGLVertexArray::~GpGLVertexArray()
{
if (m_specs)
free(m_specs);
}
bool GpGLVertexArray::Init()
{
return true;
}
bool GpGLVertexArray::InitWithSpecs(const GpGLVertexArraySpec *specs, size_t numSpecs)
{
m_specs = static_cast<GpGLVertexArraySpec*>(malloc(sizeof(GpGLVertexArraySpec) * numSpecs));
if (!m_specs)
return false;
for (size_t i = 0; i < numSpecs; i++)
m_specs[i] = specs[i];
m_numSpecs = numSpecs;
return true;
}
void GpGLVertexArray::Activate(const GLint *locations)
{
size_t numSpecs = m_numSpecs;
for (size_t i = 0; i < numSpecs; i++)
{
if (locations[i] < 0)
continue;
const GpGLVertexArraySpec &spec = m_specs[i];
m_gl->BindBuffer(GL_ARRAY_BUFFER, spec.m_buffer->GetID());
m_gl->VertexAttribPointer(locations[i], spec.m_size, spec.m_type, spec.m_normalized, spec.m_stride, static_cast<const char*>(nullptr) + spec.m_offset);
m_gl->EnableVertexAttribArray(locations[i]);
m_gl->BindBuffer(GL_ARRAY_BUFFER, 0);
}
}
void GpGLVertexArray::Deactivate(const GLint *locations)
{
size_t numSpecs = m_numSpecs;
for (size_t i = 0; i < numSpecs; i++)
{
if (locations[i] < 0)
continue;
m_gl->DisableVertexAttribArray(locations[i]);
}
}
#endif
class GpGLProgram final : public GpGLObjectImpl<GpGLProgram>
{
public:
GpGLProgram();
~GpGLProgram();
bool Init() override;
GLuint GetID() const;
private:
GLuint m_id;
};
GpGLProgram::GpGLProgram()
: m_id(0)
{
}
GpGLProgram::~GpGLProgram()
{
if (m_id)
m_gl->DeleteProgram(m_id);
}
bool GpGLProgram::Init()
{
m_id = m_gl->CreateProgram();
return m_id != 0;
}
GLuint GpGLProgram::GetID() const
{
return m_id;
}
template<GLuint TShaderType>
class GpGLShader final : public GpGLObjectImpl<GpGLShader<TShaderType> >
{
public:
GpGLShader();
~GpGLShader();
bool Init() override;
GLuint GetID() const;
private:
GLuint m_id;
};
template<GLuint TShaderType>
GpGLShader<TShaderType>::GpGLShader()
: m_id(0)
{
}
template<GLuint TShaderType>
GpGLShader<TShaderType>::~GpGLShader()
{
if (m_id != 0)
this->m_gl->DeleteShader(m_id);
}
template<GLuint TShaderType>
bool GpGLShader<TShaderType>::Init()
{
m_id = this->m_gl->CreateShader(TShaderType);
return m_id != 0;
}
template<GLuint TShaderType>
GLuint GpGLShader<TShaderType>::GetID() const
{
return m_id;
}
class GpDisplayDriverSurface_GL2 : public IGpDisplayDriverSurface
{
public:
GpDisplayDriverSurface_GL2(GpDisplayDriver_SDL_GL2 *driver, size_t width, size_t height, size_t pitch, GpGLTexture *texture, GpPixelFormat_t pixelFormat, IGpDisplayDriver::SurfaceInvalidateCallback_t invalidateCallback, void *invalidateContext);
~GpDisplayDriverSurface_GL2();
static GpDisplayDriverSurface_GL2 *Create(GpDisplayDriver_SDL_GL2 *driver, size_t width, size_t height, size_t pitch, GpPixelFormat_t pixelFormat, GpDisplayDriverSurface_GL2 *prevSurface, IGpDisplayDriver::SurfaceInvalidateCallback_t invalidateCallback, void *invalidateContext);
void Upload(const void *data, size_t x, size_t y, size_t width, size_t height, size_t pitch);
void UploadEntire(const void *data, size_t pitch);
void Destroy();
void DestroyAll();
bool RecreateAll();
size_t GetImageWidth() const;
size_t GetPaddedTextureWidth() const;
size_t GetHeight() const;
GpPixelFormat_t GetPixelFormat() const;
GpGLTexture *GetTexture() const;
private:
bool Init(GpDisplayDriverSurface_GL2 *prevSurface);
bool RecreateSingle();
GLenum ResolveGLFormat() const;
GLenum ResolveGLInternalFormat() const;
GLenum ResolveGLType() const;
GpComPtr<GpGLTexture> m_texture;
const GpGLFunctions *m_gl;
GpPixelFormat_t m_pixelFormat;
size_t m_imageWidth;
size_t m_paddedTextureWidth;
size_t m_pitch;
size_t m_height;
GpDisplayDriver_SDL_GL2 *m_driver;
GpDisplayDriverSurface_GL2 *m_next;
GpDisplayDriverSurface_GL2 *m_prev;
IGpDisplayDriver::SurfaceInvalidateCallback_t m_invalidateCallback;
void *m_invalidateContext;
};
class GpCursor_SDL2 final : public IGpCursor
{
public:
explicit GpCursor_SDL2(SDL_Cursor *cursor);
SDL_Cursor* GetCursor() const;
void IncRef();
void DecRef();
void Destroy() override { this->DecRef(); }
private:
SDL_Cursor *m_cursor;
unsigned int m_count;
};
GpCursor_SDL2::GpCursor_SDL2(SDL_Cursor *cursor)
: m_cursor(cursor)
, m_count(1)
{
}
SDL_Cursor* GpCursor_SDL2::GetCursor() const
{
return m_cursor;
}
void GpCursor_SDL2::IncRef()
{
++m_count;
}
void GpCursor_SDL2::DecRef()
{
if (m_count == 1)
delete this;
else
--m_count;
}
class GpDisplayDriver_SDL_GL2 final : public IGpDisplayDriver, public IGpPrefsHandler
{
public:
explicit GpDisplayDriver_SDL_GL2(const GpDisplayDriverProperties &properties);
~GpDisplayDriver_SDL_GL2();
bool Init() GP_ASYNCIFY_PARANOID_OVERRIDE;
void ServeTicks(int tickCount) GP_ASYNCIFY_PARANOID_OVERRIDE;
void ForceSync() override;
void Shutdown() GP_ASYNCIFY_PARANOID_OVERRIDE;
void TranslateSDLMessage(const SDL_Event *msg, IGpVOSEventQueue *eventQueue, float pixelScaleX, float pixelScaleY, bool obstructiveTextInput);
void GetInitialDisplayResolution(unsigned int *width, unsigned int *height) override;
IGpDisplayDriverSurface *CreateSurface(size_t width, size_t height, size_t pitch, GpPixelFormat_t pixelFormat, SurfaceInvalidateCallback_t invalidateCallback, void *invalidateContext) override;
void DrawSurface(IGpDisplayDriverSurface *surface, int32_t x, int32_t y, size_t width, size_t height, const GpDisplayDriverSurfaceEffects *effects) override;
IGpCursor *CreateBWCursor(size_t width, size_t height, const void *pixelData, const void *maskData, size_t hotSpotX, size_t hotSpotY) GP_ASYNCIFY_PARANOID_OVERRIDE;
IGpCursor *CreateColorCursor(size_t width, size_t height, const void *pixelDataRGBA, size_t hotSpotX, size_t hotSpotY) GP_ASYNCIFY_PARANOID_OVERRIDE;
void SetCursor(IGpCursor *cursor) override;
void SetStandardCursor(EGpStandardCursor_t standardCursor) override;
void UpdatePalette(const void *paletteData) override;
void SetBackgroundColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override;
void SetBackgroundDarkenEffect(bool isDark) override;
void SetUseICCProfile(bool useICCProfile) override;
void RequestToggleFullScreen(uint32_t timestamp) override;
void RequestResetVirtualResolution() override;
bool IsFullScreen() const override;
const GpDisplayDriverProperties &GetProperties() const override;
IGpPrefsHandler *GetPrefsHandler() const override;
bool SupportsSizedFormats() const;
void ApplyPrefs(const void *identifier, size_t identifierSize, const void *contents, size_t contentsSize, uint32_t version) override;
bool SavePrefs(void *context, WritePrefsFunc_t writeFunc) override;
void UnlinkSurface(GpDisplayDriverSurface_GL2 *surface, GpDisplayDriverSurface_GL2 *prev, GpDisplayDriverSurface_GL2 *next);
const GpGLFunctions *GetGLFunctions() const;
template<GLuint TShaderType> GpComPtr<GpGLShader<TShaderType> > CreateShader(const char *shaderSrc);
private:
struct DrawQuadPixelFloatConstants
{
float m_modulation[4];
float m_flickerAxis[2];
float m_flickerStart;
float m_flickerEnd;
float m_desaturation;
float m_unused[3];
};
struct CompactedPresentHistoryItem
{
std::chrono::time_point<std::chrono::high_resolution_clock>::duration m_timestamp;
unsigned int m_numFrames;
};
void StartOpenGLForWindow(IGpLogDriver *logger);
bool InitResources(uint32_t physicalWidth, uint32_t physicalHeight, uint32_t virtualWidth, uint32_t virtualHeight);
void BecomeFullScreen();
void BecomeWindowed();
void SynchronizeCursors();
void ChangeToCursor(SDL_Cursor *cursor);
void ChangeToStandardCursor(EGpStandardCursor_t cursor);
bool ResizeOpenGLWindow(uint32_t &windowWidth, uint32_t &windowHeight, uint32_t desiredWidth, uint32_t desiredHeight, IGpLogDriver *logger);
bool InitBackBuffer(uint32_t width, uint32_t height);
void ScaleVirtualScreen();
bool SyncRender();
GpGLFunctions m_gl;
GpDisplayDriverProperties m_properties;
struct DrawQuadProgram
{
GpComPtr<GpGLProgram> m_program;
GLint m_vertexNDCOriginAndDimensionsLocation;
GLint m_vertexSurfaceDimensionsLocation;
GLint m_vertexPosUVLocation;
GLint m_pixelModulationLocation;
GLint m_pixelFlickerAxisLocation;
GLint m_pixelFlickerStartThresholdLocation;
GLint m_pixelFlickerEndThresholdLocation;
GLint m_pixelDesaturationLocation;
GLint m_pixelSurfaceTextureLocation;
GLint m_pixelPaletteTextureLocation;
bool Link(GpDisplayDriver_SDL_GL2 *driver, const GpGLShader<GL_VERTEX_SHADER> *vertexShader, const GpGLShader<GL_FRAGMENT_SHADER> *pixelShader);
};
struct BlitQuadProgram
{
GpComPtr<GpGLProgram> m_program;
GLint m_vertexNDCOriginAndDimensionsLocation;
GLint m_vertexSurfaceDimensionsLocation;
GLint m_pixelDXDYDimensionsLocation;
GLint m_vertexPosUVLocation;
GLint m_pixelSurfaceTextureLocation;
bool Link(GpDisplayDriver_SDL_GL2 *driver, const GpGLShader<GL_VERTEX_SHADER> *vertexShader, const GpGLShader<GL_FRAGMENT_SHADER> *pixelShader);
};
struct InstancedResources
{
GpComPtr<GpGLRenderTargetView> m_virtualScreenTextureRTV;
GpComPtr<GpGLTexture> m_virtualScreenTexture;
GpComPtr<GpGLRenderTargetView> m_upscaleTextureRTV;
GpComPtr<GpGLTexture> m_upscaleTexture;
GpComPtr<GpGLVertexArray> m_quadVertexArray;
GpComPtr<GpGLBuffer> m_quadVertexBufferKeepalive;
GpComPtr<GpGLBuffer> m_quadIndexBuffer;
GpComPtr<GpGLTexture> m_paletteTexture;
BlitQuadProgram m_scaleQuadProgram;
BlitQuadProgram m_copyQuadProgram;
DrawQuadProgram m_drawQuadPaletteNoFlickerProgram;
DrawQuadProgram m_drawQuadPaletteFlickerProgram;
DrawQuadProgram m_drawQuad15NoFlickerProgram;
DrawQuadProgram m_drawQuad15FlickerProgram;
DrawQuadProgram m_drawQuad32NoFlickerProgram;
DrawQuadProgram m_drawQuad32FlickerProgram;
DrawQuadProgram m_drawQuadPaletteICCNoFlickerProgram;
DrawQuadProgram m_drawQuadPaletteICCFlickerProgram;
DrawQuadProgram m_drawQuad15ICCNoFlickerProgram;
DrawQuadProgram m_drawQuad15ICCFlickerProgram;
DrawQuadProgram m_drawQuad32ICCNoFlickerProgram;
DrawQuadProgram m_drawQuad32ICCFlickerProgram;
};
InstancedResources m_res;
SDL_Window *m_window;
SDL_GLContext m_glContext;
SDL_Cursor *m_waitCursor;
SDL_Cursor *m_iBeamCursor;
SDL_Cursor *m_arrowCursor;
bool m_cursorIsHidden;
bool m_contextLost;
bool m_isResettingSwapChain;
bool m_isFullScreen;
bool m_isFullScreenDesired;
bool m_isResolutionResetDesired;
int m_windowModeRevertX;
int m_windowModeRevertY;
int m_windowModeRevertWidth;
int m_windowModeRevertHeight;
uint32_t m_lastFullScreenToggleTimeStamp;
std::chrono::high_resolution_clock::duration m_frameTimeAccumulated;
std::chrono::high_resolution_clock::duration m_frameTimeSliceSize;
uint32_t m_windowWidthPhysical; // Physical resolution is the resolution of the actual window
uint32_t m_windowHeightPhysical;
uint32_t m_windowWidthVirtual; // Virtual resolution is the resolution reported to the game
uint32_t m_windowHeightVirtual;
uint32_t m_initialWidthVirtual; // Virtual resolution is the resolution reported to the game
uint32_t m_initialHeightVirtual;
float m_pixelScaleX;
float m_pixelScaleY;
bool m_useUpscaleFilter;
uint32_t m_upscaleTextureWidth;
uint32_t m_upscaleTextureHeight;
GpCursor_SDL2 *m_activeCursor;
GpCursor_SDL2 *m_pendingCursor;
EGpStandardCursor_t m_currentStandardCursor;
EGpStandardCursor_t m_pendingStandardCursor;
bool m_mouseIsInClientArea;
float m_bgColor[4];
bool m_bgIsDark;
bool m_useICCProfile;
std::chrono::time_point<std::chrono::high_resolution_clock>::duration m_syncTimeBase;
GpRingBuffer<CompactedPresentHistoryItem, 60> m_presentHistory;
GpDisplayDriverSurface_GL2 *m_firstSurface;
GpDisplayDriverSurface_GL2 *m_lastSurface;
uint8_t m_paletteStorage[256 * 4 + GP_SYSTEM_MEMORY_ALIGNMENT];
uint8_t *m_paletteData;
bool m_textInputEnabled;
};
GpDisplayDriverSurface_GL2::GpDisplayDriverSurface_GL2(GpDisplayDriver_SDL_GL2 *driver, size_t width, size_t height, size_t pitch, GpGLTexture *texture, GpPixelFormat_t pixelFormat, IGpDisplayDriver::SurfaceInvalidateCallback_t invalidateCallback, void *invalidateContext)
: m_gl(driver->GetGLFunctions())
, m_texture(texture)
, m_pixelFormat(pixelFormat)
, m_imageWidth(width)
, m_paddedTextureWidth(0)
, m_height(height)
, m_pitch(pitch)
, m_driver(driver)
, m_prev(nullptr)
, m_next(nullptr)
, m_invalidateCallback(invalidateCallback)
, m_invalidateContext(invalidateContext)
{
size_t paddingPixels = 0;
switch (pixelFormat)
{
case GpPixelFormats::kBW1:
case GpPixelFormats::k8BitStandard:
case GpPixelFormats::k8BitCustom:
paddingPixels = pitch - width;
break;
case GpPixelFormats::kRGB555:
assert(pitch % 2 == 0);
paddingPixels = pitch / 2 - width;
break;
case GpPixelFormats::kRGB24:
assert(pitch % 3 == 0);
paddingPixels = pitch / 3 - width;
break;
case GpPixelFormats::kRGB32:
assert(pitch % 4 == 0);
paddingPixels = pitch / 4 - width;
break;
default:
assert(false);
paddingPixels = 0;
}
m_paddedTextureWidth = width + paddingPixels;
}
GpDisplayDriverSurface_GL2::~GpDisplayDriverSurface_GL2()
{
if (m_prev)
m_prev->m_next = m_next;
if (m_next)
m_next->m_prev = m_prev;
m_driver->UnlinkSurface(this, m_prev, m_next);
}
GpDisplayDriverSurface_GL2 *GpDisplayDriverSurface_GL2::Create(GpDisplayDriver_SDL_GL2 *driver, size_t width, size_t height, size_t pitch, GpPixelFormat_t pixelFormat, GpDisplayDriverSurface_GL2 *prevSurface, IGpDisplayDriver::SurfaceInvalidateCallback_t invalidateCallback, void *invalidateContext)
{
GpComPtr<GpGLTexture> texture = GpComPtr<GpGLTexture>(GpGLTexture::Create(driver));
if (!texture)
return nullptr;
GpDisplayDriverSurface_GL2 *surface = static_cast<GpDisplayDriverSurface_GL2*>(malloc(sizeof(GpDisplayDriverSurface_GL2)));
if (!surface)
return nullptr;
new (surface) GpDisplayDriverSurface_GL2(driver, width, height, pitch, texture, pixelFormat, invalidateCallback, invalidateContext);
if (!surface->Init(prevSurface))
{
surface->Destroy();
return nullptr;
}
return surface;
}
void GpDisplayDriverSurface_GL2::Upload(const void *data, size_t x, size_t y, size_t width, size_t height, size_t pitch)
{
m_gl->BindTexture(GL_TEXTURE_2D, m_texture->GetID());
m_gl->TexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, ResolveGLFormat(), ResolveGLType(), data);
m_gl->BindTexture(GL_TEXTURE_2D, 0);
}
void GpDisplayDriverSurface_GL2::UploadEntire(const void *data, size_t pitch)
{
assert(pitch == m_pitch);
CheckGLError(*m_gl, m_driver->GetProperties().m_logger);
const GLint internalFormat = ResolveGLInternalFormat();
const GLenum glFormat = ResolveGLFormat();
const GLenum glType = ResolveGLType();
m_gl->BindTexture(GL_TEXTURE_2D, m_texture->GetID());
m_gl->TexImage2D(GL_TEXTURE_2D, 0, internalFormat, m_paddedTextureWidth, m_height, 0, glFormat, glType, data);
m_gl->BindTexture(GL_TEXTURE_2D, 0);
CheckGLError(*m_gl, m_driver->GetProperties().m_logger);
}
void GpDisplayDriverSurface_GL2::Destroy()
{
this->~GpDisplayDriverSurface_GL2();
free(this);
}
void GpDisplayDriverSurface_GL2::DestroyAll()
{
for (GpDisplayDriverSurface_GL2 *scan = this; scan; scan = scan->m_next)
{
scan->m_invalidateCallback(scan->m_invalidateContext);
scan->m_texture = nullptr;
}
}
bool GpDisplayDriverSurface_GL2::RecreateAll()
{
for (GpDisplayDriverSurface_GL2 *scan = this; scan; scan = scan->m_next)
{
if (!scan->RecreateSingle())
return false;
}
return true;
}
size_t GpDisplayDriverSurface_GL2::GetImageWidth() const
{
return m_imageWidth;
}
size_t GpDisplayDriverSurface_GL2::GetPaddedTextureWidth() const
{
return m_paddedTextureWidth;
}
size_t GpDisplayDriverSurface_GL2::GetHeight() const
{
return m_height;
}
GpPixelFormat_t GpDisplayDriverSurface_GL2::GetPixelFormat() const
{
return m_pixelFormat;
}
GpGLTexture *GpDisplayDriverSurface_GL2::GetTexture() const
{
return m_texture;
}
bool GpDisplayDriverSurface_GL2::Init(GpDisplayDriverSurface_GL2 *prevSurface)
{
CheckGLError(*m_gl, m_driver->GetProperties().m_logger);
m_gl->BindTexture(GL_TEXTURE_2D, m_texture->GetID());
m_gl->PixelStorei(GL_UNPACK_ALIGNMENT, 1);
m_gl->TexImage2D(GL_TEXTURE_2D, 0, ResolveGLInternalFormat(), m_paddedTextureWidth, m_height, 0, ResolveGLFormat(), ResolveGLType(), nullptr);
m_gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
m_gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
m_gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
m_gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
m_gl->BindTexture(GL_TEXTURE_2D, 0);
CheckGLError(*m_gl, m_driver->GetProperties().m_logger);
m_prev = prevSurface;
return true;
}
bool GpDisplayDriverSurface_GL2::RecreateSingle()
{
m_texture = GpGLTexture::Create(m_driver);
return m_texture != nullptr && this->Init(m_prev);
}
GLenum GpDisplayDriverSurface_GL2::ResolveGLFormat() const
{
switch (m_pixelFormat)
{
case GpPixelFormats::k8BitCustom:
case GpPixelFormats::k8BitStandard:
case GpPixelFormats::kBW1:
return m_driver->SupportsSizedFormats() ? GL_RED : GL_LUMINANCE;
case GpPixelFormats::kRGB24:
case GpPixelFormats::kRGB555:
return GL_RGB;
case GpPixelFormats::kRGB32:
return GL_RGBA;
default:
return GL_RGBA;
}
}
GLenum GpDisplayDriverSurface_GL2::ResolveGLInternalFormat() const
{
if (m_driver->SupportsSizedFormats())
{
switch (m_pixelFormat)
{
case GpPixelFormats::k8BitCustom:
case GpPixelFormats::k8BitStandard:
case GpPixelFormats::kBW1:
return GL_R8;
case GpPixelFormats::kRGB24:
return GL_RGB8;
case GpPixelFormats::kRGB555:
return GL_RGB5;
case GpPixelFormats::kRGB32:
return GL_RGBA8;
default:
return GL_RGBA8;
}
}
else
{
switch (m_pixelFormat)
{
case GpPixelFormats::k8BitCustom:
case GpPixelFormats::k8BitStandard:
case GpPixelFormats::kBW1:
return GL_LUMINANCE;
case GpPixelFormats::kRGB24:
return GL_RGB;
case GpPixelFormats::kRGB555:
return GL_RGBA;
case GpPixelFormats::kRGB32:
return GL_RGBA;
default:
return GL_RGBA;
}
}
}
GLenum GpDisplayDriverSurface_GL2::ResolveGLType() const
{
switch (m_pixelFormat)
{
case GpPixelFormats::k8BitCustom:
case GpPixelFormats::k8BitStandard:
case GpPixelFormats::kBW1:
return GL_UNSIGNED_BYTE;
case GpPixelFormats::kRGB24:
return GL_UNSIGNED_BYTE;
case GpPixelFormats::kRGB555:
return GL_UNSIGNED_SHORT_5_5_5_1;
case GpPixelFormats::kRGB32:
return GL_UNSIGNED_BYTE;
default:
return GL_UNSIGNED_BYTE;
}
}
GpDisplayDriver_SDL_GL2::GpDisplayDriver_SDL_GL2(const GpDisplayDriverProperties &properties)
: m_window(nullptr)
, m_frameTimeAccumulated(std::chrono::high_resolution_clock::duration::zero())
, m_frameTimeSliceSize(std::chrono::high_resolution_clock::duration::zero())
, m_windowWidthPhysical(640)
, m_windowHeightPhysical(480)
, m_windowWidthVirtual(640)
, m_windowHeightVirtual(480)
, m_initialWidthVirtual(640)
, m_initialHeightVirtual(480)
, m_pixelScaleX(1.0f)
, m_pixelScaleY(1.0f)
, m_useUpscaleFilter(false)
, m_upscaleTextureWidth(0)
, m_upscaleTextureHeight(0)
, m_pendingCursor(nullptr)
, m_activeCursor(nullptr)
, m_currentStandardCursor(EGpStandardCursors::kArrow)
, m_pendingStandardCursor(EGpStandardCursors::kArrow)
, m_mouseIsInClientArea(false)
, m_isFullScreen(false)
, m_isFullScreenDesired(false)
, m_isResolutionResetDesired(false)
, m_windowModeRevertX(200)
, m_windowModeRevertY(200)
, m_windowModeRevertWidth(640)
, m_windowModeRevertHeight(480)
, m_lastFullScreenToggleTimeStamp(0)
, m_bgIsDark(false)
, m_useICCProfile(false)
, m_properties(properties)
, m_syncTimeBase(std::chrono::time_point<std::chrono::high_resolution_clock>::duration::zero())
, m_waitCursor(nullptr)
, m_iBeamCursor(nullptr)
, m_arrowCursor(nullptr)
, m_cursorIsHidden(false)
, m_contextLost(true)
, m_lastSurface(nullptr)
, m_firstSurface(nullptr)
, m_textInputEnabled(false)
{
m_bgColor[0] = 0.f;
m_bgColor[1] = 0.f;
m_bgColor[2] = 0.f;
m_bgColor[3] = 1.f;
// Stupid hack to detect mobile...
m_isFullScreenDesired = m_properties.m_systemServices->IsFullscreenOnStartup();
const intmax_t periodNum = std::chrono::high_resolution_clock::period::num;
const intmax_t periodDen = std::chrono::high_resolution_clock::period::den;
m_frameTimeSliceSize = std::chrono::high_resolution_clock::duration(periodDen * static_cast<intmax_t>(properties.m_frameTimeLockNumerator) / static_cast<intmax_t>(properties.m_frameTimeLockDenominator) / periodNum);
m_waitCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT);
m_iBeamCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
m_arrowCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
m_paletteData = m_paletteStorage;
while (reinterpret_cast<uintptr_t>(m_paletteData) % GP_SYSTEM_MEMORY_ALIGNMENT != 0)
m_paletteData++;
memset(m_paletteData, 255, 256 * 4);
}
template<class T>
static bool LookupOpenGLFunction(T &target, const char *name)
{
target = static_cast<T>(nullptr);
void *proc = SDL_GL_GetProcAddress(name);
if (proc)
{
target = reinterpret_cast<T>(proc);
return true;
}
else
return false;
}
#define LOOKUP_FUNC(func) do { if (!LookupOpenGLFunction(this->func, "gl" #func)) return false; } while(false)
bool GpGLFunctions::LookUpFunctions()
{
LOOKUP_FUNC(Enable);
LOOKUP_FUNC(Disable);
LOOKUP_FUNC(Clear);
LOOKUP_FUNC(ClearColor);
LOOKUP_FUNC(Viewport);
LOOKUP_FUNC(GenFramebuffers);
LOOKUP_FUNC(BindFramebuffer);
LOOKUP_FUNC(FramebufferTexture2D);
LOOKUP_FUNC(CheckFramebufferStatus);
LOOKUP_FUNC(DeleteFramebuffers);
LOOKUP_FUNC(CreateProgram);
LOOKUP_FUNC(DeleteProgram);
LOOKUP_FUNC(LinkProgram);
LOOKUP_FUNC(UseProgram);
LOOKUP_FUNC(GetProgramiv);
LOOKUP_FUNC(GetProgramInfoLog);
LOOKUP_FUNC(GenBuffers);
LOOKUP_FUNC(BufferData);
LOOKUP_FUNC(BindBuffer);
LOOKUP_FUNC(DeleteBuffers);
LOOKUP_FUNC(GetUniformLocation);
LOOKUP_FUNC(GetAttribLocation);
LOOKUP_FUNC(Uniform4fv);
LOOKUP_FUNC(Uniform2fv);
LOOKUP_FUNC(Uniform1fv);
LOOKUP_FUNC(VertexAttribPointer);
LOOKUP_FUNC(EnableVertexAttribArray);
LOOKUP_FUNC(DisableVertexAttribArray);
#if GP_GL_IS_OPENGL_4_CONTEXT
LOOKUP_FUNC(GenVertexArrays);
LOOKUP_FUNC(DeleteVertexArrays);
LOOKUP_FUNC(BindVertexArray);
#endif
LOOKUP_FUNC(CreateShader);
LOOKUP_FUNC(CompileShader);
LOOKUP_FUNC(GetShaderiv);
LOOKUP_FUNC(GetShaderInfoLog);
LOOKUP_FUNC(AttachShader);
LOOKUP_FUNC(ShaderSource);
LOOKUP_FUNC(DeleteShader);
LOOKUP_FUNC(DrawElements);
LOOKUP_FUNC(ActiveTexture);
LOOKUP_FUNC(BindTexture);
LOOKUP_FUNC(TexParameteri);
LOOKUP_FUNC(TexImage2D);
LOOKUP_FUNC(TexSubImage2D);
LOOKUP_FUNC(PixelStorei);
LOOKUP_FUNC(Uniform1i);
LOOKUP_FUNC(GenTextures);
LOOKUP_FUNC(DeleteTextures);
LOOKUP_FUNC(GetError);
return true;
}
GpDisplayDriver_SDL_GL2::~GpDisplayDriver_SDL_GL2()
{
SDL_DestroyWindow(m_window);
}
bool GpDisplayDriver_SDL_GL2::Init()
{
#if GP_GL_IS_OPENGL_4_CONTEXT
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
#else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
#endif
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
IGpLogDriver *logger = m_properties.m_logger;
uint32_t windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
if (m_properties.m_systemServices->IsFullscreenOnStartup())
{
windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
m_isFullScreen = true;
}
else
windowFlags |= SDL_WINDOW_RESIZABLE;
m_window = SDL_CreateWindow(GP_APPLICATION_NAME, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, m_windowWidthPhysical, m_windowHeightPhysical, windowFlags);
if (m_isFullScreen)
{
m_windowModeRevertWidth = m_windowWidthPhysical;
m_windowModeRevertHeight = m_windowHeightPhysical;
int windowWidth = 0;
int windowHeight = 0;
SDL_GetWindowSize(m_window, &windowWidth, &windowHeight);
if (logger)
logger->Printf(IGpLogDriver::Category_Information, "Initialized fullscreen SDL window %i x %i", windowWidth, windowHeight);
m_windowWidthPhysical = windowWidth;
m_windowHeightPhysical = windowHeight;
uint32_t desiredWidth = windowWidth;
uint32_t desiredHeight = windowHeight;
uint32_t virtualWidth = m_windowWidthVirtual;
uint32_t virtualHeight = m_windowHeightVirtual;
float pixelScaleX = m_pixelScaleX;
float pixelScaleY = m_pixelScaleY;
if (m_properties.m_adjustRequestedResolutionFunc(m_properties.m_adjustRequestedResolutionFuncContext, desiredWidth, desiredHeight, virtualWidth, virtualHeight, pixelScaleX, pixelScaleY))
{
m_windowWidthVirtual = virtualWidth;
m_windowHeightVirtual = virtualHeight;
m_pixelScaleX = pixelScaleX;
m_pixelScaleY = pixelScaleY;
if (logger)
logger->Printf(IGpLogDriver::Category_Information, "AdjustedRequestedResolution succeeded. Virtual dimensions %i x %i Pixel scale %f x %f", static_cast<int>(virtualWidth), static_cast<int>(virtualHeight), static_cast<float>(pixelScaleX), static_cast<float>(pixelScaleY));
}
else
{
if (logger)
logger->Printf(IGpLogDriver::Category_Error, "AdjustedRequestedResolution failed!");
}
}
const bool obstructiveTextInput = m_properties.m_systemServices->IsTextInputObstructive();
if (!obstructiveTextInput)
SDL_StartTextInput();
StartOpenGLForWindow(logger);
if (!m_gl.LookUpFunctions())
return false;
m_initialWidthVirtual = m_windowWidthVirtual;
m_initialHeightVirtual = m_windowHeightVirtual;
return true;
}
void GpDisplayDriver_SDL_GL2::ServeTicks(int ticks)
{
IGpLogDriver *logger = m_properties.m_logger;
const bool obstructiveTextInput = m_properties.m_systemServices->IsTextInputObstructive();
for (;;)
{
SDL_Event msg;
if (SDL_PollEvent(&msg) != 0)
{
switch (msg.type)
{
case SDL_MOUSEMOTION:
{
if (!m_mouseIsInClientArea)
m_mouseIsInClientArea = true;
}
break;
//case SDL_MOUSELEAVE: // Does SDL support this??
// m_mouseIsInClientArea = false;
// break;
case SDL_RENDER_DEVICE_RESET:
case SDL_RENDER_TARGETS_RESET:
{
if (logger)
logger->Printf(IGpLogDriver::Category_Information, "Triggering GL context reset due to device loss (Type: %i)", static_cast<int>(msg.type));
m_contextLost = true;
}
break;
case SDL_CONTROLLERAXISMOTION:
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
case SDL_CONTROLLERDEVICEREMAPPED:
if (IGpInputDriverSDLGamepad *gamepadDriver = IGpInputDriverSDLGamepad::GetInstance())
gamepadDriver->ProcessSDLEvent(msg);
break;
}
TranslateSDLMessage(&msg, m_properties.m_eventQueue, m_pixelScaleX, m_pixelScaleY, obstructiveTextInput);
}
else
{
if (m_isFullScreen != m_isFullScreenDesired)
{
if (m_isFullScreenDesired)
BecomeFullScreen();
else
BecomeWindowed();
if (logger)
logger->Printf(IGpLogDriver::Category_Information, "Triggering GL context reset due to fullscreen state change");
m_contextLost = true;
continue;
}
int clientWidth = 0;
int clientHeight = 0;
SDL_GetWindowSize(m_window, &clientWidth, &clientHeight);
unsigned int desiredWidth = clientWidth;
unsigned int desiredHeight = clientHeight;
if (desiredWidth != m_windowWidthPhysical || desiredHeight != m_windowHeightPhysical || m_isResolutionResetDesired)
{
if (logger)
logger->Printf(IGpLogDriver::Category_Information, "Detected window size change");
uint32_t prevWidthPhysical = m_windowWidthPhysical;
uint32_t prevHeightPhysical = m_windowHeightPhysical;
uint32_t prevWidthVirtual = m_windowWidthVirtual;
uint32_t prevHeightVirtual = m_windowHeightVirtual;
uint32_t virtualWidth = m_windowWidthVirtual;
uint32_t virtualHeight = m_windowHeightVirtual;
float pixelScaleX = 1.0f;
float pixelScaleY = 1.0f;
if (m_properties.m_adjustRequestedResolutionFunc(m_properties.m_adjustRequestedResolutionFuncContext, desiredWidth, desiredHeight, virtualWidth, virtualHeight, pixelScaleX, pixelScaleY))
{
bool resizedOK = ResizeOpenGLWindow(m_windowWidthPhysical, m_windowHeightPhysical, desiredWidth, desiredHeight, logger);
if (!resizedOK)
break; // Critical video driver error, exit
m_windowWidthVirtual = virtualWidth;
m_windowHeightVirtual = virtualHeight;
m_pixelScaleX = pixelScaleX;
m_pixelScaleY = pixelScaleY;
m_isResolutionResetDesired = false;
if (GpVOSEvent *resizeEvent = m_properties.m_eventQueue->QueueEvent())
{
resizeEvent->m_eventType = GpVOSEventTypes::kVideoResolutionChanged;
resizeEvent->m_event.m_resolutionChangedEvent.m_prevWidth = prevWidthVirtual;
resizeEvent->m_event.m_resolutionChangedEvent.m_prevHeight = prevHeightVirtual;
resizeEvent->m_event.m_resolutionChangedEvent.m_newWidth = m_windowWidthVirtual;
resizeEvent->m_event.m_resolutionChangedEvent.m_newHeight = m_windowHeightVirtual;
}
if (logger)
logger->Printf(IGpLogDriver::Category_Information, "Triggering GL context reset due to window size change");
m_contextLost = true;
continue;
}
}
if (m_contextLost)
{
if (logger)
logger->Printf(IGpLogDriver::Category_Information, "Resetting OpenGL context. Physical: %i x %i Virtual %i x %i", static_cast<int>(m_windowWidthPhysical), static_cast<int>(m_windowHeightPhysical), static_cast<int>(m_windowWidthVirtual), static_cast<int>(m_windowHeightVirtual));
// Drop everything and reset
m_res = InstancedResources();
if (m_firstSurface)
m_firstSurface->DestroyAll();
if (!InitResources(m_windowWidthPhysical, m_windowHeightPhysical, m_windowWidthVirtual, m_windowHeightVirtual))
{
if (logger)
logger->Printf(IGpLogDriver::Category_Information, "Terminating display driver due to InitResources failing");
break;
}
if (m_firstSurface)
m_firstSurface->RecreateAll();
m_contextLost = false;
continue;
}
bool wantTextInput = m_properties.m_systemServices->IsTextInputEnabled();
if (wantTextInput != m_textInputEnabled)
{
m_textInputEnabled = wantTextInput;
if (m_textInputEnabled)
SDL_StartTextInput();
else
SDL_StopTextInput();
}
// Handle dismissal of on-screen keyboard
const bool isTextInputActuallyActive = SDL_IsTextInputActive();
m_textInputEnabled = isTextInputActuallyActive;
m_properties.m_systemServices->SetTextInputEnabled(isTextInputActuallyActive);
if (SyncRender())
{
ticks--;
if (ticks <= 0)
break;
}
}
}
}
void GpDisplayDriver_SDL_GL2::ForceSync()
{
m_frameTimeAccumulated = std::chrono::nanoseconds::zero();
}
static void PostMouseEvent(IGpVOSEventQueue *eventQueue, GpMouseEventType_t eventType, GpMouseButton_t button, int32_t x, int32_t y, float pixelScaleX, float pixelScaleY)
{
if (GpVOSEvent *evt = eventQueue->QueueEvent())
{
evt->m_eventType = GpVOSEventTypes::kMouseInput;
GpMouseInputEvent &mEvent = evt->m_event.m_mouseInputEvent;
mEvent.m_button = button;
mEvent.m_x = x;
mEvent.m_y = y;
mEvent.m_eventType = eventType;
if (pixelScaleX != 1.0f)
mEvent.m_x = static_cast<int32_t>(static_cast<float>(x) / pixelScaleX);
if (pixelScaleY != 1.0f)
mEvent.m_y = static_cast<int32_t>(static_cast<float>(y) / pixelScaleX);
}
}
static void PostTouchEvent(IGpVOSEventQueue *eventQueue, GpTouchEventType_t eventType, int32_t x, int32_t y, int64_t deviceID, int64_t fingerID)
{
if (GpVOSEvent *evt = eventQueue->QueueEvent())
{
evt->m_eventType = GpVOSEventTypes::kTouchInput;
GpTouchInputEvent &tEvent = evt->m_event.m_touchInputEvent;
tEvent.m_deviceID = deviceID;
tEvent.m_fingerID = fingerID;
tEvent.m_x = x;
tEvent.m_y = y;
tEvent.m_eventType = eventType;
}
}
static bool IdentifyVKey(const SDL_KeyboardEvent *keyEvt, GpKeyIDSubset_t &outSubset, GpKeyboardInputEvent::KeyUnion &outKey)
{
SDL_Keycode keyCode = static_cast<SDL_Keycode>(keyEvt->keysym.sym);
switch (keyCode)
{
case SDLK_ESCAPE:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kEscape;
break;
case SDLK_PRINTSCREEN:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kPrintScreen;
break;
case SDLK_SCROLLLOCK:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kScrollLock;
break;
case SDLK_PAUSE:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kPause;
break;
case SDLK_INSERT:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kInsert;
break;
case SDLK_HOME:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kHome;
break;
case SDLK_PAGEUP:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kPageUp;
break;
case SDLK_PAGEDOWN:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kPageDown;
break;
case SDLK_DELETE:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kDelete;
break;
case SDLK_TAB:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kTab;
break;
case SDLK_END:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kEnd;
break;
case SDLK_BACKSPACE:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kBackspace;
break;
case SDLK_CAPSLOCK:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kCapsLock;
break;
case SDLK_RETURN:
case SDLK_KP_ENTER:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kEnter;
break;
case SDLK_LSHIFT:
case SDLK_RSHIFT:
{
if (keyCode == SDLK_LSHIFT)
{
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kLeftShift;
}
else if (keyCode == SDLK_RSHIFT)
{
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kRightShift;
}
else
return false;
}
break;
#ifdef __MACOS__
case SDLK_LGUI:
#else
case SDLK_LCTRL:
#endif
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kLeftCtrl;
break;
#ifdef __MACOS__
case SDLK_RGUI:
#else
case SDLK_RCTRL:
#endif
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kRightCtrl;
break;
case SDLK_LALT:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kLeftAlt;
break;
case SDLK_RALT:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kRightAlt;
break;
case SDLK_NUMLOCKCLEAR:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kNumLock;
break;
case SDLK_KP_0:
outSubset = GpKeyIDSubsets::kNumPadNumber;
outKey.m_numPadNumber = 0;
break;
case SDLK_KP_1:
outSubset = GpKeyIDSubsets::kNumPadNumber;
outKey.m_numPadNumber = 1;
break;
case SDLK_KP_2:
outSubset = GpKeyIDSubsets::kNumPadNumber;
outKey.m_numPadNumber = 2;
break;
case SDLK_KP_3:
outSubset = GpKeyIDSubsets::kNumPadNumber;
outKey.m_numPadNumber = 3;
break;
case SDLK_KP_4:
outSubset = GpKeyIDSubsets::kNumPadNumber;
outKey.m_numPadNumber = 4;
break;
case SDLK_KP_5:
outSubset = GpKeyIDSubsets::kNumPadNumber;
outKey.m_numPadNumber = 5;
break;
case SDLK_KP_6:
outSubset = GpKeyIDSubsets::kNumPadNumber;
outKey.m_numPadNumber = 6;
break;
case SDLK_KP_7:
outSubset = GpKeyIDSubsets::kNumPadNumber;
outKey.m_numPadNumber = 7;
break;
case SDLK_KP_8:
outSubset = GpKeyIDSubsets::kNumPadNumber;
outKey.m_numPadNumber = 8;
break;
case SDLK_KP_9:
outSubset = GpKeyIDSubsets::kNumPadNumber;
outKey.m_numPadNumber = 9;
break;
case SDLK_F1:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 1;
break;
case SDLK_F2:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 2;
break;
case SDLK_F3:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 3;
break;
case SDLK_F4:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 4;
break;
case SDLK_F5:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 5;
break;
case SDLK_F6:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 6;
break;
case SDLK_F7:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 7;
break;
case SDLK_F8:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 8;
break;
case SDLK_F9:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 9;
break;
case SDLK_F10:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 10;
break;
case SDLK_F11:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 11;
break;
case SDLK_F12:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 12;
break;
case SDLK_F13:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 13;
break;
case SDLK_F14:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 14;
break;
case SDLK_F15:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 15;
break;
case SDLK_F16:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 16;
break;
case SDLK_F17:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 17;
break;
case SDLK_F18:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 18;
break;
case SDLK_F19:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 19;
break;
case SDLK_F20:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 20;
break;
case SDLK_F21:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 21;
break;
case SDLK_F22:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 22;
break;
case SDLK_F23:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 23;
break;
case SDLK_F24:
outSubset = GpKeyIDSubsets::kFKey;
outKey.m_fKey = 24;
break;
case SDLK_COMMA:
outSubset = GpKeyIDSubsets::kASCII;
outKey.m_asciiChar = ',';
break;
case SDLK_MINUS:
outSubset = GpKeyIDSubsets::kASCII;
outKey.m_asciiChar = '-';
break;
case SDLK_UP:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kUpArrow;
break;
case SDLK_DOWN:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kDownArrow;
break;
case SDLK_LEFT:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kLeftArrow;
break;
case SDLK_RIGHT:
outSubset = GpKeyIDSubsets::kSpecial;
outKey.m_specialKey = GpKeySpecials::kRightArrow;
break;
case SDLK_KP_COMMA:
outSubset = GpKeyIDSubsets::kNumPadSpecial;
outKey.m_numPadSpecialKey = GpNumPadSpecials::kComma;
break;
case SDLK_KP_MULTIPLY:
outSubset = GpKeyIDSubsets::kNumPadSpecial;
outKey.m_numPadSpecialKey = GpNumPadSpecials::kAsterisk;
break;
case SDLK_KP_PERIOD:
outSubset = GpKeyIDSubsets::kNumPadSpecial;
outKey.m_numPadSpecialKey = GpNumPadSpecials::kPeriod;
break;
case SDLK_KP_DIVIDE:
outSubset = GpKeyIDSubsets::kNumPadSpecial;
outKey.m_numPadSpecialKey = GpNumPadSpecials::kSlash;
break;
default:
{
if (keyCode < 128)
{
outSubset = GpKeyIDSubsets::kASCII;
if (keyCode >= 'a' && keyCode <= 'z')
outKey.m_asciiChar = static_cast<char>(keyCode + 'A' - 'a');
else
outKey.m_asciiChar = static_cast<char>(keyCode);
break;
}
}
return false;
}
return true;
}
static void PostKeyboardEvent(IGpVOSEventQueue *eventQueue, GpKeyboardInputEventType_t eventType, GpKeyIDSubset_t subset, const GpKeyboardInputEvent::KeyUnion &key, uint32_t repeatCount)
{
if (GpVOSEvent *evt = eventQueue->QueueEvent())
{
evt->m_eventType = GpVOSEventTypes::kKeyboardInput;
GpKeyboardInputEvent &mEvent = evt->m_event.m_keyboardInputEvent;
mEvent.m_key = key;
mEvent.m_eventType = eventType;
mEvent.m_keyIDSubset = subset;
mEvent.m_repeatCount = repeatCount;
}
}
void GpDisplayDriver_SDL_GL2::TranslateSDLMessage(const SDL_Event *msg, IGpVOSEventQueue *eventQueue, float pixelScaleX, float pixelScaleY, bool obstructiveTextInput)
{
switch (msg->type)
{
case SDL_MOUSEMOTION:
{
const SDL_MouseMotionEvent *mouseEvt = reinterpret_cast<const SDL_MouseMotionEvent *>(msg);
PostMouseEvent(eventQueue, GpMouseEventTypes::kMove, GpMouseButtons::kNone, mouseEvt->x, mouseEvt->y, pixelScaleX, pixelScaleY);
}
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
{
const SDL_MouseButtonEvent *mouseEvt = reinterpret_cast<const SDL_MouseButtonEvent *>(msg);
GpMouseEventType_t evtType = GpMouseEventTypes::kDown;
GpMouseButton_t mouseButton = GpMouseButtons::kLeft;
if (mouseEvt->type == SDL_MOUSEBUTTONDOWN)
evtType = GpMouseEventTypes::kDown;
else if (mouseEvt->type == SDL_MOUSEBUTTONUP)
evtType = GpMouseEventTypes::kUp;
else
break;
if (mouseEvt->button == SDL_BUTTON_LEFT)
mouseButton = GpMouseButtons::kLeft;
else if (mouseEvt->button == SDL_BUTTON_RIGHT)
mouseButton = GpMouseButtons::kRight;
else if (mouseEvt->button == SDL_BUTTON_MIDDLE)
mouseButton = GpMouseButtons::kMiddle;
else if (mouseEvt->button == SDL_BUTTON_X1)
mouseButton = GpMouseButtons::kX1;
else if (mouseEvt->button == SDL_BUTTON_X2)
mouseButton = GpMouseButtons::kX2;
else
break;
PostMouseEvent(eventQueue, evtType, mouseButton, mouseEvt->x, mouseEvt->y, pixelScaleX, pixelScaleY);
}
break;
case SDL_FINGERUP:
case SDL_FINGERDOWN:
case SDL_FINGERMOTION:
{
const SDL_TouchFingerEvent *fingerEvt = reinterpret_cast<const SDL_TouchFingerEvent *>(msg);
GpTouchEventType_t evtType = GpTouchEventTypes::kDown;
if (fingerEvt->type == SDL_FINGERUP)
evtType = GpTouchEventTypes::kUp;
else if (fingerEvt->type == SDL_FINGERDOWN)
evtType = GpTouchEventTypes::kDown;
else if (fingerEvt->type == SDL_FINGERMOTION)
evtType = GpTouchEventTypes::kMove;
else
break;
float unnormalizedX = static_cast<float>(m_windowWidthVirtual) * fingerEvt->x;
float unnormalizedY = static_cast<float>(m_windowHeightVirtual) * fingerEvt->y;
PostTouchEvent(eventQueue, evtType, static_cast<int32_t>(unnormalizedX), static_cast<int32_t>(unnormalizedY), fingerEvt->touchId, fingerEvt->fingerId);
}
break;
case SDL_KEYDOWN:
{
const SDL_KeyboardEvent *keyEvt = reinterpret_cast<const SDL_KeyboardEvent *>(msg);
GpKeyIDSubset_t subset;
GpKeyboardInputEvent::KeyUnion key;
bool isRepeat = (keyEvt->repeat != 0);
const GpKeyboardInputEventType_t keyEventType = isRepeat ? GpKeyboardInputEventTypes::kAuto : GpKeyboardInputEventTypes::kDown;
if (IdentifyVKey(keyEvt, subset, key))
{
PostKeyboardEvent(eventQueue, keyEventType, subset, key, keyEvt->repeat + 1);
if (subset == GpKeyIDSubsets::kSpecial && key.m_specialKey == GpKeySpecials::kEnter)
{
const GpKeyboardInputEventType_t charEventType = isRepeat ? GpKeyboardInputEventTypes::kAutoChar : GpKeyboardInputEventTypes::kDownChar;
GpKeyboardInputEvent::KeyUnion crKey;
crKey.m_asciiChar = '\n';
PostKeyboardEvent(eventQueue, charEventType, GpKeyIDSubsets::kASCII, crKey, keyEvt->repeat + 1);
}
}
}
break;
case SDL_KEYUP:
{
const SDL_KeyboardEvent *keyEvt = reinterpret_cast<const SDL_KeyboardEvent *>(msg);
GpKeyIDSubset_t subset;
GpKeyboardInputEvent::KeyUnion key;
if (IdentifyVKey(keyEvt, subset, key))
PostKeyboardEvent(eventQueue, GpKeyboardInputEventTypes::kUp, subset, key, keyEvt->repeat + 1);
}
break;
case SDL_TEXTINPUT:
{
// SDL doesn't report if the text input event is a repeat, which sucks...
const SDL_TextInputEvent *teEvt = reinterpret_cast<const SDL_TextInputEvent *>(msg);
size_t lenUTF8 = strlen(teEvt->text);
size_t parseOffset = 0;
while (parseOffset < lenUTF8)
{
uint32_t codePoint;
size_t numDigested;
GpUnicode::UTF8::Decode(reinterpret_cast<const uint8_t*>(teEvt->text) + parseOffset, lenUTF8 - parseOffset, numDigested, codePoint);
parseOffset += numDigested;
const GpKeyboardInputEventType_t keyEventType = GpKeyboardInputEventTypes::kDownChar;
GpKeyboardInputEvent::KeyUnion key;
GpKeyIDSubset_t subset = GpKeyIDSubsets::kASCII;
if (codePoint <= 128)
key.m_asciiChar = static_cast<char>(codePoint);
else
{
subset = GpKeyIDSubsets::kUnicode;
key.m_unicodeChar = static_cast<uint32_t>(codePoint);
}
PostKeyboardEvent(eventQueue, keyEventType, subset, key, 1);
}
if (!obstructiveTextInput)
{
SDL_StopTextInput();
SDL_StartTextInput();
}
}
break;
case SDL_QUIT:
{
if (GpVOSEvent *evt = eventQueue->QueueEvent())
evt->m_eventType = GpVOSEventTypes::kQuit;
}
break;
default:
break;
}
}
void GpDisplayDriver_SDL_GL2::Shutdown()
{
this->~GpDisplayDriver_SDL_GL2();
free(this);
}
void GpDisplayDriver_SDL_GL2::GetInitialDisplayResolution(unsigned int *width, unsigned int *height)
{
if (width)
*width = m_initialWidthVirtual;
if (height)
*height = m_initialHeightVirtual;
}
IGpDisplayDriverSurface *GpDisplayDriver_SDL_GL2::CreateSurface(size_t width, size_t height, size_t pitch, GpPixelFormat_t pixelFormat, SurfaceInvalidateCallback_t invalidateCallback, void *invalidateContext)
{
GpDisplayDriverSurface_GL2 *surface = GpDisplayDriverSurface_GL2::Create(this, width, height, pitch, pixelFormat, m_lastSurface, invalidateCallback, invalidateContext);
if (surface)
{
m_lastSurface = surface;
if (m_firstSurface == nullptr)
m_firstSurface = surface;
}
return surface;
}
void GpDisplayDriver_SDL_GL2::DrawSurface(IGpDisplayDriverSurface *surface, int32_t x, int32_t y, size_t width, size_t height, const GpDisplayDriverSurfaceEffects *effects)
{
if (!effects)
effects = &gs_defaultEffects;
GpGLVertexArray *vaPtr = m_res.m_quadVertexArray;
size_t vbStride = sizeof(float) * 2;
size_t zero = 0;
GpDisplayDriverSurface_GL2 *glSurface = static_cast<GpDisplayDriverSurface_GL2*>(surface);
GpPixelFormat_t pixelFormat = glSurface->GetPixelFormat();
DrawQuadProgram *program = nullptr;
if (pixelFormat == GpPixelFormats::k8BitStandard || pixelFormat == GpPixelFormats::k8BitCustom)
{
if (m_useICCProfile)
{
if (effects->m_flicker)
program = &m_res.m_drawQuadPaletteICCFlickerProgram;
else
program = &m_res.m_drawQuadPaletteICCNoFlickerProgram;
}
else
{
if (effects->m_flicker)
program = &m_res.m_drawQuadPaletteFlickerProgram;
else
program = &m_res.m_drawQuadPaletteNoFlickerProgram;
}
}
else if (pixelFormat == GpPixelFormats::kRGB555)
{
return;
}
else if (pixelFormat == GpPixelFormats::kRGB32)
{
if (m_useICCProfile)
{
if (effects->m_flicker)
program = &m_res.m_drawQuad32ICCFlickerProgram;
else
program = &m_res.m_drawQuad32ICCNoFlickerProgram;
}
else
{
if (effects->m_flicker)
program = &m_res.m_drawQuad32FlickerProgram;
else
program = &m_res.m_drawQuad32NoFlickerProgram;
}
}
else
{
return;
}
CheckGLError(m_gl, m_properties.m_logger);
m_gl.UseProgram(program->m_program->GetID());
CheckGLError(m_gl, m_properties.m_logger);
{
const float twoDivWidth = 2.0f / static_cast<float>(m_windowWidthVirtual);
const float negativeTwoDivHeight = -2.0f / static_cast<float>(m_windowHeightVirtual);
GLfloat ndcOriginAndDimensions[4] =
{
static_cast<GLfloat>(x) * twoDivWidth - 1.0f,
static_cast<GLfloat>(y) * negativeTwoDivHeight + 1.0f,
static_cast<GLfloat>(width) * twoDivWidth,
static_cast<GLfloat>(height) * negativeTwoDivHeight,
};
GLfloat surfaceDimensions_TextureRegion[4] =
{
static_cast<GLfloat>(glSurface->GetImageWidth()),
static_cast<GLfloat>(glSurface->GetHeight()),
static_cast<GLfloat>(static_cast<float>(glSurface->GetImageWidth()) / static_cast<float>(glSurface->GetPaddedTextureWidth())),
1.f
};
m_gl.Uniform4fv(program->m_vertexNDCOriginAndDimensionsLocation, 1, reinterpret_cast<const GLfloat*>(ndcOriginAndDimensions));
m_gl.Uniform4fv(program->m_vertexSurfaceDimensionsLocation, 1, reinterpret_cast<const GLfloat*>(surfaceDimensions_TextureRegion));
GLfloat modulation[4] = { 1.f, 1.f, 1.f, 1.f };
GLfloat flickerAxis[2] = { 0.f, 0.f };
GLfloat flickerStart = -1.f;
GLfloat flickerEnd = -2.f;
if (effects->m_flicker)
{
flickerAxis[0] = effects->m_flickerAxisX;
flickerAxis[1] = effects->m_flickerAxisY;
flickerStart = effects->m_flickerStartThreshold;
flickerEnd = effects->m_flickerEndThreshold;
}
float desaturation = effects->m_desaturation;
if (effects->m_darken)
for (int i = 0; i < 3; i++)
modulation[i] = 0.5f;
m_gl.Uniform4fv(program->m_pixelModulationLocation, 1, modulation);
m_gl.Uniform2fv(program->m_pixelFlickerAxisLocation, 1, flickerAxis);
m_gl.Uniform1fv(program->m_pixelFlickerStartThresholdLocation, 1, &flickerStart);
m_gl.Uniform1fv(program->m_pixelFlickerEndThresholdLocation, 1, &flickerEnd);
m_gl.Uniform1fv(program->m_pixelDesaturationLocation, 1, &desaturation);
}
GLint vpos[1] = { program->m_vertexPosUVLocation };
m_res.m_quadVertexArray->Activate(vpos);
m_gl.ActiveTexture(GL_TEXTURE0);
m_gl.BindTexture(GL_TEXTURE_2D, glSurface->GetTexture()->GetID());
m_gl.Uniform1i(program->m_pixelSurfaceTextureLocation, 0);
if (pixelFormat == GpPixelFormats::k8BitStandard || pixelFormat == GpPixelFormats::k8BitCustom)
{
m_gl.ActiveTexture(GL_TEXTURE1);
m_gl.BindTexture(GL_TEXTURE_2D, m_res.m_paletteTexture->GetID());
m_gl.Uniform1i(program->m_pixelPaletteTextureLocation, 1);
}
m_gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_res.m_quadIndexBuffer->GetID());
m_gl.DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
m_gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
CheckGLError(m_gl, m_properties.m_logger);
if (pixelFormat == GpPixelFormats::k8BitStandard || pixelFormat == GpPixelFormats::k8BitCustom)
{
m_gl.ActiveTexture(GL_TEXTURE1);
m_gl.BindTexture(GL_TEXTURE_2D, 0);
}
m_gl.ActiveTexture(GL_TEXTURE0);
m_gl.BindTexture(GL_TEXTURE_2D, 0);
m_res.m_quadVertexArray->Deactivate(vpos);
m_gl.UseProgram(0);
CheckGLError(m_gl, m_properties.m_logger);
}
IGpCursor *GpDisplayDriver_SDL_GL2::CreateBWCursor(size_t width, size_t height, const void *pixelData, const void *maskData, size_t hotSpotX, size_t hotSpotY)
{
SDL_Cursor *cursor = SDL_CreateCursor(static_cast<const Uint8*>(pixelData), static_cast<const Uint8*>(maskData), width, height, hotSpotX, hotSpotY);
return new GpCursor_SDL2(cursor);
}
IGpCursor *GpDisplayDriver_SDL_GL2::CreateColorCursor(size_t width, size_t height, const void *pixelDataRGBA, size_t hotSpotX, size_t hotSpotY)
{
uint32_t channelMasks[4];
for (int i = 0; i < 4; i++)
{
channelMasks[i] = 0;
reinterpret_cast<uint8_t*>(&channelMasks[i])[i] = 0xff;
}
SDL_Surface *surface = SDL_CreateRGBSurface(0, width, height, 32, channelMasks[0], channelMasks[1], channelMasks[2], channelMasks[3]);
if (!surface)
return nullptr;
size_t surfacePitch = surface->pitch;
uint8_t *destPixels = reinterpret_cast<uint8_t*>(surface->pixels);
for (size_t y = 0; y < height; y++)
memcpy(destPixels + y * surfacePitch, static_cast<const uint8_t*>(pixelDataRGBA) + y * width * 4, width * 4);
SDL_Cursor *cursor = SDL_CreateColorCursor(surface, hotSpotX, hotSpotY);
SDL_FreeSurface(surface);
if (!cursor)
return nullptr;
return new GpCursor_SDL2(cursor);
}
void GpDisplayDriver_SDL_GL2::SetCursor(IGpCursor *cursor)
{
GpCursor_SDL2 *sdlCursor = static_cast<GpCursor_SDL2*>(cursor);
sdlCursor->IncRef();
if (m_pendingCursor)
m_pendingCursor->DecRef();
m_pendingCursor = sdlCursor;
}
void GpDisplayDriver_SDL_GL2::SetStandardCursor(EGpStandardCursor_t standardCursor)
{
if (m_pendingCursor)
{
m_pendingCursor->DecRef();
m_pendingCursor = nullptr;
}
m_pendingStandardCursor = standardCursor;
}
void GpDisplayDriver_SDL_GL2::UpdatePalette(const void *paletteData)
{
memcpy(m_paletteData, paletteData, 256 * 4);
GLenum internalFormat = SupportsSizedFormats() ? GL_RGBA8 : GL_RGBA;
m_gl.BindTexture(GL_TEXTURE_2D, m_res.m_paletteTexture->GetID());
m_gl.PixelStorei(GL_UNPACK_ALIGNMENT, 1);
m_gl.TexImage2D(GL_TEXTURE_2D, 0, internalFormat, 256, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_paletteData);
m_gl.BindTexture(GL_TEXTURE_2D, 0);
}
void GpDisplayDriver_SDL_GL2::SetBackgroundColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
uint8_t rgba[4] = { r, g, b, a };
for (int i = 0; i < 4; i++)
m_bgColor[i] = static_cast<float>(rgba[i]) / 255.0f;
}
void GpDisplayDriver_SDL_GL2::SetBackgroundDarkenEffect(bool isDark)
{
m_bgIsDark = isDark;
}
void GpDisplayDriver_SDL_GL2::SetUseICCProfile(bool useICCProfile)
{
m_useICCProfile = useICCProfile;
}
void GpDisplayDriver_SDL_GL2::RequestToggleFullScreen(uint32_t timestamp)
{
// Alt-Enter gets re-sent after a full-screen toggle, so we ignore toggle requests until half a second has elapsed
if (timestamp == 0 || timestamp > m_lastFullScreenToggleTimeStamp + 30)
{
m_isFullScreenDesired = !m_isFullScreenDesired;
m_lastFullScreenToggleTimeStamp = timestamp;
}
}
void GpDisplayDriver_SDL_GL2::RequestResetVirtualResolution()
{
m_isResolutionResetDesired = true;
}
bool GpDisplayDriver_SDL_GL2::IsFullScreen() const
{
return m_isFullScreenDesired;
}
const GpDisplayDriverProperties &GpDisplayDriver_SDL_GL2::GetProperties() const
{
return m_properties;
}
IGpPrefsHandler *GpDisplayDriver_SDL_GL2::GetPrefsHandler() const
{
return const_cast<GpDisplayDriver_SDL_GL2*>(this);
}
bool GpDisplayDriver_SDL_GL2::SupportsSizedFormats() const
{
#if GP_GL_IS_OPENGL_4_CONTEXT
return true;
#else
return false;
#endif
}
void GpDisplayDriver_SDL_GL2::ApplyPrefs(const void *identifier, size_t identifierSize, const void *contents, size_t contentsSize, uint32_t version)
{
if (version == kPrefsVersion && identifierSize == strlen(kPrefsIdentifier) && !memcmp(identifier, kPrefsIdentifier, identifierSize))
{
const GpDisplayDriver_SDL_GL2_Prefs *prefs = static_cast<const GpDisplayDriver_SDL_GL2_Prefs *>(contents);
m_isFullScreenDesired = prefs->m_isFullScreen;
}
}
bool GpDisplayDriver_SDL_GL2::SavePrefs(void *context, IGpPrefsHandler::WritePrefsFunc_t writeFunc)
{
GpDisplayDriver_SDL_GL2_Prefs prefs;
prefs.m_isFullScreen = m_isFullScreenDesired;
return writeFunc(context, kPrefsIdentifier, strlen(kPrefsIdentifier), &prefs, sizeof(prefs), kPrefsVersion);
}
void GpDisplayDriver_SDL_GL2::UnlinkSurface(GpDisplayDriverSurface_GL2 *surface, GpDisplayDriverSurface_GL2 *prev, GpDisplayDriverSurface_GL2 *next)
{
if (m_lastSurface == surface)
m_lastSurface = prev;
if (m_firstSurface == surface)
m_firstSurface = next;
}
const GpGLFunctions *GpDisplayDriver_SDL_GL2::GetGLFunctions() const
{
return &m_gl;
}
template<GLuint TShaderType>
GpComPtr<GpGLShader<TShaderType>> GpDisplayDriver_SDL_GL2::CreateShader(const char *shaderSrc)
{
size_t shaderCodeLength = strlen(shaderSrc);
GpComPtr<GpGLShader<TShaderType>> shader(GpGLShader<TShaderType>::Create(this));
if (shader == nullptr)
return shader;
const GLchar *shaderSrcGL = reinterpret_cast<const GLchar*>(shaderSrc);
GLint shaderLenGL = static_cast<GLint>(shaderCodeLength);
m_gl.ShaderSource(shader->GetID(), 1, &shaderSrcGL, &shaderLenGL);
m_gl.CompileShader(shader->GetID());
GLint compiled = 0;
m_gl.GetShaderiv(shader->GetID(), GL_COMPILE_STATUS, &compiled);
if (!compiled)
{
GLint infoLength = 0;
m_gl.GetShaderiv(shader->GetID(), GL_INFO_LOG_LENGTH, &infoLength);
std::vector<char> log;
log.resize(infoLength + 1);
log[infoLength] = '\0';
m_gl.GetShaderInfoLog(shader->GetID(), static_cast<GLsizei>(log.size() - 1), nullptr, reinterpret_cast<GLchar*>(&log[0]));
const char *errorMsg = &log[0];
return GpComPtr<GpGLShader<TShaderType>>();
}
return shader;
}
void GpDisplayDriver_SDL_GL2::StartOpenGLForWindow(IGpLogDriver *logger)
{
SDL_GLContext context = SDL_GL_CreateContext(m_window);
SDL_GL_SetSwapInterval(1);
}
bool GpDisplayDriver_SDL_GL2::InitResources(uint32_t physicalWidth, uint32_t physicalHeight, uint32_t virtualWidth, uint32_t virtualHeight)
{
IGpLogDriver *logger = m_properties.m_logger;
if (logger)
logger->Printf(IGpLogDriver::Category_Information, "GpDisplayDriver_SDL_GL2::InitResources");
CheckGLError(m_gl, logger);
if (!InitBackBuffer(virtualWidth, virtualHeight))
return false;
// Quad index buffer
{
const uint16_t indexBufferData[] = { 0, 1, 2, 1, 3, 2 };
m_res.m_quadIndexBuffer = GpGLBuffer::Create(this);
if (!m_res.m_quadIndexBuffer)
{
if (logger)
logger->Printf(IGpLogDriver::Category_Error, "GpDisplayDriver_SDL_GL2::InitResources: CreateBuffer for draw quad index buffer failed");
return false;
}
m_gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_res.m_quadIndexBuffer->GetID());
m_gl.BufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexBufferData), indexBufferData, GL_STATIC_DRAW);
m_gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
// Quad vertex buffer
{
const float vertexBufferData[] =
{
0.f, 0.f,
1.f, 0.f,
0.f, 1.f,
1.f, 1.f,
};
m_res.m_quadVertexBufferKeepalive = GpGLBuffer::Create(this);
if (!m_res.m_quadVertexBufferKeepalive)
{
if (logger)
logger->Printf(IGpLogDriver::Category_Error, "GpDisplayDriver_SDL_GL2::InitResources: GpGLBuffer::Create for draw quad vertex buffer failed");
return false;
}
m_gl.BindBuffer(GL_ARRAY_BUFFER, m_res.m_quadVertexBufferKeepalive->GetID());
m_gl.BufferData(GL_ARRAY_BUFFER, sizeof(vertexBufferData), vertexBufferData, GL_STATIC_DRAW);
m_gl.BindBuffer(GL_ARRAY_BUFFER, 0);
m_res.m_quadVertexArray = GpGLVertexArray::Create(this);
if (!m_res.m_quadVertexBufferKeepalive)
{
if (logger)
logger->Printf(IGpLogDriver::Category_Error, "GpDisplayDriver_SDL_GL2::InitResources: GpGLVertexArray::Create for draw quad vertex buffer failed");
return false;
}
GpGLVertexArraySpec specs[] =
{
m_res.m_quadVertexBufferKeepalive,
0, // index
2, // size
GL_FLOAT, // type
GL_FALSE, // normalized
sizeof(float) * 2, // stride
0
};
if (!m_res.m_quadVertexArray->InitWithSpecs(specs, sizeof(specs) / sizeof(specs[0])))
{
if (logger)
logger->Printf(IGpLogDriver::Category_Error, "GpDisplayDriver_SDL_GL2::InitResources: InitWithSpecs for draw quad vertex buffer failed");
return false;
}
}
GpComPtr<GpGLShader<GL_VERTEX_SHADER>> drawQuadVertexShader = CreateShader<GL_VERTEX_SHADER>(GpBinarizedShaders::g_drawQuadV_GL2);
GpComPtr<GpGLShader<GL_FRAGMENT_SHADER>> drawQuadPaletteFlickerPixelShader = CreateShader<GL_FRAGMENT_SHADER>(GpBinarizedShaders::g_drawQuadPalettePF_GL2);
GpComPtr<GpGLShader<GL_FRAGMENT_SHADER>> drawQuadPaletteNoFlickerPixelShader = CreateShader<GL_FRAGMENT_SHADER>(GpBinarizedShaders::g_drawQuadPalettePNF_GL2);
GpComPtr<GpGLShader<GL_FRAGMENT_SHADER>> drawQuad32FlickerPixelShader = CreateShader<GL_FRAGMENT_SHADER>(GpBinarizedShaders::g_drawQuad32PF_GL2);
GpComPtr<GpGLShader<GL_FRAGMENT_SHADER>> drawQuad32NoFlickerPixelShader = CreateShader<GL_FRAGMENT_SHADER>(GpBinarizedShaders::g_drawQuad32PNF_GL2);
GpComPtr<GpGLShader<GL_FRAGMENT_SHADER>> drawQuadPaletteICCFPixelShader = CreateShader<GL_FRAGMENT_SHADER>(GpBinarizedShaders::g_drawQuadPaletteICCPF_GL2);
GpComPtr<GpGLShader<GL_FRAGMENT_SHADER>> drawQuadPaletteICCNFPixelShader = CreateShader<GL_FRAGMENT_SHADER>(GpBinarizedShaders::g_drawQuadPaletteICCPNF_GL2);
GpComPtr<GpGLShader<GL_FRAGMENT_SHADER>> drawQuad32ICCFPixelShader = CreateShader<GL_FRAGMENT_SHADER>(GpBinarizedShaders::g_drawQuad32ICCPF_GL2);
GpComPtr<GpGLShader<GL_FRAGMENT_SHADER>> drawQuad32ICCNFPixelShader = CreateShader<GL_FRAGMENT_SHADER>(GpBinarizedShaders::g_drawQuad32ICCPNF_GL2);
GpComPtr<GpGLShader<GL_FRAGMENT_SHADER>> scaleQuadPixelShader = CreateShader<GL_FRAGMENT_SHADER>(GpBinarizedShaders::g_scaleQuadP_GL2);
GpComPtr<GpGLShader<GL_FRAGMENT_SHADER>> copyQuadPixelShader = CreateShader<GL_FRAGMENT_SHADER>(GpBinarizedShaders::g_copyQuadP_GL2);
if (!m_res.m_drawQuadPaletteFlickerProgram.Link(this, drawQuadVertexShader, drawQuadPaletteFlickerPixelShader) ||
!m_res.m_drawQuadPaletteNoFlickerProgram.Link(this, drawQuadVertexShader, drawQuadPaletteNoFlickerPixelShader)
|| !m_res.m_drawQuad32FlickerProgram.Link(this, drawQuadVertexShader, drawQuad32FlickerPixelShader)
|| !m_res.m_drawQuad32NoFlickerProgram.Link(this, drawQuadVertexShader, drawQuad32NoFlickerPixelShader)
|| !m_res.m_drawQuadPaletteICCFlickerProgram.Link(this, drawQuadVertexShader, drawQuadPaletteICCFPixelShader)
|| !m_res.m_drawQuadPaletteICCNoFlickerProgram.Link(this, drawQuadVertexShader, drawQuadPaletteICCNFPixelShader)
|| !m_res.m_drawQuad32ICCFlickerProgram.Link(this, drawQuadVertexShader, drawQuad32ICCFPixelShader)
|| !m_res.m_drawQuad32ICCNoFlickerProgram.Link(this, drawQuadVertexShader, drawQuad32ICCNFPixelShader)
// || !m_drawQuadRGBICCProgram.Link(this, drawQuadVertexShader, drawQuadRGBICCPixelShader)
// || !m_drawQuad15BitICCProgram.Link(this, drawQuadVertexShader, drawQuad15BitICCPixelShader)
|| !m_res.m_scaleQuadProgram.Link(this, drawQuadVertexShader, scaleQuadPixelShader)
|| !m_res.m_copyQuadProgram.Link(this, drawQuadVertexShader, copyQuadPixelShader))
return false;
// Palette texture
{
m_res.m_paletteTexture = GpGLTexture::Create(this);
if (!m_res.m_paletteTexture)
{
if (logger)
logger->Printf(IGpLogDriver::Category_Error, "GpDisplayDriver_SDL_GL2::InitResources: GpGLTexture::Create failed");
return false;
}
GLenum internalFormat = SupportsSizedFormats() ? GL_RGBA8 : GL_RGBA;
m_gl.BindTexture(GL_TEXTURE_2D, m_res.m_paletteTexture->GetID());
m_gl.PixelStorei(GL_UNPACK_ALIGNMENT, 1);
m_gl.TexImage2D(GL_TEXTURE_2D, 0, internalFormat, 256, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_paletteData);
m_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
m_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
m_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
m_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
m_gl.BindTexture(GL_TEXTURE_2D, 0);
}
return true;
}
void GpDisplayDriver_SDL_GL2::BecomeFullScreen()
{
SDL_GetWindowPosition(m_window, &m_windowModeRevertX, &m_windowModeRevertY);
SDL_GetWindowSize(m_window, &m_windowModeRevertWidth, &m_windowModeRevertHeight);
SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
m_isFullScreen = true;
}
void GpDisplayDriver_SDL_GL2::BecomeWindowed()
{
SDL_SetWindowFullscreen(m_window, 0);
SDL_SetWindowPosition(m_window, m_windowModeRevertX, m_windowModeRevertY);
SDL_SetWindowSize(m_window, m_windowModeRevertWidth, m_windowModeRevertHeight);
m_isFullScreen = false;
}
void GpDisplayDriver_SDL_GL2::SynchronizeCursors()
{
if (m_activeCursor)
{
if (m_pendingCursor != m_activeCursor)
{
if (m_pendingCursor == nullptr)
{
m_currentStandardCursor = m_pendingStandardCursor;
ChangeToStandardCursor(m_currentStandardCursor);
m_activeCursor->DecRef();
m_activeCursor = nullptr;
}
else
{
ChangeToCursor(m_pendingCursor->GetCursor());
m_pendingCursor->IncRef();
m_activeCursor->DecRef();
m_activeCursor = m_pendingCursor;
}
}
}
else
{
if (m_pendingCursor)
{
m_pendingCursor->IncRef();
m_activeCursor = m_pendingCursor;
ChangeToCursor(m_activeCursor->GetCursor());
}
else
{
if (m_pendingStandardCursor != m_currentStandardCursor)
{
ChangeToStandardCursor(m_pendingStandardCursor);
m_currentStandardCursor = m_pendingStandardCursor;
}
}
}
}
void GpDisplayDriver_SDL_GL2::ChangeToCursor(SDL_Cursor *cursor)
{
if (cursor == nullptr)
{
if (!m_cursorIsHidden)
{
m_cursorIsHidden = true;
SDL_ShowCursor(0);
}
}
else
{
if (m_cursorIsHidden)
{
m_cursorIsHidden = false;
SDL_ShowCursor(1);
}
SDL_SetCursor(cursor);
}
}
void GpDisplayDriver_SDL_GL2::ChangeToStandardCursor(EGpStandardCursor_t cursor)
{
switch (cursor)
{
case EGpStandardCursors::kArrow:
SDL_SetCursor(m_arrowCursor);
break;
case EGpStandardCursors::kHidden:
SDL_SetCursor(nullptr);
break;
case EGpStandardCursors::kIBeam:
SDL_SetCursor(m_iBeamCursor);
break;
case EGpStandardCursors::kWait:
SDL_SetCursor(m_waitCursor);
break;
default:
break;
}
}
bool GpDisplayDriver_SDL_GL2::ResizeOpenGLWindow(uint32_t &windowWidth, uint32_t &windowHeight, uint32_t desiredWidth, uint32_t desiredHeight, IGpLogDriver *logger)
{
if (logger)
logger->Printf(IGpLogDriver::Category_Information, "ResizeOpenGLWindow: %i x %i", static_cast<int>(desiredWidth), static_cast<int>(desiredHeight));
if (desiredWidth > 32768)
desiredWidth = 32768;
if (desiredHeight > 32768)
desiredHeight = 32768;
if (logger)
logger->Printf(IGpLogDriver::Category_Information, "ResizeOpenGLWindow: Adjusted dimensions: %i x %i", static_cast<int>(desiredWidth), static_cast<int>(desiredHeight));
SDL_SetWindowSize(m_window, desiredWidth, desiredHeight);
windowWidth = desiredWidth;
windowHeight = desiredHeight;
return true;
}
bool GpDisplayDriver_SDL_GL2::InitBackBuffer(uint32_t width, uint32_t height)
{
IGpLogDriver *logger = m_properties.m_logger;
if (logger)
logger->Printf(IGpLogDriver::Category_Information, "GpDisplayDriver_SDL_GL2::InitBackBuffer: %i x %i", static_cast<int>(width), static_cast<int>(height));
{
m_res.m_virtualScreenTexture = GpGLTexture::Create(this);
if (!m_res.m_virtualScreenTexture)
{
if (logger)
logger->Printf(IGpLogDriver::Category_Error, "GpDisplayDriver_SDL_GL2::InitBackBuffer: GpGLTexture::Create for virtual screen texture failed");
return false;
}
GLenum internalFormat = SupportsSizedFormats() ? GL_RGBA8 : GL_RGBA;
m_gl.BindTexture(GL_TEXTURE_2D, m_res.m_virtualScreenTexture->GetID());
m_gl.PixelStorei(GL_UNPACK_ALIGNMENT, 1);
m_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
m_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
m_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
m_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
m_gl.TexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
m_gl.BindTexture(GL_TEXTURE_2D, 0);
CheckGLError(m_gl, logger);
}
{
m_res.m_virtualScreenTextureRTV = GpGLRenderTargetView::Create(this);
if (!m_res.m_virtualScreenTextureRTV)
{
if (logger)
logger->Printf(IGpLogDriver::Category_Error, "GpDisplayDriver_SDL_GL2::InitBackBuffer: GpGLRenderTargetView::Create for virtual screen texture failed");
return false;
}
m_gl.BindFramebuffer(GL_FRAMEBUFFER, m_res.m_virtualScreenTextureRTV->GetID());
m_gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_res.m_virtualScreenTexture->GetID(), 0);
GLenum status = m_gl.CheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
if (logger)
logger->Printf(IGpLogDriver::Category_Error, "GpDisplayDriver_SDL_GL2::InitBackBuffer: Framebuffer complete check failed, status was %i VST ID is %i", static_cast<int>(status), static_cast<int>(m_res.m_virtualScreenTextureRTV->GetID()));
return false;
}
m_gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
}
m_useUpscaleFilter = (m_pixelScaleX == 1.5f || m_pixelScaleX == 2.5f || m_pixelScaleY == 1.5f || m_pixelScaleY == 2.5f);
if (m_useUpscaleFilter)
{
uint32_t upscaleX = ceil(m_pixelScaleX);
uint32_t upscaleY = ceil(m_pixelScaleY);
{
m_res.m_upscaleTexture = GpGLTexture::Create(this);
if (!m_res.m_upscaleTexture)
{
if (logger)
logger->Printf(IGpLogDriver::Category_Error, "GpDisplayDriver_SDL_GL2::InitBackBuffer: GpGLTexture::Create for upscale texture failed");
return false;
}
m_upscaleTextureWidth = width * upscaleX;
m_upscaleTextureHeight = height * upscaleY;
GLenum internalFormat = SupportsSizedFormats() ? GL_RGBA8 : GL_RGBA;
m_gl.BindTexture(GL_TEXTURE_2D, m_res.m_upscaleTexture->GetID());
m_gl.PixelStorei(GL_UNPACK_ALIGNMENT, 1);
m_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
m_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
m_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
m_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
m_gl.TexImage2D(GL_TEXTURE_2D, 0, internalFormat, m_upscaleTextureWidth, m_upscaleTextureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
m_gl.BindTexture(GL_TEXTURE_2D, 0);
CheckGLError(m_gl, logger);
}
{
m_res.m_upscaleTextureRTV = GpGLRenderTargetView::Create(this);
if (!m_res.m_upscaleTextureRTV)
{
if (logger)
logger->Printf(IGpLogDriver::Category_Error, "GpDisplayDriver_SDL_GL2::InitBackBuffer: GpGLRenderTargetView::Create for upscale texture failed");
return false;
}
m_gl.BindFramebuffer(GL_FRAMEBUFFER, m_res.m_upscaleTextureRTV->GetID());
m_gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_res.m_upscaleTexture->GetID(), 0);
GLenum status = m_gl.CheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
if (logger)
logger->Printf(IGpLogDriver::Category_Error, "GpDisplayDriver_SDL_GL2::InitBackBuffer: Framebuffer complete check failed for upscale texture, status was %i VST ID is %i", static_cast<int>(status), static_cast<int>(m_res.m_virtualScreenTextureRTV->GetID()));
return false;
}
m_gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
}
}
CheckGLError(m_gl, logger);
return true;
}
void GpDisplayDriver_SDL_GL2::ScaleVirtualScreen()
{
if (m_useUpscaleFilter)
{
m_gl.BindFramebuffer(GL_FRAMEBUFFER, m_res.m_upscaleTextureRTV->GetID());
m_gl.Viewport(0, 0, m_upscaleTextureWidth, m_upscaleTextureHeight);
const BlitQuadProgram &program = m_res.m_scaleQuadProgram;
float ndcOriginsAndDimensions[4] =
{
-1.0f,
-1.0f,
2.0f,
2.0f,
};
float surfaceDimensions_TextureRegion[4] =
{
static_cast<float>(m_windowWidthVirtual),
static_cast<float>(m_windowHeightVirtual),
1.f,
1.f
};
m_gl.UseProgram(program.m_program->GetID());
m_gl.Uniform4fv(program.m_vertexNDCOriginAndDimensionsLocation, 1, reinterpret_cast<const GLfloat*>(ndcOriginsAndDimensions));
m_gl.Uniform4fv(program.m_vertexSurfaceDimensionsLocation, 1, reinterpret_cast<const GLfloat*>(surfaceDimensions_TextureRegion));
float dxdy_dimensions[4] =
{
static_cast<float>(1.0 / m_windowWidthVirtual),
static_cast<float>(1.0 / m_windowHeightVirtual),
static_cast<float>(m_windowWidthVirtual),
static_cast<float>(m_windowHeightVirtual)
};
m_gl.Uniform4fv(program.m_pixelDXDYDimensionsLocation, 1, reinterpret_cast<const GLfloat*>(dxdy_dimensions));
GLint attribLocations[] = { program.m_vertexPosUVLocation };
m_res.m_quadVertexArray->Activate(attribLocations);
m_gl.ActiveTexture(GL_TEXTURE0 + 0);
m_gl.BindTexture(GL_TEXTURE_2D, m_res.m_virtualScreenTexture->GetID());
m_gl.Uniform1i(program.m_pixelSurfaceTextureLocation, 0);
m_gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_res.m_quadIndexBuffer->GetID());
m_gl.DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
m_gl.UseProgram(0);
m_gl.ActiveTexture(GL_TEXTURE0 + 0);
m_gl.BindTexture(GL_TEXTURE_2D, 0);
m_res.m_quadVertexArray->Deactivate(attribLocations);
m_gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
m_gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
m_gl.Viewport(0, 0, m_windowWidthPhysical, m_windowHeightPhysical);
const BlitQuadProgram &program = m_res.m_copyQuadProgram;
{
const float twoDivWidth = 2.0f / static_cast<float>(m_windowWidthPhysical);
const float twoDivHeight = 2.0f / static_cast<float>(m_windowHeightPhysical);
// Use the scaled virtual width instead of the physical width to correctly handle cases where the window boundary is in the middle of a pixel
float fWidth = static_cast<float>(m_windowWidthVirtual) * m_pixelScaleX;
float fHeight = static_cast<float>(m_windowHeightVirtual) * m_pixelScaleY;
float ndcOriginsAndDimensions[4] =
{
-1.0f,
-1.0f,
2.0f,
2.0f,
};
float surfaceDimensions_TextureRegion[4] =
{
static_cast<float>(m_windowWidthVirtual),
static_cast<float>(m_windowHeightVirtual),
1.f,
1.f
};
m_gl.UseProgram(program.m_program->GetID());
m_gl.Uniform4fv(program.m_vertexNDCOriginAndDimensionsLocation, 1, reinterpret_cast<const GLfloat*>(ndcOriginsAndDimensions));
m_gl.Uniform4fv(program.m_vertexSurfaceDimensionsLocation, 1, reinterpret_cast<const GLfloat*>(surfaceDimensions_TextureRegion));
float dxdy_dimensions[4] =
{
static_cast<float>(1.0 / m_windowWidthVirtual),
static_cast<float>(1.0 / m_windowHeightVirtual),
static_cast<float>(m_windowWidthVirtual),
static_cast<float>(m_windowHeightVirtual)
};
m_gl.Uniform4fv(program.m_pixelDXDYDimensionsLocation, 1, reinterpret_cast<const GLfloat*>(dxdy_dimensions));
}
GLint attribLocations[] = { program.m_vertexPosUVLocation };
m_res.m_quadVertexArray->Activate(attribLocations);
GpGLTexture *inputTexture = m_useUpscaleFilter ? static_cast<GpGLTexture*>(m_res.m_upscaleTexture) : static_cast<GpGLTexture*>(m_res.m_virtualScreenTexture);
m_gl.ActiveTexture(GL_TEXTURE0 + 0);
m_gl.BindTexture(GL_TEXTURE_2D, inputTexture->GetID());
m_gl.Uniform1i(program.m_pixelSurfaceTextureLocation, 0);
m_gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_res.m_quadIndexBuffer->GetID());
m_gl.DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
m_gl.UseProgram(0);
m_gl.ActiveTexture(GL_TEXTURE0 + 0);
m_gl.BindTexture(GL_TEXTURE_2D, 0);
m_res.m_quadVertexArray->Deactivate(attribLocations);
m_gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
bool GpDisplayDriver_SDL_GL2::DrawQuadProgram::Link(GpDisplayDriver_SDL_GL2 *driver, const GpGLShader<GL_VERTEX_SHADER> *vertexShader, const GpGLShader<GL_FRAGMENT_SHADER> *pixelShader)
{
m_program = GpGLProgram::Create(driver);
if (!m_program)
return false;
const GpGLFunctions *gl = driver->GetGLFunctions();
gl->AttachShader(m_program->GetID(), vertexShader->GetID());
gl->AttachShader(m_program->GetID(), pixelShader->GetID());
gl->LinkProgram(m_program->GetID());
GLint linked = 0;
gl->GetProgramiv(m_program->GetID(), GL_LINK_STATUS, &linked);
if (!linked)
{
GLint logLength = 0;
gl->GetProgramiv(m_program->GetID(), GL_INFO_LOG_LENGTH, &logLength);
std::vector<char> errorMsgBuffer;
errorMsgBuffer.resize(static_cast<size_t>(logLength) + 1);
errorMsgBuffer[logLength] = '\0';
gl->GetProgramInfoLog(m_program->GetID(), static_cast<size_t>(logLength), nullptr, reinterpret_cast<GLchar*>(&errorMsgBuffer[0]));
const char *errorMsg = &errorMsgBuffer[0];
return false;
}
m_vertexNDCOriginAndDimensionsLocation = gl->GetUniformLocation(m_program->GetID(), "ndcOriginAndDimensions");
m_vertexSurfaceDimensionsLocation = gl->GetUniformLocation(m_program->GetID(), "surfaceDimensions_TextureRegion");
m_vertexPosUVLocation = gl->GetAttribLocation(m_program->GetID(), "posUV");
m_pixelModulationLocation = gl->GetUniformLocation(m_program->GetID(), "constants_Modulation");
m_pixelFlickerAxisLocation = gl->GetUniformLocation(m_program->GetID(), "constants_FlickerAxis");
m_pixelFlickerStartThresholdLocation = gl->GetUniformLocation(m_program->GetID(), "constants_FlickerStartThreshold");
m_pixelFlickerEndThresholdLocation = gl->GetUniformLocation(m_program->GetID(), "constants_FlickerEndThreshold");
m_pixelDesaturationLocation = gl->GetUniformLocation(m_program->GetID(), "constants_Desaturation");
m_pixelSurfaceTextureLocation = gl->GetUniformLocation(m_program->GetID(), "surfaceTexture");
m_pixelPaletteTextureLocation = gl->GetUniformLocation(m_program->GetID(), "paletteTexture");
return true;
}
bool GpDisplayDriver_SDL_GL2::BlitQuadProgram::Link(GpDisplayDriver_SDL_GL2 *driver, const GpGLShader<GL_VERTEX_SHADER> *vertexShader, const GpGLShader<GL_FRAGMENT_SHADER> *pixelShader)
{
m_program = GpGLProgram::Create(driver);
if (!m_program)
return false;
const GpGLFunctions *gl = driver->GetGLFunctions();
gl->AttachShader(m_program->GetID(), vertexShader->GetID());
gl->AttachShader(m_program->GetID(), pixelShader->GetID());
gl->LinkProgram(m_program->GetID());
GLint linked = 0;
gl->GetProgramiv(m_program->GetID(), GL_LINK_STATUS, &linked);
if (!linked)
{
GLint logLength = 0;
gl->GetProgramiv(m_program->GetID(), GL_INFO_LOG_LENGTH, &logLength);
std::vector<char> errorMsgBuffer;
errorMsgBuffer.resize(static_cast<size_t>(logLength) + 1);
errorMsgBuffer[logLength] = '\0';
gl->GetProgramInfoLog(m_program->GetID(), static_cast<size_t>(logLength), nullptr, reinterpret_cast<GLchar*>(&errorMsgBuffer[0]));
const char *errorMsg = &errorMsgBuffer[0];
return false;
}
m_vertexNDCOriginAndDimensionsLocation = gl->GetUniformLocation(m_program->GetID(), "ndcOriginAndDimensions");
m_vertexSurfaceDimensionsLocation = gl->GetUniformLocation(m_program->GetID(), "surfaceDimensions_TextureRegion");
m_pixelDXDYDimensionsLocation = gl->GetUniformLocation(m_program->GetID(), "dxdy_dimensions");
m_vertexPosUVLocation = gl->GetAttribLocation(m_program->GetID(), "posUV");
m_pixelSurfaceTextureLocation = gl->GetUniformLocation(m_program->GetID(), "surfaceTexture");
return true;
}
bool GpDisplayDriver_SDL_GL2::SyncRender()
{
if (m_frameTimeAccumulated >= m_frameTimeSliceSize)
{
m_frameTimeAccumulated -= m_frameTimeSliceSize;
return true;
}
SynchronizeCursors();
float bgColor[4];
for (int i = 0; i < 4; i++)
bgColor[i] = m_bgColor[i];
if (m_bgIsDark)
{
for (int i = 0; i < 3; i++)
bgColor[i] *= 0.25f;
}
//ID3D11RenderTargetView *const rtv = m_backBufferRTV;
GpGLRenderTargetView *const vsRTV = m_res.m_virtualScreenTextureRTV;
m_gl.BindFramebuffer(GL_FRAMEBUFFER, vsRTV->GetID());
m_gl.Viewport(0, 0, m_windowWidthVirtual, m_windowHeightVirtual);
m_gl.ClearColor(m_bgColor[0], m_bgColor[1], m_bgColor[2], m_bgColor[3]);
m_gl.Clear(GL_COLOR_BUFFER_BIT);
m_properties.m_renderFunc(m_properties.m_renderFuncContext);
ScaleVirtualScreen();
CheckGLError(m_gl, m_properties.m_logger);
SDL_GL_SwapWindow(m_window);
#ifdef __EMSCRIPTEN__
emscripten_sleep(1);
#endif
std::chrono::time_point<std::chrono::high_resolution_clock>::duration syncTime = std::chrono::high_resolution_clock::now().time_since_epoch();
const intmax_t periodNum = std::chrono::high_resolution_clock::period::num;
const intmax_t periodDen = std::chrono::high_resolution_clock::period::den;
if (syncTime.count() != 0)
{
if (m_syncTimeBase.count() == 0)
m_syncTimeBase = syncTime;
std::chrono::time_point<std::chrono::high_resolution_clock>::duration timestamp;
timestamp = syncTime - m_syncTimeBase;
bool compacted = false;
if (m_presentHistory.Size() > 0)
{
CompactedPresentHistoryItem &lastItem = m_presentHistory[m_presentHistory.Size() - 1];
std::chrono::time_point<std::chrono::high_resolution_clock>::duration timeDelta = timestamp - lastItem.m_timestamp;
if (timeDelta.count() < 0)
timeDelta = std::chrono::time_point<std::chrono::high_resolution_clock>::duration::zero(); // This should never happen
if (timeDelta.count() * static_cast<intmax_t>(m_properties.m_frameTimeLockDenominator) * periodNum < periodDen * static_cast<intmax_t>(m_properties.m_frameTimeLockNumerator))
{
lastItem.m_numFrames++;
compacted = true;
}
}
if (!compacted)
{
if (m_presentHistory.Size() == m_presentHistory.CAPACITY)
m_presentHistory.RemoveFromStart();
CompactedPresentHistoryItem *newItem = m_presentHistory.Append();
newItem->m_timestamp = timestamp;
newItem->m_numFrames = 1;
}
}
if (m_presentHistory.Size() >= 2)
{
const size_t presentHistorySizeMinusOne = m_presentHistory.Size() - 1;
unsigned int numFrames = 0;
for (size_t i = 0; i < presentHistorySizeMinusOne; i++)
numFrames += m_presentHistory[i].m_numFrames;
std::chrono::high_resolution_clock::duration timeFrame = m_presentHistory[presentHistorySizeMinusOne].m_timestamp - m_presentHistory[0].m_timestamp;
unsigned int cancelledFrames = 0;
std::chrono::high_resolution_clock::duration cancelledTime = std::chrono::high_resolution_clock::duration::zero();
const int overshootTolerance = 2;
for (size_t i = 0; i < presentHistorySizeMinusOne; i++)
{
std::chrono::high_resolution_clock::duration blockTimeframe = m_presentHistory[i + 1].m_timestamp - m_presentHistory[i].m_timestamp;
unsigned int blockNumFrames = m_presentHistory[i].m_numFrames;
if (blockTimeframe.count() * static_cast<intmax_t>(numFrames) >= timeFrame.count() * static_cast<intmax_t>(blockNumFrames) * overshootTolerance)
{
cancelledTime += blockTimeframe;
cancelledFrames += blockNumFrames;
}
}
numFrames -= cancelledFrames;
timeFrame -= cancelledTime;
// timeFrame / numFrames = Frame timestep
// Unless Frame timestep is within the frame lock range, a.k.a.
// timeFrame / numFrames / qpFreq >= minFrameTimeNum / minFrameTimeDenom
bool isInFrameTimeLock = false;
if (timeFrame.count() * static_cast<intmax_t>(m_properties.m_frameTimeLockMinDenominator) * periodNum >= static_cast<intmax_t>(numFrames) * static_cast<intmax_t>(m_properties.m_frameTimeLockMinNumerator) * periodDen
&& timeFrame.count() * static_cast<intmax_t>(m_properties.m_frameTimeLockMaxDenominator) * periodNum <= static_cast<intmax_t>(numFrames) * static_cast<intmax_t>(m_properties.m_frameTimeLockMaxNumerator) * periodDen)
{
isInFrameTimeLock = true;
}
std::chrono::high_resolution_clock::duration frameTimeStep = m_frameTimeSliceSize;
if (!isInFrameTimeLock)
{
const int MAX_FRAMES_PER_STEP = 4;
frameTimeStep = std::chrono::high_resolution_clock::duration(timeFrame.count() / numFrames);
if (frameTimeStep > m_frameTimeSliceSize * MAX_FRAMES_PER_STEP)
frameTimeStep = m_frameTimeSliceSize * MAX_FRAMES_PER_STEP;
}
m_frameTimeAccumulated += frameTimeStep;
}
return false;
}
IGpDisplayDriver *GpDriver_CreateDisplayDriver_SDL_GL2(const GpDisplayDriverProperties &properties)
{
GpDisplayDriver_SDL_GL2 *driver = static_cast<GpDisplayDriver_SDL_GL2*>(malloc(sizeof(GpDisplayDriver_SDL_GL2)));
if (!driver)
return nullptr;
return new (driver) GpDisplayDriver_SDL_GL2(properties);
}
template<class T>
T *GpGLObjectImpl<T>::Create(GpDisplayDriver_SDL_GL2 *driver)
{
T *obj = static_cast<T*>(malloc(sizeof(T)));
if (!obj)
return nullptr;
new (obj) T();
obj->InitDriver(driver, driver->GetGLFunctions());
if (!obj->Init())
{
obj->Destroy();
return nullptr;
}
return obj;
}
#if GP_ASYNCIFY_PARANOID
bool IGpDisplayDriver::Init()
{
return static_cast<GpDisplayDriver_SDL_GL2*>(this)->Init();
}
void IGpDisplayDriver::ServeTicks(int tickCount)
{
static_cast<GpDisplayDriver_SDL_GL2*>(this)->ServeTicks(tickCount);
}
void IGpDisplayDriver::Shutdown()
{
static_cast<GpDisplayDriver_SDL_GL2*>(this)->Shutdown();
}
IGpCursor *IGpDisplayDriver::CreateBWCursor(size_t width, size_t height, const void *pixelData, const void *maskData, size_t hotSpotX, size_t hotSpotY)
{
return static_cast<GpDisplayDriver_SDL_GL2*>(this)->CreateBWCursor(width, height, pixelData, maskData, hotSpotX, hotSpotY);
}
IGpCursor *IGpDisplayDriver::CreateColorCursor(size_t width, size_t height, const void *pixelDataRGBA, size_t hotSpotX, size_t hotSpotY)
{
return static_cast<GpDisplayDriver_SDL_GL2*>(this)->CreateColorCursor(width, height, pixelDataRGBA, hotSpotX, hotSpotY);
}
#endif
#pragma pop_macro("LoadCursor")