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/osx_app.m b/src/app/osx_app.m index b83e219..8588dac 100644 --- a/src/app/osx_app.m +++ b/src/app/osx_app.m @@ -304,7 +304,10 @@ oc_key_code oc_label_to_key(oc_str8 label) @interface OCWindow : NSWindow { oc_window_data* mpWindow; + @public + CVDisplayLinkRef displayLink; } + - (id)initWithWindowData:(oc_window_data*)window contentRect:(NSRect)rect styleMask:(uint32)style; @end @@ -703,6 +706,9 @@ void oc_install_keyboard_layout_listener() - (BOOL)windowShouldClose:(id)sender { + OCWindow* ocWindow = (OCWindow*)mpWindow->osx.nsWindow; + CVDisplayLinkStop(ocWindow->displayLink); + mpWindow->shouldClose = true; oc_event event = {}; @@ -2336,3 +2342,98 @@ int oc_directory_create(oc_str8 path) } } } + +static CVReturn oc_display_link_callback( + CVDisplayLinkRef displayLink, + const CVTimeStamp* inNow, + const CVTimeStamp* inOutputTime, + CVOptionFlags flagsIn, + CVOptionFlags* flagsOut, + void* displayLinkContext) +{ + oc_event event = { 0 }; + event.type = OC_EVENT_FRAME; + oc_queue_event(&event); + + OCWindow* ocWindow = (OCWindow*)displayLinkContext; + + CGDirectDisplayID displays[4]; + uint32_t matchingDisplayCount; + CGError err = CGGetDisplaysWithRect(ocWindow.frame, 4, displays, &matchingDisplayCount); + if(err == kCGErrorSuccess) + { + // determine which display has the greatest intersecting area + if(matchingDisplayCount > 0) + { + CGDirectDisplayID* selectedDisplay = NULL; + CGFloat selectedIntersectArea = 0.0f; + + for(uint32_t i = 0; i < matchingDisplayCount; ++i) + { + CGRect displayBounds = CGDisplayBounds(displays[i]); + CGRect intersection = CGRectIntersection(ocWindow.frame, displayBounds); + CGFloat intersectArea = intersection.size.width * intersection.size.width; + if(selectedDisplay == NULL || intersectArea < selectedIntersectArea) + { + selectedDisplay = displays + i; + selectedIntersectArea = intersectArea; + } + } + + if(selectedDisplay) + { + CGDirectDisplayID currentDisplay = CVDisplayLinkGetCurrentCGDisplay(ocWindow->displayLink); + if(currentDisplay != *selectedDisplay) + { + CVDisplayLinkSetCurrentCGDisplay(ocWindow->displayLink, *selectedDisplay); + } + } + } + } + else + { + oc_log_error("CGGetDisplaysWithRect failed with error: %d\n", err); + } + + return kCVReturnSuccess; +} + +void oc_vsync_init(void) +{ +} + +void oc_vsync_wait(oc_window window) +{ + // TODO figure out why this causes stuttering with triple buffering + oc_window_data* windowData = oc_window_ptr_from_handle(window); + if(!windowData) + { + return; + } + + OCWindow* ocWindow = (OCWindow*)windowData->osx.nsWindow; + + CVReturn ret; + + if((ret = CVDisplayLinkCreateWithActiveCGDisplays(&ocWindow->displayLink)) != kCVReturnSuccess) + { + oc_log_error("CVDisplayLinkCreateWithActiveCGDisplays error: %d\n", ret); + } + + CGDirectDisplayID mainDisplay = CGMainDisplayID(); + + if((ret = CVDisplayLinkSetCurrentCGDisplay(ocWindow->displayLink, mainDisplay)) != kCVReturnSuccess) + { + oc_log_error("CVDisplayLinkSetCurrentCGDisplay ret: %d\n", ret); + } + + if((ret = CVDisplayLinkSetOutputCallback(ocWindow->displayLink, oc_display_link_callback, ocWindow)) != kCVReturnSuccess) + { + oc_log_error("CVDisplayLinkSetOutputCallback ret: %d\n", ret); + } + + if((ret = CVDisplayLinkStart(ocWindow->displayLink)) != kCVReturnSuccess) + { + oc_log_error("CVDisplayLinkStart ret: %d\n", ret); + } +} diff --git a/src/app/win32_app.c b/src/app/win32_app.c index 7312295..416aaf3 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(); } } diff --git a/src/graphics/graphics.h b/src/graphics/graphics.h index 860a941..b9547a8 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(void); +ORCA_API void oc_vsync_wait(oc_window window); + //------------------------------------------------------------------------------------------ //SECTION: graphics canvas structs //------------------------------------------------------------------------------------------ diff --git a/src/graphics/graphics_surface.c b/src/graphics/graphics_surface.c index d55ba82..b938d19 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, 1); 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..7c486c0 --- /dev/null +++ b/src/graphics/win32_vsync.c @@ -0,0 +1,156 @@ +/************************************************************/ /** +* +* @file: win32_vsync.h +* @author: Reuben Dunnington +* @date: 26/08/2023 +* +*****************************************************************/ + +#include "platform/platform_thread.h" +#include "app/win32_app.h" + +#define COBJMACROS +#define interface struct +#include +#include +#undef interface + +typedef struct oc_vsync_data +{ + IDXGIFactory4* factory; + IDXGIAdapter1* adapter; +} oc_vsync_data; + +oc_vsync_data __oc_vsync_data; + +void oc_vsync_init(void) +{ + 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_wait(oc_window window) +{ + if(__oc_vsync_data.adapter) + { + oc_window_data* windowData = oc_window_ptr_from_handle(window); + if(!windowData) + { + oc_log_error("Failed to get window ptr - assuming window was closed."); + return; + } + + RECT windowRect = { 0 }; + if(GetWindowRect(windowData->win32.hWnd, &windowRect) == FALSE) + { + oc_log_error("Failed to get window rect with error: %d.", GetLastError()); + return; + } + + // 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(__oc_vsync_data.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); + } + + IDXGIOutput_Release(selectedOutput); + } + else + { + oc_log_warning("No outputs found. Were all monitors unplugged?"); + } + } +} 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..7ee08d3 100644 --- a/src/runtime.c +++ b/src/runtime.c @@ -209,6 +209,8 @@ i32 orca_surface_callback(void* user) data->surface = oc_surface_create_for_window(data->window, data->api); #if OC_PLATFORM_WINDOWS + //NOTE(martin): on windows we set all surfaces to non-synced, and do a single "manual" wait here. + // on macOS each surface is individually synced to the monitor refresh rate but don't block each other oc_surface_swap_interval(data->surface, 0); #endif @@ -838,6 +840,12 @@ i32 orca_runloop(void* user) oc_surface_present(app->debugOverlay.surface); oc_arena_clear(oc_scratch()); + +#if OC_PLATFORM_WINDOWS + //NOTE(martin): on windows we set all surfaces to non-synced, and do a single "manual" wait here. + // on macOS each surface is individually synced to the monitor refresh rate but don't block each other + oc_vsync_wait(app->window); +#endif } if(exports[OC_EXPORT_TERMINATE])