* Display resolution and refresh rate can be forced using a property.
Other modes reported as supported by the display are ignored when forcing
a specific mode.
* Force resolution and refresh rate for headless mode as well.
This is based on following 4 commits with further updates.
Author: Chih-Wei Huang <cwhuang@linux.org.tw>
Date: Wed Sep 19 22:57:32 2018 +0800
drm_hwcomposer: allow to force mode by a property
The desired resolution could be set by property debug.drm.mode.force.
The other modes are ignored.
Author: Konsta <konsta09@gmail.com>
Date: Sun Dec 5 15:40:40 2021 +0200
drm_hwcomposer: use first refresh rate reported by the display
* Android can switch between different refresh rates which
can lead to display changing modes in some cases.
* Use the first refresh rate reported by the display when
forcing the resolution unless specified otherwise.
Author: Konsta <konsta09@gmail.com>
Date: Tue Jan 25 17:46:33 2022 +0200
drm_hwcomposer: Default to 1920x1080 resolution for headless mode
Author: Konsta <konsta09@gmail.com>
Date: Tue Feb 1 17:40:43 2022 +0200
drm_hwcomposer: Allow setting resolution for headless mode
198 lines
6.3 KiB
C++
198 lines
6.3 KiB
C++
/*
|
|
* Copyright (C) 2022 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define LOG_TAG "drmhwc"
|
|
|
|
#include "HwcDisplayConfigs.h"
|
|
|
|
#include <cmath>
|
|
#include <cstring>
|
|
|
|
#include "compositor/DisplayInfo.h"
|
|
#include "drm/DrmConnector.h"
|
|
#include "utils/properties.h"
|
|
|
|
constexpr uint32_t kSyncLen = 10;
|
|
constexpr uint32_t kBackPorch = 10;
|
|
constexpr uint32_t kFrontPorch = 10;
|
|
constexpr uint32_t kHzInKHz = 1000;
|
|
|
|
namespace android::drm_hwcomposer {
|
|
|
|
void HwcDisplayConfigs::GenFakeMode(uint16_t width, uint16_t height) {
|
|
std::string force_mode = Properties::GetForceMode();
|
|
uint32_t xres = 0, yres = 0, rate = 0;
|
|
// Parse <xres>x<yres>[@<refreshrate>]
|
|
if (sscanf(force_mode.c_str(), "%dx%d@%d", &xres, &yres, &rate) != 3) {
|
|
rate = 0;
|
|
if (sscanf(force_mode.c_str(), "%dx%d", &xres, &yres) != 2) {
|
|
xres = yres = 0;
|
|
}
|
|
}
|
|
ALOGI_IF(xres && yres, "Force mode %dx%d@%dHz for HEADLESS-MODE",
|
|
xres, yres, rate);
|
|
|
|
const uint32_t kHeadlessModeDisplayWidthMm = xres ? xres : 160;
|
|
const uint32_t kHeadlessModeDisplayHeightMm = yres ? yres : 90;
|
|
const uint32_t kHeadlessModeDisplayWidthPx = xres ? xres : 1920;
|
|
const uint32_t kHeadlessModeDisplayHeightPx = yres ? yres : 1080;
|
|
const uint32_t kHeadlessModeDisplayVRefresh = rate ? rate : 60;
|
|
|
|
hwc_configs.clear();
|
|
|
|
preferred_config_id = active_config_id = next_config_id++;
|
|
auto headless_drm_mode_info = (drmModeModeInfo){
|
|
.hdisplay = width,
|
|
.vdisplay = height,
|
|
.vrefresh = kHeadlessModeDisplayVRefresh,
|
|
.name = "VIRTUAL-MODE",
|
|
};
|
|
|
|
if (width == 0 || height == 0) {
|
|
strcpy(headless_drm_mode_info.name, "HEADLESS-MODE");
|
|
headless_drm_mode_info.hdisplay = kHeadlessModeDisplayWidthPx;
|
|
headless_drm_mode_info.vdisplay = kHeadlessModeDisplayHeightPx;
|
|
}
|
|
|
|
/* We need a valid mode to pass the kernel validation */
|
|
|
|
headless_drm_mode_info.hsync_start = headless_drm_mode_info.hdisplay +
|
|
kFrontPorch;
|
|
headless_drm_mode_info.hsync_end = headless_drm_mode_info.hsync_start +
|
|
kSyncLen;
|
|
headless_drm_mode_info.htotal = headless_drm_mode_info.hsync_end + kBackPorch;
|
|
|
|
headless_drm_mode_info.vsync_start = headless_drm_mode_info.vdisplay +
|
|
kFrontPorch;
|
|
headless_drm_mode_info.vsync_end = headless_drm_mode_info.vsync_start +
|
|
kSyncLen;
|
|
headless_drm_mode_info.vtotal = headless_drm_mode_info.vsync_end + kBackPorch;
|
|
|
|
headless_drm_mode_info.clock = (headless_drm_mode_info.htotal *
|
|
headless_drm_mode_info.vtotal *
|
|
headless_drm_mode_info.vrefresh) /
|
|
kHzInKHz;
|
|
|
|
hwc_configs[active_config_id] = (HwcDisplayConfig){
|
|
.id = active_config_id,
|
|
.group_id = 1,
|
|
.mode = DrmMode(&headless_drm_mode_info),
|
|
.output_type = OutputType::kSystem,
|
|
};
|
|
|
|
mm_width = kHeadlessModeDisplayWidthMm;
|
|
mm_height = kHeadlessModeDisplayHeightMm;
|
|
|
|
ALOGI("Add mode %dx%d@%dHz for %s",
|
|
headless_drm_mode_info.hdisplay, headless_drm_mode_info.vdisplay,
|
|
headless_drm_mode_info.vrefresh, headless_drm_mode_info.name);
|
|
}
|
|
|
|
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);
|
|
|
|
// Probe the connector for modes (IOCTL).
|
|
auto ret = connector.UpdateModes();
|
|
if (ret != 0) {
|
|
ALOGE("Failed to update display modes %d", ret);
|
|
return false;
|
|
}
|
|
|
|
if (connector.GetModes().empty()) {
|
|
ALOGE("No modes reported by KMS");
|
|
return false;
|
|
}
|
|
|
|
hwc_configs.clear();
|
|
preferred_config_id = 0;
|
|
mm_width = connector.GetMmWidth();
|
|
mm_height = connector.GetMmHeight();
|
|
|
|
ConfigId first_config_id = next_config_id;
|
|
uint32_t next_group_id = 1;
|
|
for (const auto &mode : connector.GetModes()) {
|
|
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)",
|
|
mode.GetName().c_str());
|
|
disabled = true;
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
if ((mode.GetRawMode().type & DRM_MODE_TYPE_PREFERRED) != 0 &&
|
|
preferred_config_id == 0) {
|
|
preferred_config_id = new_config_id;
|
|
}
|
|
}
|
|
|
|
/* We must have preferred mode. Set first mode as preferred
|
|
* in case KMS haven't reported anything. */
|
|
if (preferred_config_id == 0 && !hwc_configs.empty()) {
|
|
preferred_config_id = first_config_id;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
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;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace android::drm_hwcomposer
|