Skip to content

Instantly share code, notes, and snippets.

@0x1F9F1
Last active October 3, 2021 13:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0x1F9F1/19c422de0ee6d817a7fc94fe82bf5fb6 to your computer and use it in GitHub Desktop.
Save 0x1F9F1/19c422de0ee6d817a7fc94fe82bf5fb6 to your computer and use it in GitHub Desktop.
An example of using WM_PAINT and WM_TIMER with SDL2 to allow updates when moving/resizing
#include <SDL.h>
#include <SDL_opengl.h>
#include <SDL_syswm.h>
#include <Windows.h>
enum class UpdateReason
{
Main,
Paint,
Timer,
};
struct Application
{
bool Running {};
SDL_Window* Window {};
SDL_GLContext GLContext {};
Uint64 LastUpdate {};
float Rotation {};
void Init();
void Shutdown();
void PumpEvents();
void Update(UpdateReason reason);
private:
void ProcessEvents();
void ProcessEvent(SDL_Event& event);
void DrawScene();
static int EventWatcher(void* data, SDL_Event* event);
static void CALLBACK TimerCallback(HWND hWnd, UINT uMsg, UINT_PTR nIDEvent, DWORD time);
} g_App;
void Application::Init()
{
Window = SDL_CreateWindow("Test Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL);
GLContext = SDL_GL_CreateContext(Window);
SDL_GL_MakeCurrent(Window, GLContext);
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
SDL_AddEventWatch(EventWatcher, nullptr);
LastUpdate = SDL_GetPerformanceCounter();
Running = true;
}
void Application::Shutdown()
{
Running = false;
SDL_DelEventWatch(EventWatcher, nullptr);
SDL_GL_DeleteContext(GLContext);
GLContext = nullptr;
SDL_DestroyWindow(Window);
Window = nullptr;
}
void Application::PumpEvents()
{
// Wait for events if the window isn't visbile
for (SDL_Event event;
Running && (SDL_GetWindowFlags(Window) & (SDL_WINDOW_HIDDEN | SDL_WINDOW_MINIMIZED)) && SDL_WaitEvent(&event);)
{
ProcessEvent(event);
}
UINT_PTR timer_id = SetTimer(NULL, 0, USER_TIMER_MINIMUM, TimerCallback);
SDL_PumpEvents();
KillTimer(NULL, timer_id);
}
void Application::Update(UpdateReason reason)
{
ProcessEvents();
Uint64 now = SDL_GetPerformanceCounter();
float elapsed = (now - LastUpdate) / static_cast<float>(SDL_GetPerformanceFrequency());
LastUpdate = now;
// SDL_Log("Update %f", elapsed * 1000.0);
Rotation = SDL_fmodf(Rotation + elapsed * 30.0f, 360.0f);
DrawScene();
// For maximum responsiveness, avoid waiting on vsync if not called from the main loop
SDL_GL_SetSwapInterval((reason == UpdateReason::Main) ? 1 : 0);
SDL_GL_SwapWindow(Window);
}
void Application::ProcessEvents()
{
// Use SDL_PeepEvents to avoid implicitly calling SDL_PumpEvents
SDL_Event events[32];
while (int count = SDL_PeepEvents(events, SDL_TABLESIZE(events), SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT))
{
for (int i = 0; i < count; ++i)
ProcessEvent(events[i]);
}
}
void Application::ProcessEvent(SDL_Event& event)
{
switch (event.type)
{
case SDL_QUIT: Running = false; break;
case SDL_KEYDOWN: {
switch (event.key.keysym.sym)
{
case SDLK_ESCAPE: Running = false; break;
case SDLK_RETURN:
if (event.key.keysym.mod & KMOD_ALT)
SDL_SetWindowFullscreen(Window, ~SDL_GetWindowFlags(Window) & SDL_WINDOW_FULLSCREEN_DESKTOP);
break;
}
break;
}
}
}
void Application::DrawScene()
{
// Draw a simple moving scene
int draw_w = 0;
int draw_h = 0;
SDL_GL_GetDrawableSize(Window, &draw_w, &draw_h);
glViewport(0, 0, draw_w, draw_h);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
float right = (float) (draw_w) / (float) (draw_h);
glFrustum(-right / 2.0, right / 2.0, -1.0, 1.0, 1.0, 40.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0, 0, -4);
glRotatef(50, 1, 0, 0);
glRotatef(Rotation, 0, 1, 0);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(1, 1, 1);
glBegin(GL_LINES);
for (GLfloat i = -2.5; i <= 2.5; i += 0.25)
{
glVertex3f(i, 0, 2.5);
glVertex3f(i, 0, -2.5);
glVertex3f(2.5, 0, i);
glVertex3f(-2.5, 0, i);
}
glEnd();
glBegin(GL_TRIANGLE_FAN);
glColor3f(1, 0, 0);
glVertex3f(0, 2, 0);
glColor3f(0, 1, 0);
glVertex3f(-1, 0, 1);
glColor3f(0, 0, 1);
glVertex3f(1, 0, 1);
glColor3f(0, 1, 1);
glVertex3f(1, 0, -1);
glColor3f(1, 1, 0);
glVertex3f(-1, 0, -1);
glColor3f(0, 1, 0);
glVertex3f(-1, 0, 1);
glEnd();
}
int Application::EventWatcher(void* data, SDL_Event* event)
{
if (event->type == SDL_SYSWMEVENT) // WndProc
{
auto& msg = event->syswm.msg->msg.win;
// Avoid a ~500ms delay when clicking but not dragging the title bar
// https://www.gamedev.net/forums/topic/520860-sizemove-loop-and-delay-in-defwindowproc/
// if (msg.msg == WM_NCLBUTTONDOWN && msg.wParam == HTCAPTION)
if (msg.msg == WM_SYSCOMMAND && msg.wParam == SC_MOVE + HTCAPTION)
{
POINT pt;
POINTSTOPOINT(pt, msg.lParam);
ScreenToClient(msg.hwnd, &pt);
PostMessage(msg.hwnd, WM_MOUSEMOVE, 0, POINTTOPOINTS(pt));
}
// SDL_Log("WndProc: %p 0x%04x 0x%x 0x%x", msg.hwnd, msg.msg, msg.wParam, msg.lParam);
}
if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_EXPOSED)
{
SDL_Log("Paint");
g_App.Update(UpdateReason::Paint);
}
return 0;
}
void Application::TimerCallback(HWND hWnd, UINT uMsg, UINT_PTR nIDEvent, DWORD time)
{
// https://docs.microsoft.com/en-us/windows/win32/gdi/drawing-at-timed-intervals
SDL_Log("Timer");
g_App.Update(UpdateReason::Timer);
}
int main(int argc, char** argv)
{
SDL_Init(SDL_INIT_VIDEO);
g_App.Init();
while (g_App.Running)
{
g_App.PumpEvents();
g_App.Update(UpdateReason::Main);
}
g_App.Shutdown();
SDL_Quit();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment