コード例 #1
0
ファイル: win32_ogl.cpp プロジェクト: Clever-Boy/Ludus
internal void
Win32InitOpenGL(HDC WindowDC)
{
    Win32LoadWGLExtensions();

    HGLRC OpenGLRC       = 0;
    bool32 ModernContext = true;

    if(wglCreateContextAttribsARB)
    {
        int Win32OpenGLAttribs[] = {
            WGL_CONTEXT_MAJOR_VERSION_ARB,
            3,
            WGL_CONTEXT_MINOR_VERSION_ARB,
            1,
            WGL_CONTEXT_FLAGS_ARB,
            WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB
#if LUDUS_INTERNAL
                |
                WGL_CONTEXT_DEBUG_BIT_ARB
#endif
            ,
            WGL_CONTEXT_PROFILE_MASK_ARB,
            WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
            0
        };

        Win32SetPixelFormat(WindowDC);
        OpenGLRC = wglCreateContextAttribsARB(WindowDC, 0, Win32OpenGLAttribs);
    }

    if(!OpenGLRC)
    {
        ModernContext = false;
        OpenGLRC      = wglCreateContext(WindowDC);
    }

    if(wglMakeCurrent(WindowDC, OpenGLRC))
    {
        InitOpenGL(ModernContext);

        if(wglSwapInterval)
        {
            wglSwapInterval(1);
        }
    }
    else
    {
        Win32Log("Couldnt set OpenGL context");
        LOG_FORMATTED_ERROR(4096);
    }
}
コード例 #2
0
ファイル: rvgMain.cpp プロジェクト: Sudoka/RenderBase
int main (int argc, char **argv)
{
  rvgGlutInit( argc, argv );
  rvgGLInit();
  wglSwapInterval( 0 );

  shaderDefault = new Shader();
  shaderDefault->addSource( ShaderType::Vertex, "shaders/default.vert.c" );
  shaderDefault->addSource( ShaderType::Fragment, "shaders/default.frag.c" );
  shaderDefault->load();
  
  glutMainLoop();
}
コード例 #3
0
ファイル: win32window.cpp プロジェクト: Ablankzin/otclient
void WIN32Window::setVerticalSync(bool enable)
{
#ifdef OPENGL_ES
    eglSwapInterval(m_eglDisplay, enable ? 1 : 0);
#else
    if(!isExtensionSupported("WGL_EXT_swap_control"))
        return;

    typedef BOOL (WINAPI * wglSwapIntervalProc)(int);
    wglSwapIntervalProc wglSwapInterval = (wglSwapIntervalProc)getExtensionProcAddress("wglSwapIntervalEXT");
    if(!wglSwapInterval)
        return;

    wglSwapInterval(enable ? 1 : 0);
#endif
}
コード例 #4
0
ファイル: raytracer_win32.cpp プロジェクト: obiwanus/praxis
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine, int nCmdShow) {
  // Init pixel buffer
  // g_pixel_buffer.allocate(&g_program_memory);

  // Create window class
  WNDCLASS WindowClass = {};
  WindowClass.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
  WindowClass.lpfnWndProc = Win32WindowProc;
  WindowClass.hInstance = hInstance;
  WindowClass.lpszClassName = "VMWindowClass";

  if (!RegisterClass(&WindowClass)) {
    // TODO: logging
    printf("Couldn't register window class\n");
    exit(1);
  }

  // Create window so that its client area is exactly kWindowWidth/Height
  DWORD WindowStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
  RECT WindowRect = {};

  const int kWindowWidth = 1024;
  const int kWindowHeight = 768;

  g_pixel_buffer.width = kWindowWidth;
  g_pixel_buffer.height = kWindowHeight;
  g_pixel_buffer.max_width = 3000;
  g_pixel_buffer.max_height = 3000;
  g_pixel_buffer.memory =
      malloc(sizeof(u32) * g_pixel_buffer.width * g_pixel_buffer.height + 100);

  WindowRect.right = kWindowWidth;
  WindowRect.bottom = kWindowHeight;
  AdjustWindowRect(&WindowRect, WindowStyle, 0);
  int WindowWidth = WindowRect.right - WindowRect.left;
  int WindowHeight = WindowRect.bottom - WindowRect.top;
  HWND Window = CreateWindow(WindowClass.lpszClassName, 0, WindowStyle,
                             CW_USEDEFAULT, CW_USEDEFAULT, WindowWidth,
                             WindowHeight, 0, 0, hInstance, 0);

  if (!Window) {
    printf("Couldn't create window\n");
    exit(1);
  }

  // We're not going to release it as we use CS_OWNDC
  HDC hdc = GetDC(Window);

  g_running = true;

  // Set proper buffer values based on actual client size
  Win32ResizeClientWindow(Window);

  printf("%d:%d\n", g_pixel_buffer.width, g_pixel_buffer.height);

  // Init OpenGL
  {
    PIXELFORMATDESCRIPTOR DesiredPixelFormat = {};
    DesiredPixelFormat.nSize = sizeof(DesiredPixelFormat);
    DesiredPixelFormat.nVersion = 1;
    DesiredPixelFormat.iPixelType = PFD_TYPE_RGBA;
    DesiredPixelFormat.dwFlags =
        PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER;
    DesiredPixelFormat.cColorBits = 32;
    DesiredPixelFormat.cAlphaBits = 8;
    DesiredPixelFormat.iLayerType = PFD_MAIN_PLANE;

    int SuggestedPixelFormatIndex = ChoosePixelFormat(hdc, &DesiredPixelFormat);
    PIXELFORMATDESCRIPTOR SuggestedPixelFormat;
    DescribePixelFormat(hdc, SuggestedPixelFormatIndex,
                        sizeof(SuggestedPixelFormat), &SuggestedPixelFormat);

    SetPixelFormat(hdc, SuggestedPixelFormatIndex, &SuggestedPixelFormat);

    HGLRC OpenGLRC = wglCreateContext(hdc);
    if (wglMakeCurrent(hdc, OpenGLRC)) {
      // Success
      glGenTextures(1, &gTextureHandle);

      typedef BOOL WINAPI wgl_swap_interval_ext(int interval);
      wgl_swap_interval_ext *wglSwapInterval =
          (wgl_swap_interval_ext *)wglGetProcAddress("wglSwapIntervalEXT");
      if (wglSwapInterval) {
        wglSwapInterval(1);
      } else {
        // VSync not enabled or not supported
        assert(false);
      }
    } else {
      // Something's wrong
      assert(false);
    }
  }

  // Event loop
  while (g_running) {
    // Process messages
    MSG message;
    while (PeekMessage(&message, 0, 0, 0, PM_REMOVE)) {
      // Get keyboard messages
      switch (message.message) {
        case WM_QUIT: {
          g_running = false;
        } break;

        case WM_SYSKEYDOWN:
        case WM_SYSKEYUP:
        case WM_KEYDOWN:
        case WM_KEYUP: {
          u32 vk_code = (u32)message.wParam;
          b32 was_down = ((message.lParam & (1 << 30)) != 0);
          b32 is_down = ((message.lParam & (1 << 31)) == 0);

          b32 alt_key_was_down = (message.lParam & (1 << 29));
          if ((vk_code == VK_F4) && alt_key_was_down) {
            g_running = false;
          }
          if (was_down == is_down) {
            break;  // nothing has changed
          }
          if (vk_code == VK_ESCAPE) {
            g_running = false;
          }
        } break;

        default: {
          TranslateMessage(&message);
          DispatchMessageA(&message);
        } break;
      }
    }

    update_and_render(&g_pixel_buffer);

    Win32UpdateWindow(hdc);
  }

  return 0;
}
コード例 #5
0
ファイル: CG1Helper.cpp プロジェクト: Sighter/cg1_pong
void DisableVSync(void) {
  wglSwapIntervalFunc wglSwapInterval = (wglSwapIntervalFunc) wglGetProcAddress("wglSwapIntervalEXT");
  if (wglSwapInterval)
    wglSwapInterval(0);
}
コード例 #6
0
MyWindow::MyWindow(const std::string &title,int width,int height)
: initError(false),clientWidth(0),clientHeight(0),iconified(false),focused(true),sized(false),
justCreated(true),mouseMoveCallback(0),inputCallback(0),lockCursor(false),foreground(true)

{
#ifdef WIN32
  hglrc=0;
  hdc=0;

  //
  HINSTANCE hInstance=GetModuleHandle(0);
  WNDCLASSEX wcex;

  wcex.cbSize = sizeof(WNDCLASSEX);
  wcex.style = CS_HREDRAW | CS_VREDRAW;
  wcex.lpfnWndProc = WndProc;
  wcex.cbClsExtra = 0;
  wcex.cbWndExtra = 0;
  wcex.hInstance = hInstance;
  wcex.hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(IDI_APPLICATION));
  wcex.hCursor = LoadCursor(NULL,IDC_ARROW);
  wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
  wcex.lpszMenuName = NULL;
#ifdef UNICODE
  wcex.lpszClassName = L"win32app";
#else
  wcex.lpszClassName = "win32app";
#endif
  wcex.hIconSm = LoadIcon(wcex.hInstance,MAKEINTRESOURCE(IDI_APPLICATION));

  if(!RegisterClassEx(&wcex)) {
    std::cout << "MyWindow : Call to RegisterClassEx failed!\n";
  }

#ifdef UNICODE
  wchar_t title2[256];
  MultiByteToWideChar(CP_ACP,0,title.c_str(),-1,title2,256);
#else
  const char *title2=title.c_str();
#endif

  hWnd = CreateWindow(wcex.lpszClassName,title2,WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT,CW_USEDEFAULT,width,height,NULL,NULL,hInstance,NULL);

  SetWindowLongPtr(hWnd,GWL_USERDATA,(LONG_PTR)this);

  ShowWindow(hWnd,SW_SHOW);


  //
  hdc= GetDC(hWnd);

  PIXELFORMATDESCRIPTOR pfd;
  ZeroMemory(&pfd,sizeof(pfd));

  pfd.nSize = sizeof(pfd);
  pfd.nVersion = 1;
  pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_GENERIC_ACCELERATED | PFD_DOUBLEBUFFER;
  pfd.iPixelType = PFD_TYPE_RGBA;
  pfd.cColorBits = 24;
  pfd.cRedBits = pfd.cGreenBits = pfd.cBlueBits = 8;
  pfd.cDepthBits = 32;

  int iPixelFormat = ChoosePixelFormat(hdc,&pfd);

  if(iPixelFormat == 0) {
    std::cout << "MyWindow : ChoosePixelFormat failed.\n";
    initError=true;
    return;
  }

  if(SetPixelFormat(hdc,iPixelFormat,&pfd) != TRUE) {
    std::cout << "MyWindow : SetPixelFormat failed.\n";
    initError=true;
    return;
  }

  //
  HGLRC tempContext = wglCreateContext(hdc);
  wglMakeCurrent(hdc,tempContext);


  //
  PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB=
    (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");


  PFNWGLSWAPINTERVALEXTPROC wglSwapInterval=
    (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
  //
  wglMakeCurrent(0,0);
  wglDeleteContext(tempContext);


  //
  int attribs[] ={
    WGL_CONTEXT_MAJOR_VERSION_ARB,3,
    WGL_CONTEXT_MINOR_VERSION_ARB,3,
    WGL_CONTEXT_FLAGS_ARB,0,
    0
  };

  hglrc=wglCreateContextAttribsARB(hdc,0,attribs);
  for(int i=2;i>=0;i--) {
    if(!hglrc) {
      attribs[3]=i;
      hglrc=wglCreateContextAttribsARB(hdc,0,attribs);
    }
  }
  if(!hglrc) {
    std::cout << "OpenGL 3+ not supported.\n";
    initError=true;
    return;
  }

  wglMakeCurrent(hdc,hglrc);
  wglSwapInterval(1);

  //

  RAWINPUTDEVICE Rid[2];
  Rid[0].usUsagePage = (USHORT)0x01;//HID_USAGE_PAGE_GENERIC;
  Rid[0].usUsage = (USHORT)0x02;//HID_USAGE_GENERIC_MOUSE;
  Rid[0].dwFlags = RIDEV_INPUTSINK;
  Rid[0].hwndTarget = hWnd;

  Rid[1].usUsagePage = (USHORT)0x01;//HID_USAGE_PAGE_GENERIC;
  Rid[1].usUsage = (USHORT)0x06;//HID_USAGE_GENERIC_KEYBOARD;
  Rid[1].dwFlags = RIDEV_INPUTSINK;
  Rid[1].hwndTarget = hWnd;

  RegisterRawInputDevices(Rid,2,sizeof(RAWINPUTDEVICE));

  //
  //inputCodeMap[65]=keyA;
  //inputCodeMap[68]=keyD;
  //inputCodeMap[83]=keyS;
  //inputCodeMap[87]=keyW;
#endif

#ifdef LINUX
  // bool ctxErrorOccurred=false;
  display = XOpenDisplay(NULL);

  if(!display) {
    std::cout << "Window : Failed to open X display.\n";
    initError=true;
    return;
  }

  static int visual_attribs[] ={
    GLX_X_RENDERABLE,True,
    GLX_DRAWABLE_TYPE,GLX_WINDOW_BIT,
    GLX_RENDER_TYPE,GLX_RGBA_BIT,
    GLX_X_VISUAL_TYPE,GLX_TRUE_COLOR,
    GLX_RED_SIZE,8,
    GLX_GREEN_SIZE,8,
    GLX_BLUE_SIZE,8,
    GLX_ALPHA_SIZE,8,
    GLX_DEPTH_SIZE,24,
    GLX_STENCIL_SIZE,8,
    GLX_DOUBLEBUFFER,True,
    //GLX_SAMPLE_BUFFERS  , 1,
    //GLX_SAMPLES         , 4,
    None
  };

  int glx_major,glx_minor;

  if(!glXQueryVersion(display,&glx_major,&glx_minor) ||
    ((glx_major == 1) && (glx_minor < 3)) || (glx_major < 1)) {
    std::cout << "Window : Invalid GLX version.\n";
    initError=true;
    return;
  }

  int fbcount;
  GLXFBConfig* fbc = glXChooseFBConfig(display,DefaultScreen(display),visual_attribs,&fbcount);

  if(!fbc) {
    std::cout << "Window :Failed to retrieve a framebuffer config.\n";
    initError=true;
    return;
  }

  int best_fbc = -1,worst_fbc = -1,best_num_samp = -1,worst_num_samp = 999;

  for(int i=0; i<fbcount; ++i) {
    XVisualInfo *vi = glXGetVisualFromFBConfig(display,fbc[i]);
    if(vi) {
      int samp_buf,samples;
      glXGetFBConfigAttrib(display,fbc[i],GLX_SAMPLE_BUFFERS,&samp_buf);
      glXGetFBConfigAttrib(display,fbc[i],GLX_SAMPLES,&samples);
      std::cout << "Matching fbconfig " << i
        <<", visual ID 0x" << vi->visualid
        << ": SAMPLE_BUFFERS = " << samp_buf
        <<", SAMPLES = " << samples
        <<"\n";


      if(best_fbc < 0 || samp_buf && samples > best_num_samp) {
        best_fbc = i;
        best_num_samp = samples;
      }

      if(worst_fbc < 0 || !samp_buf || samples < worst_num_samp) {
        worst_fbc = i;
        worst_num_samp = samples;
      }
    }

    XFree(vi);
  }

  GLXFBConfig bestFbc = fbc[best_fbc];
  XFree(fbc);

  XVisualInfo *vi = glXGetVisualFromFBConfig(display,bestFbc);
  std::cout << "Chosen visual ID = 0x" << vi->visualid <<"\n";

  XSetWindowAttributes swa;

  swa.colormap = cmap = XCreateColormap(display,
    RootWindow(display,vi->screen),
    vi->visual,AllocNone);
  swa.background_pixmap = None;
  swa.border_pixel      = 0;
  swa.event_mask        = ExposureMask | VisibilityChangeMask |KeyPressMask | PointerMotionMask    |StructureNotifyMask;


  swa.bit_gravity = StaticGravity;

  win = XCreateWindow(display,RootWindow(display,vi->screen),
    0,0,100,100,0,vi->depth,InputOutput,
    vi->visual,
    CWBorderPixel|CWColormap|CWEventMask,&swa);
  if(!win) {
    std::cout << "Window : Failed to create window.\n";
    initError=true;
    return;
  }

  XFree(vi);
  XStoreName(display,win,title.c_str());
  XMapWindow(display,win);

  const char *glxExts = glXQueryExtensionsString(display,DefaultScreen(display));

  glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0;
  glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)
    glXGetProcAddressARB((const GLubyte *) "glXCreateContextAttribsARB");

  ctx = 0;

  ctxErrorOccurred = false;
  int(*oldHandler)(Display*,XErrorEvent*) =
    XSetErrorHandler(&ctxErrorHandler);

  if(!isExtensionSupported(glxExts,"GLX_ARB_create_context") ||
    !glXCreateContextAttribsARB) {
    std::cout << "Window : glXCreateContextAttribsARB() not found.\n";
    initError=true;
    return;
  } else {
    int context_attribs[] ={
      GLX_CONTEXT_MAJOR_VERSION_ARB,3,
      GLX_CONTEXT_MINOR_VERSION_ARB,0,
      GLX_CONTEXT_FLAGS_ARB,0,
      None
    };

    ctx = glXCreateContextAttribsARB(display,bestFbc,0,
      True,context_attribs);

    XSync(display,False);

    if(!ctxErrorOccurred && ctx) {
      std::cout << "Created GL 3.0 context\n";
    } else {
      std::cout << "Window : Failed to create GL 3.0 context.\n";
      initError=true;
      return;
    }
  }
  //
  XSync(display,False);
  XSetErrorHandler(oldHandler);

  if(ctxErrorOccurred || !ctx) {
    std::cout << "Window : Failed to create an OpenGL context.\n";
    initError=true;
    return;
  }

  // Verifying that context is a direct context
  if(!glXIsDirect(display,ctx)) {
    std::cout << "Indirect GLX rendering context obtained.\n";
  } else {
    std::cout << "Direct GLX rendering context obtained.\n";
  }

  //
  glXMakeCurrent(display,win,ctx);

  if(PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA=
    (PFNGLXSWAPINTERVALMESAPROC)
    glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalMESA")) {
    glXSwapIntervalMESA(1);
  } else if(PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT=
    (PFNGLXSWAPINTERVALEXTPROC)
    glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalEXT")) {
    glXSwapIntervalEXT(display,glXGetCurrentDrawable(),1);
  } else if(PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI=
    (PFNGLXSWAPINTERVALSGIPROC)
    glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalSGI")) {
    glXSwapIntervalSGI(1);
  }
#endif


}
コード例 #7
0
ファイル: ED_win32.cpp プロジェクト: obiwanus/editor
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine, int nCmdShow) {
  // Allocate program memory
  g_program_memory.start = malloc(MAX_INTERNAL_MEMORY_SIZE);
  g_program_memory.free_memory = g_program_memory.start;
  // TODO: add checks for overflow when allocating

  // Main program state
  Program_State *state =
      (Program_State *)g_program_memory.allocate(sizeof(Program_State));
  state->init(&g_program_memory, &g_pixel_buffer,
              (Raytrace_Work_Queue *)&g_raytrace_queue);

  // Create window class
  WNDCLASS WindowClass = {};
  WindowClass.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
  WindowClass.lpfnWndProc = Win32WindowProc;
  WindowClass.hInstance = hInstance;
  WindowClass.lpszClassName = "VMWindowClass";

  // Set target sleep resolution
  {
    TIMECAPS tc;
    UINT wTimerRes;

    if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR) {
      OutputDebugStringA("Cannot set the sleep resolution\n");
      exit(1);
    }

    wTimerRes = min(max(tc.wPeriodMin, 1), tc.wPeriodMax);  // 1 ms
    timeBeginPeriod(wTimerRes);
  }

  QueryPerformanceFrequency(&gPerformanceFrequency);

  if (!RegisterClass(&WindowClass)) {
    // TODO: logging
    printf("Couldn't register window class\n");
    exit(1);
  }

  // Create window so that its client area is exactly kWindowWidth/Height
  DWORD WindowStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
  RECT WindowRect = {};

  WindowRect.right = state->kWindowWidth;
  WindowRect.bottom = state->kWindowHeight;
  AdjustWindowRect(&WindowRect, WindowStyle, 0);
  int WindowWidth = WindowRect.right - WindowRect.left;
  int WindowHeight = WindowRect.bottom - WindowRect.top;
  HWND Window = CreateWindow(WindowClass.lpszClassName, 0, WindowStyle,
                             CW_USEDEFAULT, CW_USEDEFAULT, WindowWidth,
                             WindowHeight, 0, 0, hInstance, 0);

  if (!Window) {
    printf("Couldn't create window\n");
    exit(1);
  }

  // We're not going to release it as we use CS_OWNDC
  HDC hdc = GetDC(Window);

  g_running = true;

  // Set proper buffer values based on actual client size
  Win32ResizeClientWindow(Window);

  // Init OpenGL
  {
    PIXELFORMATDESCRIPTOR DesiredPixelFormat = {};
    DesiredPixelFormat.nSize = sizeof(DesiredPixelFormat);
    DesiredPixelFormat.nVersion = 1;
    DesiredPixelFormat.iPixelType = PFD_TYPE_RGBA;
    DesiredPixelFormat.dwFlags =
        PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER;
    DesiredPixelFormat.cColorBits = 32;
    DesiredPixelFormat.cAlphaBits = 8;
    DesiredPixelFormat.iLayerType = PFD_MAIN_PLANE;

    int SuggestedPixelFormatIndex = ChoosePixelFormat(hdc, &DesiredPixelFormat);
    PIXELFORMATDESCRIPTOR SuggestedPixelFormat;
    DescribePixelFormat(hdc, SuggestedPixelFormatIndex,
                        sizeof(SuggestedPixelFormat), &SuggestedPixelFormat);

    SetPixelFormat(hdc, SuggestedPixelFormatIndex, &SuggestedPixelFormat);

    HGLRC OpenGLRC = wglCreateContext(hdc);
    if (wglMakeCurrent(hdc, OpenGLRC)) {
      // Success
      glGenTextures(1, &gTextureHandle);

      typedef BOOL WINAPI wgl_swap_interval_ext(int interval);
      wgl_swap_interval_ext *wglSwapInterval =
          (wgl_swap_interval_ext *)wglGetProcAddress("wglSwapIntervalEXT");
      if (wglSwapInterval) {
        wglSwapInterval(1);
      } else {
        // VSync not enabled or not supported
        assert(false);
      }
    } else {
      // Something's wrong
      assert(false);
    }
  }

  Cursor_Type current_cursor = Cursor_Type_Arrow;
  HCURSOR win_cursors[Cursor_Type__COUNT];
  win_cursors[Cursor_Type_Arrow] = LoadCursor(NULL, IDC_ARROW);
  win_cursors[Cursor_Type_Cross] = LoadCursor(NULL, IDC_CROSS);
  win_cursors[Cursor_Type_Hand] = LoadCursor(NULL, IDC_HAND);
  win_cursors[Cursor_Type_Resize_Vert] = LoadCursor(NULL, IDC_SIZENS);
  win_cursors[Cursor_Type_Resize_Horiz] = LoadCursor(NULL, IDC_SIZEWE);

  User_Input inputs[2];
  User_Input *old_input = &inputs[0];
  User_Input *new_input = &inputs[1];
  *new_input = {};

  LARGE_INTEGER last_timestamp = Win32GetWallClock();

  // Create worker threads
  {
    const int kNumThreads = 4;

    // Init work queue
    g_raytrace_queue.next_entry_to_add = 0;
    g_raytrace_queue.next_entry_to_do = 0;
    u32 initial_num_threads = 0;
    g_raytrace_queue.semaphore = CreateSemaphoreEx(
        0, initial_num_threads, kNumThreads, 0, 0, SEMAPHORE_ALL_ACCESS);

    Thread_Info threads[kNumThreads];
    for (int i = 0; i < kNumThreads; i++) {
      threads[i].thread_num = i + 1;
      HANDLE thread_handle = CreateThread(
          0,                     // LPSECURITY_ATTRIBUTES lpThreadAttributes,
          0,                     // SIZE_T dwStackSize,
          RaytraceWorkerThread,  // LPTHREAD_START_ROUTINE lpStartAddress,
          &threads[i],           // LPVOID lpParameter,
          0,                     // DWORD dwCreationFlags,
          NULL                   // LPDWORD lpThreadId
          );
      threads[i].thread_handle = thread_handle;
      if (thread_handle == NULL) {
        printf("CreateThread error: %d\n", GetLastError());
        exit(1);
      }
    }
  }

  // Event loop
  while (g_running) {
    // Process messages
    MSG message;
    while (PeekMessage(&message, 0, 0, 0, PM_REMOVE)) {
      // Get keyboard messages
      switch (message.message) {
        case WM_QUIT: {
          g_running = false;
        } break;

        case WM_SYSKEYDOWN:
        case WM_SYSKEYUP:
        case WM_KEYDOWN:
        case WM_KEYUP: {
          u32 vk_code = (u32)message.wParam;
          bool was_down = ((message.lParam & (1 << 30)) != 0);
          bool is_down = ((message.lParam & (1 << 31)) == 0);

          bool alt_key_was_down = (message.lParam & (1 << 29)) != 0;
          if ((vk_code == VK_F4) && alt_key_was_down) {
            g_running = false;
          }
          if (was_down == is_down) {
            break;  // nothing has changed
          }
          if (vk_code == VK_ESCAPE) {
            new_input->buttons[IB_escape] = is_down;
          }
          if (vk_code == VK_UP) {
            new_input->buttons[IB_up] = is_down;
          }
          if (vk_code == VK_DOWN) {
            new_input->buttons[IB_down] = is_down;
          }
          if (vk_code == VK_LEFT) {
            new_input->buttons[IB_left] = is_down;
          }
          if (vk_code == VK_RIGHT) {
            new_input->buttons[IB_right] = is_down;
          }
          if (vk_code == VK_SHIFT) {
            new_input->buttons[IB_shift] = is_down;
          }

          // Handle symbols
          int symbol = (int)vk_code;
          if (vk_code == VK_NUMPAD0)
            symbol = '0';
          else if (vk_code == VK_NUMPAD1)
            symbol = '1';
          else if (vk_code == VK_NUMPAD2)
            symbol = '2';
          else if (vk_code == VK_NUMPAD3)
            symbol = '3';
          else if (vk_code == VK_NUMPAD4)
            symbol = '4';
          else if (vk_code == VK_NUMPAD5)
            symbol = '5';
          else if (vk_code == VK_NUMPAD6)
            symbol = '6';
          else if (vk_code == VK_NUMPAD7)
            symbol = '7';
          else if (vk_code == VK_NUMPAD8)
            symbol = '8';
          else if (vk_code == VK_NUMPAD9)
            symbol = '9';

          if (('A' <= symbol && symbol <= 'Z') ||
              ('0' <= symbol && symbol <= '9')) {
            new_input->buttons[IB_key] = is_down;
            new_input->symbol = symbol;
          }
        } break;

        case WM_MOUSEWHEEL: {
          int delta = (int)message.wParam / (65536 * WHEEL_DELTA);
          new_input->scroll += delta;
        } break;

        default: {
          TranslateMessage(&message);
          DispatchMessageA(&message);
        } break;
      }
    }

    // Get mouse input
    {
      POINT mouse_pointer;
      GetCursorPos(&mouse_pointer);
      ScreenToClient(Window, &mouse_pointer);

      new_input->mouse = {mouse_pointer.x, mouse_pointer.y};

      new_input->buttons[IB_mouse_left] =
          (GetKeyState(VK_LBUTTON) & (1 << 15)) != 0;
      new_input->buttons[IB_mouse_right] =
          (GetKeyState(VK_RBUTTON) & (1 << 15)) != 0;
      new_input->buttons[IB_mouse_middle] =
          (GetKeyState(VK_MBUTTON) & (1 << 15)) != 0;
    }

    Update_Result result =
        update_and_render(&g_program_memory, state, new_input);

#include "debug/ED_debug_draw.cpp"

    assert(0 <= result.cursor && result.cursor < Cursor_Type__COUNT);
    SetCursor(win_cursors[result.cursor]);

    Win32UpdateWindow(hdc);

    // Swap inputs
    User_Input *tmp = old_input;
    old_input = new_input;
    new_input = tmp;

    // Zero input
    *new_input = {};
    new_input->old = old_input;  // Save so we can refer to it later

    // Retain the button state
    for (size_t i = 0; i < COUNT_OF(new_input->buttons); i++) {
      new_input->buttons[i] = old_input->buttons[i];
    }
    for (size_t i = 0; i < COUNT_OF(new_input->mouse_positions); i++) {
      new_input->mouse_positions[i] = old_input->mouse_positions[i];
    }
    new_input->symbol = old_input->symbol;

    r32 ms_elapsed =
        Win32GetMillisecondsElapsed(last_timestamp, Win32GetWallClock());
    g_FPS.value = (int)(1000.0f / ms_elapsed);
    last_timestamp = Win32GetWallClock();
  }

  return 0;
}