#include "IGpDisplayDriver.h" #include "GpApplicationName.h" #include "GpComPtr.h" #include "GpFiber_SDL.h" #include "GpDisplayDriverProperties.h" #include "GpVOSEvent.h" #include "GpRingBuffer.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 #include #include #include #include #include #include #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; namespace DeleteMe { bool DecodeCodePoint(const uint8_t *characters, size_t availableCharacters, size_t &outCharactersDigested, uint32_t &outCodePoint) { if (availableCharacters <= 0) return false; if ((characters[0] & 0x80) == 0x00) { outCharactersDigested = 1; outCodePoint = characters[0]; return true; } size_t sz = 0; uint32_t codePoint = 0; uint32_t minCodePoint = 0; if ((characters[0] & 0xe0) == 0xc0) { sz = 2; minCodePoint = 0x80; codePoint = (characters[0] & 0x1f); } else if ((characters[0] & 0xf0) == 0xe0) { sz = 3; minCodePoint = 0x800; codePoint = (characters[0] & 0x0f); } else if ((characters[0] & 0xf8) == 0xf0) { sz = 4; minCodePoint = 0x10000; codePoint = (characters[0] & 0x07); } else return false; if (availableCharacters < sz) return false; for (size_t auxByte = 1; auxByte < sz; auxByte++) { if ((characters[auxByte] & 0xc0) != 0x80) return false; codePoint = (codePoint << 6) | (characters[auxByte] & 0x3f); } if (codePoint < minCodePoint || codePoint > 0x10ffff) return false; if (codePoint >= 0xd800 && codePoint <= 0xdfff) return false; outCodePoint = codePoint; outCharactersDigested = sz; return true; } } namespace GpBinarizedShaders { extern const char *g_drawQuadV_GL2; extern const char *g_drawQuadPalettePF_GL2; extern const char *g_drawQuadPalettePNF_GL2; extern const char *g_drawQuadRGBPF_GL2; extern const char *g_drawQuadRGBPNF_GL2; extern const char *g_drawQuad15BitPF_GL2; extern const char *g_drawQuad15BitPNF_GL2; extern const char *g_drawQuadPaletteICCPF_GL2; extern const char *g_drawQuadPaletteICCPNF_GL2; extern const char *g_drawQuadRGBICCPF_GL2; extern const char *g_drawQuadRGBICCPNF_GL2; extern const char *g_drawQuad15BitICCPF_GL2; extern const char *g_drawQuad15BitICCPNF_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) { GLenum errorCode = gl.GetError(); if (errorCode != 0) { if (logger) logger->Printf(IGpLogDriver::Category_Error, "GL error reported: %x", static_cast(errorCode)); } assert(errorCode == 0); } 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 GpGLObjectImpl : public GpGLObject { public: static T *Create(GpDisplayDriver_SDL_GL2 *driver); protected: virtual bool Init() = 0; void Destroy() final override; }; template void GpGLObjectImpl::Destroy() { T *self = static_cast(this); self->~T(); free(self); } class GpGLRenderTargetView final : public GpGLObjectImpl { 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 { 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 { 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 { 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(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 { 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(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(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 { 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 class GpGLShader final : public GpGLObjectImpl > { public: GpGLShader(); ~GpGLShader(); bool Init() override; GLuint GetID() const; private: GLuint m_id; }; template GpGLShader::GpGLShader() : m_id(0) { } template GpGLShader::~GpGLShader() { if (m_id != 0) this->m_gl->DeleteShader(m_id); } template bool GpGLShader::Init() { m_id = this->m_gl->CreateShader(TShaderType); return m_id != 0; } template GLuint GpGLShader::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 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(); void TranslateSDLMessage(const SDL_Event *msg, IGpVOSEventQueue *eventQueue, float pixelScaleX, float pixelScaleY, bool obstructiveTextInput); void Run() override; void Shutdown() override; void GetDisplayResolution(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) override; IGpCursor *CreateColorCursor(size_t width, size_t height, const void *pixelDataRGBA, size_t hotSpotX, size_t hotSpotY) 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 GpComPtr > 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::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(); GpDisplayDriverTickStatus_t PresentFrameAndSync(); GpGLFunctions m_gl; GpDisplayDriverProperties m_properties; struct DrawQuadProgram { GpComPtr 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 *vertexShader, const GpGLShader *pixelShader); }; struct BlitQuadProgram { GpComPtr 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 *vertexShader, const GpGLShader *pixelShader); }; struct InstancedResources { GpComPtr m_virtualScreenTextureRTV; GpComPtr m_virtualScreenTexture; GpComPtr m_upscaleTextureRTV; GpComPtr m_upscaleTexture; uint32_t m_upscaleTextureWidth; uint32_t m_upscaleTextureHeight; GpComPtr m_quadVertexArray; GpComPtr m_quadVertexBufferKeepalive; GpComPtr m_quadIndexBuffer; GpComPtr m_paletteTexture; BlitQuadProgram m_scaleQuadProgram; BlitQuadProgram m_copyQuadProgram; DrawQuadProgram m_drawQuadPaletteNoFlickerProgram; DrawQuadProgram m_drawQuadPaletteFlickerProgram; DrawQuadProgram m_drawQuadRGBProgram; DrawQuadProgram m_drawQuad15BitProgram; DrawQuadProgram m_drawQuadPaletteICCNoFlickerProgram; DrawQuadProgram m_drawQuadPaletteICCFlickerProgram; DrawQuadProgram m_drawQuadRGBICCProgram; DrawQuadProgram m_drawQuad15BitICCProgram; }; 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; float m_pixelScaleX; float m_pixelScaleY; bool m_useUpscaleFilter; GpCursor_SDL2 *m_activeCursor; GpCursor_SDL2 *m_pendingCursor; EGpStandardCursor_t m_currentStandardCursor; EGpStandardCursor_t m_pendingStandardCursor; bool m_mouseIsInClientArea; IGpFiber *m_vosFiber; IGpThreadEvent *m_vosEvent; float m_bgColor[4]; bool m_bgIsDark; bool m_useICCProfile; std::chrono::time_point::duration m_syncTimeBase; GpRingBuffer 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 texture = GpComPtr(GpGLTexture::Create(driver)); if (!texture) return nullptr; GpDisplayDriverSurface_GL2 *surface = static_cast(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_pixelScaleX(1.0f) , m_pixelScaleY(1.0f) , m_useUpscaleFilter(false) , m_vosFiber(nullptr) , m_vosEvent(nullptr) , 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::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->IsFullscreenPreferred(); 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(properties.m_frameTimeLockNumerator) / static_cast(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(m_paletteData) % GP_SYSTEM_MEMORY_ALIGNMENT != 0) m_paletteData++; memset(m_paletteData, 255, 256 * 4); } template static bool LookupOpenGLFunction(T &target, const char *name) { target = static_cast(nullptr); void *proc = SDL_GL_GetProcAddress(name); if (proc) { target = reinterpret_cast(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() { return true; } 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(static_cast(x) / pixelScaleX); if (pixelScaleY != 1.0f) mEvent.m_y = static_cast(static_cast(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(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; case SDLK_LCTRL: outSubset = GpKeyIDSubsets::kSpecial; outKey.m_specialKey = GpKeySpecials::kLeftCtrl; break; case SDLK_RCTRL: 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(keyCode + 'A' - 'a'); else outKey.m_asciiChar = static_cast(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(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(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(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(m_windowWidthVirtual) * fingerEvt->x; float unnormalizedY = static_cast(m_windowHeightVirtual) * fingerEvt->y; PostTouchEvent(eventQueue, evtType, static_cast(unnormalizedX), static_cast(unnormalizedY), fingerEvt->touchId, fingerEvt->fingerId); } break; case SDL_KEYDOWN: { const SDL_KeyboardEvent *keyEvt = reinterpret_cast(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(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(msg); size_t lenUTF8 = strlen(teEvt->text); size_t parseOffset = 0; while (parseOffset < lenUTF8) { uint32_t codePoint; size_t numDigested; DeleteMe::DecodeCodePoint(reinterpret_cast(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(codePoint); else { subset = GpKeyIDSubsets::kUnicode; key.m_unicodeChar = static_cast(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::Run() { #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; m_vosEvent = m_properties.m_systemServices->CreateThreadEvent(true, false); m_vosFiber = new GpFiber_SDL(nullptr, m_vosEvent); uint32_t windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN; if (m_isFullScreenDesired) { 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(virtualWidth), static_cast(virtualHeight), static_cast(pixelScaleX), static_cast(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; for (;;) { SDL_Event msg; if (SDL_PollEvent(&msg) != 0) { if (msg.type == SDL_MOUSEMOTION) { if (!m_mouseIsInClientArea) m_mouseIsInClientArea = true; } //else if (msg.type == SDL_MOUSELEAVE) // Does SDL support this?? // m_mouseIsInClientArea = false; else if (msg.type == SDL_RENDER_DEVICE_RESET || msg.type == SDL_RENDER_TARGETS_RESET) { if (logger) logger->Printf(IGpLogDriver::Category_Information, "Triggering GL context reset due to device loss (Type: %i)", static_cast(msg.type)); m_contextLost = true; } 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(m_windowWidthPhysical), static_cast(m_windowHeightPhysical), static_cast(m_windowWidthVirtual), static_cast(m_windowHeightVirtual)); // Drop everything and reset m_res.~InstancedResources(); new (&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); GpDisplayDriverTickStatus_t tickStatus = PresentFrameAndSync(); if (tickStatus == GpDisplayDriverTickStatuses::kFatalFault || tickStatus == GpDisplayDriverTickStatuses::kApplicationTerminated) { if (logger) { if (tickStatus == GpDisplayDriverTickStatuses::kFatalFault) logger->Printf(IGpLogDriver::Category_Information, "Terminating display driver due to fatal fault"); if (tickStatus == GpDisplayDriverTickStatuses::kApplicationTerminated) logger->Printf(IGpLogDriver::Category_Information, "Terminating display driver due to application termination"); } break; } } } // Exit } void GpDisplayDriver_SDL_GL2::Shutdown() { this->~GpDisplayDriver_SDL_GL2(); free(this); } void GpDisplayDriver_SDL_GL2::GetDisplayResolution(unsigned int *width, unsigned int *height) { if (width) *width = m_windowWidthVirtual; if (height) *height = m_windowHeightVirtual; } 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(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) { return; } 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(m_windowWidthVirtual); const float negativeTwoDivHeight = -2.0f / static_cast(m_windowHeightVirtual); GLfloat ndcOriginAndDimensions[4] = { static_cast(x) * twoDivWidth - 1.0f, static_cast(y) * negativeTwoDivHeight + 1.0f, static_cast(width) * twoDivWidth, static_cast(height) * negativeTwoDivHeight, }; GLfloat surfaceDimensions_TextureRegion[4] = { static_cast(glSurface->GetImageWidth()), static_cast(glSurface->GetHeight()), static_cast(static_cast(glSurface->GetImageWidth()) / static_cast(glSurface->GetPaddedTextureWidth())), 1.f }; m_gl.Uniform4fv(program->m_vertexNDCOriginAndDimensionsLocation, 1, reinterpret_cast(ndcOriginAndDimensions)); m_gl.Uniform4fv(program->m_vertexSurfaceDimensionsLocation, 1, reinterpret_cast(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(pixelData), static_cast(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(&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(surface->pixels); for (size_t y = 0; y < height; y++) memcpy(destPixels + y * surfacePitch, static_cast(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(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(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(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) { } bool GpDisplayDriver_SDL_GL2::SavePrefs(void *context, WritePrefsFunc_t writeFunc) { return true; } 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 GpComPtr> GpDisplayDriver_SDL_GL2::CreateShader(const char *shaderSrc) { size_t shaderCodeLength = strlen(shaderSrc); GpComPtr> shader(GpGLShader::Create(this)); if (shader == nullptr) return shader; const GLchar *shaderSrcGL = reinterpret_cast(shaderSrc); GLint shaderLenGL = static_cast(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 log; log.resize(infoLength + 1); log[infoLength] = '\0'; m_gl.GetShaderInfoLog(shader->GetID(), static_cast(log.size() - 1), nullptr, reinterpret_cast(&log[0])); const char *errorMsg = &log[0]; return GpComPtr>(); } 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"); if ((m_pixelScaleX < 2.0f && m_pixelScaleX > 1.0f) || (m_pixelScaleY < 2.0f && m_pixelScaleY > 1.0f)) m_useUpscaleFilter = true; else m_useUpscaleFilter = false; 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> drawQuadVertexShader = CreateShader(GpBinarizedShaders::g_drawQuadV_GL2); GpComPtr> drawQuadPaletteFlickerPixelShader = CreateShader(GpBinarizedShaders::g_drawQuadPalettePF_GL2); GpComPtr> drawQuadPaletteNoFlickerPixelShader = CreateShader(GpBinarizedShaders::g_drawQuadPalettePNF_GL2); //m_drawQuadRGBPixelShader = CreateShader(GpBinarizedShaders::g_drawQuadRGBP_GL2); //m_drawQuad15BitPixelShader = CreateShader(GpBinarizedShaders::g_drawQuad15BitP_GL2); GpComPtr> drawQuadPaletteICCFPixelShader = CreateShader(GpBinarizedShaders::g_drawQuadPaletteICCPF_GL2); GpComPtr> drawQuadPaletteICCNFPixelShader = CreateShader(GpBinarizedShaders::g_drawQuadPaletteICCPNF_GL2); //m_drawQuadRGBICCPixelShader = CreateShader(GpBinarizedShaders::g_drawQuadRGBICCP_GL2); //m_drawQuad15BitICCPixelShader = CreateShader(GpBinarizedShaders::g_drawQuad15BitICCP_GL2); GpComPtr> scaleQuadPixelShader = CreateShader(GpBinarizedShaders::g_scaleQuadP_GL2); GpComPtr> copyQuadPixelShader = CreateShader(GpBinarizedShaders::g_copyQuadP_GL2); if (!m_res.m_drawQuadPaletteFlickerProgram.Link(this, drawQuadVertexShader, drawQuadPaletteFlickerPixelShader) || !m_res.m_drawQuadPaletteNoFlickerProgram.Link(this, drawQuadVertexShader, drawQuadPaletteFlickerPixelShader) //|| !m_drawQuadRGBProgram.Link(this, drawQuadVertexShader, drawQuadRGBPixelShader) //|| !m_drawQuad15BitProgram.Link(this, drawQuadVertexShader, drawQuad15BitPixelShader) || !m_res.m_drawQuadPaletteICCFlickerProgram.Link(this, drawQuadVertexShader, drawQuadPaletteICCFPixelShader) || !m_res.m_drawQuadPaletteICCNoFlickerProgram.Link(this, drawQuadVertexShader, drawQuadPaletteICCNFPixelShader) //|| !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(desiredWidth), static_cast(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(desiredWidth), static_cast(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(width), static_cast(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(status), static_cast(m_res.m_virtualScreenTextureRTV->GetID())); return false; } m_gl.BindFramebuffer(GL_FRAMEBUFFER, 0); } if (m_pixelScaleX != floor(m_pixelScaleX) || m_pixelScaleY != floor(m_pixelScaleY)) { 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_res.m_upscaleTextureWidth = width * upscaleX; m_res.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_res.m_upscaleTextureWidth, m_res.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(status), static_cast(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_res.m_upscaleTextureWidth, m_res.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(m_windowWidthVirtual), static_cast(m_windowHeightVirtual), 1.f, 1.f }; m_gl.UseProgram(program.m_program->GetID()); m_gl.Uniform4fv(program.m_vertexNDCOriginAndDimensionsLocation, 1, reinterpret_cast(ndcOriginsAndDimensions)); m_gl.Uniform4fv(program.m_vertexSurfaceDimensionsLocation, 1, reinterpret_cast(surfaceDimensions_TextureRegion)); float dxdy_dimensions[4] = { static_cast(1.0 / m_windowWidthVirtual), static_cast(1.0 / m_windowHeightVirtual), static_cast(m_windowWidthVirtual), static_cast(m_windowHeightVirtual) }; m_gl.Uniform4fv(program.m_pixelDXDYDimensionsLocation, 1, reinterpret_cast(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(m_windowWidthPhysical); const float twoDivHeight = 2.0f / static_cast(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(m_windowWidthVirtual) * m_pixelScaleX; float fHeight = static_cast(m_windowHeightVirtual) * m_pixelScaleY; float ndcOriginsAndDimensions[4] = { -1.0f, -1.0f, 2.0f, 2.0f, }; float surfaceDimensions_TextureRegion[4] = { static_cast(m_windowWidthVirtual), static_cast(m_windowHeightVirtual), 1.f, 1.f }; m_gl.UseProgram(program.m_program->GetID()); m_gl.Uniform4fv(program.m_vertexNDCOriginAndDimensionsLocation, 1, reinterpret_cast(ndcOriginsAndDimensions)); m_gl.Uniform4fv(program.m_vertexSurfaceDimensionsLocation, 1, reinterpret_cast(surfaceDimensions_TextureRegion)); float dxdy_dimensions[4] = { static_cast(1.0 / m_windowWidthVirtual), static_cast(1.0 / m_windowHeightVirtual), static_cast(m_windowWidthVirtual), static_cast(m_windowHeightVirtual) }; m_gl.Uniform4fv(program.m_pixelDXDYDimensionsLocation, 1, reinterpret_cast(dxdy_dimensions)); } GLint attribLocations[] = { program.m_vertexPosUVLocation }; m_res.m_quadVertexArray->Activate(attribLocations); GpGLTexture *inputTexture = m_useUpscaleFilter ? static_cast(m_res.m_upscaleTexture) : static_cast(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 *vertexShader, const GpGLShader *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 errorMsgBuffer; errorMsgBuffer.resize(static_cast(logLength) + 1); errorMsgBuffer[logLength] = '\0'; gl->GetProgramInfoLog(m_program->GetID(), static_cast(logLength), nullptr, reinterpret_cast(&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 *vertexShader, const GpGLShader *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 errorMsgBuffer; errorMsgBuffer.resize(static_cast(logLength) + 1); errorMsgBuffer[logLength] = '\0'; gl->GetProgramInfoLog(m_program->GetID(), static_cast(logLength), nullptr, reinterpret_cast(&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; } GpDisplayDriverTickStatus_t GpDisplayDriver_SDL_GL2::PresentFrameAndSync() { 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); std::chrono::time_point::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::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::duration timeDelta = timestamp - lastItem.m_timestamp; if (timeDelta.count() < 0) timeDelta = std::chrono::time_point::duration::zero(); // This should never happen if (timeDelta.count() * static_cast(m_properties.m_frameTimeLockDenominator) * periodNum < periodDen * static_cast(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(numFrames) >= timeFrame.count() * static_cast(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(m_properties.m_frameTimeLockMinDenominator) * periodNum >= static_cast(numFrames) * static_cast(m_properties.m_frameTimeLockMinNumerator) * periodDen && timeFrame.count() * static_cast(m_properties.m_frameTimeLockMaxDenominator) * periodNum <= static_cast(numFrames) * static_cast(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; while (m_frameTimeAccumulated >= m_frameTimeSliceSize) { GpDisplayDriverTickStatus_t tickStatus = m_properties.m_tickFunc(m_properties.m_tickFuncContext, m_vosFiber); m_frameTimeAccumulated -= m_frameTimeSliceSize; if (tickStatus == GpDisplayDriverTickStatuses::kSynchronizing) { m_frameTimeAccumulated = std::chrono::high_resolution_clock::duration::zero(); break; } else if (tickStatus != GpDisplayDriverTickStatuses::kOK) return tickStatus; } } return GpDisplayDriverTickStatuses::kOK; } IGpDisplayDriver *GpDriver_CreateDisplayDriver_SDL_GL2(const GpDisplayDriverProperties &properties) { GpDisplayDriver_SDL_GL2 *driver = static_cast(malloc(sizeof(GpDisplayDriver_SDL_GL2))); if (!driver) return nullptr; new (driver) GpDisplayDriver_SDL_GL2(properties); if (!driver->Init()) { driver->Shutdown(); return nullptr; } return driver; } template T *GpGLObjectImpl::Create(GpDisplayDriver_SDL_GL2 *driver) { T *obj = static_cast(malloc(sizeof(T))); if (!obj) return nullptr; new (obj) T(); obj->InitDriver(driver, driver->GetGLFunctions()); if (!obj->Init()) { obj->Destroy(); return nullptr; } return obj; } #pragma pop_macro("LoadCursor")