WIP
This commit is contained in:
parent
a036ae92e8
commit
48765b4009
|
@ -1,6 +1,7 @@
|
|||
#include "context_wgl.h"
|
||||
#include "../assert.h"
|
||||
#include "../log.h"
|
||||
#include "../make_array.h"
|
||||
#include "glad.h"
|
||||
#include "glad_wgl.h"
|
||||
Log_SetChannel(GL::ContextWGL);
|
||||
|
@ -29,9 +30,18 @@ ContextWGL::~ContextWGL()
|
|||
if (m_rc)
|
||||
wglDeleteContext(m_rc);
|
||||
|
||||
if (m_pbuffer)
|
||||
{
|
||||
if (m_dc)
|
||||
wglReleasePbufferDCARB(m_pbuffer, m_dc);
|
||||
wglDestroyPbufferARB(m_pbuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_dc)
|
||||
ReleaseDC(GetHWND(), m_dc);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Context> ContextWGL::Create(const WindowInfo& wi, const Version* versions_to_try,
|
||||
size_t num_versions_to_try)
|
||||
|
@ -153,7 +163,8 @@ std::unique_ptr<Context> ContextWGL::CreateSharedContext(const WindowInfo& wi)
|
|||
}
|
||||
else
|
||||
{
|
||||
Panic("Create pbuffer");
|
||||
if (!context->InitializePBuffer(m_dc, wi.surface_width, wi.surface_height))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (m_version.profile == Profile::NoProfile)
|
||||
|
@ -232,6 +243,47 @@ bool ContextWGL::InitializeDC()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ContextWGL::InitializePBuffer(HDC onscreen_dc, u32 width, u32 height)
|
||||
{
|
||||
if (!GLAD_WGL_ARB_pbuffer)
|
||||
{
|
||||
Log_ErrorPrintf("WGL_EXT_pbuffer not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr auto attribs =
|
||||
make_array(WGL_DRAW_TO_PBUFFER_ARB, 1, WGL_RED_BITS_ARB, 0, WGL_GREEN_BITS_ARB, 0, WGL_BLUE_BITS_ARB, 0,
|
||||
WGL_DEPTH_BITS_ARB, 0, WGL_STENCIL_BITS_ARB, 0, 0, 0);
|
||||
|
||||
static constexpr auto fattribs = make_array(0.0f, 0.0f);
|
||||
|
||||
int pixel_format;
|
||||
UINT num_pixel_formats;
|
||||
if (!wglChoosePixelFormatARB(onscreen_dc, attribs.data(), fattribs.data(), 1, &pixel_format, &num_pixel_formats))
|
||||
{
|
||||
Log_ErrorPrintf("wglChoosePixelFormatARB() failed: %08X", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr auto pbattribs = make_array(0, 0);
|
||||
m_pbuffer =
|
||||
wglCreatePbufferARB(onscreen_dc, pixel_format, std::max<u32>(width, 1), std::max<u32>(height, 1), pbattribs.data());
|
||||
if (!m_pbuffer)
|
||||
{
|
||||
Log_ErrorPrintf("wglCreatePbufferARB() failed: %08X", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_dc = wglGetPbufferDCARB(m_pbuffer);
|
||||
if (!m_dc)
|
||||
{
|
||||
Log_ErrorPrintf("wglGetPbufferDCARB() failed: %08X", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ContextWGL::CreateAnyContext(HGLRC share_context, bool make_current)
|
||||
{
|
||||
m_rc = wglCreateContext(m_dc);
|
||||
|
@ -294,7 +346,7 @@ bool ContextWGL::CreateVersionContext(const Version& version, HGLRC share_contex
|
|||
0,
|
||||
0};
|
||||
|
||||
new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs);
|
||||
new_rc = wglCreateContextAttribsARB(m_dc, nullptr, attribs);
|
||||
}
|
||||
else if (version.profile == Profile::ES)
|
||||
{
|
||||
|
@ -308,7 +360,7 @@ bool ContextWGL::CreateVersionContext(const Version& version, HGLRC share_contex
|
|||
0,
|
||||
0};
|
||||
|
||||
new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs);
|
||||
new_rc = wglCreateContextAttribsARB(m_dc, nullptr, attribs);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -316,6 +368,12 @@ bool ContextWGL::CreateVersionContext(const Version& version, HGLRC share_contex
|
|||
return false;
|
||||
}
|
||||
|
||||
if (share_context && !wglShareLists(share_context, new_rc))
|
||||
{
|
||||
Log_ErrorPrintf("wglShareLists() failed: %08X", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!new_rc)
|
||||
return false;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "../windows_headers.h"
|
||||
#include "context.h"
|
||||
#include <glad.h>
|
||||
#include <glad_wgl.h>
|
||||
|
||||
namespace GL {
|
||||
|
||||
|
@ -28,11 +29,13 @@ private:
|
|||
|
||||
bool Initialize(const Version* versions_to_try, size_t num_versions_to_try);
|
||||
bool InitializeDC();
|
||||
bool InitializePBuffer(HDC onscreen_dc, u32 width, u32 height);
|
||||
bool CreateAnyContext(HGLRC share_context, bool make_current);
|
||||
bool CreateVersionContext(const Version& version, HGLRC share_context, bool make_current);
|
||||
|
||||
HDC m_dc = {};
|
||||
HGLRC m_rc = {};
|
||||
HPBUFFERARB m_pbuffer = {};
|
||||
};
|
||||
|
||||
} // namespace GL
|
|
@ -279,12 +279,14 @@ private:
|
|||
std::unique_ptr<StreamBuffer> StreamBuffer::Create(GLenum target, u32 size)
|
||||
{
|
||||
std::unique_ptr<StreamBuffer> buf;
|
||||
#if 0
|
||||
if (GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage || GLAD_GL_EXT_buffer_storage)
|
||||
{
|
||||
buf = detail::BufferStorageStreamBuffer::Create(target, size);
|
||||
if (buf)
|
||||
return buf;
|
||||
}
|
||||
#endif
|
||||
|
||||
// BufferSubData is slower on all drivers except NVIDIA...
|
||||
#if 0
|
||||
|
|
|
@ -61,10 +61,10 @@ void Texture::SetLinearFilter(bool enabled)
|
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, enabled ? GL_LINEAR : GL_NEAREST);
|
||||
}
|
||||
|
||||
bool Texture::CreateFramebuffer()
|
||||
u32 Texture::CreateAndReturnFramebuffer()
|
||||
{
|
||||
if (!IsValid())
|
||||
return false;
|
||||
return 0;
|
||||
|
||||
glGetError();
|
||||
|
||||
|
@ -75,9 +75,18 @@ bool Texture::CreateFramebuffer()
|
|||
if (glGetError() != GL_NO_ERROR || glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
glDeleteFramebuffers(1, &fbo_id);
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return fbo_id;
|
||||
}
|
||||
|
||||
bool Texture::CreateFramebuffer()
|
||||
{
|
||||
GLuint fbo_id = CreateAndReturnFramebuffer();
|
||||
if (fbo_id == 0)
|
||||
return false;
|
||||
|
||||
if (m_fbo_id != 0)
|
||||
glDeleteFramebuffers(1, &m_fbo_id);
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ public:
|
|||
bool Create(u32 width, u32 height, GLenum internal_format, GLenum format, GLenum type, const void* data = nullptr,
|
||||
bool linear_filter = false, bool wrap = false);
|
||||
bool CreateFramebuffer();
|
||||
GLuint CreateAndReturnFramebuffer();
|
||||
|
||||
void Destroy();
|
||||
|
||||
|
|
|
@ -123,6 +123,8 @@ bool OpenGLHostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y
|
|||
}
|
||||
|
||||
void OpenGLHostDisplay::SetVSync(bool enabled)
|
||||
{
|
||||
if (!m_present_context)
|
||||
{
|
||||
// Window framebuffer has to be bound to call SetSwapInterval.
|
||||
GLint current_fbo = 0;
|
||||
|
@ -132,6 +134,9 @@ void OpenGLHostDisplay::SetVSync(bool enabled)
|
|||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
|
||||
}
|
||||
|
||||
m_vsync = enabled;
|
||||
}
|
||||
|
||||
const char* OpenGLHostDisplay::GetGLSLVersionString() const
|
||||
{
|
||||
if (GetRenderAPI() == RenderAPI::OpenGLES)
|
||||
|
@ -205,6 +210,11 @@ bool OpenGLHostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_vie
|
|||
m_window_info = wi;
|
||||
m_window_info.surface_width = m_gl_context->GetSurfaceWidth();
|
||||
m_window_info.surface_height = m_gl_context->GetSurfaceHeight();
|
||||
|
||||
#ifndef LIBRETRO
|
||||
InitializeAsyncPresentation();
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -255,6 +265,11 @@ void OpenGLHostDisplay::DestroyRenderDevice()
|
|||
if (!m_gl_context)
|
||||
return;
|
||||
|
||||
#ifndef LIBRETRO
|
||||
if (m_present_context)
|
||||
StopPresentThread();
|
||||
#endif
|
||||
|
||||
#ifdef WITH_IMGUI
|
||||
if (ImGui::GetCurrentContext())
|
||||
DestroyImGuiContext();
|
||||
|
@ -443,8 +458,20 @@ void OpenGLHostDisplay::DestroyResources()
|
|||
|
||||
bool OpenGLHostDisplay::Render()
|
||||
{
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
#ifndef LIBRETRO
|
||||
if (m_present_context)
|
||||
{
|
||||
if (!CheckPresentDrawFramebuffer())
|
||||
return false;
|
||||
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_present_draw_framebuffer->draw_fbo);
|
||||
}
|
||||
else
|
||||
{
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
|
@ -457,6 +484,9 @@ bool OpenGLHostDisplay::Render()
|
|||
|
||||
RenderSoftwareCursor();
|
||||
|
||||
if (m_present_context)
|
||||
PresentDrawFramebuffer();
|
||||
else
|
||||
m_gl_context->SwapBuffers();
|
||||
|
||||
#ifdef WITH_IMGUI
|
||||
|
@ -464,6 +494,7 @@ bool OpenGLHostDisplay::Render()
|
|||
ImGui_ImplOpenGL3_NewFrame();
|
||||
#endif
|
||||
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -486,16 +517,17 @@ void OpenGLHostDisplay::RenderDisplay()
|
|||
#ifndef LIBRETRO
|
||||
if (!m_post_processing_chain.IsEmpty())
|
||||
{
|
||||
ApplyPostProcessingChain(0, left, GetWindowHeight() - top - height, width, height, m_display_texture_handle,
|
||||
ApplyPostProcessingChain(m_present_context ? m_present_draw_framebuffer->draw_fbo : 0, left,
|
||||
GetWindowHeight() - top - height, width, height, m_display_texture_handle,
|
||||
m_display_texture_width, m_display_texture_height, m_display_texture_view_x,
|
||||
m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_height);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
RenderDisplay(left, GetWindowHeight() - top - height, width, height, m_display_texture_handle, m_display_texture_width, m_display_texture_height,
|
||||
m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width,
|
||||
m_display_texture_view_height, m_display_linear_filtering);
|
||||
RenderDisplay(left, GetWindowHeight() - top - height, width, height, m_display_texture_handle,
|
||||
m_display_texture_width, m_display_texture_height, m_display_texture_view_x, m_display_texture_view_y,
|
||||
m_display_texture_view_width, m_display_texture_view_height, m_display_linear_filtering);
|
||||
}
|
||||
|
||||
void OpenGLHostDisplay::RenderDisplay(s32 left, s32 bottom, s32 width, s32 height, void* texture_handle,
|
||||
|
@ -719,6 +751,197 @@ void OpenGLHostDisplay::ApplyPostProcessingChain(GLuint final_target, s32 final_
|
|||
m_post_processing_ubo->Unbind();
|
||||
}
|
||||
|
||||
bool OpenGLHostDisplay::InitializeAsyncPresentation()
|
||||
{
|
||||
WindowInfo shared_wi;
|
||||
std::unique_ptr<GL::Context> shared_context = m_gl_context->CreateSharedContext(shared_wi);
|
||||
if (!shared_context)
|
||||
return false;
|
||||
|
||||
m_gl_context->MakeCurrent();
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(m_present_framebuffers.size()); i++)
|
||||
{
|
||||
PresentFramebuffer& fb = m_present_framebuffers[i];
|
||||
|
||||
if (!fb.texture.Create(m_window_info.surface_width, m_window_info.surface_height, GL_RGBA8, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE) ||
|
||||
(fb.present_fbo = fb.texture.CreateAndReturnFramebuffer()) == 0)
|
||||
{
|
||||
// TODO: Leak fbo here
|
||||
m_present_framebuffers = {};
|
||||
m_gl_context->DoneCurrent();
|
||||
return false;
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb.present_fbo);
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
glFinish();
|
||||
|
||||
m_gl_context->DoneCurrent();
|
||||
|
||||
shared_context->MakeCurrent();
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(m_present_framebuffers.size()); i++)
|
||||
{
|
||||
PresentFramebuffer& fb = m_present_framebuffers[i];
|
||||
if ((fb.draw_fbo = fb.texture.CreateAndReturnFramebuffer()) == 0)
|
||||
{
|
||||
// TODO: Leak fbo here
|
||||
m_present_framebuffers = {};
|
||||
shared_context->DoneCurrent();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
glFinish();
|
||||
shared_context->DoneCurrent();
|
||||
|
||||
Log_InfoPrintf("Using shared context for async presentation");
|
||||
m_present_context = std::move(m_gl_context);
|
||||
m_gl_context = std::move(shared_context);
|
||||
m_present_thread_stop.store(false);
|
||||
m_present_thread = std::thread(&OpenGLHostDisplay::PresentThread, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLHostDisplay::CheckPresentDrawFramebuffer()
|
||||
{
|
||||
PresentFramebuffer* fb = m_present_draw_framebuffer;
|
||||
if (fb->texture.GetWidth() == m_window_info.surface_width && fb->texture.GetHeight() == m_window_info.surface_height)
|
||||
return true;
|
||||
|
||||
fb->texture.Destroy();
|
||||
if (fb->draw_fbo)
|
||||
{
|
||||
glDeleteFramebuffers(1, &fb->draw_fbo);
|
||||
fb->draw_fbo = 0;
|
||||
}
|
||||
|
||||
if (!fb->texture.Create(m_window_info.surface_width, m_window_info.surface_height, GL_RGBA8, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE) ||
|
||||
(fb->draw_fbo = fb->texture.CreateAndReturnFramebuffer()) == 0)
|
||||
{
|
||||
fb->texture.Destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
fb->changed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLHostDisplay::PresentDrawFramebuffer()
|
||||
{
|
||||
m_present_draw_framebuffer->draw_sync_id = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
|
||||
{
|
||||
std::unique_lock guard(m_present_lock);
|
||||
m_present_draw_framebuffer->ready = true;
|
||||
std::swap(m_present_draw_framebuffer, m_present_next_present_framebuffer);
|
||||
|
||||
if (m_vsync && !m_frame_presented.load())
|
||||
{
|
||||
// last frame not presented yet, we can wait
|
||||
m_present_complete_cv.wait(guard, [this]() { return m_frame_presented.load(); });
|
||||
}
|
||||
|
||||
m_frame_presented.store(false);
|
||||
}
|
||||
|
||||
// block until the presenter thread is done with it
|
||||
if (m_present_draw_framebuffer->present_sync_id)
|
||||
{
|
||||
glWaitSync(m_present_draw_framebuffer->present_sync_id, 0, GL_TIMEOUT_IGNORED);
|
||||
m_present_draw_framebuffer->present_sync_id = {};
|
||||
}
|
||||
|
||||
// if the last frame wasn't rendered, don't leak the sync
|
||||
if (m_present_draw_framebuffer->draw_sync_id)
|
||||
{
|
||||
glDeleteSync(m_present_draw_framebuffer->draw_sync_id);
|
||||
m_present_draw_framebuffer->draw_sync_id = {};
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLHostDisplay::PresentThread()
|
||||
{
|
||||
if (!m_present_context->MakeCurrent())
|
||||
Panic("Failed to make present context current");
|
||||
|
||||
if (!m_present_context->SetSwapInterval(1))
|
||||
Log_ErrorPrint("Failed to set swap interval to 1");
|
||||
|
||||
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
|
||||
while (!m_present_thread_stop.load())
|
||||
{
|
||||
{
|
||||
std::unique_lock guard(m_present_lock);
|
||||
if (m_present_next_present_framebuffer->ready)
|
||||
{
|
||||
std::swap(m_present_next_present_framebuffer, m_present_current_present_framebuffer);
|
||||
m_present_current_present_framebuffer->ready = false;
|
||||
}
|
||||
|
||||
PresentFramebuffer* fb = m_present_current_present_framebuffer;
|
||||
if (fb->draw_sync_id)
|
||||
{
|
||||
glWaitSync(fb->draw_sync_id, 0, GL_TIMEOUT_IGNORED);
|
||||
fb->draw_sync_id = {};
|
||||
}
|
||||
|
||||
if (fb->changed)
|
||||
{
|
||||
glDeleteFramebuffers(1, &fb->present_fbo);
|
||||
fb->present_fbo = fb->texture.CreateAndReturnFramebuffer();
|
||||
}
|
||||
|
||||
if (fb->present_fbo != 0)
|
||||
{
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fb->present_fbo);
|
||||
glBlitFramebuffer(0, 0, fb->texture.GetWidth(), fb->texture.GetHeight(), 0, 0, m_window_info.surface_width,
|
||||
m_window_info.surface_height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
// this will be non-null if we're duplicating frames
|
||||
if (fb->present_sync_id)
|
||||
glDeleteSync(fb->present_sync_id);
|
||||
fb->present_sync_id = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
|
||||
m_frame_presented.store(true);
|
||||
m_present_complete_cv.notify_one();
|
||||
}
|
||||
|
||||
m_present_context->SwapBuffers();
|
||||
}
|
||||
|
||||
m_present_context->DoneCurrent();
|
||||
}
|
||||
|
||||
void OpenGLHostDisplay::StopPresentThread()
|
||||
{
|
||||
m_present_thread_stop.store(true);
|
||||
m_present_thread.join();
|
||||
|
||||
for (PresentFramebuffer& fb : m_present_framebuffers)
|
||||
{
|
||||
if (fb.draw_sync_id)
|
||||
{
|
||||
glClientWaitSync(fb.draw_sync_id, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED);
|
||||
fb.draw_sync_id = {};
|
||||
}
|
||||
|
||||
fb.texture.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool OpenGLHostDisplay::SetPostProcessingChain(const std::string_view& config)
|
||||
|
|
|
@ -14,7 +14,10 @@
|
|||
#include "common/gl/texture.h"
|
||||
#include "common/window_info.h"
|
||||
#include "core/host_display.h"
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#ifndef LIBRETRO
|
||||
#include "postprocessing_chain.h"
|
||||
|
@ -106,6 +109,35 @@ protected:
|
|||
GL::Texture m_post_processing_input_texture;
|
||||
std::unique_ptr<GL::StreamBuffer> m_post_processing_ubo;
|
||||
std::vector<PostProcessingStage> m_post_processing_stages;
|
||||
|
||||
struct PresentFramebuffer
|
||||
{
|
||||
GL::Texture texture;
|
||||
GLuint draw_fbo;
|
||||
GLuint present_fbo;
|
||||
GLsync draw_sync_id;
|
||||
GLsync present_sync_id;
|
||||
bool changed = false;
|
||||
bool ready = false;
|
||||
};
|
||||
|
||||
bool InitializeAsyncPresentation();
|
||||
bool CheckPresentDrawFramebuffer();
|
||||
void PresentDrawFramebuffer();
|
||||
void PresentThread();
|
||||
void StopPresentThread();
|
||||
|
||||
std::unique_ptr<GL::Context> m_present_context;
|
||||
std::array<PresentFramebuffer, 3> m_present_framebuffers{};
|
||||
PresentFramebuffer* m_present_draw_framebuffer = &m_present_framebuffers[0];
|
||||
PresentFramebuffer* m_present_next_present_framebuffer = &m_present_framebuffers[1];
|
||||
PresentFramebuffer* m_present_current_present_framebuffer = &m_present_framebuffers[2];
|
||||
std::thread m_present_thread;
|
||||
std::mutex m_present_lock;
|
||||
std::condition_variable m_present_complete_cv;
|
||||
std::atomic_bool m_frame_presented{false};
|
||||
std::atomic_bool m_present_thread_stop{false};
|
||||
bool m_vsync = true;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue