Some display controllers can be hardware programmed to show non-black
colors for pixels that are either not covered by any plane or are
exposed through transparent regions of higher planes. This feature can
help reduce memory bandwidth usage, e.g. in compositors managing a UI
with a solid background color while using smaller planes to render the
remaining content.
To support this capability, introduce the BACKGROUND_COLOR standard DRM
mode property, which can be attached to a CRTC through the
drm_crtc_attach_background_color_property() helper function.
Additionally, define a 64-bit ARGB format value to be built with the
help of a couple of dedicated DRM_ARGB64_PREP*() helpers. Individual
color components can be extracted with desired precision using the
corresponding DRM_ARGB64_GET*() macros.
Co-developed-by: Matt Roper <matthew.d.roper@intel.com>
Signed-off-by: Matt Roper <matthew.d.roper@intel.com>
Reviewed-by: Nícolas F. R. A. Prado <nfraprado@collabora.com>
Tested-by: Diederik de Haas <diederik@cknow-tech.com>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/gpu/drm/drm_atomic.c | 1 +
drivers/gpu/drm/drm_atomic_state_helper.c | 1 +
drivers/gpu/drm/drm_atomic_uapi.c | 4 ++
drivers/gpu/drm/drm_blend.c | 39 ++++++++++++++--
drivers/gpu/drm/drm_mode_config.c | 6 +++
include/drm/drm_blend.h | 4 +-
include/drm/drm_crtc.h | 12 +++++
include/drm/drm_mode_config.h | 5 +++
include/uapi/drm/drm_mode.h | 75 +++++++++++++++++++++++++++++++
9 files changed, 142 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 52738b80ddbe..c489c7c26396 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -476,6 +476,7 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
drm_printf(p, "\tconnector_mask=%x\n", state->connector_mask);
drm_printf(p, "\tencoder_mask=%x\n", state->encoder_mask);
drm_printf(p, "\tmode: " DRM_MODE_FMT "\n", DRM_MODE_ARG(&state->mode));
+ drm_printf(p, "\tbackground_color=%llx\n", state->background_color);
if (crtc->funcs->atomic_print_state)
crtc->funcs->atomic_print_state(p, state);
diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index cee6d8fc44ad..57668f1c9460 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -75,6 +75,7 @@ __drm_atomic_helper_crtc_state_reset(struct drm_crtc_state *crtc_state,
struct drm_crtc *crtc)
{
crtc_state->crtc = crtc;
+ crtc_state->background_color = DRM_ARGB64_PREP(0xffff, 0, 0, 0);
}
EXPORT_SYMBOL(__drm_atomic_helper_crtc_state_reset);
diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
index dc013a22bf26..20f59a9a7ebc 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -454,6 +454,8 @@ static int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
&replaced);
state->color_mgmt_changed |= replaced;
return ret;
+ } else if (property == config->background_color_property) {
+ state->background_color = val;
} else if (property == config->prop_out_fence_ptr) {
s32 __user *fence_ptr = u64_to_user_ptr(val);
@@ -501,6 +503,8 @@ drm_atomic_crtc_get_property(struct drm_crtc *crtc,
*val = (state->ctm) ? state->ctm->base.id : 0;
else if (property == config->gamma_lut_property)
*val = (state->gamma_lut) ? state->gamma_lut->base.id : 0;
+ else if (property == config->background_color_property)
+ *val = state->background_color;
else if (property == config->prop_out_fence_ptr)
*val = 0;
else if (property == crtc->scaling_filter_property)
diff --git a/drivers/gpu/drm/drm_blend.c b/drivers/gpu/drm/drm_blend.c
index 6852d73c931c..f249af2a11af 100644
--- a/drivers/gpu/drm/drm_blend.c
+++ b/drivers/gpu/drm/drm_blend.c
@@ -191,10 +191,6 @@
* plane does not expose the "alpha" property, then this is
* assumed to be 1.0
*
- * Note that all the property extensions described here apply either to the
- * plane or the CRTC (e.g. for the background color, which currently is not
- * exposed and assumed to be black).
- *
* SCALING_FILTER:
* Indicates scaling filter to be used for plane scaler
*
@@ -207,6 +203,25 @@
*
* Drivers can set up this property for a plane by calling
* drm_plane_create_scaling_filter_property
+ *
+ * The property extensions described above all apply to the plane. Drivers
+ * may also expose the following crtc property extension:
+ *
+ * BACKGROUND_COLOR:
+ * Background color is set up with drm_crtc_attach_background_color_property(),
+ * and expects a 64-bit ARGB value following DRM_FORMAT_ARGB16161616, as
+ * generated by the DRM_ARGB64_PREP*() helpers. It controls the color of a
+ * full-screen layer that exists below all planes. This color will be used
+ * for pixels not covered by any plane and may also be blended with plane
+ * contents as allowed by a plane's alpha values.
+ * The background color defaults to black, and is assumed to be black for
+ * drivers that do not expose this property. Although background color
+ * isn't a plane, it is assumed that the color provided here undergoes the
+ * CRTC degamma/CSC/gamma transformations applied after the planes blending.
+ * Note that the color value includes an alpha channel, hence non-opaque
+ * background color values are allowed, but since physically transparent
+ * monitors do not (yet) exists, the final alpha value may not reach the
+ * video sink or it may simply ignore it.
*/
/**
@@ -621,3 +636,19 @@ int drm_plane_create_blend_mode_property(struct drm_plane *plane,
return 0;
}
EXPORT_SYMBOL(drm_plane_create_blend_mode_property);
+
+/**
+ * drm_crtc_attach_background_color_property - attach background color property
+ * @crtc: drm crtc
+ *
+ * Attaches the background color property to @crtc. The property defaults to
+ * solid black and will accept 64-bit ARGB values in the format generated by
+ * DRM_ARGB64_PREP*() helpers.
+ */
+void drm_crtc_attach_background_color_property(struct drm_crtc *crtc)
+{
+ drm_object_attach_property(&crtc->base,
+ crtc->dev->mode_config.background_color_property,
+ DRM_ARGB64_PREP(0xffff, 0, 0, 0));
+}
+EXPORT_SYMBOL(drm_crtc_attach_background_color_property);
diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c
index d12db9b0bab8..dba7aa871246 100644
--- a/drivers/gpu/drm/drm_mode_config.c
+++ b/drivers/gpu/drm/drm_mode_config.c
@@ -380,6 +380,12 @@ static int drm_mode_create_standard_properties(struct drm_device *dev)
return -ENOMEM;
dev->mode_config.gamma_lut_size_property = prop;
+ prop = drm_property_create_range(dev, 0,
+ "BACKGROUND_COLOR", 0, U64_MAX);
+ if (!prop)
+ return -ENOMEM;
+ dev->mode_config.background_color_property = prop;
+
prop = drm_property_create(dev,
DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB,
"IN_FORMATS", 0);
diff --git a/include/drm/drm_blend.h b/include/drm/drm_blend.h
index 88bdfec3bd88..c7e888767c81 100644
--- a/include/drm/drm_blend.h
+++ b/include/drm/drm_blend.h
@@ -31,8 +31,9 @@
#define DRM_MODE_BLEND_COVERAGE 1
#define DRM_MODE_BLEND_PIXEL_NONE 2
-struct drm_device;
struct drm_atomic_state;
+struct drm_crtc;
+struct drm_device;
struct drm_plane;
static inline bool drm_rotation_90_or_270(unsigned int rotation)
@@ -58,4 +59,5 @@ int drm_atomic_normalize_zpos(struct drm_device *dev,
struct drm_atomic_state *state);
int drm_plane_create_blend_mode_property(struct drm_plane *plane,
unsigned int supported_modes);
+void drm_crtc_attach_background_color_property(struct drm_crtc *crtc);
#endif
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index 66278ffeebd6..312fc1e745d2 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -274,6 +274,18 @@ struct drm_crtc_state {
*/
struct drm_property_blob *gamma_lut;
+ /**
+ * @background_color:
+ *
+ * RGB value representing the CRTC's background color. The background
+ * color (aka "canvas color") of a CRTC is the color that will be used
+ * for pixels not covered by a plane, or covered by transparent pixels
+ * of a plane. The value here should be built using DRM_ARGB64_PREP*()
+ * helpers, while the individual color components can be extracted with
+ * desired precision via the DRM_ARGB64_GET*() macros.
+ */
+ u64 background_color;
+
/**
* @target_vblank:
*
diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
index 5e1dd0cfccde..687c0ee163d2 100644
--- a/include/drm/drm_mode_config.h
+++ b/include/drm/drm_mode_config.h
@@ -836,6 +836,11 @@ struct drm_mode_config {
* gamma LUT as supported by the driver (read-only).
*/
struct drm_property *gamma_lut_size_property;
+ /**
+ * @background_color_property: Optional CRTC property to set the
+ * background color.
+ */
+ struct drm_property *background_color_property;
/**
* @suggested_x_property: Optional connector property with a hint for
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
index 3693d82b5279..f10e7fd19c58 100644
--- a/include/uapi/drm/drm_mode.h
+++ b/include/uapi/drm/drm_mode.h
@@ -27,6 +27,9 @@
#ifndef _DRM_MODE_H
#define _DRM_MODE_H
+#include <linux/bits.h>
+#include <linux/const.h>
+
#include "drm.h"
#if defined(__cplusplus)
@@ -1549,6 +1552,78 @@ struct drm_mode_closefb {
__u32 pad;
};
+/*
+ * Put 16-bit ARGB values into a standard 64-bit representation that can be
+ * used for ioctl parameters, inter-driver communication, etc.
+ *
+ * If the component values being provided contain less than 16 bits of
+ * precision, use a conversion ratio to get a better color approximation.
+ * The ratio is computed as (2^16 - 1) / (2^bpc - 1), where bpc and 16 are
+ * the input and output precision, respectively.
+ */
+#define __DRM_ARGB64_PREP(c, shift) \
+ (((__u64)(c) & __GENMASK(15, 0)) << (shift))
+
+#define __DRM_ARGB64_PREP_BPC(c, shift, bpc)({ \
+ __u16 mask = __GENMASK((bpc) - 1, 0); \
+ __u16 conv = __KERNEL_DIV_ROUND_CLOSEST((mask & (c)) * \
+ __GENMASK(15, 0), mask);\
+ __DRM_ARGB64_PREP(conv, shift); \
+})
+
+#define DRM_ARGB64_PREP(alpha, red, green, blue)( \
+ __DRM_ARGB64_PREP(alpha, 48) | \
+ __DRM_ARGB64_PREP(red, 32) | \
+ __DRM_ARGB64_PREP(green, 16) | \
+ __DRM_ARGB64_PREP(blue, 0) \
+)
+
+#define DRM_ARGB64_PREP_BPC(alpha, red, green, blue, bpc)({ \
+ __typeof__(bpc) __bpc = bpc; \
+ __DRM_ARGB64_PREP_BPC(alpha, 48, __bpc) | \
+ __DRM_ARGB64_PREP_BPC(red, 32, __bpc) | \
+ __DRM_ARGB64_PREP_BPC(green, 16, __bpc) | \
+ __DRM_ARGB64_PREP_BPC(blue, 0, __bpc); \
+})
+
+/*
+ * Extract the specified color component from a standard 64-bit ARGB value.
+ *
+ * If the requested precision is less than 16 bits, make use of a conversion
+ * ratio calculated as (2^bpc - 1) / (2^16 - 1), where bpc and 16 are the
+ * output and input precision, respectively.
+ *
+ * If speed is more important than accuracy, use DRM_ARGB64_GET*_BPCS()
+ * instead of DRM_ARGB64_GET*_BPC() in order to replace the expensive
+ * division with a simple bit right-shift operation.
+ */
+#define __DRM_ARGB64_GET(c, shift) \
+ ((__u16)(((__u64)(c) >> (shift)) & __GENMASK(15, 0)))
+
+#define __DRM_ARGB64_GET_BPC(c, shift, bpc)({ \
+ __u16 comp = __DRM_ARGB64_GET(c, shift); \
+ __KERNEL_DIV_ROUND_CLOSEST(comp * __GENMASK((bpc) - 1, 0), \
+ __GENMASK(15, 0)); \
+})
+
+#define __DRM_ARGB64_GET_BPCS(c, shift, bpc) \
+ (__DRM_ARGB64_GET(c, shift) >> (16 - (bpc)))
+
+#define DRM_ARGB64_GETA(c) __DRM_ARGB64_GET(c, 48)
+#define DRM_ARGB64_GETR(c) __DRM_ARGB64_GET(c, 32)
+#define DRM_ARGB64_GETG(c) __DRM_ARGB64_GET(c, 16)
+#define DRM_ARGB64_GETB(c) __DRM_ARGB64_GET(c, 0)
+
+#define DRM_ARGB64_GETA_BPC(c, bpc) __DRM_ARGB64_GET_BPC(c, 48, bpc)
+#define DRM_ARGB64_GETR_BPC(c, bpc) __DRM_ARGB64_GET_BPC(c, 32, bpc)
+#define DRM_ARGB64_GETG_BPC(c, bpc) __DRM_ARGB64_GET_BPC(c, 16, bpc)
+#define DRM_ARGB64_GETB_BPC(c, bpc) __DRM_ARGB64_GET_BPC(c, 0, bpc)
+
+#define DRM_ARGB64_GETA_BPCS(c, bpc) __DRM_ARGB64_GET_BPCS(c, 48, bpc)
+#define DRM_ARGB64_GETR_BPCS(c, bpc) __DRM_ARGB64_GET_BPCS(c, 32, bpc)
+#define DRM_ARGB64_GETG_BPCS(c, bpc) __DRM_ARGB64_GET_BPCS(c, 16, bpc)
+#define DRM_ARGB64_GETB_BPCS(c, bpc) __DRM_ARGB64_GET_BPCS(c, 0, bpc)
+
#if defined(__cplusplus)
}
#endif
--
2.52.0
On Thu, Jan 29, 2026 at 02:58:52AM +0200, Cristian Ciocaltea wrote:
> Some display controllers can be hardware programmed to show non-black
> colors for pixels that are either not covered by any plane or are
> exposed through transparent regions of higher planes. This feature can
> help reduce memory bandwidth usage, e.g. in compositors managing a UI
> with a solid background color while using smaller planes to render the
> remaining content.
>
> To support this capability, introduce the BACKGROUND_COLOR standard DRM
> mode property, which can be attached to a CRTC through the
> drm_crtc_attach_background_color_property() helper function.
>
> Additionally, define a 64-bit ARGB format value to be built with the
> help of a couple of dedicated DRM_ARGB64_PREP*() helpers. Individual
> color components can be extracted with desired precision using the
> corresponding DRM_ARGB64_GET*() macros.
...
> +/*
> + * Put 16-bit ARGB values into a standard 64-bit representation that can be
> + * used for ioctl parameters, inter-driver communication, etc.
> + *
> + * If the component values being provided contain less than 16 bits of
> + * precision, use a conversion ratio to get a better color approximation.
> + * The ratio is computed as (2^16 - 1) / (2^bpc - 1), where bpc and 16 are
> + * the input and output precision, respectively.
Not sure if you should explicitly mention that "bpc must not be 0"
(it can be derived from the "division by 0" in the given formula,
but still...).
> + */
> +#define __DRM_ARGB64_PREP(c, shift) \
> + (((__u64)(c) & __GENMASK(15, 0)) << (shift))
> +
> +#define __DRM_ARGB64_PREP_BPC(c, shift, bpc)({ \
Not sure if this is an accepted style in DRM, by I find it difficult
to follow. I would expect the "({" be on a separate line.
> + __u16 mask = __GENMASK((bpc) - 1, 0); \
> + __u16 conv = __KERNEL_DIV_ROUND_CLOSEST((mask & (c)) * \
> + __GENMASK(15, 0), mask);\
The whole point of the first patch is to use it in the divisions by 2^n - 1.
Can we transform this to make it "divisions" by power-of-two?
...: def dbm2(c, bpc):
...: m = (1 << bpc) - 1
...: c1 = m & c
...: r = c1 << (16 - bpc)
...: for i in range(1, 16 // bpc):
...: r = r + (c1 << (16 - (i + 1) * bpc))
...: return r
The above is a Python version of PoC of this approximation. Basically
we transform the fraction X / (2^n - 1) to a chained version of
X / 2^n + X / 2^2n + ... X / 2^kn as derived from recurrent formula
of i+1:th iteration as Xi+1 = Xi / 2^n + Xi / (2^n * (2^n - 1)).
So, maybe that one should be used instead? (It may be thought through
on how to collapse the for-loop to maybe some bitops, but even with
a for-loop it might be faster than real division.)
Note, we have some (for sure more than one, I remember the same Q appeared to
me a few years ago) of the examples which may avoid division at all. I would
like to have this macro to be kernel wide (and UAPI seems also okay).
> + __DRM_ARGB64_PREP(conv, shift); \
> +})
--
With Best Regards,
Andy Shevchenko
On 1/29/26 11:37 AM, Andy Shevchenko wrote:
> On Thu, Jan 29, 2026 at 02:58:52AM +0200, Cristian Ciocaltea wrote:
>> Some display controllers can be hardware programmed to show non-black
>> colors for pixels that are either not covered by any plane or are
>> exposed through transparent regions of higher planes. This feature can
>> help reduce memory bandwidth usage, e.g. in compositors managing a UI
>> with a solid background color while using smaller planes to render the
>> remaining content.
>>
>> To support this capability, introduce the BACKGROUND_COLOR standard DRM
>> mode property, which can be attached to a CRTC through the
>> drm_crtc_attach_background_color_property() helper function.
>>
>> Additionally, define a 64-bit ARGB format value to be built with the
>> help of a couple of dedicated DRM_ARGB64_PREP*() helpers. Individual
>> color components can be extracted with desired precision using the
>> corresponding DRM_ARGB64_GET*() macros.
>
> ...
>
>> +/*
>> + * Put 16-bit ARGB values into a standard 64-bit representation that can be
>> + * used for ioctl parameters, inter-driver communication, etc.
>> + *
>> + * If the component values being provided contain less than 16 bits of
>> + * precision, use a conversion ratio to get a better color approximation.
>> + * The ratio is computed as (2^16 - 1) / (2^bpc - 1), where bpc and 16 are
>> + * the input and output precision, respectively.
>
> Not sure if you should explicitly mention that "bpc must not be 0"
> (it can be derived from the "division by 0" in the given formula,
> but still...).
Comment section updated in v7 [1], though I somehow missed to mention it in the
changelog. :-(
>
>> + */
>> +#define __DRM_ARGB64_PREP(c, shift) \
>> + (((__u64)(c) & __GENMASK(15, 0)) << (shift))
>> +
>> +#define __DRM_ARGB64_PREP_BPC(c, shift, bpc)({ \
>
> Not sure if this is an accepted style in DRM, by I find it difficult
> to follow. I would expect the "({" be on a separate line.
I initially got confused by the plethora of different styles, e.g. in
"include/linux/math.h" we can find:
#define rounddown(x, y) ( \
[...]
#define DIV_ROUND_CLOSEST_ULL(x, divisor)( \
[...]
#define mult_frac(x, n, d) \
({ \
[...]
#define abs_diff(a, b) ({ \
[...]
I agree your option is the most readable one, hence I used it consistently in
v7.
Thanks,
Cristian
[1] https://lore.kernel.org/all/20260204-rk3588-bgcolor-v7-0-78d1d01c5ca1@collabora.com/
On Wed, Feb 04, 2026 at 10:32:23PM +0200, Cristian Ciocaltea wrote:
> On 1/29/26 11:37 AM, Andy Shevchenko wrote:
> > On Thu, Jan 29, 2026 at 02:58:52AM +0200, Cristian Ciocaltea wrote:
...
> >> +#define __DRM_ARGB64_PREP_BPC(c, shift, bpc)({ \
> >
> > Not sure if this is an accepted style in DRM, by I find it difficult
> > to follow. I would expect the "({" be on a separate line.
>
> I initially got confused by the plethora of different styles, e.g. in
> "include/linux/math.h" we can find:
>
> #define rounddown(x, y) ( \
> [...]
>
> #define DIV_ROUND_CLOSEST_ULL(x, divisor)( \
> [...]
>
> #define mult_frac(x, n, d) \
> ({ \
> [...]
>
> #define abs_diff(a, b) ({ \
> [...]
math.h is a collection of APIs from the past from different years and code,
that's why the style is inconsistent (a lot!). So better not to take that
as an example.
> I agree your option is the most readable one, hence I used it consistently in
> v7.
Thanks!
--
With Best Regards,
Andy Shevchenko
On Thu, Jan 29, 2026 at 11:37:33AM +0200, Andy Shevchenko wrote:
> On Thu, Jan 29, 2026 at 02:58:52AM +0200, Cristian Ciocaltea wrote:
...
> > +#define __DRM_ARGB64_PREP_BPC(c, shift, bpc)({ \
> > + __u16 mask = __GENMASK((bpc) - 1, 0); \
> > + __u16 conv = __KERNEL_DIV_ROUND_CLOSEST((mask & (c)) * \
> > + __GENMASK(15, 0), mask);\
>
> The whole point of the first patch is to use it in the divisions by 2^n - 1.
> Can we transform this to make it "divisions" by power-of-two?
>
> ...: def dbm2(c, bpc):
> ...: m = (1 << bpc) - 1
> ...: c1 = m & c
> ...: r = c1 << (16 - bpc)
> ...: for i in range(1, 16 // bpc):
> ...: r = r + (c1 << (16 - (i + 1) * bpc))
> ...: return r
I noticed that on some inputs it gives off-by-small-number error.
But you got the idea.
Taking this into account, perhaps we can share __KERNEL_DIV_ROUND_CLOSEST()
anyway and leave it there and improve the situation later on. Up to DRM
maintainers.
> The above is a Python version of PoC of this approximation. Basically
> we transform the fraction X / (2^n - 1) to a chained version of
> X / 2^n + X / 2^2n + ... X / 2^kn as derived from recurrent formula
> of i+1:th iteration as Xi+1 = Xi / 2^n + Xi / (2^n * (2^n - 1)).
>
> So, maybe that one should be used instead? (It may be thought through
> on how to collapse the for-loop to maybe some bitops, but even with
> a for-loop it might be faster than real division.)
>
> Note, we have some (for sure more than one, I remember the same Q appeared to
> me a few years ago) of the examples which may avoid division at all. I would
> like to have this macro to be kernel wide (and UAPI seems also okay).
> > + __DRM_ARGB64_PREP(conv, shift); \
> > +})
--
With Best Regards,
Andy Shevchenko
© 2016 - 2026 Red Hat, Inc.