Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ add_library(
src/window.c
src/dummy_render_surface.c
src/plugins/services.c
src/plugins/dpms_plugin.c
)

target_link_libraries(flutterpi_module PUBLIC
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,11 @@ There are several things you need to keep in mind:
- One of the common reasons is outdated ALSA config in which case you should delete existing config and replace it with up to date one
- Finally, if you want to verify your audio setup is good, you can use `gst-launch` command to invoke `playbin` on audio file directly.

### Display power management (DPMS)

The `dpms` Dart package can turn the selected KMS display off and back on.
See [DPMS usage, implementation and hardware checks](docs/DPMS.md).

## 📊 Performance
### Graphics Performance
Graphics performance is actually pretty good. With most of the apps inside the `flutter SDK -> examples -> catalog` directory I get smooth 50-60fps on the Pi 4 2GB and Pi 3 A+.
Expand Down
12 changes: 12 additions & 0 deletions src/compositor_ng.c
Original file line number Diff line number Diff line change
Expand Up @@ -581,3 +581,15 @@ void compositor_set_cursor(

compositor_unlock(compositor);
}

int compositor_get_dpms(struct compositor *compositor) {
return window_get_dpms(compositor->main_window);
}

int compositor_set_dpms(struct compositor *compositor, bool enabled) {
return window_set_dpms(compositor->main_window, enabled);
}

bool compositor_supports_dpms(struct compositor *compositor) {
return window_supports_dpms(compositor->main_window);
}
5 changes: 5 additions & 0 deletions src/compositor_ng.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,9 @@ DECLARE_REF_OPS(fl_layer_composition)
size_t fl_layer_composition_get_n_layers(struct fl_layer_composition *composition);
struct fl_layer *fl_layer_composition_peek_layer(struct fl_layer_composition *composition, int layer);

/** DPMS applies to the output selected by the main window. */
bool compositor_supports_dpms(struct compositor *compositor);
int compositor_get_dpms(struct compositor *compositor);
int compositor_set_dpms(struct compositor *compositor, bool enabled);

#endif // _FLUTTERPI_SRC_COMPOSITOR_NG_H
5 changes: 5 additions & 0 deletions src/flutter-pi.c
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,11 @@ int flutterpi_respond_to_platform_message(
return 0;
}

struct compositor *flutterpi_get_compositor(struct flutterpi *flutterpi) {
ASSERT_NOT_NULL(flutterpi);
return flutterpi->compositor;
}

struct texture_registry *flutterpi_get_texture_registry(struct flutterpi *flutterpi) {
ASSERT_NOT_NULL(flutterpi);
ASSERT_NOT_NULL(flutterpi->texture_registry);
Expand Down
2 changes: 2 additions & 0 deletions src/flutter-pi.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ struct texture_registry *flutterpi_get_texture_registry(struct flutterpi *flutte

struct plugin_registry *flutterpi_get_plugin_registry(struct flutterpi *flutterpi);

struct compositor *flutterpi_get_compositor(struct flutterpi *flutterpi);

FlutterPlatformMessageResponseHandle *
flutterpi_create_platform_message_response_handle(struct flutterpi *flutterpi, FlutterDataCallback data_callback, void *userdata);

Expand Down
121 changes: 113 additions & 8 deletions src/modesetting.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ struct kms_req_builder {
int n_layers;
struct kms_req_layer layers[32];

bool is_dpms_req;
bool dpms_on;
bool unset_mode;
bool has_mode;
drmModeModeInfo mode;
Expand Down Expand Up @@ -425,6 +427,12 @@ static int fetch_crtc(int drm_fd, int crtc_index, uint32_t crtc_id, struct drm_c
crtc_out->index = crtc_index;
crtc_out->bitmask = 1u << crtc_index;
crtc_out->ids = ids;
crtc_out->committed_state.active = crtc->mode_valid;
for (uint32_t i = 0; i < props->count_props; i++) {
if (props->props[i] == ids.active) {
crtc_out->committed_state.active = props->prop_values[i] != 0;
}
}
crtc_out->committed_state.has_mode = crtc->mode_valid;
crtc_out->committed_state.mode = crtc->mode;
crtc_out->committed_state.mode_blob = NULL;
Expand Down Expand Up @@ -1237,6 +1245,10 @@ int drmdev_get_event_fd(struct drmdev *drmdev) {
return drmdev->master_fd;
}

bool drmdev_supports_dpms(struct drmdev *drmdev, const struct drm_crtc *crtc, const struct drm_connector *connector) {
return drmdev->supports_atomic_modesetting ? crtc->ids.active != 0 : connector->ids.dpms != 0;
}

bool drmdev_supports_dumb_buffers(struct drmdev *drmdev) {
return drmdev->supports_dumb_buffers;
}
Expand Down Expand Up @@ -2307,6 +2319,7 @@ struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uin

if (crtc == NULL) {
LOG_ERROR("Invalid CRTC id: %" PRId32 "\n", crtc_id);
errno = EINVAL;
goto fail_unlock;
}

Expand All @@ -2322,9 +2335,6 @@ struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uin
if (req == NULL) {
goto fail_free_builder;
}

// set the CRTC to active
drmModeAtomicAddProperty(req, crtc->id, crtc->ids.active, 1);
} else {
req = NULL;
}
Expand Down Expand Up @@ -2356,6 +2366,8 @@ struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uin
builder->n_layers = 0;
builder->has_mode = false;
builder->unset_mode = false;
builder->is_dpms_req = false;
builder->dpms_on = true;
return builder;

fail_free_builder:
Expand All @@ -2366,6 +2378,33 @@ struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uin
return NULL;
}

struct kms_req *drmdev_create_dpms_req(struct drmdev *drmdev, uint32_t crtc_id, uint32_t connector_id, bool enabled) {
struct kms_req_builder *builder;
struct kms_req *req;
int ok;

builder = drmdev_create_request_builder(drmdev, crtc_id);
if (builder == NULL) {
return NULL;
}

ok = kms_req_builder_set_connector(builder, connector_id);
if (ok == 0 && (builder->use_legacy ? builder->connector->ids.dpms == 0 : builder->crtc->ids.active == 0)) {
ok = ENOTSUP;
}
if (ok != 0) {
kms_req_builder_unref(builder);
errno = ok;
return NULL;
}

builder->is_dpms_req = true;
builder->dpms_on = enabled;
req = kms_req_builder_build(builder);
kms_req_builder_unref(builder);
return req;
}

static void kms_req_builder_destroy(struct kms_req_builder *builder) {
/// TODO: Is this complete?
for (int i = 0; i < builder->n_layers; i++) {
Expand Down Expand Up @@ -2804,10 +2843,6 @@ kms_req_commit_locked(struct kms_req *req, bool blocking, kms_scanout_cb_t scano
ASSERT_NOT_NULL(req);
builder = (struct kms_req_builder *) req;

if (builder->drmdev->per_crtc_state[builder->crtc->index].flip_pending) {
return EBUSY;
}

if (builder->drmdev->master_fd < 0) {
LOG_ERROR("Commit requested, but drmdev doesn't have a DRM master fd right now.\n");
ok = EBUSY;
Expand All @@ -2820,6 +2855,55 @@ kms_req_commit_locked(struct kms_req *req, bool blocking, kms_scanout_cb_t scano
goto fail_unlock;
}

if (builder->is_dpms_req && !blocking) {
return ENOTSUP;
}

if (blocking) {
// A blocking update supersedes the queued frame. Drop it before
// draining so the flip handler cannot submit it and enable the CRTC
// after a power change.
kms_req_swap_ptrs(&builder->drmdev->per_crtc_state[builder->crtc->index].queued, NULL);
while (builder->drmdev->per_crtc_state[builder->crtc->index].flip_pending) {
ok = drmdev_on_modesetting_fd_ready_locked(builder->drmdev);
if (ok != 0) {
LOG_ERROR("Could not drain page flips before blocking commit.\n");
goto fail_unlock;
}
}
} else if (builder->drmdev->per_crtc_state[builder->crtc->index].flip_pending) {
return EBUSY;
}

if (builder->is_dpms_req) {
// A disabled CRTC produces no page-flip event. Do not enter the normal
// scanout path or replace last_flipped: its buffers remain attached.
if (builder->use_legacy) {
ok = drmModeConnectorSetProperty(
builder->drmdev->master_fd,
builder->connector->id,
builder->connector->ids.dpms,
builder->dpms_on ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF
);
} else {
ok = drmModeAtomicAddProperty(builder->req, builder->crtc->id, builder->crtc->ids.active, builder->dpms_on);
if (ok < 0) {
ok = -ok;
goto fail_unlock;
}
ok = drmModeAtomicCommit(builder->drmdev->master_fd, builder->req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
}
if (ok != 0) {
ok = errno;
goto fail_unlock;
}
builder->crtc->committed_state.active = builder->dpms_on;
if (scanout_cb != NULL) {
scanout_cb(builder->drmdev, 0, userdata);
}
return 0;
}

// only change the mode if the new mode differs from the old one

/// TOOD: If this is not a standard mode reported by connector/CRTC,
Expand Down Expand Up @@ -2852,6 +2936,18 @@ kms_req_commit_locked(struct kms_req *req, bool blocking, kms_scanout_cb_t scano
}

if (builder->use_legacy) {
if (!builder->crtc->committed_state.active && builder->connector != NULL && builder->connector->ids.dpms != 0) {
ok = drmModeConnectorSetProperty(
builder->drmdev->master_fd,
builder->connector->id,
builder->connector->ids.dpms,
DRM_MODE_DPMS_ON
);
if (ok != 0) {
ok = errno;
goto fail_maybe_destroy_mode_blob;
}
}
ASSERT_EQUALS(builder->layers[0].layer.dst_x, 0);
ASSERT_EQUALS(builder->layers[0].layer.dst_y, 0);
ASSERT_EQUALS(builder->layers[0].layer.dst_w, builder->mode.hdisplay);
Expand Down Expand Up @@ -2951,9 +3047,17 @@ kms_req_commit_locked(struct kms_req *req, bool blocking, kms_scanout_cb_t scano
/// TODO: Call drmModeSetPlane for all other layers
/// TODO: Assert here
} else {
// Every frame request implicitly enables the display. Power-only
// requests take the separate path above and never add framebuffer layers.
ok = drmModeAtomicAddProperty(builder->req, builder->crtc->id, builder->crtc->ids.active, !builder->unset_mode);
if (ok < 0) {
ok = -ok;
goto fail_maybe_destroy_mode_blob;
}
/// TODO: If we can do explicit fencing, don't use the page flip event.
/// TODO: Can we set OUT_FENCE_PTR even though we didn't set any IN_FENCE_FDs?
flags = DRM_MODE_PAGE_FLIP_EVENT | (blocking ? 0 : DRM_MODE_ATOMIC_NONBLOCK) | (update_mode ? DRM_MODE_ATOMIC_ALLOW_MODESET : 0);
flags = DRM_MODE_PAGE_FLIP_EVENT | (blocking ? 0 : DRM_MODE_ATOMIC_NONBLOCK) |
((update_mode || !builder->crtc->committed_state.active) ? DRM_MODE_ATOMIC_ALLOW_MODESET : 0);

// All planes that are not used by us and are connected to our CRTC
// should be disabled.
Expand Down Expand Up @@ -3024,6 +3128,7 @@ kms_req_commit_locked(struct kms_req *req, bool blocking, kms_scanout_cb_t scano
}

// update struct drm_crtc.committed_state
builder->crtc->committed_state.active = !builder->unset_mode;
if (update_mode) {
// destroy the old mode blob
if (builder->crtc->committed_state.mode_blob != NULL) {
Expand Down
18 changes: 16 additions & 2 deletions src/modesetting.h
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ struct drm_crtc {
struct drm_crtc_prop_ids ids;

struct {
bool active;
bool has_mode;
drmModeModeInfo mode;
struct drm_mode_blob *mode_blob;
Expand Down Expand Up @@ -635,6 +636,7 @@ struct _drmModeModeInfo;
int drmdev_get_fd(struct drmdev *drmdev);
int drmdev_get_event_fd(struct drmdev *drmdev);
bool drmdev_supports_dumb_buffers(struct drmdev *drmdev);
bool drmdev_supports_dpms(struct drmdev *drmdev, const struct drm_crtc *crtc, const struct drm_connector *connector);
int drmdev_create_dumb_buffer(
struct drmdev *drmdev,
int width,
Expand Down Expand Up @@ -752,6 +754,13 @@ struct kms_req_builder;

struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uint32_t crtc_id);

/**
* Create a power-only request for the selected output. Keeps its mode, planes and
* framebuffer references intact. Commit with kms_req_commit_blocking; there is
* no page-flip event and its returned vblank timestamp is zero.
*/
struct kms_req *drmdev_create_dpms_req(struct drmdev *drmdev, uint32_t crtc_id, uint32_t connector_id, bool enabled);

DECLARE_REF_OPS(kms_req_builder);

/**
Expand Down Expand Up @@ -901,8 +910,8 @@ int kms_req_builder_push_zpos_placeholder_layer(struct kms_req_builder *builder,
* @brief A KMS request (atomic or legacy modesetting) that can be committed to
* change the state of a single CRTC.
*
* Only way to construct this is by building a KMS request using
* @ref kms_req_builder and then calling @ref kms_req_builder_build.
* Construct a frame request using @ref kms_req_builder_build, or a power-only
* request using @ref drmdev_create_dpms_req.
*/
struct kms_req;

Expand All @@ -920,6 +929,11 @@ DECLARE_REF_OPS(kms_req);
*/
struct kms_req *kms_req_builder_build(struct kms_req_builder *builder);

/**
* Discard the queued frame for this CRTC and wait for its outstanding flip
* before committing the request. Frame requests also wait for their own flip;
* power-only requests produce no flip and return a zero vblank timestamp.
*/
int kms_req_commit_blocking(struct kms_req *req, uint64_t *vblank_ns_out);

int kms_req_commit_nonblocking(struct kms_req *req, kms_scanout_cb_t scanout_cb, void *userdata, void_callback_t destroy_cb);
Expand Down
25 changes: 25 additions & 0 deletions src/plugins/dpms_plugin.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
#include "plugins/dpms_plugin.h"

#include <errno.h>

#include "compositor_ng.h"
#include "flutter-pi.h"

int32_t flutterpi_dpms_is_available(void) {
return flutterpi != NULL && compositor_supports_dpms(flutterpi_get_compositor(flutterpi));
}

int32_t flutterpi_dpms_get(void) {
if (flutterpi == NULL) {
return -ENODEV;
}
return compositor_get_dpms(flutterpi_get_compositor(flutterpi));
}

int32_t flutterpi_dpms_set(bool enabled) {
if (flutterpi == NULL) {
return ENODEV;
}
return compositor_set_dpms(flutterpi_get_compositor(flutterpi), enabled);
}
17 changes: 17 additions & 0 deletions src/plugins/dpms_plugin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
#ifndef _FLUTTERPI_PLUGINS_DPMS_PLUGIN_H
#define _FLUTTERPI_PLUGINS_DPMS_PLUGIN_H

#include <stdbool.h>
#include <stdint.h>

/** FFI ABI: 0 unavailable, 1 available. */
int32_t flutterpi_dpms_is_available(void);

/** FFI ABI: 0 off, 1 on, negative errno on error. */
int32_t flutterpi_dpms_get(void);

/** FFI ABI: 0 success, positive errno on error. May block for a modeset. */
int32_t flutterpi_dpms_set(bool enabled);

#endif // _FLUTTERPI_PLUGINS_DPMS_PLUGIN_H
Loading
Loading