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.
master
jpark37 2022-08-03 20:19:29 -07:00 committed by Jim
parent 2b4629848d
commit 4fe6803fe4
11 changed files with 90 additions and 54 deletions

View File

@ -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<IDXGISwapChain2> swap2 = ComQIPtr<IDXGISwapChain2>(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");

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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)) {

View File

@ -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);

View File

@ -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);

View File

@ -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,

View File

@ -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;

View File

@ -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);

View File

@ -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)