Skip to content

Add --drm-vout-display Parameter #474

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

rogertje50
Copy link

Add --drm-vout-display Parameter for Selecting Specific DRM Outputs

This pull request introduces a new command-line parameter, --drm-vout-display, to allow users to specify the desired DRM output display when running Flutter applications on Raspberry Pi.

This change is implemented in response to issue #201.

Supported Options

The supported values for this parameter are:

  • HDMI-A-1
  • HDMI-A-2
  • DSI-1
  • DSI-2

If the --drm-vout-display parameter is not provided, the code will behave as before, automatically selecting the first available connected display.


Key Changes

New Command-Line Argument

  • Added the --drm-vout-display option to the command-line arguments.
  • Users can now specify the desired display output by providing one of the supported values.

Validation and Parsing

  • The provided value is validated against the supported options (HDMI-A-1, HDMI-A-2, DSI-1, DSI-2).
  • The flutterpi struct now includes a drm_vout_display field to store the selected display.

Behavior

  • If a valid display is specified, the code will attempt to use the corresponding DRM connector type and type ID.
  • If no display is specified, the code defaults to the previous behavior of selecting the first available connected display.

Documentation

  • Updated the --help output to include the new --drm-vout-display option.
  • The supported values are based on the Raspberry Pi documentation: Playing Audio and Video.

Example Usage

flutter-pi --drm-vout-display HDMI-A-1

Backward Compatibility

This change is fully backward-compatible. If the --drm-vout-display parameter is not provided, the application will function as it did previously, automatically selecting the first available connected display.


Motivation

This feature provides users with greater control over which display output is used, especially in multi-display setups or when specific outputs are required for certain use cases.

@rogertje50
Copy link
Author

This PR is redeay for review.

@ardera
Copy link
Owner

ardera commented Apr 8, 2025

Hey, thanks for the contribution! I'm currently on vacation so didn't have time to give it a thorough look yet, but I'll be back in a few days and then I'll review.

@ardera
Copy link
Owner

ardera commented Apr 8, 2025

though one small thing is the name of the cmdline argument, I'd just call it --display or whatever, that it's a DRM/KMS display is just an implementation detail IMO. :)

@rogertje50
Copy link
Author

Good catch! Changed to --display as you suggested. I took the old arg from cvlc. Enjoy your vacation!

}
return true;
}

static struct drmdev *find_drmdev(struct libseat *libseat) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static struct drmdev *find_drmdev(struct libseat *libseat) {
static struct drmdev *find_drmdev(struct libseat *libseat, const char *connector_name) {

I think passing via argument here is better, generally I don't want to add new code that uses the flutterpi global variable. (Especially since find_drmdev is called from the initialization of that same flutterpi instance, so it's very easy to make mistakes and e.g. use something before it's initialized)

@@ -2137,7 +2182,21 @@ static struct drmdev *find_drmdev(struct libseat *libseat) {

for_each_connector_in_drmdev(drmdev, connector) {
if (connector->variable_state.connection_state == kConnected_DrmConnectionState) {
goto found_connected_connector;
if (flutterpi->drm_vout_display != NULL) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (flutterpi->drm_vout_display != NULL) {
if (connector_name != NULL) {

Comment on lines +2187 to +2190
int expected_type, expected_type_id;
if (!parse_drm_vout_display(flutterpi->drm_vout_display, &expected_type, &expected_type_id)) {
continue;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be a bit counter-intuitive but what could also be done here is converting the current connector to a string and comparing that to the passed in name:

char *connector_type_name = drmModeGetConnectorTypeName(connector->type);
if (connector_type_name == null) continue;

char *connector_name = NULL;
int ok = asprintf(&connector_name, "%s-%d", connector_name, connector->type_id);
if (ok == -1) continue;

if (strcmp(connector_name) == desired_connector_name)) {
  // found connector
}

Has the benefit that it's compatible to all the connectors: https://github.com/torvalds/linux/blob/master/include/uapi/drm/drm_mode.h#L403-L423 and not just the ones present on the Pi, and it's also future-proof since we don't need to change the code to recognize new connector types, drmModeGetConnectorTypeName (which is from libdrm, not flutter-pi) will handle that for us. But that's your choice, I think this is good enough for now. :)

@ardera
Copy link
Owner

ardera commented Apr 11, 2025

I think there's some more functionality required unfortunately :/ This code currently helps choosing the correct DRM device node, by looking if the desired connector is present. However, individual DRM device nodes can (and will) have multiple displays attached. So only selecting the correct device node is not enough, you also need to make the rest of the code use the correct display of that node.
Currently, this will probably work on Pi 5, since DSI and HDMI are different device nodes there, but on Pi 4 to 1 and basically every other device this unfortunately won't work, I believe.

This code here chooses the DRM connector that should be used for display:

flutter-pi/src/window.c

Lines 724 to 869 in c7e0ff5

static int select_mode(
struct drmdev *drmdev,
struct drm_connector **connector_out,
struct drm_encoder **encoder_out,
struct drm_crtc **crtc_out,
drmModeModeInfo **mode_out,
const char *desired_videomode
) {
struct drm_connector *connector;
struct drm_encoder *encoder;
struct drm_crtc *crtc;
drmModeModeInfo *mode, *mode_iter;
int ok;
// find any connected connector
for_each_connector_in_drmdev(drmdev, connector) {
if (connector->variable_state.connection_state == kConnected_DrmConnectionState) {
break;
}
}
if (connector == NULL) {
LOG_ERROR("Could not find a connected connector!\n");
return EINVAL;
}
mode = NULL;
if (desired_videomode != NULL) {
for_each_mode_in_connector(connector, mode_iter) {
char *modeline = NULL, *modeline_nohz = NULL;
ok = asprintf(&modeline, "%" PRIu16 "x%" PRIu16 "@%" PRIu32, mode_iter->hdisplay, mode_iter->vdisplay, mode_iter->vrefresh);
if (ok < 0) {
return ENOMEM;
}
ok = asprintf(&modeline_nohz, "%" PRIu16 "x%" PRIu16, mode_iter->hdisplay, mode_iter->vdisplay);
if (ok < 0) {
return ENOMEM;
}
if (streq(modeline, desired_videomode)) {
// Probably a bit superfluos, but the refresh rate can still vary in the decimal places.
if (mode == NULL || (mode_get_vrefresh(mode_iter) > mode_get_vrefresh(mode))) {
mode = mode_iter;
}
} else if (streq(modeline_nohz, desired_videomode)) {
if (mode == NULL || (mode_get_vrefresh(mode_iter) > mode_get_vrefresh(mode))) {
mode = mode_iter;
}
}
free(modeline);
free(modeline_nohz);
}
if (mode == NULL) {
LOG_ERROR("Didn't find a videomode matching \"%s\"! Falling back to display preferred mode.\n", desired_videomode);
}
}
// Find the preferred mode (GPU drivers _should_ always supply a preferred mode, but of course, they don't)
// Alternatively, find the mode with the highest width*height. If there are multiple modes with the same w*h,
// prefer higher refresh rates. After that, prefer progressive scanout modes.
if (mode == NULL) {
for_each_mode_in_connector(connector, mode_iter) {
if (mode_iter->type & DRM_MODE_TYPE_PREFERRED) {
mode = mode_iter;
break;
} else if (mode == NULL) {
mode = mode_iter;
} else {
int area = mode_iter->hdisplay * mode_iter->vdisplay;
int old_area = mode->hdisplay * mode->vdisplay;
if ((area > old_area) || ((area == old_area) && (mode_iter->vrefresh > mode->vrefresh)) ||
((area == old_area) && (mode_iter->vrefresh == mode->vrefresh) && ((mode->flags & DRM_MODE_FLAG_INTERLACE) == 0))) {
mode = mode_iter;
}
}
}
if (mode == NULL) {
LOG_ERROR("Could not find a preferred output mode!\n");
return EINVAL;
}
}
ASSERT_NOT_NULL(mode);
// Find the encoder that's linked to the connector right now
for_each_encoder_in_drmdev(drmdev, encoder) {
if (encoder->encoder->encoder_id == connector->committed_state.encoder_id) {
break;
}
}
// Otherwise use use any encoder that the connector supports linking to
if (encoder == NULL) {
for (int i = 0; i < connector->n_encoders; i++, encoder = NULL) {
for_each_encoder_in_drmdev(drmdev, encoder) {
if (encoder->encoder->encoder_id == connector->encoders[i]) {
break;
}
}
if (encoder->encoder->possible_crtcs) {
// only use this encoder if there's a crtc we can use with it
break;
}
}
}
if (encoder == NULL) {
LOG_ERROR("Could not find a suitable DRM encoder.\n");
return EINVAL;
}
// Find the CRTC that's currently linked to this encoder
for_each_crtc_in_drmdev(drmdev, crtc) {
if (crtc->id == encoder->encoder->crtc_id) {
break;
}
}
// Otherwise use any CRTC that this encoder supports linking to
if (crtc == NULL) {
for_each_crtc_in_drmdev(drmdev, crtc) {
if (encoder->encoder->possible_crtcs & crtc->bitmask) {
// find a CRTC that is possible to use with this encoder
break;
}
}
}
if (crtc == NULL) {
LOG_ERROR("Could not find a suitable DRM CRTC.\n");
return EINVAL;
}
*connector_out = connector;
*encoder_out = encoder;
*crtc_out = crtc;
*mode_out = mode;
return 0;
}

It has a desired_videomode parameter, which comes directly from the --videomode commandline parameter, so in that way it's pretty similar to the --display arg you want to introduce. Maybe you can just go through the call stack and add an extra desired_connector parameter everywhere

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants