parent
b108684edc
commit
d50ba06321
2 changed files with 805 additions and 351 deletions
|
|
@ -159,19 +159,11 @@ DrmRenderer::DrmRenderer(AVHWDeviceType hwDeviceType, IFFmpegRenderer *backendRe
|
|||
m_HwContext(nullptr),
|
||||
m_DrmFd(-1),
|
||||
m_DrmIsMaster(false),
|
||||
m_DrmStateModified(false),
|
||||
m_MustCloseDrmFd(false),
|
||||
m_SupportsDirectRendering(false),
|
||||
m_VideoFormat(0),
|
||||
m_ConnectorId(0),
|
||||
m_EncoderId(0),
|
||||
m_CrtcId(0),
|
||||
m_PlaneId(0),
|
||||
m_CurrentFbId(0),
|
||||
m_Plane(nullptr),
|
||||
m_ColorEncodingProp(nullptr),
|
||||
m_ColorRangeProp(nullptr),
|
||||
m_HdrOutputMetadataProp(nullptr),
|
||||
m_ColorspaceProp(nullptr),
|
||||
m_OverlayRects{},
|
||||
m_Version(nullptr),
|
||||
m_HdrOutputMetadataBlobId(0),
|
||||
m_OutputRect{},
|
||||
|
|
@ -186,8 +178,17 @@ DrmRenderer::DrmRenderer(AVHWDeviceType hwDeviceType, IFFmpegRenderer *backendRe
|
|||
|
||||
DrmRenderer::~DrmRenderer()
|
||||
{
|
||||
// Ensure we're out of HDR mode
|
||||
setHdrMode(false);
|
||||
if (m_DrmStateModified) {
|
||||
// Ensure we're out of HDR mode
|
||||
setHdrMode(false);
|
||||
|
||||
// Deactivate all planes
|
||||
m_PropSetter.disablePlane(m_VideoPlane);
|
||||
for (int i = 0; i < Overlay::OverlayMax; i++) {
|
||||
m_PropSetter.disablePlane(m_OverlayPlanes[i]);
|
||||
}
|
||||
m_PropSetter.apply();
|
||||
}
|
||||
|
||||
for (int i = 0; i < k_SwFrameCount; i++) {
|
||||
if (m_SwFrame[i].primeFd) {
|
||||
|
|
@ -205,34 +206,10 @@ DrmRenderer::~DrmRenderer()
|
|||
}
|
||||
}
|
||||
|
||||
if (m_CurrentFbId != 0) {
|
||||
drmModeRmFB(m_DrmFd, m_CurrentFbId);
|
||||
}
|
||||
|
||||
if (m_HdrOutputMetadataBlobId != 0) {
|
||||
drmModeDestroyPropertyBlob(m_DrmFd, m_HdrOutputMetadataBlobId);
|
||||
}
|
||||
|
||||
if (m_ColorEncodingProp != nullptr) {
|
||||
drmModeFreeProperty(m_ColorEncodingProp);
|
||||
}
|
||||
|
||||
if (m_ColorRangeProp != nullptr) {
|
||||
drmModeFreeProperty(m_ColorRangeProp);
|
||||
}
|
||||
|
||||
if (m_HdrOutputMetadataProp != nullptr) {
|
||||
drmModeFreeProperty(m_HdrOutputMetadataProp);
|
||||
}
|
||||
|
||||
if (m_ColorspaceProp != nullptr) {
|
||||
drmModeFreeProperty(m_ColorspaceProp);
|
||||
}
|
||||
|
||||
if (m_Plane != nullptr) {
|
||||
drmModeFreePlane(m_Plane);
|
||||
}
|
||||
|
||||
if (m_Version != nullptr) {
|
||||
drmFreeVersion(m_Version);
|
||||
}
|
||||
|
|
@ -327,7 +304,7 @@ void DrmRenderer::prepareToRender()
|
|||
|
||||
// Set the output rect to match the new CRTC size after modesetting
|
||||
m_OutputRect.x = m_OutputRect.y = 0;
|
||||
drmModeCrtc* crtc = drmModeGetCrtc(m_DrmFd, m_CrtcId);
|
||||
drmModeCrtc* crtc = drmModeGetCrtc(m_DrmFd, m_Crtc.objectId());
|
||||
if (crtc != nullptr) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CRTC size after modesetting: %ux%u",
|
||||
|
|
@ -348,24 +325,9 @@ void DrmRenderer::prepareToRender()
|
|||
m_OutputRect.w,
|
||||
m_OutputRect.h);
|
||||
}
|
||||
}
|
||||
|
||||
bool DrmRenderer::getPropertyByName(drmModeObjectPropertiesPtr props, const char* name, uint64_t *value) {
|
||||
for (uint32_t j = 0; j < props->count_props; j++) {
|
||||
drmModePropertyPtr prop = drmModeGetProperty(m_DrmFd, props->props[j]);
|
||||
if (prop != nullptr) {
|
||||
if (!strcmp(prop->name, name)) {
|
||||
*value = props->prop_values[j];
|
||||
drmModeFreeProperty(prop);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
drmModeFreeProperty(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
// We've now changed state that must be restored
|
||||
m_DrmStateModified = true;
|
||||
}
|
||||
|
||||
bool DrmRenderer::initialize(PDECODER_PARAMETERS params)
|
||||
|
|
@ -490,21 +452,19 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params)
|
|||
}
|
||||
|
||||
// Look for a connected connector and get the associated encoder
|
||||
m_ConnectorId = 0;
|
||||
m_EncoderId = 0;
|
||||
for (i = 0; i < resources->count_connectors && m_EncoderId == 0; i++) {
|
||||
for (i = 0; i < resources->count_connectors && !m_Encoder.isValid(); i++) {
|
||||
drmModeConnector* connector = drmModeGetConnector(m_DrmFd, resources->connectors[i]);
|
||||
if (connector != nullptr) {
|
||||
if (connector->connection == DRM_MODE_CONNECTED && connector->count_modes > 0) {
|
||||
m_ConnectorId = resources->connectors[i];
|
||||
m_EncoderId = connector->encoder_id;
|
||||
m_Connector.load(m_DrmFd, resources->connectors[i], DRM_MODE_OBJECT_CONNECTOR);
|
||||
m_Encoder.load(m_DrmFd, connector->encoder_id, DRM_MODE_OBJECT_ENCODER);
|
||||
}
|
||||
|
||||
drmModeFreeConnector(connector);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_EncoderId == 0) {
|
||||
if (!m_Encoder.isValid()) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No connected displays found!");
|
||||
drmModeFreeResources(resources);
|
||||
|
|
@ -512,19 +472,18 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params)
|
|||
}
|
||||
|
||||
// Now find the CRTC from the encoder
|
||||
m_CrtcId = 0;
|
||||
for (i = 0; i < resources->count_encoders && m_CrtcId == 0; i++) {
|
||||
for (i = 0; i < resources->count_encoders && !m_Crtc.isValid(); i++) {
|
||||
drmModeEncoder* encoder = drmModeGetEncoder(m_DrmFd, resources->encoders[i]);
|
||||
if (encoder != nullptr) {
|
||||
if (encoder->encoder_id == m_EncoderId) {
|
||||
m_CrtcId = encoder->crtc_id;
|
||||
if (encoder->encoder_id == m_Encoder.objectId()) {
|
||||
m_Crtc.load(m_DrmFd, encoder->crtc_id, DRM_MODE_OBJECT_CRTC);
|
||||
}
|
||||
|
||||
drmModeFreeEncoder(encoder);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_CrtcId == 0) {
|
||||
if (!m_Crtc.isValid()) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"DRM encoder not found!");
|
||||
drmModeFreeResources(resources);
|
||||
|
|
@ -533,7 +492,7 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params)
|
|||
|
||||
int crtcIndex = -1;
|
||||
for (int i = 0; i < resources->count_crtcs; i++) {
|
||||
if (resources->crtcs[i] == m_CrtcId) {
|
||||
if (resources->crtcs[i] == m_Crtc.objectId()) {
|
||||
crtcIndex = i;
|
||||
break;
|
||||
}
|
||||
|
|
@ -547,7 +506,19 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params)
|
|||
return DIRECT_RENDERING_INIT_FAILED;
|
||||
}
|
||||
|
||||
drmSetClientCap(m_DrmFd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
|
||||
if (drmSetClientCap(m_DrmFd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Universal planes are not supported!");
|
||||
return DIRECT_RENDERING_INIT_FAILED;
|
||||
}
|
||||
|
||||
bool atomic;
|
||||
if (!Utils::getEnvironmentVariableOverride("DRM_ATOMIC", &atomic)) {
|
||||
// Use atomic by default if available
|
||||
atomic = true;
|
||||
}
|
||||
|
||||
m_PropSetter.initialize(m_DrmFd, atomic, !params->enableVsync);
|
||||
|
||||
drmModePlaneRes* planeRes = drmModeGetPlaneResources(m_DrmFd);
|
||||
if (planeRes == nullptr) {
|
||||
|
|
@ -563,24 +534,21 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params)
|
|||
for (uint32_t i = 0; i < planeRes->count_planes; i++) {
|
||||
drmModePlane* plane = drmModeGetPlane(m_DrmFd, planeRes->planes[i]);
|
||||
if (plane != nullptr) {
|
||||
if (plane->crtc_id == m_CrtcId) {
|
||||
if (plane->crtc_id == m_Crtc.objectId()) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Plane %u is active on CRTC %u",
|
||||
plane->plane_id,
|
||||
m_CrtcId);
|
||||
plane->crtc_id);
|
||||
|
||||
drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(m_DrmFd, planeRes->planes[i], DRM_MODE_OBJECT_PLANE);
|
||||
if (props != nullptr) {
|
||||
// Don't consider cursor planes when searching for the highest active zpos
|
||||
uint64_t type;
|
||||
if (getPropertyByName(props, "type", &type) && (type == DRM_PLANE_TYPE_PRIMARY || type == DRM_PLANE_TYPE_OVERLAY)) {
|
||||
uint64_t zPos;
|
||||
if (getPropertyByName(props, "zpos", &zPos) && zPos > maxActiveZpos) {
|
||||
maxActiveZpos = zPos;
|
||||
// Don't consider cursor planes when searching for the highest active zpos
|
||||
DrmPropertyMap props { m_DrmFd, planeRes->planes[i], DRM_MODE_OBJECT_PLANE };
|
||||
if (props.property("type")->initialValue() == DRM_PLANE_TYPE_PRIMARY ||
|
||||
props.property("type")->initialValue() == DRM_PLANE_TYPE_OVERLAY) {
|
||||
if (auto zpos = props.property("zpos")) {
|
||||
if (zpos->initialValue() > maxActiveZpos) {
|
||||
maxActiveZpos = zpos->initialValue();
|
||||
}
|
||||
}
|
||||
|
||||
drmModeFreeObjectProperties(props);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -601,12 +569,13 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params)
|
|||
allowPrimaryPlane = strcmp(m_Version->name, "spacemit") != 0;
|
||||
}
|
||||
|
||||
// Find a plane with the required format to render on
|
||||
// Find a video plane with the required format to render on
|
||||
//
|
||||
// FIXME: We should check the actual DRM format in a real AVFrame rather
|
||||
// than just assuming it will be a certain hardcoded type like NV12 based
|
||||
// on the chosen video format.
|
||||
for (uint32_t i = 0; i < planeRes->count_planes && !m_PlaneId; i++) {
|
||||
uint64_t videoPlaneZpos = 0;
|
||||
for (uint32_t i = 0; i < planeRes->count_planes && !m_VideoPlane.isValid(); i++) {
|
||||
drmModePlane* plane = drmModeGetPlane(m_DrmFd, planeRes->planes[i]);
|
||||
if (plane != nullptr) {
|
||||
// If the plane can't be used on our CRTC, don't consider it further
|
||||
|
|
@ -620,123 +589,111 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params)
|
|||
// control back to Qt, it will repopulate the plane with the FB it owns and render as normal.
|
||||
|
||||
// Validate that the candidate plane supports our pixel format
|
||||
m_SupportedPlaneFormats.clear();
|
||||
m_SupportedVideoPlaneFormats.clear();
|
||||
for (uint32_t j = 0; j < plane->count_formats; j++) {
|
||||
if (drmFormatMatchesVideoFormat(plane->formats[j], m_VideoFormat)) {
|
||||
m_SupportedPlaneFormats.emplace(plane->formats[j]);
|
||||
m_SupportedVideoPlaneFormats.emplace(plane->formats[j]);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_SupportedPlaneFormats.empty()) {
|
||||
if (m_SupportedVideoPlaneFormats.empty()) {
|
||||
drmModeFreePlane(plane);
|
||||
continue;
|
||||
}
|
||||
|
||||
drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(m_DrmFd, planeRes->planes[i], DRM_MODE_OBJECT_PLANE);
|
||||
if (props != nullptr) {
|
||||
uint64_t type;
|
||||
uint64_t zPos;
|
||||
|
||||
// Only consider overlay and primary (if allowed) planes as valid render targets
|
||||
if (!getPropertyByName(props, "type", &type) ||
|
||||
(type != DRM_PLANE_TYPE_OVERLAY && (type != DRM_PLANE_TYPE_PRIMARY || !allowPrimaryPlane))) {
|
||||
drmModeFreePlane(plane);
|
||||
}
|
||||
// If this plane has a zpos property and it's lower (further from user) than
|
||||
// the highest active plane we found, avoid this plane. It won't be visible.
|
||||
//
|
||||
// Note: zpos is not a required property, but if any plane has it, all planes must.
|
||||
else if (getPropertyByName(props, "zpos", &zPos) && zPos < maxActiveZpos) {
|
||||
drmModeFreePlane(plane);
|
||||
}
|
||||
else {
|
||||
SDL_assert(!m_PlaneId);
|
||||
SDL_assert(!m_Plane);
|
||||
|
||||
m_PlaneId = plane->plane_id;
|
||||
m_Plane = plane;
|
||||
}
|
||||
|
||||
drmModeFreeObjectProperties(props);
|
||||
// Only consider overlay and primary (if allowed) planes as valid render targets
|
||||
DrmPropertyMap props { m_DrmFd, planeRes->planes[i], DRM_MODE_OBJECT_PLANE };
|
||||
if (auto type = props.property("type");
|
||||
type->initialValue() != DRM_PLANE_TYPE_OVERLAY &&
|
||||
(type->initialValue() != DRM_PLANE_TYPE_PRIMARY || !allowPrimaryPlane)) {
|
||||
drmModeFreePlane(plane);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this plane has a zpos property and it's lower (further from user) than
|
||||
// the highest active plane we found, avoid this plane. It won't be visible.
|
||||
//
|
||||
// Note: zpos is not a required property, but if any plane has it, all planes must.
|
||||
auto zpos = props.property("zpos");
|
||||
if (zpos && zpos->initialValue() < maxActiveZpos) {
|
||||
drmModeFreePlane(plane);
|
||||
continue;
|
||||
}
|
||||
|
||||
SDL_assert(!m_VideoPlane.isValid());
|
||||
m_VideoPlane.load(m_DrmFd, plane->plane_id, DRM_MODE_OBJECT_PLANE);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Selected plane %u for video", plane->plane_id);
|
||||
videoPlaneZpos = zpos ? zpos->initialValue() : 0;
|
||||
drmModeFreePlane(plane);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_VideoPlane.isValid()) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to find suitable video plane!");
|
||||
drmModeFreePlaneResources(planeRes);
|
||||
return DIRECT_RENDERING_INIT_FAILED;
|
||||
}
|
||||
|
||||
// Find overlay planes when using the atomic API
|
||||
int overlayIndex = 0;
|
||||
for (uint32_t i = 0; i < planeRes->count_planes && overlayIndex < Overlay::OverlayMax && m_PropSetter.isAtomic(); i++) {
|
||||
drmModePlane* plane = drmModeGetPlane(m_DrmFd, planeRes->planes[i]);
|
||||
if (plane != nullptr) {
|
||||
// If the plane can't be used on our CRTC, don't consider it further
|
||||
if (!(plane->possible_crtcs & (1 << crtcIndex))) {
|
||||
drmModeFreePlane(plane);
|
||||
continue;
|
||||
}
|
||||
|
||||
DrmPropertyMap props { m_DrmFd, planeRes->planes[i], DRM_MODE_OBJECT_PLANE };
|
||||
// Only consider overlay planes as valid targets
|
||||
if (auto type = props.property("type"); type->initialValue() != DRM_PLANE_TYPE_OVERLAY) {
|
||||
drmModeFreePlane(plane);
|
||||
continue;
|
||||
}
|
||||
// If this overlay plane has a zpos property and it's lower (further from user) than
|
||||
// the video plane we selected, avoid this plane. It won't be visible on top.
|
||||
//
|
||||
// Note: zpos is not a required property, but if any plane has it, all planes must.
|
||||
else if (auto zpos = props.property("zpos"); !zpos || zpos->initialValue() <= videoPlaneZpos) {
|
||||
drmModeFreePlane(plane);
|
||||
continue;
|
||||
}
|
||||
|
||||
// The overlay plane must support ARGB8888
|
||||
bool foundFormat = false;
|
||||
for (uint32_t j = 0; j < plane->count_formats; j++) {
|
||||
if (plane->formats[j] == DRM_FORMAT_ARGB8888) {
|
||||
foundFormat = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundFormat) {
|
||||
drmModeFreePlane(plane);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allocate this overlay plane to the next unused overlay slot
|
||||
SDL_assert(!m_OverlayPlanes[overlayIndex].isValid());
|
||||
m_OverlayPlanes[overlayIndex++].load(m_DrmFd, plane->plane_id, DRM_MODE_OBJECT_PLANE);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Selected plane %u for overlay %d",
|
||||
plane->plane_id, overlayIndex);
|
||||
drmModeFreePlane(plane);
|
||||
}
|
||||
}
|
||||
|
||||
drmModeFreePlaneResources(planeRes);
|
||||
|
||||
if (m_PlaneId == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to find suitable primary/overlay plane!");
|
||||
return DIRECT_RENDERING_INIT_FAILED;
|
||||
if (!m_PropSetter.isAtomic()) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Overlays require DRM atomic support");
|
||||
}
|
||||
|
||||
// Populate plane properties
|
||||
{
|
||||
drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(m_DrmFd, m_PlaneId, DRM_MODE_OBJECT_PLANE);
|
||||
if (props != nullptr) {
|
||||
for (uint32_t j = 0; j < props->count_props; j++) {
|
||||
drmModePropertyPtr prop = drmModeGetProperty(m_DrmFd, props->props[j]);
|
||||
if (prop != nullptr) {
|
||||
if (!strcmp(prop->name, "COLOR_ENCODING")) {
|
||||
m_ColorEncodingProp = prop;
|
||||
}
|
||||
else if (!strcmp(prop->name, "COLOR_RANGE")) {
|
||||
m_ColorRangeProp = prop;
|
||||
}
|
||||
else {
|
||||
drmModeFreeProperty(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drmModeFreeObjectProperties(props);
|
||||
}
|
||||
}
|
||||
|
||||
// Populate connector properties
|
||||
{
|
||||
drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(m_DrmFd, m_ConnectorId, DRM_MODE_OBJECT_CONNECTOR);
|
||||
if (props != nullptr) {
|
||||
for (uint32_t j = 0; j < props->count_props; j++) {
|
||||
drmModePropertyPtr prop = drmModeGetProperty(m_DrmFd, props->props[j]);
|
||||
if (prop != nullptr) {
|
||||
if (!strcmp(prop->name, "HDR_OUTPUT_METADATA")) {
|
||||
m_HdrOutputMetadataProp = prop;
|
||||
}
|
||||
else if (!strcmp(prop->name, "Colorspace")) {
|
||||
m_ColorspaceProp = prop;
|
||||
}
|
||||
else if (!strcmp(prop->name, "max bpc") && (m_VideoFormat & VIDEO_FORMAT_MASK_10BIT)) {
|
||||
if (drmModeObjectSetProperty(m_DrmFd, m_ConnectorId, DRM_MODE_OBJECT_CONNECTOR, prop->prop_id, 16) == 0) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Enabled 48-bit HDMI Deep Color");
|
||||
}
|
||||
else if (drmModeObjectSetProperty(m_DrmFd, m_ConnectorId, DRM_MODE_OBJECT_CONNECTOR, prop->prop_id, 12) == 0) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Enabled 36-bit HDMI Deep Color");
|
||||
}
|
||||
else if (drmModeObjectSetProperty(m_DrmFd, m_ConnectorId, DRM_MODE_OBJECT_CONNECTOR, prop->prop_id, 10) == 0) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Enabled 30-bit HDMI Deep Color");
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"drmModeObjectSetProperty(%s) failed: %d",
|
||||
prop->name,
|
||||
errno);
|
||||
// Non-fatal
|
||||
}
|
||||
|
||||
drmModeFreeProperty(prop);
|
||||
}
|
||||
else {
|
||||
drmModeFreeProperty(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drmModeFreeObjectProperties(props);
|
||||
}
|
||||
else if (overlayIndex < Overlay::OverlayMax) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to find suitable overlay planes (%d of %d found)",
|
||||
overlayIndex,
|
||||
Overlay::OverlayMax);
|
||||
}
|
||||
|
||||
// If we got this far, we can do direct rendering via the DRM FD.
|
||||
|
|
@ -782,8 +739,8 @@ bool DrmRenderer::isPixelFormatSupported(int videoFormat, AVPixelFormat pixelFor
|
|||
}
|
||||
|
||||
// If we've been called after initialize(), use the actual supported plane formats
|
||||
if (!m_SupportedPlaneFormats.empty()) {
|
||||
return m_SupportedPlaneFormats.find(avToDrmTuple->second) != m_SupportedPlaneFormats.end();
|
||||
if (!m_SupportedVideoPlaneFormats.empty()) {
|
||||
return m_SupportedVideoPlaneFormats.find(avToDrmTuple->second) != m_SupportedVideoPlaneFormats.end();
|
||||
}
|
||||
else {
|
||||
// If we've been called before initialize(), use any valid plane format for our video formats
|
||||
|
|
@ -840,25 +797,15 @@ int DrmRenderer::getRendererAttributes()
|
|||
|
||||
void DrmRenderer::setHdrMode(bool enabled)
|
||||
{
|
||||
if (m_ColorspaceProp != nullptr) {
|
||||
int err = drmModeObjectSetProperty(m_DrmFd, m_ConnectorId, DRM_MODE_OBJECT_CONNECTOR,
|
||||
m_ColorspaceProp->prop_id,
|
||||
enabled ? DRM_MODE_COLORIMETRY_BT2020_RGB : DRM_MODE_COLORIMETRY_DEFAULT);
|
||||
if (err == 0) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Set HDMI Colorspace: %s",
|
||||
enabled ? "BT.2020 RGB" : "Default");
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"drmModeObjectSetProperty(%s) failed: %d",
|
||||
m_ColorspaceProp->name,
|
||||
errno);
|
||||
// Non-fatal
|
||||
}
|
||||
if (auto prop = m_Connector.property("Colorspace")) {
|
||||
m_PropSetter.set(*prop, enabled ? "BT2020_RGB" : "Default");
|
||||
}
|
||||
|
||||
if (m_HdrOutputMetadataProp != nullptr) {
|
||||
if (auto prop = m_Connector.property("max bpc")) {
|
||||
m_PropSetter.set(*prop, enabled ? 10 : 8);
|
||||
}
|
||||
|
||||
if (auto prop = m_Connector.property("HDR_OUTPUT_METADATA")) {
|
||||
if (m_HdrOutputMetadataBlobId != 0) {
|
||||
drmModeDestroyPropertyBlob(m_DrmFd, m_HdrOutputMetadataBlobId);
|
||||
m_HdrOutputMetadataBlobId = 0;
|
||||
|
|
@ -897,20 +844,7 @@ void DrmRenderer::setHdrMode(bool enabled)
|
|||
}
|
||||
}
|
||||
|
||||
int err = drmModeObjectSetProperty(m_DrmFd, m_ConnectorId, DRM_MODE_OBJECT_CONNECTOR,
|
||||
m_HdrOutputMetadataProp->prop_id,
|
||||
enabled ? m_HdrOutputMetadataBlobId : 0);
|
||||
if (err == 0) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Set display HDR mode: %s", enabled ? "enabled" : "disabled");
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"drmModeObjectSetProperty(%s) failed: %d",
|
||||
m_HdrOutputMetadataProp->name,
|
||||
errno);
|
||||
// Non-fatal
|
||||
}
|
||||
m_PropSetter.set(*prop, enabled ? m_HdrOutputMetadataBlobId : 0);
|
||||
}
|
||||
else if (enabled) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
|
|
@ -1108,6 +1042,145 @@ Exit:
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool DrmRenderer::uploadSurfaceToFb(SDL_Surface *surface, uint32_t* handle, uint32_t* fbId)
|
||||
{
|
||||
struct drm_mode_create_dumb createBuf = {};
|
||||
void* mapping;
|
||||
uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0};
|
||||
|
||||
createBuf.width = surface->w;
|
||||
createBuf.height = surface->h;
|
||||
createBuf.bpp = 32;
|
||||
int err = drmIoctl(m_DrmFd, DRM_IOCTL_MODE_CREATE_DUMB, &createBuf);
|
||||
if (err < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"DRM_IOCTL_MODE_CREATE_DUMB failed: %d",
|
||||
errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct drm_mode_map_dumb mapBuf = {};
|
||||
mapBuf.handle = createBuf.handle;
|
||||
err = drmIoctl(m_DrmFd, DRM_IOCTL_MODE_MAP_DUMB, &mapBuf);
|
||||
if (err < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"DRM_IOCTL_MODE_MAP_DUMB failed: %d",
|
||||
errno);
|
||||
goto Fail;
|
||||
}
|
||||
|
||||
// Raspberry Pi on kernel 6.1 defaults to an aarch64 kernel with a 32-bit userspace (and off_t).
|
||||
// This leads to issues when DRM_IOCTL_MODE_MAP_DUMB returns a > 4GB offset. The high bits are
|
||||
// chopped off when passed via the normal mmap() call using 32-bit off_t. We avoid this issue
|
||||
// by explicitly calling mmap64() to ensure the 64-bit offset is never truncated.
|
||||
#if defined(__GLIBC__) && QT_POINTER_SIZE == 4
|
||||
mapping = mmap64(nullptr, createBuf.size, PROT_WRITE, MAP_SHARED, m_DrmFd, mapBuf.offset);
|
||||
#else
|
||||
mapping = mmap(nullptr, createBuf.size, PROT_WRITE, MAP_SHARED, m_DrmFd, mapBuf.offset);
|
||||
#endif
|
||||
if (mapping == MAP_FAILED) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"mmap() failed for dumb buffer: %d",
|
||||
errno);
|
||||
goto Fail;
|
||||
}
|
||||
|
||||
// Convert and copy the surface pixels into the dumb buffer with premultiplied alpha
|
||||
SDL_PremultiplyAlpha(surface->w, surface->h, surface->format->format, surface->pixels, surface->pitch,
|
||||
SDL_PIXELFORMAT_ARGB8888, mapping, createBuf.pitch);
|
||||
|
||||
munmap(mapping, createBuf.size);
|
||||
|
||||
// Create a FB backed by the dumb buffer
|
||||
handles[0] = createBuf.handle;
|
||||
pitches[0] = createBuf.pitch;
|
||||
err = drmModeAddFB2(m_DrmFd, createBuf.width, createBuf.height,
|
||||
DRM_FORMAT_ARGB8888,
|
||||
handles, pitches, offsets, fbId, 0);
|
||||
if (err < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"drmModeAddFB2() failed: %d",
|
||||
errno);
|
||||
goto Fail;
|
||||
}
|
||||
|
||||
*handle = createBuf.handle;
|
||||
return true;
|
||||
|
||||
Fail:
|
||||
struct drm_mode_destroy_dumb destroyBuf = {};
|
||||
destroyBuf.handle = createBuf.handle;
|
||||
drmIoctl(m_DrmFd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroyBuf);
|
||||
return false;
|
||||
}
|
||||
|
||||
void DrmRenderer::notifyOverlayUpdated(Overlay::OverlayType type)
|
||||
{
|
||||
// If we are not using atomic KMS, we can't support overlays
|
||||
if (!m_PropSetter.isAtomic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have a plane for this overlay, we can't draw it
|
||||
if (!m_OverlayPlanes[type].isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't upload if the overlay is disabled
|
||||
if (!Session::get()->getOverlayManager().isOverlayEnabled(type)) {
|
||||
// Turn the overlay plane off when transitioning from enabled to disabled
|
||||
if (m_OverlayRects[type].w || m_OverlayRects[type].h) {
|
||||
m_PropSetter.disablePlane(m_OverlayPlanes[type]);
|
||||
memset(&m_OverlayRects[type], 0, sizeof(m_OverlayRects[type]));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Upload a new overlay surface if needed
|
||||
SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type);
|
||||
if (newSurface != nullptr) {
|
||||
uint32_t dumbBuffer, fbId;
|
||||
if (!uploadSurfaceToFb(newSurface, &dumbBuffer, &fbId)) {
|
||||
SDL_FreeSurface(newSurface);
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Rect overlayRect = {};
|
||||
|
||||
if (type == Overlay::OverlayStatusUpdate) {
|
||||
// Bottom Left
|
||||
overlayRect.x = 0;
|
||||
overlayRect.y = m_OutputRect.h - newSurface->h;
|
||||
}
|
||||
else if (type == Overlay::OverlayDebug) {
|
||||
// Top left
|
||||
overlayRect.x = 0;
|
||||
overlayRect.y = 0;
|
||||
}
|
||||
|
||||
overlayRect.w = newSurface->w;
|
||||
overlayRect.h = newSurface->h;
|
||||
|
||||
// If we changed our overlay rect, we need to reconfigure the plane
|
||||
if (memcmp(&m_OverlayRects[type], &overlayRect, sizeof(overlayRect)) != 0) {
|
||||
m_PropSetter.configurePlane(m_OverlayPlanes[type], m_Crtc.objectId(),
|
||||
overlayRect.x, overlayRect.y, overlayRect.w, overlayRect.h,
|
||||
0, 0,
|
||||
newSurface->w << 16,
|
||||
newSurface->h << 16);
|
||||
memcpy(&m_OverlayRects[type], &overlayRect, sizeof(overlayRect));
|
||||
}
|
||||
|
||||
// Queue the plane flip with the new FB
|
||||
//
|
||||
// NB: This takes ownership of the FB and dumb buffer, even on failure
|
||||
m_PropSetter.flipPlane(m_OverlayPlanes[type], fbId, dumbBuffer);
|
||||
|
||||
SDL_FreeSurface(newSurface);
|
||||
}
|
||||
}
|
||||
|
||||
bool DrmRenderer::addFbForFrame(AVFrame *frame, uint32_t* newFbId, bool testMode)
|
||||
{
|
||||
AVDRMFrameDescriptor mappedFrame;
|
||||
|
|
@ -1192,16 +1265,38 @@ bool DrmRenderer::addFbForFrame(AVFrame *frame, uint32_t* newFbId, bool testMode
|
|||
}
|
||||
|
||||
if (testMode) {
|
||||
drmModePlanePtr videoPlane = drmModeGetPlane(m_DrmFd, m_VideoPlane.objectId());
|
||||
if (!videoPlane) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"drmModeGetPlane() failed: %d",
|
||||
errno);
|
||||
drmModeRmFB(m_DrmFd, *newFbId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if plane can actually be imported
|
||||
for (uint32_t i = 0; i < m_Plane->count_formats; i++) {
|
||||
if (drmFrame->layers[0].format == m_Plane->formats[i]) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Selected DRM plane supports chosen decoding format: %08x",
|
||||
drmFrame->layers[0].format);
|
||||
return true;
|
||||
bool formatMatch = false;
|
||||
for (uint32_t i = 0; i < videoPlane->count_formats; i++) {
|
||||
if (drmFrame->layers[0].format == videoPlane->formats[i]) {
|
||||
formatMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
drmModeFreePlane(videoPlane);
|
||||
|
||||
if (!formatMatch) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Selected DRM plane doesn't support chosen decoding format: %08x",
|
||||
drmFrame->layers[0].format);
|
||||
drmModeRmFB(m_DrmFd, *newFbId);
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Selected DRM plane supports chosen decoding format: %08x",
|
||||
drmFrame->layers[0].format);
|
||||
|
||||
// TODO: We can also check the modifier support using the IN_FORMATS property,
|
||||
// but checking format alone is probably enough for real world cases since we're
|
||||
// either getting linear buffers from software mapping or DMA-BUFs from the
|
||||
|
|
@ -1210,16 +1305,9 @@ bool DrmRenderer::addFbForFrame(AVFrame *frame, uint32_t* newFbId, bool testMode
|
|||
// Hopefully no actual hardware vendors are dumb enough to ship display hardware
|
||||
// or drivers that lack support for the format modifiers required by their own
|
||||
// video decoders.
|
||||
}
|
||||
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Selected DRM plane doesn't support chosen decoding format: %08x",
|
||||
drmFrame->layers[0].format);
|
||||
drmModeRmFB(m_DrmFd, *newFbId);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DrmRenderer::drmFormatMatchesVideoFormat(uint32_t drmFormat, int videoFormat)
|
||||
|
|
@ -1247,137 +1335,83 @@ bool DrmRenderer::drmFormatMatchesVideoFormat(uint32_t drmFormat, int videoForma
|
|||
|
||||
void DrmRenderer::renderFrame(AVFrame* frame)
|
||||
{
|
||||
int err;
|
||||
SDL_Rect src, dst;
|
||||
|
||||
SDL_assert(m_OutputRect.w > 0 && m_OutputRect.h > 0);
|
||||
|
||||
src.x = src.y = 0;
|
||||
src.w = frame->width;
|
||||
src.h = frame->height;
|
||||
dst = m_OutputRect;
|
||||
|
||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
||||
|
||||
// Remember the last FB object we created so we can free it
|
||||
// when we are finished rendering this one (if successful).
|
||||
uint32_t lastFbId = m_CurrentFbId;
|
||||
|
||||
// Register a frame buffer object for this frame
|
||||
if (!addFbForFrame(frame, &m_CurrentFbId, false)) {
|
||||
m_CurrentFbId = lastFbId;
|
||||
uint32_t fbId;
|
||||
if (!addFbForFrame(frame, &fbId, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasFrameFormatChanged(frame)) {
|
||||
SDL_Rect src, dst;
|
||||
src.x = src.y = 0;
|
||||
src.w = frame->width;
|
||||
src.h = frame->height;
|
||||
dst = m_OutputRect;
|
||||
|
||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
||||
|
||||
// Set the video plane size and location
|
||||
m_PropSetter.configurePlane(m_VideoPlane, m_Crtc.objectId(),
|
||||
dst.x, dst.y,
|
||||
dst.w, dst.h,
|
||||
0, 0,
|
||||
frame->width << 16,
|
||||
frame->height << 16);
|
||||
|
||||
// Set COLOR_RANGE property for the plane
|
||||
{
|
||||
if (auto prop = m_VideoPlane.property("COLOR_RANGE")) {
|
||||
const char* desiredValue = getDrmColorRangeValue(frame);
|
||||
|
||||
if (m_ColorRangeProp != nullptr && desiredValue != nullptr) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < m_ColorRangeProp->count_enums; i++) {
|
||||
if (!strcmp(desiredValue, m_ColorRangeProp->enums[i].name)) {
|
||||
err = drmModeObjectSetProperty(m_DrmFd, m_PlaneId, DRM_MODE_OBJECT_PLANE,
|
||||
m_ColorRangeProp->prop_id, m_ColorRangeProp->enums[i].value);
|
||||
if (err == 0) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"%s: %s",
|
||||
m_ColorRangeProp->name,
|
||||
desiredValue);
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"drmModeObjectSetProperty(%s) failed: %d",
|
||||
m_ColorRangeProp->name,
|
||||
errno);
|
||||
// Non-fatal
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == m_ColorRangeProp->count_enums) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to find matching COLOR_RANGE value for '%s'. Colors may be inaccurate!",
|
||||
desiredValue);
|
||||
}
|
||||
if (prop->containsValue(desiredValue)) {
|
||||
m_PropSetter.set(*prop, desiredValue);
|
||||
}
|
||||
else if (desiredValue != nullptr) {
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"COLOR_RANGE property does not exist on output plane. Colors may be inaccurate!");
|
||||
"Unable to find matching COLOR_RANGE value for '%s'. Colors may be inaccurate!",
|
||||
desiredValue);
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"COLOR_RANGE property does not exist on video plane. Colors may be inaccurate!");
|
||||
}
|
||||
|
||||
|
||||
// Set COLOR_ENCODING property for the plane
|
||||
{
|
||||
if (auto prop = m_VideoPlane.property("COLOR_ENCODING")) {
|
||||
const char* desiredValue = getDrmColorEncodingValue(frame);
|
||||
|
||||
if (m_ColorEncodingProp != nullptr && desiredValue != nullptr) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < m_ColorEncodingProp->count_enums; i++) {
|
||||
if (!strcmp(desiredValue, m_ColorEncodingProp->enums[i].name)) {
|
||||
err = drmModeObjectSetProperty(m_DrmFd, m_PlaneId, DRM_MODE_OBJECT_PLANE,
|
||||
m_ColorEncodingProp->prop_id, m_ColorEncodingProp->enums[i].value);
|
||||
if (err == 0) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"%s: %s",
|
||||
m_ColorEncodingProp->name,
|
||||
desiredValue);
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"drmModeObjectSetProperty(%s) failed: %d",
|
||||
m_ColorEncodingProp->name,
|
||||
errno);
|
||||
// Non-fatal
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == m_ColorEncodingProp->count_enums) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to find matching COLOR_ENCODING value for '%s'. Colors may be inaccurate!",
|
||||
desiredValue);
|
||||
}
|
||||
if (prop->containsValue(desiredValue)) {
|
||||
m_PropSetter.set(*prop, desiredValue);
|
||||
}
|
||||
else if (desiredValue != nullptr) {
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"COLOR_ENCODING property does not exist on output plane. Colors may be inaccurate!");
|
||||
"Unable to find matching COLOR_ENCODING value for '%s'. Colors may be inaccurate!",
|
||||
desiredValue);
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"COLOR_ENCODING property does not exist on video plane. Colors may be inaccurate!");
|
||||
}
|
||||
}
|
||||
|
||||
// Update the overlay
|
||||
err = drmModeSetPlane(m_DrmFd, m_PlaneId, m_CrtcId, m_CurrentFbId, 0,
|
||||
dst.x, dst.y,
|
||||
dst.w, dst.h,
|
||||
0, 0,
|
||||
frame->width << 16,
|
||||
frame->height << 16);
|
||||
if (err < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"drmModeSetPlane() failed: %d",
|
||||
errno);
|
||||
drmModeRmFB(m_DrmFd, m_CurrentFbId);
|
||||
m_CurrentFbId = lastFbId;
|
||||
return;
|
||||
}
|
||||
// Update the video plane
|
||||
//
|
||||
// NB: This takes ownership of fbId, even on failure
|
||||
m_PropSetter.flipPlane(m_VideoPlane, fbId, 0);
|
||||
|
||||
// Free the previous FB object which has now been superseded
|
||||
drmModeRmFB(m_DrmFd, lastFbId);
|
||||
// Apply pending atomic transaction (if in atomic mode)
|
||||
m_PropSetter.apply();
|
||||
}
|
||||
|
||||
bool DrmRenderer::testRenderFrame(AVFrame* frame) {
|
||||
uint32_t fbId;
|
||||
|
||||
// If we don't even have a plane, we certainly can't render
|
||||
if (!m_Plane) {
|
||||
if (!m_VideoPlane.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1400,15 +1434,12 @@ bool DrmRenderer::isDirectRenderingSupported()
|
|||
|
||||
int DrmRenderer::getDecoderColorspace()
|
||||
{
|
||||
if (m_ColorEncodingProp != nullptr) {
|
||||
// Search for a COLOR_ENCODING property that fits a value we support
|
||||
for (int i = 0; i < m_ColorEncodingProp->count_enums; i++) {
|
||||
if (!strcmp(m_ColorEncodingProp->enums[i].name, "ITU-R BT.601 YCbCr")) {
|
||||
return COLORSPACE_REC_601;
|
||||
}
|
||||
else if (!strcmp(m_ColorEncodingProp->enums[i].name, "ITU-R BT.709 YCbCr")) {
|
||||
return COLORSPACE_REC_709;
|
||||
}
|
||||
if (auto prop = m_VideoPlane.property("COLOR_ENCODING")) {
|
||||
if (prop->containsValue("ITU-R BT.601 YCbCr")) {
|
||||
return COLORSPACE_REC_601;
|
||||
}
|
||||
else if (prop->containsValue("ITU-R BT.709 YCbCr")) {
|
||||
return COLORSPACE_REC_709;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
#include <xf86drmMode.h>
|
||||
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
|
||||
// Newer libdrm headers have these HDR structs, but some older ones don't.
|
||||
namespace DrmDefs
|
||||
|
|
@ -49,6 +51,428 @@ namespace DrmDefs
|
|||
}
|
||||
|
||||
class DrmRenderer : public IFFmpegRenderer {
|
||||
class DrmProperty {
|
||||
public:
|
||||
DrmProperty(uint32_t objectId, uint32_t objectType, drmModePropertyPtr prop, uint64_t initialValue) :
|
||||
m_ObjectId(objectId), m_ObjectType(objectType), m_Prop(prop), m_InitialValue(initialValue) {
|
||||
for (int i = 0; i < m_Prop->count_enums; i++) {
|
||||
m_Values.emplace(m_Prop->enums[i].name, m_Prop->enums[i].value);
|
||||
}
|
||||
}
|
||||
|
||||
~DrmProperty() noexcept {
|
||||
if (m_Prop) {
|
||||
drmModeFreeProperty(m_Prop);
|
||||
}
|
||||
}
|
||||
|
||||
DrmProperty(DrmProperty &&other) = delete;
|
||||
DrmProperty(const DrmProperty &) = delete;
|
||||
|
||||
bool isImmutable() const {
|
||||
return m_Prop->flags & DRM_MODE_PROP_IMMUTABLE;
|
||||
}
|
||||
|
||||
const char* name() const {
|
||||
return m_Prop->name;
|
||||
}
|
||||
|
||||
uint32_t id() const {
|
||||
return m_Prop->prop_id;
|
||||
}
|
||||
|
||||
std::pair<uint64_t, uint64_t> range() const {
|
||||
if ((m_Prop->flags & (DRM_MODE_PROP_RANGE | DRM_MODE_PROP_SIGNED_RANGE)) &&
|
||||
m_Prop->count_values == 2) {
|
||||
return std::make_pair(m_Prop->values[0], m_Prop->values[1]);
|
||||
}
|
||||
else {
|
||||
SDL_assert(false);
|
||||
return std::make_pair(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool containsValue(const std::string &name) const {
|
||||
return m_Values.find(name) != m_Values.end();
|
||||
}
|
||||
|
||||
uint64_t value(const std::string &name) const {
|
||||
return m_Values.find(name)->second;
|
||||
}
|
||||
|
||||
uint32_t objectId() const {
|
||||
return m_ObjectId;
|
||||
}
|
||||
|
||||
uint32_t objectType() const {
|
||||
return m_ObjectType;
|
||||
}
|
||||
|
||||
uint64_t initialValue() const {
|
||||
return m_InitialValue;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t m_ObjectId;
|
||||
uint32_t m_ObjectType;
|
||||
drmModePropertyPtr m_Prop;
|
||||
std::unordered_map<std::string, uint64_t> m_Values;
|
||||
uint64_t m_InitialValue;
|
||||
};
|
||||
|
||||
class DrmPropertyMap {
|
||||
public:
|
||||
DrmPropertyMap() {}
|
||||
DrmPropertyMap(int fd, uint32_t objectId, uint32_t objectType) {
|
||||
SDL_assert(m_ObjectType == 0);
|
||||
SDL_assert(m_ObjectId == 0);
|
||||
load(fd, objectId, objectType);
|
||||
}
|
||||
|
||||
DrmPropertyMap(const DrmPropertyMap &) = delete;
|
||||
DrmPropertyMap(DrmPropertyMap &&other) {
|
||||
m_Props = std::move(other.m_Props);
|
||||
m_ObjectId = other.m_ObjectId;
|
||||
m_ObjectType = other.m_ObjectType;
|
||||
}
|
||||
|
||||
bool isValid() const {
|
||||
return m_ObjectType != 0;
|
||||
}
|
||||
|
||||
void load(int fd, uint32_t objectId, uint32_t objectType) {
|
||||
drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(fd, objectId, objectType);
|
||||
if (props) {
|
||||
for (uint32_t i = 0; i < props->count_props; i++) {
|
||||
drmModePropertyPtr prop = drmModeGetProperty(fd, props->props[i]);
|
||||
if (prop) {
|
||||
// DrmProperty takes ownership of the drmModePropertyPtr
|
||||
m_Props.try_emplace(prop->name, objectId, objectType, prop, props->prop_values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
drmModeFreeObjectProperties(props);
|
||||
}
|
||||
|
||||
m_ObjectId = objectId;
|
||||
m_ObjectType = objectType;
|
||||
}
|
||||
|
||||
bool hasProperty(const std::string& name) const {
|
||||
return m_Props.find(name) != m_Props.end();
|
||||
}
|
||||
|
||||
const DrmProperty* property(const std::string& name) const {
|
||||
auto it = m_Props.find(name);
|
||||
if (it == m_Props.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
uint32_t objectId() const {
|
||||
return m_ObjectId;
|
||||
}
|
||||
|
||||
uint32_t objectType() const {
|
||||
return m_ObjectType;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t m_ObjectId = 0;
|
||||
uint32_t m_ObjectType = 0;
|
||||
std::unordered_map<std::string, DrmProperty> m_Props;
|
||||
};
|
||||
|
||||
class DrmPropertySetter {
|
||||
struct PlaneConfiguration {
|
||||
uint32_t crtcId;
|
||||
int32_t crtcX, crtcY;
|
||||
uint32_t crtcW, crtcH, srcX, srcY, srcW, srcH;
|
||||
};
|
||||
|
||||
struct PlaneBuffer {
|
||||
uint32_t fbId;
|
||||
uint32_t dumbBufferHandle;
|
||||
|
||||
// Atomic only
|
||||
uint32_t pendingFbId;
|
||||
uint32_t pendingDumbBuffer;
|
||||
bool modified;
|
||||
};
|
||||
|
||||
public:
|
||||
DrmPropertySetter() {}
|
||||
~DrmPropertySetter() {
|
||||
for (auto it = m_PlaneBuffers.begin(); it != m_PlaneBuffers.end(); it++) {
|
||||
SDL_assert(!it->second.fbId);
|
||||
SDL_assert(!it->second.dumbBufferHandle);
|
||||
|
||||
if (it->second.pendingFbId) {
|
||||
drmModeRmFB(m_Fd, it->second.pendingFbId);
|
||||
}
|
||||
if (it->second.pendingDumbBuffer) {
|
||||
struct drm_mode_destroy_dumb destroyBuf = {};
|
||||
destroyBuf.handle = it->second.pendingDumbBuffer;
|
||||
drmIoctl(m_Fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroyBuf);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_AtomicReq) {
|
||||
drmModeAtomicFree(m_AtomicReq);
|
||||
}
|
||||
}
|
||||
|
||||
DrmPropertySetter(const DrmPropertySetter &) = delete;
|
||||
DrmPropertySetter(DrmPropertySetter &&) = delete;
|
||||
|
||||
void initialize(int drmFd, bool wantsAtomic, bool wantsAsyncFlip) {
|
||||
m_Fd = drmFd;
|
||||
m_Atomic = wantsAtomic && drmSetClientCap(drmFd, DRM_CLIENT_CAP_ATOMIC, 1) == 0;
|
||||
m_AsyncFlip = wantsAsyncFlip;
|
||||
}
|
||||
|
||||
bool set(const DrmProperty& prop, uint64_t value, bool verbose = true) {
|
||||
bool ret;
|
||||
|
||||
if (m_Atomic) {
|
||||
// Synchronize with other threads that might be committing or setting properties
|
||||
std::lock_guard lg { m_Lock };
|
||||
|
||||
if (!m_AtomicReq) {
|
||||
m_AtomicReq = drmModeAtomicAlloc();
|
||||
}
|
||||
|
||||
ret = drmModeAtomicAddProperty(m_AtomicReq, prop.objectId(), prop.id(), value) > 0;
|
||||
}
|
||||
else {
|
||||
ret = drmModeObjectSetProperty(m_Fd, prop.objectId(), prop.objectType(), prop.id(), value) == 0;
|
||||
}
|
||||
|
||||
if (verbose && ret) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Set property '%s': %" PRIu64,
|
||||
prop.name(),
|
||||
value);
|
||||
}
|
||||
else if (!ret) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to set property '%s': %d",
|
||||
prop.name(),
|
||||
errno);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool set(const DrmProperty& prop, const std::string &value) {
|
||||
if (set(prop, prop.value(value), false)) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Set property '%s': %s",
|
||||
prop.name(),
|
||||
value.c_str());
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Unconditionally takes ownership of fbId and dumbBufferHandle (if present)
|
||||
bool flipPlane(const DrmPropertyMap& plane, uint32_t fbId, uint32_t dumbBufferHandle) {
|
||||
bool ret;
|
||||
|
||||
if (m_Atomic) {
|
||||
std::lock_guard lg { m_Lock };
|
||||
|
||||
ret = set(*plane.property("FB_ID"), fbId, false);
|
||||
if (ret) {
|
||||
// If we updated the FB_ID property, free the old pending buffer.
|
||||
// Otherwise, we'll free the new buffer which was never used.
|
||||
std::swap(fbId, m_PlaneBuffers[plane.objectId()].pendingFbId);
|
||||
std::swap(dumbBufferHandle, m_PlaneBuffers[plane.objectId()].pendingDumbBuffer);
|
||||
m_PlaneBuffers[plane.objectId()].modified = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
PlaneConfiguration planeConfig;
|
||||
|
||||
{
|
||||
// Latch the plane configuration and release the lock
|
||||
std::lock_guard lg { m_Lock };
|
||||
planeConfig = m_PlaneConfigs.at(plane.objectId());
|
||||
}
|
||||
|
||||
ret = drmModeSetPlane(m_Fd, plane.objectId(),
|
||||
planeConfig.crtcId, fbId, 0,
|
||||
planeConfig.crtcX, planeConfig.crtcY,
|
||||
planeConfig.crtcW, planeConfig.crtcH,
|
||||
planeConfig.srcX, planeConfig.srcY,
|
||||
planeConfig.srcW, planeConfig.srcH) == 0;
|
||||
|
||||
// If we succeeded updating the plane, free the old FB state
|
||||
// Otherwise, we'll free the new data which was never used.
|
||||
if (ret) {
|
||||
std::lock_guard lg { m_Lock };
|
||||
|
||||
std::swap(fbId, m_PlaneBuffers[plane.objectId()].fbId);
|
||||
std::swap(dumbBufferHandle, m_PlaneBuffers[plane.objectId()].dumbBufferHandle);
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"drmModeSetPlane() failed: %d",
|
||||
errno);
|
||||
}
|
||||
}
|
||||
|
||||
// Free the unused resources
|
||||
if (fbId) {
|
||||
drmModeRmFB(m_Fd, fbId);
|
||||
}
|
||||
if (dumbBufferHandle) {
|
||||
struct drm_mode_destroy_dumb destroyBuf = {};
|
||||
destroyBuf.handle = dumbBufferHandle;
|
||||
drmIoctl(m_Fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroyBuf);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool configurePlane(const DrmPropertyMap& plane,
|
||||
uint32_t crtcId,
|
||||
int32_t crtcX, int32_t crtcY,
|
||||
uint32_t crtcW, uint32_t crtcH,
|
||||
uint32_t srcX, uint32_t srcY,
|
||||
uint32_t srcW, uint32_t srcH) {
|
||||
bool ret = true;
|
||||
std::lock_guard lg { m_Lock };
|
||||
|
||||
if (m_Atomic) {
|
||||
ret = ret && set(*plane.property("CRTC_ID"), crtcId, false);
|
||||
ret = ret && set(*plane.property("CRTC_X"), crtcX, false);
|
||||
ret = ret && set(*plane.property("CRTC_Y"), crtcY, false);
|
||||
ret = ret && set(*plane.property("CRTC_W"), crtcW, false);
|
||||
ret = ret && set(*plane.property("CRTC_H"), crtcH, false);
|
||||
ret = ret && set(*plane.property("SRC_X"), srcX, false);
|
||||
ret = ret && set(*plane.property("SRC_Y"), srcY, false);
|
||||
ret = ret && set(*plane.property("SRC_W"), srcW, false);
|
||||
ret = ret && set(*plane.property("SRC_H"), srcH, false);
|
||||
}
|
||||
else {
|
||||
auto& planeConfig = m_PlaneConfigs[plane.objectId()];
|
||||
planeConfig.crtcId = crtcId;
|
||||
planeConfig.crtcX = crtcX;
|
||||
planeConfig.crtcY = crtcY;
|
||||
planeConfig.crtcW = crtcW;
|
||||
planeConfig.crtcH = crtcH;
|
||||
planeConfig.srcX = srcX;
|
||||
planeConfig.srcY = srcY;
|
||||
planeConfig.srcW = srcW;
|
||||
planeConfig.srcH = srcH;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void disablePlane(const DrmPropertyMap& plane) {
|
||||
if (plane.isValid()) {
|
||||
configurePlane(plane, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
flipPlane(plane, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool apply() {
|
||||
if (!m_Atomic) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
drmModeAtomicReqPtr req = nullptr;
|
||||
std::unordered_map<uint32_t, PlaneBuffer> pendingBuffers;
|
||||
|
||||
{
|
||||
// Take ownership of the current atomic request to commit it and
|
||||
// allow other threads to queue up changes for the next one.
|
||||
std::lock_guard lg { m_Lock };
|
||||
std::swap(req, m_AtomicReq);
|
||||
std::swap(pendingBuffers, m_PlaneBuffers);
|
||||
}
|
||||
|
||||
if (!req) {
|
||||
// Nothing to apply
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try an async flip if requested
|
||||
bool ret = drmModeAtomicCommit(m_Fd, req, m_AsyncFlip ? DRM_MODE_PAGE_FLIP_ASYNC : 0, nullptr) == 0;
|
||||
|
||||
// The driver may not support async flips (especially if we changed a non-FB_ID property),
|
||||
// so try again with a regular flip if we get an error from the async flip attempt.
|
||||
if (!ret && m_AsyncFlip) {
|
||||
ret = drmModeAtomicCommit(m_Fd, req, 0, nullptr) == 0;
|
||||
}
|
||||
|
||||
// If we flipped to a new buffer, free the old one
|
||||
if (ret) {
|
||||
std::lock_guard lg { m_Lock };
|
||||
|
||||
// Update the buffer state for any modified planes
|
||||
for (auto it = pendingBuffers.begin(); it != pendingBuffers.end(); it++) {
|
||||
if (it->second.modified) {
|
||||
if (it->second.fbId) {
|
||||
drmModeRmFB(m_Fd, it->second.fbId);
|
||||
it->second.fbId = 0;
|
||||
}
|
||||
if (it->second.dumbBufferHandle) {
|
||||
struct drm_mode_destroy_dumb destroyBuf = {};
|
||||
destroyBuf.handle = it->second.dumbBufferHandle;
|
||||
drmIoctl(m_Fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroyBuf);
|
||||
it->second.dumbBufferHandle = 0;
|
||||
}
|
||||
|
||||
// The pending buffers become the active buffers for this FB
|
||||
m_PlaneBuffers[it->first].fbId = it->second.pendingFbId;
|
||||
m_PlaneBuffers[it->first].dumbBufferHandle = it->second.pendingDumbBuffer;
|
||||
}
|
||||
else if (it->second.fbId || it->second.dumbBufferHandle) {
|
||||
// This FB wasn't modified in this commit, so the current buffers stay around
|
||||
m_PlaneBuffers[it->first].fbId = it->second.fbId;
|
||||
m_PlaneBuffers[it->first].dumbBufferHandle = it->second.dumbBufferHandle;
|
||||
}
|
||||
|
||||
// NB: We swapped in a new plane buffers map which will clear the modified value.
|
||||
// It's important that we don't try to clear it here because we might stomp on
|
||||
// a flipPlane() performed by another thread that queued up another modification.
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"drmModeAtomicCommit() failed: %d",
|
||||
errno);
|
||||
}
|
||||
|
||||
drmModeAtomicFree(req);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool isAtomic() {
|
||||
return m_Atomic;
|
||||
}
|
||||
|
||||
private:
|
||||
int m_Fd = -1;
|
||||
bool m_Atomic = false;
|
||||
bool m_AsyncFlip = false;
|
||||
std::recursive_mutex m_Lock;
|
||||
std::unordered_map<uint32_t, PlaneBuffer> m_PlaneBuffers;
|
||||
|
||||
// Legacy context
|
||||
std::unordered_map<uint32_t, PlaneConfiguration> m_PlaneConfigs;
|
||||
|
||||
// Atomic context
|
||||
drmModeAtomicReqPtr m_AtomicReq = nullptr;
|
||||
};
|
||||
|
||||
public:
|
||||
DrmRenderer(AVHWDeviceType hwDeviceType = AV_HWDEVICE_TYPE_NONE, IFFmpegRenderer *backendRenderer = nullptr);
|
||||
virtual ~DrmRenderer() override;
|
||||
|
|
@ -64,6 +488,7 @@ public:
|
|||
virtual bool isDirectRenderingSupported() override;
|
||||
virtual int getDecoderColorspace() override;
|
||||
virtual void setHdrMode(bool enabled) override;
|
||||
virtual void notifyOverlayUpdated(Overlay::OverlayType type) override;
|
||||
#ifdef HAVE_EGL
|
||||
virtual bool canExportEGL() override;
|
||||
virtual AVPixelFormat getEGLImagePixelFormat() override;
|
||||
|
|
@ -73,11 +498,11 @@ public:
|
|||
#endif
|
||||
|
||||
private:
|
||||
bool getPropertyByName(drmModeObjectPropertiesPtr props, const char* name, uint64_t *value);
|
||||
const char* getDrmColorEncodingValue(AVFrame* frame);
|
||||
const char* getDrmColorRangeValue(AVFrame* frame);
|
||||
bool mapSoftwareFrame(AVFrame* frame, AVDRMFrameDescriptor* mappedFrame);
|
||||
bool addFbForFrame(AVFrame* frame, uint32_t* newFbId, bool testMode);
|
||||
bool uploadSurfaceToFb(SDL_Surface *surface, uint32_t* handle, uint32_t* fbId);
|
||||
static bool drmFormatMatchesVideoFormat(uint32_t drmFormat, int videoFormat);
|
||||
|
||||
IFFmpegRenderer* m_BackendRenderer;
|
||||
|
|
@ -87,23 +512,21 @@ private:
|
|||
AVBufferRef* m_HwContext;
|
||||
int m_DrmFd;
|
||||
bool m_DrmIsMaster;
|
||||
bool m_DrmStateModified;
|
||||
bool m_MustCloseDrmFd;
|
||||
bool m_SupportsDirectRendering;
|
||||
int m_VideoFormat;
|
||||
uint32_t m_ConnectorId;
|
||||
uint32_t m_EncoderId;
|
||||
uint32_t m_CrtcId;
|
||||
uint32_t m_PlaneId;
|
||||
uint32_t m_CurrentFbId;
|
||||
drmModePlanePtr m_Plane;
|
||||
drmModePropertyPtr m_ColorEncodingProp;
|
||||
drmModePropertyPtr m_ColorRangeProp;
|
||||
drmModePropertyPtr m_HdrOutputMetadataProp;
|
||||
drmModePropertyPtr m_ColorspaceProp;
|
||||
DrmPropertyMap m_Encoder;
|
||||
DrmPropertyMap m_Connector;
|
||||
DrmPropertyMap m_Crtc;
|
||||
DrmPropertyMap m_VideoPlane;
|
||||
DrmPropertyMap m_OverlayPlanes[Overlay::OverlayMax];
|
||||
DrmPropertySetter m_PropSetter;
|
||||
SDL_Rect m_OverlayRects[Overlay::OverlayMax];
|
||||
drmVersionPtr m_Version;
|
||||
uint32_t m_HdrOutputMetadataBlobId;
|
||||
SDL_Rect m_OutputRect;
|
||||
std::set<uint32_t> m_SupportedPlaneFormats;
|
||||
std::set<uint32_t> m_SupportedVideoPlaneFormats;
|
||||
|
||||
static constexpr int k_SwFrameCount = 2;
|
||||
SwFrameMapper m_SwFrameMapper;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue