diff --git a/src/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp index 6623c9e9..23534a74 100644 --- a/src/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -538,15 +538,28 @@ namespace cuda { } else if (descriptor.sequence > sequence) { sequence = descriptor.sequence; - rgb = egl::rgb_t {}; - auto rgb_opt = egl::import_source(display.get(), descriptor.sd); if (!rgb_opt) { - return -1; - } + // The plane's current format/modifier can't be imported (e.g. a game switched to a + // 10bpc swapchain in exclusive fullscreen that this driver won't bind to a GL texture). + // Encode a blank frame instead of dropping the client, and only log once per failure + // streak to avoid flooding the log every frame. + if (!import_failed_last_frame) { + BOOST_LOG(warning) << "Skipping frame(s): plane format (fourcc: "sv << util::hex(descriptor.sd.fourcc).to_string_view() + << ") failed to import; will resume automatically if the format changes back"sv; + import_failed_last_frame = true; + } - rgb = std::move(*rgb_opt); + rgb = egl::create_blank(img); + } else { + if (import_failed_last_frame) { + BOOST_LOG(info) << "Resumed capture after plane format import failure"sv; + import_failed_last_frame = false; + } + + rgb = std::move(*rgb_opt); + } } auto fmt_desc = av_pix_fmt_desc_get(sw_format); @@ -640,6 +653,8 @@ namespace cuda { int offset_y; ///< Vertical offset in physical pixels. bool is_yuv444; ///< Whether the CUDA converter outputs YUV 4:4:4. + + bool import_failed_last_frame = false; ///< Whether the previous frame's plane import failed, to avoid log spam. }; /** diff --git a/src/platform/linux/graphics.cpp b/src/platform/linux/graphics.cpp index 1ef27c71..0ae0e94e 100644 --- a/src/platform/linux/graphics.cpp +++ b/src/platform/linux/graphics.cpp @@ -655,10 +655,23 @@ namespace egl { gl::ctx.BindTexture(GL_TEXTURE_2D, rgb->tex[0]); if (!gl::egl_image_target_texture_2d()) { BOOST_LOG(error) << "glEGLImageTargetTexture2DOES is not available; cannot import RGB DMA-BUF"sv; + gl::ctx.BindTexture(GL_TEXTURE_2D, 0); return std::nullopt; } gl::egl_image_target_texture_2d()(GL_TEXTURE_2D, rgb->xrgb8); + // Some drivers accept an EGLImage of a given DRM format/modifier from eglCreateImage() + // but then reject binding it to a GL texture, e.g. Mesa/RADV rejecting a 10bpc format + // like DRM_FORMAT_XBGR2101010. When that happens, the texture is left with stale or + // incomplete contents, so this must be treated as an import failure rather than + // silently streaming whatever ends up in the texture. + if (auto err = gl::ctx.GetError(); err != GL_NO_ERROR) { + BOOST_LOG(error) << "Failed to bind EGLImage (DRM fourcc: "sv << util::hex(xrgb.fourcc).to_string_view() + << ") to GL texture: "sv << util::hex(err).to_string_view(); + gl::ctx.BindTexture(GL_TEXTURE_2D, 0); + return std::nullopt; + } + gl::ctx.BindTexture(GL_TEXTURE_2D, 0); gl_drain_errors; diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index 05c972a1..56b12666 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -1668,7 +1668,21 @@ namespace platf { auto rgb_opt = egl::import_source(display.get(), sd); if (!rgb_opt) { - return capture_e::error; + // The plane's current format/modifier can't be imported (e.g. a game switched to a + // 10bpc swapchain in exclusive fullscreen that this driver won't bind to a GL texture). + // Skip this frame rather than tearing down the whole capture thread for every client, + // and only log once per failure streak to avoid flooding the log every frame. + if (!import_failed_last_frame) { + BOOST_LOG(warning) << "Skipping frame(s): plane format (fourcc: "sv << util::hex(sd.fourcc).to_string_view() + << ") failed to import; will resume automatically if the format changes back"sv; + import_failed_last_frame = true; + } + return capture_e::timeout; + } + + if (import_failed_last_frame) { + BOOST_LOG(info) << "Resumed capture after plane format import failure"sv; + import_failed_last_frame = false; } auto &rgb = *rgb_opt; @@ -1726,6 +1740,7 @@ namespace platf { gbm::gbm_t gbm; ///< GBM device used for buffer allocation. egl::display_t display; ///< EGL display created from the GBM device. egl::ctx_t ctx; ///< EGL context used to copy KMS frames into RAM. + bool import_failed_last_frame = false; ///< Whether the previous frame's plane import failed, to avoid log spam. }; /** diff --git a/src/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp index a9190a7b..4cb30378 100644 --- a/src/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -558,15 +558,28 @@ namespace va { } else if (descriptor.sequence > sequence) { sequence = descriptor.sequence; - rgb = egl::rgb_t {}; - auto rgb_opt = egl::import_source(display.get(), descriptor.sd); if (!rgb_opt) { - return -1; - } + // The plane's current format/modifier can't be imported (e.g. a game switched to a + // 10bpc swapchain in exclusive fullscreen that this driver won't bind to a GL texture). + // Encode a blank frame instead of dropping the client, and only log once per failure + // streak to avoid flooding the log every frame. + if (!import_failed_last_frame) { + BOOST_LOG(warning) << "Skipping frame(s): plane format (fourcc: "sv << util::hex(descriptor.sd.fourcc).to_string_view() + << ") failed to import; will resume automatically if the format changes back"sv; + import_failed_last_frame = true; + } - rgb = std::move(*rgb_opt); + rgb = egl::create_blank(img); + } else { + if (import_failed_last_frame) { + BOOST_LOG(info) << "Resumed capture after plane format import failure"sv; + import_failed_last_frame = false; + } + + rgb = std::move(*rgb_opt); + } } sws.load_vram(descriptor, offset_x, offset_y, rgb->tex[0], false); @@ -603,6 +616,8 @@ namespace va { int offset_x; ///< Horizontal offset in physical pixels. int offset_y; ///< Vertical offset in physical pixels. + + bool import_failed_last_frame = false; ///< Whether the previous frame's plane import failed, to avoid log spam. }; /**