mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2025-10-30 08:01:01 +00:00
786 lines
29 KiB
C++
786 lines
29 KiB
C++
#ifdef WAPI_DXGI
|
|
|
|
#include <stdint.h>
|
|
#include <math.h>
|
|
|
|
#include <map>
|
|
#include <set>
|
|
#include <string>
|
|
|
|
#include <windows.h>
|
|
#include <wrl/client.h>
|
|
#include <dxgi1_3.h>
|
|
#include <versionhelpers.h>
|
|
|
|
#include <shellscalingapi.h>
|
|
|
|
|
|
#ifndef _LANGUAGE_C
|
|
#define _LANGUAGE_C
|
|
#endif
|
|
#include <PR/gbi.h>
|
|
|
|
#define DECLARE_GFX_DXGI_FUNCTIONS
|
|
#include "gfx_dxgi.h"
|
|
|
|
extern "C" {
|
|
#include "pc/mods/mod_import.h"
|
|
#include "pc/rom_checker.h"
|
|
#include "pc/network/version.h"
|
|
#include "pc/configfile.h"
|
|
}
|
|
|
|
#include "pc/pc_main.h"
|
|
|
|
#include "gfx_window_manager_api.h"
|
|
#include "gfx_rendering_api.h"
|
|
#include "gfx_direct3d_common.h"
|
|
#include "gfx_screen_config.h"
|
|
#include "gfx_pc.h"
|
|
|
|
#define WINCLASS_NAME L"N64GAME"
|
|
|
|
#ifdef VERSION_EU
|
|
#define FRAME_INTERVAL_US_NUMERATOR 40000
|
|
#define FRAME_INTERVAL_US_DENOMINATOR 2
|
|
#else
|
|
#define FRAME_INTERVAL_US_NUMERATOR 100000
|
|
#define FRAME_INTERVAL_US_DENOMINATOR 6
|
|
#endif
|
|
|
|
// TODO: figure out if this shit even works
|
|
#ifdef VERSION_EU
|
|
# define FRAMERATE 25
|
|
#else
|
|
# define FRAMERATE 30
|
|
#endif
|
|
// time between consequtive game frames
|
|
static const f64 sFrameTime = 1.0 / ((double)FRAMERATE);
|
|
static f64 sFrameTargetTime = 0;
|
|
extern "C" f64 clock_elapsed_f64(void);
|
|
|
|
using namespace Microsoft::WRL; // For ComPtr
|
|
|
|
static bool inTextInput = false;
|
|
|
|
static struct {
|
|
HWND h_wnd;
|
|
bool showing_error;
|
|
uint32_t current_width, current_height;
|
|
std::string window_title;
|
|
|
|
HMODULE dxgi_module;
|
|
HRESULT (__stdcall *CreateDXGIFactory1)(REFIID riid, void **factory);
|
|
HRESULT (__stdcall *CreateDXGIFactory2)(UINT flags, REFIID iid, void **factory);
|
|
|
|
bool process_dpi_awareness_done;
|
|
|
|
RECT last_window_rect;
|
|
bool is_full_screen, last_maximized_state;
|
|
|
|
ComPtr<IDXGIFactory2> factory;
|
|
ComPtr<IDXGISwapChain1> swap_chain;
|
|
HANDLE waitable_object;
|
|
uint64_t qpc_init, qpc_freq;
|
|
uint64_t frame_timestamp; // in units of 1/FRAME_INTERVAL_US_DENOMINATOR microseconds
|
|
std::map<UINT, DXGI_FRAME_STATISTICS> frame_stats;
|
|
std::set<std::pair<UINT, UINT>> pending_frame_stats;
|
|
bool dropped_frame;
|
|
bool sync_interval_means_frames_to_wait;
|
|
UINT length_in_vsync_frames;
|
|
|
|
bool (*on_key_down)(int scancode);
|
|
bool (*on_key_up)(int scancode);
|
|
void (*on_all_keys_up)(void);
|
|
void (*on_text_input)(char*);
|
|
} dxgi;
|
|
|
|
static void load_dxgi_library(void) {
|
|
dxgi.dxgi_module = LoadLibraryW(L"dxgi.dll");
|
|
*(FARPROC *)&dxgi.CreateDXGIFactory1 = GetProcAddress(dxgi.dxgi_module, "CreateDXGIFactory1");
|
|
*(FARPROC *)&dxgi.CreateDXGIFactory2 = GetProcAddress(dxgi.dxgi_module, "CreateDXGIFactory2");
|
|
}
|
|
|
|
template <typename Fun>
|
|
static void run_as_dpi_aware(Fun f) {
|
|
// Make sure Windows 8.1 or newer doesn't upscale/downscale the rendered images.
|
|
// This is an issue on Windows 8.1 and newer where moving around the window
|
|
// between different monitors having different scaling settings will
|
|
// by default result in the DirectX image will also be scaled accordingly.
|
|
// The resulting scale factor is the curent monitor's scale factor divided by
|
|
// the initial monitor's scale factor. Setting per-monitor aware disables scaling.
|
|
|
|
// On Windows 10 1607 and later, that is solved by setting the awarenenss per window,
|
|
// which is done by using SetThreadDpiAwarenessContext before and after creating
|
|
// any window. When the message handler runs, the corresponding context also applies.
|
|
|
|
// From windef.h, missing in MinGW.
|
|
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
|
|
#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
|
|
#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
|
|
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
|
|
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
|
|
#define DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED ((DPI_AWARENESS_CONTEXT)-5)
|
|
|
|
DPI_AWARENESS_CONTEXT (WINAPI *SetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT dpiContext);
|
|
*(FARPROC *)&SetThreadDpiAwarenessContext = GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetThreadDpiAwarenessContext");
|
|
DPI_AWARENESS_CONTEXT old_awareness_context;
|
|
if (SetThreadDpiAwarenessContext != nullptr) {
|
|
old_awareness_context = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
|
} else {
|
|
// Solution for Windows 8.1 and newer, but before Windows 10 1607.
|
|
// SetProcessDpiAwareness must be called before any drawing related API is called.
|
|
if (!dxgi.process_dpi_awareness_done) {
|
|
HMODULE shcore_module = LoadLibraryW(L"SHCore.dll");
|
|
if (shcore_module != nullptr) {
|
|
HRESULT (WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS value);
|
|
*(FARPROC *)&SetProcessDpiAwareness = GetProcAddress(shcore_module, "SetProcessDpiAwareness");
|
|
if (SetProcessDpiAwareness != nullptr) {
|
|
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
|
// Ignore result, will fail if already called or manifest already specifies dpi awareness.
|
|
}
|
|
FreeLibrary(shcore_module);
|
|
}
|
|
dxgi.process_dpi_awareness_done = true;
|
|
}
|
|
}
|
|
|
|
f();
|
|
|
|
// Restore the old context
|
|
if (SetThreadDpiAwarenessContext != nullptr && old_awareness_context != nullptr) {
|
|
SetThreadDpiAwarenessContext(old_awareness_context);
|
|
}
|
|
}
|
|
|
|
static void toggle_borderless_window_full_screen(bool enable) {
|
|
// Windows 7 + flip mode + waitable object can't go to exclusive fullscreen,
|
|
// so do borderless instead. If DWM is enabled, this means we get one monitor
|
|
// sync interval of latency extra. On Win 10 however (maybe Win 8 too), due to
|
|
// "fullscreen optimizations" the latency is eliminated.
|
|
|
|
if (enable == dxgi.is_full_screen) {
|
|
return;
|
|
}
|
|
|
|
if (!enable) {
|
|
RECT r = dxgi.last_window_rect;
|
|
|
|
// Set in window mode with the last saved position and size
|
|
SetWindowLongPtr(dxgi.h_wnd, GWL_STYLE, WS_VISIBLE | WS_OVERLAPPEDWINDOW);
|
|
|
|
if (dxgi.last_maximized_state) {
|
|
SetWindowPos(dxgi.h_wnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
|
|
ShowWindow(dxgi.h_wnd, SW_MAXIMIZE);
|
|
} else {
|
|
SetWindowPos(dxgi.h_wnd, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_FRAMECHANGED);
|
|
ShowWindow(dxgi.h_wnd, SW_RESTORE);
|
|
}
|
|
|
|
dxgi.is_full_screen = false;
|
|
} else {
|
|
// Save if window is maximized or not
|
|
WINDOWPLACEMENT window_placement;
|
|
window_placement.length = sizeof(WINDOWPLACEMENT);
|
|
GetWindowPlacement(dxgi.h_wnd, &window_placement);
|
|
dxgi.last_maximized_state = window_placement.showCmd == SW_SHOWMAXIMIZED;
|
|
|
|
// Save window position and size if the window is not maximized
|
|
GetWindowRect(dxgi.h_wnd, &dxgi.last_window_rect);
|
|
configWindow.x = dxgi.last_window_rect.left;
|
|
configWindow.y = dxgi.last_window_rect.top;
|
|
configWindow.w = dxgi.last_window_rect.right - dxgi.last_window_rect.left;
|
|
configWindow.h = dxgi.last_window_rect.bottom - dxgi.last_window_rect.top;
|
|
|
|
// Get in which monitor the window is
|
|
HMONITOR h_monitor = MonitorFromWindow(dxgi.h_wnd, MONITOR_DEFAULTTONEAREST);
|
|
|
|
// Get info from that monitor
|
|
MONITORINFOEX monitor_info;
|
|
monitor_info.cbSize = sizeof(MONITORINFOEX);
|
|
GetMonitorInfo(h_monitor, &monitor_info);
|
|
RECT r = monitor_info.rcMonitor;
|
|
|
|
// Set borderless full screen to that monitor
|
|
SetWindowLongPtr(dxgi.h_wnd, GWL_STYLE, WS_VISIBLE | WS_POPUP);
|
|
SetWindowPos(dxgi.h_wnd, HWND_TOP, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_FRAMECHANGED);
|
|
|
|
dxgi.is_full_screen = true;
|
|
}
|
|
}
|
|
|
|
static void update_screen_settings(void) {
|
|
if (configWindow.fullscreen != dxgi.is_full_screen)
|
|
toggle_borderless_window_full_screen(configWindow.fullscreen);
|
|
if (!dxgi.is_full_screen) {
|
|
// this code is buggy, and I just simply don't care enough about direct x to fix it
|
|
// when this is enabled, the window will be placed in the wrong spot...
|
|
const int screen_width = GetSystemMetrics(SM_CXSCREEN);
|
|
const int screen_height = GetSystemMetrics(SM_CYSCREEN);
|
|
|
|
const int xpos = (configWindow.x == WAPI_WIN_CENTERPOS) ? (screen_width - configWindow.w) * 0.5 : configWindow.x;
|
|
const int ypos = (configWindow.y == WAPI_WIN_CENTERPOS) ? (screen_height - configWindow.h) * 0.5 : configWindow.y;
|
|
RECT wr = { xpos, ypos, xpos + (int)configWindow.w, ypos + (int)configWindow.h };
|
|
AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);
|
|
SetWindowPos(dxgi.h_wnd, NULL, wr.left, wr.top, wr.right - wr.left, wr.bottom - wr.top, SWP_NOACTIVATE | SWP_NOZORDER);
|
|
}
|
|
}
|
|
|
|
static void gfx_dxgi_on_resize(void) {
|
|
if (dxgi.swap_chain.Get() != nullptr) {
|
|
gfx_get_current_rendering_api()->on_resize();
|
|
|
|
DXGI_SWAP_CHAIN_DESC1 desc1;
|
|
ThrowIfFailed(dxgi.swap_chain->GetDesc1(&desc1));
|
|
dxgi.current_width = desc1.Width;
|
|
dxgi.current_height = desc1.Height;
|
|
if (!dxgi.is_full_screen) {
|
|
configWindow.w = dxgi.current_width;
|
|
configWindow.h = dxgi.current_height;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void gfx_dxgi_on_key_down(WPARAM w_param, LPARAM l_param) {
|
|
int key = ((l_param >> 16) & 0x1ff);
|
|
if (dxgi.on_key_down != nullptr) { dxgi.on_key_down(key); }
|
|
}
|
|
static void gfx_dxgi_on_key_up(WPARAM w_param, LPARAM l_param) {
|
|
int key = ((l_param >> 16) & 0x1ff);
|
|
if (dxgi.on_key_up != nullptr) { dxgi.on_key_up(key); }
|
|
}
|
|
|
|
static void gfx_dxgi_on_text_input(wchar_t code_unit) {
|
|
if (inTextInput && (!IS_HIGH_SURROGATE(code_unit)) && (!IS_LOW_SURROGATE(code_unit))) {
|
|
char utf8_buffer[3 + 1];
|
|
if (code_unit >= 0x0800) { // 3-byte encoding
|
|
utf8_buffer[0] = 0xe0 | ((code_unit >> 12) & 0x0f);
|
|
utf8_buffer[1] = 0x80 | ((code_unit >> 6) & 0x3f);
|
|
utf8_buffer[2] = 0x80 | (code_unit & 0x3f);
|
|
utf8_buffer[3] = '\0';
|
|
} else if (code_unit >= 0x0080) { // 2-byte encoding
|
|
utf8_buffer[0] = 0xc0 | ((code_unit >> 6) & 0x1f);
|
|
utf8_buffer[1] = 0x80 | (code_unit & 0x3f);
|
|
utf8_buffer[2] = '\0';
|
|
} else { // 1-byte encoding
|
|
if (code_unit < ' ') { return; } // skipping control chars
|
|
utf8_buffer[0] = (char)code_unit;
|
|
utf8_buffer[1] = '\0';
|
|
}
|
|
|
|
dxgi.on_text_input(utf8_buffer);
|
|
}
|
|
}
|
|
|
|
static LRESULT CALLBACK gfx_dxgi_wnd_proc(HWND h_wnd, UINT message, WPARAM w_param, LPARAM l_param) {
|
|
WCHAR wcsFileName[MAX_PATH];
|
|
char szFileName[MAX_PATH];
|
|
|
|
switch (message) {
|
|
case WM_SIZE: {
|
|
gfx_dxgi_on_resize();
|
|
return 0;
|
|
}
|
|
case WM_CLOSE: {
|
|
DestroyWindow(h_wnd);
|
|
return 0;
|
|
}
|
|
case WM_DESTROY: {
|
|
game_exit();
|
|
PostQuitMessage(0);
|
|
return 0;
|
|
}
|
|
case WM_ACTIVATEAPP: {
|
|
if (dxgi.on_all_keys_up != nullptr) {
|
|
dxgi.on_all_keys_up();
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
case WM_KEYDOWN: {
|
|
gfx_dxgi_on_key_down(w_param, l_param);
|
|
return 0;
|
|
}
|
|
case WM_KEYUP: {
|
|
gfx_dxgi_on_key_up(w_param, l_param);
|
|
return 0;
|
|
}
|
|
case WM_CHAR: {
|
|
// some keyboard input translated to a single UTF-16LE code unit
|
|
gfx_dxgi_on_text_input((wchar_t)w_param);
|
|
return 0;
|
|
}
|
|
case WM_SYSKEYDOWN: {
|
|
if ((w_param == VK_RETURN) && ((l_param & 1 << 30) == 0)) {
|
|
toggle_borderless_window_full_screen(!dxgi.is_full_screen);
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
case WM_LBUTTONDOWN: {
|
|
if (!gRomIsValid) {
|
|
OPENFILENAMEW ofn;
|
|
ZeroMemory(&ofn, sizeof(ofn));
|
|
ofn.lStructSize = sizeof(ofn);
|
|
ofn.hwndOwner = h_wnd;
|
|
ofn.lpstrFilter = L"N64 ROM files (*.z64)\0*.z64\0";
|
|
ofn.lpstrFile = wcsFileName;
|
|
ofn.nMaxFile = MAX_PATH;
|
|
ofn.lpstrTitle = L"Select the \"Super Mario 64 (U) [!]\" ROM file..";
|
|
ofn.Flags = (OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST);
|
|
|
|
wcsFileName[0] = L'\0';
|
|
if (GetOpenFileNameW(&ofn) && sys_windows_short_path_from_wcs(szFileName, MAX_PATH, wcsFileName)) {
|
|
rom_on_drop_file(szFileName);
|
|
}
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
case WM_DROPFILES: {
|
|
HDROP hDrop = (HDROP)w_param;
|
|
UINT nFiles = DragQueryFileW(hDrop, 0xFFFFFFFF, NULL, 0);
|
|
for (UINT i = 0; i < nFiles; i++) {
|
|
if (0 != DragQueryFileW(hDrop, i, wcsFileName, MAX_PATH)) {
|
|
if (sys_windows_short_path_from_wcs(szFileName, MAX_PATH, wcsFileName)) {
|
|
if (!gRomIsValid) {
|
|
rom_on_drop_file(szFileName);
|
|
} else if (gGameInited) {
|
|
mod_import_file(szFileName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
DragFinish(hDrop);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return DefWindowProcW(h_wnd, message, w_param, l_param);
|
|
}
|
|
|
|
static void gfx_dxgi_init(const char *window_title) {
|
|
LARGE_INTEGER qpc_init, qpc_freq;
|
|
QueryPerformanceCounter(&qpc_init);
|
|
QueryPerformanceFrequency(&qpc_freq);
|
|
dxgi.qpc_init = qpc_init.QuadPart;
|
|
dxgi.qpc_freq = qpc_freq.QuadPart;
|
|
|
|
// Prepare window title
|
|
|
|
wchar_t w_title[512];
|
|
int len = strlen(window_title);
|
|
mbstowcs(w_title, window_title, len + 1);
|
|
dxgi.window_title = window_title;
|
|
|
|
// Create window
|
|
WNDCLASSEXW wcex;
|
|
|
|
wcex.cbSize = sizeof(WNDCLASSEX);
|
|
|
|
wcex.style = CS_HREDRAW | CS_VREDRAW;
|
|
wcex.lpfnWndProc = gfx_dxgi_wnd_proc;
|
|
wcex.cbClsExtra = 0;
|
|
wcex.cbWndExtra = 0;
|
|
wcex.hInstance = nullptr;
|
|
wcex.hIcon = nullptr;
|
|
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
|
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
|
wcex.lpszMenuName = nullptr;
|
|
wcex.lpszClassName = WINCLASS_NAME;
|
|
wcex.hIconSm = nullptr;
|
|
|
|
ATOM winclass = RegisterClassExW(&wcex);
|
|
|
|
|
|
run_as_dpi_aware([&] () {
|
|
// We need to be dpi aware when calculating the size
|
|
RECT wr = {0, 0, DESIRED_SCREEN_WIDTH, DESIRED_SCREEN_HEIGHT};
|
|
AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);
|
|
|
|
dxgi.h_wnd = CreateWindowW(WINCLASS_NAME, w_title, WS_OVERLAPPEDWINDOW,
|
|
CW_USEDEFAULT, 0, wr.right - wr.left, wr.bottom - wr.top, nullptr, nullptr, nullptr, nullptr);
|
|
});
|
|
|
|
load_dxgi_library();
|
|
|
|
ShowWindow(dxgi.h_wnd, SW_SHOW);
|
|
UpdateWindow(dxgi.h_wnd);
|
|
if (configWindow.fullscreen) {
|
|
ShowCursor(FALSE);
|
|
}
|
|
|
|
// enable drag & drop
|
|
DragAcceptFiles(dxgi.h_wnd, TRUE);
|
|
SetWindowLongPtr(dxgi.h_wnd, GWL_EXSTYLE, GetWindowLongPtr(dxgi.h_wnd, GWL_EXSTYLE) | WS_EX_ACCEPTFILES);
|
|
|
|
update_screen_settings();
|
|
}
|
|
|
|
static void gfx_dxgi_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void), void (*on_text_input)(char*)) {
|
|
dxgi.on_key_down = on_key_down;
|
|
dxgi.on_key_up = on_key_up;
|
|
dxgi.on_all_keys_up = on_all_keys_up;
|
|
dxgi.on_text_input = on_text_input;
|
|
}
|
|
|
|
static void gfx_dxgi_main_loop(void (*run_one_game_iter)(void)) {
|
|
run_one_game_iter();
|
|
}
|
|
|
|
static void gfx_dxgi_get_dimensions(uint32_t *width, uint32_t *height) {
|
|
*width = dxgi.current_width;
|
|
*height = dxgi.current_height;
|
|
}
|
|
|
|
static void gfx_dxgi_handle_events(void) {
|
|
MSG msg;
|
|
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE) != 0) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessageW(&msg);
|
|
if (msg.message == WM_QUIT) { break; }
|
|
}
|
|
|
|
if (configWindow.reset) {
|
|
dxgi.last_maximized_state = false;
|
|
configWindow.reset = false;
|
|
configWindow.x = WAPI_WIN_CENTERPOS;
|
|
configWindow.y = WAPI_WIN_CENTERPOS;
|
|
configWindow.w = DESIRED_SCREEN_WIDTH;
|
|
configWindow.h = DESIRED_SCREEN_HEIGHT;
|
|
configWindow.fullscreen = false;
|
|
configWindow.settings_changed = true;
|
|
}
|
|
|
|
if (configWindow.settings_changed) {
|
|
configWindow.settings_changed = false;
|
|
update_screen_settings();
|
|
}
|
|
}
|
|
|
|
static uint64_t qpc_to_us(uint64_t qpc) {
|
|
return qpc / dxgi.qpc_freq * 1000000 + qpc % dxgi.qpc_freq * 1000000 / dxgi.qpc_freq;
|
|
}
|
|
|
|
static bool gfx_dxgi_start_frame(void) {
|
|
// HACK: all of this is too confusing to bother with right now
|
|
/*DXGI_FRAME_STATISTICS stats;
|
|
if (dxgi.swap_chain->GetFrameStatistics(&stats) == S_OK && (stats.SyncRefreshCount != 0 || stats.SyncQPCTime.QuadPart != 0ULL)) {
|
|
{
|
|
LARGE_INTEGER t0;
|
|
QueryPerformanceCounter(&t0);
|
|
//printf("Get frame stats: %llu\n", (unsigned long long)(t0.QuadPart - dxgi.qpc_init));
|
|
}
|
|
//printf("stats: %u %u %u %u %u %.6f\n", dxgi.pending_frame_stats.rbegin()->first, dxgi.pending_frame_stats.rbegin()->second, stats.PresentCount, stats.PresentRefreshCount, stats.SyncRefreshCount, (double)(stats.SyncQPCTime.QuadPart - dxgi.qpc_init) / dxgi.qpc_freq);
|
|
if (dxgi.frame_stats.empty() || dxgi.frame_stats.rbegin()->second.PresentCount != stats.PresentCount) {
|
|
dxgi.frame_stats.insert(std::make_pair(stats.PresentCount, stats));
|
|
}
|
|
if (dxgi.frame_stats.size() > 3) {
|
|
dxgi.frame_stats.erase(dxgi.frame_stats.begin());
|
|
}
|
|
}
|
|
if (!dxgi.frame_stats.empty()) {
|
|
while (!dxgi.pending_frame_stats.empty() && dxgi.pending_frame_stats.begin()->first < dxgi.frame_stats.rbegin()->first) {
|
|
dxgi.pending_frame_stats.erase(dxgi.pending_frame_stats.begin());
|
|
}
|
|
}
|
|
while (dxgi.pending_frame_stats.size() > 15) {
|
|
// Just make sure the list doesn't grow too large if GetFrameStatistics fails.
|
|
dxgi.pending_frame_stats.erase(dxgi.pending_frame_stats.begin());
|
|
}
|
|
|
|
dxgi.frame_timestamp += FRAME_INTERVAL_US_NUMERATOR;
|
|
|
|
if (dxgi.frame_stats.size() >= 2) {
|
|
DXGI_FRAME_STATISTICS *first = &dxgi.frame_stats.begin()->second;
|
|
DXGI_FRAME_STATISTICS *last = &dxgi.frame_stats.rbegin()->second;
|
|
uint64_t sync_qpc_diff = last->SyncQPCTime.QuadPart - first->SyncQPCTime.QuadPart;
|
|
UINT sync_vsync_diff = last->SyncRefreshCount - first->SyncRefreshCount;
|
|
UINT present_vsync_diff = last->PresentRefreshCount - first->PresentRefreshCount;
|
|
UINT present_diff = last->PresentCount - first->PresentCount;
|
|
|
|
if (sync_vsync_diff == 0) {
|
|
sync_vsync_diff = 1;
|
|
}
|
|
|
|
double estimated_vsync_interval = (double)sync_qpc_diff / (double)sync_vsync_diff;
|
|
uint64_t estimated_vsync_interval_us = qpc_to_us(estimated_vsync_interval);
|
|
//printf("Estimated vsync_interval: %d\n", (int)estimated_vsync_interval_us);
|
|
if (estimated_vsync_interval_us < 2 || estimated_vsync_interval_us > 1000000) {
|
|
// Unreasonable, maybe a monitor change
|
|
estimated_vsync_interval_us = 16666;
|
|
estimated_vsync_interval = estimated_vsync_interval_us * dxgi.qpc_freq / 1000000;
|
|
}
|
|
|
|
UINT queued_vsyncs = 0;
|
|
bool is_first = true;
|
|
for (const std::pair<UINT, UINT>& p : dxgi.pending_frame_stats) {
|
|
if (is_first && dxgi.sync_interval_means_frames_to_wait) {
|
|
is_first = false;
|
|
continue;
|
|
}
|
|
queued_vsyncs += p.second;
|
|
}
|
|
|
|
uint64_t last_frame_present_end_qpc = (last->SyncQPCTime.QuadPart - dxgi.qpc_init) + estimated_vsync_interval * queued_vsyncs;
|
|
uint64_t last_end_us = qpc_to_us(last_frame_present_end_qpc);
|
|
|
|
double vsyncs_to_wait = (double)(int64_t)(dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR - last_end_us) / estimated_vsync_interval_us;
|
|
//printf("ts: %llu, last_end_us: %llu, Init v: %f\n", dxgi.frame_timestamp / 3, last_end_us, vsyncs_to_wait);
|
|
|
|
if (vsyncs_to_wait <= 0) {
|
|
// Too late
|
|
|
|
if ((int64_t)(dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR - last_end_us) < -66666) {
|
|
// The application must have been paused or similar
|
|
vsyncs_to_wait = round(((double)FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR) / estimated_vsync_interval_us);
|
|
if (vsyncs_to_wait < 1) {
|
|
vsyncs_to_wait = 1;
|
|
}
|
|
dxgi.frame_timestamp = FRAME_INTERVAL_US_DENOMINATOR * (last_end_us + vsyncs_to_wait * estimated_vsync_interval_us);
|
|
} else {
|
|
// Drop frame
|
|
//printf("Dropping frame\n");
|
|
dxgi.dropped_frame = true;
|
|
return false;
|
|
}
|
|
}
|
|
if (floor(vsyncs_to_wait) != vsyncs_to_wait) {
|
|
uint64_t left = last_end_us + floor(vsyncs_to_wait) * estimated_vsync_interval_us;
|
|
uint64_t right = last_end_us + ceil(vsyncs_to_wait) * estimated_vsync_interval_us;
|
|
uint64_t adjusted_desired_time = dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR + (last_end_us + (FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR) > dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR ? 2000 : -2000);
|
|
int64_t diff_left = adjusted_desired_time - left;
|
|
int64_t diff_right = right - adjusted_desired_time;
|
|
if (diff_left < 0) {
|
|
diff_left = -diff_left;
|
|
}
|
|
if (diff_right < 0) {
|
|
diff_right = -diff_right;
|
|
}
|
|
if (diff_left < diff_right) {
|
|
vsyncs_to_wait = floor(vsyncs_to_wait);
|
|
} else {
|
|
vsyncs_to_wait = ceil(vsyncs_to_wait);
|
|
}
|
|
if (vsyncs_to_wait == 0) {
|
|
//printf("vsyncs_to_wait became 0 so dropping frame\n");
|
|
dxgi.dropped_frame = true;
|
|
return false;
|
|
}
|
|
}
|
|
//printf("v: %d\n", (int)vsyncs_to_wait);
|
|
if (vsyncs_to_wait > 4) {
|
|
// Invalid, so change to 4
|
|
vsyncs_to_wait = 4;
|
|
}
|
|
dxgi.length_in_vsync_frames = vsyncs_to_wait;
|
|
} else {
|
|
dxgi.length_in_vsync_frames = 2;
|
|
}*/
|
|
|
|
dxgi.length_in_vsync_frames = configWindow.vsync;
|
|
dxgi.dropped_frame = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void gfx_dxgi_swap_buffers_begin(void) {
|
|
ThrowIfFailed(dxgi.swap_chain->Present(dxgi.length_in_vsync_frames, 0));
|
|
//UINT this_present_id;
|
|
//if (dxgi.swap_chain->GetLastPresentCount(&this_present_id) == S_OK) {
|
|
// dxgi.pending_frame_stats.insert(std::make_pair(this_present_id, dxgi.length_in_vsync_frames));
|
|
//}
|
|
dxgi.dropped_frame = false;
|
|
}
|
|
|
|
static void gfx_dxgi_swap_buffers_end(void) {
|
|
LARGE_INTEGER t0, t1, t2;
|
|
QueryPerformanceCounter(&t0);
|
|
QueryPerformanceCounter(&t1);
|
|
|
|
/*if (!dxgi.dropped_frame) {
|
|
if (dxgi.waitable_object != nullptr) {
|
|
WaitForSingleObject(dxgi.waitable_object, INFINITE);
|
|
}
|
|
// else TODO: maybe sleep until some estimated time the frame will be shown to reduce lag
|
|
}*/
|
|
|
|
DXGI_FRAME_STATISTICS stats;
|
|
dxgi.swap_chain->GetFrameStatistics(&stats);
|
|
|
|
QueryPerformanceCounter(&t2);
|
|
|
|
//dxgi.sync_interval_means_frames_to_wait = dxgi.pending_frame_stats.rbegin()->first == stats.PresentCount;
|
|
|
|
//printf("done %llu gpu:%d wait:%d freed:%llu frame:%u %u monitor:%u t:%llu\n", (unsigned long long)(t0.QuadPart - dxgi.qpc_init), (int)(t1.QuadPart - t0.QuadPart), (int)(t2.QuadPart - t0.QuadPart), (unsigned long long)(t2.QuadPart - dxgi.qpc_init), dxgi.pending_frame_stats.rbegin()->first, stats.PresentCount, stats.SyncRefreshCount, (unsigned long long)(stats.SyncQPCTime.QuadPart - dxgi.qpc_init));
|
|
}
|
|
|
|
static double gfx_dxgi_get_time(void) {
|
|
LARGE_INTEGER t;
|
|
QueryPerformanceCounter(&t);
|
|
return (double)(t.QuadPart - dxgi.qpc_init) / dxgi.qpc_freq;
|
|
}
|
|
|
|
void gfx_dxgi_create_factory_and_device(bool debug, int d3d_version, bool (*create_device_fn)(IDXGIAdapter1 *adapter, bool test_only)) {
|
|
if (dxgi.CreateDXGIFactory2 != nullptr) {
|
|
ThrowIfFailed(dxgi.CreateDXGIFactory2(debug ? DXGI_CREATE_FACTORY_DEBUG : 0, __uuidof(IDXGIFactory2), &dxgi.factory));
|
|
} else {
|
|
ThrowIfFailed(dxgi.CreateDXGIFactory1(__uuidof(IDXGIFactory2), &dxgi.factory));
|
|
}
|
|
|
|
ComPtr<IDXGIAdapter1> adapter;
|
|
for (UINT i = 0; dxgi.factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND; i++) {
|
|
DXGI_ADAPTER_DESC1 desc;
|
|
adapter->GetDesc1(&desc);
|
|
if (desc.Flags & 2/*DXGI_ADAPTER_FLAG_SOFTWARE*/) { // declaration missing in mingw headers
|
|
continue;
|
|
}
|
|
if (create_device_fn(adapter.Get(), true)) {
|
|
break;
|
|
}
|
|
}
|
|
create_device_fn(adapter.Get(), false);
|
|
|
|
wchar_t w_title[512];
|
|
int len = dxgi.window_title.length();
|
|
mbstowcs(w_title, dxgi.window_title.c_str(), len + 1);
|
|
SetWindowTextW(dxgi.h_wnd, w_title);
|
|
}
|
|
|
|
ComPtr<IDXGISwapChain1> gfx_dxgi_create_swap_chain(IUnknown *device) {
|
|
bool win8 = IsWindows8OrGreater(); // DXGI_SCALING_NONE is only supported on Win8 and beyond
|
|
bool dxgi_13 = dxgi.CreateDXGIFactory2 != nullptr; // DXGI 1.3 introduced waitable object
|
|
|
|
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
|
|
swap_chain_desc.BufferCount = 2;
|
|
swap_chain_desc.Width = 0;
|
|
swap_chain_desc.Height = 0;
|
|
swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
|
swap_chain_desc.Scaling = win8 ? DXGI_SCALING_NONE : DXGI_SCALING_STRETCH;
|
|
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // Apparently this was backported to Win 7 Platform Update
|
|
swap_chain_desc.Flags = dxgi_13 ? DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT : 0;
|
|
swap_chain_desc.SampleDesc.Count = 1;
|
|
|
|
run_as_dpi_aware([&] () {
|
|
// When setting size for the buffers, the values that DXGI puts into the desc (that can later be retrieved by GetDesc1)
|
|
// have been divided by the current scaling factor. By making this call dpi aware, no division will be performed.
|
|
// The same goes for IDXGISwapChain::ResizeBuffers(), however that function is currently only called from the message handler.
|
|
ThrowIfFailed(dxgi.factory->CreateSwapChainForHwnd(device, dxgi.h_wnd, &swap_chain_desc, nullptr, nullptr, &dxgi.swap_chain));
|
|
});
|
|
ThrowIfFailed(dxgi.factory->MakeWindowAssociation(dxgi.h_wnd, DXGI_MWA_NO_ALT_ENTER));
|
|
|
|
ComPtr<IDXGISwapChain2> swap_chain2;
|
|
if (dxgi.swap_chain->QueryInterface(__uuidof(IDXGISwapChain2), &swap_chain2) == S_OK) {
|
|
ThrowIfFailed(swap_chain2->SetMaximumFrameLatency(1));
|
|
dxgi.waitable_object = swap_chain2->GetFrameLatencyWaitableObject();
|
|
WaitForSingleObject(dxgi.waitable_object, INFINITE);
|
|
} else {
|
|
ComPtr<IDXGIDevice1> device1;
|
|
ThrowIfFailed(device->QueryInterface(IID_PPV_ARGS(&device1)));
|
|
ThrowIfFailed(device1->SetMaximumFrameLatency(1));
|
|
}
|
|
|
|
ThrowIfFailed(dxgi.swap_chain->GetDesc1(&swap_chain_desc));
|
|
dxgi.current_width = swap_chain_desc.Width;
|
|
dxgi.current_height = swap_chain_desc.Height;
|
|
|
|
return dxgi.swap_chain;
|
|
}
|
|
|
|
void gfx_dxgi_delay(u32 ms) {
|
|
Sleep(ms);
|
|
}
|
|
|
|
static int gfx_dxgi_get_max_msaa(void) {
|
|
return 0;
|
|
}
|
|
|
|
static void gfx_dxgi_set_window_title(const char* title) {
|
|
SetWindowTextA(dxgi.h_wnd, title);
|
|
}
|
|
|
|
static void gfx_dxgi_reset_window_title(void) {
|
|
SetWindowTextA(dxgi.h_wnd, TITLE);
|
|
}
|
|
|
|
static bool gfx_dxgi_has_focus(void) {
|
|
return GetFocus() == dxgi.h_wnd;
|
|
}
|
|
|
|
extern "C" HWND gfx_dxgi_get_h_wnd(void) {
|
|
return dxgi.h_wnd;
|
|
}
|
|
|
|
void gfx_dxgi_shutdown(void) {
|
|
}
|
|
|
|
void gfx_dxgi_start_text_input(void) { inTextInput = TRUE; }
|
|
void gfx_dxgi_stop_text_input(void) { inTextInput = FALSE; }
|
|
|
|
static char* gfx_dxgi_get_clipboard_text(void) {
|
|
if (OpenClipboard(NULL)) {
|
|
HANDLE clip = GetClipboardData(CF_TEXT);
|
|
CloseClipboard();
|
|
return (char*)clip;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void gfx_dxgi_set_clipboard_text(char* text) {
|
|
if (OpenClipboard(NULL)) {
|
|
HGLOBAL clipbuffer;
|
|
char *buffer;
|
|
EmptyClipboard();
|
|
clipbuffer = GlobalAlloc(GMEM_DDESHARE, strlen(text) + 1);
|
|
buffer = (char *) GlobalLock(clipbuffer);
|
|
strcpy(buffer, LPCSTR(text));
|
|
GlobalUnlock(clipbuffer);
|
|
SetClipboardData(CF_TEXT, clipbuffer);
|
|
CloseClipboard();
|
|
}
|
|
}
|
|
|
|
void gfx_dxgi_set_cursor_visible(bool visible) { ShowCursor(visible); }
|
|
|
|
void ThrowIfFailed(HRESULT res) {
|
|
if (FAILED(res)) {
|
|
fprintf(stderr, "Error: 0x%08X\n", res);
|
|
throw res;
|
|
}
|
|
}
|
|
|
|
void ThrowIfFailed(HRESULT res, HWND h_wnd, const char *message) {
|
|
if (FAILED(res)) {
|
|
char full_message[256];
|
|
sprintf(full_message, "%s\n\nHRESULT: 0x%08X", message, res);
|
|
dxgi.showing_error = true;
|
|
MessageBox(h_wnd, full_message, "Error", MB_OK | MB_ICONERROR);
|
|
throw res;
|
|
}
|
|
}
|
|
|
|
struct GfxWindowManagerAPI gfx_dxgi = {
|
|
gfx_dxgi_init,
|
|
gfx_dxgi_set_keyboard_callbacks,
|
|
gfx_dxgi_main_loop,
|
|
gfx_dxgi_get_dimensions,
|
|
gfx_dxgi_handle_events,
|
|
gfx_dxgi_start_frame,
|
|
gfx_dxgi_swap_buffers_begin,
|
|
gfx_dxgi_swap_buffers_end,
|
|
gfx_dxgi_get_time,
|
|
gfx_dxgi_shutdown,
|
|
gfx_dxgi_start_text_input,
|
|
gfx_dxgi_stop_text_input,
|
|
gfx_dxgi_get_clipboard_text,
|
|
gfx_dxgi_set_clipboard_text,
|
|
gfx_dxgi_set_cursor_visible,
|
|
gfx_dxgi_delay,
|
|
gfx_dxgi_get_max_msaa,
|
|
gfx_dxgi_set_window_title,
|
|
gfx_dxgi_reset_window_title,
|
|
gfx_dxgi_has_focus
|
|
};
|
|
|
|
#endif
|