From be5b6aea30b00395161f4156f6412b602b118348 Mon Sep 17 00:00:00 2001 From: Reuben Dunnington Date: Sat, 26 Aug 2023 22:13:38 -0700 Subject: [PATCH] vblank notification: win32 --- scripts/dev.py | 4 +- src/app/win32_app.c | 11 ++ src/app/win32_app.h | 1 + src/graphics/graphics.h | 9 ++ src/graphics/graphics_surface.c | 1 + src/graphics/win32_vsync.c | 206 ++++++++++++++++++++++++++++++++ src/orca.c | 1 + src/runtime.c | 15 +++ 8 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 src/graphics/win32_vsync.c diff --git a/scripts/dev.py b/scripts/dev.py index 6a6ad8a..ea9d3e8 100644 --- a/scripts/dev.py +++ b/scripts/dev.py @@ -128,7 +128,6 @@ def build_platform_layer(target, release): def build_platform_layer_lib_win(release): - embed_text_files("src\\graphics\\glsl_shaders.h", "glsl_", [ "src\\graphics\\glsl_shaders\\common.glsl", "src\\graphics\\glsl_shaders\\blit_vertex.glsl", @@ -157,6 +156,8 @@ def build_platform_layer_lib_win(release): "ole32.lib", "shell32.lib", "shlwapi.lib", + "dxgi.lib", + "dxguid.lib", "/LIBPATH:ext/angle/lib", "libEGL.dll.lib", "libGLESv2.dll.lib", @@ -178,7 +179,6 @@ def build_platform_layer_lib_win(release): "/IMPLIB:build/bin/orca.dll.lib", ], check=True) - def build_platform_layer_lib_mac(release): sdk_dir = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" diff --git a/src/app/win32_app.c b/src/app/win32_app.c index 7312295..c286bb8 100644 --- a/src/app/win32_app.c +++ b/src/app/win32_app.c @@ -9,6 +9,7 @@ #include "app.c" #include "platform/platform_thread.h" +#include "graphics/graphics.h" #include void oc_init_keys() @@ -169,6 +170,8 @@ void oc_init() u32 wheelScrollLines = 3; SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheelScrollLines, 0); oc_appData.win32.wheelScrollLines = wheelScrollLines; + + oc_vsync_init(); } } @@ -583,6 +586,14 @@ LRESULT oc_win32_win_proc(HWND windowHandle, UINT message, WPARAM wParam, LPARAM } break; + case OC_WM_USER_VBLANK: + { + oc_event event = { 0 }; + event.type = OC_EVENT_FRAME; + oc_queue_event(&event); + } + break; + default: { handled = false; diff --git a/src/app/win32_app.h b/src/app/win32_app.h index 8f41e84..7f682c4 100644 --- a/src/app/win32_app.h +++ b/src/app/win32_app.h @@ -49,6 +49,7 @@ typedef struct oc_win32_app_data enum OC_WM_USER { OC_WM_USER_DISPATCH_PROC = 0x0400, // WM_USER messages are defined from 0x400 to 0x7FFF + OC_WM_USER_VBLANK = 0x0401, }; #endif __WIN32_APP_H_ diff --git a/src/graphics/graphics.h b/src/graphics/graphics.h index 860a941..0e0ba77 100644 --- a/src/graphics/graphics.h +++ b/src/graphics/graphics.h @@ -125,6 +125,15 @@ ORCA_API oc_vec2 oc_surface_contents_scaling(oc_surface surface); //DOC: returns ORCA_API void oc_surface_bring_to_front(oc_surface surface); //DOC: puts surface on top of the surface stack ORCA_API void oc_surface_send_to_back(oc_surface surface); //DOC: puts surface at the bottom of the surface stack +//------------------------------------------------------------------------------------------ +//SECTION: vsync +//------------------------------------------------------------------------------------------ + +typedef void (*oc_vsync_callback)(void* data); + +ORCA_API void oc_vsync_init(); +ORCA_API void oc_vsync_window(oc_window window); + //------------------------------------------------------------------------------------------ //SECTION: graphics canvas structs //------------------------------------------------------------------------------------------ diff --git a/src/graphics/graphics_surface.c b/src/graphics/graphics_surface.c index d55ba82..246bf53 100644 --- a/src/graphics/graphics_surface.c +++ b/src/graphics/graphics_surface.c @@ -148,6 +148,7 @@ oc_surface oc_surface_create_for_window(oc_window window, oc_surface_api api) if(surface) { surfaceHandle = oc_surface_handle_alloc(surface); + oc_surface_swap_interval(surfaceHandle, 0); oc_surface_select(surfaceHandle); } return (surfaceHandle); diff --git a/src/graphics/win32_vsync.c b/src/graphics/win32_vsync.c new file mode 100644 index 0000000..88ef542 --- /dev/null +++ b/src/graphics/win32_vsync.c @@ -0,0 +1,206 @@ +/************************************************************/ /** +* +* @file: win32_vsync.h +* @author: Reuben Dunnington +* @date: 26/08/2023 +* +*****************************************************************/ + +// #define WIN32_LEAN_AND_MEAN +// #define WIN32_EXTRA_LEAN +// #define NOMINMAX + +// #define D3D11_NO_HELPERS +// #define CINTERFACE +// #define COBJMACROS + +// #define interface struct + +// #include + +#include "platform/platform_thread.h" +#include "app/win32_app.h" + +// This forward declaration is needed to suppress an order of declarations bug in the d3d headers. +// d3d11shader.h(454): warning C4115: 'ID3D11ModuleInstance': named type definition in parentheses +// struct ID3D11ModuleInstance; + +#define COBJMACROS +#define interface struct +#include +#include +#undef interface + +// #include +// #include + +typedef struct oc_vsync_data +{ + IDXGIFactory4* factory; + IDXGIAdapter1* adapter; +} oc_vsync_data; + +oc_vsync_data __oc_vsync_data; + +typedef struct oc_vsync_thread_data +{ + IDXGIAdapter1* adapter; + oc_window window; + bool quit; +} oc_vsync_thread_data; + +static i32 oc_window_vysnc_notification_thread(void* userPointer) +{ + oc_vsync_thread_data* threadData = (oc_vsync_thread_data*)userPointer; + + while(!threadData->quit) + { + oc_window_data* windowData = oc_window_ptr_from_handle(threadData->window); + if(!windowData) + { + oc_log_error("Failed to get window ptr - assuming window was closed."); + break; + } + + RECT windowRect = { 0 }; + if(GetWindowRect(windowData->win32.hWnd, &windowRect) == FALSE) + { + oc_log_error("Failed to get window rect with error: %d.", GetLastError()); + return 1; + } + + //NOTE(Reuben): Wait for VBlank on the display device with which the window has the most intersecting area + IDXGIOutput* output = NULL; + IDXGIOutput* selectedOutput = NULL; + uint32_t selected_intersect_area = 0; + for(UINT i = 0; DXGI_ERROR_NOT_FOUND != IDXGIAdapter1_EnumOutputs(threadData->adapter, i, &output); ++i) + { + DXGI_OUTPUT_DESC outputDesc = { 0 }; + HRESULT hr = IDXGIOutput_GetDesc(output, &outputDesc); + if(SUCCEEDED(hr)) + { + RECT intersectRect = { 0 }; + if(IntersectRect(&intersectRect, &windowRect, &outputDesc.DesktopCoordinates)) + { + uint32_t outputIntersectArea = (intersectRect.right - intersectRect.left) * (intersectRect.bottom - intersectRect.top); + + if(selectedOutput == NULL || outputIntersectArea > selected_intersect_area) + { + selectedOutput = output; + selected_intersect_area = outputIntersectArea; + } + } + } + else + { + oc_log_error("Failed to get IDXGIOutput desc with error: %d", hr); + } + + if(selectedOutput != output) + { + IDXGIOutput_Release(output); + } + } + + if(selectedOutput) + { + HRESULT hr = IDXGIOutput_WaitForVBlank(selectedOutput); + + if(FAILED(hr)) + { + // TODO(reuben) - fall back to software timer + oc_log_warning("Failed to wait for vblank with error: %d", hr); + } + + SendMessage(windowData->win32.hWnd, OC_WM_USER_VBLANK, 0, 0); + + IDXGIOutput_Release(selectedOutput); + } + else + { + oc_log_warning("No outputs found. Were all monitors unplugged?"); + } + } + + return 0; +} + +void oc_vsync_init() +{ + if(__oc_vsync_data.adapter) + { + return; + } + + IDXGIFactory4* factory = NULL; + { + UINT flags = 0; + flags |= DXGI_CREATE_FACTORY_DEBUG; // TODO make this optional + + HRESULT hr = CreateDXGIFactory2(flags, &IID_IDXGIFactory4, (void**)&factory); + if(!SUCCEEDED(hr)) + { + return; + } + } + + IDXGIAdapter1* adapter = NULL; + IDXGIAdapter1* adapterFallback = NULL; + IDXGIAdapter1* enumeratedAdapter = NULL; + + for(UINT i = 0; DXGI_ERROR_NOT_FOUND != IDXGIFactory1_EnumAdapters1(factory, i, &enumeratedAdapter); ++i) + { + DXGI_ADAPTER_DESC1 adapterDesc; + IDXGIAdapter1_GetDesc1(enumeratedAdapter, &adapterDesc); + if(adapterDesc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) + { + adapterFallback = enumeratedAdapter; + continue; + } + else + { + adapter = enumeratedAdapter; + } + break; + } + + if(adapter == NULL) + { + adapter = adapterFallback; + if(adapter) + { + oc_log_info("Couldn't find a dedicated hardware DXGI adapater, using software fallback."); + } + } + + if(adapter) + { + __oc_vsync_data = (oc_vsync_data){ + .factory = factory, + .adapter = adapter, + }; + } + else + { + oc_log_info("Couldn't find any DXGI adapters. Vsync will be unavailable."); + IDXGIFactory_Release(factory); + } +} + +void oc_vsync_window(oc_window window) +{ + if(__oc_vsync_data.adapter) + { + oc_vsync_thread_data* data = malloc(sizeof(oc_vsync_thread_data)); + *data = (oc_vsync_thread_data){ + .adapter = __oc_vsync_data.adapter, + .window = window, + .quit = false, + }; + oc_thread_create_with_name(oc_window_vysnc_notification_thread, data, OC_STR8("vblank thread")); + } + else + { + oc_log_error("No DXGI adapter available - unable to start vsync notification thread."); + } +} diff --git a/src/orca.c b/src/orca.c index 996f370..fbe410b 100644 --- a/src/orca.c +++ b/src/orca.c @@ -74,6 +74,7 @@ #if OC_PLATFORM_WINDOWS #include "app/win32_app.c" + #include "graphics/win32_vsync.c" #include "graphics/graphics_common.c" #include "graphics/graphics_surface.c" diff --git a/src/runtime.c b/src/runtime.c index 14ca3a9..da67cfe 100644 --- a/src/runtime.c +++ b/src/runtime.c @@ -676,6 +676,19 @@ i32 orca_runloop(void* user) } break; + case OC_EVENT_FRAME: + { + if(exports[OC_EXPORT_FRAME_REFRESH]) + { + M3Result res = m3_Call(exports[OC_EXPORT_FRAME_REFRESH], 0, 0); + if(res) + { + ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); + } + } + } + break; + default: break; } @@ -867,6 +880,8 @@ int main(int argc, char** argv) oc_rect windowRect = { .x = 100, .y = 100, .w = 810, .h = 610 }; app->window = oc_window_create(windowRect, OC_STR8("orca"), 0); + oc_vsync_window(app->window); + app->debugOverlay.show = false; app->debugOverlay.surface = oc_surface_create_for_window(app->window, OC_CANVAS); app->debugOverlay.canvas = oc_canvas_create();