From 4fe6803fe4d65497cee56b243d6936ac3920c8cc Mon Sep 17 00:00:00 2001 From: jpark37 Date: Wed, 3 Aug 2022 20:19:29 -0700 Subject: [PATCH] libobs: Prevent D3D11 projectors from tearing Some users stream projectors, so don't let them tear. Use the waitable object to check the flip queue, and only flip if there's space. Metal and Vulkan can probably perform similar flip throttling once OBS starts using them. --- libobs-d3d11/d3d11-subsystem.cpp | 49 +++++++++++------------ libobs-d3d11/d3d11-subsystem.hpp | 1 - libobs-opengl/gl-cocoa.m | 6 +++ libobs-opengl/gl-nix.c | 6 +++ libobs-opengl/gl-windows.c | 6 +++ libobs/graphics/device-exports.h | 1 + libobs/graphics/graphics-imports.c | 1 + libobs/graphics/graphics-internal.h | 1 + libobs/graphics/graphics.c | 10 +++++ libobs/graphics/graphics.h | 1 + libobs/obs-display.c | 62 ++++++++++++++++------------- 11 files changed, 90 insertions(+), 54 deletions(-) diff --git a/libobs-d3d11/d3d11-subsystem.cpp b/libobs-d3d11/d3d11-subsystem.cpp index 8fad75087..2a0f0b20f 100644 --- a/libobs-d3d11/d3d11-subsystem.cpp +++ b/libobs-d3d11/d3d11-subsystem.cpp @@ -274,16 +274,6 @@ gs_swap_chain::gs_swap_chain(gs_device *device, const gs_init_data *data) effect = DXGI_SWAP_EFFECT_FLIP_DISCARD; flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; - - BOOL featureSupportData = FALSE; - const HRESULT hr = factory5->CheckFeatureSupport( - DXGI_FEATURE_PRESENT_ALLOW_TEARING, &featureSupportData, - sizeof(featureSupportData)); - if (SUCCEEDED(hr) && featureSupportData) { - presentFlags |= DXGI_PRESENT_ALLOW_TEARING; - - flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; - } } space = make_swap_desc(device, swapDesc, &initData, effect, flags); @@ -298,11 +288,9 @@ gs_swap_chain::gs_swap_chain(gs_device *device, const gs_init_data *data) if (flags & DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT) { ComPtr swap2 = ComQIPtr(swap); hWaitable = swap2->GetFrameLatencyWaitableObject(); - if (hWaitable) { - hr = swap2->SetMaximumFrameLatency(40); - if (FAILED(hr)) - throw HRError("Could not relax frame latency", - hr); + if (hWaitable == NULL) { + throw HRError("Failed to GetFrameLatencyWaitableObject", + hr); } } @@ -2348,20 +2336,31 @@ void device_clear(gs_device_t *device, uint32_t clear_flags, } } +bool device_is_present_ready(gs_device_t *device) +{ + gs_swap_chain *const curSwapChain = device->curSwapChain; + bool ready = curSwapChain != nullptr; + if (ready) { + const HANDLE hWaitable = curSwapChain->hWaitable; + ready = (hWaitable == NULL) || + WaitForSingleObject(hWaitable, 0) == WAIT_OBJECT_0; + } else { + blog(LOG_WARNING, + "device_is_present_ready (D3D11): No active swap"); + } + + return ready; +} + void device_present(gs_device_t *device) { gs_swap_chain *const curSwapChain = device->curSwapChain; if (curSwapChain) { - /* Skip Present at frame limit to avoid stall */ - const HANDLE hWaitable = curSwapChain->hWaitable; - if ((hWaitable == NULL) || - WaitForSingleObject(hWaitable, 0) == WAIT_OBJECT_0) { - const HRESULT hr = curSwapChain->swap->Present( - 0, curSwapChain->presentFlags); - if (hr == DXGI_ERROR_DEVICE_REMOVED || - hr == DXGI_ERROR_DEVICE_RESET) { - device->RebuildDevice(); - } + const UINT interval = curSwapChain->hWaitable ? 1 : 0; + const HRESULT hr = curSwapChain->swap->Present(interval, 0); + if (hr == DXGI_ERROR_DEVICE_REMOVED || + hr == DXGI_ERROR_DEVICE_RESET) { + device->RebuildDevice(); } } else { blog(LOG_WARNING, "device_present (D3D11): No active swap"); diff --git a/libobs-d3d11/d3d11-subsystem.hpp b/libobs-d3d11/d3d11-subsystem.hpp index 6ffc5fcb8..0d4ff24a2 100644 --- a/libobs-d3d11/d3d11-subsystem.hpp +++ b/libobs-d3d11/d3d11-subsystem.hpp @@ -821,7 +821,6 @@ struct gs_swap_chain : gs_obj { gs_init_data initData; DXGI_SWAP_CHAIN_DESC swapDesc = {}; gs_color_space space; - UINT presentFlags = 0; gs_texture_2d target; gs_zstencil_buffer zs; diff --git a/libobs-opengl/gl-cocoa.m b/libobs-opengl/gl-cocoa.m index c4a69d08c..bdd8d54e1 100644 --- a/libobs-opengl/gl-cocoa.m +++ b/libobs-opengl/gl-cocoa.m @@ -287,6 +287,12 @@ void device_load_swapchain(gs_device_t *device, gs_swapchain_t *swap) } } +bool device_is_present_ready(gs_device_t *device) +{ + UNUSED_PARAMETER(device); + return true; +} + void device_present(gs_device_t *device) { glFlush(); diff --git a/libobs-opengl/gl-nix.c b/libobs-opengl/gl-nix.c index 68a412cf8..52a300346 100644 --- a/libobs-opengl/gl-nix.c +++ b/libobs-opengl/gl-nix.c @@ -115,6 +115,12 @@ extern void device_load_swapchain(gs_device_t *device, gs_swapchain_t *swap) gl_vtable->device_load_swapchain(device, swap); } +extern bool device_is_present_ready(gs_device_t *device) +{ + UNUSED_PARAMETER(device); + return true; +} + extern void device_present(gs_device_t *device) { gl_vtable->device_present(device); diff --git a/libobs-opengl/gl-windows.c b/libobs-opengl/gl-windows.c index d9fcd28a2..e9f3f0938 100644 --- a/libobs-opengl/gl-windows.c +++ b/libobs-opengl/gl-windows.c @@ -573,6 +573,12 @@ void device_load_swapchain(gs_device_t *device, gs_swapchain_t *swap) } } +bool device_is_present_ready(gs_device_t *device) +{ + UNUSED_PARAMETER(device); + return true; +} + void device_present(gs_device_t *device) { if (!SwapBuffers(device->cur_swap->wi->hdc)) { diff --git a/libobs/graphics/device-exports.h b/libobs/graphics/device-exports.h index 290d33f94..6200796ed 100644 --- a/libobs/graphics/device-exports.h +++ b/libobs/graphics/device-exports.h @@ -133,6 +133,7 @@ EXPORT void device_load_swapchain(gs_device_t *device, EXPORT void device_clear(gs_device_t *device, uint32_t clear_flags, const struct vec4 *color, float depth, uint8_t stencil); +EXPORT bool device_is_present_ready(gs_device_t *device); EXPORT void device_present(gs_device_t *device); EXPORT void device_flush(gs_device_t *device); EXPORT void device_set_cull_mode(gs_device_t *device, enum gs_cull_mode mode); diff --git a/libobs/graphics/graphics-imports.c b/libobs/graphics/graphics-imports.c index 4a7e31d9b..b7a32890e 100644 --- a/libobs/graphics/graphics-imports.c +++ b/libobs/graphics/graphics-imports.c @@ -96,6 +96,7 @@ bool load_graphics_imports(struct gs_exports *exports, void *module, GRAPHICS_IMPORT(device_load_swapchain); GRAPHICS_IMPORT(device_end_scene); GRAPHICS_IMPORT(device_clear); + GRAPHICS_IMPORT(device_is_present_ready); GRAPHICS_IMPORT(device_present); GRAPHICS_IMPORT(device_flush); GRAPHICS_IMPORT(device_set_cull_mode); diff --git a/libobs/graphics/graphics-internal.h b/libobs/graphics/graphics-internal.h index fb2a75e3c..434b76187 100644 --- a/libobs/graphics/graphics-internal.h +++ b/libobs/graphics/graphics-internal.h @@ -133,6 +133,7 @@ struct gs_exports { void (*device_clear)(gs_device_t *device, uint32_t clear_flags, const struct vec4 *color, float depth, uint8_t stencil); + bool (*device_is_present_ready)(gs_device_t *device); void (*device_present)(gs_device_t *device); void (*device_flush)(gs_device_t *device); void (*device_set_cull_mode)(gs_device_t *device, diff --git a/libobs/graphics/graphics.c b/libobs/graphics/graphics.c index 66365e1bf..c68b26afc 100644 --- a/libobs/graphics/graphics.c +++ b/libobs/graphics/graphics.c @@ -1943,6 +1943,16 @@ void gs_clear(uint32_t clear_flags, const struct vec4 *color, float depth, depth, stencil); } +bool gs_is_present_ready(void) +{ + graphics_t *graphics = thread_graphics; + + if (!gs_valid("gs_is_present_ready")) + return false; + + return graphics->exports.device_is_present_ready(graphics->device); +} + void gs_present(void) { graphics_t *graphics = thread_graphics; diff --git a/libobs/graphics/graphics.h b/libobs/graphics/graphics.h index fa9f9e55b..b18f5d9bb 100644 --- a/libobs/graphics/graphics.h +++ b/libobs/graphics/graphics.h @@ -742,6 +742,7 @@ EXPORT void gs_end_scene(void); EXPORT void gs_load_swapchain(gs_swapchain_t *swapchain); EXPORT void gs_clear(uint32_t clear_flags, const struct vec4 *color, float depth, uint8_t stencil); +EXPORT bool gs_is_present_ready(void); EXPORT void gs_present(void); EXPORT void gs_flush(void); diff --git a/libobs/obs-display.c b/libobs/obs-display.c index 9f12a2b47..75ed1ecbe 100644 --- a/libobs/obs-display.c +++ b/libobs/obs-display.c @@ -166,7 +166,7 @@ void obs_display_remove_draw_callback(obs_display_t *display, pthread_mutex_unlock(&display->draw_callbacks_mutex); } -static inline void render_display_begin(struct obs_display *display, +static inline bool render_display_begin(struct obs_display *display, uint32_t cx, uint32_t cy, bool update_color_space) { @@ -182,23 +182,29 @@ static inline void render_display_begin(struct obs_display *display, gs_update_color_space(); } - gs_begin_scene(); + const bool success = gs_is_present_ready(); + if (success) { + gs_begin_scene(); - if (gs_get_color_space() == GS_CS_SRGB) - vec4_from_rgba(&clear_color, display->background_color); - else - vec4_from_rgba_srgb(&clear_color, display->background_color); - clear_color.w = 1.0f; + if (gs_get_color_space() == GS_CS_SRGB) + vec4_from_rgba(&clear_color, display->background_color); + else + vec4_from_rgba_srgb(&clear_color, + display->background_color); + clear_color.w = 1.0f; - gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH | GS_CLEAR_STENCIL, - &clear_color, 1.0f, 0); + gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH | GS_CLEAR_STENCIL, + &clear_color, 1.0f, 0); - gs_enable_depth_test(false); - /* gs_enable_blending(false); */ - gs_set_cull_mode(GS_NEITHER); + gs_enable_depth_test(false); + /* gs_enable_blending(false); */ + gs_set_cull_mode(GS_NEITHER); - gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f); - gs_set_viewport(0, 0, cx, cy); + gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f); + gs_set_viewport(0, 0, cx, cy); + } + + return success; } static inline void render_display_end() @@ -230,24 +236,24 @@ void render_display(struct obs_display *display) /* -------------------------------------------- */ - render_display_begin(display, cx, cy, update_color_space); + if (render_display_begin(display, cx, cy, update_color_space)) { + pthread_mutex_lock(&display->draw_callbacks_mutex); - pthread_mutex_lock(&display->draw_callbacks_mutex); + for (size_t i = 0; i < display->draw_callbacks.num; i++) { + struct draw_callback *callback; + callback = display->draw_callbacks.array + i; - for (size_t i = 0; i < display->draw_callbacks.num; i++) { - struct draw_callback *callback; - callback = display->draw_callbacks.array + i; + callback->draw(callback->param, cx, cy); + } - callback->draw(callback->param, cx, cy); + pthread_mutex_unlock(&display->draw_callbacks_mutex); + + render_display_end(); + + GS_DEBUG_MARKER_END(); + + gs_present(); } - - pthread_mutex_unlock(&display->draw_callbacks_mutex); - - render_display_end(); - - GS_DEBUG_MARKER_END(); - - gs_present(); } void obs_display_set_enabled(obs_display_t *display, bool enable)