1
0
Fork 0

drm_hwcomposer: Re-enable MRR on AL devices

Configuration groups for the Multiple Refresh Rate (MRR) feature support
is currently disabled in DRM HWComposer. This is because it is
insufficient to group modes solely based on their active region size.

The primary reasons for this insufficiency are:
1. drm-hwc clients may assume they can seamlessly switch between
   configurations within the same group to support variable refresh
   rates.
2. Not all System-on-a-Chip (SoC) architectures support seamless mode
   transitions.
3. For SoCs that do support seamless switching, transitions often depend
   on other hardware-specific properties like matching blanking periods.
4. A fallback to a non-seamless (blocking) modeset is unacceptable for
   internal displays if a seamless transition fails, as this causes
   flickering. The current framework does not handle such failures
   gracefully.

To Resolve these challenges and re-enable seamless Multiple Refresh-Rate
transitions on internal panels, we add logic to validate all configs
against the preferred initial active config. This validation preforms a
seamless modeset test on each available config against the config that
was set during the display initialization. Configs that fail the
screening are assigned new group IDs.

Caveats:
1. Currently, this is only implemented for internal panels. External
   display seamless transition between variable refresh rates is not
   support. However this work can be extended to support seamless
   transitions on external displays as well.
2. Grouping is only preformed for the preffered/active config at the
   time of display init, and is valid only for the duration of the
   current full modeset commit. No other groups are not created (since
   they cannot be validated), and any new full commits will not honor
   the guarantee provided by the check. This work can be extended to
   refresh and validate all configs within the same group ID of any
   incoming full modeset commit.

Change-Id: Ie2661e74bd3ff704b40c8c7ef50c56e604e5e8df
Signed-off-by: Gil Dekel <gildekel@google.com>
This commit is contained in:
Gil Dekel 2025-07-07 17:35:37 -04:00
parent a4016556d0
commit 3b2050b7f2
8 changed files with 149 additions and 168 deletions

View file

@ -110,7 +110,7 @@ bool DrmAtomicStateManager::CommitFrame(AtomicCommitArgs &args) {
return false;
}
uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET;
uint32_t flags = args.seamless ? 0U : DRM_MODE_ATOMIC_ALLOW_MODESET;
const int error_buf_max_size = 64;
char err_buf[error_buf_max_size];
auto *drm = pipe_->device;
@ -119,7 +119,8 @@ bool DrmAtomicStateManager::CommitFrame(AtomicCommitArgs &args) {
auto err = drmModeAtomicCommit(*drm->GetFd(), pset.get(),
flags | DRM_MODE_ATOMIC_TEST_ONLY, drm);
ALOGE_IF(err != 0, "Test-only ret=%d errno=%d strerror=%s\n", err, errno,
ALOGW_IF(err != 0, "Test-only seamless=%d ret=%d errno=%d strerror=%s\n",
args.seamless, err, errno,
strerror_r(errno, err_buf, error_buf_max_size));
return err == 0;
}
@ -128,11 +129,18 @@ bool DrmAtomicStateManager::CommitFrame(AtomicCommitArgs &args) {
bool nonblock = !args.blocking && !args.active;
if (nonblock) {
flags |= DRM_MODE_ATOMIC_NONBLOCK;
flags |= nonblock ? DRM_MODE_ATOMIC_NONBLOCK : 0U;
auto err = drmModeAtomicCommit(*drm->GetFd(), pset.get(), flags, drm);
if (err != 0 && args.seamless) {
ALOGE(
"Seamless commit failed, retrying a full modeset (visual artifacts may "
"be observed). Error: %s",
strerror_r(errno, err_buf, error_buf_max_size));
err = drmModeAtomicCommit(*drm->GetFd(), pset.get(),
flags | DRM_MODE_ATOMIC_ALLOW_MODESET, drm);
}
auto err = drmModeAtomicCommit(*drm->GetFd(), pset.get(), flags, drm);
if (err != 0) {
ALOGE("Failed to commit pset ret=%d errno=%d strerror=%s\n", err, errno,
strerror_r(errno, err_buf, error_buf_max_size));

View file

@ -54,6 +54,7 @@ struct AtomicCommitArgs {
bool test_only = false;
bool blocking = false;
bool teardown = false;
bool seamless = false;
std::optional<DrmMode> display_mode;
std::optional<bool> active;
std::shared_ptr<DrmKmsPlan> composition;

View file

@ -19,18 +19,14 @@
#include "HwcDisplay.h"
#include <cinttypes>
#include <ui/ColorSpace.h>
#include "backend/Backend.h"
#include "backend/BackendManager.h"
#include "bufferinfo/BufferInfoGetter.h"
#include "compositor/DisplayInfo.h"
#include "drm/DrmConnector.h"
#include "drm/DrmDisplayPipeline.h"
#include "drm/DrmHwc.h"
#include "utils/log.h"
#include "utils/properties.h"
using ::android::DrmDisplayPipeline;
@ -235,46 +231,12 @@ HwcDisplay::ConfigError HwcDisplay::SetConfig(ConfigId config) {
return ConfigError::kNone;
}
const HwcDisplayConfig *current_config = GetCurrentConfig();
const uint32_t width = new_config->mode.GetRawMode().hdisplay;
const uint32_t height = new_config->mode.GetRawMode().vdisplay;
std::optional<LayerData> modeset_layer_data;
// If a client layer has already been provided, and its size matches the
// new config, use it for the modeset.
if (client_layer_.IsLayerUsableAsDevice() && current_config &&
current_config->mode.GetRawMode().hdisplay == width &&
current_config->mode.GetRawMode().vdisplay == height) {
ALOGV("Use existing client_layer for blocking config.");
modeset_layer_data = client_layer_.GetLayerData();
} else {
ALOGV("Allocate modeset buffer.");
auto modeset_buffer = //
GetPipe().device->CreateBufferForModeset(width, height);
if (modeset_buffer) {
auto modeset_layer = std::make_unique<HwcLayer>(this);
HwcLayer::LayerProperties properties;
properties.slot_buffer = {
.slot_id = 0,
.bi = modeset_buffer,
};
properties.active_slot = {
.slot_id = 0,
.fence = {},
};
properties.blend_mode = BufferBlendMode::kNone;
modeset_layer->SetLayerProperties(properties);
modeset_layer->PopulateLayerData();
modeset_layer_data = modeset_layer->GetLayerData();
}
}
ALOGV("Create modeset commit.");
SetOutputType(new_config->output_type);
// Create atomic commit args for a blocking modeset. There's no need to do a
// separate test commit, since the commit does a test anyways.
std::optional<LayerData> modeset_layer_data = GetModesetLayerData(new_config);
AtomicCommitArgs commit_args = CreateModesetCommit(new_config,
modeset_layer_data);
commit_args.blocking = true;
@ -615,11 +577,19 @@ bool HwcDisplay::Init() {
pipeline_->writeback_connector = pipeline_->connector;
} else if (IsInHeadlessMode()) {
configs_.GenFakeMode(0, 0);
} else if (!configs_.Update(*pipeline_->connector->Get())) {
} else if (!configs_.Init(*pipeline_->connector->Get())) {
return false;
}
return SetConfig(configs_.preferred_config_id) ==
HwcDisplay::ConfigError::kNone;
if (SetConfig(configs_.preferred_config_id) !=
HwcDisplay::ConfigError::kNone) {
return false;
}
if (GetPipe().connector->Get()->IsInternal()) {
SetConfigGroupsForActiveConfig();
}
return true;
}
std::optional<PanelOrientation> HwcDisplay::getDisplayPhysicalOrientation() {
@ -814,6 +784,7 @@ bool HwcDisplay::CreateComposition(AtomicCommitArgs &a_args) {
configs_.active_config_id = staged_mode_config_id_.value();
a_args.display_mode = staged_config->mode;
a_args.seamless = true;
if (!a_args.test_only) {
new_vsync_period_ns = staged_config->mode.GetVSyncPeriodNs();
}
@ -1081,4 +1052,62 @@ bool HwcDisplay::NeedsClientLayerUpdate() const {
});
}
std::optional<LayerData> HwcDisplay::GetModesetLayerData(
const HwcDisplayConfig *new_config) {
const uint32_t new_width = new_config->mode.GetRawMode().hdisplay;
const uint32_t new_height = new_config->mode.GetRawMode().vdisplay;
const HwcDisplayConfig *active_config = GetCurrentConfig();
if (client_layer_.IsLayerUsableAsDevice() && active_config &&
active_config->mode.GetRawMode().hdisplay == new_width &&
active_config->mode.GetRawMode().vdisplay == new_height) {
ALOGV("Use existing client_layer for config.");
return client_layer_.GetLayerData();
}
ALOGV("Allocate modeset buffer.");
auto modeset_buffer = GetPipe().device->CreateBufferForModeset(new_width,
new_height);
if (!modeset_buffer)
return std::nullopt;
auto modeset_layer = std::make_unique<HwcLayer>(this);
modeset_layer->SetLayerProperties({
.slot_buffer = std::optional<HwcLayer::Buffer>({
.slot_id = 0,
.bi = modeset_buffer,
}),
.active_slot = std::optional<HwcLayer::Slot>({
.slot_id = 0,
.fence = {},
}),
.blend_mode = BufferBlendMode::kNone,
});
modeset_layer->PopulateLayerData();
return modeset_layer->GetLayerData();
}
void HwcDisplay::SetConfigGroupsForActiveConfig() {
const auto *active_config = GetCurrentConfig();
if (!active_config) {
ALOGW("Could not fetch active config for config group assignment.");
return;
}
const std::optional<LayerData> modeset_layer_data = GetModesetLayerData(
active_config);
for (auto &[_, config] : configs_.hwc_configs) {
AtomicCommitArgs commit_args = CreateModesetCommit(&config,
modeset_layer_data);
commit_args.test_only = true;
commit_args.seamless = true;
if (pipeline_->atomic_state_manager->ExecuteAtomicCommit(commit_args)) {
config.group_id = active_config->group_id;
}
}
configs_.SanitizeGroups();
}
} // namespace android

View file

@ -16,9 +16,7 @@
#pragma once
#include <atomic>
#include <optional>
#include <sstream>
#include <ui/GraphicTypes.h>
@ -28,7 +26,6 @@
#include "compositor/FlatteningController.h"
#include "compositor/LayerData.h"
#include "drm/DrmAtomicStateManager.h"
#include "drm/ResourceManager.h"
#include "drm/VSyncWorker.h"
#include "stats/CompositionStats.h"
@ -238,6 +235,15 @@ class HwcDisplay {
uint32_t GetCurrentVsyncPeriodNs() const;
// Returns a client's layer if one was already provided and its size matches
// the new config, otherwise allocates a new one.
std::optional<LayerData> GetModesetLayerData(
const HwcDisplayConfig *new_config);
// Seamless-tests all configs against the active config for future seamless
// transitions and update the config groups.
void SetConfigGroupsForActiveConfig();
HwcDisplayConfigs configs_;
DrmHwc *const hwc_;

View file

@ -23,8 +23,6 @@
#include "compositor/DisplayInfo.h"
#include "drm/DrmConnector.h"
#include "utils/log.h"
#include "utils/properties.h"
constexpr uint32_t kHeadlessModeDisplayWidthMm = 163;
constexpr uint32_t kHeadlessModeDisplayHeightMm = 122;
@ -38,14 +36,10 @@ constexpr uint32_t kHzInKHz = 1000;
namespace android {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
ConfigId HwcDisplayConfigs::last_config_id = 1;
void HwcDisplayConfigs::GenFakeMode(uint16_t width, uint16_t height) {
hwc_configs.clear();
last_config_id++;
preferred_config_id = active_config_id = last_config_id;
preferred_config_id = active_config_id = next_config_id++;
auto headless_drm_mode_info = (drmModeModeInfo){
.hdisplay = width,
.vdisplay = height,
@ -88,13 +82,12 @@ void HwcDisplayConfigs::GenFakeMode(uint16_t width, uint16_t height) {
mm_height = kHeadlessModeDisplayHeightMm;
}
// NOLINTNEXTLINE (readability-function-cognitive-complexity): Fixme
bool HwcDisplayConfigs::Update(DrmConnector &connector) {
/* In case UpdateModes will fail we will still have one mode for headless
* mode
*/
bool HwcDisplayConfigs::Init(DrmConnector &connector) {
// Ensure one config is available for headless mode in case we end up with no
// real modes from the connector.
GenFakeMode(0, 0);
/* Read real configs */
// Probe the connector for modes (IOCTL).
auto ret = connector.UpdateModes();
if (ret != 0) {
ALOGE("Failed to update display modes %d", ret);
@ -107,34 +100,13 @@ bool HwcDisplayConfigs::Update(DrmConnector &connector) {
}
hwc_configs.clear();
preferred_config_id = 0;
mm_width = connector.GetMmWidth();
mm_height = connector.GetMmHeight();
preferred_config_id = 0;
uint32_t preferred_config_group_id = 0;
auto first_config_id = last_config_id;
uint32_t last_group_id = 1;
const bool use_config_groups = Properties::UseConfigGroups();
/* Group modes */
ConfigId first_config_id = next_config_id;
uint32_t next_group_id = 1;
for (const auto &mode : connector.GetModes()) {
/* Find group for the new mode or create new group */
uint32_t group_found = 0;
if (use_config_groups) {
for (auto &hwc_config : hwc_configs) {
if (mode.GetRawMode().hdisplay ==
hwc_config.second.mode.GetRawMode().hdisplay &&
mode.GetRawMode().vdisplay ==
hwc_config.second.mode.GetRawMode().vdisplay) {
group_found = hwc_config.second.group_id;
}
}
}
if (group_found == 0) {
group_found = last_group_id++;
}
bool disabled = false;
if ((mode.GetRawMode().flags & DRM_MODE_FLAG_3D_MASK) != 0) {
ALOGI("Disabling display mode %s (Modes with 3D flag aren't supported)",
@ -142,97 +114,62 @@ bool HwcDisplayConfigs::Update(DrmConnector &connector) {
disabled = true;
}
/* Add config */
hwc_configs[last_config_id] = {
.id = last_config_id,
.group_id = group_found,
const ConfigId new_config_id = next_config_id++;
const uint32_t new_group_id = next_group_id++;
hwc_configs[new_config_id] = {
.id = new_config_id,
.group_id = new_group_id,
.mode = mode,
.disabled = disabled,
.output_type = OutputType::kSystem,
};
/* Chwck if the mode is preferred */
if ((mode.GetRawMode().type & DRM_MODE_TYPE_PREFERRED) != 0 &&
preferred_config_id == 0) {
preferred_config_id = last_config_id;
preferred_config_group_id = group_found;
preferred_config_id = new_config_id;
}
last_config_id++;
}
/* We must have preferred mode. Set first mode as preferred
* in case KMS haven't reported anything. */
if (preferred_config_id == 0) {
if (preferred_config_id == 0 && !hwc_configs.empty()) {
preferred_config_id = first_config_id;
preferred_config_group_id = 1;
}
for (uint32_t group = 1; group < last_group_id; group++) {
bool has_interlaced = false;
bool has_progressive = false;
for (auto &hwc_config : hwc_configs) {
if (hwc_config.second.group_id != group || hwc_config.second.disabled) {
continue;
}
return true;
}
if (hwc_config.second.IsInterlaced()) {
has_interlaced = true;
} else {
has_progressive = true;
}
}
auto has_both = has_interlaced && has_progressive;
if (!has_both) {
continue;
}
bool group_contains_preferred_interlaced = false;
if (group == preferred_config_group_id &&
hwc_configs[preferred_config_id].IsInterlaced()) {
group_contains_preferred_interlaced = true;
}
for (auto &hwc_config : hwc_configs) {
if (hwc_config.second.group_id != group || hwc_config.second.disabled) {
continue;
}
auto disable = group_contains_preferred_interlaced
? !hwc_config.second.IsInterlaced()
: hwc_config.second.IsInterlaced();
if (disable) {
ALOGI(
"Group %i: Disabling display mode %s (This group should consist "
"of %s modes)",
group, hwc_config.second.mode.GetName().c_str(),
group_contains_preferred_interlaced ? "interlaced" : "progressive");
hwc_config.second.disabled = true;
}
}
}
/* Group should not contain 2 modes with FPS delta less than ~1HZ
bool HwcDisplayConfigs::SanitizeGroups() {
/* A config group should not contain 2 modes with FPS delta less than ~1HZ
* otherwise android.graphics.cts.SetFrameRateTest CTS will fail
*/
constexpr float kMinFpsDelta = 1.0; // FPS
for (ConfigId m1 = first_config_id; m1 < last_config_id; m1++) {
for (ConfigId m2 = first_config_id; m2 < last_config_id; m2++) {
if (m1 != m2 && hwc_configs[m1].group_id == hwc_configs[m2].group_id &&
!hwc_configs[m1].disabled && !hwc_configs[m2].disabled &&
fabsf(hwc_configs[m1].mode.GetVRefresh() -
hwc_configs[m2].mode.GetVRefresh()) < kMinFpsDelta) {
ALOGI(
"Group %i: Disabling display mode %s (Refresh rate value is "
"too close to existing mode %s)",
hwc_configs[m2].group_id, hwc_configs[m2].mode.GetName().c_str(),
hwc_configs[m1].mode.GetName().c_str());
hwc_configs[m2].disabled = true;
constexpr float kMinFpsDelta = 1.0;
for (const auto &[id1, config1] : hwc_configs) {
for (auto &[id2, config2] : hwc_configs) {
if (id1 == id2) {
continue;
}
if (config1.group_id != config2.group_id) {
continue;
}
if (config1.disabled || config2.disabled) {
continue;
}
if (fabsf(config1.mode.GetVRefresh() - config2.mode.GetVRefresh()) >=
kMinFpsDelta) {
continue;
}
ALOGI(
"Group %i: Disabling display mode %s (Refresh rate value is "
"too close to existing mode %s)",
config2.group_id, config2.mode.GetName().c_str(),
config1.mode.GetName().c_str());
config2.disabled = true;
}
}

View file

@ -49,16 +49,21 @@ struct HwcDisplayConfig {
};
struct HwcDisplayConfigs {
bool Update(DrmConnector &conn);
bool Init(DrmConnector &connector);
void GenFakeMode(uint16_t width, uint16_t height);
// Removes problematic configs from groups after they were set.
bool SanitizeGroups();
std::map<ConfigId, struct HwcDisplayConfig> hwc_configs;
ConfigId active_config_id = 0;
ConfigId preferred_config_id = 0;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static ConfigId last_config_id;
// Use sequential config IDs throughout the lifetime of the owner display to
// prevent race conditions around hotplugs (mode updates). See:
// https://source.android.com/docs/core/graphics/hotplug#prevent-race-conditions
ConfigId next_config_id = 1;
uint32_t mm_width = 0;
uint32_t mm_height = 0;

View file

@ -94,10 +94,6 @@ auto Properties::IsPresentFenceNotReliable() -> bool {
0) != 0);
}
auto Properties::UseConfigGroups() -> bool {
return (property_get_bool("ro.vendor.hwc.drm.use_config_groups", 0) != 0);
}
auto Properties::InternalDisplayNames() -> std::string {
char buf[PROPERTY_VALUE_MAX] = {};
property_get("vendor.hwc.drm.internal_display_names", buf, "");
@ -148,4 +144,4 @@ auto Properties::GetDevicePath() -> std::string {
return {path_pattern};
}
} // namespace android
} // namespace android

View file

@ -29,7 +29,6 @@ enum class CtmHandling {
class Properties {
public:
static auto IsPresentFenceNotReliable() -> bool;
static auto UseConfigGroups() -> bool;
static auto InternalDisplayNames() -> std::string;
static auto UseOverlayPlanes() -> bool;
static auto ScaleWithGpu() -> bool;