vsync wait on win32 #86
|
@ -128,7 +128,6 @@ def build_platform_layer(target, release):
|
||||||
|
|
||||||
|
|
||||||
def build_platform_layer_lib_win(release):
|
def build_platform_layer_lib_win(release):
|
||||||
|
|
||||||
embed_text_files("src\\graphics\\glsl_shaders.h", "glsl_", [
|
embed_text_files("src\\graphics\\glsl_shaders.h", "glsl_", [
|
||||||
"src\\graphics\\glsl_shaders\\common.glsl",
|
"src\\graphics\\glsl_shaders\\common.glsl",
|
||||||
"src\\graphics\\glsl_shaders\\blit_vertex.glsl",
|
"src\\graphics\\glsl_shaders\\blit_vertex.glsl",
|
||||||
|
@ -157,6 +156,8 @@ def build_platform_layer_lib_win(release):
|
||||||
"ole32.lib",
|
"ole32.lib",
|
||||||
"shell32.lib",
|
"shell32.lib",
|
||||||
"shlwapi.lib",
|
"shlwapi.lib",
|
||||||
|
"dxgi.lib",
|
||||||
|
"dxguid.lib",
|
||||||
"/LIBPATH:ext/angle/lib",
|
"/LIBPATH:ext/angle/lib",
|
||||||
"libEGL.dll.lib",
|
"libEGL.dll.lib",
|
||||||
"libGLESv2.dll.lib",
|
"libGLESv2.dll.lib",
|
||||||
|
@ -178,7 +179,6 @@ def build_platform_layer_lib_win(release):
|
||||||
"/IMPLIB:build/bin/orca.dll.lib",
|
"/IMPLIB:build/bin/orca.dll.lib",
|
||||||
], check=True)
|
], check=True)
|
||||||
|
|
||||||
|
|
||||||
def build_platform_layer_lib_mac(release):
|
def build_platform_layer_lib_mac(release):
|
||||||
sdk_dir = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"
|
sdk_dir = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"
|
||||||
|
|
||||||
|
|
|
@ -304,7 +304,10 @@ oc_key_code oc_label_to_key(oc_str8 label)
|
||||||
@interface OCWindow : NSWindow
|
@interface OCWindow : NSWindow
|
||||||
{
|
{
|
||||||
oc_window_data* mpWindow;
|
oc_window_data* mpWindow;
|
||||||
|
@public
|
||||||
|
CVDisplayLinkRef displayLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)initWithWindowData:(oc_window_data*)window contentRect:(NSRect)rect styleMask:(uint32)style;
|
- (id)initWithWindowData:(oc_window_data*)window contentRect:(NSRect)rect styleMask:(uint32)style;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -703,6 +706,9 @@ void oc_install_keyboard_layout_listener()
|
||||||
|
|
||||||
- (BOOL)windowShouldClose:(id)sender
|
- (BOOL)windowShouldClose:(id)sender
|
||||||
{
|
{
|
||||||
|
OCWindow* ocWindow = (OCWindow*)mpWindow->osx.nsWindow;
|
||||||
|
CVDisplayLinkStop(ocWindow->displayLink);
|
||||||
|
|
||||||
mpWindow->shouldClose = true;
|
mpWindow->shouldClose = true;
|
||||||
|
|
||||||
oc_event event = {};
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include "app.c"
|
#include "app.c"
|
||||||
#include "platform/platform_thread.h"
|
#include "platform/platform_thread.h"
|
||||||
|
#include "graphics/graphics.h"
|
||||||
#include <dwmapi.h>
|
#include <dwmapi.h>
|
||||||
|
|
||||||
void oc_init_keys()
|
void oc_init_keys()
|
||||||
|
@ -169,6 +170,8 @@ void oc_init()
|
||||||
u32 wheelScrollLines = 3;
|
u32 wheelScrollLines = 3;
|
||||||
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheelScrollLines, 0);
|
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheelScrollLines, 0);
|
||||||
oc_appData.win32.wheelScrollLines = wheelScrollLines;
|
oc_appData.win32.wheelScrollLines = wheelScrollLines;
|
||||||
|
|
||||||
|
oc_vsync_init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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_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
|
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
|
//SECTION: graphics canvas structs
|
||||||
//------------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------------
|
||||||
|
|
|
@ -148,6 +148,7 @@ oc_surface oc_surface_create_for_window(oc_window window, oc_surface_api api)
|
||||||
if(surface)
|
if(surface)
|
||||||
{
|
{
|
||||||
surfaceHandle = oc_surface_handle_alloc(surface);
|
surfaceHandle = oc_surface_handle_alloc(surface);
|
||||||
|
oc_surface_swap_interval(surfaceHandle, 1);
|
||||||
oc_surface_select(surfaceHandle);
|
oc_surface_select(surfaceHandle);
|
||||||
}
|
}
|
||||||
return (surfaceHandle);
|
return (surfaceHandle);
|
||||||
|
|
|
@ -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 <d3d11_1.h>
|
||||||
|
#include <dxgi1_6.h>
|
||||||
|
#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?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -74,6 +74,7 @@
|
||||||
|
|
||||||
#if OC_PLATFORM_WINDOWS
|
#if OC_PLATFORM_WINDOWS
|
||||||
#include "app/win32_app.c"
|
#include "app/win32_app.c"
|
||||||
|
#include "graphics/win32_vsync.c"
|
||||||
#include "graphics/graphics_common.c"
|
#include "graphics/graphics_common.c"
|
||||||
#include "graphics/graphics_surface.c"
|
#include "graphics/graphics_surface.c"
|
||||||
|
|
||||||
|
|
|
@ -209,6 +209,8 @@ i32 orca_surface_callback(void* user)
|
||||||
data->surface = oc_surface_create_for_window(data->window, data->api);
|
data->surface = oc_surface_create_for_window(data->window, data->api);
|
||||||
|
|
||||||
#if OC_PLATFORM_WINDOWS
|
#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);
|
oc_surface_swap_interval(data->surface, 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -838,6 +840,12 @@ i32 orca_runloop(void* user)
|
||||||
oc_surface_present(app->debugOverlay.surface);
|
oc_surface_present(app->debugOverlay.surface);
|
||||||
|
|
||||||
oc_arena_clear(oc_scratch());
|
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])
|
if(exports[OC_EXPORT_TERMINATE])
|
||||||
|
|
Loading…
Reference in New Issue