PKGBUILDs/alarm/vlc-rpi/0003-dev-3.0.19-port_1.patch

29299 lines
916 KiB
Diff
Raw Normal View History

diff --git a/bin/vlc.c b/bin/vlc.c
index 72e0eee428..6d92b95990 100644
--- a/bin/vlc.c
+++ b/bin/vlc.c
@@ -106,7 +106,10 @@ static void vlc_kill (void *data)
static void exit_timeout (int signum)
{
(void) signum;
- signal (SIGINT, SIG_DFL);
+// This doesn't seem to be strong enough to reliably kill us if we fail to exit
+// in a timely fashion - so upgrade to _exit().
+// signal (SIGINT, SIG_DFL);
+ _exit(0);
}
/*****************************************************************************
diff --git a/configure.ac b/configure.ac
index 753d0610e6..2933715dc6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3083,6 +3083,21 @@ AS_IF([test "${have_gl}" = "yes"], [
dnl OpenGL ES 2: depends on EGL 1.1
PKG_ENABLE_MODULES_VLC([GLES2], [], [glesv2], [OpenGL ES v2 support], [disabled])
+dnl
+dnl DRM
+dnl
+AC_ARG_ENABLE(drm,
+ [ --enable-drm DRM output support (default enabled)],, [
+ enable_drm="yes"
+])
+
+have_drm="no"
+AS_IF([test "${enable_drm}" != "no"], [
+ PKG_CHECK_MODULES(DRM, [libdrm])
+ have_drm="yes"
+])
+AM_CONDITIONAL([HAVE_DRM], [test "${have_drm}" = "yes"])
+
dnl
dnl Xlib
dnl
@@ -3447,20 +3462,24 @@ AM_CONDITIONAL([HAVE_KVA], [test "${have_kva}" = "yes"])
dnl
dnl MMAL plugin
dnl
+have_mmal="no"
AC_ARG_ENABLE(mmal,
AS_HELP_STRING([--enable-mmal],
[Multi-Media Abstraction Layer (MMAL) hardware plugin (default enable)]))
+AC_ARG_ENABLE(mmal_avcodec,
+ AS_HELP_STRING([--enable-mmal-avcodec],
+ [Use MMAL enabled avcodec libs (default disable)]))
if test "${enable_mmal}" != "no"; then
VLC_SAVE_FLAGS
LDFLAGS="${LDFLAGS} -L/opt/vc/lib -lvchostif"
- CPPFLAGS="${CPPFLAGS} -isystem /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux"
+ CPPFLAGS="${CPPFLAGS} -idirafter /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux"
AC_CHECK_HEADERS(interface/mmal/mmal.h,
[ AC_CHECK_LIB(bcm_host, vc_tv_unregister_callback_full, [
have_mmal="yes"
VLC_ADD_PLUGIN([mmal])
VLC_ADD_LDFLAGS([mmal],[ -L/opt/vc/lib ])
- VLC_ADD_CFLAGS([mmal],[ -isystem /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux ])
- VLC_ADD_LIBS([mmal],[ -lbcm_host -lmmal -lmmal_core -lmmal_components -lmmal_util -lvchostif ]) ], [
+ VLC_ADD_CFLAGS([mmal],[ -idirafter /opt/vc/include -isystem /opt/vc/include/interface/vcos/pthreads -isystem /opt/vc/include/interface/vmcs_host/linux ])
+ VLC_ADD_LIBS([mmal],[ -lbcm_host -lmmal -lmmal_core -lmmal_components -lmmal_util -lvchostif -lvchiq_arm -lvcsm ]) ], [
AS_IF([test "${enable_mmal}" = "yes"],
[ AC_MSG_ERROR([Cannot find bcm library...]) ],
[ AC_MSG_WARN([Cannot find bcm library...]) ])
@@ -3472,6 +3491,7 @@ if test "${enable_mmal}" != "no"; then
VLC_RESTORE_FLAGS
fi
AM_CONDITIONAL([HAVE_MMAL], [test "${have_mmal}" = "yes"])
+AM_CONDITIONAL([HAVE_MMAL_AVCODEC], [test "${enable_mmal_avcodec}" = "yes"])
dnl
dnl evas plugin
diff --git a/include/vlc_fourcc.h b/include/vlc_fourcc.h
index 97827bd4c1..76ad846f2f 100644
--- a/include/vlc_fourcc.h
+++ b/include/vlc_fourcc.h
2022-12-08 21:49:23 +00:00
@@ -374,6 +374,11 @@
/* Broadcom MMAL opaque buffer type */
#define VLC_CODEC_MMAL_OPAQUE VLC_FOURCC('M','M','A','L')
+#define VLC_CODEC_MMAL_ZC_SAND8 VLC_FOURCC('Z','S','D','8')
+#define VLC_CODEC_MMAL_ZC_SAND10 VLC_FOURCC('Z','S','D','0')
+#define VLC_CODEC_MMAL_ZC_SAND30 VLC_FOURCC('Z','S','D','3')
+#define VLC_CODEC_MMAL_ZC_I420 VLC_FOURCC('Z','4','2','0')
+#define VLC_CODEC_MMAL_ZC_RGB32 VLC_FOURCC('Z','R','G','B')
/* DXVA2 opaque video surface for use with D3D9 */
#define VLC_CODEC_D3D9_OPAQUE VLC_FOURCC('D','X','A','9') /* 4:2:0 8 bpc */
2022-12-08 21:49:23 +00:00
@@ -383,6 +388,12 @@
#define VLC_CODEC_D3D11_OPAQUE VLC_FOURCC('D','X','1','1') /* 4:2:0 8 bpc */
#define VLC_CODEC_D3D11_OPAQUE_10B VLC_FOURCC('D','X','1','0') /* 4:2:0 10 bpc */
+/* DRM Prime */
+#define VLC_CODEC_DRM_PRIME_I420 VLC_FOURCC('D','P','V','0')
+#define VLC_CODEC_DRM_PRIME_NV12 VLC_FOURCC('D','P','N','1')
+#define VLC_CODEC_DRM_PRIME_SAND8 VLC_FOURCC('D','P','S','8')
+#define VLC_CODEC_DRM_PRIME_SAND30 VLC_FOURCC('D','P','S','3')
+
/* CVPixelBuffer opaque buffer type */
#define VLC_CODEC_CVPX_NV12 VLC_FOURCC('C','V','P','N')
#define VLC_CODEC_CVPX_UYVY VLC_FOURCC('C','V','P','Y')
diff --git a/include/vlc_opengl.h b/include/vlc_opengl.h
index fdebfeb2e1..42671f85db 100644
2023-03-28 20:02:15 +00:00
--- a/include/vlc_opengl.h
+++ b/include/vlc_opengl.h
@@ -68,6 +68,9 @@ struct vlc_gl_t
const int32_t *attrib_list);
/* call eglDestroyImageKHR() with current display, can be NULL */
bool (*destroyImageKHR)(vlc_gl_t *, void *image);
+ bool (*queryDmaBufModifiersEXT)(vlc_gl_t *gl, uint32_t format,
+ unsigned int max_modifiers, uint64_t *modifiers,
+ unsigned int *external_only, int32_t *num_modifiers);
} egl;
/* if ext == VLC_GL_EXT_WGL */
struct
diff --git a/modules/Makefile.am b/modules/Makefile.am
index 53b90a2713..6a29b126e9 100644
--- a/modules/Makefile.am
+++ b/modules/Makefile.am
@@ -10,9 +10,7 @@ EXTRA_SUBDIRS = \
SUBDIRS = .
DIST_SUBDIRS = . $(EXTRA_SUBDIRS)
-if HAVE_MMAL
SUBDIRS += hw/mmal
-endif
TESTS =
@@ -34,6 +32,7 @@ include demux/Makefile.am
include gui/Makefile.am
include hw/d3d9/Makefile.am
include hw/d3d11/Makefile.am
+include hw/drm/Makefile.am
include hw/vaapi/Makefile.am
include hw/vdpau/Makefile.am
include keystore/Makefile.am
diff --git a/modules/audio_filter/converter/tospdif.c b/modules/audio_filter/converter/tospdif.c
index 0c725d99e7..8d962d6e81 100644
--- a/modules/audio_filter/converter/tospdif.c
+++ b/modules/audio_filter/converter/tospdif.c
@@ -484,6 +484,7 @@ static int write_buffer_dtshd( filter_t *p_filter, block_t *p_in_buf )
p_in_buf->i_buffer ) != VLC_SUCCESS )
return SPDIF_ERROR;
unsigned i_period = p_filter->fmt_out.audio.i_rate
+ * (p_filter->fmt_out.audio.i_bytes_per_frame / p_filter->fmt_out.audio.i_frame_length) / 4
* core.i_frame_length / core.i_rate;
int i_subtype = dtshd_get_subtype( i_period );
diff --git a/modules/audio_output/alsa.c b/modules/audio_output/alsa.c
index e10e1eb8c3..b8f190d782 100644
--- a/modules/audio_output/alsa.c
+++ b/modules/audio_output/alsa.c
@@ -39,6 +39,8 @@
#include <alsa/asoundlib.h>
#include <alsa/version.h>
+#define TRACE_ALL 0
+
/** Private data for an ALSA PCM playback stream */
struct aout_sys_t
{
@@ -51,11 +53,19 @@ struct aout_sys_t
bool soft_mute;
float soft_gain;
char *device;
+ unsigned int pause_bytes;
+
+ vlc_fourcc_t * passthrough_types;
};
#include "audio_output/volume.h"
-#define A52_FRAME_NB 1536
+enum {
+ PASSTHROUGH_UNSET = -1,
+ PASSTHROUGH_NONE = 0,
+ PASSTHROUGH_SPDIF,
+ PASSTHROUGH_HDMI,
+};
static int Open (vlc_object_t *);
static void Close (vlc_object_t *);
@@ -77,6 +87,22 @@ static const char *const channels_text[] = {
N_("Surround 5.0"), N_("Surround 5.1"), N_("Surround 7.1"),
};
+#define PASSTHROUGH_NAME "alsa-passthrough"
+#define PASSTHROUGH_TEXT N_("Audio passthrough mode")
+#define PASSTHROUGH_LONGTEXT N_("Audio passthrough mode. Defaults to 0 (none)")
+static const int passthrough_modes[] = {
+ PASSTHROUGH_UNSET, PASSTHROUGH_NONE, PASSTHROUGH_SPDIF, PASSTHROUGH_HDMI,
+};
+static const char *const passthrough_modes_text[] = {
+ N_("unset"), N_("none"), N_("S/PDIF"), N_("HDMI"),
+};
+
+#define PASSTHROUGH_TYPES_NAME "alsa-passthrough-types"
+#define PASSTHROUGH_TYPES_TEXT "List of codecs to accept for passthrough"
+#define PASSTHROUGH_TYPES_LONGTEXT "List of codecs to accept for passthrough, comma separated. Default is to try everything."\
+ "If this option is given then " PASSTHROUGH_NAME " defaults to HDMI"
+
+
vlc_module_begin ()
set_shortname( "ALSA" )
set_description( N_("ALSA audio output") )
@@ -88,12 +114,85 @@ vlc_module_begin ()
add_integer ("alsa-audio-channels", AOUT_CHANS_FRONT,
AUDIO_CHAN_TEXT, AUDIO_CHAN_LONGTEXT, false)
change_integer_list (channels, channels_text)
+ add_integer (PASSTHROUGH_NAME, PASSTHROUGH_UNSET, PASSTHROUGH_TEXT,
+ PASSTHROUGH_LONGTEXT, false)
+ change_integer_list (passthrough_modes, passthrough_modes_text)
+ add_string(PASSTHROUGH_TYPES_NAME, NULL, PASSTHROUGH_TYPES_TEXT,
+ PASSTHROUGH_TYPES_LONGTEXT, false)
add_sw_gain ()
set_capability( "audio output", 150 )
set_callbacks( Open, Close )
vlc_module_end ()
+static vlc_fourcc_t * parse_passthrough(audio_output_t * const aout, const char * const str)
+{
+ const char * p = str;
+ size_t n = 2;
+ vlc_fourcc_t * rv = NULL;
+ vlc_fourcc_t * f;
+
+ if (str == NULL)
+ return NULL;
+
+ while (*p != '\0')
+ if (*p++ == ',')
+ ++n;
+
+ rv = malloc(sizeof(vlc_fourcc_t) * n);
+ if (rv == NULL)
+ return NULL;
+ f = rv;
+
+ if (strcasecmp(str, "none") == 0)
+ goto done;
+
+ for (p = str; *p != 0;)
+ {
+ unsigned int i;
+ const char *c = strchrnul(p, ',');
+ vlc_fourcc_t fcc = 0;
+
+ static const struct {
+ const char * str;
+ vlc_fourcc_t val;
+ } codecs[] = {
+ {.str = "truehd", .val = VLC_CODEC_TRUEHD },
+ {.str = "mlp", .val = VLC_CODEC_MLP },
+ {.str = "dts", .val = VLC_CODEC_DTS },
+ {.str = "dtshd", .val = VLC_CODEC_DTS },
+ {.str = "ac3", .val = VLC_CODEC_A52 },
+ {.str = "ac-3", .val = VLC_CODEC_A52 },
+ {.str = "eac3", .val = VLC_CODEC_EAC3 },
+ {.str = "eac-3", .val = VLC_CODEC_EAC3 },
+ {.str = "all", .val = VLC_CODEC_UNKNOWN },
+ };
+
+ for (i = 0; i != ARRAY_SIZE(codecs); ++i)
+ {
+ if (strncasecmp(p, codecs[i].str, c - p) == 0)
+ {
+ fcc = codecs[i].val;
+ break;
+ }
+ }
+
+ if (fcc != 0)
+ *f++ = fcc;
+ else
+ msg_Warn(aout, "Unknown codec type '%.*s'", (int)(c - p), p);
+
+ if (*c == 0)
+ break;
+
+ p = c + 1;
+ }
+
+done:
+ *f = 0;
+ return rv;
+}
+
/** Helper for ALSA -> VLC debugging output */
static void Dump (vlc_object_t *obj, const char *msg,
int (*cb)(void *, snd_output_t *), void *p)
@@ -285,11 +384,20 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt)
{
aout_sys_t *sys = aout->sys;
snd_pcm_format_t pcm_format; /* ALSA sample format */
- bool spdif = false;
+ unsigned channels;
+ int passthrough = PASSTHROUGH_NONE;
+ snd_pcm_uframes_t periodSizeMax;
+ snd_pcm_uframes_t periodSize;
+ snd_pcm_uframes_t bufferSize;
+ unsigned int req_rate = fmt->i_rate;
+ vlc_fourcc_t req_format = fmt->i_format;
- if (aout_FormatNbChannels(fmt) == 0)
+ msg_Dbg(aout, "Start: Format: %.4s, Chans: %d, Rate:%d", (char*)&fmt->i_format, aout_FormatNbChannels(fmt), fmt->i_rate);
+
+ if (aout_FormatNbChannels(fmt) == 0 && AOUT_FMT_LINEAR(fmt))
return VLC_EGENERIC;
+ sys->pause_bytes = 0;
switch (fmt->i_format)
{
case VLC_CODEC_FL64:
@@ -308,36 +416,88 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt)
pcm_format = SND_PCM_FORMAT_U8;
break;
default:
- if (AOUT_FMT_SPDIF(fmt))
- spdif = var_InheritBool (aout, "spdif");
- if (spdif)
+ if (AOUT_FMT_SPDIF(fmt) || AOUT_FMT_HDMI(fmt))
{
- fmt->i_format = VLC_CODEC_SPDIFL;
+ if (sys->passthrough_types != NULL)
+ {
+ // VLC_CODEC_UNKNOWN used as explicit "all"
+ const vlc_fourcc_t *p;
+ for (p = sys->passthrough_types; *p != 0 || *p == VLC_CODEC_UNKNOWN; ++p)
+ if (*p == fmt->i_format)
+ break;
+ if (*p == 0)
+ {
+ msg_Dbg(aout, "Codec %.4s not in passthrough-types", (const char *)&fmt->i_format);
+ return VLC_EGENERIC;
+ }
+ }
+
+ passthrough = var_InheritInteger(aout, PASSTHROUGH_NAME);
+ // Explicit passthrough will override spdif
+ if (passthrough == PASSTHROUGH_UNSET)
+ passthrough =
+ var_InheritBool(aout, "spdif") ? PASSTHROUGH_SPDIF :
+ sys->passthrough_types != NULL ? PASSTHROUGH_HDMI : PASSTHROUGH_NONE;
+ msg_Dbg(aout, "Passthrough %d for format %4.4s", passthrough, (const char *)&fmt->i_format);
+ }
+
+ if (passthrough != PASSTHROUGH_NONE)
+ {
+ req_format = VLC_CODEC_SPDIFL;
pcm_format = SND_PCM_FORMAT_S16;
+ sys->pause_bytes = 3 * 4;
+ channels = 2;
+
+ switch (fmt->i_format) {
+ case VLC_CODEC_MLP:
+ case VLC_CODEC_TRUEHD:
+ sys->pause_bytes = 4 * 4;
+ req_rate = fmt->i_rate * 4;
+ channels = 8;
+ break;
+
+ case VLC_CODEC_DTS:
+ {
+ if (passthrough == PASSTHROUGH_SPDIF)
+ break;
+ req_rate = 192000;
+ channels = 8;
+ break;
+ }
+
+ case VLC_CODEC_EAC3:
+ sys->pause_bytes = 4 * 4;
+ req_rate = fmt->i_rate * 4;
+ break;
+
+ default:
+ break;
+ }
}
else
if (HAVE_FPU)
{
- fmt->i_format = VLC_CODEC_FL32;
+ req_format = VLC_CODEC_FL32;
pcm_format = SND_PCM_FORMAT_FLOAT;
}
else
{
- fmt->i_format = VLC_CODEC_S16N;
+ req_format = VLC_CODEC_S16N;
pcm_format = SND_PCM_FORMAT_S16;
}
+ break;
}
const char *device = sys->device;
/* Choose the IEC device for S/PDIF output */
char sep = '\0';
- if (spdif)
+ if (passthrough != PASSTHROUGH_NONE)
{
const char *opt = NULL;
if (!strcmp (device, "default"))
- device = "iec958"; /* TODO: hdmi */
+ device = (passthrough == PASSTHROUGH_HDMI) ? "hdmi" : "iec958";
if (!strncmp (device, "iec958", 6))
opt = device + 6;
@@ -364,8 +524,12 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt)
FS( 44100) /* def. */ FS( 48000) FS( 32000)
FS( 22050) FS( 24000)
FS( 88200) FS(768000) FS( 96000)
- FS(176400) FS(192000)
+ FS(176400)
#undef FS
+ case 192000:
+ aes3 = (passthrough == PASSTHROUGH_HDMI && channels == 8) ?
+ IEC958_AES3_CON_FS_768000 : IEC958_AES3_CON_FS_192000;
+ break;
default:
aes3 = IEC958_AES3_CON_FS_NOTID;
break;
@@ -430,21 +594,27 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt)
if (snd_pcm_hw_params_test_format (pcm, hw, pcm_format) == 0)
;
else
- if (snd_pcm_hw_params_test_format (pcm, hw, SND_PCM_FORMAT_FLOAT) == 0)
+ if (passthrough != PASSTHROUGH_NONE)
{
- fmt->i_format = VLC_CODEC_FL32;
+ msg_Warn(aout, "Failed to set required passthrough format");
+ goto error;
+ }
+ else
+ if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_FLOAT) == 0)
+ {
+ req_format = VLC_CODEC_FL32;
pcm_format = SND_PCM_FORMAT_FLOAT;
}
else
if (snd_pcm_hw_params_test_format (pcm, hw, SND_PCM_FORMAT_S32) == 0)
{
- fmt->i_format = VLC_CODEC_S32N;
+ req_format = VLC_CODEC_S32N;
pcm_format = SND_PCM_FORMAT_S32;
}
else
if (snd_pcm_hw_params_test_format (pcm, hw, SND_PCM_FORMAT_S16) == 0)
{
- fmt->i_format = VLC_CODEC_S16N;
+ req_format = VLC_CODEC_S16N;
pcm_format = SND_PCM_FORMAT_S16;
}
else
@@ -459,10 +629,10 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt)
msg_Err (aout, "cannot set sample format: %s", snd_strerror (val));
goto error;
}
+ sys->format = req_format;
/* Set channels count */
- unsigned channels;
- if (!spdif)
+ if (passthrough == PASSTHROUGH_NONE)
{
uint16_t map = var_InheritInteger (aout, "alsa-audio-channels");
@@ -474,7 +644,6 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt)
else
{
sys->chans_to_reorder = 0;
- channels = 2;
}
/* By default, ALSA plug will pad missing channels with zeroes, which is
@@ -490,14 +659,26 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt)
}
/* Set sample rate */
- val = snd_pcm_hw_params_set_rate_near (pcm, hw, &fmt->i_rate, NULL);
+ sys->rate = req_rate;
+ val = snd_pcm_hw_params_set_rate_near (pcm, hw, &sys->rate, NULL);
if (val)
{
msg_Err (aout, "cannot set sample rate: %s", snd_strerror (val));
goto error;
}
- sys->rate = fmt->i_rate;
+ if (passthrough != PASSTHROUGH_NONE && sys->rate != req_rate)
+ {
+ msg_Warn(aout, "Passthrough requires rate %d, got %d", req_rate, sys->rate);
+ goto error;
+ }
+ bufferSize = req_rate / 10; // 100ms - bigger than this & truehd goes unhappy?
+ periodSize = bufferSize / 4;
+ periodSizeMax = bufferSize / 3;
+ snd_pcm_hw_params_set_period_size_max(pcm, hw, &periodSizeMax, NULL);
+
+ snd_pcm_hw_params_set_buffer_size_near(pcm, hw, &bufferSize);
+ snd_pcm_hw_params_set_period_size_near(pcm, hw, &periodSize, NULL);
#if 1 /* work-around for period-long latency outputs (e.g. PulseAudio): */
param = AOUT_MIN_PREPARE_TIME;
val = snd_pcm_hw_params_set_period_time_near (pcm, hw, &param, NULL);
@@ -579,13 +760,12 @@ static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt)
}
/* Setup audio_output_t */
- if (spdif)
- {
- fmt->i_bytes_per_frame = AOUT_SPDIF_SIZE;
- fmt->i_frame_length = A52_FRAME_NB;
- }
+ fmt->i_frame_length = 1;
+ fmt->i_bytes_per_frame = snd_pcm_frames_to_bytes(pcm, fmt->i_frame_length);
+ fmt->i_channels = channels;
+ fmt->i_rate = sys->rate;
+ fmt->i_format = sys->format;
fmt->channel_type = AUDIO_CHANNEL_TYPE_BITMAP;
- sys->format = fmt->i_format;
aout->time_get = TimeGet;
aout->play = Play;
@@ -616,7 +796,7 @@ static int TimeGet (audio_output_t *aout, vlc_tick_t *restrict delay)
msg_Err (aout, "cannot estimate delay: %s", snd_strerror (val));
return -1;
}
- *delay = frames * CLOCK_FREQ / sys->rate;
+ *delay = (uint_fast64_t)frames * CLOCK_FREQ / sys->rate;
return 0;
}
@@ -627,6 +807,29 @@ static void Play (audio_output_t *aout, block_t *block)
{
aout_sys_t *sys = aout->sys;
+#if TRACE_ALL
+ static mtime_t last_pts = 0;
+ msg_Dbg(aout, "<<< %s: PTS: %"PRId64" samples: %u, bytes: %zu, delta: %"PRId64, __func__,
+ block->i_pts, block->i_nb_samples, block->i_buffer,
+ block->i_pts - last_pts);
+ last_pts = block->i_pts;
+#endif
+
+ // S/pdif packets always start with sync so if no sync then this must
+ // be a padding buffer
+ if (sys->pause_bytes != 0 && block->p_buffer[0] == 0)
+ {
+ static const uint8_t pause_le[16] = {0x72, 0xf8, 0x1f, 0x4e, 3, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ static const uint8_t pause_be[16] = {0xf8, 0x72, 0x4e, 0x1f, 0, 3, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0};
+ const uint8_t *const pause = sys->format == VLC_CODEC_SPDIFB ? pause_be : pause_le;
+ size_t n = block->i_buffer / sys->pause_bytes;
+ size_t i;
+
+ msg_Dbg(aout, "Silence detected");
+ for (i = 0; i != n; ++i)
+ memcpy(block->p_buffer + i * sys->pause_bytes, pause, sys->pause_bytes);
+ }
+
if (sys->chans_to_reorder != 0)
aout_ChannelReorder(block->p_buffer, block->i_buffer,
sys->chans_to_reorder, sys->chans_table, sys->format);
@@ -792,7 +995,7 @@ static int DeviceSelect (audio_output_t *aout, const char *id)
static int Open(vlc_object_t *obj)
{
audio_output_t *aout = (audio_output_t *)obj;
- aout_sys_t *sys = malloc (sizeof (*sys));
+ aout_sys_t *sys = calloc (1, sizeof (*sys));
if (unlikely(sys == NULL))
return VLC_ENOMEM;
@@ -822,6 +1025,12 @@ static int Open(vlc_object_t *obj)
free (ids);
}
+ {
+ const char *types = var_InheritString(aout, PASSTHROUGH_TYPES_NAME);
+ sys->passthrough_types = parse_passthrough(aout, types);
+ free((void *)types);
+ }
+
return VLC_SUCCESS;
error:
free (sys);
@@ -833,6 +1042,7 @@ static void Close(vlc_object_t *obj)
audio_output_t *aout = (audio_output_t *)obj;
aout_sys_t *sys = aout->sys;
+ free (sys->passthrough_types);
free (sys->device);
free (sys);
}
diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am
index 6d9465fdae..5d46e5e95f 100644
--- a/modules/codec/Makefile.am
+++ b/modules/codec/Makefile.am
@@ -378,6 +378,7 @@ libavcodec_plugin_la_SOURCES = \
codec/avcodec/subtitle.c \
codec/avcodec/audio.c \
codec/avcodec/va.c codec/avcodec/va.h \
+ codec/avcodec/drm_pic.c codec/avcodec/drm_pic.h \
codec/avcodec/avcodec.c codec/avcodec/avcodec.h \
packetizer/av1_obu.c packetizer/av1_obu.h packetizer/av1.h
if ENABLE_SOUT
diff --git a/modules/codec/avcodec/avcodec.c b/modules/codec/avcodec/avcodec.c
index d0780e9a17..13bf0f921a 100644
--- a/modules/codec/avcodec/avcodec.c
+++ b/modules/codec/avcodec/avcodec.c
@@ -252,28 +252,51 @@ vlc_module_begin ()
#endif
vlc_module_end ()
-AVCodecContext *ffmpeg_AllocContext( decoder_t *p_dec,
- const AVCodec **restrict codecp )
+static const char *
+hw_v4l2m2m_dec_str(const unsigned i_codec_id)
+{
+ switch( i_codec_id )
+ {
+ case AV_CODEC_ID_MPEG2VIDEO:
+ return "mpeg2_v4l2m2m";
+ case AV_CODEC_ID_H264:
+ return "h264_v4l2m2m";
+ default:
+ break;
+ }
+ return NULL;
+}
+
+AVCodecContext *ffmpeg_AllocContextHw( decoder_t *p_dec,
+ const AVCodec **restrict codecp, const int hw )
{
unsigned i_codec_id;
const char *psz_namecodec;
const AVCodec *p_codec = NULL;
+ const char * hw_dec_name = NULL;
+ const char * const psz_decoder = var_InheritString( p_dec, "avcodec-codec" );
+
+ // If named decoder do not attempt hw override - wait for non-hw pass
+ if( hw != 0 && psz_decoder != NULL )
+ return NULL;
/* *** determine codec type *** */
if( !GetFfmpegCodec( p_dec->fmt_in.i_cat, p_dec->fmt_in.i_codec,
&i_codec_id, &psz_namecodec ) )
return NULL;
+ if( hw != 0 && (hw_dec_name = hw_v4l2m2m_dec_str(i_codec_id)) == NULL )
+ return NULL;
+
msg_Dbg( p_dec, "using %s %s", AVPROVIDER(LIBAVCODEC), LIBAVCODEC_IDENT );
/* Initialization must be done before avcodec_find_decoder() */
vlc_init_avcodec(VLC_OBJECT(p_dec));
/* *** ask ffmpeg for a decoder *** */
- char *psz_decoder = var_InheritString( p_dec, "avcodec-codec" );
if( psz_decoder != NULL )
{
- p_codec = avcodec_find_decoder_by_name( psz_decoder );
+ p_codec = avcodec_find_decoder_by_name(psz_decoder);
if( !p_codec )
msg_Err( p_dec, "Decoder `%s' not found", psz_decoder );
else if( p_codec->id != i_codec_id )
@@ -284,7 +307,9 @@ AVCodecContext *ffmpeg_AllocContext( decoder_t *p_dec,
}
free( psz_decoder );
}
- if( !p_codec )
+ if( hw_dec_name != NULL )
+ p_codec = avcodec_find_decoder_by_name(hw_dec_name);
+ else if( !p_codec )
p_codec = avcodec_find_decoder( i_codec_id );
if( !p_codec )
{
@@ -304,6 +329,12 @@ AVCodecContext *ffmpeg_AllocContext( decoder_t *p_dec,
return avctx;
}
+AVCodecContext *ffmpeg_AllocContext( decoder_t *p_dec,
+ const AVCodec **restrict codecp )
+{
+ return ffmpeg_AllocContextHw(p_dec, codecp, 0);
+}
+
/*****************************************************************************
* ffmpeg_OpenCodec:
*****************************************************************************/
diff --git a/modules/codec/avcodec/avcodec.h b/modules/codec/avcodec/avcodec.h
index c14170ebb8..7ee1463463 100644
--- a/modules/codec/avcodec/avcodec.h
+++ b/modules/codec/avcodec/avcodec.h
@@ -48,6 +48,7 @@ int InitSubtitleDec( vlc_object_t * );
void EndSubtitleDec( vlc_object_t * );
/* Initialize decoder */
+AVCodecContext *ffmpeg_AllocContextHw( decoder_t *p_dec, const AVCodec **restrict codecp, const int hw );
AVCodecContext *ffmpeg_AllocContext( decoder_t *, const AVCodec ** );
int ffmpeg_OpenCodec( decoder_t *p_dec, AVCodecContext *, const AVCodec * );
diff --git a/modules/codec/avcodec/drm_pic.c b/modules/codec/avcodec/drm_pic.c
new file mode 100644
index 0000000000..8319c1c210
--- /dev/null
+++ b/modules/codec/avcodec/drm_pic.c
@@ -0,0 +1,62 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_picture.h>
+
+#include <libavutil/buffer.h>
+#include <libavutil/frame.h>
+
+#include "drm_pic.h"
+
+static picture_context_t *
+drm_prime_picture_context_new(AVBufferRef * const buf, const void * data, AVBufferRef * const hw_frames_ctx);
+
+static void
+drm_prime_pic_ctx_destroy(struct picture_context_t * ctx)
+{
+ drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)ctx;
+ av_buffer_unref(&pctx->buf);
+ av_buffer_unref(&pctx->hw_frames_ctx);
+ free(pctx);
+}
+
+static picture_context_t *
+drm_prime_pic_ctx_copy(struct picture_context_t * src)
+{
+ drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)src;
+
+ // We could ref count this structure but easier to just create a new one
+ // (which will ref the buf)
+ return drm_prime_picture_context_new(pctx->buf, pctx->desc, pctx->hw_frames_ctx);
+}
+
+static picture_context_t *
+drm_prime_picture_context_new(AVBufferRef * const buf, const void * data, AVBufferRef * const hw_frames_ctx)
+{
+ drm_prime_video_sys_t * const pctx = calloc(1, sizeof(*pctx));
+
+ if (pctx == NULL)
+ return NULL;
+
+ pctx->cmn.copy = drm_prime_pic_ctx_copy;
+ pctx->cmn.destroy = drm_prime_pic_ctx_destroy;
+
+ pctx->buf = av_buffer_ref(buf);
+ pctx->desc = data;
+ pctx->hw_frames_ctx = !hw_frames_ctx ? NULL : av_buffer_ref(hw_frames_ctx);
+ return &pctx->cmn;
+}
+
+int drm_prime_attach_buf_to_pic(picture_t *pic, AVFrame *frame)
+{
+ if (pic->context)
+ return VLC_EGENERIC;
+
+ pic->context = drm_prime_picture_context_new(frame->buf[0], frame->data[0], frame->hw_frames_ctx);
+ return VLC_SUCCESS;
+}
+
+
+
diff --git a/modules/codec/avcodec/drm_pic.h b/modules/codec/avcodec/drm_pic.h
new file mode 100644
index 0000000000..a15f512c23
--- /dev/null
+++ b/modules/codec/avcodec/drm_pic.h
@@ -0,0 +1,46 @@
+#include <vlc_fourcc.h>
+#include <vlc_picture.h>
+
+struct picture_t;
+struct AVFrame;
+struct AVBufferRef;
+struct AVDRMFrameDescriptor;
+
+typedef struct drm_prime_video_sys_s {
+ picture_context_t cmn; // PARENT: Common els at start
+
+ struct AVBufferRef * buf;
+ const struct AVDRMFrameDescriptor * desc;
+ struct AVBufferRef * hw_frames_ctx;
+} drm_prime_video_sys_t;
+
+static inline const struct AVDRMFrameDescriptor *
+drm_prime_get_desc(picture_t *pic)
+{
+ drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)pic->context;
+
+ return !pctx ? NULL : pctx->desc;
+}
+
+static inline char safechar(unsigned int x)
+{
+ const unsigned int c = x & 0xff;
+ return c > ' ' && c < 0x7f ? (char)c : '.';
+}
+
+static inline const char *
+str_fourcc(char buf[5], const uint32_t fcc)
+{
+ buf[0] = safechar(fcc);
+ buf[1] = safechar(fcc >> 8);
+ buf[2] = safechar(fcc >> 16);
+ buf[3] = safechar(fcc >> 24);
+ buf[4] = 0;
+ return buf;
+}
+
+#define fourcc2str(fcc) \
+ str_fourcc((char[5]){0}, fcc)
+
+int drm_prime_attach_buf_to_pic(struct picture_t *pic, struct AVFrame *frame);
+
diff --git a/modules/codec/avcodec/va.c b/modules/codec/avcodec/va.c
index 0feb03b974..aa3a80f929 100644
--- a/modules/codec/avcodec/va.c
+++ b/modules/codec/avcodec/va.c
@@ -57,6 +57,22 @@ vlc_fourcc_t vlc_va_GetChroma(enum PixelFormat hwfmt, enum PixelFormat swfmt)
return VLC_CODEC_D3D9_OPAQUE;
}
break;
+ case AV_PIX_FMT_DRM_PRIME:
+ switch (swfmt)
+ {
+ case AV_PIX_FMT_YUV420P:
+ return VLC_CODEC_DRM_PRIME_I420;
+ case AV_PIX_FMT_NV12:
+ return VLC_CODEC_DRM_PRIME_NV12;
+ case AV_PIX_FMT_RPI4_8:
+ return VLC_CODEC_DRM_PRIME_SAND8;
+ case AV_PIX_FMT_RPI4_10:
+ case AV_PIX_FMT_YUV420P10LE: // When probing this is the swfmt given
+ return VLC_CODEC_DRM_PRIME_SAND30;
+ default:
+ return 0;
+ }
+ break;
#if LIBAVUTIL_VERSION_CHECK(54, 13, 1, 24, 100)
case AV_PIX_FMT_D3D11VA_VLD:
diff --git a/modules/codec/avcodec/video.c b/modules/codec/avcodec/video.c
index 55ea1c82fb..c299fe2a38 100644
--- a/modules/codec/avcodec/video.c
+++ b/modules/codec/avcodec/video.c
2022-12-08 21:49:23 +00:00
@@ -29,6 +29,8 @@
# include "config.h"
#endif
+#define OPT_RPI 1
+
#include <vlc_common.h>
#include <vlc_codec.h>
#include <vlc_avcodec.h>
@@ -46,6 +48,8 @@
#include "avcodec.h"
#include "va.h"
+#include "drm_pic.h"
+
#include "../../packetizer/av1_obu.h"
#include "../../packetizer/av1.h"
#include "../codec/cc.h"
@@ -53,6 +57,13 @@
/*****************************************************************************
* decoder_sys_t : decoder descriptor
*****************************************************************************/
+
+struct hw_setup_params_s {
+ uint32_t i_codec;
+ unsigned int width;
+ unsigned int height;
+};
+
struct decoder_sys_t
{
AVCodecContext *p_context;
@@ -61,6 +72,9 @@ struct decoder_sys_t
/* Video decoder specific part */
date_t pts;
+ mtime_t dts0;
+ bool dts0_used;
+
/* Closed captions for decoders */
cc_data_t cc;
@@ -91,6 +105,7 @@ struct decoder_sys_t
2022-12-08 21:49:23 +00:00
/* VA API */
vlc_va_t *p_va;
enum PixelFormat pix_fmt;
+ enum PixelFormat sw_pix_fmt;
int profile;
int level;
@@ -359,6 +374,13 @@ static int lavc_CopyPicture(decoder_t *dec, picture_t *pic, AVFrame *frame)
{
decoder_sys_t *sys = dec->p_sys;
+ // DRM prime frames are alloced by the decoder
+ // To copy out just attach the buf to the pic
+ if (frame->format == AV_PIX_FMT_DRM_PRIME)
+ {
+ return drm_prime_attach_buf_to_pic(pic, frame);
+ }
+
vlc_fourcc_t fourcc = FindVlcChroma(frame->format);
if (!fourcc)
{
@@ -423,6 +445,9 @@ static int OpenVideoCodec( decoder_t *p_dec )
2022-12-08 21:49:23 +00:00
ctx->bits_per_coded_sample = p_dec->fmt_in.video.i_bits_per_pixel;
p_sys->pix_fmt = AV_PIX_FMT_NONE;
+ p_sys->sw_pix_fmt = AV_PIX_FMT_NONE;
+ p_sys->profile = -1;
+ p_sys->level = -1;
2022-12-08 21:49:23 +00:00
cc_Init( &p_sys->cc );
set_video_color_settings( &p_dec->fmt_in.video, ctx );
@@ -462,6 +487,32 @@ static int OpenVideoCodec( decoder_t *p_dec )
return 0;
}
+static es_format_t hw_fail;
+
+static bool
+hw_check_bad(const es_format_t * const fmt)
+{
+ if (hw_fail.i_codec == fmt->i_codec &&
+ hw_fail.video.i_width == fmt->video.i_width &&
+ hw_fail.video.i_height == fmt->video.i_height)
+ return true;
+
+ return false;
+}
+
+static void
+hw_set_bad(const es_format_t * const fmt)
+{
+ if (fmt->video.i_width != 0 && fmt->video.i_height != 0)
+ hw_fail = *fmt;
+}
+
+/*****************************************************************************
+ * InitVideo: initialize the video decoder
+ *****************************************************************************
+ * the ffmpeg codec will be opened, some memory allocated. The vout is not yet
+ * opened (done after the first decoded frame).
+ *****************************************************************************/
static int InitVideoDecCommon( decoder_t *p_dec )
{
decoder_sys_t *p_sys = p_dec->p_sys;
@@ -587,6 +638,8 @@ static int InitVideoDecCommon( decoder_t *p_dec )
/* ***** misc init ***** */
date_Init(&p_sys->pts, 1, 30001);
date_Set(&p_sys->pts, VLC_TICK_INVALID);
+ p_sys->dts0 = VLC_TICK_INVALID;
+ p_sys->dts0_used = false;
p_sys->b_first_frame = true;
p_sys->i_late_frames = 0;
p_sys->b_from_preroll = false;
@@ -610,17 +663,31 @@ static int InitVideoDecCommon( decoder_t *p_dec )
} else
p_sys->palette_sent = true;
+ // If we want DRM_PRIME then we need to create the context before Open
+ // * This probably applies to anything that wants device_ctx init
+ {
+ const AVCodecHWConfig * hw_config;
+ for (int i = 0; (hw_config = avcodec_get_hw_config(p_codec, i)) != NULL; ++i)
+ {
+ if ((hw_config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) != 0 &&
+ hw_config->device_type == AV_HWDEVICE_TYPE_DRM)
+ {
+ int err;
+ if ((err = av_hwdevice_ctx_create(&p_context->hw_device_ctx, hw_config->device_type, NULL, NULL, 0)) < 0) {
+ msg_Dbg(p_dec, "Failed to create specified HW device: %s", av_err2str(err));
+ goto error;
+ }
+ break;
+ }
+ }
+ }
+
/* ***** init this codec with special data ***** */
ffmpeg_InitCodec( p_dec );
/* ***** Open the codec ***** */
if( OpenVideoCodec( p_dec ) < 0 )
- {
- vlc_sem_destroy( &p_sys->sem_mt );
- free( p_sys );
- avcodec_free_context( &p_context );
- return VLC_EGENERIC;
- }
+ goto error;
p_dec->pf_decode = DecodeVideo;
p_dec->pf_flush = Flush;
@@ -631,6 +698,12 @@ static int InitVideoDecCommon( decoder_t *p_dec )
if( p_context->level != FF_LEVEL_UNKNOWN )
p_dec->fmt_in.i_level = p_context->level;
return VLC_SUCCESS;
+
+error:
+ vlc_sem_destroy( &p_sys->sem_mt );
+ free( p_sys );
+ avcodec_free_context( &p_context );
+ return VLC_EGENERIC;
}
static int ffmpeg_OpenVa(decoder_t *p_dec, AVCodecContext *p_context,
@@ -686,6 +759,10 @@ static int ffmpeg_OpenVa(decoder_t *p_dec, AVCodecContext *p_context,
static const enum PixelFormat hwfmts[] =
{
+#if OPT_RPI
+ // If Pi then do not bother with stuff we know will fail
+ AV_PIX_FMT_DRM_PRIME,
+#else
#ifdef _WIN32
#if LIBAVUTIL_VERSION_CHECK(54, 13, 1, 24, 100)
AV_PIX_FMT_D3D11VA_VLD,
@@ -695,6 +772,7 @@ static const enum PixelFormat hwfmts[] =
AV_PIX_FMT_VAAPI,
#if (LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 4, 0))
AV_PIX_FMT_VDPAU,
+#endif
#endif
AV_PIX_FMT_NONE,
};
@@ -814,11 +892,11 @@ failed:
* the ffmpeg codec will be opened, some memory allocated. The vout is not yet
* opened (done after the first decoded frame).
*****************************************************************************/
-int InitVideoDec( vlc_object_t *obj )
+static int InitVideoDec2( vlc_object_t *obj, const int hw )
{
decoder_t *p_dec = (decoder_t *)obj;
const AVCodec *p_codec;
- AVCodecContext *p_context = ffmpeg_AllocContext( p_dec, &p_codec );
+ AVCodecContext *p_context = ffmpeg_AllocContextHw( p_dec, &p_codec, hw );
if( p_context == NULL )
return VLC_EGENERIC;
@@ -840,6 +918,27 @@ int InitVideoDec( vlc_object_t *obj )
return InitVideoDecCommon( p_dec );
}
+int InitVideoDec( vlc_object_t *obj )
+{
+ decoder_t * const p_dec = (decoder_t *)obj;
+
+ // Don't retry something we know failed
+ if (!hw_check_bad(&p_dec->fmt_in))
+ {
+ if (InitVideoDec2(obj, 1) == 0)
+ return 0;
+
+ hw_set_bad(&p_dec->fmt_in);
+ msg_Dbg(p_dec, "Set hw fail for %4.4s %dx%d", (char *)&p_dec->fmt_in.i_codec, p_dec->fmt_in.video.i_width, p_dec->fmt_in.video.i_height);
+ }
+ else
+ {
+ msg_Dbg(p_dec, "Avoid trying hw decoder for %4.4s %dx%d", (char *)&p_dec->fmt_in.i_codec, p_dec->fmt_in.video.i_width, p_dec->fmt_in.video.i_height);
+ }
+
+ return InitVideoDec2(obj, 0);
+}
+
/*****************************************************************************
* Flush:
*****************************************************************************/
@@ -849,6 +948,8 @@ static void Flush( decoder_t *p_dec )
AVCodecContext *p_context = p_sys->p_context;
date_Set(&p_sys->pts, VLC_TICK_INVALID); /* To make sure we recover properly */
+ p_sys->dts0 = VLC_TICK_INVALID;
+ p_sys->dts0_used = false;
p_sys->i_late_frames = 0;
p_sys->b_draining = false;
cc_Flush( &p_sys->cc );
@@ -876,6 +977,8 @@ static bool check_block_validity( decoder_sys_t *p_sys, block_t *block )
if( block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED) )
{
date_Set( &p_sys->pts, VLC_TICK_INVALID ); /* To make sure we recover properly */
+ p_sys->dts0 = VLC_TICK_INVALID;
+ p_sys->dts0_used = false;
cc_Flush( &p_sys->cc );
p_sys->i_late_frames = 0;
@@ -1216,6 +1319,10 @@ static picture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block, bool *error
}
if( b_has_data )
{
+ /* Remember 1st DTS in case we need to invent a timebase */
+ if (p_sys->dts0 <= VLC_TICK_INVALID)
+ p_sys->dts0 = p_block->i_dts;
+
pkt->data = p_block->p_buffer;
pkt->size = p_block->i_buffer;
pkt->pts = p_block->i_pts > VLC_TICK_INVALID ? p_block->i_pts : AV_NOPTS_VALUE;
@@ -1322,6 +1429,17 @@ static picture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block, bool *error
if( i_pts == AV_NOPTS_VALUE )
i_pts = date_Get( &p_sys->pts );
+ /* VLC doesn't like having no pts - but a simple timestamp at the
+ * start of time is all that is needed to get it going - pick the
+ * first dts we saw as being in the right general area */
+ if (i_pts > VLC_TICK_INVALID)
+ p_sys->dts0_used = true;
+ else if (p_sys->dts0 > VLC_TICK_INVALID && !p_sys->dts0_used)
+ {
+ i_pts = p_sys->dts0;
+ p_sys->dts0_used = true;
+ }
+
/* Interpolate the next PTS */
if( i_pts > VLC_TICK_INVALID )
date_Set( &p_sys->pts, i_pts );
@@ -1374,9 +1492,10 @@ static picture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block, bool *error
{ /* When direct rendering is not used, get_format() and get_buffer()
* might not be called. The output video format must be set here
* then picture buffer can be allocated. */
- if (p_sys->p_va == NULL
2022-12-08 21:49:23 +00:00
+ if ((frame->format == AV_PIX_FMT_DRM_PRIME ||
+ p_sys->p_va == NULL)
&& lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt,
- p_context->pix_fmt) == 0)
2022-12-08 21:49:23 +00:00
+ p_context->sw_pix_fmt) == 0)
p_pic = decoder_NewPicture(p_dec);
if( !p_pic )
@@ -1769,6 +1888,7 @@ static enum PixelFormat ffmpeg_GetFormat( AVCodecContext *p_context,
}
swfmt = defaultfmt;
2022-12-08 21:49:23 +00:00
}
+ p_sys->sw_pix_fmt = swfmt;
if (p_sys->pix_fmt == AV_PIX_FMT_NONE)
goto no_reuse;
diff --git a/modules/gui/qt/components/interface_widgets.cpp b/modules/gui/qt/components/interface_widgets.cpp
index 4a0a0dae12..17946c58d6 100644
--- a/modules/gui/qt/components/interface_widgets.cpp
+++ b/modules/gui/qt/components/interface_widgets.cpp
@@ -107,6 +107,36 @@ void VideoWidget::sync( void )
#ifdef QT5_HAS_X11
if( QX11Info::isPlatformX11() )
XSync( QX11Info::display(), False );
+#endif
+ refreshHandles();
+}
+
+/**
+ * The wayland surface may change if the window is hidden which
+ * seems to happen sometimes on resize
+ * In Qt it looks like this happens if the window is hidden
+ **/
+void VideoWidget::refreshHandles()
+{
+#ifdef QT5_HAS_WAYLAND
+ if (!p_window || p_window->type != VOUT_WINDOW_TYPE_WAYLAND)
+ return;
+
+ /* Ensure only the video widget is native (needed for Wayland) */
+ stable->setAttribute( Qt::WA_DontCreateNativeAncestors, true);
+
+ QWindow *window = stable->windowHandle();
+ assert(window != NULL);
+ window->create();
+
+ QPlatformNativeInterface *qni = qApp->platformNativeInterface();
+ assert(qni != NULL);
+
+ p_window->handle.wl = static_cast<wl_surface*>(
+ qni->nativeResourceForWindow(QByteArrayLiteral("surface"),
+ window));
+ p_window->display.wl = static_cast<wl_display*>(
+ qni->nativeResourceForIntegration(QByteArrayLiteral("wl_display")));
2022-12-08 21:49:23 +00:00
#endif
}
2022-12-08 21:49:23 +00:00
@@ -165,21 +195,7 @@ bool VideoWidget::request( struct vout_window_t *p_wnd )
#ifdef QT5_HAS_WAYLAND
case VOUT_WINDOW_TYPE_WAYLAND:
{
- /* Ensure only the video widget is native (needed for Wayland) */
- stable->setAttribute( Qt::WA_DontCreateNativeAncestors, true);
-
- QWindow *window = stable->windowHandle();
- assert(window != NULL);
- window->create();
-
- QPlatformNativeInterface *qni = qApp->platformNativeInterface();
- assert(qni != NULL);
-
- p_wnd->handle.wl = static_cast<wl_surface*>(
- qni->nativeResourceForWindow(QByteArrayLiteral("surface"),
- window));
- p_wnd->display.wl = static_cast<wl_display*>(
- qni->nativeResourceForIntegration(QByteArrayLiteral("wl_display")));
+ refreshHandles();
break;
}
#endif
diff --git a/modules/gui/qt/components/interface_widgets.hpp b/modules/gui/qt/components/interface_widgets.hpp
index 583f2d1f50..9dfda5b1de 100644
--- a/modules/gui/qt/components/interface_widgets.hpp
+++ b/modules/gui/qt/components/interface_widgets.hpp
@@ -88,6 +88,7 @@ private:
bool enable_mouse_events;
void reportSize();
+ void refreshHandles();
signals:
void sizeChanged( int, int );
diff --git a/modules/gui/qt/qt.cpp b/modules/gui/qt/qt.cpp
index cefc75830f..3493db6958 100644
--- a/modules/gui/qt/qt.cpp
+++ b/modules/gui/qt/qt.cpp
@@ -31,6 +31,7 @@
#include <QApplication>
#include <QDate>
#include <QMutex>
+#include <QScreen>
#include "qt.hpp"
@@ -284,7 +285,7 @@ vlc_module_begin ()
add_string( "qt-slider-colours", "153;210;153;20;210;20;255;199;15;245;39;29",
SLIDERCOL_TEXT, SLIDERCOL_LONGTEXT, false )
- add_bool( "qt-privacy-ask", true, PRIVACY_TEXT, PRIVACY_TEXT,
+ add_bool( "qt-privacy-ask", false, PRIVACY_TEXT, PRIVACY_TEXT,
false )
change_private ()
@@ -529,6 +530,22 @@ static void *ThreadPlatform( void *obj, char *platform_name )
QApplication::setAttribute( Qt::AA_UseHighDpiPixmaps );
#endif
+ bool do_disable = false;
+ {
+ QVLCApp app( argc, argv );
+ const double dpr = app.devicePixelRatio();
+ if( dpr > 2.1 )
+ {
+ do_disable = true;
+ msg_Warn(p_intf, "devicePixelRatio (%g) > 2.1: assuming broken", dpr);
+ app.quit();
+ }
+ }
+ if( do_disable )
+ {
+ QApplication::setAttribute( Qt::AA_DisableHighDpiScaling );
+ }
+
/* Start the QApplication here */
QVLCApp app( argc, argv );
diff --git a/modules/hw/drm/Makefile.am b/modules/hw/drm/Makefile.am
new file mode 100644
index 0000000000..8c3232364e
--- /dev/null
+++ b/modules/hw/drm/Makefile.am
@@ -0,0 +1,26 @@
+drmdir = $(pluginsdir)/drm
+
+libdrm_avcodec_plugin_la_SOURCES = hw/drm/drm_avcodec.c
+libdrm_avcodec_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS)
+libdrm_avcodec_plugin_la_LIBADD = $(AVCODEC_LIBS)
+
+libdrm_gl_conv_plugin_la_SOURCES = hw/drm/drm_gl_conv.c
+libdrm_gl_conv_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS)
+
+libdrm_conv_sand30_plugin_la_SOURCES = hw/drm/conv_sand30.c
+libdrm_conv_sand30_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS)
+libdrm_conv_sand30_plugin_la_LIBADD = $(AVCODEC_LIBS)
+
+libdrm_av_deinterlace_plugin_la_SOURCES = hw/drm/drm_av_deinterlace.c \
+ codec/avcodec/drm_pic.c codec/avcodec/drm_pic.h
+libdrm_av_deinterlace_plugin_la_CFLAGS = $(AM_CFLAGS) $(AVCODEC_CFLAGS)
+libdrm_av_deinterlace_plugin_la_LDFLAGS = $(AM_LDFLAGS) $(AVCODEC_LDFLAGS) -lavfilter
+libdrm_av_deinterlace_plugin_la_LIBADD = $(AVCODEC_LIBS)
+
+if HAVE_DRM
+drm_LTLIBRARIES = \
+ libdrm_av_deinterlace_plugin.la \
+ libdrm_avcodec_plugin.la \
+ libdrm_conv_sand30_plugin.la \
+ libdrm_gl_conv_plugin.la
+endif
diff --git a/modules/hw/drm/conv_sand30.c b/modules/hw/drm/conv_sand30.c
new file mode 100644
index 0000000000..c60b5462e5
--- /dev/null
+++ b/modules/hw/drm/conv_sand30.c
2022-12-08 21:49:23 +00:00
@@ -0,0 +1,212 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_filter.h>
+#include <vlc_picture.h>
+#include <vlc_plugin.h>
+
+#include <libavutil/buffer.h>
+#include <libavutil/frame.h>
+#include <libavutil/hwcontext.h>
+
+#include "../../codec/avcodec/drm_pic.h"
+
+#include <assert.h>
+
2022-12-08 21:49:23 +00:00
+#define TRACE_ALL 0
+
+//----------------------------------------------------------------------------
+//
+// Simple copy in to ZC
+
+typedef struct to_nv12_sys_s {
+ int dummy;
+} to_nv12_sys_t;
+
+static vlc_fourcc_t
+dst_fourcc_vlc_to_av(const vlc_fourcc_t av)
+{
+ switch (av) {
2022-12-08 21:49:23 +00:00
+ case VLC_CODEC_I420:
+ return AV_PIX_FMT_YUV420P;
+ case VLC_CODEC_NV12:
+ return AV_PIX_FMT_NV12;
+ case VLC_CODEC_I420_10L:
+ return AV_PIX_FMT_YUV420P10LE;
+ }
+ return 0;
+}
+
+static void
+pic_buf_free(void *opaque, uint8_t *data)
+{
+ VLC_UNUSED(data);
+ picture_Release(opaque);
+}
+
+static AVBufferRef *
+mk_buf_from_pic(picture_t * const pic, uint8_t * const data, const size_t size)
+{
+ return av_buffer_create(data, size, pic_buf_free, picture_Hold(pic), 0);
+}
+
+static picture_t *
+to_nv12_filter(filter_t *p_filter, picture_t *in_pic)
+{
+ to_nv12_sys_t * const sys = (to_nv12_sys_t *)p_filter->p_sys;
+#if TRACE_ALL
+ msg_Dbg(p_filter, "<<< %s", __func__);
+#endif
+ AVFrame * frame_in = av_frame_alloc();
+ AVFrame * frame_out = av_frame_alloc();
+ drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)in_pic->context;
+ int rv;
+
+ VLC_UNUSED(sys);
+
+ if (!frame_in || !frame_out || !pctx)
+ goto fail0;
+
+ picture_t * const out_pic = filter_NewPicture(p_filter);
+ if (out_pic == NULL)
+ goto fail0;
+
+ frame_in->format = AV_PIX_FMT_DRM_PRIME;
+ frame_in->buf[0] = av_buffer_ref(pctx->buf);
+ frame_in->data[0] = (uint8_t *)pctx->desc;
+ frame_in->hw_frames_ctx = av_buffer_ref(pctx->hw_frames_ctx);
+ frame_in->width = in_pic->format.i_width;
+ frame_in->height = in_pic->format.i_height;
+ frame_in->crop_left = in_pic->format.i_x_offset;
+ frame_in->crop_top = in_pic->format.i_y_offset;
+ frame_in->crop_right = frame_in->width - in_pic->format.i_visible_width - frame_in->crop_left;
+ frame_in->crop_bottom = frame_in->height - in_pic->format.i_visible_height - frame_in->crop_top;
+
+ frame_out->format = dst_fourcc_vlc_to_av(p_filter->fmt_out.video.i_chroma);
+ frame_out->width = out_pic->format.i_width;
+ frame_out->height = out_pic->format.i_height;
+ for (int i = 0; i != out_pic->i_planes; ++i) {
+ frame_out->buf[i] = mk_buf_from_pic(out_pic, out_pic->p[i].p_pixels, out_pic->p[i].i_lines * out_pic->p[i].i_pitch);
+ if (!frame_out->buf[i]) {
+ msg_Err(p_filter, "Failed to make buf from pic");
+ goto fail1;
+ }
+ frame_out->data[i] = out_pic->p[i].p_pixels;
+ frame_out->linesize[i] = out_pic->p[i].i_pitch;
+ }
+
+ if ((rv = av_hwframe_transfer_data(frame_out, frame_in, 0)) != 0) {
+ msg_Err(p_filter, "Failed to transfer data: %s", av_err2str(rv));
+ goto fail1;
+ }
+
+ av_frame_free(&frame_in);
+ av_frame_free(&frame_out);
+ picture_Release(in_pic);
+ return out_pic;
+
+fail1:
+ picture_Release(out_pic);
+fail0:
+ av_frame_free(&frame_in);
+ av_frame_free(&frame_out);
+ picture_Release(in_pic);
+ return NULL;
+}
+
+static void to_nv12_flush(filter_t * p_filter)
+{
+ VLC_UNUSED(p_filter);
+}
+
+static void CloseConverterToNv12(vlc_object_t * obj)
+{
+ filter_t * const p_filter = (filter_t *)obj;
+ to_nv12_sys_t * const sys = (to_nv12_sys_t *)p_filter->p_sys;
+
+ if (sys == NULL)
+ return;
+
+ p_filter->p_sys = NULL;
+
+ free(sys);
+}
+
+static bool to_nv12_validate_fmt(const video_format_t * const f_in, const video_format_t * const f_out)
+{
+ if (f_in->i_height != f_out->i_height ||
+ f_in->i_width != f_out->i_width)
+ {
+ return false;
+ }
+
2022-12-08 21:49:23 +00:00
+ if (f_in->i_chroma == VLC_CODEC_DRM_PRIME_SAND8 &&
+ (f_out->i_chroma == VLC_CODEC_I420 || f_out->i_chroma == VLC_CODEC_NV12))
+ return true;
+
+ if (f_in->i_chroma == VLC_CODEC_DRM_PRIME_I420 &&
+ f_out->i_chroma == VLC_CODEC_I420)
+ return true;
+
+ if (f_in->i_chroma == VLC_CODEC_DRM_PRIME_NV12 &&
+ f_out->i_chroma == VLC_CODEC_NV12)
+ return true;
+
+ if (f_in->i_chroma == VLC_CODEC_DRM_PRIME_SAND30 &&
+ (f_out->i_chroma == VLC_CODEC_I420_10L || f_out->i_chroma == VLC_CODEC_NV12))
+ return true;
+
+ return false;
+}
+
+static int OpenConverterToNv12(vlc_object_t * obj)
+{
+ int ret = VLC_EGENERIC;
+ filter_t * const p_filter = (filter_t *)obj;
+
+ if (!to_nv12_validate_fmt(&p_filter->fmt_in.video, &p_filter->fmt_out.video))
+ goto fail;
+
+ {
+ msg_Dbg(p_filter, "%s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d->%s,%dx%d [(%d,%d) %dx%d] rgb:%#x:%#x:%#x sar:%d/%d", __func__,
+ fourcc2str(p_filter->fmt_in.video.i_chroma),
+ p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height,
+ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset,
+ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height,
+ p_filter->fmt_in.video.i_sar_num, p_filter->fmt_in.video.i_sar_den,
+ fourcc2str(p_filter->fmt_out.video.i_chroma),
+ p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height,
+ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset,
+ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height,
+ p_filter->fmt_out.video.i_rmask, p_filter->fmt_out.video.i_gmask, p_filter->fmt_out.video.i_bmask,
+ p_filter->fmt_out.video.i_sar_num, p_filter->fmt_out.video.i_sar_den);
+ }
+
+ to_nv12_sys_t * const sys = calloc(1, sizeof(*sys));
+ if (!sys) {
+ ret = VLC_ENOMEM;
+ goto fail;
+ }
+ p_filter->p_sys = (filter_sys_t *)sys;
+
+ p_filter->pf_video_filter = to_nv12_filter;
+ p_filter->pf_flush = to_nv12_flush;
+ return VLC_SUCCESS;
+
+fail:
+ CloseConverterToNv12(obj);
+ return ret;
+}
+
+vlc_module_begin()
+ set_category( CAT_VIDEO )
+ set_subcategory( SUBCAT_VIDEO_VFILTER )
2022-12-08 21:49:23 +00:00
+ set_shortname(N_("DRMPRIME to s/w"))
+ set_description(N_("DRMPRIME-to software picture filter"))
+ add_shortcut("drmprime_to_sw")
+ set_capability( "video converter", 50 )
+ set_callbacks(OpenConverterToNv12, CloseConverterToNv12)
+vlc_module_end()
+
diff --git a/modules/hw/drm/drm_av_deinterlace.c b/modules/hw/drm/drm_av_deinterlace.c
new file mode 100644
index 0000000000..9e0b569989
--- /dev/null
+++ b/modules/hw/drm/drm_av_deinterlace.c
@@ -0,0 +1,273 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_filter.h>
+#include <vlc_picture.h>
+#include <vlc_plugin.h>
+
+#include "../../codec/avcodec/drm_pic.h"
+
+#include <libavcodec/avcodec.h>
+#include <libavfilter/buffersink.h>
+#include <libavfilter/buffersrc.h>
+#include <libavformat/avformat.h>
+#include <libavutil/opt.h>
+#include <libavutil/pixdesc.h>
+#include <libavutil/hwcontext.h>
+
+#define TRACE_ALL 1
+
+typedef struct filter_sys_t {
+ AVFilterGraph *filter_graph;
+ AVFilterContext *buffersink_ctx; // Allocated within graph - no explicit free
+ AVFilterContext *buffersrc_ctx; // Allocated within graph - no explicit free
+ bool has_out;
+ AVFrame *out_frame;
+} filter_sys_t;
+
+static void drmp_av_flush(filter_t * filter)
+{
+ // Nothing to do
+ VLC_UNUSED(filter);
+
+#if TRACE_ALL
+ msg_Dbg(filter, "<<< %s", __func__);
+#endif
+}
+
+static picture_t * drmp_av_deinterlace(filter_t * filter, picture_t * in_pic)
+{
+ filter_sys_t *const sys = filter->p_sys;
+ AVFrame * frame = av_frame_alloc();
+ drm_prime_video_sys_t * const pctx = (drm_prime_video_sys_t *)in_pic->context;
+ picture_t * out_pic = NULL;
+ picture_t ** pp_pic = &out_pic;
+ int ret;
+
+#if TRACE_ALL
+ msg_Dbg(filter, "<<< %s", __func__);
+#endif
+
+ if (!frame) {
+ msg_Err(filter, "Frame alloc failure");
+ goto fail;
+ }
+
+ frame->format = AV_PIX_FMT_DRM_PRIME;
+ frame->buf[0] = av_buffer_ref(pctx->buf);
+ frame->data[0] = (uint8_t *)pctx->desc;
+ frame->hw_frames_ctx = av_buffer_ref(pctx->hw_frames_ctx);
+ frame->width = in_pic->format.i_width;
+ frame->height = in_pic->format.i_height;
+ frame->crop_left = in_pic->format.i_x_offset;
+ frame->crop_top = in_pic->format.i_y_offset;
+ frame->crop_right = frame->width - in_pic->format.i_visible_width - frame->crop_left;
+ frame->crop_bottom = frame->height - in_pic->format.i_visible_height - frame->crop_top;
+ frame->interlaced_frame = !in_pic->b_progressive;
+ frame->top_field_first = in_pic->b_top_field_first;
+ frame->pts = (in_pic->date == VLC_TS_INVALID) ? AV_NOPTS_VALUE : in_pic->date;
+
+ picture_Release(in_pic);
+ in_pic = NULL;
+
+ if ((ret = av_buffersrc_add_frame_flags(sys->buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) {
+ msg_Err(filter, "Failed to feed filtergraph: %s", av_err2str(ret));
+ goto fail;
+ }
+ av_frame_unref(frame);
+
+ while (sys->has_out || (ret = av_buffersink_get_frame(sys->buffersink_ctx, sys->out_frame)) == 0) {
+ picture_t *const pic = filter_NewPicture(filter);
+ sys->has_out = true;
+ // Failure to get an output pic happens quite often, just keep the
+ // frame for next time
+ if (!pic)
+ break;
+
+ if (drm_prime_attach_buf_to_pic(pic, sys->out_frame) != VLC_SUCCESS) {
+ msg_Err(filter, "Failed to attach frame to out pic");
+ picture_Release(pic);
+ goto fail;
+ }
+ pic->date = sys->out_frame->pts == AV_NOPTS_VALUE ? VLC_TS_INVALID : sys->out_frame->pts;
+ av_frame_unref(sys->out_frame);
+ sys->has_out = false;
+
+ *pp_pic = pic;
+ pp_pic = &pic->p_next;
+ }
+
+ if (ret < 0 && ret != AVERROR_EOF && ret != AVERROR(EAGAIN)) {
+ msg_Err(filter, "Failed to get frame: %s", av_err2str(ret));
+ goto fail;
+ }
+
+ // Even if we get an error we may have processed some pics and we need to return them
+fail:
+ if (in_pic)
+ picture_Release(in_pic);
+ av_frame_free(&frame);
+
+#if TRACE_ALL
+ msg_Dbg(filter, ">>> %s: %p", __func__, out_pic);
+#endif
+
+ return out_pic;
+}
+
+static void CloseDrmpAvDeinterlace(filter_t *filter)
+{
+ filter_sys_t * const sys = filter->p_sys;
+
+#if TRACE_ALL
+ msg_Dbg(filter, "<<< %s", __func__);
+#endif
+
+ if (sys == NULL)
+ return;
+
+ av_frame_free(&sys->out_frame);
+ avfilter_graph_free(&sys->filter_graph);
+ free(sys);
+}
+
+
+// Copied almost directly from ffmpeg filtering_video.c example
+static int init_filters(filter_t * const filter,
+ const char * const filters_descr)
+{
+ filter_sys_t *const sys = filter->p_sys;
+ const video_format_t * const fmt = &filter->fmt_in.video;
+ char args[512];
+ int ret = 0;
+ const AVFilter *buffersrc = avfilter_get_by_name("buffer");
+ const AVFilter *buffersink = avfilter_get_by_name("buffersink");
+ AVFilterInOut *outputs = avfilter_inout_alloc();
+ AVFilterInOut *inputs = avfilter_inout_alloc();
+ enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_DRM_PRIME, AV_PIX_FMT_NONE };
+
+ sys->out_frame = av_frame_alloc();
+ sys->filter_graph = avfilter_graph_alloc();
+ if (!outputs || !inputs || !sys->filter_graph || !sys->out_frame) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+
+ /* buffer video source: the decoded frames from the decoder will be inserted here. */
+ snprintf(args, sizeof(args),
+ "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
+ fmt->i_visible_width, fmt->i_visible_height, AV_PIX_FMT_DRM_PRIME,
+ 1, (int)CLOCK_FREQ,
+ fmt->i_sar_num, fmt->i_sar_den);
+
+ ret = avfilter_graph_create_filter(&sys->buffersrc_ctx, buffersrc, "in",
+ args, NULL, sys->filter_graph);
+ if (ret < 0) {
+ msg_Err(filter, "Cannot create buffer source");
+ goto end;
+ }
+
+ /* buffer video sink: to terminate the filter chain. */
+ ret = avfilter_graph_create_filter(&sys->buffersink_ctx, buffersink, "out",
+ NULL, NULL, sys->filter_graph);
+ if (ret < 0) {
+ msg_Err(filter, "Cannot create buffer sink");
+ goto end;
+ }
+
+ ret = av_opt_set_int_list(sys->buffersink_ctx, "pix_fmts", pix_fmts,
+ AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
+ if (ret < 0) {
+ msg_Err(filter, "Cannot set output pixel format");
+ goto end;
+ }
+
+ /*
+ * Set the endpoints for the filter graph. The filter_graph will
+ * be linked to the graph described by filters_descr.
+ */
+
+ /*
+ * The buffer source output must be connected to the input pad of
+ * the first filter described by filters_descr; since the first
+ * filter input label is not specified, it is set to "in" by
+ * default.
+ */
+ outputs->name = av_strdup("in");
+ outputs->filter_ctx = sys->buffersrc_ctx;
+ outputs->pad_idx = 0;
+ outputs->next = NULL;
+
+ /*
+ * The buffer sink input must be connected to the output pad of
+ * the last filter described by filters_descr; since the last
+ * filter output label is not specified, it is set to "out" by
+ * default.
+ */
+ inputs->name = av_strdup("out");
+ inputs->filter_ctx = sys->buffersink_ctx;
+ inputs->pad_idx = 0;
+ inputs->next = NULL;
+
+ if ((ret = avfilter_graph_parse_ptr(sys->filter_graph, filters_descr,
+ &inputs, &outputs, NULL)) < 0)
+ goto end;
+
+ if ((ret = avfilter_graph_config(sys->filter_graph, NULL)) < 0)
+ goto end;
+
+end:
+ avfilter_inout_free(&inputs);
+ avfilter_inout_free(&outputs);
+
+ return ret == 0 ? VLC_SUCCESS : ret == AVERROR(ENOMEM) ? VLC_ENOMEM : VLC_EGENERIC;
+}
+
+static bool is_fmt_valid_in(const vlc_fourcc_t fmt)
+{
+ return fmt == VLC_CODEC_DRM_PRIME_I420 ||
+ fmt == VLC_CODEC_DRM_PRIME_NV12 ||
+ fmt == VLC_CODEC_DRM_PRIME_SAND8;
+}
+
+static int OpenDrmpAvDeinterlace(filter_t *filter)
+{
+ filter_sys_t *sys;
+ int ret;
+
+ msg_Dbg(filter, "<<< %s", __func__);
+
+ if (!is_fmt_valid_in(filter->fmt_in.video.i_chroma) ||
+ filter->fmt_out.video.i_chroma != filter->fmt_in.video.i_chroma)
+ return VLC_EGENERIC;
+
+ sys = calloc(1, sizeof(filter_sys_t));
+ if (!sys)
+ return VLC_ENOMEM;
+ filter->p_sys = sys;
+
+ if ((ret = init_filters(filter, "deinterlace_v4l2m2m")) != 0)
+ goto fail;
+
+ filter->pf_video_filter = drmp_av_deinterlace;
+ filter->pf_flush = drmp_av_flush;
+
+ return VLC_SUCCESS;
+
+fail:
+ CloseDrmpAvDeinterlace(filter);
+ return VLC_EGENERIC;
+}
+
+vlc_module_begin()
+ set_shortname(N_("DRM PRIME deinterlace"))
+ set_description(N_("libav-based DRM_PRIME deinterlace filter plugin"))
+ set_capability("video filter", 902)
+ set_category(CAT_VIDEO)
+ set_subcategory(SUBCAT_VIDEO_VFILTER)
+ set_callbacks(OpenDrmpAvDeinterlace, CloseDrmpAvDeinterlace)
+ add_shortcut("deinterlace")
+vlc_module_end()
+
diff --git a/modules/hw/drm/drm_avcodec.c b/modules/hw/drm/drm_avcodec.c
new file mode 100644
index 0000000000..b20eaf700e
--- /dev/null
+++ b/modules/hw/drm/drm_avcodec.c
@@ -0,0 +1,80 @@
+/*****************************************************************************
+ * avcodec.c: VDPAU decoder for libav
+ *****************************************************************************
+ * Copyright (C) 2012-2013 Rémi Denis-Courmont
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_fourcc.h>
+#include <vlc_picture.h>
+#include "../../codec/avcodec/va.h"
+
+// Dummy - not expected to be called
+static int drm_va_get(vlc_va_t *va, picture_t *pic, uint8_t **data)
+{
+ VLC_UNUSED(va);
+ VLC_UNUSED(pic);
+ VLC_UNUSED(data);
+
+ return VLC_SUCCESS;
+}
+
+static int Open(vlc_va_t *va, AVCodecContext *avctx, const AVPixFmtDescriptor *desc,
+ enum PixelFormat pix_fmt,
+ const es_format_t *fmt, picture_sys_t *p_sys)
+{
+ VLC_UNUSED(fmt);
+ VLC_UNUSED(p_sys);
+ VLC_UNUSED(desc);
+
+ msg_Dbg(va, "%s: pix_fmt=%d", __func__, pix_fmt);
+
+ if (pix_fmt != AV_PIX_FMT_DRM_PRIME)
+ return VLC_EGENERIC;
+
+ // This gives us whatever the decode requires + 6 frames that will be
+ // alloced by ffmpeg before it blocks (at least for Pi HEVC)
+ avctx->extra_hw_frames = 6;
+
+ va->description = "DRM Video Accel";
+ va->get = drm_va_get;
+ return VLC_SUCCESS;
+}
+
+static void Close(vlc_va_t *va, void **hwctx)
+{
+ VLC_UNUSED(hwctx);
+
+ msg_Dbg(va, "%s", __func__);
+}
+
+vlc_module_begin()
+ set_description(N_("DRM video decoder"))
+ set_capability("hw decoder", 100)
+ set_category(CAT_INPUT)
+ set_subcategory(SUBCAT_INPUT_VCODEC)
+ set_callbacks(Open, Close)
+ add_shortcut("drm_prime")
+vlc_module_end()
diff --git a/modules/hw/drm/drm_gl_conv.c b/modules/hw/drm/drm_gl_conv.c
new file mode 100644
index 0000000000..b61b55f4e6
--- /dev/null
+++ b/modules/hw/drm/drm_gl_conv.c
2023-03-28 20:02:15 +00:00
@@ -0,0 +1,367 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_picture.h>
+
+#include <libavutil/hwcontext_drm.h>
+#include <libdrm/drm_fourcc.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include "../../video_output/opengl/converter.h"
+#include "../../codec/avcodec/drm_pic.h"
+
+#include <assert.h>
+
+#define TRACE_ALL 0
+
2022-12-08 21:49:23 +00:00
+#define ICACHE_SIZE 2
+
+typedef struct drm_gl_converter_s
+{
+ EGLint drm_fourcc;
+
2022-12-08 21:49:23 +00:00
+ unsigned int icache_n;
+ struct icache_s {
+ EGLImageKHR last_image;
+ picture_context_t * last_ctx;
+ } icache[ICACHE_SIZE];
+
+ PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
+} drm_gl_converter_t;
+
+
2022-12-08 21:49:23 +00:00
+static void
+unset_icache_ent(const opengl_tex_converter_t * const tc, struct icache_s * const s)
+{
2022-12-08 21:49:23 +00:00
+ if (s->last_image)
+ {
2022-12-08 21:49:23 +00:00
+ tc->gl->egl.destroyImageKHR(tc->gl, s->last_image);
+ s->last_image = NULL;
+ }
+
2022-12-08 21:49:23 +00:00
+ if (s->last_ctx)
+ {
2022-12-08 21:49:23 +00:00
+ s->last_ctx->destroy(s->last_ctx);
+ s->last_ctx = NULL;
+ }
+}
+
2022-12-08 21:49:23 +00:00
+static void
+update_icache(const opengl_tex_converter_t * const tc, EGLImageKHR image, picture_t * pic)
+{
+ drm_gl_converter_t * const sys = tc->priv;
2022-12-08 21:49:23 +00:00
+ struct icache_s * const s = sys->icache + sys->icache_n;
+
+ s->last_image = image;
+ // DRM buffer is held by the context, pictures can be in surprisingly
+ // small pools for filters so let go of the pic and keep a ref on the
+ // context
+ unset_icache_ent(tc, s);
+ s->last_ctx = pic->context->copy(pic->context);
+ sys->icache_n = sys->icache_n + 1 >= ICACHE_SIZE ? 0 : sys->icache_n + 1;
+}
+
+static int
+tc_drm_update(const opengl_tex_converter_t *tc, GLuint *textures,
+ const GLsizei *tex_width, const GLsizei *tex_height,
+ picture_t *pic, const size_t *plane_offset)
+{
+ drm_gl_converter_t * const sys = tc->priv;
+#if TRACE_ALL
+ {
+ char cbuf[5];
+ msg_Dbg(tc, "%s: %s %d*%dx%d : %d*%dx%d", __func__,
+ str_fourcc(cbuf, pic->format.i_chroma),
+ tc->tex_count, tex_width[0], tex_height[0], pic->i_planes, pic->p[0].i_pitch, pic->p[0].i_lines);
+ }
+#endif
+ VLC_UNUSED(tex_width);
+ VLC_UNUSED(tex_height);
+ VLC_UNUSED(plane_offset);
+
+ {
+ const AVDRMFrameDescriptor * const desc = drm_prime_get_desc(pic);
+ EGLint attribs[64] = {0};
+
+ static const EGLint plane_exts[] = {
+ EGL_DMA_BUF_PLANE0_FD_EXT,
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE0_PITCH_EXT,
+ EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
+ EGL_DMA_BUF_PLANE1_FD_EXT,
+ EGL_DMA_BUF_PLANE1_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE1_PITCH_EXT,
+ EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT,
+ EGL_DMA_BUF_PLANE2_FD_EXT,
+ EGL_DMA_BUF_PLANE2_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE2_PITCH_EXT,
+ EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT,
+ };
+
+ // msg_Info(o, "<<< %s", __func__);
+
+ if (!desc)
+ {
+ msg_Err(tc, "%s: No DRM Frame desriptor found", __func__);
+ return VLC_EGENERIC;
+ }
+
+ EGLint *a = attribs;
+ *a++ = EGL_WIDTH;
+ *a++ = tex_width[0];
+ *a++ = EGL_HEIGHT;
+ *a++ = tex_height[0];
+ *a++ = EGL_LINUX_DRM_FOURCC_EXT;
+ *a++ = desc->layers[0].format;
+
+ const EGLint * ext = plane_exts;
+
+ for (int i = 0; i < desc->nb_layers; ++i)
+ {
+ const AVDRMLayerDescriptor * const layer = desc->layers + i;
+ for (int j = 0; j != layer->nb_planes; ++j)
+ {
+ const AVDRMPlaneDescriptor * const plane = layer->planes + j;
+ const AVDRMObjectDescriptor * const obj = desc->objects + plane->object_index;
+
+ *a++ = *ext++; // FD
+ *a++ = obj->fd;
+ *a++ = *ext++; // OFFSET
+ *a++ = plane->offset;
+ *a++ = *ext++; // PITCH
+ *a++ = plane->pitch;
+ if (!obj->format_modifier || obj->format_modifier == DRM_FORMAT_MOD_INVALID)
+ {
+ ext += 2;
+ }
+ else
+ {
+ *a++ = *ext++; // MODIFIER_LO
+ *a++ = (EGLint)(obj->format_modifier & 0xffffffff);
+ *a++ = *ext++; // MODIFIER_HI
+ *a++ = (EGLint)(obj->format_modifier >> 32);
+ }
+ }
+ }
+ *a++ = EGL_NONE;
+ *a++ = 0;
+
+ const EGLImageKHR image = tc->gl->egl.createImageKHR(tc->gl, EGL_LINUX_DMA_BUF_EXT, NULL, attribs);
+ if (!image) {
+ msg_Err(tc, "Failed to createImageKHR: Err=%#x", tc->vt->GetError());
+ return VLC_EGENERIC;
+ }
+
+ // *** MMAL ZC does this a little differently
+ // tc->tex_target == OES???
+
+ tc->vt->BindTexture(GL_TEXTURE_EXTERNAL_OES, textures[0]);
+ tc->vt->TexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ tc->vt->TexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ sys->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image);
+
2022-12-08 21:49:23 +00:00
+ update_icache(tc, image, pic);
+ }
+
+ return VLC_SUCCESS;
+}
+
+static int
+tc_drm_fetch_locations(opengl_tex_converter_t *tc, GLuint program)
+{
+ tc->uloc.Texture[0] = tc->vt->GetUniformLocation(program, "Texture0");
+ return tc->uloc.Texture[0] != -1 ? VLC_SUCCESS : VLC_EGENERIC;
+}
+
+static void
+tc_drm_prepare_shader(const opengl_tex_converter_t *tc,
+ const GLsizei *tex_width, const GLsizei *tex_height,
+ float alpha)
+{
+ (void) tex_width; (void) tex_height; (void) alpha;
+ VLC_UNUSED(tc);
+// tc->vt->Uniform1i(tc->uloc.Texture[0], 0);
+}
+
+static GLuint
+tc_fragment_shader_init(opengl_tex_converter_t * const tc, const GLenum tex_target,
+ const vlc_fourcc_t chroma, const video_color_space_t yuv_space)
+{
+ VLC_UNUSED(yuv_space);
+
+ tc->tex_count = 1;
+ tc->tex_target = tex_target;
+ tc->texs[0] = (struct opengl_tex_cfg) {
+ { 1, 1 }, { 1, 1 }, GL_RGB, chroma, GL_UNSIGNED_SHORT //** ??
+ };
+
+ tc->pf_fetch_locations = tc_drm_fetch_locations;
+ tc->pf_prepare_shader = tc_drm_prepare_shader;
+
+
+ const char fs[] =
+ "#extension GL_OES_EGL_image_external : enable\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES Texture0;\n"
+ "varying vec2 TexCoord0;\n"
+ "void main() {\n"
+ " gl_FragColor = texture2D(Texture0, TexCoord0);\n"
+ "}\n";
+
+
+ const char *code = fs;
+
+ GLuint fragment_shader = tc->vt->CreateShader(GL_FRAGMENT_SHADER);
+ tc->vt->ShaderSource(fragment_shader, 1, &code, NULL);
+ tc->vt->CompileShader(fragment_shader);
+ return fragment_shader;
+}
+
+
+static void
+CloseGLConverter(vlc_object_t *obj)
+{
+ opengl_tex_converter_t * const tc = (opengl_tex_converter_t *)obj;
+ drm_gl_converter_t * const sys = tc->priv;
2022-12-08 21:49:23 +00:00
+ unsigned int i;
+
+ if (sys == NULL)
+ return;
+
2022-12-08 21:49:23 +00:00
+ for (i = 0; i != ICACHE_SIZE; ++i)
+ unset_icache_ent(tc, sys->icache + i);
+ free(sys);
+}
+
2023-03-28 20:02:15 +00:00
+#ifndef DRM_FORMAT_P030
+#define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0')
+#endif
+
+static struct vlc_to_drm_mod_s {
+ vlc_fourcc_t chroma;
+ uint32_t drm_fmt;
+ uint64_t drm_mod;
+} vlc_to_drm_mods[] = {
+ {VLC_CODEC_DRM_PRIME_I420, DRM_FORMAT_YUV420, DRM_FORMAT_MOD_LINEAR},
+ {VLC_CODEC_DRM_PRIME_NV12, DRM_FORMAT_NV12, DRM_FORMAT_MOD_LINEAR},
+ {VLC_CODEC_DRM_PRIME_SAND8, DRM_FORMAT_NV12, DRM_FORMAT_MOD_BROADCOM_SAND128},
+ {VLC_CODEC_DRM_PRIME_SAND30, DRM_FORMAT_P030, DRM_FORMAT_MOD_BROADCOM_SAND128},
+};
+
+static bool check_chroma(opengl_tex_converter_t * const tc)
+{
+ char fcc[5] = {0};
+ vlc_fourcc_to_char(tc->fmt.i_chroma, fcc);
+ uint32_t fmt = 0;
+ uint64_t mod = DRM_FORMAT_MOD_INVALID;
+ uint64_t mods[16];
+ int32_t mod_count = 0;
+
+ for (unsigned int i = 0; i != ARRAY_SIZE(vlc_to_drm_mods); ++i)
+ {
+ if (tc->fmt.i_chroma == vlc_to_drm_mods[i].chroma)
+ {
+ fmt = vlc_to_drm_mods[i].drm_fmt;
+ mod = vlc_to_drm_mods[i].drm_mod;
+ break;
+ }
+ }
+ if (!fmt)
+ return false;
+
+ if (!tc->gl->egl.queryDmaBufModifiersEXT)
+ {
+ msg_Dbg(tc, "No queryDmaBufModifiersEXT");
+ return false;
+ }
+
+ if (!tc->gl->egl.queryDmaBufModifiersEXT(tc->gl, fmt, 16, mods, NULL, &mod_count))
+ {
+ msg_Dbg(tc, "queryDmaBufModifiersEXT Failed for %s", fcc);
+ return false;
+ }
+
+ for (int32_t i = 0; i < mod_count; ++i)
+ {
+ if (mods[i] == mod)
+ return true;
+ }
+ msg_Dbg(tc, "Mod %" PRIx64 " not found for %s/%.4s in %d mods", mod, fcc, (char*)&fmt, mod_count);
+ return false;
+}
+
+static int
+OpenGLConverter(vlc_object_t *obj)
+{
+ opengl_tex_converter_t * const tc = (opengl_tex_converter_t *)obj;
+ int rv = VLC_EGENERIC;
+
+ // Do we know what to do with this?
2023-03-28 20:02:15 +00:00
+ if (!check_chroma(tc))
+ return VLC_EGENERIC;
+
+ {
+ msg_Dbg(tc, "<<< %s: %.4s %dx%d [(%d,%d) %d/%d] sar:%d/%d", __func__,
+ (char *)&tc->fmt.i_chroma,
+ tc->fmt.i_width, tc->fmt.i_height,
+ tc->fmt.i_x_offset, tc->fmt.i_y_offset,
+ tc->fmt.i_visible_width, tc->fmt.i_visible_height,
+ tc->fmt.i_sar_num, tc->fmt.i_sar_den);
+ }
+
+ if (tc->gl->ext != VLC_GL_EXT_EGL ||
+ !tc->gl->egl.createImageKHR || !tc->gl->egl.destroyImageKHR)
+ {
+ // Missing an important callback
+ msg_Dbg(tc, "Missing EGL xxxImageKHR calls");
+ return rv;
+ }
+
+ if ((tc->priv = calloc(1, sizeof(drm_gl_converter_t))) == NULL)
+ {
+ msg_Err(tc, "priv alloc failure");
+ rv = VLC_ENOMEM;
+ goto fail;
+ }
+ drm_gl_converter_t * const sys = tc->priv;
+
+ if ((sys->glEGLImageTargetTexture2DOES = vlc_gl_GetProcAddress(tc->gl, "glEGLImageTargetTexture2DOES")) == NULL)
+ {
+ msg_Err(tc, "Failed to bind GL fns");
+ goto fail;
+ }
+
+ if ((tc->fshader = tc_fragment_shader_init(tc, GL_TEXTURE_EXTERNAL_OES, tc->fmt.i_chroma, tc->fmt.space)) == 0)
+ {
+ msg_Err(tc, "Failed to make shader");
+ goto fail;
+ }
+
+ tc->handle_texs_gen = true; // We manage the texs
+ tc->pf_update = tc_drm_update;
+
+ return VLC_SUCCESS;
+
+fail:
+ CloseGLConverter(obj);
+ return rv;
+}
+
+vlc_module_begin ()
+ set_description("DRM OpenGL surface converter")
+ set_shortname (N_("DRMGLConverter"))
+ set_capability("glconv", 900)
+ set_callbacks(OpenGLConverter, CloseGLConverter)
+ set_category(CAT_VIDEO)
+ set_subcategory(SUBCAT_VIDEO_VOUT)
+ add_shortcut("drm_gl_converter")
+vlc_module_end ()
+
diff --git a/modules/hw/mmal/Makefile.am b/modules/hw/mmal/Makefile.am
index 1dfb3e35ce..f85effd0b5 100644
--- a/modules/hw/mmal/Makefile.am
+++ b/modules/hw/mmal/Makefile.am
@@ -1,23 +1,60 @@
include $(top_srcdir)/modules/common.am
mmaldir = $(pluginsdir)/mmal
-AM_CFLAGS += $(CFLAGS_mmal)
-AM_LDFLAGS += -rpath '$(mmaldir)' $(LDFLAGS_mmal)
+AM_CFLAGS += -pthread $(CFLAGS_mmal)
+AM_LDFLAGS += -pthread -rpath '$(mmaldir)' $(LDFLAGS_mmal)
-libmmal_vout_plugin_la_SOURCES = vout.c mmal_picture.c mmal_picture.h
+mmal_LTLIBRARIES =
+
+if HAVE_MMAL
+libmmal_vout_plugin_la_SOURCES = vout.c mmal_cma.c mmal_picture.c subpic.c\
+ mmal_cma.h mmal_cma_int.h mmal_picture.h subpic.h transform_ops.h\
+ mmal_piccpy_neon.S
libmmal_vout_plugin_la_CFLAGS = $(AM_CFLAGS)
-libmmal_vout_plugin_la_LDFLAGS = $(AM_LDFLAGS) -lm
+libmmal_vout_plugin_la_LDFLAGS = $(AM_LDFLAGS) -lm -lX11 -lXrandr
libmmal_vout_plugin_la_LIBADD = $(LIBS_mmal)
-mmal_LTLIBRARIES = libmmal_vout_plugin.la
+mmal_LTLIBRARIES += libmmal_vout_plugin.la
-libmmal_codec_plugin_la_SOURCES = codec.c
+libmmal_codec_plugin_la_SOURCES = codec.c mmal_cma.c mmal_picture.c subpic.c\
+ mmal_cma.h mmal_cma_int.h mmal_picture.h subpic.h transform_ops.h\
+ blend_rgba_neon.S mmal_piccpy_neon.S
libmmal_codec_plugin_la_CFLAGS = $(AM_CFLAGS)
libmmal_codec_plugin_la_LDFLAGS = $(AM_LDFLAGS)
libmmal_codec_plugin_la_LIBADD = $(LIBS_mmal)
mmal_LTLIBRARIES += libmmal_codec_plugin.la
-libmmal_deinterlace_plugin_la_SOURCES = deinterlace.c mmal_picture.c
+libmmal_deinterlace_plugin_la_SOURCES = deinterlace.c mmal_picture.c mmal_cma.c\
+ mmal_cma.h mmal_cma_int.h mmal_picture.h transform_ops.h\
+ mmal_piccpy_neon.S
libmmal_deinterlace_plugin_la_CFLAGS = $(AM_CFLAGS)
libmmal_deinterlace_plugin_la_LDFLAGS = $(AM_LDFLAGS)
libmmal_deinterlace_plugin_la_LIBADD = $(LIBS_mmal)
mmal_LTLIBRARIES += libmmal_deinterlace_plugin.la
+
+libmmal_converter_plugin_la_SOURCES = converter_mmal.c mmal_cma.c mmal_picture.c\
+ mmal_cma.h mmal_cma_int.h mmal_picture.h transform_ops.h\
+ mmal_piccpy_neon.S
+libmmal_converter_plugin_la_CFLAGS = $(AM_CFLAGS)
+libmmal_converter_plugin_la_LDFLAGS = $(AM_LDFLAGS)
+libmmal_converter_plugin_la_LIBADD = $(LIBS_mmal)
+mmal_LTLIBRARIES += libmmal_converter_plugin.la
+endif
+
+libmmal_xsplitter_plugin_la_SOURCES = xsplitter.c
+libmmal_xsplitter_plugin_la_CFLAGS = $(AM_CFLAGS)
+libmmal_xsplitter_plugin_la_LDFLAGS = $(AM_LDFLAGS)
+libmmal_xsplitter_plugin_la_LIBADD = $(LIBS_mmal)
+mmal_LTLIBRARIES += libmmal_xsplitter_plugin.la
+
+
+if HAVE_MMAL_AVCODEC
+libmmal_avcodec_plugin_la_SOURCES = mmal_avcodec.c mmal_cma_drmprime.c mmal_cma.c\
+ mmal_picture.c\
+ mmal_cma.h mmal_cma_int.h mmal_cma_drmprime.h mmal_picture.h transform_ops.h\
+ mmal_piccpy_neon.S
+libmmal_avcodec_plugin_la_CFLAGS = $(AM_CFLAGS)
+libmmal_avcodec_plugin_la_LDFLAGS = $(AM_LDFLAGS)
+libmmal_avcodec_plugin_la_LIBADD = $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(LIBS_mmal)
+mmal_LTLIBRARIES += libmmal_avcodec_plugin.la
+endif
+
diff --git a/modules/hw/mmal/blend_rgba_neon.S b/modules/hw/mmal/blend_rgba_neon.S
new file mode 100644
index 0000000000..d17bf6b9aa
--- /dev/null
+++ b/modules/hw/mmal/blend_rgba_neon.S
@@ -0,0 +1,189 @@
+#include "../../arm_neon/asm.S"
+ .align 16
+ .arch armv7-a
+ .syntax unified
+#if HAVE_AS_FPU_DIRECTIVE
+ .fpu neon-vfpv4
+#endif
+
+@ blend_rgbx_rgba_neon
+
+@ Implements /255 as ((x * 257) + 0x8000) >> 16
+@ This generates something in the range [(x+126)/255, (x+127)/255] which is good enough
+
+@ There is advantage to aligning src and/or dest - dest gives a bit more due to being used twice
+
+
+
+@ [r0] RGBx dest loaded into d20-d23
+@ [r1] RGBA src merge loaded into d16-d19
+@ r2 plane alpha
+@ r3 count (pixels)
+
+.macro blend_main sR, sG, sB, sA, dR, dG, dB, dA
+
+ push { r4, lr }
+
+ vdup.u8 d7, r2
+
+ subs r3, #8
+ vmov.u8 d6, #0xff
+
+ blt 2f
+
+ @ If < 16 bytes to move then don't bother trying to align
+ @ (a) This means the the align doesn't need to worry about r3 underflow
+ @ (b) The overhead would be greater than any gain
+ cmp r3, #8
+ mov r4, r3
+ ble 1f
+
+ @ Align r1 on a 32 byte boundary
+ neg r3, r0
+ ubfx r3, r3, #2, #3
+
+ cmp r3, #0
+ blne 10f
+
+ sub r3, r4, r3
+
+1:
+ vld4.8 {d16, d17, d18, d19}, [r1]
+
+1:
+ vmull.u8 q15, \sA, d7
+
+ vld4.8 {d20, d21, d22, d23}, [r0]
+
+ vsra.u16 q15, q15, #8
+ subs r3, #8
+ vrshrn.u16 d31, q15, #8
+ vsub.u8 d30, d6, d31
+
+ vmull.u8 q12, \sR, d31
+ vmull.u8 q13, \sG, d31
+ vmull.u8 q14, \sB, d31
+ addge r1, #32
+
+ vmlal.u8 q12, \dR, d30
+ vmlal.u8 q13, \dG, d30
+ vmlal.u8 q14, \dB, d30
+ vld4.8 {d16, d17, d18, d19}, [r1]
+
+ vsra.u16 q12, q12, #8 @ * 257/256
+ vsra.u16 q13, q13, #8
+ vsra.u16 q14, q14, #8
+
+ vrshrn.u16 \dR, q12, #8
+ vrshrn.u16 \dG, q13, #8
+ vrshrn.u16 \dB, q14, #8
+ vmov.u8 \dA, #0xff
+
+ vst4.8 {d20, d21, d22, d23}, [r0]!
+ bge 1b
+ add r1, #32
+
+2:
+ cmp r3, #-8
+ blgt 10f
+
+ pop { r4, pc }
+
+
+// Partial version
+// Align @ start & deal with tail
+10:
+ lsls r2, r3, #30 @ b2 -> C, b1 -> N
+ mov r2, r0
+ bcc 1f
+ vld4.8 {d16[0], d17[0], d18[0], d19[0]}, [r1]!
+ vld4.8 {d20[0], d21[0], d22[0], d23[0]}, [r2]!
+ vld4.8 {d16[1], d17[1], d18[1], d19[1]}, [r1]!
+ vld4.8 {d20[1], d21[1], d22[1], d23[1]}, [r2]!
+ vld4.8 {d16[2], d17[2], d18[2], d19[2]}, [r1]!
+ vld4.8 {d20[2], d21[2], d22[2], d23[2]}, [r2]!
+ vld4.8 {d16[3], d17[3], d18[3], d19[3]}, [r1]!
+ vld4.8 {d20[3], d21[3], d22[3], d23[3]}, [r2]!
+1:
+ bpl 1f
+ vld4.8 {d16[4], d17[4], d18[4], d19[4]}, [r1]!
+ vld4.8 {d20[4], d21[4], d22[4], d23[4]}, [r2]!
+ vld4.8 {d16[5], d17[5], d18[5], d19[5]}, [r1]!
+ vld4.8 {d20[5], d21[5], d22[5], d23[5]}, [r2]!
+1:
+ tst r3, #1
+ beq 1f
+ vld4.8 {d16[6], d17[6], d18[6], d19[6]}, [r1]!
+ vld4.8 {d20[6], d21[6], d22[6], d23[6]}, [r2]!
+1:
+ @ Set conditions for later
+ lsls r2, r3, #30 @ b2 -> C, b1 -> N
+
+ vmull.u8 q15, \sA, d7
+ vsra.u16 q15, q15, #8
+ vrshrn.u16 d31, q15, #8
+ vsub.u8 d30, d6, d31
+
+ vmull.u8 q12, \sR, d31
+ vmull.u8 q13, \sG, d31
+ vmull.u8 q14, \sB, d31
+
+ vmlal.u8 q12, \dR, d30
+ vmlal.u8 q13, \dG, d30
+ vmlal.u8 q14, \dB, d30
+
+ vsra.u16 q12, q12, #8
+ vsra.u16 q13, q13, #8
+ vsra.u16 q14, q14, #8
+
+ vrshrn.u16 \dR, q12, #8
+ vrshrn.u16 \dG, q13, #8
+ vrshrn.u16 \dB, q14, #8
+ vmov.u8 \dA, #0xff
+
+ bcc 1f
+ vst4.8 {d20[0], d21[0], d22[0], d23[0]}, [r0]!
+ vst4.8 {d20[1], d21[1], d22[1], d23[1]}, [r0]!
+ vst4.8 {d20[2], d21[2], d22[2], d23[2]}, [r0]!
+ vst4.8 {d20[3], d21[3], d22[3], d23[3]}, [r0]!
+1:
+ bpl 1f
+ vst4.8 {d20[4], d21[4], d22[4], d23[4]}, [r0]!
+ vst4.8 {d20[5], d21[5], d22[5], d23[5]}, [r0]!
+1:
+ tst r3, #1
+ bxeq lr
+ vst4.8 {d20[6], d21[6], d22[6], d23[6]}, [r0]!
+
+ bx lr
+
+.endm
+
+
+@ [r0] RGBx dest (Byte order: R, G, B, x)
+@ [r1] RGBA src merge (Byte order: R, G, B, A)
+@ r2 plane alpha
+@ r3 count (pixels)
+
+@ Whilst specified as RGBx+RGBA the only important part is the position of
+@ alpha, the other components are all treated the same
+
+@ [r0] RGBx dest (Byte order: R, G, B, x)
+@ [r1] RGBA src merge (Byte order: R, G, B, A) - same as above
+@ r2 plane alpha
+@ r3 count (pixels)
+ .align 16
+function blend_rgbx_rgba_neon
+ blend_main d16, d17, d18, d19, d20, d21, d22, d23
+
+
+@ [r0] RGBx dest (Byte order: R, G, B, x)
+@ [r1] RGBA src merge (Byte order: B, G, R, A) - B / R swapped
+@ r2 plane alpha
+@ r3 count (pixels)
+ .align 16
+function blend_bgrx_rgba_neon
+ blend_main d18, d17, d16, d19, d20, d21, d22, d23
+
+
+
diff --git a/modules/hw/mmal/blend_rgba_neon.h b/modules/hw/mmal/blend_rgba_neon.h
new file mode 100644
index 0000000000..ac0810855e
--- /dev/null
+++ b/modules/hw/mmal/blend_rgba_neon.h
@@ -0,0 +1,17 @@
+#ifndef HW_MMAL_BLEND_RGBA_NEON_H
+#define HW_MMAL_BLEND_RGBA_NEON_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void blend_neon_fn(void * dest, const void * src, int alpha, unsigned int n);
+extern blend_neon_fn blend_rgbx_rgba_neon;
+extern blend_neon_fn blend_bgrx_rgba_neon;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/modules/hw/mmal/blend_test.c b/modules/hw/mmal/blend_test.c
new file mode 100644
index 0000000000..1612a0e07c
--- /dev/null
+++ b/modules/hw/mmal/blend_test.c
@@ -0,0 +1,180 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <memory.h>
+
+#include "blend_rgba_neon.h"
+
+#define RPI_PROFILE 1
+#define RPI_PROC_ALLOC 1
+#include "rpi_prof.h"
+
+static inline unsigned div255(unsigned v)
+{
+ // This models what we we do in the asm for / 255
+ // It generates something in the range [(i+126)/255, (i+127)/255] which is good enough
+ return ((v * 257) + 0x8000) >> 16;
+}
+
+static inline unsigned int a_merge(unsigned int dst, unsigned src, unsigned f)
+{
+ return div255((255 - f) * (dst) + src * f);
+}
+
+
+static void merge_line(void * dest, const void * src, int alpha, unsigned int n)
+{
+ unsigned int i;
+ const uint8_t * s_data = src;
+ uint8_t * d_data = dest;
+
+ for (i = 0; i != n; ++i) {
+ const uint32_t s_pel = ((const uint32_t *)s_data)[i];
+ const uint32_t d_pel = ((const uint32_t *)d_data)[i];
+ const unsigned int a = div255(alpha * (s_pel >> 24));
+ ((uint32_t *)d_data)[i] = 0xff000000 |
+ (a_merge((d_pel >> 16) & 0xff, (s_pel >> 16) & 0xff, a) << 16) |
+ (a_merge((d_pel >> 8) & 0xff, (s_pel >> 8) & 0xff, a) << 8 ) |
+ (a_merge((d_pel >> 0) & 0xff, (s_pel >> 0) & 0xff, a) << 0 );
+ }
+}
+
+
+// Merge RGBA with BGRA
+static void merge_line2(void * dest, const void * src, int alpha, unsigned int n)
+{
+ unsigned int i;
+ const uint8_t * s_data = src;
+ uint8_t * d_data = dest;
+
+ for (i = 0; i != n; ++i) {
+ const uint32_t s_pel = ((const uint32_t *)s_data)[i];
+ const uint32_t d_pel = ((const uint32_t *)d_data)[i];
+ const unsigned int a = div255(alpha * (s_pel >> 24));
+ ((uint32_t *)d_data)[i] = 0xff000000 |
+ (a_merge((d_pel >> 0) & 0xff, (s_pel >> 16) & 0xff, a) << 0 ) |
+ (a_merge((d_pel >> 8) & 0xff, (s_pel >> 8) & 0xff, a) << 8 ) |
+ (a_merge((d_pel >> 16) & 0xff, (s_pel >> 0) & 0xff, a) << 16);
+ }
+}
+
+#define BUF_SIZE 256
+#define BUF_SLACK 16
+#define BUF_ALIGN 64
+#define BUF_ALLOC (BUF_SIZE + 2*BUF_SLACK + BUF_ALIGN)
+
+static void test_line(const uint32_t * const dx, const unsigned int d_off,
+ const uint32_t * const sx, const unsigned int s_off,
+ const unsigned int alpha, const unsigned int len, const int prof_no)
+{
+ uint32_t d0_buf[BUF_ALLOC];
+ uint32_t d1_buf[BUF_ALLOC];
+ const uint32_t * const s0 = sx + s_off;
+
+ uint32_t * const d0 = (uint32_t *)(((uintptr_t)d0_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off;
+ uint32_t * const d1 = (uint32_t *)(((uintptr_t)d1_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off;
+ unsigned int i;
+
+ memcpy(d0, dx, (BUF_SIZE + BUF_SLACK*2)*4);
+ memcpy(d1, dx, (BUF_SIZE + BUF_SLACK*2)*4);
+
+ merge_line(d0 + BUF_SLACK, s0 + BUF_SLACK, alpha, len);
+
+ PROFILE_START();
+ blend_rgbx_rgba_neon(d1 + BUF_SLACK, s0 + BUF_SLACK, alpha, len);
+ PROFILE_ACC_N(prof_no);
+
+ for (i = 0; i != BUF_SIZE + BUF_SLACK*2; ++i) {
+ if (d0[i] != d1[i]) {
+ printf("%3d: %08x + %08x * %02x: %08x / %08x: len=%d\n", (int)(i - BUF_SLACK), dx[i], s0[i], alpha, d0[i], d1[i], len);
+ }
+ }
+}
+
+static void test_line2(const uint32_t * const dx, const unsigned int d_off,
+ const uint32_t * const sx, const unsigned int s_off,
+ const unsigned int alpha, const unsigned int len, const int prof_no)
+{
+ uint32_t d0_buf[BUF_ALLOC];
+ uint32_t d1_buf[BUF_ALLOC];
+ const uint32_t * const s0 = sx + s_off;
+
+ uint32_t * const d0 = (uint32_t *)(((uintptr_t)d0_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off;
+ uint32_t * const d1 = (uint32_t *)(((uintptr_t)d1_buf + (BUF_ALIGN - 1)) & ~(BUF_ALIGN - 1)) + d_off;
+ unsigned int i;
+
+ memcpy(d0, dx, (BUF_SIZE + BUF_SLACK*2)*4);
+ memcpy(d1, dx, (BUF_SIZE + BUF_SLACK*2)*4);
+
+ merge_line2(d0 + BUF_SLACK, s0 + BUF_SLACK, alpha, len);
+
+ PROFILE_START();
+ blend_bgrx_rgba_neon(d1 + BUF_SLACK, s0 + BUF_SLACK, alpha, len);
+ PROFILE_ACC_N(prof_no);
+
+ for (i = 0; i != BUF_SIZE + BUF_SLACK*2; ++i) {
+ if (d0[i] != d1[i]) {
+ printf("%3d: %08x + %08x * %02x: %08x / %08x: len=%d\n", (int)(i - BUF_SLACK), dx[i], s0[i], alpha, d0[i], d1[i], len);
+ }
+ }
+}
+
+
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, j;
+ uint32_t d0_buf[BUF_ALLOC];
+ uint32_t s0_buf[BUF_ALLOC];
+
+ uint32_t * const d0 = (uint32_t *)(((uintptr_t)d0_buf + 63) & ~63) + 0;
+ uint32_t * const s0 = (uint32_t *)(((uintptr_t)s0_buf + 63) & ~63) + 0;
+
+ PROFILE_INIT();
+
+ for (i = 0; i != 255*255; ++i) {
+ unsigned int a = div255(i);
+ unsigned int b = (i + 127)/255;
+ unsigned int c = (i + 126)/255;
+ if (a != b && a != c)
+ printf("%d/255: %d != %d/%d\n", i, a, b, c);
+ }
+
+ for (i = 0; i != BUF_ALLOC; ++i) {
+ d0_buf[i] = 0xff00 | i;
+ s0_buf[i] = (i << 24) | 0x40ffc0;
+ }
+
+ for (i = 0; i != 256; ++i) {
+ test_line(d0, 0, s0, 0, i, 256, -1);
+ }
+ for (i = 0; i != 256; ++i) {
+ test_line(d0, 0, s0, 0, 128, i, -1);
+ }
+
+ for (j = 0; j != 16; ++j) {
+ for (i = 0; i != 256; ++i) {
+ test_line(d0, j & 3, s0, j >> 2, i, 256, j);
+ }
+ PROFILE_PRINTF_N(j);
+ PROFILE_CLEAR_N(j);
+ }
+ printf("Done 1\n");
+
+ for (i = 0; i != 256; ++i) {
+ test_line2(d0, 0, s0, 0, i, 256, -1);
+ }
+ for (i = 0; i != 256; ++i) {
+ test_line2(d0, 0, s0, 0, 128, i, -1);
+ }
+
+ for (j = 0; j != 16; ++j) {
+ for (i = 0; i != 256; ++i) {
+ test_line2(d0, j & 3, s0, j >> 2, i, 256, j);
+ }
+ PROFILE_PRINTF_N(j);
+ }
+ printf("Done 2\n");
+
+ return 0;
+}
+
diff --git a/modules/hw/mmal/codec.c b/modules/hw/mmal/codec.c
index 99ff21ca70..a6ee2919d3 100644
--- a/modules/hw/mmal/codec.c
+++ b/modules/hw/mmal/codec.c
@@ -26,267 +26,448 @@
#include "config.h"
#endif
+#include <stdatomic.h>
+
#include <vlc_common.h>
-#include <vlc_atomic.h>
+#include <vlc_cpu.h>
#include <vlc_plugin.h>
#include <vlc_codec.h>
+#include <vlc_filter.h>
#include <vlc_threads.h>
-#include <bcm_host.h>
#include <interface/mmal/mmal.h>
#include <interface/mmal/util/mmal_util.h>
#include <interface/mmal/util/mmal_default_components.h>
+#include <interface/vcsm/user-vcsm.h>
+
+#include "mmal_cma.h"
#include "mmal_picture.h"
+#include "subpic.h"
+#include "blend_rgba_neon.h"
+
+#define TRACE_ALL 0
+
+#define OPT_TO_FROM_ZC 0
+
/*
* This seems to be a bit high, but reducing it causes instabilities
*/
#define NUM_EXTRA_BUFFERS 5
+//#define NUM_EXTRA_BUFFERS 10
#define NUM_DECODER_BUFFER_HEADERS 30
-#define MIN_NUM_BUFFERS_IN_TRANSIT 2
+#define CONVERTER_BUFFERS 4 // Buffers on the output of the converter
+
+#define MMAL_SLICE_HEIGHT 16
+#define MMAL_ALIGN_W 32
+#define MMAL_ALIGN_H 16
#define MMAL_OPAQUE_NAME "mmal-opaque"
#define MMAL_OPAQUE_TEXT N_("Decode frames directly into RPI VideoCore instead of host memory.")
#define MMAL_OPAQUE_LONGTEXT N_("Decode frames directly into RPI VideoCore instead of host memory. This option must only be used with the MMAL video output plugin.")
-static int OpenDecoder(decoder_t *dec);
-static void CloseDecoder(decoder_t *dec);
+#define MMAL_RESIZE_NAME "mmal-resize"
+#define MMAL_RESIZE_TEXT N_("Use mmal resizer rather than hvs.")
+#define MMAL_RESIZE_LONGTEXT N_("Use mmal resizer rather than isp. This uses less gpu memory than the ISP but is slower.")
-vlc_module_begin()
- set_shortname(N_("MMAL decoder"))
- set_description(N_("MMAL-based decoder plugin for Raspberry Pi"))
- set_capability("video decoder", 90)
- add_shortcut("mmal_decoder")
- add_bool(MMAL_OPAQUE_NAME, true, MMAL_OPAQUE_TEXT, MMAL_OPAQUE_LONGTEXT, false)
- set_callbacks(OpenDecoder, CloseDecoder)
-vlc_module_end()
+#define MMAL_ISP_NAME "mmal-isp"
+#define MMAL_ISP_TEXT N_("Use mmal isp rather than hvs.")
+#define MMAL_ISP_LONGTEXT N_("Use mmal isp rather than hvs. This may be faster but has no blend.")
-struct decoder_sys_t {
- bool opaque;
+#define MMAL_DECODE_ENABLE_NAME "mmal-decode-enable"
+#define MMAL_DECODE_ENABLE_TEXT N_("Enable mmal decode even if normally disabled")
+#define MMAL_DECODE_ENABLE_LONGTEXT N_("Enable mmal decode even if normally disabled. MMAL decode is normally disabled on Pi4 or later.")
+
+typedef struct decoder_sys_t
+{
MMAL_COMPONENT_T *component;
MMAL_PORT_T *input;
MMAL_POOL_T *input_pool;
MMAL_PORT_T *output;
- MMAL_POOL_T *output_pool; /* only used for non-opaque mode */
+ hw_mmal_port_pool_ref_t *ppr;
MMAL_ES_FORMAT_T *output_format;
- vlc_sem_t sem;
+ MMAL_STATUS_T err_stream;
bool b_top_field_first;
bool b_progressive;
+ bool b_flushed;
+
+ vcsm_init_type_t vcsm_init_type;
+
+ // Lock to avoid pic update & allocate happenening simultainiously
+ // * We should be able to arrange life s.t. this isn't needed
+ // but while we are confused apply belt & braces
+ vlc_mutex_t pic_lock;
+
/* statistics */
- int output_in_transit;
- int input_in_transit;
atomic_bool started;
-};
+} decoder_sys_t;
-/* Utilities */
-static int change_output_format(decoder_t *dec);
-static int send_output_buffer(decoder_t *dec);
-static void fill_output_port(decoder_t *dec);
-/* VLC decoder callback */
-static int decode(decoder_t *dec, block_t *block);
-static void flush_decoder(decoder_t *dec);
+typedef struct supported_mmal_enc_s {
+ struct {
+ MMAL_PARAMETER_HEADER_T header;
+ MMAL_FOURCC_T encodings[64];
+ } supported;
+ int n;
+} supported_mmal_enc_t;
-/* MMAL callbacks */
-static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
-static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
-static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
+#define SUPPORTED_MMAL_ENC_INIT \
+{ \
+ {{MMAL_PARAMETER_SUPPORTED_ENCODINGS, sizeof(((supported_mmal_enc_t *)0)->supported)}, {0}}, \
+ -1 \
+}
-static int OpenDecoder(decoder_t *dec)
-{
- int ret = VLC_SUCCESS;
- decoder_sys_t *sys;
- MMAL_PARAMETER_UINT32_T extra_buffers;
- MMAL_STATUS_T status;
+static supported_mmal_enc_t supported_decode_in_enc = SUPPORTED_MMAL_ENC_INIT;
- if (dec->fmt_in.i_codec != VLC_CODEC_MPGV &&
- dec->fmt_in.i_codec != VLC_CODEC_H264)
- return VLC_EGENERIC;
+static bool is_enc_supported(supported_mmal_enc_t * const support, const MMAL_FOURCC_T fcc)
+{
+ int i;
- sys = calloc(1, sizeof(decoder_sys_t));
- if (!sys) {
- ret = VLC_ENOMEM;
- goto out;
+ if (fcc == 0)
+ return false;
+ if (support->n == -1)
+ return true; // Unknown - say OK
+ for (i = 0; i < support->n; ++i) {
+ if (support->supported.encodings[i] == fcc)
+ return true;
}
- dec->p_sys = sys;
+ return false;
+}
- sys->opaque = var_InheritBool(dec, MMAL_OPAQUE_NAME);
- bcm_host_init();
+static bool set_and_test_enc_supported(supported_mmal_enc_t * const support, MMAL_PORT_T * port, const MMAL_FOURCC_T fcc)
+{
+ if (support->n >= 0)
+ /* already done */;
+ else if (mmal_port_parameter_get(port, (MMAL_PARAMETER_HEADER_T *)&support->supported) != MMAL_SUCCESS)
+ support->n = 0;
+ else
+ support->n = (support->supported.header.size - sizeof(support->supported.header)) /
+ sizeof(support->supported.encodings[0]);
- status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, &sys->component);
- if (status != MMAL_SUCCESS) {
- msg_Err(dec, "Failed to create MMAL component %s (status=%"PRIx32" %s)",
- MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
+ return is_enc_supported(support, fcc);
+}
+
+static MMAL_FOURCC_T vlc_to_mmal_es_fourcc(const unsigned int fcc)
+{
+ switch (fcc){
+ case VLC_CODEC_MJPG:
+ return MMAL_ENCODING_MJPEG;
+ case VLC_CODEC_MP1V:
+ return MMAL_ENCODING_MP1V;
+ case VLC_CODEC_MPGV:
+ case VLC_CODEC_MP2V:
+ return MMAL_ENCODING_MP2V;
+ case VLC_CODEC_H263:
+ return MMAL_ENCODING_H263;
+ case VLC_CODEC_MP4V:
+ return MMAL_ENCODING_MP4V;
+ case VLC_CODEC_H264:
+ return MMAL_ENCODING_H264;
+ case VLC_CODEC_VP6:
+ return MMAL_ENCODING_VP6;
+ case VLC_CODEC_VP8:
+ return MMAL_ENCODING_VP8;
+ case VLC_CODEC_WMV1:
+ return MMAL_ENCODING_WMV1;
+ case VLC_CODEC_WMV2:
+ return MMAL_ENCODING_WMV2;
+ case VLC_CODEC_WMV3:
+ return MMAL_ENCODING_WMV3;
+ case VLC_CODEC_VC1:
+ return MMAL_ENCODING_WVC1;
+ case VLC_CODEC_THEORA:
+ return MMAL_ENCODING_THEORA;
+ default:
+ break;
}
+ return 0;
+}
- sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)dec;
- status = mmal_port_enable(sys->component->control, control_port_cb);
- if (status != MMAL_SUCCESS) {
- msg_Err(dec, "Failed to enable control port %s (status=%"PRIx32" %s)",
- sys->component->control->name, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
+static MMAL_FOURCC_T pic_to_slice_mmal_fourcc(const MMAL_FOURCC_T fcc)
+{
+ switch (fcc){
+ case MMAL_ENCODING_I420:
+ return MMAL_ENCODING_I420_SLICE;
+ case MMAL_ENCODING_I422:
+ return MMAL_ENCODING_I422_SLICE;
+ case MMAL_ENCODING_ARGB:
+ return MMAL_ENCODING_ARGB_SLICE;
+ case MMAL_ENCODING_RGBA:
+ return MMAL_ENCODING_RGBA_SLICE;
+ case MMAL_ENCODING_ABGR:
+ return MMAL_ENCODING_ABGR_SLICE;
+ case MMAL_ENCODING_BGRA:
+ return MMAL_ENCODING_BGRA_SLICE;
+ case MMAL_ENCODING_RGB16:
+ return MMAL_ENCODING_RGB16_SLICE;
+ case MMAL_ENCODING_RGB24:
+ return MMAL_ENCODING_RGB24_SLICE;
+ case MMAL_ENCODING_RGB32:
+ return MMAL_ENCODING_RGB32_SLICE;
+ case MMAL_ENCODING_BGR16:
+ return MMAL_ENCODING_BGR16_SLICE;
+ case MMAL_ENCODING_BGR24:
+ return MMAL_ENCODING_BGR24_SLICE;
+ case MMAL_ENCODING_BGR32:
+ return MMAL_ENCODING_BGR32_SLICE;
+ default:
+ break;
}
+ return 0;
+}
- sys->input = sys->component->input[0];
- sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)dec;
- if (dec->fmt_in.i_codec == VLC_CODEC_MPGV)
- sys->input->format->encoding = MMAL_ENCODING_MP2V;
- else
- sys->input->format->encoding = MMAL_ENCODING_H264;
-
- if (dec->fmt_in.i_codec == VLC_CODEC_H264) {
- if (dec->fmt_in.i_extra > 0) {
- status = mmal_format_extradata_alloc(sys->input->format,
- dec->fmt_in.i_extra);
- if (status == MMAL_SUCCESS) {
- memcpy(sys->input->format->extradata, dec->fmt_in.p_extra,
- dec->fmt_in.i_extra);
- sys->input->format->extradata_size = dec->fmt_in.i_extra;
- } else {
- msg_Err(dec, "Failed to allocate extra format data on input port %s (status=%"PRIx32" %s)",
- sys->input->name, status, mmal_status_to_string(status));
- }
+#define DEBUG_SQUARES 0
+#if DEBUG_SQUARES
+static void draw_square(void * pic_buf, size_t pic_stride, unsigned int x, unsigned int y, unsigned int w, unsigned int h, uint32_t val)
+{
+ uint32_t * p = (uint32_t *)pic_buf + y * pic_stride + x;
+ unsigned int i;
+ for (i = 0; i != h; ++i) {
+ unsigned int j;
+ for (j = 0; j != w; ++j) {
+ p[j] = val;
}
+ p += pic_stride;
}
+}
+#endif
- status = mmal_port_format_commit(sys->input);
- if (status != MMAL_SUCCESS) {
- msg_Err(dec, "Failed to commit format for input port %s (status=%"PRIx32" %s)",
- sys->input->name, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
+#if 0
+static inline void draw_line(void * pic_buf, size_t pic_stride, unsigned int x, unsigned int y, unsigned int len, int inc)
+{
+ uint32_t * p = (uint32_t *)pic_buf + y * pic_stride + x;
+ while (len-- != 0) {
+ *p = ~0U;
+ p += inc;
}
- sys->input->buffer_size = sys->input->buffer_size_recommended;
- sys->input->buffer_num = sys->input->buffer_num_recommended;
+}
- status = mmal_port_enable(sys->input, input_port_cb);
- if (status != MMAL_SUCCESS) {
- msg_Err(dec, "Failed to enable input port %s (status=%"PRIx32" %s)",
- sys->input->name, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
- }
- sys->output = sys->component->output[0];
- sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)dec;
+static void draw_corners(void * pic_buf, size_t pic_stride, unsigned int x, unsigned int y, unsigned int w, unsigned int h)
+{
+ const unsigned int len = 20;
+ draw_line(pic_buf, pic_stride, x, y, len, 1);
+ draw_line(pic_buf, pic_stride, x, y, len, pic_stride);
+ draw_line(pic_buf, pic_stride, x + w - 1, y, len, -1);
+ draw_line(pic_buf, pic_stride, x + w - 1, y, len, pic_stride);
+ draw_line(pic_buf, pic_stride, x + w - 1, y + h - 1, len, -1);
+ draw_line(pic_buf, pic_stride, x + w - 1, y + h - 1, len, -(int)pic_stride);
+ draw_line(pic_buf, pic_stride, x, y + h - 1, len, 1);
+ draw_line(pic_buf, pic_stride, x, y + h - 1, len, -(int)pic_stride);
+}
+#endif
- if (sys->opaque) {
- extra_buffers.hdr.id = MMAL_PARAMETER_EXTRA_BUFFERS;
- extra_buffers.hdr.size = sizeof(MMAL_PARAMETER_UINT32_T);
- extra_buffers.value = NUM_EXTRA_BUFFERS;
- status = mmal_port_parameter_set(sys->output, &extra_buffers.hdr);
- if (status != MMAL_SUCCESS) {
- msg_Err(dec, "Failed to set MMAL_PARAMETER_EXTRA_BUFFERS on output port (status=%"PRIx32" %s)",
- status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
- }
+static MMAL_RATIONAL_T
+rationalize_sar(unsigned int num, unsigned int den)
+{
+ static const unsigned int primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 0};
+ const unsigned int * p = primes;
- msg_Dbg(dec, "Activate zero-copy for output port");
- MMAL_PARAMETER_BOOLEAN_T zero_copy = {
- { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) },
- 1
- };
+ // If either num or den is 0 then return a well formed "unknown"
+ if (num == 0 || den == 0) {
+ return (MMAL_RATIONAL_T){.num = 0, .den = 0};
+ }
- status = mmal_port_parameter_set(sys->output, &zero_copy.hdr);
- if (status != MMAL_SUCCESS) {
- msg_Err(dec, "Failed to set zero copy on port %s (status=%"PRIx32" %s)",
- sys->output->name, status, mmal_status_to_string(status));
- goto out;
+ while (*p != 0 && num >= *p && den >= *p) {
+ if (num % *p != 0 || den % *p != 0)
+ ++p;
+ else {
+ num /= *p;
+ den /= *p;
}
}
+ return (MMAL_RATIONAL_T){.num = num, .den = den};
+}
- status = mmal_port_enable(sys->output, output_port_cb);
- if (status != MMAL_SUCCESS) {
- msg_Err(dec, "Failed to enable output port %s (status=%"PRIx32" %s)",
- sys->output->name, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
- }
+// Buffer either attached to pic or released
+static picture_t * alloc_opaque_pic(decoder_t * const dec, MMAL_BUFFER_HEADER_T * const buf)
+{
+ decoder_sys_t *const dec_sys = dec->p_sys;
- status = mmal_component_enable(sys->component);
- if (status != MMAL_SUCCESS) {
- msg_Err(dec, "Failed to enable component %s (status=%"PRIx32" %s)",
- sys->component->name, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
+ vlc_mutex_lock(&dec_sys->pic_lock);
+ picture_t * const pic = decoder_NewPicture(dec);
+ vlc_mutex_unlock(&dec_sys->pic_lock);
+
+ if (pic == NULL)
+ goto fail1;
+
+ if (buf->length == 0) {
+ msg_Err(dec, "%s: Empty buffer", __func__);
+ goto fail2;
}
- sys->input_pool = mmal_pool_create(sys->input->buffer_num, 0);
+ if ((pic->context = hw_mmal_gen_context(buf, dec_sys->ppr)) == NULL)
+ goto fail2;
- if (sys->opaque) {
- dec->fmt_out.i_codec = VLC_CODEC_MMAL_OPAQUE;
- dec->fmt_out.video.i_chroma = VLC_CODEC_MMAL_OPAQUE;
- } else {
- dec->fmt_out.i_codec = VLC_CODEC_I420;
- dec->fmt_out.video.i_chroma = VLC_CODEC_I420;
+ buf_to_pic_copy_props(pic, buf);
+
+#if TRACE_ALL
+ msg_Dbg(dec, "pic: prog=%d, tff=%d, date=%lld", pic->b_progressive, pic->b_top_field_first, (long long)pic->date);
+#endif
+
+ return pic;
+
+fail2:
+ picture_Release(pic);
+fail1:
+ // Recycle rather than release to avoid buffer starvation if NewPic fails
+ hw_mmal_port_pool_ref_recycle(dec_sys->ppr, buf);
+ return NULL;
+}
+
+static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+{
+ decoder_t *dec = (decoder_t *)port->userdata;
+ MMAL_STATUS_T status;
+
+#if TRACE_ALL
+ msg_Dbg(dec, "<<< %s: cmd=%d, data=%p", __func__, buffer->cmd, buffer->data);
+#endif
+
+ if (buffer->cmd == MMAL_EVENT_ERROR) {
+ status = *(uint32_t *)buffer->data;
+ dec->p_sys->err_stream = status;
+ msg_Err(dec, "MMAL error %"PRIx32" \"%s\"", status,
+ mmal_status_to_string(status));
}
- dec->pf_decode = decode;
- dec->pf_flush = flush_decoder;
+ mmal_buffer_header_release(buffer);
+}
- vlc_sem_init(&sys->sem, 0);
+static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+{
+ block_t * const block = (block_t *)buffer->user_data;
-out:
- if (ret != VLC_SUCCESS)
- CloseDecoder(dec);
+ (void)port; // Unused
- return ret;
+#if TRACE_ALL
+ msg_Dbg((decoder_t *)port->userdata, "<<< %s: cmd=%d, data=%p, len=%d/%d, pts=%lld", __func__,
+ buffer->cmd, buffer->data, buffer->length, buffer->alloc_size, (long long)buffer->pts);
+#endif
+
+ mmal_buffer_header_reset(buffer);
+ mmal_buffer_header_release(buffer);
+
+ if (block != NULL)
+ block_Release(block);
}
-static void CloseDecoder(decoder_t *dec)
+static void decoder_output_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
{
- decoder_sys_t *sys = dec->p_sys;
- MMAL_BUFFER_HEADER_T *buffer;
+ decoder_t * const dec = (decoder_t *)port->userdata;
- if (!sys)
+ if (buffer->cmd == 0 && buffer->length != 0)
+ {
+#if TRACE_ALL
+ msg_Dbg(dec, "<<< %s: cmd=%d, data=%p, len=%d/%d, pts=%lld", __func__,
+ buffer->cmd, buffer->data, buffer->length, buffer->alloc_size, (long long)buffer->pts);
+#endif
+
+ picture_t *pic = alloc_opaque_pic(dec, buffer);
+#if TRACE_ALL
+ msg_Dbg(dec, "flags=%#x, video flags=%#x", buffer->flags, buffer->type->video.flags);
+#endif
+ if (pic == NULL)
+ msg_Err(dec, "Failed to allocate new picture");
+ else
+ decoder_QueueVideo(dec, pic);
+ // Buffer released or attached to pic - do not release again
return;
+ }
- if (sys->component && sys->component->control->is_enabled)
- mmal_port_disable(sys->component->control);
+ if (buffer->cmd == MMAL_EVENT_FORMAT_CHANGED)
+ {
+ decoder_sys_t * const sys = dec->p_sys;
+ MMAL_EVENT_FORMAT_CHANGED_T * const fmt = mmal_event_format_changed_get(buffer);
+ MMAL_ES_FORMAT_T * const format = mmal_format_alloc();
- if (sys->input && sys->input->is_enabled)
- mmal_port_disable(sys->input);
+ if (format == NULL)
+ msg_Err(dec, "Failed to allocate new format");
+ else
+ {
+ mmal_format_full_copy(format, fmt->format);
+ format->encoding = MMAL_ENCODING_OPAQUE;
- if (sys->output && sys->output->is_enabled)
- mmal_port_disable(sys->output);
+ // If no PAR in the stream - see if we've got one from the demux
+ if (format->es->video.par.den <= 0 || format->es->video.par.num <= 0) {
+ unsigned int n = dec->fmt_in.video.i_sar_num;
+ unsigned int d = dec->fmt_in.video.i_sar_den;
+
+ if (n == 0 || d == 0) {
+ // Guesswork required
+ const unsigned int w = format->es->video.width;
+ const unsigned int h = format->es->video.height;
+ if ((w == 704 || w == 720) && (h == 480 || h == 576)) {
+ // Very likely SD 4:3
+ n = w * 3;
+ d = h * 4;
+ }
+ else
+ {
+ // Otherwise guess SAR 1:1
+ n = 1;
+ d = 1;
+ }
+ }
+
+ format->es->video.par = rationalize_sar(n, d);
+ }
- if (sys->component && sys->component->is_enabled)
- mmal_component_disable(sys->component);
+ if (sys->output_format != NULL)
+ mmal_format_free(sys->output_format);
- if (sys->input_pool)
- mmal_pool_destroy(sys->input_pool);
+ sys->output_format = format;
+ }
+ }
+ else if (buffer->cmd != 0) {
+ char buf0[5];
+ msg_Warn(dec, "Unexpected output cb event: %s", str_fourcc(buf0, buffer->cmd));
+ }
- if (sys->output_format)
- mmal_format_free(sys->output_format);
+ // If we get here then we were flushing (cmd == 0 && len == 0) or
+ // that was an EVENT - in either case we want to release the buffer
+ // back to its pool rather than recycle it.
+ mmal_buffer_header_reset(buffer);
+ buffer->user_data = NULL;
+ mmal_buffer_header_release(buffer);
+}
- if (sys->output_pool)
- mmal_pool_destroy(sys->output_pool);
- if (sys->component)
- mmal_component_release(sys->component);
- vlc_sem_destroy(&sys->sem);
- free(sys);
+static void fill_output_port(decoder_t *dec)
+{
+ decoder_sys_t *sys = dec->p_sys;
+
+ if (decoder_UpdateVideoFormat(dec) != 0)
+ {
+ // If we have a new format don't bother stuffing the buffer
+ // We should get a reset RSN
+#if TRACE_ALL
+ msg_Dbg(dec, "%s: Updated", __func__);
+#endif
+
+ return;
+ }
- bcm_host_deinit();
+ hw_mmal_port_pool_ref_fill(sys->ppr);
+ return;
}
static int change_output_format(decoder_t *dec)
{
MMAL_PARAMETER_VIDEO_INTERLACE_TYPE_T interlace_type;
- decoder_sys_t *sys = dec->p_sys;
+ decoder_sys_t * const sys = dec->p_sys;
MMAL_STATUS_T status;
- int pool_size;
int ret = 0;
+#if TRACE_ALL
+ msg_Dbg(dec, "%s: <<<", __func__);
+#endif
+
if (atomic_load(&sys->started)) {
mmal_format_full_copy(sys->output->format, sys->output_format);
status = mmal_port_format_commit(sys->output);
@@ -300,7 +481,9 @@ static int change_output_format(decoder_t *dec)
}
port_reset:
+#if TRACE_ALL
msg_Dbg(dec, "%s: Do full port reset", __func__);
+#endif
status = mmal_port_disable(sys->output);
if (status != MMAL_SUCCESS) {
msg_Err(dec, "Failed to disable output port (status=%"PRIx32" %s)",
@@ -310,6 +493,7 @@ port_reset:
}
mmal_format_full_copy(sys->output->format, sys->output_format);
+
status = mmal_port_format_commit(sys->output);
if (status != MMAL_SUCCESS) {
msg_Err(dec, "Failed to commit output format (status=%"PRIx32" %s)",
@@ -318,18 +502,10 @@ port_reset:
goto out;
}
- if (sys->opaque) {
- sys->output->buffer_num = NUM_DECODER_BUFFER_HEADERS;
- pool_size = NUM_DECODER_BUFFER_HEADERS;
- } else {
- sys->output->buffer_num = __MAX(sys->output->buffer_num_recommended,
- MIN_NUM_BUFFERS_IN_TRANSIT);
- pool_size = sys->output->buffer_num;
- }
-
+ sys->output->buffer_num = NUM_DECODER_BUFFER_HEADERS;
sys->output->buffer_size = sys->output->buffer_size_recommended;
- status = mmal_port_enable(sys->output, output_port_cb);
+ status = mmal_port_enable(sys->output, decoder_output_cb);
if (status != MMAL_SUCCESS) {
msg_Err(dec, "Failed to enable output port (status=%"PRIx32" %s)",
status, mmal_status_to_string(status));
@@ -338,25 +514,14 @@ port_reset:
}
if (!atomic_load(&sys->started)) {
- if (!sys->opaque) {
- sys->output_pool = mmal_port_pool_create(sys->output, pool_size, 0);
- msg_Dbg(dec, "Created output pool with %d pictures", sys->output_pool->headers_num);
- }
-
atomic_store(&sys->started, true);
/* we need one picture from vout for each buffer header on the output
* port */
- dec->i_extra_picture_buffers = pool_size;
-
- /* remove what VLC core reserves as it is part of the pool_size
- * already */
- if (dec->fmt_in.i_codec == VLC_CODEC_H264)
- dec->i_extra_picture_buffers -= 19;
- else
- dec->i_extra_picture_buffers -= 3;
-
+ dec->i_extra_picture_buffers = 10;
+#if TRACE_ALL
msg_Dbg(dec, "Request %d extra pictures", dec->i_extra_picture_buffers);
+#endif
}
apply_fmt:
@@ -366,8 +531,8 @@ apply_fmt:
dec->fmt_out.video.i_y_offset = sys->output->format->es->video.crop.y;
dec->fmt_out.video.i_visible_width = sys->output->format->es->video.crop.width;
dec->fmt_out.video.i_visible_height = sys->output->format->es->video.crop.height;
- dec->fmt_out.video.i_sar_num = sys->output->format->es->video.par.num;
- dec->fmt_out.video.i_sar_den = sys->output->format->es->video.par.den;
+ dec->fmt_out.video.i_sar_num = sys->output_format->es->video.par.num; // SAR can be killed by commit
+ dec->fmt_out.video.i_sar_den = sys->output_format->es->video.par.den;
dec->fmt_out.video.i_frame_rate = sys->output->format->es->video.frame_rate.num;
dec->fmt_out.video.i_frame_rate_base = sys->output->format->es->video.frame_rate.den;
@@ -382,12 +547,19 @@ apply_fmt:
sys->b_progressive = (interlace_type.eMode == MMAL_InterlaceProgressive);
sys->b_top_field_first = sys->b_progressive ? true :
(interlace_type.eMode == MMAL_InterlaceFieldsInterleavedUpperFirst);
+#if TRACE_ALL
msg_Dbg(dec, "Detected %s%s video (%d)",
sys->b_progressive ? "progressive" : "interlaced",
sys->b_progressive ? "" : (sys->b_top_field_first ? " tff" : " bff"),
interlace_type.eMode);
+#endif
}
+ // Tell the rest of the world we have changed format
+ vlc_mutex_lock(&sys->pic_lock);
+ ret = decoder_UpdateVideoFormat(dec);
+ vlc_mutex_unlock(&sys->pic_lock);
+
out:
mmal_format_free(sys->output_format);
sys->output_format = NULL;
@@ -395,144 +567,85 @@ out:
return ret;
}
-static int send_output_buffer(decoder_t *dec)
+static MMAL_STATUS_T
+set_extradata_and_commit(decoder_t * const dec, decoder_sys_t * const sys)
{
- decoder_sys_t *sys = dec->p_sys;
- MMAL_BUFFER_HEADER_T *buffer;
- picture_sys_t *p_sys;
- picture_t *picture = NULL;
MMAL_STATUS_T status;
- unsigned buffer_size = 0;
- int ret = 0;
-
- if (!sys->output->is_enabled)
- return VLC_EGENERIC;
-
- /* If local output pool is allocated, use it - this is only the case for
- * non-opaque modes */
- if (sys->output_pool) {
- buffer = mmal_queue_get(sys->output_pool->queue);
- if (!buffer) {
- msg_Warn(dec, "Failed to get new buffer");
- return VLC_EGENERIC;
- }
- }
-
- if (!decoder_UpdateVideoFormat(dec))
- picture = decoder_NewPicture(dec);
- if (!picture) {
- msg_Warn(dec, "Failed to get new picture");
- ret = -1;
- goto err;
- }
-
- p_sys = picture->p_sys;
- for (int i = 0; i < picture->i_planes; i++)
- buffer_size += picture->p[i].i_lines * picture->p[i].i_pitch;
-
- if (sys->output_pool) {
- mmal_buffer_header_reset(buffer);
- buffer->alloc_size = sys->output->buffer_size;
- if (buffer_size < sys->output->buffer_size) {
- msg_Err(dec, "Retrieved picture with too small data block (%d < %d)",
- buffer_size, sys->output->buffer_size);
- ret = VLC_EGENERIC;
- goto err;
- }
-
- if (!sys->opaque)
- buffer->data = picture->p[0].p_pixels;
- } else {
- buffer = p_sys->buffer;
- if (!buffer) {
- msg_Warn(dec, "Picture has no buffer attached");
- picture_Release(picture);
- return VLC_EGENERIC;
- }
- buffer->data = p_sys->buffer->data;
- }
- buffer->user_data = picture;
- buffer->cmd = 0;
- status = mmal_port_send_buffer(sys->output, buffer);
+ status = mmal_port_format_commit(sys->input);
if (status != MMAL_SUCCESS) {
- msg_Err(dec, "Failed to send buffer to output port (status=%"PRIx32" %s)",
- status, mmal_status_to_string(status));
- ret = -1;
- goto err;
- }
- atomic_fetch_add(&sys->output_in_transit, 1);
-
- return ret;
-
-err:
- if (picture)
- picture_Release(picture);
- if (sys->output_pool && buffer) {
- buffer->data = NULL;
- mmal_buffer_header_release(buffer);
+ msg_Err(dec, "Failed to commit format for input port %s (status=%"PRIx32" %s)",
+ sys->input->name, status, mmal_status_to_string(status));
}
- return ret;
+ return status;
}
-static void fill_output_port(decoder_t *dec)
+static MMAL_STATUS_T decoder_send_extradata(decoder_t * const dec, decoder_sys_t *const sys)
{
- decoder_sys_t *sys = dec->p_sys;
-
- unsigned max_buffers_in_transit = 0;
- int buffers_available = 0;
- int buffers_to_send = 0;
- int i;
-
- if (sys->output_pool) {
- max_buffers_in_transit = __MAX(sys->output_pool->headers_num,
- MIN_NUM_BUFFERS_IN_TRANSIT);
- buffers_available = mmal_queue_length(sys->output_pool->queue);
- } else {
- max_buffers_in_transit = NUM_DECODER_BUFFER_HEADERS;
- buffers_available = NUM_DECODER_BUFFER_HEADERS - atomic_load(&sys->output_in_transit);
+ if (dec->fmt_in.i_codec == VLC_CODEC_H264 &&
+ dec->fmt_in.i_extra > 0)
+ {
+ MMAL_BUFFER_HEADER_T * const buf = mmal_queue_wait(sys->input_pool->queue);
+ MMAL_STATUS_T status;
+
+ mmal_buffer_header_reset(buf);
+ buf->cmd = 0;
+ buf->user_data = NULL;
+ buf->alloc_size = sys->input->buffer_size;
+ buf->length = dec->fmt_in.i_extra;
+ buf->data = dec->fmt_in.p_extra;
+ buf->flags = MMAL_BUFFER_HEADER_FLAG_CONFIG;
+
+ status = mmal_port_send_buffer(sys->input, buf);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(dec, "Failed to send extradata buffer to input port (status=%"PRIx32" %s)",
+ status, mmal_status_to_string(status));
+ return status;
+ }
}
- buffers_to_send = max_buffers_in_transit - atomic_load(&sys->output_in_transit);
- if (buffers_to_send > buffers_available)
- buffers_to_send = buffers_available;
-
-#ifndef NDEBUG
- msg_Dbg(dec, "Send %d buffers to output port (available: %d, "
- "in_transit: %d, buffer_num: %d)",
- buffers_to_send, buffers_available,
- atomic_load(&sys->output_in_transit),
- sys->output->buffer_num);
-#endif
- for (i = 0; i < buffers_to_send; ++i)
- if (send_output_buffer(dec) < 0)
- break;
+ return MMAL_SUCCESS;
}
static void flush_decoder(decoder_t *dec)
{
- decoder_sys_t *sys = dec->p_sys;
- MMAL_BUFFER_HEADER_T *buffer;
- MMAL_STATUS_T status;
+ decoder_sys_t *const sys = dec->p_sys;
- msg_Dbg(dec, "Flushing decoder ports...");
- mmal_port_flush(sys->output);
- mmal_port_flush(sys->input);
+#if TRACE_ALL
+ msg_Dbg(dec, "%s: <<<", __func__);
+#endif
- while (atomic_load(&sys->output_in_transit) ||
- atomic_load(&sys->input_in_transit))
- vlc_sem_wait(&sys->sem);
+ if (!sys->b_flushed) {
+ mmal_port_disable(sys->input);
+ mmal_port_disable(sys->output);
+ // We can leave the input disabled, but we want the output enabled
+ // in order to sink any buffers returning from other modules
+ mmal_port_enable(sys->output, decoder_output_cb);
+ sys->b_flushed = true;
+ }
+#if TRACE_ALL
+ msg_Dbg(dec, "%s: >>>", __func__);
+#endif
}
static int decode(decoder_t *dec, block_t *block)
{
decoder_sys_t *sys = dec->p_sys;
MMAL_BUFFER_HEADER_T *buffer;
- bool need_flush = false;
uint32_t len;
- uint32_t flags = 0;
+ uint32_t flags = MMAL_BUFFER_HEADER_FLAG_FRAME_START;
MMAL_STATUS_T status;
+#if TRACE_ALL
+ msg_Dbg(dec, "<<< %s: %lld/%lld", __func__, block == NULL ? -1LL : block->i_dts, block == NULL ? -1LL : block->i_pts);
+#endif
+
+ if (sys->err_stream != MMAL_SUCCESS) {
+ msg_Err(dec, "MMAL error reported by ctrl");
+ flush_decoder(dec);
+ return VLCDEC_ECRITICAL; /// I think they are all fatal
+ }
+
/*
* Configure output port if necessary
*/
@@ -541,18 +654,50 @@ static int decode(decoder_t *dec, block_t *block)
msg_Err(dec, "Failed to change output port format");
}
- if (!block)
- goto out;
+ if (block == NULL)
+ return VLCDEC_SUCCESS;
/*
* Check whether full flush is required
*/
- if (block && block->i_flags & BLOCK_FLAG_DISCONTINUITY) {
+ if (block->i_flags & BLOCK_FLAG_DISCONTINUITY) {
+#if TRACE_ALL
+ msg_Dbg(dec, "%s: >>> Discontinuity", __func__);
+#endif
flush_decoder(dec);
+ }
+
+ if (block->i_buffer == 0)
+ {
block_Release(block);
return VLCDEC_SUCCESS;
}
+ // Reenable stuff if the last thing we did was flush
+ if (!sys->output->is_enabled &&
+ (status = mmal_port_enable(sys->output, decoder_output_cb)) != MMAL_SUCCESS)
+ {
+ msg_Err(dec, "Output port enable failed");
+ goto fail;
+ }
+
+ if (!sys->input->is_enabled)
+ {
+ if ((status = set_extradata_and_commit(dec, sys)) != MMAL_SUCCESS)
+ goto fail;
+
+ if ((status = mmal_port_enable(sys->input, input_port_cb)) != MMAL_SUCCESS)
+ {
+ msg_Err(dec, "Input port enable failed");
+ goto fail;
+ }
+
+ if ((status = decoder_send_extradata(dec, sys)) != MMAL_SUCCESS)
+ goto fail;
+ }
+
+ // *** We cannot get a picture to put the result in 'till we have
+ // reported the size & the output stages have been set up
if (atomic_load(&sys->started))
fill_output_port(dec);
@@ -563,18 +708,21 @@ static int decode(decoder_t *dec, block_t *block)
if (block->i_flags & BLOCK_FLAG_CORRUPTED)
flags |= MMAL_BUFFER_HEADER_FLAG_CORRUPTED;
- while (block && block->i_buffer > 0) {
- buffer = mmal_queue_timedwait(sys->input_pool->queue, 100);
+ while (block != NULL)
+ {
+ buffer = mmal_queue_wait(sys->input_pool->queue);
if (!buffer) {
msg_Err(dec, "Failed to retrieve buffer header for input data");
- need_flush = true;
- break;
+ goto fail;
}
+
mmal_buffer_header_reset(buffer);
buffer->cmd = 0;
- buffer->pts = block->i_pts != 0 ? block->i_pts : block->i_dts;
+ buffer->pts = block->i_pts != VLC_TICK_INVALID ? block->i_pts :
+ block->i_dts != VLC_TICK_INVALID ? block->i_dts : MMAL_TIME_UNKNOWN;
buffer->dts = block->i_dts;
buffer->alloc_size = sys->input->buffer_size;
+ buffer->user_data = NULL;
len = block->i_buffer;
if (len > buffer->alloc_size)
@@ -585,94 +733,1835 @@ static int decode(decoder_t *dec, block_t *block)
block->i_buffer -= len;
buffer->length = len;
if (block->i_buffer == 0) {
+ flags |= MMAL_BUFFER_HEADER_FLAG_FRAME_END;
+ if (block->i_flags & BLOCK_FLAG_END_OF_SEQUENCE) {
+ msg_Dbg(dec, "EOS sent");
+ flags |= MMAL_BUFFER_HEADER_FLAG_EOS;
+ }
buffer->user_data = block;
block = NULL;
}
buffer->flags = flags;
+#if TRACE_ALL
+ msg_Dbg(dec, "%s: -- Send buffer: cmd=%d, data=%p, size=%d, len=%d, offset=%d, flags=%#x, pts=%lld, dts=%lld", __func__,\
+ buffer->cmd, buffer->data, buffer->alloc_size, buffer->length, buffer->offset,
+ buffer->flags, (long long)buffer->pts, (long long)buffer->dts);
+#endif
status = mmal_port_send_buffer(sys->input, buffer);
if (status != MMAL_SUCCESS) {
msg_Err(dec, "Failed to send buffer to input port (status=%"PRIx32" %s)",
status, mmal_status_to_string(status));
- break;
+ goto fail;
}
- atomic_fetch_add(&sys->input_in_transit, 1);
+
+ // Reset flushed flag once we have sent a buf
+ sys->b_flushed = false;
+ flags &= ~MMAL_BUFFER_HEADER_FLAG_FRAME_START;
}
+ return VLCDEC_SUCCESS;
-out:
- if (need_flush)
- flush_decoder(dec);
+fail:
+ flush_decoder(dec);
+ return VLCDEC_ECRITICAL;
- return VLCDEC_SUCCESS;
}
-static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+
+static void CloseDecoder(decoder_t *dec)
{
- decoder_t *dec = (decoder_t *)port->userdata;
+ decoder_sys_t *sys = dec->p_sys;
+
+#if TRACE_ALL
+ msg_Dbg(dec, "%s: <<<", __func__);
+#endif
+
+ if (!sys)
+ return;
+
+ if (sys->component != NULL) {
+ if (sys->input->is_enabled)
+ mmal_port_disable(sys->input);
+
+ if (sys->output->is_enabled)
+ mmal_port_disable(sys->output);
+
+ if (sys->component->control->is_enabled)
+ mmal_port_disable(sys->component->control);
+
+ if (sys->component->is_enabled)
+ mmal_component_disable(sys->component);
+
+ mmal_component_release(sys->component);
+ }
+
+ if (sys->input_pool != NULL)
+ mmal_pool_destroy(sys->input_pool);
+
+ if (sys->output_format != NULL)
+ mmal_format_free(sys->output_format);
+
+ hw_mmal_port_pool_ref_release(sys->ppr, false);
+
+ cma_vcsm_exit(sys->vcsm_init_type);
+
+ vlc_mutex_destroy(&sys->pic_lock);
+ free(sys);
+}
+
+static int OpenDecoder(decoder_t *dec)
+{
+ int ret = VLC_EGENERIC;
+ decoder_sys_t *sys;
MMAL_STATUS_T status;
+ const MMAL_FOURCC_T in_fcc = vlc_to_mmal_es_fourcc(dec->fmt_in.i_codec);
+ static bool is_kms = false;
+ static bool kms_checked = false;
+
+ if (!kms_checked) {
+ // This is true for Pi3 and earlier
+ // On Pi4 and later it is almost certainly better to be using V4L2
+ is_kms = !rpi_use_qpu_deinterlace();
+ kms_checked = true;
+ }
+ if (is_kms && !var_InheritBool(dec, MMAL_DECODE_ENABLE_NAME)) {
+#if TRACE_ALL
+ msg_Dbg(dec, "%s: <<< Disabled: Is KMS", __func__);
+#endif
+ return VLC_EGENERIC;
+ }
+
+#if TRACE_ALL || 1
+ {
+ char buf1[5], buf2[5], buf2a[5];
+ char buf3[5], buf4[5];
+ MMAL_RATIONAL_T r = rationalize_sar(dec->fmt_in.video.i_sar_num, dec->fmt_in.video.i_sar_den);
+
+ msg_Dbg(dec, "%s: <<< (%s/%s)[%s] %dx%d %d/%d=%d/%d o:%#x -> (%s/%s) %dx%d %d/%d o:%#x", __func__,
+ str_fourcc(buf1, dec->fmt_in.i_codec),
+ str_fourcc(buf2, dec->fmt_in.video.i_chroma),
+ str_fourcc(buf2a, in_fcc),
+ dec->fmt_in.video.i_width, dec->fmt_in.video.i_height,
+ dec->fmt_in.video.i_sar_num, dec->fmt_in.video.i_sar_den,
+ r.num, r.den,
+ (int)dec->fmt_in.video.orientation,
+ str_fourcc(buf3, dec->fmt_out.i_codec),
+ str_fourcc(buf4, dec->fmt_out.video.i_chroma),
+ dec->fmt_out.video.i_width, dec->fmt_out.video.i_height,
+ dec->fmt_out.video.i_sar_num, dec->fmt_out.video.i_sar_den,
+ (int)dec->fmt_out.video.orientation);
+ }
+#endif
+
+ if (!is_enc_supported(&supported_decode_in_enc, in_fcc))
+ return VLC_EGENERIC;
+
+ sys = calloc(1, sizeof(decoder_sys_t));
+ if (!sys) {
+ ret = VLC_ENOMEM;
+ goto fail;
+ }
+ dec->p_sys = sys;
+ vlc_mutex_init(&sys->pic_lock);
+
+ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) {
+ msg_Err(dec, "VCSM init failed");
+ goto fail;
+ }
+ msg_Info(dec, "VCSM init succeeded: %s", cma_vcsm_init_str(sys->vcsm_init_type));
+
+ sys->err_stream = MMAL_SUCCESS;
+
+ status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, &sys->component);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(dec, "Failed to create MMAL component %s (status=%"PRIx32" %s)",
+ MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, status, mmal_status_to_string(status));
+ goto fail;
+ }
+
+ sys->input = sys->component->input[0];
+ sys->output = sys->component->output[0];
+
+ sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)dec;
+ sys->input->format->encoding = in_fcc;
+
+ if (!set_and_test_enc_supported(&supported_decode_in_enc, sys->input, in_fcc)) {
+#if TRACE_ALL
+ char cbuf[5];
+ msg_Dbg(dec, "Format not supported: %s", str_fourcc(cbuf, in_fcc));
+#endif
+ goto fail;
+ }
+
+ sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)dec;
+ status = mmal_port_enable(sys->component->control, control_port_cb);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(dec, "Failed to enable control port %s (status=%"PRIx32" %s)",
+ sys->component->control->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
+
+ if ((status = set_extradata_and_commit(dec, sys)) != MMAL_SUCCESS)
+ goto fail;
+
+ sys->input->buffer_size = sys->input->buffer_size_recommended;
+ sys->input->buffer_num = sys->input->buffer_num_recommended;
+
+ status = mmal_port_enable(sys->input, input_port_cb);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(dec, "Failed to enable input port %s (status=%"PRIx32" %s)",
+ sys->input->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
+
+ // Set vanishingly unlikely shape (or at least crop)
+ // to ensure that we get a resolution changed event
+ // Small wxh are rejected (128x128 is rejected) so pick a
+ // plausible size.
+ // Crop doesn't seem to be checked for being constrained by wxh
+ // so we could place it outside the pic to be sure that it is
+ // never matched but stick with something legal in case it is ever
+ // actually checked
+ sys->output->format->es->video.height = 256;
+ sys->output->format->es->video.width = 256;
+ sys->output->format->es->video.crop.height = 4;
+ sys->output->format->es->video.crop.width = 2;
+ sys->output->format->es->video.crop.x = 66;
+ sys->output->format->es->video.crop.y = 88;
+
+ if ((status = hw_mmal_opaque_output(VLC_OBJECT(dec), &sys->ppr,
+ sys->output, NUM_EXTRA_BUFFERS, decoder_output_cb)) != MMAL_SUCCESS)
+ goto fail;
+
+ status = mmal_component_enable(sys->component);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(dec, "Failed to enable component %s (status=%"PRIx32" %s)",
+ sys->component->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
+
+ if ((sys->input_pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL)
+ {
+ msg_Err(dec, "Failed to create input pool");
+ goto fail;
+ }
+
+ sys->b_flushed = true;
+
+ if ((status = decoder_send_extradata(dec, sys)) != MMAL_SUCCESS)
+ goto fail;
+
+ // Given no better ideas at this point copy input format to output
+ // This also copies container stuff (such as orientation) that we do not
+ // decode from the ES but may be important to display
+ video_format_Copy(&dec->fmt_out.video, &dec->fmt_in.video);
+ dec->fmt_out.i_codec = VLC_CODEC_MMAL_OPAQUE;
+ dec->fmt_out.video.i_chroma = VLC_CODEC_MMAL_OPAQUE;
+
+
+ dec->pf_decode = decode;
+ dec->pf_flush = flush_decoder;
+
+#if TRACE_ALL
+ msg_Dbg(dec, ">>> %s: ok", __func__);
+#endif
+ return 0;
+
+fail:
+ CloseDecoder(dec);
+#if TRACE_ALL
+msg_Dbg(dec, ">>> %s: FAIL: ret=%d", __func__, ret);
+#endif
+ return ret;
+}
+
+// ----------------------------
+
+#define CONV_MAX_LATENCY 1 // In frames
+
+typedef struct pic_fifo_s {
+ picture_t * head;
+ picture_t * tail;
+} pic_fifo_t;
+
+static inline picture_t * pic_fifo_get(pic_fifo_t * const pf)
+{
+ picture_t * const pic = pf->head;;
+ if (pic != NULL) {
+ pf->head = pic->p_next;
+ pic->p_next = NULL;
+ }
+ return pic;
+}
+
+static inline picture_t * pic_fifo_get_all(pic_fifo_t * const pf)
+{
+ picture_t * const pic = pf->head;;
+ pf->head = NULL;
+ return pic;
+}
+
+static inline void pic_fifo_release_all(pic_fifo_t * const pf)
+{
+ picture_t * pic;
+ while ((pic = pic_fifo_get(pf)) != NULL) {
+ picture_Release(pic);
+ }
+}
+
+static inline void pic_fifo_init(pic_fifo_t * const pf)
+{
+ pf->head = NULL;
+ pf->tail = NULL; // Not strictly needed
+}
+
+static inline void pic_fifo_put(pic_fifo_t * const pf, picture_t * pic)
+{
+ pic->p_next = NULL;
+ if (pf->head == NULL)
+ pf->head = pic;
+ else
+ pf->tail->p_next = pic;
+ pf->tail = pic;
+}
+
+#define SUBS_MAX 3
+
+typedef enum filter_resizer_e {
+ FILTER_RESIZER_RESIZER,
+ FILTER_RESIZER_ISP,
+ FILTER_RESIZER_HVS
+} filter_resizer_t;
+
+typedef struct conv_frame_stash_s
+{
+ mtime_t pts;
+ MMAL_BUFFER_HEADER_T * sub_bufs[SUBS_MAX];
+} conv_frame_stash_t;
+
+typedef struct filter_sys_t {
+ filter_resizer_t resizer_type;
+ MMAL_COMPONENT_T *component;
+ MMAL_PORT_T *input;
+ MMAL_PORT_T *output;
+ MMAL_POOL_T *out_pool; // Free output buffers
+ MMAL_POOL_T *in_pool; // Input pool to get BH for replication
+
+ cma_buf_pool_t * cma_in_pool;
+ cma_buf_pool_t * cma_out_pool;
+
+ subpic_reg_stash_t subs[SUBS_MAX];
+
+ pic_fifo_t ret_pics;
+
+ unsigned int pic_n;
+ vlc_sem_t sem;
+ vlc_mutex_t lock;
+
+ MMAL_STATUS_T err_stream;
+
+ bool needs_copy_in;
+ bool is_cma;
+ bool is_sliced;
+ bool out_fmt_set;
+ const char * component_name;
+ MMAL_PORT_BH_CB_T in_port_cb_fn;
+ MMAL_PORT_BH_CB_T out_port_cb_fn;
+
+ uint64_t frame_seq;
+ conv_frame_stash_t stash[16];
+
+ // Slice specific tracking stuff
+ struct {
+ pic_fifo_t pics;
+ unsigned int line; // Lines filled
+ } slice;
+
+ vcsm_init_type_t vcsm_init_type;
+} filter_sys_t;
+
+
+static MMAL_STATUS_T pic_to_format(MMAL_ES_FORMAT_T * const es_fmt, const picture_t * const pic)
+{
+ unsigned int bpp = (pic->format.i_bits_per_pixel + 7) >> 3;
+ MMAL_VIDEO_FORMAT_T * const v_fmt = &es_fmt->es->video;
+
+ es_fmt->type = MMAL_ES_TYPE_VIDEO;
+ es_fmt->encoding = vlc_to_mmal_video_fourcc(&pic->format);
+ es_fmt->encoding_variant = 0;
+
+ // Fill in crop etc.
+ hw_mmal_vlc_fmt_to_mmal_fmt(es_fmt, &pic->format);
+ // Override width / height with strides if appropriate
+ if (bpp != 0) {
+ v_fmt->width = pic->p[0].i_pitch / bpp;
+ v_fmt->height = pic->p[0].i_lines;
+ }
+ return MMAL_SUCCESS;
+}
+
+
+static MMAL_STATUS_T conv_enable_in(filter_t * const p_filter, filter_sys_t * const sys)
+{
+ MMAL_STATUS_T err = MMAL_SUCCESS;
+
+ if (!sys->input->is_enabled &&
+ (err = mmal_port_enable(sys->input, sys->in_port_cb_fn)) != MMAL_SUCCESS)
+ {
+ msg_Err(p_filter, "Failed to enable input port %s (status=%"PRIx32" %s)",
+ sys->input->name, err, mmal_status_to_string(err));
+ }
+ return err;
+}
+
+static MMAL_STATUS_T conv_enable_out(filter_t * const p_filter, filter_sys_t * const sys)
+{
+ MMAL_STATUS_T err = MMAL_SUCCESS;
+
+ if (sys->is_cma)
+ {
+ if (sys->cma_out_pool == NULL &&
+ (sys->cma_out_pool = cma_buf_pool_new(CONVERTER_BUFFERS, CONVERTER_BUFFERS, true, "mmal_resizer")) == NULL)
+ {
+ msg_Err(p_filter, "Failed to alloc cma buf pool");
+ return MMAL_ENOMEM;
+ }
+ }
+ else
+ {
+ cma_buf_pool_deletez(&sys->cma_out_pool);
+ }
+
+ if (!sys->output->is_enabled &&
+ (err = mmal_port_enable(sys->output, sys->out_port_cb_fn)) != MMAL_SUCCESS)
+ {
+ msg_Err(p_filter, "Failed to enable output port %s (status=%"PRIx32" %s)",
+ sys->output->name, err, mmal_status_to_string(err));
+ }
+ return err;
+}
+
+static void conv_control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+{
+ filter_t * const p_filter = (filter_t *)port->userdata;
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, "%s: <<< cmd=%d, data=%p, pic=%p", __func__, buffer->cmd, buffer->data, buffer->user_data);
+#endif
if (buffer->cmd == MMAL_EVENT_ERROR) {
- status = *(uint32_t *)buffer->data;
- msg_Err(dec, "MMAL error %"PRIx32" \"%s\"", status,
+ MMAL_STATUS_T status = *(uint32_t *)buffer->data;
+
+ p_filter->p_sys->err_stream = status;
+
+ msg_Err(p_filter, "MMAL error %"PRIx32" \"%s\"", status,
mmal_status_to_string(status));
}
mmal_buffer_header_release(buffer);
}
-static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+static void conv_input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf)
{
- block_t *block = (block_t *)buffer->user_data;
- decoder_t *dec = (decoder_t *)port->userdata;
- decoder_sys_t *sys = dec->p_sys;
- buffer->user_data = NULL;
+#if TRACE_ALL
+ picture_context_t * ctx = buf->user_data;
+// filter_sys_t *const sys = ((filter_t *)port->userdata)->p_sys;
+
+ msg_Dbg((filter_t *)port->userdata, "<<< %s cmd=%d, ctx=%p, buf=%p, flags=%#x, len=%d/%d, pts=%lld",
+ __func__, buf->cmd, ctx, buf, buf->flags, buf->length, buf->alloc_size, (long long)buf->pts);
+#else
+ VLC_UNUSED(port);
+#endif
+
+ mmal_buffer_header_release(buf);
+
+#if TRACE_ALL
+ msg_Dbg((filter_t *)port->userdata, ">>> %s", __func__);
+#endif
+}
+
+static void conv_out_q_pic(filter_sys_t * const sys, picture_t * const pic)
+{
+ pic->p_next = NULL;
+
+ vlc_mutex_lock(&sys->lock);
+ pic_fifo_put(&sys->ret_pics, pic);
+ vlc_mutex_unlock(&sys->lock);
- mmal_buffer_header_release(buffer);
- if (block)
- block_Release(block);
- atomic_fetch_sub(&sys->input_in_transit, 1);
vlc_sem_post(&sys->sem);
}
-static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+static void conv_output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf)
{
- decoder_t *dec = (decoder_t *)port->userdata;
- decoder_sys_t *sys = dec->p_sys;
- picture_t *picture;
- MMAL_EVENT_FORMAT_CHANGED_T *fmt;
- MMAL_ES_FORMAT_T *format;
-
- if (buffer->cmd == 0) {
- picture = (picture_t *)buffer->user_data;
- if (buffer->length > 0) {
- picture->date = buffer->pts;
- picture->b_progressive = sys->b_progressive;
- picture->b_top_field_first = sys->b_top_field_first;
- decoder_QueueVideo(dec, picture);
- } else {
- picture_Release(picture);
- if (sys->output_pool) {
- buffer->user_data = NULL;
- buffer->alloc_size = 0;
- buffer->data = NULL;
- mmal_buffer_header_release(buffer);
+ filter_t * const p_filter = (filter_t *)port->userdata;
+ filter_sys_t * const sys = p_filter->p_sys;
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, "<<< %s: cmd=%d, flags=%#x, pic=%p, data=%p, len=%d/%d, pts=%lld/%lld", __func__,
+ buf->cmd, buf->flags, buf->user_data, buf->data, buf->length, buf->alloc_size,
+ (long long)buf->pts, (long long)sys->stash[(unsigned int)(buf->pts & 0xf)].pts);
+#endif
+ if (buf->cmd == 0) {
+ picture_t * const pic = (picture_t *)buf->user_data;
+
+ if (pic == NULL) {
+ msg_Err(p_filter, "%s: Buffer has no attached picture", __func__);
+ }
+ else if (buf->data == NULL || buf->length == 0)
+ {
+#if TRACE_ALL
+ msg_Dbg(p_filter, "%s: Buffer has no data", __func__);
+#endif
+ }
+ else
+ {
+ buf_to_pic_copy_props(pic, buf);
+
+ // Set pic data pointers from buf aux info now it has it
+ if (sys->is_cma) {
+ if (cma_pic_set_data(pic, sys->output->format, buf) != VLC_SUCCESS)
+ msg_Err(p_filter, "Failed to set data");
}
+
+// draw_corners(pic->p[0].p_pixels, pic->p[0].i_pitch / 4, 0, 0, pic->p[0].i_visible_pitch / 4, pic->p[0].i_visible_lines);
+#if DEBUG_SQUARES
+ draw_square(pic->p[0].p_pixels, pic->p[0].i_pitch / 4, 0, 0, 32, 32, 0xffff0000);
+ draw_square(pic->p[0].p_pixels, pic->p[0].i_pitch / 4, 32, 0, 32, 32, 0xff00ff00);
+ draw_square(pic->p[0].p_pixels, pic->p[0].i_pitch / 4, 64, 0, 32, 32, 0xff0000ff);
+#endif
+
+ buf->user_data = NULL; // Responsability for this pic no longer with buffer
+ conv_out_q_pic(sys, pic);
}
- atomic_fetch_sub(&sys->output_in_transit, 1);
- vlc_sem_post(&sys->sem);
- } else if (buffer->cmd == MMAL_EVENT_FORMAT_CHANGED) {
- fmt = mmal_event_format_changed_get(buffer);
+ }
- format = mmal_format_alloc();
- mmal_format_full_copy(format, fmt->format);
+ mmal_buffer_header_release(buf);
+}
- if (sys->opaque)
- format->encoding = MMAL_ENCODING_OPAQUE;
- sys->output_format = format;
+static void slice_output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf)
+{
+ filter_t * const p_filter = (filter_t *)port->userdata;
+ filter_sys_t * const sys = p_filter->p_sys;
- mmal_buffer_header_release(buffer);
- } else {
- mmal_buffer_header_release(buffer);
+#if TRACE_ALL
+ msg_Dbg(p_filter, "<<< %s: cmd=%d, flags=%#x, pic=%p, data=%p, len=%d/%d, pts=%lld", __func__,
+ buf->cmd, buf->flags, buf->user_data, buf->data, buf->length, buf->alloc_size, (long long)buf->pts);
+#endif
+
+ if (buf->cmd != 0)
+ {
+ mmal_buffer_header_release(buf);
+ return;
+ }
+
+ if (buf->data == NULL || buf->length == 0)
+ {
+#if TRACE_ALL
+ msg_Dbg(p_filter, "%s: Buffer has no data", __func__);
+#endif
+ }
+ else
+ {
+ // Got slice
+ picture_t *pic = sys->slice.pics.head;
+ const unsigned int scale_lines = sys->output->format->es->video.height; // Expected lines of callback
+
+ if (pic == NULL) {
+ msg_Err(p_filter, "No output picture");
+ goto fail;
+ }
+
+ // Copy lines
+ // * single plane only - fix for I420
+ {
+ const unsigned int scale_n = __MIN(scale_lines - sys->slice.line, MMAL_SLICE_HEIGHT);
+ const unsigned int pic_lines = pic->p[0].i_lines;
+ const unsigned int copy_n = sys->slice.line + scale_n <= pic_lines ? scale_n :
+ sys->slice.line >= pic_lines ? 0 :
+ pic_lines - sys->slice.line;
+
+ const unsigned int src_stride = buf->type->video.pitch[0];
+ const unsigned int dst_stride = pic->p[0].i_pitch;
+ uint8_t *dst = pic->p[0].p_pixels + sys->slice.line * dst_stride;
+ const uint8_t *src = buf->data + buf->type->video.offset[0];
+
+ if (src_stride == dst_stride) {
+ if (copy_n != 0)
+ memcpy(dst, src, src_stride * copy_n);
+ }
+ else {
+ unsigned int i;
+ for (i = 0; i != copy_n; ++i) {
+ memcpy(dst, src, __MIN(dst_stride, src_stride));
+ dst += dst_stride;
+ src += src_stride;
+ }
+ }
+ sys->slice.line += scale_n;
+ }
+
+ if ((buf->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END) != 0 || sys->slice.line >= scale_lines) {
+
+ if ((buf->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END) == 0 || sys->slice.line != scale_lines) {
+ // Stuff doesn't add up...
+ msg_Err(p_filter, "Line count (%d/%d) & EOF disagree (flags=%#x)", sys->slice.line, scale_lines, buf->flags);
+ goto fail;
+ }
+ else {
+ sys->slice.line = 0;
+
+ vlc_mutex_lock(&sys->lock);
+ pic_fifo_get(&sys->slice.pics); // Remove head from Q
+ vlc_mutex_unlock(&sys->lock);
+
+ buf_to_pic_copy_props(pic, buf);
+ conv_out_q_pic(sys, pic);
+ }
+ }
}
+
+ // Put back
+ buf->user_data = NULL; // Zap here to make sure we can't reuse later
+ mmal_buffer_header_reset(buf);
+
+ if (mmal_port_send_buffer(sys->output, buf) != MMAL_SUCCESS) {
+ mmal_buffer_header_release(buf);
+ }
+ return;
+
+fail:
+ sys->err_stream = MMAL_EIO;
+ vlc_sem_post(&sys->sem); // If we were waiting then break us out - the flush should fix sem values
}
+
+
+static void conv_flush(filter_t * p_filter)
+{
+ filter_sys_t * const sys = p_filter->p_sys;
+ unsigned int i;
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, "<<< %s", __func__);
+#endif
+
+ if (sys->resizer_type == FILTER_RESIZER_HVS)
+ {
+ for (i = 0; i != SUBS_MAX; ++i) {
+ hw_mmal_subpic_flush(VLC_OBJECT(p_filter), sys->subs + i);
+ }
+ }
+
+ if (sys->input != NULL && sys->input->is_enabled)
+ mmal_port_disable(sys->input);
+
+ if (sys->output != NULL && sys->output->is_enabled)
+ mmal_port_disable(sys->output);
+
+// cma_buf_pool_deletez(&sys->cma_out_pool);
+
+ // Free up anything we may have already lying around
+ // Don't need lock as the above disables should have prevented anything
+ // happening in the background
+
+ for (i = 0; i != 16; ++i) {
+ conv_frame_stash_t *const stash = sys->stash + i;
+ unsigned int sub_no;
+
+ stash->pts = MMAL_TIME_UNKNOWN;
+ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) {
+ if (stash->sub_bufs[sub_no] != NULL) {
+ mmal_buffer_header_release(stash->sub_bufs[sub_no]);
+ stash->sub_bufs[sub_no] = NULL;
+ }
+ }
+ }
+
+ pic_fifo_release_all(&sys->slice.pics);
+ pic_fifo_release_all(&sys->ret_pics);
+
+ // Reset sem values - easiest & most reliable way is to just kill & re-init
+ vlc_sem_destroy(&sys->sem);
+ vlc_sem_init(&sys->sem, 0);
+ sys->pic_n = 0;
+
+ // Reset error status
+ sys->err_stream = MMAL_SUCCESS;
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, ">>> %s", __func__);
+#endif
+}
+
+static void conv_stash_fixup(filter_t * const p_filter, filter_sys_t * const sys, picture_t * const p_pic)
+{
+ conv_frame_stash_t * const stash = sys->stash + (p_pic->date & 0xf);
+ unsigned int sub_no;
+ VLC_UNUSED(p_filter);
+
+ p_pic->date = stash->pts;
+ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) {
+ if (stash->sub_bufs[sub_no] != NULL) {
+ // **** Do stashed blend
+ // **** Aaargh, bother... need to rescale subs too
+
+ mmal_buffer_header_release(stash->sub_bufs[sub_no]);
+ stash->sub_bufs[sub_no] = NULL;
+ }
+ }
+}
+
+// Output buffers may contain a pic ref on error or flush
+// Free it
+static MMAL_BOOL_T out_buffer_pre_release_cb(MMAL_BUFFER_HEADER_T *header, void *userdata)
+{
+ VLC_UNUSED(userdata);
+
+ picture_t * const pic = header->user_data;
+ header->user_data = NULL;
+
+ if (pic != NULL)
+ picture_Release(pic);
+
+ return MMAL_FALSE;
+}
+
+static MMAL_STATUS_T conv_set_output(filter_t * const p_filter, filter_sys_t * const sys, picture_t * const pic)
+{
+ MMAL_STATUS_T status;
+
+ sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)p_filter;
+ sys->output->format->type = MMAL_ES_TYPE_VIDEO;
+ sys->output->format->encoding = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video);
+ sys->output->format->encoding_variant = 0;
+ hw_mmal_vlc_fmt_to_mmal_fmt(sys->output->format, &p_filter->fmt_out.video);
+
+ if (pic != NULL)
+ {
+ // Override default format width/height if we have a pic we need to match
+ if ((status = pic_to_format(sys->output->format, pic)) != MMAL_SUCCESS)
+ {
+ char cbuf[5];
+ msg_Err(p_filter, "Bad format desc: %s, pic=%p, bits=%d", str_fourcc(cbuf, pic->format.i_chroma), pic, pic->format.i_bits_per_pixel);
+ return status;
+ }
+
+ MMAL_VIDEO_FORMAT_T *fmt = &sys->output->format->es->video;
+ msg_Dbg(p_filter, "%s: %dx%d [(0,0) %dx%d]", __func__, fmt->width, fmt->height, fmt->crop.width, fmt->crop.height);
+ }
+
+ if (sys->is_sliced) {
+ // Override height for slice
+ sys->output->format->es->video.height = MMAL_SLICE_HEIGHT;
+ }
+
+ mmal_log_dump_format(sys->output->format);
+
+ status = mmal_port_format_commit(sys->output);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(p_filter, "Failed to commit format for output port %s (status=%"PRIx32" %s)",
+ sys->output->name, status, mmal_status_to_string(status));
+ return status;
+ }
+
+ sys->output->buffer_num = __MAX(sys->is_sliced ? 16 : 2, sys->output->buffer_num_recommended);
+ sys->output->buffer_size = sys->output->buffer_size_recommended;
+
+ if ((status = conv_enable_out(p_filter, sys)) != MMAL_SUCCESS)
+ return status;
+
+ return MMAL_SUCCESS;
+}
+
+
+static picture_t *conv_get_out_pics(filter_sys_t * const sys)
+{
+ picture_t * ret_pics;
+
+ vlc_sem_wait(&sys->sem);
+
+ // Return a single pending buffer
+ vlc_mutex_lock(&sys->lock);
+ ret_pics = pic_fifo_get(&sys->ret_pics);
+ vlc_mutex_unlock(&sys->lock);
+
+ return ret_pics;
+}
+
+static picture_t *conv_filter(filter_t *p_filter, picture_t *p_pic)
+{
+ filter_sys_t * const sys = p_filter->p_sys;
+ picture_t * ret_pics = NULL;
+ MMAL_STATUS_T err;
+ const uint64_t frame_seq = ++sys->frame_seq;
+ conv_frame_stash_t * const stash = sys->stash + (frame_seq & 0xf);
+ MMAL_BUFFER_HEADER_T * out_buf = NULL;
+
+#if TRACE_ALL
+ {
+ char dbuf0[5], dbuf1[5];
+ msg_Dbg(p_filter, "<<< %s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d->%s,%dx%d [(%d,%d) %dx%d] sar:%d/%d", __func__,
+ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma), p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height,
+ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset,
+ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height,
+ p_filter->fmt_in.video.i_sar_num, p_filter->fmt_in.video.i_sar_den,
+ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma), p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height,
+ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset,
+ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height,
+ p_filter->fmt_out.video.i_sar_num, p_filter->fmt_out.video.i_sar_den);
+ }
+#endif
+
+ if (sys->err_stream != MMAL_SUCCESS) {
+ goto stream_fail;
+ }
+
+ // Check pic fmt corresponds to what we have set up
+ if (hw_mmal_vlc_pic_to_mmal_fmt_update(sys->input->format, p_pic))
+ {
+ msg_Dbg(p_filter, "Reset input port format");
+
+ // HVS can take new formats without disable, others need it
+ if (sys->resizer_type != FILTER_RESIZER_HVS) {
+ // Extract any pending pic
+ if (sys->pic_n >= 2) {
+ ret_pics = conv_get_out_pics(sys);
+ // If pic_n == 1 then we return without trying to get stuff
+ sys->pic_n = 1;
+ }
+ if (sys->input->is_enabled) {
+ if ((err = mmal_port_disable(sys->input)) != MMAL_SUCCESS)
+ msg_Warn(p_filter, "Format update disable failed: %s", mmal_status_to_string(err));
+ }
+ }
+
+// mmal_log_dump_port(sys->input);
+ if ((err = mmal_port_format_commit(sys->input)) != MMAL_SUCCESS)
+ msg_Warn(p_filter, "Format update commit failed: %s", mmal_status_to_string(err));
+
+ // (Re)enable if required will be done later
+ }
+
+ if (p_pic->context == NULL) {
+ // Can't have stashed subpics if not one of our pics
+ if (!sys->needs_copy_in)
+ msg_Dbg(p_filter, "%s: No context", __func__);
+ }
+ else if (sys->resizer_type == FILTER_RESIZER_HVS)
+ {
+ unsigned int sub_no = 0;
+
+ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) {
+ int rv;
+ if ((rv = hw_mmal_subpic_update(VLC_OBJECT(p_filter),
+ hw_mmal_pic_sub_buf_get(p_pic, sub_no),
+ sys->subs + sub_no,
+ &p_pic->format,
+ &sys->output->format->es->video.crop,
+ MMAL_DISPLAY_ROT0,
+ frame_seq)) == 0)
+ break;
+ else if (rv < 0)
+ goto fail;
+ }
+ }
+ else
+ {
+ unsigned int sub_no = 0;
+ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) {
+ if ((stash->sub_bufs[sub_no] = hw_mmal_pic_sub_buf_get(p_pic, sub_no)) != NULL) {
+ mmal_buffer_header_acquire(stash->sub_bufs[sub_no]);
+ }
+ }
+ }
+
+ if (!sys->out_fmt_set) {
+ sys->out_fmt_set = true;
+
+ if (sys->is_sliced) {
+ // If zc then we will do stride conversion when we copy to arm side
+ // so no need to worry about actual pic dimensions here
+ if ((err = conv_set_output(p_filter, sys, NULL)) != MMAL_SUCCESS)
+ goto fail;
+
+ sys->out_pool = mmal_port_pool_create(sys->output, sys->output->buffer_num, sys->output->buffer_size);
+ }
+ else {
+ picture_t *pic = filter_NewPicture(p_filter);
+ err = conv_set_output(p_filter, sys, pic);
+ picture_Release(pic);
+ if (err != MMAL_SUCCESS)
+ goto fail;
+
+ sys->out_pool = mmal_pool_create(sys->output->buffer_num, 0);
+ }
+
+ if (sys->out_pool == NULL) {
+ msg_Err(p_filter, "Failed to create output pool");
+ goto fail;
+ }
+ }
+
+ // Reenable stuff if the last thing we did was flush
+ if ((err = conv_enable_out(p_filter, sys)) != MMAL_SUCCESS ||
+ (err = conv_enable_in(p_filter, sys)) != MMAL_SUCCESS)
+ goto fail;
+
+ // We attach pic to buf before stuffing the output port
+ // We could attach the pic on output for cma, but it is a lot easier to keep
+ // the code common.
+ {
+ picture_t * const out_pic = filter_NewPicture(p_filter);
+
+ if (out_pic == NULL)
+ {
+ msg_Err(p_filter, "Failed to alloc required filter output pic");
+ goto fail;
+ }
+
+ if (p_filter->fmt_out.video.i_sar_den == 0 || p_filter->fmt_out.video.i_sar_num == 0) {
+ out_pic->format.i_sar_den = 1;
+ out_pic->format.i_sar_num = 1;
+ }
+ else {
+ out_pic->format.i_sar_den = p_filter->fmt_out.video.i_sar_den;
+ out_pic->format.i_sar_num = p_filter->fmt_out.video.i_sar_num;
+ }
+
+ if (sys->is_sliced) {
+ vlc_mutex_lock(&sys->lock);
+ pic_fifo_put(&sys->slice.pics, out_pic);
+ vlc_mutex_unlock(&sys->lock);
+
+ // Poke any returned pic buffers into output
+ // In general this should only happen immediately after enable
+ while ((out_buf = mmal_queue_get(sys->out_pool->queue)) != NULL)
+ mmal_port_send_buffer(sys->output, out_buf);
+ }
+ else
+ {
+ // 1 in - 1 out
+ if ((out_buf = mmal_queue_wait(sys->out_pool->queue)) == NULL)
+ {
+ msg_Err(p_filter, "Failed to get output buffer");
+ picture_Release(out_pic);
+ goto fail;
+ }
+ mmal_buffer_header_reset(out_buf);
+
+ // Attach out_pic to the buffer & ensure it is freed when the buffer is released
+ // On a good send callback the pic will be extracted to avoid this
+ out_buf->user_data = out_pic;
+ mmal_buffer_header_pre_release_cb_set(out_buf, out_buffer_pre_release_cb, NULL);
+
+#if 0
+ {
+ char dbuf0[5];
+ msg_Dbg(p_filter, "out_pic %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d",
+ str_fourcc(dbuf0, out_pic->format.i_chroma),
+ out_pic->format.i_width, out_pic->format.i_height,
+ out_pic->format.i_x_offset, out_pic->format.i_y_offset,
+ out_pic->format.i_visible_width, out_pic->format.i_visible_height,
+ out_pic->format.i_sar_num, out_pic->format.i_sar_den);
+ }
+#endif
+
+ if (sys->is_cma) {
+ int rv;
+
+ cma_buf_t * const cb = cma_buf_pool_alloc_buf(sys->cma_out_pool, sys->output->buffer_size);
+ if (cb == NULL) {
+ char dbuf0[5];
+ msg_Err(p_filter, "Failed to alloc CMA buf: fmt=%s, size=%d",
+ str_fourcc(dbuf0, out_pic->format.i_chroma),
+ sys->output->buffer_size);
+ goto fail;
+ }
+ const unsigned int vc_h = cma_buf_vc_handle(cb); // Cannot coerce without going via variable
+ out_buf->data = (uint8_t *)vc_h;
+ out_buf->alloc_size = sys->output->buffer_size;
+
+ if ((rv = cma_buf_pic_attach(cb, out_pic)) != VLC_SUCCESS)
+ {
+ char dbuf0[5];
+ msg_Err(p_filter, "Failed to attach CMA to pic: fmt=%s err=%d",
+ str_fourcc(dbuf0, out_pic->format.i_chroma),
+ rv);
+ cma_buf_unref(cb);
+ goto fail;
+ }
+ }
+ else {
+ out_buf->data = out_pic->p[0].p_pixels;
+ out_buf->alloc_size = out_pic->p[0].i_pitch * out_pic->p[0].i_lines;
+ //**** stride ????
+ }
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, "Out buf send: pic=%p, data=%p, user=%p, flags=%#x, len=%d/%d, pts=%lld",
+ p_pic, out_buf->data, out_buf->user_data, out_buf->flags,
+ out_buf->length, out_buf->alloc_size, (long long)out_buf->pts);
+#endif
+
+ if ((err = mmal_port_send_buffer(sys->output, out_buf)) != MMAL_SUCCESS)
+ {
+ msg_Err(p_filter, "Send buffer to output failed");
+ goto fail;
+ }
+ out_buf = NULL;
+ }
+ }
+
+
+ // Stuff into input
+ // We assume the BH is already set up with values reflecting pic date etc.
+ stash->pts = p_pic->date;
+ {
+ MMAL_BUFFER_HEADER_T *const pic_buf = sys->needs_copy_in ?
+ hw_mmal_pic_buf_copied(p_pic, sys->in_pool, sys->input, sys->cma_in_pool) :
+ hw_mmal_pic_buf_replicated(p_pic, sys->in_pool);
+
+ // Whether or not we extracted the pic_buf we are done with the picture
+ picture_Release(p_pic);
+ p_pic = NULL;
+
+ if (pic_buf == NULL) {
+ msg_Err(p_filter, "Pic has no attached buffer");
+ goto fail;
+ }
+
+ pic_buf->pts = frame_seq;
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, "In buf send: pic=%p, data=%p, user=%p, flags=%#x, len=%d/%d/%d, pts=%lld",
+ p_pic, pic_buf->data, pic_buf->user_data, pic_buf->flags,
+ pic_buf->length, pic_buf->alloc_size, sys->input->buffer_size, (long long)pic_buf->pts);
+#endif
+
+ if ((err = mmal_port_send_buffer(sys->input, pic_buf)) != MMAL_SUCCESS)
+ {
+ msg_Err(p_filter, "Send buffer to input failed");
+ mmal_buffer_header_release(pic_buf);
+ goto fail;
+ }
+ }
+
+ // We have a 1 pic latency for everything except the 1st pic which we
+ // wait for.
+ // This means we get a single static pic out
+ if (sys->pic_n++ == 1) {
+#if TRACE_ALL
+ msg_Dbg(p_filter, ">>> %s: Pic1=%p", __func__, ret_pics);
+#endif
+ return ret_pics;
+ }
+
+ ret_pics = conv_get_out_pics(sys);
+
+ if (sys->err_stream != MMAL_SUCCESS)
+ goto stream_fail;
+
+ conv_stash_fixup(p_filter, sys, ret_pics);
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, ">>> %s: pic=%p", __func__, ret_pics);
+#endif
+
+ return ret_pics;
+
+stream_fail:
+ msg_Err(p_filter, "MMAL error reported by callback");
+fail:
+#if TRACE_ALL
+ msg_Err(p_filter, ">>> %s: FAIL", __func__);
+#endif
+ if (ret_pics != NULL)
+ picture_Release(ret_pics);
+ if (out_buf != NULL)
+ mmal_buffer_header_release(out_buf);
+ if (p_pic != NULL)
+ picture_Release(p_pic);
+ conv_flush(p_filter);
+ return NULL;
+}
+
+static void CloseConverter(vlc_object_t * obj)
+{
+ filter_t * const p_filter = (filter_t *)obj;
+ filter_sys_t * const sys = p_filter->p_sys;
+ unsigned int i;
+
+#if TRACE_ALL
+ msg_Dbg(obj, "<<< %s", __func__);
+#endif
+
+ if (sys == NULL)
+ return;
+
+ // Disables input & output ports
+ conv_flush(p_filter);
+
+ cma_buf_pool_deletez(&sys->cma_in_pool);
+ cma_buf_pool_deletez(&sys->cma_out_pool);
+
+ if (sys->component && sys->component->control->is_enabled)
+ mmal_port_disable(sys->component->control);
+
+ if (sys->component && sys->component->is_enabled)
+ mmal_component_disable(sys->component);
+
+ if (sys->resizer_type == FILTER_RESIZER_HVS)
+ {
+ for (i = 0; i != SUBS_MAX; ++i) {
+ hw_mmal_subpic_close(VLC_OBJECT(p_filter), sys->subs + i);
+ }
+ }
+
+ if (sys->out_pool)
+ {
+ if (sys->is_sliced)
+ mmal_port_pool_destroy(sys->output, sys->out_pool);
+ else
+ mmal_pool_destroy(sys->out_pool);
+ }
+
+ if (sys->in_pool != NULL)
+ mmal_pool_destroy(sys->in_pool);
+
+ if (sys->component)
+ mmal_component_release(sys->component);
+
+ cma_vcsm_exit(sys->vcsm_init_type);
+
+ vlc_sem_destroy(&sys->sem);
+ vlc_mutex_destroy(&sys->lock);
+
+ p_filter->p_sys = NULL;
+ free(sys);
+}
+
+
+static inline MMAL_FOURCC_T filter_enc_in(const video_format_t * const fmt)
+{
+ if (hw_mmal_chroma_is_mmal(fmt->i_chroma))
+ return vlc_to_mmal_video_fourcc(fmt);
+
+ if (fmt->i_chroma == VLC_CODEC_I420 ||
+ fmt->i_chroma == VLC_CODEC_I420_10L)
+ return MMAL_ENCODING_I420;
+
+ return 0;
+}
+
+static inline MMAL_FOURCC_T filter_enc_out(const video_format_t * const fmt)
+{
+ const MMAL_FOURCC_T mmes = vlc_to_mmal_video_fourcc(fmt);
+ // Can only copy out single plane stuff currently - this could be fixed!
+ return hw_mmal_chroma_is_mmal(fmt->i_chroma) || mmes != MMAL_ENCODING_I420 ? mmes : 0;
+}
+
+
+static int OpenConverter(vlc_object_t * obj)
+{
+ filter_t * const p_filter = (filter_t *)obj;
+ int ret = VLC_EGENERIC;
+ filter_sys_t *sys;
+ MMAL_STATUS_T status = 0;
+ MMAL_FOURCC_T enc_out = filter_enc_out(&p_filter->fmt_out.video);
+ const MMAL_FOURCC_T enc_in = filter_enc_in(&p_filter->fmt_in.video);
+ bool use_resizer;
+ bool use_isp;
+ int gpu_mem;
+
+ // At least in principle we should deal with any mmal format as input
+ if (enc_in == 0 || enc_out == 0)
+ return VLC_EGENERIC;
+
+ // Can't transform
+ if (p_filter->fmt_in.video.orientation != p_filter->fmt_out.video.orientation)
+ return VLC_EGENERIC;
+
+ use_resizer = var_InheritBool(p_filter, MMAL_RESIZE_NAME);
+ use_isp = var_InheritBool(p_filter, MMAL_ISP_NAME);
+
+retry:
+ // ** Make more generic by checking supported encs
+ //
+ // Must use ISP - HVS can't do this, nor can resizer
+ if (enc_in == MMAL_ENCODING_YUVUV64_10) {
+ // If resizer selected then just give up
+ if (use_resizer)
+ return VLC_EGENERIC;
+ // otherwise downgrade HVS to ISP
+ use_isp = true;
+ }
+ // HVS can't do I420
+ if (enc_out == MMAL_ENCODING_I420) {
+ use_isp = true;
+ }
+ // Only HVS can deal with SAND30
+ if (enc_in == MMAL_ENCODING_YUV10_COL) {
+ if (use_isp || use_resizer)
+ return VLC_EGENERIC;
+ }
+
+
+ if (use_resizer) {
+ // use resizer overrides use_isp
+ use_isp = false;
+ }
+
+ // Check we have a sliced version of the fourcc if we want the resizer
+ if (use_resizer &&
+ (enc_out = pic_to_slice_mmal_fourcc(enc_out)) == 0) {
+ return VLC_EGENERIC;
+ }
+
+ gpu_mem = hw_mmal_get_gpu_mem();
+
+ {
+ char dbuf0[5], dbuf1[5], dbuf2[5], dbuf3[5];
+ msg_Dbg(p_filter, "%s: (%s) %s/%s,%dx%d [(%d,%d) %d/%d] sar:%d/%d->%s/%s,%dx%d [(%d,%d) %dx%d] rgb:%#x:%#x:%#x sar:%d/%d (gpu=%d)", __func__,
+ use_resizer ? "resize" : use_isp ? "isp" : "hvs",
+ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma), str_fourcc(dbuf2, enc_in),
+ p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height,
+ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset,
+ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height,
+ p_filter->fmt_in.video.i_sar_num, p_filter->fmt_in.video.i_sar_den,
+ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma), str_fourcc(dbuf3, enc_out),
+ p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height,
+ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset,
+ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height,
+ p_filter->fmt_out.video.i_rmask, p_filter->fmt_out.video.i_gmask, p_filter->fmt_out.video.i_bmask,
+ p_filter->fmt_out.video.i_sar_num, p_filter->fmt_out.video.i_sar_den,
+ gpu_mem);
+ }
+
+ sys = calloc(1, sizeof(filter_sys_t));
+ if (!sys) {
+ ret = VLC_ENOMEM;
+ goto fail;
+ }
+ p_filter->p_sys = sys;
+
+ // Init stuff the we destroy unconditionaly in Close first
+ vlc_mutex_init(&sys->lock);
+ vlc_sem_init(&sys->sem, 0);
+ sys->err_stream = MMAL_SUCCESS;
+ pic_fifo_init(&sys->ret_pics);
+ pic_fifo_init(&sys->slice.pics);
+
+ sys->needs_copy_in = !hw_mmal_chroma_is_mmal(p_filter->fmt_in.video.i_chroma);
+ sys->in_port_cb_fn = conv_input_port_cb;
+
+ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) {
+ msg_Err(p_filter, "VCSM init failed");
+ goto fail;
+ }
+
+ if (use_resizer) {
+ sys->resizer_type = FILTER_RESIZER_RESIZER;
+ sys->is_sliced = true;
+ sys->component_name = MMAL_COMPONENT_DEFAULT_RESIZER;
+ sys->out_port_cb_fn = slice_output_port_cb;
+ }
+ else if (use_isp) {
+ sys->resizer_type = FILTER_RESIZER_ISP;
+ sys->is_sliced = false; // Copy directly into filter picture
+ sys->component_name = MMAL_COMPONENT_ISP_RESIZER;
+ sys->out_port_cb_fn = conv_output_port_cb;
+ } else {
+ sys->resizer_type = FILTER_RESIZER_HVS;
+ sys->is_sliced = false; // Copy directly into filter picture
+ sys->component_name = MMAL_COMPONENT_HVS;
+ sys->out_port_cb_fn = conv_output_port_cb;
+ }
+ sys->is_cma = is_cma_buf_pic_chroma(p_filter->fmt_out.video.i_chroma);
+
+ status = mmal_component_create(sys->component_name, &sys->component);
+ if (status != MMAL_SUCCESS) {
+ if (!use_isp && !use_resizer) {
+ msg_Warn(p_filter, "Failed to rcreate HVS resizer - retrying with ISP");
+ CloseConverter(obj);
+ use_isp = true;
+ goto retry;
+ }
+ msg_Err(p_filter, "Failed to create MMAL component %s (status=%"PRIx32" %s)",
+ MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, status, mmal_status_to_string(status));
+ goto fail;
+ }
+ sys->output = sys->component->output[0];
+ sys->input = sys->component->input[0];
+
+ sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)p_filter;
+ status = mmal_port_enable(sys->component->control, conv_control_port_cb);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(p_filter, "Failed to enable control port %s (status=%"PRIx32" %s)",
+ sys->component->control->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
+
+ if (sys->needs_copy_in &&
+ (sys->cma_in_pool = cma_buf_pool_new(2, 2, true, "conv-copy-in")) == NULL)
+ {
+ msg_Err(p_filter, "Failed to allocate input CMA pool");
+ goto fail;
+ }
+
+ sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)p_filter;
+ sys->input->format->type = MMAL_ES_TYPE_VIDEO;
+ sys->input->format->encoding = enc_in;
+ sys->input->format->encoding_variant = MMAL_ENCODING_I420;
+ hw_mmal_vlc_fmt_to_mmal_fmt(sys->input->format, &p_filter->fmt_in.video);
+ port_parameter_set_bool(sys->input, MMAL_PARAMETER_ZERO_COPY, 1);
+
+ mmal_log_dump_format(sys->input->format);
+
+ status = mmal_port_format_commit(sys->input);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(p_filter, "Failed to commit format for input port %s (status=%"PRIx32" %s)",
+ sys->input->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
+ sys->input->buffer_size = sys->input->buffer_size_recommended;
+ sys->input->buffer_num = NUM_DECODER_BUFFER_HEADERS;
+
+ if ((status = conv_enable_in(p_filter, sys)) != MMAL_SUCCESS)
+ goto fail;
+
+ port_parameter_set_bool(sys->output, MMAL_PARAMETER_ZERO_COPY, sys->is_sliced || sys->is_cma);
+
+ status = mmal_component_enable(sys->component);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(p_filter, "Failed to enable component %s (status=%"PRIx32" %s)",
+ sys->component->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
+
+ if ((sys->in_pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL)
+ {
+ msg_Err(p_filter, "Failed to create input pool");
+ goto fail;
+ }
+
+ if (sys->resizer_type == FILTER_RESIZER_HVS)
+ {
+ unsigned int i;
+ for (i = 0; i != SUBS_MAX; ++i) {
+ if (hw_mmal_subpic_open(VLC_OBJECT(p_filter), sys->subs + i, sys->component->input[i + 1], -1, i + 1) != MMAL_SUCCESS)
+ {
+ msg_Err(p_filter, "Failed to open subpic %d", i);
+ goto fail;
+ }
+ }
+ }
+
+ p_filter->pf_video_filter = conv_filter;
+ p_filter->pf_flush = conv_flush;
+ // video_drain NIF in filter structure
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, ">>> %s: ok", __func__);
+#endif
+
+ return VLC_SUCCESS;
+
+fail:
+ CloseConverter(obj);
+
+ if (!use_resizer && status == MMAL_ENOMEM) {
+ use_resizer = true;
+ msg_Warn(p_filter, "Lack of memory to use HVS/ISP: trying resizer");
+ goto retry;
+ }
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, ">>> %s: FAIL: %d", __func__, ret);
+#endif
+ return ret;
+}
+
+#if OPT_TO_FROM_ZC
+//----------------------------------------------------------------------------
+//
+// Simple copy in to ZC
+
+typedef struct to_zc_sys_s {
+ vcsm_init_type_t vcsm_init_type;
+ cma_buf_pool_t * cma_out_pool;
+} to_zc_sys_t;
+
+
+static size_t buf_alloc_size(const vlc_fourcc_t i_chroma, const unsigned int width, const unsigned int height)
+{
+ const unsigned int pels = width * height;
+
+ switch (i_chroma)
+ {
+ case VLC_CODEC_MMAL_ZC_RGB32:
+ return pels * 4;
+ case VLC_CODEC_MMAL_ZC_I420:
+ return pels * 3 / 2;
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+static picture_t *
+to_zc_filter(filter_t *p_filter, picture_t *in_pic)
+{
+ to_zc_sys_t * const sys = (to_zc_sys_t *)p_filter->p_sys;
+#if TRACE_ALL
+ msg_Dbg(p_filter, "<<< %s", __func__);
+#endif
+
+ assert(p_filter->fmt_out.video.i_chroma == VLC_CODEC_MMAL_ZC_I420);
+
+ picture_t * const out_pic = filter_NewPicture(p_filter);
+ if (out_pic == NULL)
+ goto fail0;
+
+ MMAL_ES_SPECIFIC_FORMAT_T mm_vfmt = {.video={0}};
+ MMAL_ES_FORMAT_T mm_esfmt = {
+ .encoding = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video),
+ .es = &mm_vfmt};
+
+ hw_mmal_vlc_fmt_to_mmal_fmt(&mm_esfmt, &p_filter->fmt_out.video);
+
+ const size_t buf_alloc = buf_alloc_size(p_filter->fmt_out.video.i_chroma,
+ mm_vfmt.video.width, mm_vfmt.video.height);
+ if (buf_alloc == 0)
+ goto fail1;
+ cma_buf_t *const cb = cma_buf_pool_alloc_buf(sys->cma_out_pool, buf_alloc);
+ if (cb == NULL)
+ goto fail1;
+
+ if (cma_buf_pic_attach(cb, out_pic) != VLC_SUCCESS)
+ goto fail2;
+ cma_pic_set_data(out_pic, &mm_esfmt, NULL);
+
+ hw_mmal_copy_pic_to_buf(cma_buf_addr(cb), NULL, &mm_esfmt, in_pic);
+
+ // Copy pic properties
+ out_pic->date = in_pic->date;
+ out_pic->b_force = in_pic->b_force;
+ out_pic->b_progressive = in_pic->b_progressive;
+ out_pic->b_top_field_first = in_pic->b_top_field_first;
+ out_pic->i_nb_fields = in_pic->i_nb_fields;
+
+ picture_Release(in_pic);
+
+ return out_pic;
+
+fail2:
+ cma_buf_unref(cb);
+fail1:
+ picture_Release(out_pic);
+fail0:
+ picture_Release(in_pic);
+ return NULL;
+}
+
+static void to_zc_flush(filter_t * p_filter)
+{
+ VLC_UNUSED(p_filter);
+}
+
+static void CloseConverterToZc(vlc_object_t * obj)
+{
+ filter_t * const p_filter = (filter_t *)obj;
+ to_zc_sys_t * const sys = (to_zc_sys_t *)p_filter->p_sys;
+
+ if (sys == NULL)
+ return;
+
+ p_filter->p_sys = NULL;
+
+ cma_buf_pool_deletez(&sys->cma_out_pool);
+ cma_vcsm_exit(sys->vcsm_init_type);
+
+ free(sys);
+}
+
+static bool to_zc_validate_fmt(const video_format_t * const f_in, const video_format_t * const f_out)
+{
+ if (!((f_in->i_chroma == VLC_CODEC_I420 || f_in->i_chroma == VLC_CODEC_I420_10L) &&
+ f_out->i_chroma == VLC_CODEC_MMAL_ZC_I420))
+ {
+ return false;
+ }
+ if (f_in->i_height != f_out->i_height ||
+ f_in->i_width != f_out->i_width)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+static int OpenConverterToZc(vlc_object_t * obj)
+{
+ int ret = VLC_EGENERIC;
+ filter_t * const p_filter = (filter_t *)obj;
+
+ if (!to_zc_validate_fmt(&p_filter->fmt_in.video, &p_filter->fmt_out.video))
+ goto fail;
+
+ {
+ char dbuf0[5], dbuf1[5];
+ msg_Dbg(p_filter, "%s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d->%s,%dx%d [(%d,%d) %dx%d] rgb:%#x:%#x:%#x sar:%d/%d", __func__,
+ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma),
+ p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height,
+ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset,
+ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height,
+ p_filter->fmt_in.video.i_sar_num, p_filter->fmt_in.video.i_sar_den,
+ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma),
+ p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height,
+ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset,
+ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height,
+ p_filter->fmt_out.video.i_rmask, p_filter->fmt_out.video.i_gmask, p_filter->fmt_out.video.i_bmask,
+ p_filter->fmt_out.video.i_sar_num, p_filter->fmt_out.video.i_sar_den);
+ }
+
+ to_zc_sys_t * const sys = calloc(1, sizeof(*sys));
+ if (!sys) {
+ ret = VLC_ENOMEM;
+ goto fail;
+ }
+ p_filter->p_sys = (filter_sys_t *)sys;
+
+ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) {
+ msg_Err(p_filter, "VCSM init failed");
+ goto fail;
+ }
+
+ if ((sys->cma_out_pool = cma_buf_pool_new(5, 5, true, "conv-to-zc")) == NULL)
+ {
+ msg_Err(p_filter, "Failed to allocate input CMA pool");
+ goto fail;
+ }
+
+ p_filter->pf_video_filter = to_zc_filter;
+ p_filter->pf_flush = to_zc_flush;
+ return VLC_SUCCESS;
+
+fail:
+ CloseConverterToZc(obj);
+ return ret;
+}
+
+//----------------------------------------------------------------------------
+//
+// Simple "copy" from ZC
+
+static void CloseConverterFromZc(vlc_object_t * obj)
+{
+ VLC_UNUSED(obj);
+}
+
+static int OpenConverterFromZc(vlc_object_t * obj)
+{
+ return VLC_EGENERIC;
+}
+#endif
+//----------------------------------------------------------------------------
+
+typedef struct blend_sys_s {
+ vzc_pool_ctl_t * vzc;
+ const picture_t * last_dst; // Not a ref, just a hint that we have a new pic
+ vcsm_init_type_t vcsm_init_type;
+} blend_sys_t;
+
+static void FilterBlendMmal(filter_t *p_filter,
+ picture_t *dst, const picture_t * src,
+ int x_offset, int y_offset, int alpha)
+{
+ blend_sys_t * const sys = (blend_sys_t *)p_filter->p_sys;
+#if TRACE_ALL
+ msg_Dbg(p_filter, "%s (%d,%d:%d) pic=%p, pts=%lld, force=%d", __func__, x_offset, y_offset, alpha, src, src->date, src->b_force);
+#endif
+ // If nothing to do then do nothing
+ if (alpha == 0 ||
+ src->format.i_visible_height == 0 ||
+ src->format.i_visible_width == 0)
+ {
+ return;
+ }
+
+ if (dst->context == NULL)
+ msg_Err(p_filter, "MMAL pic missing context");
+ else
+ {
+ // cast away src const so we can ref it
+ MMAL_BUFFER_HEADER_T *buf = hw_mmal_vzc_buf_from_pic(sys->vzc, (picture_t *)src,
+ &p_filter->fmt_in.video,
+ vis_mmal_rect(&p_filter->fmt_out.video),
+ x_offset, y_offset,
+ alpha,
+ dst != sys->last_dst || !hw_mmal_pic_has_sub_bufs(dst));
+ if (buf == NULL) {
+ msg_Err(p_filter, "Failed to allocate vzc buffer for subpic");
+ return;
+ }
+
+ hw_mmal_pic_sub_buf_add(dst, buf);
+
+ sys->last_dst = dst;
+ }
+}
+
+static void FlushBlendMmal(filter_t * p_filter)
+{
+ blend_sys_t * const sys = (blend_sys_t *)p_filter->p_sys;
+ sys->last_dst = NULL;
+ hw_mmal_vzc_pool_flush(sys->vzc);
+}
+
+static void CloseBlendMmal(vlc_object_t *object)
+{
+ filter_t * const p_filter = (filter_t *)object;
+ blend_sys_t * const sys = (blend_sys_t *)p_filter->p_sys;
+
+ if (sys != NULL) {
+ p_filter->p_sys = NULL;
+
+ hw_mmal_vzc_pool_release(sys->vzc);
+ cma_vcsm_exit(sys->vcsm_init_type);
+ free(sys);
+ }
+}
+
+static int OpenBlendMmal(vlc_object_t *object)
+{
+ filter_t * const p_filter = (filter_t *)object;
+ const vlc_fourcc_t vfcc_dst = p_filter->fmt_out.video.i_chroma;
+
+ if (!hw_mmal_chroma_is_mmal(vfcc_dst) ||
+ !hw_mmal_vzc_subpic_fmt_valid(&p_filter->fmt_in.video))
+ {
+ return VLC_EGENERIC;
+ }
+
+ {
+ char dbuf0[5], dbuf1[5];
+ msg_Dbg(p_filter, "%s: (%s) %s,%dx%d [(%d,%d) %dx%d]->%s,%dx%d [(%d,%d) %dx%d]", __func__,
+ "blend",
+ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma), p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height,
+ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset,
+ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height,
+ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma), p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height,
+ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset,
+ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height);
+ }
+
+ {
+ blend_sys_t * const sys = calloc(1, sizeof (*sys));
+ if (sys == NULL)
+ return VLC_ENOMEM;
+
+ p_filter->p_sys = (filter_sys_t *)sys;
+
+ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) {
+ msg_Err(p_filter, "VCSM init failed");
+ goto fail;
+ }
+
+ if ((sys->vzc = hw_mmal_vzc_pool_new()) == NULL)
+ goto fail;
+ }
+
+ p_filter->pf_video_blend = FilterBlendMmal;
+ p_filter->pf_flush = FlushBlendMmal;
+
+ return VLC_SUCCESS;
+
+fail:
+ CloseBlendMmal(VLC_OBJECT(p_filter));
+ return VLC_ENOMEM;
+}
+
+// ---------------------------------------------------------------------------
+
+static void FilterBlendNeon(filter_t *p_filter,
+ picture_t *dst_pic, const picture_t * src_pic,
+ int x_offset, int y_offset, int alpha)
+{
+ const uint8_t * s_data;
+ uint8_t * d_data;
+ const video_format_t *const src_fmt = &p_filter->fmt_in.video; // Format here has different visible params to pic
+ const video_format_t *const dst_fmt = &p_filter->fmt_out.video;
+ int width = src_fmt->i_visible_width;
+ int height = src_fmt->i_visible_height;
+ blend_neon_fn *const blend_fn = (blend_neon_fn * )p_filter->p_sys;
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, "%s (%d,%d:%d) pic=%p (%dx%d/%dx%d), pts=%lld, force=%d, filter in %dx%d/%dx%d", __func__, x_offset, y_offset, alpha,
+ src_pic, src_pic->format.i_width, src_pic->format.i_height, src_pic->format.i_visible_width, src_pic->format.i_visible_height,
+ src_pic->date, src_pic->b_force,
+ src_fmt->i_width, src_fmt->i_height, src_fmt->i_visible_width, src_fmt->i_visible_height);
+#endif
+
+ if (alpha == 0)
+ return;
+
+ x_offset += dst_fmt->i_x_offset;
+ y_offset += dst_fmt->i_y_offset;
+
+ // Deal with R/B overrun
+ if (x_offset + width >= (int)(dst_fmt->i_x_offset + dst_fmt->i_visible_width))
+ width = dst_fmt->i_x_offset + dst_fmt->i_visible_width - x_offset;
+ if (y_offset + height >= (int)(dst_fmt->i_y_offset + dst_fmt->i_visible_height))
+ height = dst_fmt->i_y_offset + dst_fmt->i_visible_height - y_offset;
+
+ if (width <= 0 || height <= 0)
+ return;
+
+ // *** L/U overrun
+
+ s_data = src_pic->p[0].p_pixels +
+ src_pic->p[0].i_pixel_pitch * src_fmt->i_x_offset +
+ src_pic->p[0].i_pitch * src_fmt->i_y_offset;
+ d_data = dst_pic->p[0].p_pixels +
+ dst_pic->p[0].i_pixel_pitch * x_offset +
+ dst_pic->p[0].i_pitch * y_offset;
+
+
+ do {
+ blend_fn(d_data, s_data, alpha, width);
+ s_data += src_pic->p[0].i_pitch;
+ d_data += dst_pic->p[0].i_pitch;
+ } while (--height > 0);
+}
+
+static void CloseBlendNeon(vlc_object_t *object)
+{
+ VLC_UNUSED(object);
+}
+
+static int OpenBlendNeon(vlc_object_t *object)
+{
+ filter_t * const p_filter = (filter_t *)object;
+ const vlc_fourcc_t vfcc_dst = p_filter->fmt_out.video.i_chroma;
+ MMAL_FOURCC_T mfcc_src = vlc_to_mmal_video_fourcc(&p_filter->fmt_in.video);
+ MMAL_FOURCC_T mfcc_dst = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video);
+ blend_neon_fn * blend_fn = (blend_neon_fn *)0;
+
+ // Obviously can't use this if we have no neon
+ if (!vlc_CPU_ARM_NEON())
+ return VLC_EGENERIC;
+
+ // Non-alpha RGB only for dest
+ if (vfcc_dst != VLC_CODEC_RGB32)
+ return VLC_EGENERIC;
+
+ // Check we have appropriate blend fn (mmal doesn't have a non-alpha RGB32)
+ switch (mfcc_src) {
+ case MMAL_ENCODING_RGBA:
+ if (mfcc_dst == MMAL_ENCODING_RGBA)
+ blend_fn = blend_rgbx_rgba_neon;
+ else if (mfcc_dst == MMAL_ENCODING_BGRA)
+ blend_fn = blend_bgrx_rgba_neon;
+ break;
+
+ case MMAL_ENCODING_BGRA:
+ if (mfcc_dst == MMAL_ENCODING_BGRA)
+ blend_fn = blend_rgbx_rgba_neon;
+ else if (mfcc_dst == MMAL_ENCODING_RGBA)
+ blend_fn = blend_bgrx_rgba_neon;
+ break;
+
+ default:
+ break;
+ }
+
+ if (blend_fn == (blend_neon_fn *)0)
+ {
+ return VLC_EGENERIC;
+ }
+
+ p_filter->p_sys = (void *)blend_fn;
+ p_filter->pf_video_blend = FilterBlendNeon;
+
+ {
+ char dbuf0[5], dbuf1[5];
+ char dbuf0a[5], dbuf1a[5];
+ msg_Dbg(p_filter, "%s: (%s) %s/%s,%dx%d [(%d,%d) %dx%d]->%s/%s,%dx%d [(%d,%d) %dx%d]", __func__,
+ "blend",
+ str_fourcc(dbuf0, p_filter->fmt_in.video.i_chroma),
+ str_fourcc(dbuf0a, mfcc_src),
+ p_filter->fmt_in.video.i_width, p_filter->fmt_in.video.i_height,
+ p_filter->fmt_in.video.i_x_offset, p_filter->fmt_in.video.i_y_offset,
+ p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height,
+ str_fourcc(dbuf1, p_filter->fmt_out.video.i_chroma),
+ str_fourcc(dbuf1a, mfcc_dst),
+ p_filter->fmt_out.video.i_width, p_filter->fmt_out.video.i_height,
+ p_filter->fmt_out.video.i_x_offset, p_filter->fmt_out.video.i_y_offset,
+ p_filter->fmt_out.video.i_visible_width, p_filter->fmt_out.video.i_visible_height);
+ }
+
+ return VLC_SUCCESS;
+}
+
+vlc_module_begin()
+ set_category( CAT_INPUT )
+ set_subcategory( SUBCAT_INPUT_VCODEC )
+ set_shortname(N_("MMAL decoder"))
+ set_description(N_("MMAL-based decoder plugin for Raspberry Pi"))
+ set_capability("video decoder", 90)
+ add_shortcut("mmal_decoder")
+ add_bool(MMAL_OPAQUE_NAME, true, MMAL_OPAQUE_TEXT, MMAL_OPAQUE_LONGTEXT, false)
+ add_bool(MMAL_DECODE_ENABLE_NAME, false, MMAL_DECODE_ENABLE_TEXT, MMAL_DECODE_ENABLE_LONGTEXT, true)
+ set_callbacks(OpenDecoder, CloseDecoder)
+
+ add_submodule()
+ set_category( CAT_VIDEO )
+ set_subcategory( SUBCAT_VIDEO_VFILTER )
+ set_shortname(N_("MMAL resizer"))
+ set_description(N_("MMAL resizing conversion filter"))
+ add_shortcut("mmal_converter")
+ set_capability( "video converter", 900 )
+ add_bool(MMAL_RESIZE_NAME, /* default */ false, MMAL_RESIZE_TEXT, MMAL_RESIZE_LONGTEXT, /* advanced option */ false)
+ add_bool(MMAL_ISP_NAME, /* default */ false, MMAL_ISP_TEXT, MMAL_ISP_LONGTEXT, /* advanced option */ false)
+ set_callbacks(OpenConverter, CloseConverter)
+
+#if OPT_TO_FROM_ZC
+ add_submodule()
+ set_category( CAT_VIDEO )
+ set_subcategory( SUBCAT_VIDEO_VFILTER )
+ set_shortname(N_("MMAL to ZC"))
+ set_description(N_("MMAL conversion to ZC filter"))
+ add_shortcut("mmal_to_zc")
+ set_capability( "video converter", 901 )
+ set_callbacks(OpenConverterToZc, CloseConverterToZc)
+
+ add_submodule()
+ set_category( CAT_VIDEO )
+ set_subcategory( SUBCAT_VIDEO_VFILTER )
+ set_shortname(N_("MMAL from ZC"))
+ set_description(N_("MMAL conversion from ZC filter"))
+ add_shortcut("mmal_from_zc")
+ set_capability( "video converter", 902 )
+ set_callbacks(OpenConverterFromZc, CloseConverterFromZc)
+#endif
+
+ add_submodule()
+ set_category( CAT_VIDEO )
+ set_subcategory( SUBCAT_VIDEO_VFILTER )
+ set_description(N_("Video pictures blending for MMAL"))
+ add_shortcut("mmal_blend")
+ set_capability("video blending", 120)
+ set_callbacks(OpenBlendMmal, CloseBlendMmal)
+
+ add_submodule()
+ set_category( CAT_VIDEO )
+ set_subcategory( SUBCAT_VIDEO_VFILTER )
+ set_description(N_("Video pictures blending for neon"))
+ add_shortcut("neon_blend")
+ set_capability("video blending", 110)
+ set_callbacks(OpenBlendNeon, CloseBlendNeon)
+
+vlc_module_end()
+
+
diff --git a/modules/hw/mmal/converter_mmal.c b/modules/hw/mmal/converter_mmal.c
new file mode 100644
index 0000000000..44333c43f8
--- /dev/null
+++ b/modules/hw/mmal/converter_mmal.c
@@ -0,0 +1,480 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include <interface/vcsm/user-vcsm.h>
+
+#include <vlc_common.h>
+#include <vlc_picture.h>
+
+#include <libdrm/drm_fourcc.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include "mmal_cma.h"
+
+#include "../../video_output/opengl/converter.h"
+
+#include "mmal_picture.h"
+
+#include <assert.h>
+
+#define TRACE_ALL 0
+
+typedef struct mmal_gl_converter_s
+{
+ EGLint drm_fourcc;
+ vcsm_init_type_t vcsm_init_type;
+ cma_buf_t * last_cb;
+
+ PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
+} mmal_gl_converter_t;
+
+
+static EGLint vlc_to_gl_fourcc(const video_format_t * const fmt)
+{
+ // Converting to mmal selects the right RGB32 varient
+ switch(vlc_to_mmal_video_fourcc(fmt))
+ {
+ case MMAL_ENCODING_I420:
+ return MMAL_FOURCC('Y','U','1','2');
+ case MMAL_ENCODING_YV12:
+ return MMAL_FOURCC('Y','V','1','2');
+ case MMAL_ENCODING_I422:
+ return MMAL_FOURCC('Y','U','1','6');
+ case MMAL_ENCODING_YUVUV128: // Doesn't actually work yet
+ case MMAL_ENCODING_NV12:
+ return MMAL_FOURCC('N','V','1','2');
+ case MMAL_ENCODING_NV21:
+ return MMAL_FOURCC('N','V','2','1');
+ case MMAL_ENCODING_RGB16:
+ return MMAL_FOURCC('R','G','1','6');
+ case MMAL_ENCODING_RGB24:
+ return MMAL_FOURCC('B','G','2','4');
+ case MMAL_ENCODING_BGR24:
+ return MMAL_FOURCC('R','G','2','4');
+ case MMAL_ENCODING_BGR32:
+ case MMAL_ENCODING_BGRA:
+ return MMAL_FOURCC('X','R','2','4');
+ case MMAL_ENCODING_RGB32:
+ case MMAL_ENCODING_RGBA:
+ return MMAL_FOURCC('X','B','2','4');
+ default:
+ break;
+ }
+ return 0;
+}
+
+typedef struct tex_context_s {
+ picture_context_t cmn;
+ GLuint texture;
+
+ PFNGLDELETETEXTURESPROC DeleteTextures; // Copy fn pointer so we don't need tc on delete
+} tex_context_t;
+
+static void tex_context_delete(tex_context_t * const tex)
+{
+ tex->DeleteTextures(1, &tex->texture);
+ free(tex);
+}
+
+static void tex_context_destroy(picture_context_t * pic_ctx)
+{
+ tex_context_delete((tex_context_t *)pic_ctx);
+}
+
+static picture_context_t * tex_context_copy(picture_context_t * pic_ctx)
+{
+ return pic_ctx;
+}
+
+static tex_context_t * get_tex_context(const opengl_tex_converter_t * const tc, picture_t * const pic, cma_buf_t * const cb)
+{
+ mmal_gl_converter_t * const sys = tc->priv;
+ tex_context_t * tex = (tex_context_t *)cma_buf_context2(cb);
+ if (tex != NULL)
+ return tex;
+
+ if ((tex = malloc(sizeof(*tex))) == NULL)
+ return NULL;
+
+ *tex = (tex_context_t){
+ .cmn = {
+ .destroy = tex_context_destroy,
+ .copy = tex_context_copy
+ },
+ .texture = 0,
+ .DeleteTextures = tc->vt->DeleteTextures
+ };
+
+ {
+ EGLint attribs[30];
+ EGLint * a = attribs;
+ const int fd = cma_buf_fd(cb);
+ uint8_t * base_addr = cma_buf_addr(cb);
+
+ if (pic->i_planes >= 4 || pic->i_planes <= 0)
+ {
+ msg_Err(tc, "%s: Bad planes: %d", __func__, pic->i_planes);
+ goto fail;
+ }
+
+ *a++ = EGL_WIDTH;
+ *a++ = pic->format.i_visible_width;
+ *a++ = EGL_HEIGHT;
+ *a++ = pic->format.i_visible_height;
+ *a++ = EGL_LINUX_DRM_FOURCC_EXT;
+ *a++ = sys->drm_fourcc;
+
+ if (pic->format.i_chroma == VLC_CODEC_MMAL_ZC_SAND8)
+ {
+ // Sand is its own very special bunny :-(
+ static const EGLint attnames[] = {
+ EGL_DMA_BUF_PLANE0_FD_EXT,
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE0_PITCH_EXT,
+ EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
+ EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE1_FD_EXT,
+ EGL_DMA_BUF_PLANE1_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE1_PITCH_EXT,
+ EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT,
+ EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT
+ };
+
+ const EGLint * n = attnames;
+
+ for (int i = 0; i < pic->i_planes; ++i)
+ {
+ const uint64_t mod = DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(pic->p[i].i_pitch);
+
+ *a++ = *n++;
+ *a++ = fd;
+ *a++ = *n++;
+ *a++ = pic->p[i].p_pixels - base_addr;
+ *a++ = *n++;
+ *a++ = pic->format.i_width;
+ *a++ = *n++;
+ *a++ = (EGLint)(mod >> 32);
+ *a++ = *n++;
+ *a++ = (EGLint)(mod & 0xffffffff);
+ }
+ }
+ else
+ {
+ static const EGLint attnames[] = {
+ EGL_DMA_BUF_PLANE0_FD_EXT,
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE0_PITCH_EXT,
+ EGL_DMA_BUF_PLANE1_FD_EXT,
+ EGL_DMA_BUF_PLANE1_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE1_PITCH_EXT,
+ EGL_DMA_BUF_PLANE2_FD_EXT,
+ EGL_DMA_BUF_PLANE2_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE2_PITCH_EXT,
+ EGL_DMA_BUF_PLANE3_FD_EXT,
+ EGL_DMA_BUF_PLANE3_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE3_PITCH_EXT
+ };
+
+ const EGLint * n = attnames;
+
+ for (int i = 0; i < pic->i_planes; ++i)
+ {
+ *a++ = *n++;
+ *a++ = fd;
+ *a++ = *n++;
+ *a++ = pic->p[i].p_pixels - base_addr;
+ *a++ = *n++;
+ *a++ = pic->p[i].i_pitch;
+ }
+ }
+
+ *a = EGL_NONE;
+
+ const EGLImage image = tc->gl->egl.createImageKHR(tc->gl, EGL_LINUX_DMA_BUF_EXT, NULL, attribs);
+ if (!image) {
+ msg_Err(tc, "Failed to import fd %d: Err=%#x", fd, tc->vt->GetError());
+ goto fail;
+ }
+
+ // ** ?? tc->tex_target
+ tc->vt->GenTextures(1, &tex->texture);
+ tc->vt->BindTexture(GL_TEXTURE_EXTERNAL_OES, tex->texture);
+ tc->vt->TexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ tc->vt->TexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ sys->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image);
+
+ tc->gl->egl.destroyImageKHR(tc->gl, image);
+ }
+
+ if (cma_buf_add_context2(cb, &tex->cmn) != VLC_SUCCESS)
+ {
+ msg_Err(tc, "%s: add_context2 failed", __func__);
+ goto fail;
+ }
+ return tex;
+
+fail:
+ tex_context_delete(tex);
+ return NULL;
+}
+
+
+static int
+tc_mmal_update(const opengl_tex_converter_t *tc, GLuint *textures,
+ const GLsizei *tex_width, const GLsizei *tex_height,
+ picture_t *pic, const size_t *plane_offset)
+{
+ mmal_gl_converter_t * const sys = tc->priv;
+#if TRACE_ALL
+ {
+ char cbuf[5];
+ msg_Dbg(tc, "%s: %s %d*%dx%d : %d*%dx%d", __func__,
+ str_fourcc(cbuf, pic->format.i_chroma),
+ tc->tex_count, tex_width[0], tex_height[0], pic->i_planes, pic->p[0].i_pitch, pic->p[0].i_lines);
+ }
+#endif
+ VLC_UNUSED(tex_width);
+ VLC_UNUSED(tex_height);
+ VLC_UNUSED(plane_offset);
+
+ if (!is_cma_buf_pic_chroma(pic->format.i_chroma))
+ {
+ char cbuf[5];
+ msg_Err(tc, "Pic with unexpected chroma: %s", str_fourcc(cbuf, pic->format.i_chroma));
+ return VLC_EGENERIC;
+ }
+
+ cma_buf_t * const cb = cma_buf_pic_get(pic);
+ if (cb == NULL)
+ {
+ msg_Err(tc, "Pic missing cma buf");
+ return VLC_EGENERIC;
+ }
+
+ tex_context_t * const tex = get_tex_context(tc, pic, cb);
+ if (tex == NULL)
+ return VLC_EGENERIC;
+
+ tc->vt->BindTexture(GL_TEXTURE_EXTERNAL_OES, tex->texture);
+
+ cma_buf_unref(sys->last_cb);
+ sys->last_cb = cma_buf_ref(cb);
+
+ textures[0] = tex->texture;
+ return VLC_SUCCESS;
+}
+
+static int
+tc_mmal_fetch_locations(opengl_tex_converter_t *tc, GLuint program)
+{
+ tc->uloc.Texture[0] = tc->vt->GetUniformLocation(program, "Texture0");
+ return tc->uloc.Texture[0] != -1 ? VLC_SUCCESS : VLC_EGENERIC;
+}
+
+static void
+tc_mmal_prepare_shader(const opengl_tex_converter_t *tc,
+ const GLsizei *tex_width, const GLsizei *tex_height,
+ float alpha)
+{
+ (void) tex_width; (void) tex_height; (void) alpha;
+ VLC_UNUSED(tc);
+// tc->vt->Uniform1i(tc->uloc.Texture[0], 0);
+}
+
+static GLuint
+tc_fragment_shader_init(opengl_tex_converter_t * const tc, const GLenum tex_target,
+ const vlc_fourcc_t chroma, const video_color_space_t yuv_space)
+{
+ VLC_UNUSED(yuv_space);
+
+ tc->tex_count = 1;
+ tc->tex_target = tex_target;
+ tc->texs[0] = (struct opengl_tex_cfg) {
+ { 1, 1 }, { 1, 1 }, GL_RGB, chroma, GL_UNSIGNED_SHORT //** ??
+ };
+
+ tc->pf_fetch_locations = tc_mmal_fetch_locations;
+ tc->pf_prepare_shader = tc_mmal_prepare_shader;
+
+
+ const char fs[] =
+ "#extension GL_OES_EGL_image_external : enable\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES Texture0;\n"
+ "varying vec2 TexCoord0;\n"
+ "void main() {\n"
+ " gl_FragColor = texture2D(Texture0, TexCoord0);\n"
+ "}\n";
+
+
+ const char *code = fs;
+
+ GLuint fragment_shader = tc->vt->CreateShader(GL_FRAGMENT_SHADER);
+ tc->vt->ShaderSource(fragment_shader, 1, &code, NULL);
+ tc->vt->CompileShader(fragment_shader);
+ return fragment_shader;
+}
+
+
+static void
+CloseGLConverter(vlc_object_t *obj)
+{
+ opengl_tex_converter_t * const tc = (opengl_tex_converter_t *)obj;
+ mmal_gl_converter_t * const sys = tc->priv;
+
+ if (sys == NULL)
+ return;
+
+ cma_buf_unref(sys->last_cb);
+ cma_vcsm_exit(sys->vcsm_init_type);
+ free(sys);
+}
+
+
+// Pick a chroma that we can convert to
+// Prefer I420 as smallest
+static vlc_fourcc_t chroma_in_out(const vlc_fourcc_t chroma_in)
+{
+ switch (chroma_in)
+ {
+ case VLC_CODEC_MMAL_OPAQUE:
+ case VLC_CODEC_MMAL_ZC_I420:
+ case VLC_CODEC_MMAL_ZC_SAND10: // ISP only
+ return VLC_CODEC_MMAL_ZC_I420;
+ case VLC_CODEC_MMAL_ZC_SAND30: // HVS only
+ case VLC_CODEC_MMAL_ZC_RGB32:
+ return VLC_CODEC_MMAL_ZC_RGB32; // HVS can't generate YUV of any sort
+ case VLC_CODEC_MMAL_ZC_SAND8:
+ return VLC_CODEC_MMAL_ZC_SAND8;
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+static int
+OpenGLConverter(vlc_object_t *obj)
+{
+ opengl_tex_converter_t * const tc = (opengl_tex_converter_t *)obj;
+ int rv = VLC_EGENERIC;
+ const EGLint eglfmt = vlc_to_gl_fourcc(&tc->fmt);
+ const vlc_fourcc_t chroma_out = chroma_in_out(tc->fmt.i_chroma);
+
+ // Do we know what to do with this?
+ if (chroma_out == 0)
+ return rv;
+
+ {
+ char dbuf0[5], dbuf1[5], dbuf2[5];
+ msg_Dbg(tc, "<<< %s: V:%s/E:%s,%dx%d [(%d,%d) %d/%d] sar:%d/%d -> %s", __func__,
+ str_fourcc(dbuf0, tc->fmt.i_chroma),
+ str_fourcc(dbuf1, eglfmt),
+ tc->fmt.i_width, tc->fmt.i_height,
+ tc->fmt.i_x_offset, tc->fmt.i_y_offset,
+ tc->fmt.i_visible_width, tc->fmt.i_visible_height,
+ tc->fmt.i_sar_num, tc->fmt.i_sar_den,
+ str_fourcc(dbuf2, chroma_out));
+ }
+
+ if (tc->gl->ext != VLC_GL_EXT_EGL ||
+ !tc->gl->egl.createImageKHR || !tc->gl->egl.destroyImageKHR)
+ {
+ // Missing an important callback
+ msg_Dbg(tc, "Missing EGL xxxImageKHR calls");
+ return rv;
+ }
+
+ if ((tc->priv = calloc(1, sizeof(mmal_gl_converter_t))) == NULL)
+ {
+ msg_Err(tc, "priv alloc failure");
+ rv = VLC_ENOMEM;
+ goto fail;
+ }
+ mmal_gl_converter_t * const sys = tc->priv;
+
+ sys->drm_fourcc = eglfmt;
+
+ if ((sys->vcsm_init_type = cma_vcsm_init()) != VCSM_INIT_CMA) {
+ msg_Dbg(tc, "VCSM init failed");
+ goto fail;
+ }
+
+ if ((sys->glEGLImageTargetTexture2DOES = vlc_gl_GetProcAddress(tc->gl, "glEGLImageTargetTexture2DOES")) == NULL)
+ {
+ msg_Err(tc, "Failed to bind GL fns");
+ goto fail;
+ }
+
+ if ((tc->fshader = tc_fragment_shader_init(tc, GL_TEXTURE_EXTERNAL_OES,
+ eglfmt == 0 ? VLC_CODEC_RGB32 : tc->fmt.i_chroma,
+ eglfmt == 0 ? COLOR_SPACE_SRGB : tc->fmt.space)) == 0)
+ {
+ msg_Err(tc, "Failed to make shader");
+ goto fail;
+ }
+
+ if (eglfmt == 0)
+ {
+ tc->fmt.i_chroma = chroma_out;
+ tc->fmt.i_bits_per_pixel = 8;
+ if (tc->fmt.i_chroma == VLC_CODEC_MMAL_ZC_RGB32)
+ {
+ tc->fmt.i_rmask = 0xff0000;
+ tc->fmt.i_gmask = 0xff00;
+ tc->fmt.i_bmask = 0xff;
+ tc->fmt.space = COLOR_SPACE_SRGB;
+ }
+ else
+ {
+ tc->fmt.i_rmask = 0;
+ tc->fmt.i_gmask = 0;
+ tc->fmt.i_bmask = 0;
+ tc->fmt.space = COLOR_SPACE_UNDEF;
+ }
+ sys->drm_fourcc = vlc_to_gl_fourcc(&tc->fmt);
+ }
+
+ tc->handle_texs_gen = true; // We manage the texs
+ tc->pf_update = tc_mmal_update;
+
+#if TRACE_ALL
+ {
+ char dbuf0[5], dbuf1[5], dbuf2[5];
+ msg_Dbg(tc, ">>> %s: V:%s/E:%s,%dx%d [(%d,%d) %d/%d] sar:%d/%d -> %s", __func__,
+ str_fourcc(dbuf0, tc->fmt.i_chroma),
+ str_fourcc(dbuf1, sys->drm_fourcc),
+ tc->fmt.i_width, tc->fmt.i_height,
+ tc->fmt.i_x_offset, tc->fmt.i_y_offset,
+ tc->fmt.i_visible_width, tc->fmt.i_visible_height,
+ tc->fmt.i_sar_num, tc->fmt.i_sar_den,
+ str_fourcc(dbuf2, chroma_out));
+ }
+#endif
+
+ return VLC_SUCCESS;
+
+fail:
+ CloseGLConverter(obj);
+ return rv;
+}
+
+vlc_module_begin ()
+ set_description("MMAL OpenGL surface converter")
+ set_shortname (N_("MMALGLConverter"))
+ set_capability("glconv", 900)
+ set_callbacks(OpenGLConverter, CloseGLConverter)
+ set_category(CAT_VIDEO)
+ set_subcategory(SUBCAT_VIDEO_VOUT)
+ add_shortcut("mmal_gl_converter")
+vlc_module_end ()
+
diff --git a/modules/hw/mmal/deinterlace.c b/modules/hw/mmal/deinterlace.c
index 4b08eee9b6..e535dbe07f 100644
--- a/modules/hw/mmal/deinterlace.c
+++ b/modules/hw/mmal/deinterlace.c
@@ -26,11 +26,12 @@
#include "config.h"
#endif
-#include <vlc_picture_pool.h>
+#include <stdatomic.h>
+
#include <vlc_common.h>
+#include <vlc_picture_pool.h>
#include <vlc_plugin.h>
#include <vlc_filter.h>
-#include <vlc_atomic.h>
#include "mmal_picture.h"
@@ -39,468 +40,814 @@
#include <interface/mmal/util/mmal_util.h>
#include <interface/mmal/util/mmal_default_components.h>
-#define MIN_NUM_BUFFERS_IN_TRANSIT 2
+#define MMAL_DEINTERLACE_NO_QPU "mmal-deinterlace-no-qpu"
+#define MMAL_DEINTERLACE_NO_QPU_TEXT N_("Do not use QPUs for advanced HD deinterlacing.")
+#define MMAL_DEINTERLACE_NO_QPU_LONGTEXT N_("Do not make use of the QPUs to allow higher quality deinterlacing of HD content.")
-#define MMAL_DEINTERLACE_QPU "mmal-deinterlace-adv-qpu"
-#define MMAL_DEINTERLACE_QPU_TEXT N_("Use QPUs for advanced HD deinterlacing.")
-#define MMAL_DEINTERLACE_QPU_LONGTEXT N_("Make use of the QPUs to allow higher quality deinterlacing of HD content.")
+#define MMAL_DEINTERLACE_ADV "mmal-deinterlace-adv"
+#define MMAL_DEINTERLACE_ADV_TEXT N_("Force advanced deinterlace")
+#define MMAL_DEINTERLACE_ADV_LONGTEXT N_("Force advanced deinterlace")
-static int Open(filter_t *filter);
-static void Close(filter_t *filter);
+#define MMAL_DEINTERLACE_FAST "mmal-deinterlace-fast"
+#define MMAL_DEINTERLACE_FAST_TEXT N_("Force fast deinterlace")
+#define MMAL_DEINTERLACE_FAST_LONGTEXT N_("Force fast deinterlace")
-vlc_module_begin()
- set_shortname(N_("MMAL deinterlace"))
- set_description(N_("MMAL-based deinterlace filter plugin"))
- set_capability("video filter", 0)
- set_category(CAT_VIDEO)
- set_subcategory(SUBCAT_VIDEO_VFILTER)
- set_callbacks(Open, Close)
- add_shortcut("deinterlace")
- add_bool(MMAL_DEINTERLACE_QPU, false, MMAL_DEINTERLACE_QPU_TEXT,
- MMAL_DEINTERLACE_QPU_LONGTEXT, true);
-vlc_module_end()
+#define MMAL_DEINTERLACE_NONE "mmal-deinterlace-none"
+#define MMAL_DEINTERLACE_NONE_TEXT N_("Force no deinterlace")
+#define MMAL_DEINTERLACE_NONE_LONGTEXT N_("Force no interlace. Simply strips off the interlace markers and passes the frame straight through. "\
+ "This is the default for > SD if < 96M gpu-mem")
+
+#define MMAL_DEINTERLACE_HALF_RATE "mmal-deinterlace-half-rate"
+#define MMAL_DEINTERLACE_HALF_RATE_TEXT N_("Halve output framerate")
+#define MMAL_DEINTERLACE_HALF_RATE_LONGTEXT N_("Halve output framerate. 1 output frame for each pair of interlaced fields input")
+
+#define MMAL_DEINTERLACE_FULL_RATE "mmal-deinterlace-full-rate"
+#define MMAL_DEINTERLACE_FULL_RATE_TEXT N_("Full output framerate")
+#define MMAL_DEINTERLACE_FULL_RATE_LONGTEXT N_("Full output framerate. 1 output frame for each interlaced field input")
-struct filter_sys_t {
+
+typedef struct filter_sys_t
+{
MMAL_COMPONENT_T *component;
MMAL_PORT_T *input;
MMAL_PORT_T *output;
+ MMAL_POOL_T *in_pool;
- MMAL_QUEUE_T *filtered_pictures;
- vlc_sem_t sem;
+ MMAL_QUEUE_T * out_q;
- atomic_bool started;
+ // Bind this lot somehow into ppr????
+ bool is_cma;
+ cma_buf_pool_t * cma_out_pool;
+ MMAL_POOL_T * out_pool;
- /* statistics */
- int output_in_transit;
- int input_in_transit;
-};
+ hw_mmal_port_pool_ref_t *out_ppr;
+
+ bool half_rate;
+ bool use_qpu;
+ bool use_fast;
+ bool use_passthrough;
+ unsigned int seq_in; // Seq of next frame to submit (1-15) [Init=1]
+ unsigned int seq_out; // Seq of last frame received (1-15) [Init=15]
+
+ vcsm_init_type_t vcsm_init_type;
+
+} filter_sys_t;
-static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
-static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
-static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
-static picture_t *deinterlace(filter_t *filter, picture_t *picture);
-static void flush(filter_t *filter);
#define MMAL_COMPONENT_DEFAULT_DEINTERLACE "vc.ril.image_fx"
-static int Open(filter_t *filter)
+#define TRACE_ALL 0
+
+
+
+// Buffer attached to pic on success, is still valid on failure
+static picture_t * di_alloc_opaque(filter_t * const p_filter, MMAL_BUFFER_HEADER_T * const buf)
{
- int32_t frame_duration = filter->fmt_in.video.i_frame_rate != 0 ?
- (int64_t)1000000 * filter->fmt_in.video.i_frame_rate_base /
- filter->fmt_in.video.i_frame_rate : 0;
- bool use_qpu = var_InheritBool(filter, MMAL_DEINTERLACE_QPU);
+ filter_sys_t *const filter_sys = p_filter->p_sys;
+ picture_t * const pic = filter_NewPicture(p_filter);
- MMAL_PARAMETER_IMAGEFX_PARAMETERS_T imfx_param = {
- { MMAL_PARAMETER_IMAGE_EFFECT_PARAMETERS, sizeof(imfx_param) },
- MMAL_PARAM_IMAGEFX_DEINTERLACE_ADV,
- 4,
- { 3, frame_duration, 0, use_qpu }
- };
+ if (pic == NULL)
+ goto fail1;
- int ret = VLC_SUCCESS;
- MMAL_STATUS_T status;
- filter_sys_t *sys;
+ if (buf->length == 0) {
+ msg_Err(p_filter, "%s: Empty buffer", __func__);
+ goto fail2;
+ }
- msg_Dbg(filter, "Try to open mmal_deinterlace filter. frame_duration: %d, QPU %s!",
- frame_duration, use_qpu ? "used" : "unused");
+ if ((pic->context = hw_mmal_gen_context(buf, filter_sys->out_ppr)) == NULL)
+ goto fail2;
- if (filter->fmt_in.video.i_chroma != VLC_CODEC_MMAL_OPAQUE)
- return VLC_EGENERIC;
+ buf_to_pic_copy_props(pic, buf);
- if (filter->fmt_out.video.i_chroma != VLC_CODEC_MMAL_OPAQUE)
- return VLC_EGENERIC;
+#if TRACE_ALL
+ msg_Dbg(p_filter, "pic: prog=%d, tff=%d, date=%lld", pic->b_progressive, pic->b_top_field_first, (long long)pic->date);
+#endif
- sys = calloc(1, sizeof(filter_sys_t));
- if (!sys)
- return VLC_ENOMEM;
- filter->p_sys = sys;
+ return pic;
- bcm_host_init();
+fail2:
+ picture_Release(pic);
+fail1:
+// mmal_buffer_header_release(buf);
+ return NULL;
+}
- status = mmal_component_create(MMAL_COMPONENT_DEFAULT_DEINTERLACE, &sys->component);
- if (status != MMAL_SUCCESS) {
- msg_Err(filter, "Failed to create MMAL component %s (status=%"PRIx32" %s)",
- MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
- }
+static void di_input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+{
+#if TRACE_ALL
+ pic_ctx_mmal_t * ctx = buffer->user_data;
+// filter_sys_t *const sys = ((filter_t *)port->userdata)->p_sys;
+
+ msg_Dbg((filter_t *)port->userdata, "<<< %s: cmd=%d, ctx=%p, buf=%p, flags=%#x, pts=%lld", __func__, buffer->cmd, ctx, buffer,
+ buffer->flags, (long long)buffer->pts);
+#else
+ VLC_UNUSED(port);
+#endif
- status = mmal_port_parameter_set(sys->component->output[0], &imfx_param.hdr);
- if (status != MMAL_SUCCESS) {
- msg_Err(filter, "Failed to configure MMAL component %s (status=%"PRIx32" %s)",
- MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
- }
+ mmal_buffer_header_release(buffer);
- sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)filter;
- status = mmal_port_enable(sys->component->control, control_port_cb);
- if (status != MMAL_SUCCESS) {
- msg_Err(filter, "Failed to enable control port %s (status=%"PRIx32" %s)",
- sys->component->control->name, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
+#if TRACE_ALL
+ msg_Dbg((filter_t *)port->userdata, ">>> %s", __func__);
+#endif
+}
+
+static void di_output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf)
+{
+ if (buf->cmd == 0 && buf->length != 0)
+ {
+ // The filter structure etc. should always exist if we have contents
+ // but might not on later flushes as we shut down
+ filter_t * const p_filter = (filter_t *)port->userdata;
+ filter_sys_t * const sys = p_filter->p_sys;
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, "<<< %s: cmd=%d; flags=%#x, pts=%lld", __func__, buf->cmd, buf->flags, (long long) buf->pts);
+#endif
+ mmal_queue_put(sys->out_q, buf);
+#if TRACE_ALL
+ msg_Dbg(p_filter, ">>> %s: out Q len=%d", __func__, mmal_queue_length(sys->out_q));
+#endif
+ return;
}
- sys->input = sys->component->input[0];
- sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)filter;
- if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE)
- sys->input->format->encoding = MMAL_ENCODING_OPAQUE;
- sys->input->format->es->video.width = filter->fmt_in.video.i_width;
- sys->input->format->es->video.height = filter->fmt_in.video.i_height;
- sys->input->format->es->video.crop.x = 0;
- sys->input->format->es->video.crop.y = 0;
- sys->input->format->es->video.crop.width = filter->fmt_in.video.i_width;
- sys->input->format->es->video.crop.height = filter->fmt_in.video.i_height;
- sys->input->format->es->video.par.num = filter->fmt_in.video.i_sar_num;
- sys->input->format->es->video.par.den = filter->fmt_in.video.i_sar_den;
+ mmal_buffer_header_reset(buf); // User data stays intact so release will kill pic
+ mmal_buffer_header_release(buf);
+}
- es_format_Copy(&filter->fmt_out, &filter->fmt_in);
- filter->fmt_out.video.i_frame_rate *= 2;
- status = mmal_port_format_commit(sys->input);
- if (status != MMAL_SUCCESS) {
- msg_Err(filter, "Failed to commit format for input port %s (status=%"PRIx32" %s)",
- sys->input->name, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
+
+static MMAL_STATUS_T fill_output_from_q(filter_t * const p_filter, filter_sys_t * const sys, MMAL_QUEUE_T * const q)
+{
+ MMAL_BUFFER_HEADER_T * out_buf;
+
+ while ((out_buf = mmal_queue_get(q)) != NULL)
+ {
+ MMAL_STATUS_T err;
+ if ((err = mmal_port_send_buffer(sys->output, out_buf)) != MMAL_SUCCESS)
+ {
+ msg_Err(p_filter, "Send buffer to output failed");
+ mmal_queue_put_back(q, out_buf);
+ return err;
+ }
}
- sys->input->buffer_size = sys->input->buffer_size_recommended;
- sys->input->buffer_num = sys->input->buffer_num_recommended;
+ return MMAL_SUCCESS;
+}
- if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE) {
- MMAL_PARAMETER_BOOLEAN_T zero_copy = {
- { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) },
- 1
- };
+// Output buffers may contain a pic ref on error or flush
+// Free it
+static MMAL_BOOL_T out_buffer_pre_release_cb(MMAL_BUFFER_HEADER_T *header, void *userdata)
+{
+ VLC_UNUSED(userdata);
- status = mmal_port_parameter_set(sys->input, &zero_copy.hdr);
- if (status != MMAL_SUCCESS) {
- msg_Err(filter, "Failed to set zero copy on port %s (status=%"PRIx32" %s)",
- sys->input->name, status, mmal_status_to_string(status));
- goto out;
- }
+ cma_buf_t * const cb = header->user_data;
+ header->user_data = NULL;
+ cma_buf_unref(cb); // Copes fine with NULL
+
+ return MMAL_FALSE;
+}
+
+static inline unsigned int seq_inc(unsigned int x)
+{
+ return x + 1 >= 16 ? 1 : x + 1;
+}
+
+static inline unsigned int seq_delta(unsigned int sseq, unsigned int fseq)
+{
+ return fseq == 0 ? 0 : fseq <= sseq ? sseq - fseq : 15 - (fseq - sseq);
+}
+
+static picture_t *deinterlace(filter_t * p_filter, picture_t * p_pic)
+{
+ filter_sys_t * const sys = p_filter->p_sys;
+ picture_t *ret_pics = NULL;
+ MMAL_STATUS_T err;
+ MMAL_BUFFER_HEADER_T * out_buf = NULL;
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, "<<< %s", __func__);
+#endif
+
+ if (hw_mmal_vlc_pic_to_mmal_fmt_update(sys->input->format, p_pic))
+ {
+ // ****** Breaks on opaque (at least)
+
+ if (sys->input->is_enabled)
+ mmal_port_disable(sys->input);
+#if 0
+ if (sys->output->is_enabled)
+ mmal_port_disable(sys->output);
+
+ mmal_format_full_copy(sys->output->format, sys->input->format);
+ mmal_port_format_commit(sys->output);
+ sys->output->buffer_num = 30;
+ sys->output->buffer_size = sys->input->buffer_size_recommended;
+ mmal_port_enable(sys->output, di_output_port_cb);
+#endif
+ if (mmal_port_format_commit(sys->input) != MMAL_SUCCESS)
+ msg_Err(p_filter, "Failed to update pic format");
+ sys->input->buffer_num = 30;
+ sys->input->buffer_size = sys->input->buffer_size_recommended;
+ mmal_log_dump_format(sys->input->format);
}
- status = mmal_port_enable(sys->input, input_port_cb);
- if (status != MMAL_SUCCESS) {
- msg_Err(filter, "Failed to enable input port %s (status=%"PRIx32" %s)",
- sys->input->name, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
+ // Reenable stuff if the last thing we did was flush
+ // Output should always be enabled
+ if (!sys->input->is_enabled &&
+ (err = mmal_port_enable(sys->input, di_input_port_cb)) != MMAL_SUCCESS)
+ {
+ msg_Err(p_filter, "Input port reenable failed");
+ goto fail;
}
- sys->output = sys->component->output[0];
- sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)filter;
- mmal_format_full_copy(sys->output->format, sys->input->format);
+ if (!sys->is_cma)
+ {
+ // Fill output from anything that has turned up in pool Q
+ if (hw_mmal_port_pool_ref_fill(sys->out_ppr) != MMAL_SUCCESS)
+ {
+ msg_Err(p_filter, "Out port fill fail");
+ goto fail;
+ }
+ }
+ else
+ {
+ // We are expecting one in - one out so simply wedge a new bufer
+ // into the output port. Flow control will happen on cma alloc.
+
+ if ((out_buf = mmal_queue_get(sys->out_pool->queue)) == NULL)
+ {
+ // Should never happen
+ msg_Err(p_filter, "Failed to get output buffer");
+ goto fail;
+ }
+ mmal_buffer_header_reset(out_buf);
+
+ // Attach cma_buf to the buffer & ensure it is freed when the buffer is released
+ // On a good send callback the pic will be extracted to avoid this
+ mmal_buffer_header_pre_release_cb_set(out_buf, out_buffer_pre_release_cb, p_filter);
+
+ cma_buf_t * const cb = cma_buf_pool_alloc_buf(sys->cma_out_pool, sys->output->buffer_size);
+ if ((out_buf->user_data = cb) == NULL) // Check & attach cb to buf
+ {
+ char dbuf0[5];
+ msg_Err(p_filter, "Failed to alloc CMA buf: fmt=%s, size=%d",
+ str_fourcc(dbuf0, p_pic->format.i_chroma),
+ sys->output->buffer_size);
+ goto fail;
+ }
+ const unsigned int vc_h = cma_buf_vc_handle(cb); // Cannot coerce without going via variable
+ out_buf->data = (uint8_t *)vc_h;
+ out_buf->alloc_size = sys->output->buffer_size;
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, "Out buf send: pic=%p, data=%p, user=%p, flags=%#x, len=%d/%d, pts=%lld",
+ p_pic, out_buf->data, out_buf->user_data, out_buf->flags,
+ out_buf->length, out_buf->alloc_size, (long long)out_buf->pts);
+#endif
- status = mmal_port_format_commit(sys->output);
- if (status != MMAL_SUCCESS) {
- msg_Err(filter, "Failed to commit format for output port %s (status=%"PRIx32" %s)",
- sys->input->name, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
+ if ((err = mmal_port_send_buffer(sys->output, out_buf)) != MMAL_SUCCESS)
+ {
+ msg_Err(p_filter, "Send buffer to output failed");
+ goto fail;
+ }
+ out_buf = NULL;
}
- sys->output->buffer_num = 3;
+ // Stuff into input
+ // We assume the BH is already set up with values reflecting pic date etc.
+ {
+ MMAL_BUFFER_HEADER_T * const pic_buf = hw_mmal_pic_buf_replicated(p_pic, sys->in_pool);
- if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE) {
- MMAL_PARAMETER_UINT32_T extra_buffers = {
- { MMAL_PARAMETER_EXTRA_BUFFERS, sizeof(MMAL_PARAMETER_UINT32_T) },
- 5
- };
- status = mmal_port_parameter_set(sys->output, &extra_buffers.hdr);
- if (status != MMAL_SUCCESS) {
- msg_Err(filter, "Failed to set MMAL_PARAMETER_EXTRA_BUFFERS on output port (status=%"PRIx32" %s)",
- status, mmal_status_to_string(status));
- goto out;
+ if (pic_buf == NULL)
+ {
+ msg_Err(p_filter, "Pic has not attached buffer");
+ goto fail;
}
- MMAL_PARAMETER_BOOLEAN_T zero_copy = {
- { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) },
- 1
- };
+ picture_Release(p_pic);
- status = mmal_port_parameter_set(sys->output, &zero_copy.hdr);
- if (status != MMAL_SUCCESS) {
- msg_Err(filter, "Failed to set zero copy on port %s (status=%"PRIx32" %s)",
- sys->output->name, status, mmal_status_to_string(status));
- goto out;
+ // Add a sequence to the flags so we can track what we have actually
+ // deinterlaced
+ pic_buf->flags = (pic_buf->flags & ~(0xfU * MMAL_BUFFER_HEADER_FLAG_USER0)) | (sys->seq_in * (MMAL_BUFFER_HEADER_FLAG_USER0));
+ sys->seq_in = seq_inc(sys->seq_in);
+
+ if ((err = mmal_port_send_buffer(sys->input, pic_buf)) != MMAL_SUCCESS)
+ {
+ msg_Err(p_filter, "Send buffer to input failed");
+ mmal_buffer_header_release(pic_buf);
+ goto fail;
}
}
- status = mmal_port_enable(sys->output, output_port_cb);
- if (status != MMAL_SUCCESS) {
- msg_Err(filter, "Failed to enable output port %s (status=%"PRIx32" %s)",
- sys->output->name, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
+ // Return anything that is in the out Q
+ {
+ picture_t ** pp_pic = &ret_pics;
+
+ // Advanced di has a 3 frame latency, so if the seq delta is greater
+ // than that then we are expecting at least two frames of output. Wait
+ // for one of those.
+ // seq_in is seq of the next frame we are going to submit (1-15, no 0)
+ // seq_out is last frame we removed from Q
+ // So after 4 frames sent (1st time we want to wait), 0 rx seq_in=5, seq_out=15, delta=5
+
+ while ((out_buf = (seq_delta(sys->seq_in, sys->seq_out) >= 5 ? mmal_queue_timedwait(sys->out_q, 1000) : mmal_queue_get(sys->out_q))) != NULL)
+ {
+ const unsigned int seq_out = (out_buf->flags / MMAL_BUFFER_HEADER_FLAG_USER0) & 0xf;
+ int rv;
+
+ picture_t * out_pic;
+
+ if (sys->is_cma)
+ {
+ // Alloc pic
+ if ((out_pic = filter_NewPicture(p_filter)) == NULL)
+ {
+ // Can't alloc pic - just stop extraction
+ mmal_queue_put_back(sys->out_q, out_buf);
+ out_buf = NULL;
+ msg_Warn(p_filter, "Failed to alloc new filter output pic");
+ break;
+ }
+
+ // Extract cma_buf from buf & attach to pic
+ cma_buf_t * const cb = (cma_buf_t *)out_buf->user_data;
+ if ((rv = cma_buf_pic_attach(cb, out_pic)) != VLC_SUCCESS)
+ {
+ char dbuf0[5];
+ msg_Err(p_filter, "Failed to attach CMA to pic: fmt=%s err=%d",
+ str_fourcc(dbuf0, out_pic->format.i_chroma),
+ rv);
+ // cb still attached to buffer and will be freed with it
+ goto fail;
+ }
+ out_buf->user_data = NULL;
+
+ buf_to_pic_copy_props(out_pic, out_buf);
+
+ // Set pic data pointers from buf aux info now it has it
+ if ((rv = cma_pic_set_data(out_pic, sys->output->format, out_buf)) != VLC_SUCCESS)
+ {
+ char dbuf0[5];
+ msg_Err(p_filter, "Failed to set data: fmt=%s, rv=%d",
+ str_fourcc(dbuf0, sys->output->format->encoding),
+ rv);
+ }
+
+ out_buf->user_data = NULL; // Responsability for this pic no longer with buffer
+ mmal_buffer_header_release(out_buf);
+ }
+ else
+ {
+ out_pic = di_alloc_opaque(p_filter, out_buf);
+
+ if (out_pic == NULL) {
+ msg_Warn(p_filter, "Failed to alloc new filter output pic");
+ mmal_queue_put_back(sys->out_q, out_buf); // Wedge buf back into Q in the hope we can alloc a pic later
+ out_buf = NULL;
+ break;
+ }
+ }
+ out_buf = NULL; // Now attached to pic or recycled
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, "-- %s: Q pic=%p: seq_in=%d, seq_out=%d, delta=%d", __func__, out_pic, sys->seq_in, seq_out, seq_delta(sys->seq_in, seq_out));
+#endif
+
+ *pp_pic = out_pic;
+ pp_pic = &out_pic->p_next;
+
+ // Ignore 0 seqs
+ // Don't think these should actually happen
+ if (seq_out != 0)
+ sys->seq_out = seq_out;
+ }
+
+ // Crash on lockup
+ assert(ret_pics != NULL || seq_delta(sys->seq_in, sys->seq_out) < 5);
}
- status = mmal_component_enable(sys->component);
- if (status != MMAL_SUCCESS) {
- msg_Err(filter, "Failed to enable component %s (status=%"PRIx32" %s)",
- sys->component->name, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
+#if TRACE_ALL
+ msg_Dbg(p_filter, ">>> %s: pic=%p", __func__, ret_pics);
+#endif
+
+ return ret_pics;
+
+fail:
+ if (out_buf != NULL)
+ mmal_buffer_header_release(out_buf);
+ picture_Release(p_pic);
+ return NULL;
+}
+
+static void di_flush(filter_t *p_filter)
+{
+ filter_sys_t * const sys = p_filter->p_sys;
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, "<<< %s", __func__);
+#endif
+
+ if (sys->input != NULL && sys->input->is_enabled)
+ mmal_port_disable(sys->input);
+
+ if (sys->output != NULL && sys->output->is_enabled)
+ {
+ if (sys->is_cma)
+ {
+ MMAL_BUFFER_HEADER_T * buf;
+ mmal_port_disable(sys->output);
+ while ((buf = mmal_queue_get(sys->out_q)) != NULL)
+ mmal_buffer_header_release(buf);
+ }
+ else
+ {
+ // Wedge anything we've got into the output port as that will free the underlying buffers
+ fill_output_from_q(p_filter, sys, sys->out_q);
+
+ mmal_port_disable(sys->output);
+
+ // If that dumped anything real into the out_q then have another go
+ if (mmal_queue_length(sys->out_q) != 0)
+ {
+ mmal_port_enable(sys->output, di_output_port_cb);
+ fill_output_from_q(p_filter, sys, sys->out_q);
+ mmal_port_disable(sys->output);
+ // Out q should now be empty & should remain so until the input is reenabled
+ }
+ }
+ mmal_port_enable(sys->output, di_output_port_cb);
+
+ // Leaving the input disabled is fine - but we want to leave the output enabled
+ // so we can retrieve buffers that are still bound to pictures
}
- sys->filtered_pictures = mmal_queue_create();
+ sys->seq_in = 1;
+ sys->seq_out = 15;
- filter->pf_video_filter = deinterlace;
- filter->pf_flush = flush;
+#if TRACE_ALL
+ msg_Dbg(p_filter, ">>> %s", __func__);
+#endif
+}
- vlc_sem_init(&sys->sem, 0);
-out:
- if (ret != VLC_SUCCESS)
- Close(filter);
+static void pass_flush(filter_t *p_filter)
+{
+ // Nothing to do
+ VLC_UNUSED(p_filter);
+}
- return ret;
+static picture_t * pass_deinterlace(filter_t * p_filter, picture_t * p_pic)
+{
+ VLC_UNUSED(p_filter);
+
+ p_pic->b_progressive = true;
+ return p_pic;
}
-static void Close(filter_t *filter)
+
+static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
{
- filter_sys_t *sys = filter->p_sys;
- MMAL_BUFFER_HEADER_T *buffer;
+ filter_t *filter = (filter_t *)port->userdata;
+ MMAL_STATUS_T status;
- if (!sys)
+ if (buffer->cmd == MMAL_EVENT_ERROR) {
+ status = *(uint32_t *)buffer->data;
+ msg_Err(filter, "MMAL error %"PRIx32" \"%s\"", status,
+ mmal_status_to_string(status));
+ }
+
+ mmal_buffer_header_reset(buffer);
+ mmal_buffer_header_release(buffer);
+}
+
+static void CloseMmalDeinterlace(filter_t *filter)
+{
+ filter_sys_t * const sys = filter->p_sys;
+
+#if TRACE_ALL
+ msg_Dbg(filter, "<<< %s", __func__);
+#endif
+
+ if (sys == NULL)
return;
- if (sys->component && sys->component->control->is_enabled)
- mmal_port_disable(sys->component->control);
+ if (sys->use_passthrough)
+ {
+ free(sys);
+ return;
+ }
- if (sys->input && sys->input->is_enabled)
- mmal_port_disable(sys->input);
+ di_flush(filter);
- if (sys->output && sys->output->is_enabled)
- mmal_port_disable(sys->output);
+ if (sys->component && sys->component->control->is_enabled)
+ mmal_port_disable(sys->component->control);
if (sys->component && sys->component->is_enabled)
mmal_component_disable(sys->component);
- while ((buffer = mmal_queue_get(sys->filtered_pictures))) {
- picture_t *pic = (picture_t *)buffer->user_data;
- picture_Release(pic);
+ if (sys->in_pool != NULL)
+ mmal_pool_destroy(sys->in_pool);
+
+ hw_mmal_port_pool_ref_release(sys->out_ppr, false);
+ // Once we exit filter & sys are invalid so mark as such
+ if (sys->output != NULL)
+ sys->output->userdata = NULL;
+
+ if (sys->is_cma)
+ {
+ if (sys->output && sys->output->is_enabled)
+ mmal_port_disable(sys->output);
+
+ cma_buf_pool_deletez(&sys->cma_out_pool);
+
+ if (sys->out_pool != NULL)
+ mmal_pool_destroy(sys->out_pool);
}
- if (sys->filtered_pictures)
- mmal_queue_destroy(sys->filtered_pictures);
+ if (sys->out_q != NULL)
+ mmal_queue_destroy(sys->out_q);
if (sys->component)
mmal_component_release(sys->component);
- vlc_sem_destroy(&sys->sem);
+ cma_vcsm_exit(sys->vcsm_init_type);
+
free(sys);
+}
- bcm_host_deinit();
+
+static bool is_fmt_valid_in(const vlc_fourcc_t fmt)
+{
+ return fmt == VLC_CODEC_MMAL_OPAQUE ||
+ fmt == VLC_CODEC_MMAL_ZC_I420 ||
+ fmt == VLC_CODEC_MMAL_ZC_SAND8;
}
-static int send_output_buffer(filter_t *filter)
+static int OpenMmalDeinterlace(filter_t *filter)
{
- filter_sys_t *sys = filter->p_sys;
- MMAL_BUFFER_HEADER_T *buffer;
+ int32_t frame_duration = filter->fmt_in.video.i_frame_rate != 0 ?
+ CLOCK_FREQ * filter->fmt_in.video.i_frame_rate_base /
+ filter->fmt_in.video.i_frame_rate : 0;
+
+ int ret = VLC_EGENERIC;
MMAL_STATUS_T status;
- picture_t *picture;
- int ret = 0;
+ filter_sys_t *sys;
- if (!sys->output->is_enabled) {
- ret = VLC_EGENERIC;
- goto out;
- }
+ msg_Dbg(filter, "<<< %s", __func__);
- picture = filter_NewPicture(filter);
- if (!picture) {
- msg_Warn(filter, "Failed to get new picture");
- ret = -1;
- goto out;
- }
- picture->format.i_frame_rate = filter->fmt_out.video.i_frame_rate;
- picture->format.i_frame_rate_base = filter->fmt_out.video.i_frame_rate_base;
+ if (!is_fmt_valid_in(filter->fmt_in.video.i_chroma) ||
+ filter->fmt_out.video.i_chroma != filter->fmt_in.video.i_chroma)
+ return VLC_EGENERIC;
- buffer = picture->p_sys->buffer;
- buffer->user_data = picture;
- buffer->cmd = 0;
+ sys = calloc(1, sizeof(filter_sys_t));
+ if (!sys)
+ return VLC_ENOMEM;
+ filter->p_sys = sys;
- mmal_picture_lock(picture);
+ sys->seq_in = 1;
+ sys->seq_out = 15;
+ sys->is_cma = is_cma_buf_pic_chroma(filter->fmt_out.video.i_chroma);
- status = mmal_port_send_buffer(sys->output, buffer);
- if (status != MMAL_SUCCESS) {
- msg_Err(filter, "Failed to send buffer to output port (status=%"PRIx32" %s)",
- status, mmal_status_to_string(status));
- mmal_buffer_header_release(buffer);
- picture_Release(picture);
- ret = -1;
- } else {
- atomic_fetch_add(&sys->output_in_transit, 1);
- vlc_sem_post(&sys->sem);
+ if ((sys->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE) {
+ msg_Err(filter, "VCSM init failed");
+ goto fail;
}
-out:
- return ret;
-}
+ if (!rpi_use_qpu_deinterlace())
+ {
+ sys->half_rate = true;
+ sys->use_qpu = false;
+ sys->use_fast = true;
+ }
+ else
+ {
+ sys->half_rate = false;
+ sys->use_qpu = true;
+ sys->use_fast = false;
+ }
+ sys->use_passthrough = false;
+
+ if (filter->fmt_in.video.i_width * filter->fmt_in.video.i_height > 768 * 576)
+ {
+ // We get stressed if we have to try too hard - so make life easier
+ sys->half_rate = true;
+ // Also check we actually have enough memory to do this
+ // Memory always comes from GPU if Opaque
+ // Assume we have plenty of memory if it comes from CMA
+ if ((!sys->is_cma || sys->vcsm_init_type == VCSM_INIT_LEGACY) &&
+ hw_mmal_get_gpu_mem() < (96 << 20))
+ {
+ sys->use_passthrough = true;
+ msg_Warn(filter, "Deinterlace bypassed due to lack of GPU memory");
+ }
+ }
-static void fill_output_port(filter_t *filter)
-{
- filter_sys_t *sys = filter->p_sys;
- /* allow at least 2 buffers in transit */
- unsigned max_buffers_in_transit = __MAX(2, MIN_NUM_BUFFERS_IN_TRANSIT);
- int buffers_available = sys->output->buffer_num -
- atomic_load(&sys->output_in_transit) -
- mmal_queue_length(sys->filtered_pictures);
- int buffers_to_send = max_buffers_in_transit - sys->output_in_transit;
- int i;
-
- if (buffers_to_send > buffers_available)
- buffers_to_send = buffers_available;
-
-#ifndef NDEBUG
- msg_Dbg(filter, "Send %d buffers to output port (available: %d, in_transit: %d, buffer_num: %d)",
- buffers_to_send, buffers_available, sys->output_in_transit,
- sys->output->buffer_num);
-#endif
- for (i = 0; i < buffers_to_send; ++i) {
- if (send_output_buffer(filter) < 0)
- break;
+ if (var_InheritBool(filter, MMAL_DEINTERLACE_NO_QPU))
+ sys->use_qpu = false;
+ if (var_InheritBool(filter, MMAL_DEINTERLACE_ADV))
+ {
+ sys->use_fast = false;
+ sys->use_passthrough = false;
+ }
+ if (var_InheritBool(filter, MMAL_DEINTERLACE_FAST))
+ {
+ sys->use_fast = true;
+ sys->use_passthrough = false;
+ }
+ if (var_InheritBool(filter, MMAL_DEINTERLACE_NONE))
+ sys->use_passthrough = true;
+ if (var_InheritBool(filter, MMAL_DEINTERLACE_FULL_RATE))
+ sys->half_rate = false;
+ if (var_InheritBool(filter, MMAL_DEINTERLACE_HALF_RATE))
+ sys->half_rate = true;
+
+ if (sys->use_passthrough)
+ {
+ filter->pf_video_filter = pass_deinterlace;
+ filter->pf_flush = pass_flush;
+ // Don't need VCSM - get rid of it now
+ cma_vcsm_exit(sys->vcsm_init_type);
+ sys->vcsm_init_type = VCSM_INIT_NONE;
+ return 0;
}
-}
-static picture_t *deinterlace(filter_t *filter, picture_t *picture)
-{
- filter_sys_t *sys = filter->p_sys;
- MMAL_BUFFER_HEADER_T *buffer;
- picture_t *out_picture = NULL;
- picture_t *ret = NULL;
- MMAL_STATUS_T status;
- unsigned i = 0;
+ {
+ char dbuf0[5], dbuf1[5];
+ msg_Dbg(filter, "%s: %s,%dx%d [(%d,%d) %d/%d] -> %s,%dx%d [(%d,%d) %dx%d]: %s %s %s", __func__,
+ str_fourcc(dbuf0, filter->fmt_in.video.i_chroma),
+ filter->fmt_in.video.i_width, filter->fmt_in.video.i_height,
+ filter->fmt_in.video.i_x_offset, filter->fmt_in.video.i_y_offset,
+ filter->fmt_in.video.i_visible_width, filter->fmt_in.video.i_visible_height,
+ str_fourcc(dbuf1, filter->fmt_out.video.i_chroma),
+ filter->fmt_out.video.i_width, filter->fmt_out.video.i_height,
+ filter->fmt_out.video.i_x_offset, filter->fmt_out.video.i_y_offset,
+ filter->fmt_out.video.i_visible_width, filter->fmt_out.video.i_visible_height,
+ sys->use_qpu ? "QPU" : "VPU",
+ sys->use_fast ? "FAST" : "ADV",
+ sys->use_passthrough ? "PASS" : sys->half_rate ? "HALF" : "FULL");
+ }
- fill_output_port(filter);
+ status = mmal_component_create(MMAL_COMPONENT_DEFAULT_DEINTERLACE, &sys->component);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(filter, "Failed to create MMAL component %s (status=%"PRIx32" %s)",
+ MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status));
+ goto fail;
+ }
- buffer = picture->p_sys->buffer;
- buffer->user_data = picture;
- buffer->pts = picture->date;
- buffer->cmd = 0;
+ {
+ const MMAL_PARAMETER_IMAGEFX_PARAMETERS_T imfx_param = {
+ { MMAL_PARAMETER_IMAGE_EFFECT_PARAMETERS, sizeof(imfx_param) },
+ sys->use_fast ?
+ MMAL_PARAM_IMAGEFX_DEINTERLACE_FAST :
+ MMAL_PARAM_IMAGEFX_DEINTERLACE_ADV,
+ 4,
+ { 5 /* Frame type: mixed */, frame_duration, sys->half_rate, sys->use_qpu }
+ };
- if (!picture->p_sys->displayed) {
- status = mmal_port_send_buffer(sys->input, buffer);
+ status = mmal_port_parameter_set(sys->component->output[0], &imfx_param.hdr);
if (status != MMAL_SUCCESS) {
- msg_Err(filter, "Failed to send buffer to input port (status=%"PRIx32" %s)",
- status, mmal_status_to_string(status));
- picture_Release(picture);
- } else {
- picture->p_sys->displayed = true;
- atomic_fetch_add(&sys->input_in_transit, 1);
- vlc_sem_post(&sys->sem);
- }
- } else {
- picture_Release(picture);
- }
-
- /*
- * Send output buffers
- */
- while(atomic_load(&sys->started) && i < 2) {
- if (buffer = mmal_queue_timedwait(sys->filtered_pictures, 2000)) {
- i++;
- if (!out_picture) {
- out_picture = (picture_t *)buffer->user_data;
- ret = out_picture;
- } else {
- out_picture->p_next = (picture_t *)buffer->user_data;
- out_picture = out_picture->p_next;
- }
- out_picture->date = buffer->pts;
- } else {
- msg_Dbg(filter, "Failed waiting for filtered picture");
- break;
+ msg_Err(filter, "Failed to configure MMAL component %s (status=%"PRIx32" %s)",
+ MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status));
+ goto fail;
}
}
- if (out_picture)
- out_picture->p_next = NULL;
-
- return ret;
-}
-static void flush(filter_t *filter)
-{
- filter_sys_t *sys = filter->p_sys;
- MMAL_BUFFER_HEADER_T *buffer;
+ sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)filter;
+ status = mmal_port_enable(sys->component->control, control_port_cb);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(filter, "Failed to enable control port %s (status=%"PRIx32" %s)",
+ sys->component->control->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
- msg_Dbg(filter, "flush deinterlace filter");
+ sys->input = sys->component->input[0];
+ sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)filter;
+ sys->input->format->encoding = vlc_to_mmal_video_fourcc(&filter->fmt_in.video);
+ hw_mmal_vlc_fmt_to_mmal_fmt(sys->input->format, &filter->fmt_in.video);
- msg_Dbg(filter, "flush: flush ports (input: %d, output: %d in transit)",
- sys->input_in_transit, sys->output_in_transit);
- mmal_port_flush(sys->output);
- mmal_port_flush(sys->input);
+ es_format_Copy(&filter->fmt_out, &filter->fmt_in);
+ if (!sys->half_rate)
+ filter->fmt_out.video.i_frame_rate *= 2;
- msg_Dbg(filter, "flush: wait for all buffers to be returned");
- while (atomic_load(&sys->input_in_transit) ||
- atomic_load(&sys->output_in_transit))
- vlc_sem_wait(&sys->sem);
+ status = mmal_port_format_commit(sys->input);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(filter, "Failed to commit format for input port %s (status=%"PRIx32" %s)",
+ sys->input->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
+ sys->input->buffer_size = sys->input->buffer_size_recommended;
+ sys->input->buffer_num = 30;
+// sys->input->buffer_num = sys->input->buffer_num_recommended;
- while ((buffer = mmal_queue_get(sys->filtered_pictures))) {
- picture_t *pic = (picture_t *)buffer->user_data;
- msg_Dbg(filter, "flush: release already filtered pic %p",
- (void *)pic);
- picture_Release(pic);
+ if ((sys->in_pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL)
+ {
+ msg_Err(filter, "Failed to create input pool");
+ goto fail;
}
- atomic_store(&sys->started, false);
- msg_Dbg(filter, "flush: done");
-}
-static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
-{
- filter_t *filter = (filter_t *)port->userdata;
- MMAL_STATUS_T status;
+ status = port_parameter_set_bool(sys->input, MMAL_PARAMETER_ZERO_COPY, true);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(filter, "Failed to set zero copy on port %s (status=%"PRIx32" %s)",
+ sys->input->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
- if (buffer->cmd == MMAL_EVENT_ERROR) {
- status = *(uint32_t *)buffer->data;
- msg_Err(filter, "MMAL error %"PRIx32" \"%s\"", status,
- mmal_status_to_string(status));
+ status = mmal_port_enable(sys->input, di_input_port_cb);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(filter, "Failed to enable input port %s (status=%"PRIx32" %s)",
+ sys->input->name, status, mmal_status_to_string(status));
+ goto fail;
}
- mmal_buffer_header_release(buffer);
-}
-static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
-{
- picture_t *picture = (picture_t *)buffer->user_data;
- filter_t *filter = (filter_t *)port->userdata;
- filter_sys_t *sys = filter->p_sys;
+ if ((sys->out_q = mmal_queue_create()) == NULL)
+ {
+ msg_Err(filter, "Failed to create out Q");
+ goto fail;
+ }
+
+ sys->output = sys->component->output[0];
+ mmal_format_full_copy(sys->output->format, sys->input->format);
- if (picture) {
- picture_Release(picture);
- } else {
- msg_Warn(filter, "Got buffer without picture on input port - OOOPS");
- mmal_buffer_header_release(buffer);
+ if (!sys->is_cma)
+ {
+ if ((status = hw_mmal_opaque_output(VLC_OBJECT(filter), &sys->out_ppr, sys->output, 5, di_output_port_cb)) != MMAL_SUCCESS)
+ goto fail;
}
+ else
+ {
+ // CMA stuff
+ sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)filter;
+
+ if ((sys->cma_out_pool = cma_buf_pool_new(8, 8, true, "deinterlace")) == NULL)
+ {
+ msg_Err(filter, "Failed to alloc cma buf pool");
+ goto fail;
+ }
- atomic_fetch_sub(&sys->input_in_transit, 1);
- vlc_sem_post(&sys->sem);
-}
+ // Rate control done by CMA in flight logic, so have "inexhaustable" pool here
+ if ((sys->out_pool = mmal_pool_create(30, 0)) == NULL)
+ {
+ msg_Err(filter, "Failed to alloc out pool");
+ goto fail;
+ }
-static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
-{
- filter_t *filter = (filter_t *)port->userdata;
- filter_sys_t *sys = filter->p_sys;
- picture_t *picture;
-
- if (buffer->cmd == 0) {
- if (buffer->length > 0) {
- atomic_store(&sys->started, true);
- mmal_queue_put(sys->filtered_pictures, buffer);
- picture = (picture_t *)buffer->user_data;
- } else {
- picture = (picture_t *)buffer->user_data;
- picture_Release(picture);
+ port_parameter_set_bool(sys->output, MMAL_PARAMETER_ZERO_COPY, true);
+
+ if ((status = mmal_port_format_commit(sys->output)) != MMAL_SUCCESS)
+ {
+ msg_Err(filter, "Output port format commit failed");
+ goto fail;
}
- atomic_fetch_sub(&sys->output_in_transit, 1);
- vlc_sem_post(&sys->sem);
- } else if (buffer->cmd == MMAL_EVENT_FORMAT_CHANGED) {
- msg_Warn(filter, "MMAL_EVENT_FORMAT_CHANGED seen but not handled");
- mmal_buffer_header_release(buffer);
- } else {
- mmal_buffer_header_release(buffer);
+ sys->output->buffer_num = 30;
+ sys->output->buffer_size = sys->output->buffer_size_recommended;
+
+ // CB just drops all bufs into out_q
+ if ((status = mmal_port_enable(sys->output, di_output_port_cb)) != MMAL_SUCCESS)
+ {
+ msg_Err(filter, "Failed to enable output port %s (status=%"PRIx32" %s)",
+ sys->output->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
}
+
+ status = mmal_component_enable(sys->component);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(filter, "Failed to enable component %s (status=%"PRIx32" %s)",
+ sys->component->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
+
+ filter->pf_video_filter = deinterlace;
+ filter->pf_flush = di_flush;
+ return 0;
+
+fail:
+ CloseMmalDeinterlace(filter);
+ return ret;
}
+
+vlc_module_begin()
+ set_shortname(N_("MMAL deinterlace"))
+ set_description(N_("MMAL-based deinterlace filter plugin"))
+ set_capability("video filter", 900)
+ set_category(CAT_VIDEO)
+ set_subcategory(SUBCAT_VIDEO_VFILTER)
+ set_callbacks(OpenMmalDeinterlace, CloseMmalDeinterlace)
+ add_shortcut("deinterlace")
+ add_bool(MMAL_DEINTERLACE_NO_QPU, false, MMAL_DEINTERLACE_NO_QPU_TEXT,
+ MMAL_DEINTERLACE_NO_QPU_LONGTEXT, true);
+ add_bool(MMAL_DEINTERLACE_ADV, false, MMAL_DEINTERLACE_ADV_TEXT,
+ MMAL_DEINTERLACE_ADV_LONGTEXT, true);
+ add_bool(MMAL_DEINTERLACE_FAST, false, MMAL_DEINTERLACE_FAST_TEXT,
+ MMAL_DEINTERLACE_FAST_LONGTEXT, true);
+ add_bool(MMAL_DEINTERLACE_NONE, false, MMAL_DEINTERLACE_NONE_TEXT,
+ MMAL_DEINTERLACE_NONE_LONGTEXT, true);
+ add_bool(MMAL_DEINTERLACE_HALF_RATE, false, MMAL_DEINTERLACE_HALF_RATE_TEXT,
+ MMAL_DEINTERLACE_HALF_RATE_LONGTEXT, true);
+ add_bool(MMAL_DEINTERLACE_FULL_RATE, false, MMAL_DEINTERLACE_FULL_RATE_TEXT,
+ MMAL_DEINTERLACE_FULL_RATE_LONGTEXT, true);
+
+vlc_module_end()
+
+
diff --git a/modules/hw/mmal/mmal_avcodec.c b/modules/hw/mmal/mmal_avcodec.c
new file mode 100644
index 0000000000..e32c2819f4
--- /dev/null
+++ b/modules/hw/mmal/mmal_avcodec.c
@@ -0,0 +1,2302 @@
+/*****************************************************************************
+ * video.c: video decoder using the libavcodec library
+ *****************************************************************************
+ * Copyright (C) 1999-2001 VLC authors and VideoLAN
+ * $Id$
+ *
+ * Authors: Laurent Aimar <fenrir@via.ecp.fr>
+ * Gildas Bazin <gbazin@videolan.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+#include "config.h"
+
+#include <vlc_common.h>
+#include <vlc_codec.h>
+#include <vlc_avcodec.h>
+#include <vlc_cpu.h>
+#include <vlc_atomic.h>
+
+#include <assert.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavutil/mem.h>
+#include <libavutil/pixdesc.h>
+#include <libavutil/hwcontext_drm.h>
+#if (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( 55, 16, 101 ) )
+#include <libavutil/mastering_display_metadata.h>
+#endif
+
+//#include "avcodec.h"
+//#include "va.h"
+
+#include <vlc_plugin.h>
+#include <libavutil/rpi_sand_fns.h>
+#include <libavcodec/rpi_zc.h>
+#include "../../codec/cc.h"
+#include "../../codec/avcodec/avcommon.h" // ??? Beware over inclusion
+#include "mmal_cma.h"
+#include "mmal_cma_drmprime.h"
+#include "mmal_picture.h"
+
+#include <libdrm/drm_fourcc.h>
+
+#define TRACE_ALL 0
+
+#define BUFFERS_IN_FLIGHT 5 // Default max value for in flight buffers
+#define BUFFERS_IN_FLIGHT_UHD 3 // Fewer if very big
+
+#define MMAL_AVCODEC_BUFFERS "mmal-avcodec-buffers"
+#define MMAL_AVCODEC_BUFFERS_TEXT N_("In flight buffer count before blocking.")
+#define MMAL_AVCODEC_BUFFERS_LONGTEXT N_("In flight buffer count before blocking. " \
+"Beware that incautious changing of this can lead to lockup. " \
+"Zero will disable the module.")
+
+
+// Fwd declarations required due to wanting to avoid reworking the original
+// code too much
+static void MmalAvcodecCloseDecoder( vlc_object_t *obj );
+
+
+/*****************************************************************************
+ * decoder_sys_t : decoder descriptor
+ *****************************************************************************/
+struct decoder_sys_t
+{
+ AVCodecContext *p_context;
+ const AVCodec *p_codec;
+
+ /* Video decoder specific part */
+ date_t pts;
+
+ /* Closed captions for decoders */
+ cc_data_t cc;
+
+ /* for frame skipping algo */
+ bool b_hurry_up;
+ bool b_show_corrupted;
+ bool b_from_preroll;
+ enum AVDiscard i_skip_frame;
+
+ /* how many decoded frames are late */
+ int i_late_frames;
+ mtime_t i_late_frames_start;
+ mtime_t i_last_late_delay;
+
+ /* for direct rendering */
+ bool b_direct_rendering;
+ atomic_bool b_dr_failure;
+
+ /* Hack to force display of still pictures */
+ bool b_first_frame;
+
+
+ /* */
+ bool palette_sent;
+
+ /* VA API */
+// vlc_va_t *p_va;
+ enum PixelFormat pix_fmt;
+ int profile;
+ int level;
+
+ vlc_sem_t sem_mt;
+
+ // Rpi vars
+ cma_buf_pool_t * cma_pool;
+ bool pool_alloc_1;
+ vcsm_init_type_t vcsm_init_type;
+ int cma_in_flight_max;
+ bool use_drm;
+ // Debug
+ decoder_t * p_dec;
+};
+
+
+static vlc_fourcc_t
+ZcFindVlcChroma(const int i_ffmpeg_chroma, const int i_ffmpeg_sw_chroma)
+{
+ int c = i_ffmpeg_chroma;
+
+ if (c == AV_PIX_FMT_DRM_PRIME)
+ c = i_ffmpeg_sw_chroma;
+
+ switch (c)
+ {
+ // This is all we claim to deal with
+ // In theory RGB should be doable within our current framework
+ case AV_PIX_FMT_YUV420P:
+ return VLC_CODEC_MMAL_ZC_I420;
+ case AV_PIX_FMT_SAND128:
+ case AV_PIX_FMT_RPI4_8:
+ return VLC_CODEC_MMAL_ZC_SAND8;
+ case AV_PIX_FMT_SAND64_10:
+ return VLC_CODEC_MMAL_ZC_SAND10;
+ case AV_PIX_FMT_RPI4_10:
+ return VLC_CODEC_MMAL_ZC_SAND30;
+ default:
+ break;
+ }
+ return 0;
+}
+
+// Pix Fmt conv for MMal
+// video_fromat from ffmpeg pic_fmt
+static int
+ZcGetVlcChroma( video_format_t *fmt, int i_ffmpeg_chroma, const int i_ffmpeg_sw_chroma)
+{
+ fmt->i_rmask = 0;
+ fmt->i_gmask = 0;
+ fmt->i_bmask = 0;
+ fmt->i_chroma = ZcFindVlcChroma(i_ffmpeg_chroma, i_ffmpeg_sw_chroma);
+
+ return fmt->i_chroma == 0 ? -1 : 0;
+}
+
+
+// Format chooser is way simpler than vlc
+static enum PixelFormat
+ZcGetFormat(AVCodecContext *p_context, const enum PixelFormat *pi_fmt)
+{
+ enum PixelFormat swfmt = avcodec_default_get_format(p_context, pi_fmt);
+ decoder_t * const p_dec = av_rpi_zc_in_use(p_context) ? NULL : p_context->opaque;
+ const bool use_drm = (p_dec != NULL) && p_dec->p_sys->use_drm;
+
+ for (size_t i = 0; pi_fmt[i] != AV_PIX_FMT_NONE; i++)
+ {
+ if (use_drm && pi_fmt[i] == AV_PIX_FMT_DRM_PRIME)
+ return pi_fmt[i];
+
+ if (!use_drm && pi_fmt[i] != AV_PIX_FMT_DRM_PRIME &&
+ ZcFindVlcChroma(pi_fmt[i], p_context->sw_pix_fmt) != 0)
+ return pi_fmt[i];
+ }
+ return swfmt;
+}
+
+
+static void cma_avbuf_pool_free(void * v)
+{
+ cma_buf_unref(v);
+}
+
+static unsigned int zc_buf_vcsm_handle(void * v)
+{
+ return cma_buf_vcsm_handle(v);
+}
+
+static unsigned int zc_buf_vc_handle(void * v)
+{
+ return cma_buf_vc_handle(v);
+}
+
+static void * zc_buf_map_arm(void * v)
+{
+ return cma_buf_addr(v);
+}
+
+static unsigned int zc_buf_map_vc(void * v)
+{
+ return cma_buf_vc_addr(v);
+}
+
+
+
+static const av_rpi_zc_buf_fn_tab_t zc_buf_fn_tab = {
+ .free = cma_avbuf_pool_free,
+
+ .vcsm_handle = zc_buf_vcsm_handle,
+ .vc_handle = zc_buf_vc_handle,
+ .map_arm = zc_buf_map_arm,
+ .map_vc = zc_buf_map_vc
+};
+
+
+static AVBufferRef *
+zc_alloc_buf(void * v, size_t size, const AVRpiZcFrameGeometry * geo)
+{
+ decoder_t * const dec = v;
+ decoder_sys_t * const sys = dec->p_sys;
+
+ VLC_UNUSED(geo);
+
+ assert(sys != NULL);
+
+ const unsigned int dec_pool_req = av_rpi_zc_get_decoder_pool_size(sys->p_context->opaque);
+ if (dec_pool_req != 0)
+ {
+ cma_buf_pool_resize(sys->cma_pool, dec_pool_req + sys->cma_in_flight_max, sys->cma_in_flight_max);
+
+ if (!sys->pool_alloc_1)
+ {
+ sys->pool_alloc_1 = true;
+ msg_Dbg(dec, "Pool size: (%d+%d) * %zd", dec_pool_req, sys->cma_in_flight_max, size);
+ if (cma_buf_pool_fill(sys->cma_pool, size) != 0)
+ msg_Warn(dec, "Failed to preallocate decoder pool (%d+%d) * %zd", dec_pool_req, sys->cma_in_flight_max, size);
+ }
+ }
+
+ void * const cmabuf = cma_buf_pool_alloc_buf(sys->cma_pool, size);
+
+ if (cmabuf == NULL)
+ {
+ msg_Err(dec, "CMA buf pool alloc buf failed");
+ return NULL;
+ }
+
+ AVBufferRef *const avbuf = av_rpi_zc_buf(cma_buf_size(cmabuf), 0, cmabuf, &zc_buf_fn_tab);
+
+ if (avbuf == NULL)
+ {
+ msg_Err(dec, "av_rpi_zc_buf failed");
+ cma_buf_unref(cmabuf);
+ return NULL;
+ }
+
+ return avbuf;
+}
+
+static void
+zc_free_pool(void * v)
+{
+ decoder_t * const dec = v;
+ cma_buf_pool_delete(dec->p_sys->cma_pool);
+}
+
+
+static const uint8_t shift_01[] = {0,1,1,1};
+static const uint8_t pb_1[] = {1,1,1,1};
+static const uint8_t pb_12[] = {1,2,2,2};
+static const uint8_t pb_24[] = {2,4,4,4};
+static const uint8_t pb_4[] = {4,4,4,4};
+
+static inline int pitch_from_mod(const uint64_t mod)
+{
+ return fourcc_mod_broadcom_mod(mod) != DRM_FORMAT_MOD_BROADCOM_SAND128 ? 0 :
+ fourcc_mod_broadcom_param(mod);
+}
+
+static int set_pic_from_frame(picture_t * const pic, const AVFrame * const frame)
+{
+ const uint8_t * hs = shift_01;
+ const uint8_t * ws = shift_01;
+ const uint8_t * pb = pb_1;
+
+ switch (pic->format.i_chroma)
+ {
+ case VLC_CODEC_MMAL_ZC_RGB32:
+ pic->i_planes = 1;
+ pb = pb_4;
+ break;
+ case VLC_CODEC_MMAL_ZC_I420:
+ pic->i_planes = 3;
+ break;
+ case VLC_CODEC_MMAL_ZC_SAND8:
+ pic->i_planes = 2;
+ pb = pb_12;
+ break;
+ case VLC_CODEC_MMAL_ZC_SAND10:
+ case VLC_CODEC_MMAL_ZC_SAND30: // Lies: SAND30 is "special"
+ pic->i_planes = 2;
+ pb = pb_24;
+ break;
+ default:
+ return VLC_EGENERIC;
+ }
+
+ const cma_buf_t * const cb = cma_buf_pic_get(pic);
+ uint8_t * const data = cma_buf_addr(cb);
+ if (data == NULL) {
+ return VLC_ENOMEM;
+ }
+
+ if (frame->format == AV_PIX_FMT_DRM_PRIME)
+ {
+ const AVDRMFrameDescriptor * const desc = (AVDRMFrameDescriptor*)frame->data[0];
+ const AVDRMLayerDescriptor * layer = desc->layers + 0;
+ const AVDRMPlaneDescriptor * plane = layer->planes + 0;
+ const uint64_t mod = desc->objects[0].format_modifier;
+ const int set_pitch = pitch_from_mod(mod);
+ int nb_plane = 0;
+
+ if (desc->nb_objects != 1)
+ return VLC_EGENERIC;
+
+ for (int i = 0; i != pic->i_planes; ++i)
+ {
+ if (nb_plane >= layer->nb_planes)
+ {
+ ++layer;
+ plane = layer->planes + 0;
+ nb_plane = 0;
+ }
+
+ pic->p[i] = (plane_t){
+ .p_pixels = data + plane->offset,
+ .i_lines = frame->height >> hs[i],
+ .i_pitch = set_pitch != 0 ? set_pitch : plane->pitch,
+ .i_pixel_pitch = pb[i],
+ .i_visible_lines = av_frame_cropped_height(frame) >> hs[i],
+ .i_visible_pitch = av_frame_cropped_width(frame) >> ws[i]
+ };
+
+ ++plane;
+ ++nb_plane;
+ }
+
+ // Calculate lines from gap between planes
+ // This will give us an accurate "height" for later use by MMAL
+ for (int i = 0; i + 1 < pic->i_planes; ++i)
+ pic->p[i].i_lines = (pic->p[i + 1].p_pixels - pic->p[i].p_pixels) / pic->p[i].i_pitch;
+ }
+ else
+ {
+ uint8_t * frame_end = frame->data[0] + cma_buf_size(cb);
+ for (int i = 0; i != pic->i_planes; ++i) {
+ // Calculate lines from gap between planes
+ // This will give us an accurate "height" for later use by MMAL
+ const int lines = ((i + 1 == pic->i_planes ? frame_end : frame->data[i + 1]) -
+ frame->data[i]) / frame->linesize[i];
+ pic->p[i] = (plane_t){
+ .p_pixels = data + (frame->data[i] - frame->data[0]),
+ .i_lines = lines,
+ .i_pitch = av_rpi_is_sand_frame(frame) ? av_rpi_sand_frame_stride2(frame) : frame->linesize[i],
+ .i_pixel_pitch = pb[i],
+ .i_visible_lines = av_frame_cropped_height(frame) >> hs[i],
+ .i_visible_pitch = av_frame_cropped_width(frame) >> ws[i]
+ };
+ }
+ }
+ return 0;
+}
+
+
+//============================================================================
+//
+// Nicked from avcodec/fourcc.c
+//
+// * Really we should probably use that directly
+
+/*
+ * Video Codecs
+ */
+
+struct vlc_avcodec_fourcc
+{
+ vlc_fourcc_t i_fourcc;
+ unsigned i_codec;
+};
+
+
+static const struct vlc_avcodec_fourcc video_codecs[] =
+{
+ { VLC_CODEC_MP1V, AV_CODEC_ID_MPEG1VIDEO },
+ { VLC_CODEC_MP2V, AV_CODEC_ID_MPEG2VIDEO }, /* prefer MPEG2 over MPEG1 */
+ { VLC_CODEC_MPGV, AV_CODEC_ID_MPEG2VIDEO }, /* prefer MPEG2 over MPEG1 */
+ /* AV_CODEC_ID_MPEG2VIDEO_XVMC */
+ { VLC_CODEC_H261, AV_CODEC_ID_H261 },
+ { VLC_CODEC_H263, AV_CODEC_ID_H263 },
+ { VLC_CODEC_RV10, AV_CODEC_ID_RV10 },
+ { VLC_CODEC_RV13, AV_CODEC_ID_RV10 },
+ { VLC_CODEC_RV20, AV_CODEC_ID_RV20 },
+ { VLC_CODEC_MJPG, AV_CODEC_ID_MJPEG },
+ { VLC_CODEC_MJPGB, AV_CODEC_ID_MJPEGB },
+ { VLC_CODEC_LJPG, AV_CODEC_ID_LJPEG },
+ { VLC_CODEC_SP5X, AV_CODEC_ID_SP5X },
+ { VLC_CODEC_JPEGLS, AV_CODEC_ID_JPEGLS },
+ { VLC_CODEC_MP4V, AV_CODEC_ID_MPEG4 },
+ /* AV_CODEC_ID_RAWVIDEO */
+ { VLC_CODEC_DIV1, AV_CODEC_ID_MSMPEG4V1 },
+ { VLC_CODEC_DIV2, AV_CODEC_ID_MSMPEG4V2 },
+ { VLC_CODEC_DIV3, AV_CODEC_ID_MSMPEG4V3 },
+ { VLC_CODEC_WMV1, AV_CODEC_ID_WMV1 },
+ { VLC_CODEC_WMV2, AV_CODEC_ID_WMV2 },
+ { VLC_CODEC_H263P, AV_CODEC_ID_H263P },
+ { VLC_CODEC_H263I, AV_CODEC_ID_H263I },
+ { VLC_CODEC_FLV1, AV_CODEC_ID_FLV1 },
+ { VLC_CODEC_SVQ1, AV_CODEC_ID_SVQ1 },
+ { VLC_CODEC_SVQ3, AV_CODEC_ID_SVQ3 },
+ { VLC_CODEC_DV, AV_CODEC_ID_DVVIDEO },
+ { VLC_CODEC_HUFFYUV, AV_CODEC_ID_HUFFYUV },
+ { VLC_CODEC_CYUV, AV_CODEC_ID_CYUV },
+ { VLC_CODEC_H264, AV_CODEC_ID_H264 },
+ { VLC_CODEC_INDEO3, AV_CODEC_ID_INDEO3 },
+ { VLC_CODEC_VP3, AV_CODEC_ID_VP3 },
+ { VLC_CODEC_THEORA, AV_CODEC_ID_THEORA },
+#if ( !defined( WORDS_BIGENDIAN ) )
+ /* Asus Video (Another thing that doesn't work on PPC) */
+ { VLC_CODEC_ASV1, AV_CODEC_ID_ASV1 },
+ { VLC_CODEC_ASV2, AV_CODEC_ID_ASV2 },
+#endif
+ { VLC_CODEC_FFV1, AV_CODEC_ID_FFV1 },
+ { VLC_CODEC_4XM, AV_CODEC_ID_4XM },
+ { VLC_CODEC_VCR1, AV_CODEC_ID_VCR1 },
+ { VLC_CODEC_CLJR, AV_CODEC_ID_CLJR },
+ { VLC_CODEC_MDEC, AV_CODEC_ID_MDEC },
+ { VLC_CODEC_ROQ, AV_CODEC_ID_ROQ },
+ { VLC_CODEC_INTERPLAY, AV_CODEC_ID_INTERPLAY_VIDEO },
+ { VLC_CODEC_XAN_WC3, AV_CODEC_ID_XAN_WC3 },
+ { VLC_CODEC_XAN_WC4, AV_CODEC_ID_XAN_WC4 },
+ { VLC_CODEC_RPZA, AV_CODEC_ID_RPZA },
+ { VLC_CODEC_CINEPAK, AV_CODEC_ID_CINEPAK },
+ { VLC_CODEC_WS_VQA, AV_CODEC_ID_WS_VQA },
+ { VLC_CODEC_MSRLE, AV_CODEC_ID_MSRLE },
+ { VLC_CODEC_MSVIDEO1, AV_CODEC_ID_MSVIDEO1 },
+ { VLC_CODEC_IDCIN, AV_CODEC_ID_IDCIN },
+ { VLC_CODEC_8BPS, AV_CODEC_ID_8BPS },
+ { VLC_CODEC_SMC, AV_CODEC_ID_SMC },
+ { VLC_CODEC_FLIC, AV_CODEC_ID_FLIC },
+ { VLC_CODEC_TRUEMOTION1, AV_CODEC_ID_TRUEMOTION1 },
+ { VLC_CODEC_VMDVIDEO, AV_CODEC_ID_VMDVIDEO },
+ { VLC_CODEC_LCL_MSZH, AV_CODEC_ID_MSZH },
+ { VLC_CODEC_LCL_ZLIB, AV_CODEC_ID_ZLIB },
+ { VLC_CODEC_QTRLE, AV_CODEC_ID_QTRLE },
+ { VLC_CODEC_TSCC, AV_CODEC_ID_TSCC },
+ { VLC_CODEC_ULTI, AV_CODEC_ID_ULTI },
+ { VLC_CODEC_QDRAW, AV_CODEC_ID_QDRAW },
+ { VLC_CODEC_VIXL, AV_CODEC_ID_VIXL },
+ { VLC_CODEC_QPEG, AV_CODEC_ID_QPEG },
+ { VLC_CODEC_PNG, AV_CODEC_ID_PNG },
+ { VLC_CODEC_PPM, AV_CODEC_ID_PPM },
+ /* AV_CODEC_ID_PBM */
+ { VLC_CODEC_PGM, AV_CODEC_ID_PGM },
+ { VLC_CODEC_PGMYUV, AV_CODEC_ID_PGMYUV },
+ { VLC_CODEC_PAM, AV_CODEC_ID_PAM },
+ { VLC_CODEC_FFVHUFF, AV_CODEC_ID_FFVHUFF },
+ { VLC_CODEC_RV30, AV_CODEC_ID_RV30 },
+ { VLC_CODEC_RV40, AV_CODEC_ID_RV40 },
+ { VLC_CODEC_VC1, AV_CODEC_ID_VC1 },
+ { VLC_CODEC_WMVA, AV_CODEC_ID_VC1 },
+ { VLC_CODEC_WMV3, AV_CODEC_ID_WMV3 },
+ { VLC_CODEC_WMVP, AV_CODEC_ID_WMV3 },
+ { VLC_CODEC_LOCO, AV_CODEC_ID_LOCO },
+ { VLC_CODEC_WNV1, AV_CODEC_ID_WNV1 },
+ { VLC_CODEC_AASC, AV_CODEC_ID_AASC },
+ { VLC_CODEC_INDEO2, AV_CODEC_ID_INDEO2 },
+ { VLC_CODEC_FRAPS, AV_CODEC_ID_FRAPS },
+ { VLC_CODEC_TRUEMOTION2, AV_CODEC_ID_TRUEMOTION2 },
+ { VLC_CODEC_BMP, AV_CODEC_ID_BMP },
+ { VLC_CODEC_CSCD, AV_CODEC_ID_CSCD },
+ { VLC_CODEC_MMVIDEO, AV_CODEC_ID_MMVIDEO },
+ { VLC_CODEC_ZMBV, AV_CODEC_ID_ZMBV },
+ { VLC_CODEC_AVS, AV_CODEC_ID_AVS },
+ { VLC_CODEC_SMACKVIDEO, AV_CODEC_ID_SMACKVIDEO },
+ { VLC_CODEC_NUV, AV_CODEC_ID_NUV },
+ { VLC_CODEC_KMVC, AV_CODEC_ID_KMVC },
+ { VLC_CODEC_FLASHSV, AV_CODEC_ID_FLASHSV },
+ { VLC_CODEC_CAVS, AV_CODEC_ID_CAVS },
+ { VLC_CODEC_JPEG2000, AV_CODEC_ID_JPEG2000 },
+ { VLC_CODEC_VMNC, AV_CODEC_ID_VMNC },
+ { VLC_CODEC_VP5, AV_CODEC_ID_VP5 },
+ { VLC_CODEC_VP6, AV_CODEC_ID_VP6 },
+ { VLC_CODEC_VP6F, AV_CODEC_ID_VP6F },
+ { VLC_CODEC_TARGA, AV_CODEC_ID_TARGA },
+ { VLC_CODEC_DSICINVIDEO, AV_CODEC_ID_DSICINVIDEO },
+ { VLC_CODEC_TIERTEXSEQVIDEO, AV_CODEC_ID_TIERTEXSEQVIDEO },
+ { VLC_CODEC_TIFF, AV_CODEC_ID_TIFF },
+ { VLC_CODEC_GIF, AV_CODEC_ID_GIF },
+ { VLC_CODEC_DXA, AV_CODEC_ID_DXA },
+ { VLC_CODEC_DNXHD, AV_CODEC_ID_DNXHD },
+ { VLC_CODEC_THP, AV_CODEC_ID_THP },
+ { VLC_CODEC_SGI, AV_CODEC_ID_SGI },
+ { VLC_CODEC_C93, AV_CODEC_ID_C93 },
+ { VLC_CODEC_BETHSOFTVID, AV_CODEC_ID_BETHSOFTVID },
+ /* AV_CODEC_ID_PTX */
+ { VLC_CODEC_TXD, AV_CODEC_ID_TXD },
+ { VLC_CODEC_VP6A, AV_CODEC_ID_VP6A },
+ { VLC_CODEC_AMV, AV_CODEC_ID_AMV },
+ { VLC_CODEC_VB, AV_CODEC_ID_VB },
+ { VLC_CODEC_PCX, AV_CODEC_ID_PCX },
+ /* AV_CODEC_ID_SUNRAST */
+ { VLC_CODEC_INDEO4, AV_CODEC_ID_INDEO4 },
+ { VLC_CODEC_INDEO5, AV_CODEC_ID_INDEO5 },
+ { VLC_CODEC_MIMIC, AV_CODEC_ID_MIMIC },
+ { VLC_CODEC_RL2, AV_CODEC_ID_RL2 },
+ { VLC_CODEC_ESCAPE124, AV_CODEC_ID_ESCAPE124 },
+ { VLC_CODEC_DIRAC, AV_CODEC_ID_DIRAC },
+ { VLC_CODEC_BFI, AV_CODEC_ID_BFI },
+ { VLC_CODEC_CMV, AV_CODEC_ID_CMV },
+ { VLC_CODEC_MOTIONPIXELS, AV_CODEC_ID_MOTIONPIXELS },
+ { VLC_CODEC_TGV, AV_CODEC_ID_TGV },
+ { VLC_CODEC_TGQ, AV_CODEC_ID_TGQ },
+ { VLC_CODEC_TQI, AV_CODEC_ID_TQI },
+ { VLC_CODEC_AURA, AV_CODEC_ID_AURA },
+ /* AV_CODEC_ID_AURA2 */
+ /* AV_CODEC_ID_V210X */
+ { VLC_CODEC_TMV, AV_CODEC_ID_TMV },
+ { VLC_CODEC_V210, AV_CODEC_ID_V210 },
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT( 54, 50, 100 ) && LIBAVCODEC_VERSION_MICRO >= 100
+ { VLC_CODEC_VUYA, AV_CODEC_ID_AYUV },
+#endif
+ /* AV_CODEC_ID_DPX */
+ { VLC_CODEC_MAD, AV_CODEC_ID_MAD },
+ { VLC_CODEC_FRWU, AV_CODEC_ID_FRWU },
+ { VLC_CODEC_FLASHSV2, AV_CODEC_ID_FLASHSV2 },
+ /* AV_CODEC_ID_CDGRAPHICS */
+ /* AV_CODEC_ID_R210 */
+ { VLC_CODEC_ANM, AV_CODEC_ID_ANM },
+ { VLC_CODEC_BINKVIDEO, AV_CODEC_ID_BINKVIDEO },
+ /* AV_CODEC_ID_IFF_ILBM */
+ /* AV_CODEC_ID_IFF_BYTERUN1 */
+ { VLC_CODEC_KGV1, AV_CODEC_ID_KGV1 },
+ { VLC_CODEC_YOP, AV_CODEC_ID_YOP },
+ { VLC_CODEC_VP8, AV_CODEC_ID_VP8 },
+ /* AV_CODEC_ID_PICTOR */
+ /* AV_CODEC_ID_ANSI */
+ /* AV_CODEC_ID_A64_MULTI */
+ /* AV_CODEC_ID_A64_MULTI5 */
+ /* AV_CODEC_ID_R10K */
+ { VLC_CODEC_MXPEG, AV_CODEC_ID_MXPEG },
+ { VLC_CODEC_LAGARITH, AV_CODEC_ID_LAGARITH },
+ { VLC_CODEC_PRORES, AV_CODEC_ID_PRORES },
+ { VLC_CODEC_JV, AV_CODEC_ID_JV },
+ { VLC_CODEC_DFA, AV_CODEC_ID_DFA },
+ { VLC_CODEC_WMVP, AV_CODEC_ID_WMV3IMAGE },
+ { VLC_CODEC_WMVP2, AV_CODEC_ID_VC1IMAGE },
+ { VLC_CODEC_UTVIDEO, AV_CODEC_ID_UTVIDEO },
+ { VLC_CODEC_BMVVIDEO, AV_CODEC_ID_BMV_VIDEO },
+ { VLC_CODEC_VBLE, AV_CODEC_ID_VBLE },
+ { VLC_CODEC_DXTORY, AV_CODEC_ID_DXTORY },
+ /* AV_CODEC_ID_V410 */
+ /* AV_CODEC_ID_XWD */
+ { VLC_CODEC_CDXL, AV_CODEC_ID_CDXL },
+ /* AV_CODEC_ID_XBM */
+ /* AV_CODEC_ID_ZEROCODEC */
+ { VLC_CODEC_MSS1, AV_CODEC_ID_MSS1 },
+ { VLC_CODEC_MSA1, AV_CODEC_ID_MSA1 },
+ { VLC_CODEC_TSC2, AV_CODEC_ID_TSCC2 },
+ { VLC_CODEC_MTS2, AV_CODEC_ID_MTS2 },
+ { VLC_CODEC_CLLC, AV_CODEC_ID_CLLC },
+ { VLC_CODEC_MSS2, AV_CODEC_ID_MSS2 },
+ { VLC_CODEC_VP9, AV_CODEC_ID_VP9 },
+#if LIBAVCODEC_VERSION_CHECK( 57, 26, 0, 83, 101 )
+ { VLC_CODEC_AV1, AV_CODEC_ID_AV1 },
+#endif
+ { VLC_CODEC_ICOD, AV_CODEC_ID_AIC },
+ /* AV_CODEC_ID_ESCAPE130 */
+ { VLC_CODEC_G2M4, AV_CODEC_ID_G2M },
+ { VLC_CODEC_G2M2, AV_CODEC_ID_G2M },
+ { VLC_CODEC_G2M3, AV_CODEC_ID_G2M },
+ /* AV_CODEC_ID_WEBP */
+ { VLC_CODEC_HNM4_VIDEO, AV_CODEC_ID_HNM4_VIDEO },
+ { VLC_CODEC_HEVC, AV_CODEC_ID_HEVC },
+
+ { VLC_CODEC_FIC , AV_CODEC_ID_FIC },
+ /* AV_CODEC_ID_ALIAS_PIX */
+ /* AV_CODEC_ID_BRENDER_PIX */
+ /* AV_CODEC_ID_PAF_VIDEO */
+ /* AV_CODEC_ID_EXR */
+
+ { VLC_CODEC_VP7 , AV_CODEC_ID_VP7 },
+ /* AV_CODEC_ID_SANM */
+ /* AV_CODEC_ID_SGIRLE */
+ /* AV_CODEC_ID_MVC1 */
+ /* AV_CODEC_ID_MVC2 */
+ { VLC_CODEC_HQX, AV_CODEC_ID_HQX },
+
+ { VLC_CODEC_TDSC, AV_CODEC_ID_TDSC },
+
+ { VLC_CODEC_HQ_HQA, AV_CODEC_ID_HQ_HQA },
+
+ { VLC_CODEC_HAP, AV_CODEC_ID_HAP },
+ /* AV_CODEC_ID_DDS */
+
+ { VLC_CODEC_DXV, AV_CODEC_ID_DXV },
+
+ /* ffmpeg only: AV_CODEC_ID_BRENDER_PIX */
+ /* ffmpeg only: AV_CODEC_ID_Y41P */
+ /* ffmpeg only: AV_CODEC_ID_EXR */
+ /* ffmpeg only: AV_CODEC_ID_AVRP */
+ /* ffmpeg only: AV_CODEC_ID_012V */
+ /* ffmpeg only: AV_CODEC_ID_AVUI */
+ /* ffmpeg only: AV_CODEC_ID_TARGA_Y216 */
+ /* ffmpeg only: AV_CODEC_ID_V308 */
+ /* ffmpeg only: AV_CODEC_ID_V408 */
+ /* ffmpeg only: AV_CODEC_ID_YUV4 */
+ /* ffmpeg only: AV_CODEC_ID_SANM */
+ /* ffmpeg only: AV_CODEC_ID_PAF_VIDEO */
+ /* ffmpeg only: AV_CODEC_ID_AVRN */
+ /* ffmpeg only: AV_CODEC_ID_CPIA */
+ /* ffmpeg only: AV_CODEC_ID_XFACE */
+ /* ffmpeg only: AV_CODEC_ID_SGIRLE */
+ /* ffmpeg only: AV_CODEC_ID_MVC1 */
+ /* ffmpeg only: AV_CODEC_ID_MVC2 */
+ /* ffmpeg only: AV_CODEC_ID_SNOW */
+ /* ffmpeg only: AV_CODEC_ID_SMVJPEG */
+
+#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 24, 102 )
+ { VLC_CODEC_CINEFORM, AV_CODEC_ID_CFHD },
+#endif
+
+#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 70, 100 )
+ { VLC_CODEC_PIXLET, AV_CODEC_ID_PIXLET },
+#endif
+
+#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 71, 101 )
+ { VLC_CODEC_SPEEDHQ, AV_CODEC_ID_SPEEDHQ },
+#endif
+
+#if LIBAVCODEC_VERSION_CHECK( 57, 999, 999, 79, 100 )
+ { VLC_CODEC_FMVC, AV_CODEC_ID_FMVC },
+#endif
+};
+
+// *** Really we should probably use GetFfmpegCodec with a pre-kludge for the bits we care about
+static bool
+ZcGetFfmpegCodec( enum es_format_category_e cat, vlc_fourcc_t i_fourcc,
+ unsigned *pi_ffmpeg_codec, const char **ppsz_name )
+{
+ const struct vlc_avcodec_fourcc *base;
+ size_t count;
+
+ base = video_codecs;
+ count = ARRAY_SIZE(video_codecs);
+ i_fourcc = vlc_fourcc_GetCodec( cat, i_fourcc );
+
+ for( size_t i = 0; i < count; i++ )
+ {
+ if( base[i].i_fourcc == i_fourcc )
+ {
+ if( pi_ffmpeg_codec != NULL )
+ *pi_ffmpeg_codec = base[i].i_codec;
+ if( ppsz_name )
+ *ppsz_name = vlc_fourcc_GetDescription( cat, i_fourcc );
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+//============================================================================
+// Derived from codec/avcodec/avcodec.c
+
+static AVCodecContext *
+ZcFfmpeg_AllocContext( decoder_t *p_dec,
+ const AVCodec **restrict codecp )
+{
+ unsigned i_codec_id;
+ const char *psz_namecodec;
+ const AVCodec *p_codec = NULL;
+
+ /* *** determine codec type *** */
+ if( !ZcGetFfmpegCodec( p_dec->fmt_in.i_cat, p_dec->fmt_in.i_codec,
+ &i_codec_id, &psz_namecodec ) )
+ return NULL;
+
+ msg_Dbg( p_dec, "using %s %s", AVPROVIDER(LIBAVCODEC), LIBAVCODEC_IDENT );
+
+ /* Initialization must be done before avcodec_find_decoder() */
+ vlc_init_avcodec(VLC_OBJECT(p_dec));
+
+ /* *** ask ffmpeg for a decoder *** */
+ char *psz_decoder = var_InheritString( p_dec, "avcodec-codec" );
+ if( psz_decoder != NULL )
+ {
+ p_codec = avcodec_find_decoder_by_name( psz_decoder );
+ if( !p_codec )
+ msg_Err( p_dec, "Decoder `%s' not found", psz_decoder );
+ else if( p_codec->id != i_codec_id )
+ {
+ msg_Err( p_dec, "Decoder `%s' can't handle %4.4s",
+ psz_decoder, (char*)&p_dec->fmt_in.i_codec );
+ p_codec = NULL;
+ }
+ free( psz_decoder );
+ }
+ if( !p_codec )
+// p_codec = avcodec_find_decoder( i_codec_id );
+ {
+ if( p_dec->fmt_in.i_codec != VLC_CODEC_HEVC )
+ p_codec = avcodec_find_decoder(i_codec_id);
+ else
+ {
+ psz_namecodec = rpi_use_pi3_hevc() ? "hevc_rpi" : "hevc" ;
+ msg_Info(p_dec, "Looking for HEVC decoder '%s'", psz_namecodec);
+ p_codec = avcodec_find_decoder_by_name(psz_namecodec);
+ }
+ }
+
+ if( !p_codec )
+ {
+ msg_Dbg( p_dec, "codec not found (%s)", psz_namecodec );
+ return NULL;
+ }
+
+ *codecp = p_codec;
+
+ /* *** get a p_context *** */
+ AVCodecContext *avctx = avcodec_alloc_context3(p_codec);
+ if( unlikely(avctx == NULL) )
+ return NULL;
+
+ avctx->debug = var_InheritInteger( p_dec, "avcodec-debug" );
+ avctx->opaque = p_dec;
+ return avctx;
+}
+
+/*****************************************************************************
+ * ffmpeg_OpenCodec:
+ *****************************************************************************/
+
+static int
+ZcFfmpeg_OpenCodec( decoder_t *p_dec, AVCodecContext *ctx,
+ const AVCodec *codec )
+{
+ char *psz_opts = var_InheritString( p_dec, "avcodec-options" );
+ AVDictionary *options = NULL;
+ int ret;
+
+ if (psz_opts) {
+ vlc_av_get_options(psz_opts, &options);
+ free(psz_opts);
+ }
+
+ if (!p_dec->p_sys->use_drm &&
+ av_rpi_zc_init2(ctx, p_dec, zc_alloc_buf, zc_free_pool) != 0)
+ {
+ msg_Err(p_dec, "Failed to init AV ZC");
+ return VLC_EGENERIC;
+ }
+
+ vlc_avcodec_lock();
+ ret = avcodec_open2( ctx, codec, options ? &options : NULL );
+ vlc_avcodec_unlock();
+
+ AVDictionaryEntry *t = NULL;
+ while ((t = av_dict_get(options, "", t, AV_DICT_IGNORE_SUFFIX))) {
+ msg_Err( p_dec, "Unknown option \"%s\"", t->key );
+ }
+ av_dict_free(&options);
+
+ if( ret < 0 )
+ {
+ msg_Err( p_dec, "cannot start codec (%s)", codec->name );
+ return VLC_EGENERIC;
+ }
+
+ msg_Dbg( p_dec, "codec (%s) started", codec->name );
+ return VLC_SUCCESS;
+}
+
+//============================================================================
+// Derived from 3.0.7.1 codec/avcodec/video.c
+
+static inline void wait_mt(decoder_sys_t *sys)
+{
+#if 1
+ // As we only ever update the output in our main thread this lock is
+ // redundant
+ VLC_UNUSED(sys);
+#else
+ vlc_sem_wait(&sys->sem_mt);
+#endif
+}
+
+static inline void post_mt(decoder_sys_t *sys)
+{
+#if 1
+ // As we only ever update the output in our main thread this lock is
+ // redundant
+ VLC_UNUSED(sys);
+#else
+ vlc_sem_post(&sys->sem_mt);
+#endif
+}
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+static void ffmpeg_InitCodec ( decoder_t * );
+static int DecodeVideo( decoder_t *, block_t * );
+static void Flush( decoder_t * );
+
+static uint32_t ffmpeg_CodecTag( vlc_fourcc_t fcc )
+{
+ uint8_t *p = (uint8_t*)&fcc;
+ return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+}
+
+/*****************************************************************************
+ * Local Functions
+ *****************************************************************************/
+
+/**
+ * Sets the decoder output format.
+ */
+static int lavc_GetVideoFormat(decoder_t *dec, video_format_t *restrict fmt,
+ AVCodecContext *ctx, enum AVPixelFormat pix_fmt,
+ enum AVPixelFormat sw_pix_fmt)
+{
+ int width = ctx->coded_width;
+ int height = ctx->coded_height;
+
+ video_format_Init(fmt, 0);
+
+#if 1
+ if ((fmt->i_chroma = ZcFindVlcChroma(pix_fmt, sw_pix_fmt)) == 0) {
+ msg_Info(dec, "Find chroma fail");
+ return -1;
+ }
+#else
+ if (pix_fmt == sw_pix_fmt)
+ { /* software decoding */
+ int aligns[AV_NUM_DATA_POINTERS];
+
+ if (GetVlcChroma(fmt, pix_fmt))
+ return -1;
+
+ /* The libavcodec palette can only be fetched when the first output
+ * frame is decoded. Assume that the current chroma is RGB32 while we
+ * are waiting for a valid palette. Indeed, fmt_out.video.p_palette
+ * doesn't trigger a new vout request, but a new chroma yes. */
+ if (pix_fmt == AV_PIX_FMT_PAL8 && !dec->fmt_out.video.p_palette)
+ fmt->i_chroma = VLC_CODEC_RGB32;
+
+ avcodec_align_dimensions2(ctx, &width, &height, aligns);
+ }
+ else /* hardware decoding */
+ fmt->i_chroma = vlc_va_GetChroma(pix_fmt, sw_pix_fmt);
+#endif
+
+ if( width == 0 || height == 0 || width > 8192 || height > 8192 ||
+ width < ctx->width || height < ctx->height )
+ {
+ msg_Err(dec, "Invalid frame size %dx%d vsz %dx%d",
+ width, height, ctx->width, ctx->height );
+ return -1; /* invalid display size */
+ }
+
+ fmt->i_width = width;
+ fmt->i_height = height;
+ fmt->i_visible_width = ctx->width;
+ fmt->i_visible_height = ctx->height;
+
+ /* If an aspect-ratio was specified in the input format then force it */
+ if (dec->fmt_in.video.i_sar_num > 0 && dec->fmt_in.video.i_sar_den > 0)
+ {
+ fmt->i_sar_num = dec->fmt_in.video.i_sar_num;
+ fmt->i_sar_den = dec->fmt_in.video.i_sar_den;
+ }
+ else
+ {
+ fmt->i_sar_num = ctx->sample_aspect_ratio.num;
+ fmt->i_sar_den = ctx->sample_aspect_ratio.den;
+
+ if (fmt->i_sar_num == 0 || fmt->i_sar_den == 0)
+ fmt->i_sar_num = fmt->i_sar_den = 1;
+ }
+
+ if (dec->fmt_in.video.i_frame_rate > 0
+ && dec->fmt_in.video.i_frame_rate_base > 0)
+ {
+ fmt->i_frame_rate = dec->fmt_in.video.i_frame_rate;
+ fmt->i_frame_rate_base = dec->fmt_in.video.i_frame_rate_base;
+ }
+ else if (ctx->framerate.num > 0 && ctx->framerate.den > 0)
+ {
+ fmt->i_frame_rate = ctx->framerate.num;
+ fmt->i_frame_rate_base = ctx->framerate.den;
+# if LIBAVCODEC_VERSION_MICRO < 100
+ // for some reason libav don't thinkg framerate presents actually same thing as in ffmpeg
+ fmt->i_frame_rate_base *= __MAX(ctx->ticks_per_frame, 1);
+# endif
+ }
+ else if (ctx->time_base.num > 0 && ctx->time_base.den > 0)
+ {
+ fmt->i_frame_rate = ctx->time_base.den;
+ fmt->i_frame_rate_base = ctx->time_base.num
+ * __MAX(ctx->ticks_per_frame, 1);
+ }
+
+ /* FIXME we should only set the known values and let the core decide
+ * later of fallbacks, but we can't do that with a boolean */
+ switch ( ctx->color_range )
+ {
+ case AVCOL_RANGE_JPEG:
+ fmt->b_color_range_full = true;
+ break;
+ case AVCOL_RANGE_UNSPECIFIED:
+ fmt->b_color_range_full = !vlc_fourcc_IsYUV( fmt->i_chroma );
+ break;
+ case AVCOL_RANGE_MPEG:
+ default:
+ fmt->b_color_range_full = false;
+ break;
+ }
+
+ switch( ctx->colorspace )
+ {
+ case AVCOL_SPC_BT709:
+ fmt->space = COLOR_SPACE_BT709;
+ break;
+ case AVCOL_SPC_SMPTE170M:
+ case AVCOL_SPC_BT470BG:
+ fmt->space = COLOR_SPACE_BT601;
+ break;
+ case AVCOL_SPC_BT2020_NCL:
+ case AVCOL_SPC_BT2020_CL:
+ fmt->space = COLOR_SPACE_BT2020;
+ break;
+ default:
+ break;
+ }
+
+ switch( ctx->color_trc )
+ {
+ case AVCOL_TRC_LINEAR:
+ fmt->transfer = TRANSFER_FUNC_LINEAR;
+ break;
+ case AVCOL_TRC_GAMMA22:
+ fmt->transfer = TRANSFER_FUNC_SRGB;
+ break;
+ case AVCOL_TRC_BT709:
+ fmt->transfer = TRANSFER_FUNC_BT709;
+ break;
+ case AVCOL_TRC_SMPTE170M:
+ case AVCOL_TRC_BT2020_10:
+ case AVCOL_TRC_BT2020_12:
+ fmt->transfer = TRANSFER_FUNC_BT2020;
+ break;
+#if LIBAVUTIL_VERSION_CHECK( 55, 14, 0, 31, 100)
+ case AVCOL_TRC_ARIB_STD_B67:
+ fmt->transfer = TRANSFER_FUNC_ARIB_B67;
+ break;
+#endif
+#if LIBAVUTIL_VERSION_CHECK( 55, 17, 0, 37, 100)
+ case AVCOL_TRC_SMPTE2084:
+ fmt->transfer = TRANSFER_FUNC_SMPTE_ST2084;
+ break;
+ case AVCOL_TRC_SMPTE240M:
+ fmt->transfer = TRANSFER_FUNC_SMPTE_240;
+ break;
+ case AVCOL_TRC_GAMMA28:
+ fmt->transfer = TRANSFER_FUNC_BT470_BG;
+ break;
+#endif
+ default:
+ break;
+ }
+
+ switch( ctx->color_primaries )
+ {
+ case AVCOL_PRI_BT709:
+ fmt->primaries = COLOR_PRIMARIES_BT709;
+ break;
+ case AVCOL_PRI_BT470BG:
+ fmt->primaries = COLOR_PRIMARIES_BT601_625;
+ break;
+ case AVCOL_PRI_SMPTE170M:
+ case AVCOL_PRI_SMPTE240M:
+ fmt->primaries = COLOR_PRIMARIES_BT601_525;
+ break;
+ case AVCOL_PRI_BT2020:
+ fmt->primaries = COLOR_PRIMARIES_BT2020;
+ break;
+ default:
+ break;
+ }
+
+ switch( ctx->chroma_sample_location )
+ {
+ case AVCHROMA_LOC_LEFT:
+ fmt->chroma_location = CHROMA_LOCATION_LEFT;
+ break;
+ case AVCHROMA_LOC_CENTER:
+ fmt->chroma_location = CHROMA_LOCATION_CENTER;
+ break;
+ case AVCHROMA_LOC_TOPLEFT:
+ fmt->chroma_location = CHROMA_LOCATION_TOP_LEFT;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int lavc_UpdateVideoFormat(decoder_t *dec, AVCodecContext *ctx,
+ enum AVPixelFormat fmt,
+ enum AVPixelFormat swfmt)
+{
+ video_format_t fmt_out;
+ int val;
+#if TRACE_ALL
+ msg_Dbg(dec, "<<< %s", __func__);
+#endif
+ val = lavc_GetVideoFormat(dec, &fmt_out, ctx, fmt, swfmt);
+ if (val)
+ {
+ msg_Dbg(dec, "Failed to get format");
+ return val;
+ }
+
+ /* always have date in fields/ticks units */
+ if(dec->p_sys->pts.i_divider_num)
+ date_Change(&dec->p_sys->pts, fmt_out.i_frame_rate *
+ __MAX(ctx->ticks_per_frame, 1),
+ fmt_out.i_frame_rate_base);
+ else
+ date_Init(&dec->p_sys->pts, fmt_out.i_frame_rate *
+ __MAX(ctx->ticks_per_frame, 1),
+ fmt_out.i_frame_rate_base);
+
+ fmt_out.p_palette = dec-> fmt_out.video.p_palette;
+ dec->fmt_out.video.p_palette = NULL;
+
+ es_format_Change(&dec->fmt_out, VIDEO_ES, fmt_out.i_chroma);
+ dec->fmt_out.video = fmt_out;
+ dec->fmt_out.video.orientation = dec->fmt_in.video.orientation;
+ dec->fmt_out.video.projection_mode = dec->fmt_in.video.projection_mode;
+ dec->fmt_out.video.multiview_mode = dec->fmt_in.video.multiview_mode;
+ dec->fmt_out.video.pose = dec->fmt_in.video.pose;
+ if ( dec->fmt_in.video.mastering.max_luminance )
+ dec->fmt_out.video.mastering = dec->fmt_in.video.mastering;
+ dec->fmt_out.video.lighting = dec->fmt_in.video.lighting;
+
+ val = decoder_UpdateVideoFormat(dec);
+#if TRACE_ALL
+ msg_Dbg(dec, ">>> %s: rv=%d", __func__, val);
+#endif
+ return val;
+}
+
+static int OpenVideoCodec( decoder_t *p_dec )
+{
+ decoder_sys_t *p_sys = p_dec->p_sys;
+ AVCodecContext *ctx = p_sys->p_context;
+ const AVCodec *codec = p_sys->p_codec;
+ int ret;
+
+ if( ctx->extradata_size <= 0 )
+ {
+ if( codec->id == AV_CODEC_ID_VC1 ||
+ codec->id == AV_CODEC_ID_THEORA )
+ {
+ msg_Warn( p_dec, "waiting for extra data for codec %s",
+ codec->name );
+ return 1;
+ }
+ }
+
+ ctx->width = p_dec->fmt_in.video.i_visible_width;
+ ctx->height = p_dec->fmt_in.video.i_visible_height;
+
+ ctx->coded_width = p_dec->fmt_in.video.i_width;
+ ctx->coded_height = p_dec->fmt_in.video.i_height;
+
+ ctx->bits_per_coded_sample = p_dec->fmt_in.video.i_bits_per_pixel;
+ p_sys->pix_fmt = AV_PIX_FMT_NONE;
+ p_sys->profile = -1;
+ p_sys->level = -1;
+ cc_Init( &p_sys->cc );
+
+ set_video_color_settings( &p_dec->fmt_in.video, ctx );
+ if( p_dec->fmt_in.video.i_frame_rate_base &&
+ p_dec->fmt_in.video.i_frame_rate &&
+ (double) p_dec->fmt_in.video.i_frame_rate /
+ p_dec->fmt_in.video.i_frame_rate_base < 6 )
+ {
+ ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
+ }
+
+ post_mt( p_sys );
+ ret = ZcFfmpeg_OpenCodec( p_dec, ctx, codec );
+ wait_mt( p_sys );
+ if( ret < 0 )
+ return ret;
+
+ switch( ctx->active_thread_type )
+ {
+ case FF_THREAD_FRAME:
+ msg_Dbg( p_dec, "using frame thread mode with %d threads",
+ ctx->thread_count );
+ break;
+ case FF_THREAD_SLICE:
+ msg_Dbg( p_dec, "using slice thread mode with %d threads",
+ ctx->thread_count );
+ break;
+ case 0:
+ if( ctx->thread_count > 1 )
+ msg_Warn( p_dec, "failed to enable threaded decoding" );
+ break;
+ default:
+ msg_Warn( p_dec, "using unknown thread mode with %d threads",
+ ctx->thread_count );
+ break;
+ }
+ return 0;
+}
+
+/*****************************************************************************
+ * InitVideo: initialize the video decoder
+ *****************************************************************************
+ * the ffmpeg codec will be opened, some memory allocated. The vout is not yet
+ * opened (done after the first decoded frame).
+ *****************************************************************************/
+
+static bool has_legacy_rpivid()
+{
+ static int cached_state = 0;
+
+ if (cached_state == 0)
+ {
+ struct stat buf;
+ cached_state = stat("/dev/rpivid-intcmem", &buf) != 0 ? -1 : 1;
+ }
+ return cached_state > 0;
+}
+
+
+static int MmalAvcodecOpenDecoder( vlc_object_t *obj )
+{
+ decoder_t *p_dec = (decoder_t *)obj;
+ const AVCodec *p_codec;
+ bool use_zc;
+ bool use_drm;
+ AVBufferRef * hw_dev_ctx = NULL;
+
+ int extra_buffers = var_InheritInteger(p_dec, MMAL_AVCODEC_BUFFERS);
+
+ if (extra_buffers < 0)
+ {
+ extra_buffers = p_dec->fmt_in.video.i_height * p_dec->fmt_in.video.i_width >= 1920 * 1088 ?
+ BUFFERS_IN_FLIGHT_UHD : BUFFERS_IN_FLIGHT;
+ }
+
+ if (extra_buffers <= 0)
+ {
+ msg_Dbg(p_dec, "%s: extra_buffers=%d - cannot use module", __func__, extra_buffers);
+ return VLC_EGENERIC;
+ }
+
+ use_zc = has_legacy_rpivid();
+ use_drm = !use_zc; // ** At least for the moment
+ const vcsm_init_type_t vcsm_type = use_zc ? cma_vcsm_init() : VCSM_INIT_NONE;
+ const int vcsm_size = vcsm_type == VCSM_INIT_NONE ? 0 :
+ vcsm_type == VCSM_INIT_LEGACY ? hw_mmal_get_gpu_mem() : 512 << 20;
+
+#if 1
+ {
+ char buf1[5], buf2[5], buf2a[5];
+ char buf3[5], buf4[5];
+ uint32_t in_fcc = 0;
+ msg_Dbg(p_dec, "%s: <<< (%s/%s)[%s] %dx%d -> (%s/%s) %dx%d [%s/%d] xb:%d", __func__,
+ str_fourcc(buf1, p_dec->fmt_in.i_codec),
+ str_fourcc(buf2, p_dec->fmt_in.video.i_chroma),
+ str_fourcc(buf2a, in_fcc),
+ p_dec->fmt_in.video.i_width, p_dec->fmt_in.video.i_height,
+ str_fourcc(buf3, p_dec->fmt_out.i_codec),
+ str_fourcc(buf4, p_dec->fmt_out.video.i_chroma),
+ p_dec->fmt_out.video.i_width, p_dec->fmt_out.video.i_height,
+ cma_vcsm_init_str(vcsm_type), vcsm_size, extra_buffers);
+ }
+#endif
+
+ if (vcsm_type == VCSM_INIT_NONE && !use_drm)
+ return VLC_EGENERIC;
+
+ if (use_drm)
+ {
+ int dev_type = av_hwdevice_find_type_by_name("drm");
+
+ if (dev_type == AV_HWDEVICE_TYPE_NONE ||
+ av_hwdevice_ctx_create(&hw_dev_ctx, dev_type, NULL, NULL, 0))
+ {
+ msg_Warn(p_dec, "Failed to create drm hw device context");
+ use_drm = false;
+ }
+ }
+
+
+#if 1
+ if (use_zc &&
+ ((p_dec->fmt_in.i_codec != VLC_CODEC_HEVC &&
+ (vcsm_type == VCSM_INIT_CMA || vcsm_size < (96 << 20))) ||
+ (p_dec->fmt_in.i_codec == VLC_CODEC_HEVC &&
+ vcsm_size < (128 << 20))))
+ {
+ cma_vcsm_exit(vcsm_type);
+ return VLC_EGENERIC;
+ }
+#endif
+
+ AVCodecContext *p_context = ZcFfmpeg_AllocContext( p_dec, &p_codec );
+ if( p_context == NULL )
+ {
+ av_buffer_unref(&hw_dev_ctx);
+ cma_vcsm_exit(vcsm_type);
+ return VLC_EGENERIC;
+ }
+ p_context->hw_device_ctx = hw_dev_ctx;
+ hw_dev_ctx = NULL;
+
+ int i_val;
+
+ /* Allocate the memory needed to store the decoder's structure */
+ decoder_sys_t *p_sys = calloc( 1, sizeof(*p_sys) );
+ if( unlikely(p_sys == NULL) )
+ {
+ avcodec_free_context( &p_context );
+ cma_vcsm_exit(vcsm_type);
+ return VLC_ENOMEM;
+ }
+
+ p_dec->p_sys = p_sys;
+ p_sys->p_context = p_context;
+ p_sys->p_codec = p_codec;
+ p_sys->p_dec = p_dec;
+// p_sys->p_va = NULL;
+ p_sys->cma_in_flight_max = extra_buffers;
+ p_sys->vcsm_init_type = vcsm_type;
+ p_sys->use_drm = use_drm;
+ vlc_sem_init( &p_sys->sem_mt, 0 );
+
+ /* ***** Fill p_context with init values ***** */
+ p_context->codec_tag = ffmpeg_CodecTag( p_dec->fmt_in.i_original_fourcc ?
+ p_dec->fmt_in.i_original_fourcc : p_dec->fmt_in.i_codec );
+
+ /* ***** Get configuration of ffmpeg plugin ***** */
+ p_context->workaround_bugs =
+ var_InheritInteger( p_dec, "avcodec-workaround-bugs" );
+ p_context->err_recognition =
+ var_InheritInteger( p_dec, "avcodec-error-resilience" );
+
+ if( var_CreateGetBool( p_dec, "grayscale" ) )
+ p_context->flags |= AV_CODEC_FLAG_GRAY;
+
+ /* ***** Output always the frames ***** */
+ p_context->flags |= AV_CODEC_FLAG_OUTPUT_CORRUPT;
+
+ i_val = var_CreateGetInteger( p_dec, "avcodec-skiploopfilter" );
+ if( i_val >= 4 ) p_context->skip_loop_filter = AVDISCARD_ALL;
+ else if( i_val == 3 ) p_context->skip_loop_filter = AVDISCARD_NONKEY;
+ else if( i_val == 2 ) p_context->skip_loop_filter = AVDISCARD_BIDIR;
+ else if( i_val == 1 ) p_context->skip_loop_filter = AVDISCARD_NONREF;
+ else p_context->skip_loop_filter = AVDISCARD_DEFAULT;
+
+ if( var_CreateGetBool( p_dec, "avcodec-fast" ) )
+ p_context->flags2 |= AV_CODEC_FLAG2_FAST;
+
+ /* ***** libavcodec frame skipping ***** */
+ p_sys->b_hurry_up = var_CreateGetBool( p_dec, "avcodec-hurry-up" );
+ p_sys->b_show_corrupted = var_CreateGetBool( p_dec, "avcodec-corrupted" );
+
+ i_val = var_CreateGetInteger( p_dec, "avcodec-skip-frame" );
+ if( i_val >= 4 ) p_sys->i_skip_frame = AVDISCARD_ALL;
+ else if( i_val == 3 ) p_sys->i_skip_frame = AVDISCARD_NONKEY;
+ else if( i_val == 2 ) p_sys->i_skip_frame = AVDISCARD_BIDIR;
+ else if( i_val == 1 ) p_sys->i_skip_frame = AVDISCARD_NONREF;
+ else if( i_val == -1 ) p_sys->i_skip_frame = AVDISCARD_NONE;
+ else p_sys->i_skip_frame = AVDISCARD_DEFAULT;
+ p_context->skip_frame = p_sys->i_skip_frame;
+
+ i_val = var_CreateGetInteger( p_dec, "avcodec-skip-idct" );
+ if( i_val >= 4 ) p_context->skip_idct = AVDISCARD_ALL;
+ else if( i_val == 3 ) p_context->skip_idct = AVDISCARD_NONKEY;
+ else if( i_val == 2 ) p_context->skip_idct = AVDISCARD_BIDIR;
+ else if( i_val == 1 ) p_context->skip_idct = AVDISCARD_NONREF;
+ else if( i_val == -1 ) p_context->skip_idct = AVDISCARD_NONE;
+ else p_context->skip_idct = AVDISCARD_DEFAULT;
+
+ /* ***** libavcodec direct rendering ***** */
+ p_sys->b_direct_rendering = false;
+ atomic_init(&p_sys->b_dr_failure, false);
+ if( var_CreateGetBool( p_dec, "avcodec-dr" ) &&
+ (p_codec->capabilities & AV_CODEC_CAP_DR1) &&
+ /* No idea why ... but this fixes flickering on some TSCC streams */
+ p_sys->p_codec->id != AV_CODEC_ID_TSCC &&
+ p_sys->p_codec->id != AV_CODEC_ID_CSCD &&
+ p_sys->p_codec->id != AV_CODEC_ID_CINEPAK )
+ {
+ /* Some codecs set pix_fmt only after the 1st frame has been decoded,
+ * so we need to do another check in ffmpeg_GetFrameBuf() */
+ p_sys->b_direct_rendering = true;
+ }
+
+ p_context->get_format = ZcGetFormat;
+#if 0
+ p_context->get_format = ffmpeg_GetFormat;
+ /* Always use our get_buffer wrapper so we can calculate the
+ * PTS correctly */
+ p_context->get_buffer2 = lavc_GetFrame;
+ p_context->opaque = p_dec;
+#endif
+
+ int i_thread_count = var_InheritInteger( p_dec, "avcodec-threads" );
+ if( i_thread_count <= 0 )
+#if 1
+ {
+ // Pick 5 threads for everything on Pi except for HEVC where the h/w
+ // really limits the useful size to 3
+ i_thread_count = p_codec->id == AV_CODEC_ID_HEVC ? 3 : 5;
+ }
+#else
+ {
+ i_thread_count = vlc_GetCPUCount();
+ if( i_thread_count > 1 )
+ i_thread_count++;
+
+ //FIXME: take in count the decoding time
+#if VLC_WINSTORE_APP
+ i_thread_count = __MIN( i_thread_count, 6 );
+#else
+ i_thread_count = __MIN( i_thread_count, p_codec->id == AV_CODEC_ID_HEVC ? 10 : 6 );
+#endif
+ }
+ i_thread_count = __MIN( i_thread_count, p_codec->id == AV_CODEC_ID_HEVC ? 32 : 16 );
+#endif
+ msg_Dbg( p_dec, "allowing %d thread(s) for decoding", i_thread_count );
+ p_context->thread_count = i_thread_count;
+ p_context->thread_safe_callbacks = true;
+
+ switch( p_codec->id )
+ {
+ case AV_CODEC_ID_MPEG4:
+ case AV_CODEC_ID_H263:
+ p_context->thread_type = 0;
+ break;
+ case AV_CODEC_ID_MPEG1VIDEO:
+ case AV_CODEC_ID_MPEG2VIDEO:
+ p_context->thread_type &= ~FF_THREAD_SLICE;
+ /* fall through */
+# if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 1, 0))
+ case AV_CODEC_ID_H264:
+ case AV_CODEC_ID_VC1:
+ case AV_CODEC_ID_WMV3:
+ p_context->thread_type &= ~FF_THREAD_FRAME;
+# endif
+ default:
+ break;
+ }
+
+ if( p_context->thread_type & FF_THREAD_FRAME )
+ p_dec->i_extra_picture_buffers = 2 * p_context->thread_count;
+
+ /* ***** misc init ***** */
+ date_Init(&p_sys->pts, 1, 30001);
+ date_Set(&p_sys->pts, VLC_TS_INVALID);
+ p_sys->b_first_frame = true;
+ p_sys->i_late_frames = 0;
+ p_sys->b_from_preroll = false;
+
+ /* Set output properties */
+ if (ZcGetVlcChroma(&p_dec->fmt_out.video, p_context->pix_fmt, p_context->sw_pix_fmt) != VLC_SUCCESS)
+ {
+ /* we are doomed. but not really, because most codecs set their pix_fmt later on */
+// p_dec->fmt_out.i_codec = VLC_CODEC_I420;
+ p_dec->fmt_out.i_codec = VLC_CODEC_MMAL_ZC_I420;
+ }
+ p_dec->fmt_out.i_codec = p_dec->fmt_out.video.i_chroma;
+
+ p_dec->fmt_out.video.orientation = p_dec->fmt_in.video.orientation;
+
+ if( p_dec->fmt_in.video.p_palette ) {
+ p_sys->palette_sent = false;
+ p_dec->fmt_out.video.p_palette = malloc( sizeof(video_palette_t) );
+ if( p_dec->fmt_out.video.p_palette )
+ *p_dec->fmt_out.video.p_palette = *p_dec->fmt_in.video.p_palette;
+ } else
+ p_sys->palette_sent = true;
+
+ if (use_drm)
+ p_sys->cma_pool = cma_drmprime_pool_new(p_sys->cma_in_flight_max, p_sys->cma_in_flight_max, false, "drm_avcodec");
+ else if (use_zc)
+ p_sys->cma_pool = cma_buf_pool_new(p_sys->cma_in_flight_max, p_sys->cma_in_flight_max, false, "mmal_avcodec");
+
+ if (p_sys->cma_pool == NULL)
+ {
+ msg_Err(p_dec, "CMA pool alloc failure");
+ goto fail;
+ }
+
+ /* ***** init this codec with special data ***** */
+ ffmpeg_InitCodec( p_dec );
+
+ /* ***** Open the codec ***** */
+ if( OpenVideoCodec( p_dec ) < 0 )
+ {
+ vlc_sem_destroy( &p_sys->sem_mt );
+ free( p_sys );
+ avcodec_free_context( &p_context );
+ return VLC_EGENERIC;
+ }
+
+ p_dec->pf_decode = DecodeVideo;
+ p_dec->pf_flush = Flush;
+
+ /* XXX: Writing input format makes little sense. */
+ if( p_context->profile != FF_PROFILE_UNKNOWN )
+ p_dec->fmt_in.i_profile = p_context->profile;
+ if( p_context->level != FF_LEVEL_UNKNOWN )
+ p_dec->fmt_in.i_level = p_context->level;
+
+#if 1
+ // Most of the time we have nothing useful by way of a format here
+ // wait till we've decoded something
+#else
+ // Update output format
+ if (lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt,
+ p_context->pix_fmt) != 0)
+ {
+ msg_Err(p_dec, "Unable to update format: pix_fmt=%d", p_context->pix_fmt);
+// goto fail;
+ }
+#endif
+
+#if TRACE_ALL
+ msg_Dbg(p_dec, "<<< %s: OK", __func__);
+#endif
+ return VLC_SUCCESS;
+
+fail:
+ MmalAvcodecCloseDecoder(VLC_OBJECT(p_dec));
+
+#if TRACE_ALL
+ msg_Dbg(p_dec, "<<< %s: FAIL", __func__);
+#endif
+
+ return VLC_EGENERIC;
+}
+
+/*****************************************************************************
+ * Flush:
+ *****************************************************************************/
+static void Flush( decoder_t *p_dec )
+{
+ decoder_sys_t *p_sys = p_dec->p_sys;
+ AVCodecContext *p_context = p_sys->p_context;
+
+#if TRACE_ALL
+ msg_Dbg(p_dec, "<<< %s", __func__);
+#endif
+
+ date_Set(&p_sys->pts, VLC_TS_INVALID); /* To make sure we recover properly */
+ p_sys->i_late_frames = 0;
+ cc_Flush( &p_sys->cc );
+
+ /* Abort pictures in order to unblock all avcodec workers threads waiting
+ * for a picture. This will avoid a deadlock between avcodec_flush_buffers
+ * and workers threads */
+// It would probably be good to use AbortPicture but that often deadlocks on close
+// and given that we wait for pics in the main thread it should be unneeded (whereas
+// cma is alloced in the depths of ffmpeg on its own threads)
+// decoder_AbortPictures( p_dec, true );
+ cma_buf_pool_cancel(p_sys->cma_pool);
+
+ post_mt( p_sys );
+ /* do not flush buffers if codec hasn't been opened (theora/vorbis/VC1) */
+ if( avcodec_is_open( p_context ) )
+ avcodec_flush_buffers( p_context );
+ wait_mt( p_sys );
+
+ /* Reset cancel state to false */
+ cma_buf_pool_uncancel(p_sys->cma_pool);
+// decoder_AbortPictures( p_dec, false );
+
+#if TRACE_ALL
+ msg_Dbg(p_dec, ">>> %s", __func__);
+#endif
+
+}
+
+static bool check_block_validity( decoder_sys_t *p_sys, block_t *block )
+{
+ if( !block)
+ return true;
+
+ if( block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED) )
+ {
+ date_Set( &p_sys->pts, VLC_TS_INVALID ); /* To make sure we recover properly */
+ cc_Flush( &p_sys->cc );
+
+ p_sys->i_late_frames = 0;
+ if( block->i_flags & BLOCK_FLAG_CORRUPTED )
+ {
+ block_Release( block );
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool check_block_being_late( decoder_sys_t *p_sys, block_t *block, mtime_t current_time)
+{
+ if( !block )
+ return false;
+ if( block->i_flags & BLOCK_FLAG_PREROLL )
+ {
+ /* Do not care about late frames when prerolling
+ * TODO avoid decoding of non reference frame
+ * (ie all B except for H264 where it depends only on nal_ref_idc) */
+ p_sys->i_late_frames = 0;
+ p_sys->b_from_preroll = true;
+ p_sys->i_last_late_delay = INT64_MAX;
+ }
+
+ if( p_sys->i_late_frames <= 0 )
+ return false;
+
+ if( current_time - p_sys->i_late_frames_start > (5*CLOCK_FREQ))
+ {
+ date_Set( &p_sys->pts, VLC_TS_INVALID ); /* To make sure we recover properly */
+ block_Release( block );
+ p_sys->i_late_frames--;
+ return true;
+ }
+ return false;
+}
+
+static bool check_frame_should_be_dropped( decoder_sys_t *p_sys, AVCodecContext *p_context, bool *b_need_output_picture )
+{
+ if( p_sys->i_late_frames <= 4)
+ return false;
+
+ *b_need_output_picture = false;
+ if( p_sys->i_late_frames < 12 )
+ {
+ p_context->skip_frame =
+ (p_sys->i_skip_frame <= AVDISCARD_NONREF) ?
+ AVDISCARD_NONREF : p_sys->i_skip_frame;
+ }
+ else
+ {
+ /* picture too late, won't decode
+ * but break picture until a new I, and for mpeg4 ...*/
+ p_sys->i_late_frames--; /* needed else it will never be decrease */
+ return true;
+ }
+ return false;
+}
+
+static mtime_t interpolate_next_pts( decoder_t *p_dec, AVFrame *frame )
+{
+ decoder_sys_t *p_sys = p_dec->p_sys;
+ AVCodecContext *p_context = p_sys->p_context;
+
+ if( date_Get( &p_sys->pts ) == VLC_TS_INVALID ||
+ p_sys->pts.i_divider_num == 0 )
+ return VLC_TS_INVALID;
+
+ int i_tick = p_context->ticks_per_frame;
+ if( i_tick <= 0 )
+ i_tick = 1;
+
+ /* interpolate the next PTS */
+ return date_Increment( &p_sys->pts, i_tick + frame->repeat_pict );
+}
+
+static void update_late_frame_count( decoder_t *p_dec, block_t *p_block,
+ mtime_t current_time, mtime_t i_pts,
+ mtime_t i_next_pts )
+{
+ decoder_sys_t *p_sys = p_dec->p_sys;
+ /* Update frame late count (except when doing preroll) */
+ mtime_t i_display_date = VLC_TS_INVALID;
+ if( !p_block || !(p_block->i_flags & BLOCK_FLAG_PREROLL) )
+ i_display_date = decoder_GetDisplayDate( p_dec, i_pts );
+
+ mtime_t i_threshold = i_next_pts != VLC_TS_INVALID ? (i_next_pts - i_pts) / 2 : 20000;
+
+ if( i_display_date > VLC_TS_INVALID && i_display_date + i_threshold <= current_time )
+ {
+ /* Out of preroll, consider only late frames on rising delay */
+ if( p_sys->b_from_preroll )
+ {
+ if( p_sys->i_last_late_delay > current_time - i_display_date )
+ {
+ p_sys->i_last_late_delay = current_time - i_display_date;
+ return;
+ }
+ p_sys->b_from_preroll = false;
+ }
+
+ p_sys->i_late_frames++;
+ if( p_sys->i_late_frames == 1 )
+ p_sys->i_late_frames_start = current_time;
+
+ }
+ else
+ {
+ p_sys->i_late_frames = 0;
+ }
+}
+
+
+static int DecodeSidedata( decoder_t *p_dec, const AVFrame *frame, picture_t *p_pic )
+{
+ decoder_sys_t *p_sys = p_dec->p_sys;
+ bool format_changed = false;
+
+#if (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( 55, 16, 101 ) )
+#define FROM_AVRAT(default_factor, avrat) \
+(uint64_t)(default_factor) * (avrat).num / (avrat).den
+ const AVFrameSideData *metadata =
+ av_frame_get_side_data( frame,
+ AV_FRAME_DATA_MASTERING_DISPLAY_METADATA );
+ if ( metadata )
+ {
+ const AVMasteringDisplayMetadata *hdr_meta =
+ (const AVMasteringDisplayMetadata *) metadata->data;
+ if ( hdr_meta->has_luminance )
+ {
+#define ST2086_LUMA_FACTOR 10000
+ p_pic->format.mastering.max_luminance =
+ FROM_AVRAT(ST2086_LUMA_FACTOR, hdr_meta->max_luminance);
+ p_pic->format.mastering.min_luminance =
+ FROM_AVRAT(ST2086_LUMA_FACTOR, hdr_meta->min_luminance);
+ }
+ if ( hdr_meta->has_primaries )
+ {
+#define ST2086_RED 2
+#define ST2086_GREEN 0
+#define ST2086_BLUE 1
+#define LAV_RED 0
+#define LAV_GREEN 1
+#define LAV_BLUE 2
+#define ST2086_PRIM_FACTOR 50000
+ p_pic->format.mastering.primaries[ST2086_RED*2 + 0] =
+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_RED][0]);
+ p_pic->format.mastering.primaries[ST2086_RED*2 + 1] =
+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_RED][1]);
+ p_pic->format.mastering.primaries[ST2086_GREEN*2 + 0] =
+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_GREEN][0]);
+ p_pic->format.mastering.primaries[ST2086_GREEN*2 + 1] =
+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_GREEN][1]);
+ p_pic->format.mastering.primaries[ST2086_BLUE*2 + 0] =
+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_BLUE][0]);
+ p_pic->format.mastering.primaries[ST2086_BLUE*2 + 1] =
+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->display_primaries[LAV_BLUE][1]);
+ p_pic->format.mastering.white_point[0] =
+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->white_point[0]);
+ p_pic->format.mastering.white_point[1] =
+ FROM_AVRAT(ST2086_PRIM_FACTOR, hdr_meta->white_point[1]);
+ }
+
+ if ( memcmp( &p_dec->fmt_out.video.mastering,
+ &p_pic->format.mastering,
+ sizeof(p_pic->format.mastering) ) )
+ {
+ p_dec->fmt_out.video.mastering = p_pic->format.mastering;
+ format_changed = true;
+ }
+#undef FROM_AVRAT
+ }
+#endif
+#if (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( 55, 60, 100 ) )
+ const AVFrameSideData *metadata_lt =
+ av_frame_get_side_data( frame,
+ AV_FRAME_DATA_CONTENT_LIGHT_LEVEL );
+ if ( metadata_lt )
+ {
+ const AVContentLightMetadata *light_meta =
+ (const AVContentLightMetadata *) metadata_lt->data;
+ p_pic->format.lighting.MaxCLL = light_meta->MaxCLL;
+ p_pic->format.lighting.MaxFALL = light_meta->MaxFALL;
+ if ( memcmp( &p_dec->fmt_out.video.lighting,
+ &p_pic->format.lighting,
+ sizeof(p_pic->format.lighting) ) )
+ {
+ p_dec->fmt_out.video.lighting = p_pic->format.lighting;
+ format_changed = true;
+ }
+ }
+#endif
+
+ if (format_changed && decoder_UpdateVideoFormat( p_dec ))
+ return -1;
+
+ const AVFrameSideData *p_avcc = av_frame_get_side_data( frame, AV_FRAME_DATA_A53_CC );
+ if( p_avcc )
+ {
+ cc_Extract( &p_sys->cc, CC_PAYLOAD_RAW, true, p_avcc->data, p_avcc->size );
+ if( p_sys->cc.b_reorder || p_sys->cc.i_data )
+ {
+ block_t *p_cc = block_Alloc( p_sys->cc.i_data );
+ if( p_cc )
+ {
+ memcpy( p_cc->p_buffer, p_sys->cc.p_data, p_sys->cc.i_data );
+ if( p_sys->cc.b_reorder )
+ p_cc->i_dts = p_cc->i_pts = p_pic->date;
+ else
+ p_cc->i_pts = p_cc->i_dts;
+ decoder_cc_desc_t desc;
+ desc.i_608_channels = p_sys->cc.i_608channels;
+ desc.i_708_channels = p_sys->cc.i_708channels;
+ desc.i_reorder_depth = 4;
+ decoder_QueueCc( p_dec, p_cc, &desc );
+ }
+ cc_Flush( &p_sys->cc );
+ }
+ }
+ return 0;
+}
+
+/*****************************************************************************
+ * DecodeBlock: Called to decode one or more frames
+ *****************************************************************************/
+
+static picture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block, bool *error )
+{
+ decoder_sys_t *p_sys = p_dec->p_sys;
+ AVCodecContext *p_context = p_sys->p_context;
+ /* Boolean if we assume that we should get valid pic as result */
+ bool b_need_output_picture = true;
+
+ /* Boolean for END_OF_SEQUENCE */
+ bool eos_spotted = false;
+
+#if TRACE_ALL
+ msg_Dbg(p_dec, "<<< %s: (buf_size=%d)", __func__, pp_block == NULL || *pp_block == NULL ? 0 : (*pp_block)->i_buffer);
+#endif
+
+ block_t *p_block;
+ mtime_t current_time;
+ picture_t *p_pic = NULL;
+ AVFrame *frame = NULL;
+ cma_buf_t * cb = NULL;
+
+ // By default we are OK
+ *error = false;
+
+ if( !p_context->extradata_size && p_dec->fmt_in.i_extra )
+ {
+ ffmpeg_InitCodec( p_dec );
+ if( !avcodec_is_open( p_context ) )
+ OpenVideoCodec( p_dec );
+ }
+
+ p_block = pp_block ? *pp_block : NULL;
+ if(!p_block && !(p_sys->p_codec->capabilities & AV_CODEC_CAP_DELAY) )
+ return NULL;
+
+ if( !avcodec_is_open( p_context ) )
+ {
+ if( p_block )
+ block_Release( p_block );
+ return NULL;
+ }
+
+ if( !check_block_validity( p_sys, p_block ) )
+ return NULL;
+
+ current_time = mdate();
+ if( p_dec->b_frame_drop_allowed && check_block_being_late( p_sys, p_block, current_time) )
+ {
+ msg_Err( p_dec, "more than 5 seconds of late video -> "
+ "dropping frame (computer too slow ?)" );
+ return NULL;
+ }
+
+
+ /* A good idea could be to decode all I pictures and see for the other */
+
+ /* Defaults that if we aren't in prerolling, we want output picture
+ same for if we are flushing (p_block==NULL) */
+ if( !p_block || !(p_block->i_flags & BLOCK_FLAG_PREROLL) )
+ b_need_output_picture = true;
+ else
+ b_need_output_picture = false;
+
+ /* Change skip_frame config only if hurry_up is enabled */
+ if( p_sys->b_hurry_up )
+ {
+ p_context->skip_frame = p_sys->i_skip_frame;
+
+ /* Check also if we should/can drop the block and move to next block
+ as trying to catchup the speed*/
+ if( p_dec->b_frame_drop_allowed &&
+ check_frame_should_be_dropped( p_sys, p_context, &b_need_output_picture ) )
+ {
+ if( p_block )
+ block_Release( p_block );
+ msg_Warn( p_dec, "More than 11 late frames, dropping frame" );
+ return NULL;
+ }
+ }
+ if( !b_need_output_picture )
+ {
+ p_context->skip_frame = __MAX( p_context->skip_frame,
+ AVDISCARD_NONREF );
+ }
+
+ /*
+ * Do the actual decoding now */
+
+ /* Don't forget that libavcodec requires a little more bytes
+ * that the real frame size */
+ if( p_block && p_block->i_buffer > 0 )
+ {
+ eos_spotted = ( p_block->i_flags & BLOCK_FLAG_END_OF_SEQUENCE ) != 0;
+
+ p_block = block_Realloc( p_block, 0,
+ p_block->i_buffer + FF_INPUT_BUFFER_PADDING_SIZE );
+ if( !p_block )
+ return NULL;
+ p_block->i_buffer -= FF_INPUT_BUFFER_PADDING_SIZE;
+ *pp_block = p_block;
+ memset( p_block->p_buffer + p_block->i_buffer, 0,
+ FF_INPUT_BUFFER_PADDING_SIZE );
+ }
+
+ while( !p_block || p_block->i_buffer > 0 || eos_spotted )
+ {
+ int i_used;
+ AVPacket pkt;
+
+ post_mt( p_sys );
+
+ av_init_packet( &pkt );
+ if( p_block && p_block->i_buffer > 0 )
+ {
+ pkt.data = p_block->p_buffer;
+ pkt.size = p_block->i_buffer;
+ pkt.pts = p_block->i_pts > VLC_TS_INVALID ? p_block->i_pts : AV_NOPTS_VALUE;
+ pkt.dts = p_block->i_dts > VLC_TS_INVALID ? p_block->i_dts : AV_NOPTS_VALUE;
+ }
+ else
+ {
+ /* Return delayed frames if codec has CODEC_CAP_DELAY */
+ pkt.data = NULL;
+ pkt.size = 0;
+ }
+
+ if( !p_sys->palette_sent )
+ {
+ uint8_t *pal = av_packet_new_side_data(&pkt, AV_PKT_DATA_PALETTE, AVPALETTE_SIZE);
+ if (pal) {
+ memcpy(pal, p_dec->fmt_in.video.p_palette->palette, AVPALETTE_SIZE);
+ p_sys->palette_sent = true;
+ }
+ }
+
+ /* Make sure we don't reuse the same timestamps twice */
+ if( p_block )
+ {
+ p_block->i_pts =
+ p_block->i_dts = VLC_TS_INVALID;
+ }
+
+ int ret = avcodec_send_packet(p_context, &pkt);
+ if( ret != 0 && ret != AVERROR(EAGAIN) )
+ {
+ if (ret == AVERROR(ENOMEM) || ret == AVERROR(EINVAL))
+ {
+ msg_Err(p_dec, "avcodec_send_packet critical error");
+ *error = true;
+ }
+ av_packet_unref( &pkt );
+ break;
+ }
+ i_used = ret != AVERROR(EAGAIN) ? pkt.size : 0;
+ av_packet_unref( &pkt );
+
+ frame = av_frame_alloc();
+ if (unlikely(frame == NULL))
+ {
+ *error = true;
+ break;
+ }
+
+ ret = avcodec_receive_frame(p_context, frame);
+ if( ret != 0 && ret != AVERROR(EAGAIN) )
+ {
+ msg_Dbg(p_dec, "No receive");
+ if (ret == AVERROR(ENOMEM) || ret == AVERROR(EINVAL))
+ {
+ msg_Err(p_dec, "avcodec_receive_frame critical error");
+ *error = true;
+ }
+ av_frame_free(&frame);
+ /* After draining, we need to reset decoder with a flush */
+ if( ret == AVERROR_EOF )
+ avcodec_flush_buffers( p_sys->p_context );
+ break;
+ }
+ bool not_received_frame = ret;
+
+ wait_mt( p_sys );
+
+ if( eos_spotted )
+ p_sys->b_first_frame = true;
+
+ if( p_block )
+ {
+ if( p_block->i_buffer <= 0 )
+ eos_spotted = false;
+
+ /* Consumed bytes */
+ p_block->p_buffer += i_used;
+ p_block->i_buffer -= i_used;
+ }
+
+ /* Nothing to display */
+ if( not_received_frame )
+ {
+// msg_Dbg(p_dec, "No rx: used=%d", i_used);
+ av_frame_free(&frame);
+ if( i_used == 0 ) break;
+ continue;
+ }
+
+ /* Compute the PTS */
+#ifdef FF_API_PKT_PTS
+ mtime_t i_pts = frame->pts;
+#else
+ mtime_t i_pts = frame->pkt_pts;
+#endif
+ if (i_pts == AV_NOPTS_VALUE )
+ i_pts = frame->pkt_dts;
+
+ if( i_pts == AV_NOPTS_VALUE )
+ i_pts = date_Get( &p_sys->pts );
+
+ /* Interpolate the next PTS */
+ if( i_pts > VLC_TS_INVALID )
+ date_Set( &p_sys->pts, i_pts );
+
+ const mtime_t i_next_pts = interpolate_next_pts(p_dec, frame);
+
+ update_late_frame_count( p_dec, p_block, current_time, i_pts, i_next_pts);
+
+ if( !b_need_output_picture ||
+// ( !p_sys->p_va && !frame->linesize[0] ) ||
+ (frame->format != AV_PIX_FMT_DRM_PRIME && !frame->linesize[0] ) ||
+ ( p_dec->b_frame_drop_allowed && (frame->flags & AV_FRAME_FLAG_CORRUPT) &&
+ !p_sys->b_show_corrupted ) )
+ {
+ av_frame_free(&frame);
+// msg_Dbg(p_dec, "Bad frame");
+ continue;
+ }
+
+ if( p_context->pix_fmt == AV_PIX_FMT_PAL8
+ && !p_dec->fmt_out.video.p_palette )
+ {
+ /* See AV_PIX_FMT_PAL8 comment in avc_GetVideoFormat(): update the
+ * fmt_out palette and change the fmt_out chroma to request a new
+ * vout */
+ assert( p_dec->fmt_out.video.i_chroma != VLC_CODEC_RGBP );
+
+ video_palette_t *p_palette;
+ p_palette = p_dec->fmt_out.video.p_palette
+ = malloc( sizeof(video_palette_t) );
+ if( !p_palette )
+ {
+ *error = true;
+ av_frame_free(&frame);
+ break;
+ }
+ static_assert( sizeof(p_palette->palette) == AVPALETTE_SIZE,
+ "Palette size mismatch between vlc and libavutil" );
+ assert( frame->data[1] != NULL );
+ memcpy( p_palette->palette, frame->data[1], AVPALETTE_SIZE );
+ p_palette->i_entries = AVPALETTE_COUNT;
+ p_dec->fmt_out.video.i_chroma = VLC_CODEC_RGBP;
+ if( decoder_UpdateVideoFormat( p_dec ) )
+ {
+ av_frame_free(&frame);
+ continue;
+ }
+ }
+
+#if 1
+ if (lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt,
+ p_context->sw_pix_fmt) != 0)
+ {
+ msg_Err(p_dec, "Failed to update format");
+ goto fail;
+ }
+
+ if ((p_pic = decoder_NewPicture(p_dec)) == NULL)
+ {
+ msg_Err(p_dec, "Failed to allocate pic");
+ goto fail;
+ }
+
+ if (p_sys->use_drm)
+ {
+ cb = cma_drmprime_pool_alloc_buf(p_sys->cma_pool, frame);
+ if (cb == NULL)
+ {
+ msg_Err(p_dec, "Failed to alloc CMA buf from DRM_PRIME");
+ goto fail;
+ }
+ }
+ else
+ {
+ cb = cma_buf_ref(av_rpi_zc_buf_v(frame->buf[0]));
+ if (cb == NULL)
+ {
+ msg_Err(p_dec, "Frame has no attached CMA buffer");
+ goto fail;
+ }
+ }
+
+ if (cma_buf_pic_attach(cb, p_pic) != 0)
+ {
+ cma_buf_unref(cb); // Undo the in_flight
+ char dbuf0[5];
+ msg_Err(p_dec, "Failed to attach bufs to pic: fmt=%s", str_fourcc(dbuf0, p_pic->format.i_chroma));
+ goto fail;
+ }
+ cb = NULL; // Now attached to pic
+
+ // ****** Set planes etc.
+ set_pic_from_frame(p_pic, frame);
+#else
+ picture_t *p_pic = frame->opaque;
+ if( p_pic == NULL )
+ { /* When direct rendering is not used, get_format() and get_buffer()
+ * might not be called. The output video format must be set here
+ * then picture buffer can be allocated. */
+ if (p_sys->p_va == NULL
+ && lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt,
+ p_context->pix_fmt) == 0)
+ p_pic = decoder_NewPicture(p_dec);
+
+ if( !p_pic )
+ {
+ av_frame_free(&frame);
+ break;
+ }
+
+ /* Fill picture_t from AVFrame */
+ if( lavc_CopyPicture( p_dec, p_pic, frame ) != VLC_SUCCESS )
+ {
+ av_frame_free(&frame);
+ picture_Release( p_pic );
+ break;
+ }
+ }
+ else
+ {
+ /* Some codecs can return the same frame multiple times. By the
+ * time that the same frame is returned a second time, it will be
+ * too late to clone the underlying picture. So clone proactively.
+ * A single picture CANNOT be queued multiple times.
+ */
+ p_pic = picture_Clone( p_pic );
+ if( unlikely(p_pic == NULL) )
+ {
+ av_frame_free(&frame);
+ break;
+ }
+ }
+#endif
+
+ if( !p_dec->fmt_in.video.i_sar_num || !p_dec->fmt_in.video.i_sar_den )
+ {
+ /* Fetch again the aspect ratio in case it changed */
+ p_dec->fmt_out.video.i_sar_num
+ = p_context->sample_aspect_ratio.num;
+ p_dec->fmt_out.video.i_sar_den
+ = p_context->sample_aspect_ratio.den;
+
+ if( !p_dec->fmt_out.video.i_sar_num || !p_dec->fmt_out.video.i_sar_den )
+ {
+ p_dec->fmt_out.video.i_sar_num = 1;
+ p_dec->fmt_out.video.i_sar_den = 1;
+ }
+ }
+
+ p_pic->date = i_pts;
+ /* Hack to force display of still pictures */
+ p_pic->b_force = p_sys->b_first_frame;
+ p_pic->i_nb_fields = 2 + frame->repeat_pict;
+ p_pic->b_progressive = !frame->interlaced_frame;
+ p_pic->b_top_field_first = frame->top_field_first;
+
+ if (DecodeSidedata(p_dec, frame, p_pic))
+ i_pts = VLC_TS_INVALID;
+
+ av_frame_free(&frame);
+
+ /* Send decoded frame to vout */
+ if (i_pts > VLC_TS_INVALID)
+ {
+ p_sys->b_first_frame = false;
+#if TRACE_ALL
+ msg_Dbg(p_dec, ">>> %s: Got pic", __func__);
+#endif
+ return p_pic;
+ }
+ else
+ picture_Release( p_pic );
+ }
+
+ if( p_block )
+ block_Release( p_block );
+
+#if TRACE_ALL
+ msg_Dbg(p_dec, ">>> %s: NULL", __func__);
+#endif
+ return NULL;
+
+fail:
+#if TRACE_ALL
+ msg_Dbg(p_dec, ">>> %s: FAIL", __func__);
+#endif
+ av_frame_free(&frame);
+ if (p_pic != NULL)
+ picture_Release(p_pic);
+ if (p_block != NULL)
+ block_Release(p_block);
+ *error = true;
+ return NULL;
+}
+
+static int DecodeVideo( decoder_t *p_dec, block_t *p_block )
+{
+ block_t **pp_block = p_block ? &p_block : NULL;
+ picture_t *p_pic;
+ bool error = false;
+ while( ( p_pic = DecodeBlock( p_dec, pp_block, &error ) ) != NULL )
+ decoder_QueueVideo( p_dec, p_pic );
+ return VLCDEC_SUCCESS;
+// Easiest to just ignore all errors - returning a real error seems to
+// kill output forever
+// return error ? VLCDEC_ECRITICAL : VLCDEC_SUCCESS;
+}
+
+/*****************************************************************************
+ * EndVideo: decoder destruction
+ *****************************************************************************
+ * This function is called when the thread ends after a successful
+ * initialization.
+ *****************************************************************************/
+static void MmalAvcodecCloseDecoder( vlc_object_t *obj )
+{
+ decoder_t *p_dec = (decoder_t *)obj;
+ decoder_sys_t *p_sys = p_dec->p_sys;
+ AVCodecContext *ctx = p_sys->p_context;
+// void *hwaccel_context;
+
+ msg_Dbg(obj, "<<< %s", __func__);
+
+ post_mt( p_sys );
+
+ cma_buf_pool_cancel(p_sys->cma_pool); // Abort any pending frame allocs
+
+ /* do not flush buffers if codec hasn't been opened (theora/vorbis/VC1) */
+ if( avcodec_is_open( ctx ) )
+ avcodec_flush_buffers( ctx );
+
+ if (!p_sys->use_drm)
+ av_rpi_zc_uninit2(ctx);
+
+ wait_mt( p_sys );
+
+ cc_Flush( &p_sys->cc );
+
+// hwaccel_context = ctx->hwaccel_context;
+ avcodec_free_context( &ctx );
+
+// if( p_sys->p_va )
+// vlc_va_Delete( p_sys->p_va, &hwaccel_context );
+
+ cma_vcsm_exit(p_sys->vcsm_init_type);
+
+ vlc_sem_destroy( &p_sys->sem_mt );
+ free( p_sys );
+}
+
+/*****************************************************************************
+ * ffmpeg_InitCodec: setup codec extra initialization data for ffmpeg
+ *****************************************************************************/
+static void ffmpeg_InitCodec( decoder_t *p_dec )
+{
+ decoder_sys_t *p_sys = p_dec->p_sys;
+ size_t i_size = p_dec->fmt_in.i_extra;
+
+ if( !i_size ) return;
+
+ if( p_sys->p_codec->id == AV_CODEC_ID_SVQ3 )
+ {
+ uint8_t *p;
+
+ p_sys->p_context->extradata_size = i_size + 12;
+ p = p_sys->p_context->extradata =
+ av_malloc( p_sys->p_context->extradata_size +
+ FF_INPUT_BUFFER_PADDING_SIZE );
+ if( !p )
+ return;
+
+ memcpy( &p[0], "SVQ3", 4 );
+ memset( &p[4], 0, 8 );
+ memcpy( &p[12], p_dec->fmt_in.p_extra, i_size );
+
+ /* Now remove all atoms before the SMI one */
+ if( p_sys->p_context->extradata_size > 0x5a &&
+ strncmp( (char*)&p[0x56], "SMI ", 4 ) )
+ {
+ uint8_t *psz = &p[0x52];
+
+ while( psz < &p[p_sys->p_context->extradata_size - 8] )
+ {
+ uint_fast32_t atom_size = GetDWBE( psz );
+ if( atom_size <= 1 )
+ {
+ /* FIXME handle 1 as long size */
+ break;
+ }
+ if( !strncmp( (char*)&psz[4], "SMI ", 4 ) )
+ {
+ memmove( &p[0x52], psz,
+ &p[p_sys->p_context->extradata_size] - psz );
+ break;
+ }
+
+ psz += atom_size;
+ }
+ }
+ }
+ else
+ {
+ p_sys->p_context->extradata_size = i_size;
+ p_sys->p_context->extradata =
+ av_malloc( i_size + FF_INPUT_BUFFER_PADDING_SIZE );
+ if( p_sys->p_context->extradata )
+ {
+ memcpy( p_sys->p_context->extradata,
+ p_dec->fmt_in.p_extra, i_size );
+ memset( p_sys->p_context->extradata + i_size,
+ 0, FF_INPUT_BUFFER_PADDING_SIZE );
+ }
+ }
+}
+
+
+vlc_module_begin()
+ set_category( CAT_INPUT )
+ set_subcategory( SUBCAT_INPUT_VCODEC )
+ set_shortname(N_("MMAL avcodec"))
+ set_description(N_("MMAL buffered avcodec "))
+ set_capability("video decoder", 80)
+ add_shortcut("mmal_avcodec")
+ add_integer(MMAL_AVCODEC_BUFFERS, -1, MMAL_AVCODEC_BUFFERS_TEXT,
+ MMAL_AVCODEC_BUFFERS_LONGTEXT, true)
+ set_callbacks(MmalAvcodecOpenDecoder, MmalAvcodecCloseDecoder)
+vlc_module_end()
+
diff --git a/modules/hw/mmal/mmal_cma.c b/modules/hw/mmal/mmal_cma.c
new file mode 100644
index 0000000000..4dfedbc57c
--- /dev/null
+++ b/modules/hw/mmal/mmal_cma.c
@@ -0,0 +1,672 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdatomic.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include <interface/vcsm/user-vcsm.h>
+
+#include <vlc_common.h>
+#include <vlc_picture.h>
+
+#include "mmal_cma.h"
+#include "mmal_cma_int.h"
+#include "mmal_picture.h"
+
+#include <assert.h>
+
+#define TRACE_ALL 0
+
+//-----------------------------------------------------------------------------
+//
+// Generic pool functions
+// Knows nothing about pool entries
+
+typedef void * cma_pool_alloc_fn(void * v, size_t size);
+typedef void cma_pool_free_fn(void * v, void * el, size_t size);
+
+#if TRACE_ALL
+static atomic_int pool_seq;
+#endif
+
+// Pool structure
+// Ref count is held by pool owner and pool els that have been got
+// Els in the pool do not count towards its ref count
+struct cma_pool_fixed_s
+{
+ atomic_int ref_count;
+
+ vlc_mutex_t lock;
+ unsigned int n_in;
+ unsigned int n_out;
+ unsigned int pool_size;
+ int flight_size;
+ size_t el_size;
+ void ** pool;
+
+ bool cancel;
+ int in_flight;
+ vlc_cond_t flight_cond;
+
+ void * alloc_v;
+ cma_pool_alloc_fn * el_alloc_fn;
+ cma_pool_free_fn * el_free_fn;
+ cma_pool_on_put_fn * on_put_fn;
+ cma_pool_on_delete_fn * on_delete_fn;
+
+ const char * name;
+#if TRACE_ALL
+ int seq;
+#endif
+};
+
+static void cma_pool_fixed_on_put_null_cb(void * v)
+{
+ VLC_UNUSED(v);
+}
+
+static inline unsigned int inc_mod(const unsigned int n, const unsigned int m)
+{
+ return n + 1 >= m ? 0 : n + 1;
+}
+
+static void free_pool(const cma_pool_fixed_t * const p, void ** const pool,
+ const unsigned int pool_size, const size_t el_size)
+{
+ if (pool == NULL)
+ return;
+
+ for (unsigned int n = 0; n != pool_size; ++n)
+ if (pool[n] != NULL)
+ p->el_free_fn(p->alloc_v, pool[n], el_size);
+ free(pool);
+}
+
+// Just kill this - no checks
+static void cma_pool_fixed_delete(cma_pool_fixed_t * const p)
+{
+ cma_pool_on_delete_fn *const on_delete_fn = p->on_delete_fn;
+ void *const v = p->alloc_v;
+
+ free_pool(p, p->pool, p->pool_size, p->el_size);
+
+ if (p->name != NULL)
+ free((void *)p->name); // Discard const
+
+ vlc_cond_destroy(&p->flight_cond);
+ vlc_mutex_destroy(&p->lock);
+ free(p);
+
+ // Inform our container that we are dead (if it cares)
+ if (on_delete_fn)
+ on_delete_fn(v);
+}
+
+static void cma_pool_fixed_unref(cma_pool_fixed_t * const p)
+{
+ if (atomic_fetch_sub(&p->ref_count, 1) <= 1)
+ cma_pool_fixed_delete(p);
+}
+
+static void cma_pool_fixed_ref(cma_pool_fixed_t * const p)
+{
+ atomic_fetch_add(&p->ref_count, 1);
+}
+
+static void cma_pool_fixed_inc_in_flight(cma_pool_fixed_t * const p)
+{
+ vlc_mutex_lock(&p->lock);
+ ++p->in_flight;
+ vlc_mutex_unlock(&p->lock);
+}
+
+static void cma_pool_fixed_dec_in_flight(cma_pool_fixed_t * const p)
+{
+ vlc_mutex_lock(&p->lock);
+ if (--p->in_flight == 0)
+ vlc_cond_signal(&p->flight_cond);
+ vlc_mutex_unlock(&p->lock);
+}
+
+static void * cma_pool_fixed_get(cma_pool_fixed_t * const p, const size_t req_el_size, const bool inc_flight, const bool no_pool)
+{
+ void * v = NULL;
+
+ vlc_mutex_lock(&p->lock);
+
+ for (;;)
+ {
+ if (req_el_size != p->el_size)
+ {
+ void ** const deadpool = p->pool;
+ const size_t dead_size = p->el_size;
+ const unsigned int dead_n = p->pool_size;
+
+ p->pool = NULL;
+ p->n_in = 0;
+ p->n_out = 0;
+ p->el_size = req_el_size;
+
+ if (deadpool != NULL)
+ {
+ vlc_mutex_unlock(&p->lock);
+ // Do the free old op outside the mutex in case the free is slow
+ free_pool(p, deadpool, dead_n, dead_size);
+ vlc_mutex_lock(&p->lock);
+ continue;
+ }
+ }
+
+ // Late abort if flush or cancel so we can still kill the pool
+ if (req_el_size == 0 || p->cancel)
+ {
+ vlc_mutex_unlock(&p->lock);
+ return NULL;
+ }
+
+ if (p->pool != NULL && !no_pool)
+ {
+ v = p->pool[p->n_in];
+ if (v != NULL)
+ {
+ p->pool[p->n_in] = NULL;
+ p->n_in = inc_mod(p->n_in, p->pool_size);
+ break;
+ }
+ }
+
+ if (p->in_flight <= 0)
+ break;
+
+ vlc_cond_wait(&p->flight_cond, &p->lock);
+ }
+
+ if (inc_flight)
+ ++p->in_flight;
+
+ vlc_mutex_unlock(&p->lock);
+
+ if (v == NULL && req_el_size != 0)
+ v = p->el_alloc_fn(p->alloc_v, req_el_size);
+
+ // Tag ref
+ if (v != NULL)
+ cma_pool_fixed_ref(p);
+ // Remove flight if we set it and error
+ else if (inc_flight)
+ cma_pool_fixed_dec_in_flight(p);
+
+ return v;
+}
+
+static void cma_pool_fixed_put(cma_pool_fixed_t * const p, void * v, const size_t el_size, const bool was_in_flight)
+{
+ p->on_put_fn(v);
+
+ vlc_mutex_lock(&p->lock);
+
+ if (el_size == p->el_size && (p->pool == NULL || p->pool[p->n_out] == NULL))
+ {
+ if (p->pool == NULL)
+ p->pool = calloc(p->pool_size, sizeof(void*));
+
+ p->pool[p->n_out] = v;
+ p->n_out = inc_mod(p->n_out, p->pool_size);
+ v = NULL;
+ }
+
+ if (was_in_flight)
+ --p->in_flight;
+
+ vlc_mutex_unlock(&p->lock);
+
+ vlc_cond_signal(&p->flight_cond);
+
+ if (v != NULL)
+ p->el_free_fn(p->alloc_v, v, el_size);
+
+ cma_pool_fixed_unref(p);
+}
+
+static int cma_pool_fixed_resize(cma_pool_fixed_t * const p,
+ const unsigned int new_pool_size, const int new_flight_size)
+{
+ void ** dead_pool = NULL;
+ size_t dead_size = 0;
+ unsigned int dead_n = 0;
+
+ // This makes this non-reentrant but saves us a lot of time in the normal
+ // "nothing happens" case
+ if (p->pool_size == new_pool_size && p->flight_size == new_flight_size)
+ return 0;
+
+ vlc_mutex_lock(&p->lock);
+
+ if (p->pool != NULL && new_pool_size != p->pool_size)
+ {
+ void ** const new_pool = calloc(new_pool_size, sizeof(void*));
+ unsigned int d, s;
+ dead_pool = p->pool;
+ dead_size = p->el_size;
+ dead_n = p->pool_size;
+
+ if (new_pool == NULL)
+ {
+ vlc_mutex_unlock(&p->lock);
+ return -1;
+ }
+
+ for (d = 0, s = p->n_in; d != new_pool_size && (new_pool[d] = dead_pool[s]) != NULL; ++d, s = inc_mod(s, dead_n))
+ dead_pool[s] = NULL;
+
+ p->n_out = 0;
+ p->n_in = (d != new_pool_size) ? d : 0;
+ p->pool = new_pool;
+ }
+
+ p->pool_size = new_pool_size;
+ if (new_flight_size > p->flight_size)
+ vlc_cond_broadcast(&p->flight_cond); // Lock still active so nothing happens till we release it
+ p->in_flight += p->flight_size - new_flight_size;
+ p->flight_size = new_flight_size;
+
+ vlc_mutex_unlock(&p->lock);
+
+ free_pool(p, dead_pool, dead_n, dead_size);
+ return 0;
+}
+
+static int cma_pool_fixed_fill(cma_pool_fixed_t * const p, const size_t el_size)
+{
+ for (;;)
+ {
+ vlc_mutex_lock(&p->lock);
+ bool done = el_size == p->el_size && p->pool != NULL && p->pool[p->n_out] != NULL;
+ vlc_mutex_unlock(&p->lock);
+ if (done)
+ break;
+ void * buf = cma_pool_fixed_get(p, el_size, false, true);
+ if (buf == NULL)
+ return -ENOMEM;
+ cma_pool_fixed_put(p, buf, el_size, false);
+ }
+ return 0;
+}
+
+static void cma_pool_fixed_cancel(cma_pool_fixed_t * const p)
+{
+ vlc_mutex_lock(&p->lock);
+ p->cancel = true;
+ vlc_cond_broadcast(&p->flight_cond);
+ vlc_mutex_unlock(&p->lock);
+}
+
+static void cma_pool_fixed_uncancel(cma_pool_fixed_t * const p)
+{
+ vlc_mutex_lock(&p->lock);
+ p->cancel = false;
+ vlc_mutex_unlock(&p->lock);
+}
+
+
+// Purge pool & unref
+static void cma_pool_fixed_kill(cma_pool_fixed_t * const p)
+{
+ if (p == NULL)
+ return;
+
+ // This flush is not strictly needed but it reclaims what memory we can reclaim asap
+ cma_pool_fixed_get(p, 0, false, false);
+ cma_pool_fixed_unref(p);
+}
+
+// Create a new pool
+cma_pool_fixed_t*
+cma_pool_fixed_new(const unsigned int pool_size,
+ const int flight_size,
+ void * const alloc_v,
+ cma_pool_alloc_fn * const alloc_fn, cma_pool_free_fn * const free_fn,
+ cma_pool_on_put_fn * const on_put_fn, cma_pool_on_delete_fn * const on_delete_fn,
+ const char * const name)
+{
+ cma_pool_fixed_t* const p = calloc(1, sizeof(cma_pool_fixed_t));
+ if (p == NULL)
+ return NULL;
+
+ atomic_store(&p->ref_count, 1);
+ vlc_mutex_init(&p->lock);
+ vlc_cond_init(&p->flight_cond);
+
+ p->pool_size = pool_size;
+ p->flight_size = flight_size;
+ p->in_flight = -flight_size;
+
+ p->alloc_v = alloc_v;
+ p->el_alloc_fn = alloc_fn;
+ p->el_free_fn = free_fn;
+ p->on_put_fn = on_put_fn;
+ p->on_delete_fn = on_delete_fn;
+ p->name = name == NULL ? NULL : strdup(name);
+#if TRACE_ALL
+ p->seq = atomic_fetch_add(&pool_seq, 1);
+#endif
+
+ return p;
+}
+
+// ---------------------------------------------------------------------------
+//
+// CMA buffer functions - uses cma_pool_fixed for pooling
+
+void cma_pool_delete(cma_buf_t * const cb)
+{
+ assert(atomic_load(&cb->ref_count) == 0);
+#if TRACE_ALL
+ cb->cbp->alloc_size -= cb->size;
+ --cb->cbp->alloc_n;
+ fprintf(stderr, "%s[%d:%s]: N=%d, Total=%d\n", __func__, cb->cbp->pool->seq, cb->cbp->pool->name, cb->cbp->alloc_n, cb->cbp->alloc_size);
+#endif
+
+ if (cb->ctx2 != NULL)
+ cb->ctx2->destroy(cb->ctx2);
+
+ if (cb->mmap != MAP_FAILED)
+ {
+ if (cb->cbp->buf_type != CMA_BUF_TYPE_VCSM)
+ munmap(cb->mmap, cb->size);
+ else
+ vcsm_unlock_hdl(cb->vcsm_h);
+ }
+ if (cb->fd != -1)
+ close(cb->fd);
+ if (cb->vcsm_h != 0)
+ vcsm_free(cb->vcsm_h);
+ free(cb);
+}
+
+static void cma_pool_free_cb(void * v, void * el, size_t size)
+{
+ VLC_UNUSED(v);
+ VLC_UNUSED(size);
+
+ cma_pool_delete(el);
+}
+
+static void * cma_pool_alloc_cb(void * v, size_t size)
+{
+ cma_buf_pool_t * const cbp = v;
+
+ cma_buf_t * const cb = malloc(sizeof(cma_buf_t));
+ if (cb == NULL)
+ return NULL;
+
+ *cb = (cma_buf_t){
+ .ref_count = ATOMIC_VAR_INIT(0),
+ .cbp = cbp,
+ .in_flight = 0,
+ .size = size,
+ .vcsm_h = 0,
+ .vc_h = 0,
+ .fd = -1,
+ .mmap = MAP_FAILED,
+ .ctx2 = NULL
+ };
+#if TRACE_ALL
+ cb->cbp->alloc_size += cb->size;
+ ++cb->cbp->alloc_n;
+ fprintf(stderr, "%s[%d:%s]: N=%d, Total=%d\n", __func__, cbp->pool->seq, cbp->pool->name, cbp->alloc_n, cbp->alloc_size);
+#endif
+
+ // 0x80 is magic value to force full ARM-side mapping - otherwise
+ // cache requests can cause kernel crashes
+ if ((cb->vcsm_h = vcsm_malloc_cache(size, VCSM_CACHE_TYPE_HOST | 0x80, "VLC frame")) == 0)
+ {
+#if TRACE_ALL
+ fprintf(stderr, "vcsm_malloc_cache fail\n");
+#endif
+ goto fail;
+ }
+
+ if ((cb->vc_h = vcsm_vc_hdl_from_hdl(cb->vcsm_h)) == 0)
+ {
+#if TRACE_ALL
+ fprintf(stderr, "vcsm_vc_hdl_from_hdl fail\n");
+#endif
+ goto fail;
+ }
+
+ if (cbp->buf_type == CMA_BUF_TYPE_CMA)
+ {
+ if ((cb->fd = vcsm_export_dmabuf(cb->vcsm_h)) == -1)
+ {
+#if TRACE_ALL
+ fprintf(stderr, "vcsm_export_dmabuf fail\n");
+#endif
+ goto fail;
+ }
+
+ if ((cb->mmap = mmap(NULL, cb->size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, cb->fd, 0)) == MAP_FAILED)
+ goto fail;
+ }
+ else
+ {
+ void * arm_addr;
+ if ((arm_addr = vcsm_lock(cb->vcsm_h)) == NULL)
+ {
+#if TRACE_ALL
+ fprintf(stderr, "vcsm_lock fail\n");
+#endif
+ goto fail;
+ }
+ cb->mmap = arm_addr;
+ }
+
+ cb->vc_addr = vcsm_vc_addr_from_hdl(cb->vcsm_h);
+
+ return cb;
+
+fail:
+ cma_pool_delete(cb);
+ return NULL;
+}
+
+// Pool has died - safe now to exit vcsm
+static void cma_buf_pool_on_delete_cb(void * v)
+{
+ cma_buf_pool_t * const cbp = v;
+
+ switch (cbp->buf_type)
+ {
+ case CMA_BUF_TYPE_CMA:
+ cma_vcsm_exit(VCSM_INIT_CMA);
+ break;
+ case CMA_BUF_TYPE_VCSM:
+ cma_vcsm_exit(VCSM_INIT_LEGACY);
+ break;
+ default:
+ break;
+ }
+ free(cbp);
+}
+
+void cma_buf_pool_cancel(cma_buf_pool_t * const cbp)
+{
+ if (cbp == NULL || cbp->pool == NULL)
+ return;
+
+ cma_pool_fixed_cancel(cbp->pool);
+}
+
+void cma_buf_pool_uncancel(cma_buf_pool_t * const cbp)
+{
+ if (cbp == NULL || cbp->pool == NULL)
+ return;
+
+ cma_pool_fixed_uncancel(cbp->pool);
+}
+
+// User finished with pool
+void cma_buf_pool_delete(cma_buf_pool_t * const cbp)
+{
+ if (cbp == NULL)
+ return;
+
+ if (cbp->pool != NULL)
+ {
+ // We will call cma_buf_pool_on_delete_cb when the pool finally dies
+ // (might be now) which will free up our env.
+ cma_pool_fixed_kill(cbp->pool);
+ }
+ else
+ {
+ // Had no pool for some reason (error) but must still finish cleanup
+ cma_buf_pool_on_delete_cb(cbp);
+ }
+}
+
+int cma_buf_pool_fill(cma_buf_pool_t * const cbp, const size_t el_size)
+{
+ return cma_pool_fixed_fill(cbp->pool, el_size);
+}
+
+int cma_buf_pool_resize(cma_buf_pool_t * const cbp,
+ const unsigned int new_pool_size, const int new_flight_size)
+{
+ return cma_pool_fixed_resize(cbp->pool, new_pool_size, new_flight_size);
+}
+
+cma_buf_pool_t * cma_buf_pool_new(const unsigned int pool_size, const unsigned int flight_size, const bool all_in_flight, const char * const name)
+{
+ vcsm_init_type_t const init_type = cma_vcsm_init();
+ if (init_type == VCSM_INIT_NONE)
+ return NULL;
+
+ cma_buf_pool_t * const cbp = calloc(1, sizeof(cma_buf_pool_t));
+ if (cbp == NULL)
+ return NULL;
+
+ cbp->buf_type = (init_type == VCSM_INIT_CMA) ? CMA_BUF_TYPE_CMA : CMA_BUF_TYPE_VCSM;
+ cbp->all_in_flight = all_in_flight;
+
+ if ((cbp->pool = cma_pool_fixed_new(pool_size, flight_size, cbp,
+ cma_pool_alloc_cb, cma_pool_free_cb,
+ cma_pool_fixed_on_put_null_cb, cma_buf_pool_on_delete_cb,
+ name)) == NULL)
+ goto fail;
+ return cbp;
+
+fail:
+ cma_buf_pool_delete(cbp);
+ return NULL;
+}
+
+
+void cma_buf_in_flight(cma_buf_t * const cb)
+{
+ if (!cb->cbp->all_in_flight)
+ {
+ assert(!cb->in_flight);
+ cb->in_flight = true;
+ cma_pool_fixed_inc_in_flight(cb->cbp->pool);
+ }
+}
+
+void cma_buf_end_flight(cma_buf_t * const cb)
+{
+ if (cb != NULL && !cb->cbp->all_in_flight && cb->in_flight)
+ {
+ cb->in_flight = false;
+ cma_pool_fixed_dec_in_flight(cb->cbp->pool);
+ }
+}
+
+
+// Return vcsm handle
+unsigned int cma_buf_vcsm_handle(cma_buf_t * const cb)
+{
+ if (cb->vcsm_h == 0 && cb->fd != -1)
+ cb->vcsm_h = vcsm_import_dmabuf(cb->fd, "vlc-drmprime");
+ return cb->vcsm_h;
+}
+
+size_t cma_buf_size(const cma_buf_t * const cb)
+{
+ return cb->size;
+}
+
+int cma_buf_add_context2(cma_buf_t *const cb, picture_context_t * const ctx2)
+{
+ if (cb->ctx2 != NULL)
+ return VLC_EGENERIC;
+
+ cb->ctx2 = ctx2;
+ return VLC_SUCCESS;
+}
+
+unsigned int cma_buf_vc_handle(cma_buf_t *const cb)
+{
+ if (cb->vc_h == 0)
+ {
+ const int vcsm_h = cma_buf_vcsm_handle(cb);
+ if (vcsm_h != 0)
+ cb->vc_h = vcsm_vc_hdl_from_hdl(vcsm_h);
+ }
+ return cb->vc_h;
+}
+
+unsigned int cma_buf_vc_addr(cma_buf_t *const cb)
+{
+ if (cb->vc_addr == 0)
+ {
+ const int vcsm_h = cma_buf_vcsm_handle(cb);
+ if (vcsm_h != 0)
+ cb->vc_addr = vcsm_vc_addr_from_hdl(vcsm_h);
+ }
+ return cb->vc_addr;
+}
+
+
+picture_context_t * cma_buf_context2(const cma_buf_t *const cb)
+{
+ return cb->ctx2;
+}
+
+
+void cma_buf_unref(cma_buf_t * const cb)
+{
+ if (cb == NULL)
+ return;
+ if (atomic_fetch_sub(&cb->ref_count, 1) <= 1)
+ {
+ const bool was_in_flight = cb->in_flight;
+ cb->in_flight = false;
+ cma_pool_fixed_put(cb->cbp->pool, cb, cb->size, was_in_flight);
+ }
+}
+
+cma_buf_t * cma_buf_ref(cma_buf_t * const cb)
+{
+ if (cb == NULL)
+ return NULL;
+ atomic_fetch_add(&cb->ref_count, 1);
+ return cb;
+}
+
+cma_buf_t * cma_buf_pool_alloc_buf(cma_buf_pool_t * const cbp, const size_t size)
+{
+ cma_buf_t *const cb = cma_pool_fixed_get(cbp->pool, size, cbp->all_in_flight, false);
+
+ if (cb == NULL)
+ return NULL;
+
+ cb->type = CMA_BUF_TYPE_CMA;
+ cb->in_flight = cbp->all_in_flight;
+ // When 1st allocated or retrieved from the pool the block will have a
+ // ref count of 0 so ref here
+ return cma_buf_ref(cb);
+}
+
diff --git a/modules/hw/mmal/mmal_cma.h b/modules/hw/mmal/mmal_cma.h
new file mode 100644
index 0000000000..fd8ea34562
--- /dev/null
+++ b/modules/hw/mmal/mmal_cma.h
@@ -0,0 +1,43 @@
+#ifndef VLC_MMAL_MMAL_CMA_H_
+#define VLC_MMAL_MMAL_CMA_H_
+
+#include <vlc_picture.h>
+
+struct cma_buf_s;
+typedef struct cma_buf_s cma_buf_t;
+
+void cma_buf_in_flight(cma_buf_t * const cb);
+void cma_buf_end_flight(cma_buf_t * const cb);
+unsigned int cma_buf_vcsm_handle(cma_buf_t * const cb);
+size_t cma_buf_size(const cma_buf_t * const cb);
+int cma_buf_add_context2(cma_buf_t *const cb, picture_context_t * const ctx2);
+unsigned int cma_buf_vc_handle(cma_buf_t *const cb);
+unsigned int cma_buf_vc_addr(cma_buf_t *const cb);
+picture_context_t * cma_buf_context2(const cma_buf_t *const cb);
+
+void cma_buf_unref(cma_buf_t * const cb);
+cma_buf_t * cma_buf_ref(cma_buf_t * const cb);
+
+struct cma_buf_pool_s;
+typedef struct cma_buf_pool_s cma_buf_pool_t;
+
+cma_buf_t * cma_buf_pool_alloc_buf(cma_buf_pool_t * const p, const size_t size);
+void cma_buf_pool_cancel(cma_buf_pool_t * const cbp);
+void cma_buf_pool_uncancel(cma_buf_pool_t * const cbp);
+void cma_buf_pool_delete(cma_buf_pool_t * const p);
+int cma_buf_pool_fill(cma_buf_pool_t * const cbp, const size_t el_size);
+int cma_buf_pool_resize(cma_buf_pool_t * const cbp,
+ const unsigned int new_pool_size, const int new_flight_size);
+cma_buf_pool_t * cma_buf_pool_new(const unsigned int pool_size, const unsigned int flight_size,
+ const bool all_in_flight, const char * const name);
+
+static inline void cma_buf_pool_deletez(cma_buf_pool_t ** const pp)
+{
+ cma_buf_pool_t * const p = *pp;
+ if (p != NULL) {
+ *pp = NULL;
+ cma_buf_pool_delete(p);
+ }
+}
+
+#endif // VLC_MMAL_MMAL_CMA_H_
diff --git a/modules/hw/mmal/mmal_cma_drmprime.c b/modules/hw/mmal/mmal_cma_drmprime.c
new file mode 100644
index 0000000000..d58bfdd944
--- /dev/null
+++ b/modules/hw/mmal/mmal_cma_drmprime.c
@@ -0,0 +1,120 @@
+#include <vlc_common.h>
+
+#include "mmal_cma.h"
+#include "mmal_cma_int.h"
+#include "mmal_cma_drmprime.h"
+
+#include <sys/mman.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/hwcontext_drm.h>
+#include <interface/vcsm/user-vcsm.h>
+
+#define TRACE_ALL 0
+
+typedef struct cma_drmprime_buf_s
+{
+ cma_buf_t cb;
+ const AVDRMFrameDescriptor *desc;
+ AVBufferRef * avbuf;
+} cma_drmprime_buf_t;
+
+static void drmprime_pool_free_cb(void * v, void * el, size_t size)
+{
+ VLC_UNUSED(v);
+ VLC_UNUSED(size);
+
+ cma_pool_delete(el);
+}
+
+static void * drmprime_pool_alloc_cb(void * v, size_t size)
+{
+ cma_buf_pool_t * const cbp = v;
+
+ cma_drmprime_buf_t * const cdb = malloc(sizeof(cma_drmprime_buf_t));
+ if (cdb == NULL)
+ return NULL;
+
+ *cdb = (cma_drmprime_buf_t){
+ .cb = {
+ .ref_count = ATOMIC_VAR_INIT(0),
+ .cbp = cbp,
+ .in_flight = 0,
+ .size = size,
+ .vcsm_h = 0,
+ .vc_h = 0,
+ .fd = -1,
+ .mmap = MAP_FAILED,
+ .ctx2 = NULL
+ }
+ };
+#if TRACE_ALL
+ cdb->cbp->alloc_size += cdb->size;
+ ++cdb->cbp->alloc_n;
+ fprintf(stderr, "%s[%d:%s]: N=%d, Total=%d\n", __func__, cbp->pool->seq, cbp->pool->name, cbp->alloc_n, cbp->alloc_size);
+#endif
+
+ return cdb;
+}
+
+// Buf being returned to pool
+static void drmprime_buf_pool_on_put_cb(void * v)
+{
+ cma_drmprime_buf_t * const cdb = v;
+ cdb->cb.fd = -1;
+ if (cdb->cb.vcsm_h != 0)
+ vcsm_free(cdb->cb.vcsm_h);
+ cdb->cb.vcsm_h = 0;
+ cdb->cb.vc_h = 0;
+ cdb->cb.vc_addr = 0;
+ av_buffer_unref(&cdb->avbuf);
+ if (cdb->cb.ctx2) {
+ cdb->cb.ctx2->destroy(cdb->cb.ctx2);
+ cdb->cb.ctx2 = NULL;
+ }
+}
+
+// Pool has died - safe now to exit vcsm
+static void drmprime_buf_pool_on_delete_cb(void * v)
+{
+ cma_buf_pool_t * const cbp = v;
+ free(cbp);
+}
+
+cma_buf_pool_t * cma_drmprime_pool_new(const unsigned int pool_size, const unsigned int flight_size, const bool all_in_flight, const char * const name)
+{
+ cma_buf_pool_t * const cbp = calloc(1, sizeof(cma_buf_pool_t));
+ if (cbp == NULL)
+ return NULL;
+
+ cbp->buf_type = CMA_BUF_TYPE_DRMPRIME;
+ cbp->all_in_flight = all_in_flight;
+
+ if ((cbp->pool = cma_pool_fixed_new(pool_size, flight_size, cbp,
+ drmprime_pool_alloc_cb, drmprime_pool_free_cb,
+ drmprime_buf_pool_on_put_cb, drmprime_buf_pool_on_delete_cb,
+ name)) == NULL)
+ goto fail;
+ return cbp;
+
+fail:
+ cma_buf_pool_delete(cbp);
+ return NULL;
+}
+
+cma_buf_t * cma_drmprime_pool_alloc_buf(cma_buf_pool_t * const cbp, struct AVFrame * frame)
+{
+ const AVDRMFrameDescriptor* const desc = (AVDRMFrameDescriptor*)frame->data[0];
+ cma_drmprime_buf_t *const cdb = (cma_drmprime_buf_t *)cma_buf_pool_alloc_buf(cbp, desc->objects[0].size);
+
+ if (cdb == NULL)
+ return NULL;
+
+ cdb->cb.type = CMA_BUF_TYPE_DRMPRIME;
+ cdb->cb.fd = desc->objects[0].fd;
+
+ cdb->desc = desc;
+ cdb->avbuf = av_buffer_ref(frame->buf[0]);
+ return &cdb->cb;
+}
+
+
diff --git a/modules/hw/mmal/mmal_cma_drmprime.h b/modules/hw/mmal/mmal_cma_drmprime.h
new file mode 100644
index 0000000000..2ca92a72a7
--- /dev/null
+++ b/modules/hw/mmal/mmal_cma_drmprime.h
@@ -0,0 +1,9 @@
+#ifndef VLC_HW_MMAL_MMAL_CMA_DRMPRIME_H_
+#define VLC_HW_MMAL_MMAL_CMA_DRMPRIME_H_
+
+struct AVFrame;
+cma_buf_pool_t * cma_drmprime_pool_new(const unsigned int pool_size, const unsigned int flight_size, const bool all_in_flight, const char * const name);
+cma_buf_t * cma_drmprime_pool_alloc_buf(cma_buf_pool_t * const cbp, struct AVFrame * frame);
+
+#endif // VLC_MMAL_MMAL_CMA_H_
+
diff --git a/modules/hw/mmal/mmal_cma_int.h b/modules/hw/mmal/mmal_cma_int.h
new file mode 100644
index 0000000000..1ff3fa4fd0
--- /dev/null
+++ b/modules/hw/mmal/mmal_cma_int.h
@@ -0,0 +1,58 @@
+#ifndef VLC_HW_MMAL_MMAL_CMA_INT_H_
+#define VLC_HW_MMAL_MMAL_CMA_INT_H_
+
+#include <stdatomic.h>
+
+typedef void * cma_pool_alloc_fn(void * usr_v, size_t size);
+typedef void cma_pool_free_fn(void * usr_v, void * buffer_v, size_t size);
+typedef void cma_pool_on_delete_fn(void * usr_v);
+typedef void cma_pool_on_put_fn(void * buffer_v);
+
+// Pool structure
+// Ref count is held by pool owner and pool els that have been got
+// Els in the pool do not count towards its ref count
+struct cma_pool_fixed_s;
+typedef struct cma_pool_fixed_s cma_pool_fixed_t;
+
+cma_pool_fixed_t*
+cma_pool_fixed_new(const unsigned int pool_size,
+ const int flight_size,
+ void * const alloc_v,
+ cma_pool_alloc_fn * const alloc_fn, cma_pool_free_fn * const free_fn,
+ cma_pool_on_put_fn * const on_put_fn, cma_pool_on_delete_fn * const on_delete_fn,
+ const char * const name);
+
+typedef enum cma_buf_type_e {
+ CMA_BUF_TYPE_NONE = 0,
+ CMA_BUF_TYPE_CMA,
+ CMA_BUF_TYPE_VCSM,
+ CMA_BUF_TYPE_DRMPRIME,
+} cma_buf_type_t;
+
+struct cma_buf_pool_s {
+ cma_pool_fixed_t * pool;
+ cma_buf_type_t buf_type;
+
+ bool all_in_flight;
+ size_t alloc_n;
+ size_t alloc_size;
+};
+
+typedef struct cma_buf_s {
+ atomic_int ref_count;
+ cma_buf_type_t type;
+ struct cma_buf_pool_s * cbp;
+ bool in_flight;
+ size_t size;
+ unsigned int vcsm_h; // VCSM handle from initial alloc
+ unsigned int vc_h; // VC handle for ZC mmal buffers
+ unsigned int vc_addr; // VC addr - unused by us but wanted by FFmpeg
+ int fd; // dmabuf handle for GL
+ void * mmap; // ARM mapped address
+ picture_context_t *ctx2;
+} cma_buf_t;
+
+void cma_pool_delete(cma_buf_t * const cb);
+
+#endif
+
diff --git a/modules/hw/mmal/mmal_cma_pic.h b/modules/hw/mmal/mmal_cma_pic.h
new file mode 100644
index 0000000000..7d7d60d3a3
--- /dev/null
+++ b/modules/hw/mmal/mmal_cma_pic.h
@@ -0,0 +1,49 @@
+#include <vlc_fourcc.h>
+#include <vlc_picture.h>
+#include "mmal_cma_int.h"
+
+static inline int
+cma_buf_fd(const cma_buf_t *const cb)
+{
+ return cb->fd;
+}
+
+static inline void *
+cma_buf_addr(const cma_buf_t *const cb)
+{
+ return cb->mmap;
+}
+
+#define CTX_BUFS_MAX 4
+struct MMAL_BUFFER_HEADER_T;
+
+typedef struct pic_ctx_mmal_s {
+ picture_context_t cmn; // PARENT: Common els at start
+
+ cma_buf_t * cb;
+
+ unsigned int buf_count;
+ struct MMAL_BUFFER_HEADER_T * bufs[CTX_BUFS_MAX];
+
+} pic_ctx_mmal_t;
+
+static inline bool
+is_cma_buf_pic_chroma(const uint32_t chroma)
+{
+ return chroma == VLC_CODEC_MMAL_ZC_RGB32 ||
+ chroma == VLC_CODEC_MMAL_ZC_SAND8 ||
+ chroma == VLC_CODEC_MMAL_ZC_SAND10 ||
+ chroma == VLC_CODEC_MMAL_ZC_SAND30 ||
+ chroma == VLC_CODEC_MMAL_ZC_I420;
+}
+
+// Returns a pointer to the cma_buf attached to the pic
+// Just a pointer - doesn't add a ref
+static inline cma_buf_t *
+cma_buf_pic_get(picture_t * const pic)
+{
+ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic->context;
+ return !is_cma_buf_pic_chroma(pic->format.i_chroma) || ctx == NULL ? 0 : ctx->cb;
+}
+
+
diff --git a/modules/hw/mmal/mmal_piccpy_neon.S b/modules/hw/mmal/mmal_piccpy_neon.S
new file mode 100644
index 0000000000..6fec851800
--- /dev/null
+++ b/modules/hw/mmal/mmal_piccpy_neon.S
@@ -0,0 +1,95 @@
+#include "../../arm_neon/asm.S"
+ .align 16
+ .arch armv7-a
+ .syntax unified
+#if HAVE_AS_FPU_DIRECTIVE
+ .fpu neon-vfpv4
+#endif
+
+// Copy pix
+
+.macro piccpy_to_8, bit_depth
+ subs r2, #128
+ vpush {q4-q7}
+ blt 2f
+1:
+ vldm r1!, {q0-q7}
+ subs r2, #128
+ vqrshrn.u16 d0, q0, #\bit_depth - 8
+ vqrshrn.u16 d1, q1, #\bit_depth - 8
+ vqrshrn.u16 d2, q2, #\bit_depth - 8
+ vqrshrn.u16 d3, q3, #\bit_depth - 8
+ vldm r1!, {q8-q15}
+ vqrshrn.u16 d4, q4, #\bit_depth - 8
+ vqrshrn.u16 d5, q5, #\bit_depth - 8
+ vqrshrn.u16 d6, q6, #\bit_depth - 8
+ vqrshrn.u16 d7, q7, #\bit_depth - 8
+ vqrshrn.u16 d8, q8, #\bit_depth - 8
+ vqrshrn.u16 d9, q9, #\bit_depth - 8
+ vqrshrn.u16 d10, q10, #\bit_depth - 8
+ vqrshrn.u16 d11, q11, #\bit_depth - 8
+ vqrshrn.u16 d12, q12, #\bit_depth - 8
+ vqrshrn.u16 d13, q13, #\bit_depth - 8
+ vqrshrn.u16 d14, q14, #\bit_depth - 8
+ vqrshrn.u16 d15, q15, #\bit_depth - 8
+ vstm r0!, {q0-q7}
+ bge 1b
+2:
+ adds r2, #64
+ blt 1f
+
+ vldm r1!, {q0-q7}
+ vqrshrn.u16 d0, q0, #\bit_depth - 8
+ vqrshrn.u16 d1, q1, #\bit_depth - 8
+ vqrshrn.u16 d2, q2, #\bit_depth - 8
+ vqrshrn.u16 d3, q3, #\bit_depth - 8
+ vqrshrn.u16 d4, q4, #\bit_depth - 8
+ vqrshrn.u16 d5, q5, #\bit_depth - 8
+ vqrshrn.u16 d6, q6, #\bit_depth - 8
+ vqrshrn.u16 d7, q7, #\bit_depth - 8
+ vstm r0!, {q0-q3}
+1:
+ adds r2, #32
+ blt 1f
+
+ vldm r1!, {q0-q3}
+ vqrshrn.u16 d0, q0, #\bit_depth - 8
+ vqrshrn.u16 d1, q1, #\bit_depth - 8
+ vqrshrn.u16 d2, q2, #\bit_depth - 8
+ vqrshrn.u16 d3, q3, #\bit_depth - 8
+ vstm r0!, {q0-q1}
+1:
+ adds r2, #16
+ blt 1f
+
+ vldm r1!, {q0-q1}
+ vqrshrn.u16 d0, q0, #\bit_depth - 8
+ vqrshrn.u16 d1, q1, #\bit_depth - 8
+ vstm r0!, {q0}
+1:
+ adds r2, #8
+ blt 1f
+
+ vldm r1!, {q0}
+ vqrshrn.u16 d0, q0, #\bit_depth - 8
+ vstr d0, [r0]
+ add r0, #8
+1:
+ adds r2, #4
+ blt 1f
+
+ vldr d0, [r1]
+ vqrshrn.u16 d0, q0, #\bit_depth - 8
+ vstr s0, [r0]
+1:
+ vpop {q4-q7}
+ bx lr
+.endm
+
+
+@ [r0] Dest
+@ [r1] Src
+@ r2 Pels
+function mmal_piccpy_10_to_8_neon
+ piccpy_to_8 10
+
diff --git a/modules/hw/mmal/mmal_picture.c b/modules/hw/mmal/mmal_picture.c
index 7ddd5b566c..c7f8b42af0 100644
--- a/modules/hw/mmal/mmal_picture.c
+++ b/modules/hw/mmal/mmal_picture.c
@@ -21,25 +21,1574 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
+// We would really like to use vlc_thread.h but the detach thread stuff can't be
+// used here :-(
+#include <pthread.h>
+
+#include <stdatomic.h>
+#include <unistd.h>
+#include <fcntl.h>
+
#include <vlc_common.h>
+#include <vlc_cpu.h>
#include <vlc_picture.h>
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wbad-function-cast"
+#include <bcm_host.h>
+#pragma GCC diagnostic pop
#include <interface/mmal/mmal.h>
+#include <interface/mmal/util/mmal_util.h>
+#include <interface/mmal/util/mmal_default_components.h>
+#include <interface/vmcs_host/vcgencmd.h>
+#include <interface/vcsm/user-vcsm.h>
+#include "mmal_cma.h"
#include "mmal_picture.h"
+#include "transform_ops.h"
+
+#define TRACE_TRANSFORMS 0
+
+#define UINT64_SIZE(s) (((s) + sizeof(uint64_t) - 1)/sizeof(uint64_t))
+
+static inline char safe_char(const unsigned int c0)
+{
+ const unsigned int c = c0 & 0xff;
+ return c > ' ' && c < 0x7f ? c : '.';
+}
+
+const char * str_fourcc(char * const buf, const unsigned int fcc)
+{
+ if (fcc == 0)
+ return "----";
+ buf[0] = safe_char(fcc >> 0);
+ buf[1] = safe_char(fcc >> 8);
+ buf[2] = safe_char(fcc >> 16);
+ buf[3] = safe_char(fcc >> 24);
+ buf[4] = 0;
+ return buf;
+}
+
+// WB + Inv
+static inline void flush_range(void * const start, const size_t len)
+{
+ uint64_t buf[UINT64_SIZE(sizeof(struct vcsm_user_clean_invalid2_s) + sizeof(struct vcsm_user_clean_invalid2_block_s))];
+ struct vcsm_user_clean_invalid2_s * const b = (struct vcsm_user_clean_invalid2_s *)buf;
+
+ *b = (struct vcsm_user_clean_invalid2_s){
+ .op_count = 1
+ };
+
+ b->s[0] = (struct vcsm_user_clean_invalid2_block_s){
+ .invalidate_mode = 3, // wb + invalidate
+ .block_count = 1,
+ .start_address = start, // Rely on clean inv to fix up align & size boundries
+ .block_size = len,
+ .inter_block_stride = 0
+ };
+
+ vcsm_clean_invalid2(b);
+}
+
+MMAL_FOURCC_T vlc_to_mmal_color_space(const video_color_space_t vlc_cs)
+{
+ switch (vlc_cs)
+ {
+ case COLOR_SPACE_BT601:
+ return MMAL_COLOR_SPACE_ITUR_BT601;
+ case COLOR_SPACE_BT709:
+ return MMAL_COLOR_SPACE_ITUR_BT709;
+ default:
+ break;
+ }
+ return MMAL_COLOR_SPACE_UNKNOWN;
+}
+
+MMAL_FOURCC_T vlc_to_mmal_video_fourcc(const video_frame_format_t * const vf_vlc)
+{
+ switch (vf_vlc->i_chroma) {
+ case VLC_CODEC_MMAL_ZC_RGB32:
+ case VLC_CODEC_RGB32:
+ {
+ // VLC RGB32 aka RV32 means we have to look at the mask values
+ const uint32_t r = vf_vlc->i_rmask;
+ const uint32_t g = vf_vlc->i_gmask;
+ const uint32_t b = vf_vlc->i_bmask;
+ if (r == 0xff0000 && g == 0xff00 && b == 0xff)
+ return MMAL_ENCODING_BGRA;
+ if (r == 0xff && g == 0xff00 && b == 0xff0000)
+ return MMAL_ENCODING_RGBA;
+ if (r == 0xff000000 && g == 0xff0000 && b == 0xff00)
+ return MMAL_ENCODING_ABGR;
+ if (r == 0xff00 && g == 0xff0000 && b == 0xff000000)
+ return MMAL_ENCODING_ARGB;
+ break;
+ }
+ case VLC_CODEC_RGB16:
+ {
+ // VLC RGB16 aka RV16 means we have to look at the mask values
+ const uint32_t r = vf_vlc->i_rmask;
+ const uint32_t g = vf_vlc->i_gmask;
+ const uint32_t b = vf_vlc->i_bmask;
+ if (r == 0xf800 && g == 0x7e0 && b == 0x1f)
+ return MMAL_ENCODING_RGB16;
+ break;
+ }
+ case VLC_CODEC_I420:
+ case VLC_CODEC_MMAL_ZC_I420:
+ return MMAL_ENCODING_I420;
+ case VLC_CODEC_RGBA:
+ return MMAL_ENCODING_RGBA;
+ case VLC_CODEC_BGRA:
+ return MMAL_ENCODING_BGRA;
+ case VLC_CODEC_ARGB:
+ return MMAL_ENCODING_ARGB;
+ // VLC_CODEC_ABGR does not exist in VLC
+ case VLC_CODEC_MMAL_OPAQUE:
+ return MMAL_ENCODING_OPAQUE;
+ case VLC_CODEC_MMAL_ZC_SAND8:
+ return MMAL_ENCODING_YUVUV128;
+ case VLC_CODEC_MMAL_ZC_SAND10:
+ return MMAL_ENCODING_YUVUV64_10;
+ case VLC_CODEC_MMAL_ZC_SAND30:
+ return MMAL_ENCODING_YUV10_COL;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static void vlc_fmt_to_video_format(MMAL_VIDEO_FORMAT_T *const vf_mmal, const video_frame_format_t * const vf_vlc)
+{
+ const unsigned int wmask = (vf_vlc->i_chroma == VLC_CODEC_MMAL_ZC_I420 ||
+ vf_vlc->i_chroma == VLC_CODEC_I420) ? 31 : 15;
+
+ vf_mmal->width = (vf_vlc->i_width + wmask) & ~wmask;
+ vf_mmal->height = (vf_vlc->i_height + 15) & ~15;
+ vf_mmal->crop.x = vf_vlc->i_x_offset;
+ vf_mmal->crop.y = vf_vlc->i_y_offset;
+ vf_mmal->crop.width = vf_vlc->i_visible_width;
+ vf_mmal->crop.height = vf_vlc->i_visible_height;
+ if (vf_vlc->i_sar_num == 0 || vf_vlc->i_sar_den == 0) {
+ vf_mmal->par.num = 1;
+ vf_mmal->par.den = 1;
+ } else {
+ vf_mmal->par.num = vf_vlc->i_sar_num;
+ vf_mmal->par.den = vf_vlc->i_sar_den;
+ }
+ vf_mmal->frame_rate.num = vf_vlc->i_frame_rate;
+ vf_mmal->frame_rate.den = vf_vlc->i_frame_rate_base;
+ vf_mmal->color_space = vlc_to_mmal_color_space(vf_vlc->space);
+}
+
+
+void hw_mmal_vlc_fmt_to_mmal_fmt(MMAL_ES_FORMAT_T *const es_fmt, const video_frame_format_t * const vf_vlc)
+{
+ vlc_fmt_to_video_format(&es_fmt->es->video, vf_vlc);
+}
+
+bool hw_mmal_vlc_pic_to_mmal_fmt_update(MMAL_ES_FORMAT_T *const es_fmt, const picture_t * const pic)
+{
+ MMAL_VIDEO_FORMAT_T vf_new_ss;
+ MMAL_VIDEO_FORMAT_T *const vf_old = &es_fmt->es->video;
+ MMAL_VIDEO_FORMAT_T *const vf_new = &vf_new_ss;
+
+ vlc_fmt_to_video_format(vf_new, &pic->format);
+
+ // If we have a format that might have come from ffmpeg then rework for
+ // a better guess as to layout. All sand stuff is "special" with regards to
+ // width/height vs real layout so leave as is if that
+ if ((pic->format.i_chroma == VLC_CODEC_MMAL_ZC_I420 ||
+ pic->format.i_chroma == VLC_CODEC_MMAL_ZC_RGB32) &&
+ pic->p[0].i_pixel_pitch != 0)
+ {
+ // Now overwrite width/height with a better guess as to actual layout info
+ vf_new->height = pic->p[0].i_lines;
+ vf_new->width = pic->p[0].i_pitch / pic->p[0].i_pixel_pitch;
+ }
+
+ if (
+ vf_new->width != vf_old->width ||
+ vf_new->height != vf_old->height ||
+ vf_new->crop.x != vf_old->crop.x ||
+ vf_new->crop.y != vf_old->crop.y ||
+ vf_new->crop.width != vf_old->crop.width ||
+ vf_new->crop.height != vf_old->crop.height ||
+ vf_new->par.num != vf_old->par.num ||
+ vf_new->par.den != vf_old->par.den ||
+ // Frame rate ignored
+ vf_new->color_space != vf_old->color_space)
+ {
+#if 0
+ char dbuf0[5], dbuf1[5];
+ printf("%dx%d (%d,%d %dx%d) par:%d/%d %s -> %dx%d (%d,%d %dx%d) par:%d/%d %s\n",
+ vf_old->width ,
+ vf_old->height ,
+ vf_old->crop.x ,
+ vf_old->crop.y ,
+ vf_old->crop.width ,
+ vf_old->crop.height ,
+ vf_old->par.num ,
+ vf_old->par.den ,
+ str_fourcc(dbuf0, vf_old->color_space) ,
+ vf_new->width ,
+ vf_new->height ,
+ vf_new->crop.x ,
+ vf_new->crop.y ,
+ vf_new->crop.width ,
+ vf_new->crop.height ,
+ vf_new->par.num ,
+ vf_new->par.den ,
+ str_fourcc(dbuf1, vf_new->color_space) );
+#endif
+ *vf_old = *vf_new;
+ return true;
+ }
+ return false;
+}
+
+
+hw_mmal_port_pool_ref_t * hw_mmal_port_pool_ref_create(MMAL_PORT_T * const port,
+ const unsigned int headers, const uint32_t payload_size)
+{
+ hw_mmal_port_pool_ref_t * ppr = calloc(1, sizeof(hw_mmal_port_pool_ref_t));
+ if (ppr == NULL)
+ return NULL;
+
+ if ((ppr->pool = mmal_port_pool_create(port, headers, payload_size)) == NULL)
+ goto fail;
+
+ ppr->port = port;
+ atomic_store(&ppr->refs, 1);
+ return ppr;
+
+fail:
+ free(ppr);
+ return NULL;
+}
+
+static void do_detached(void *(*fn)(void *), void * v)
+{
+ pthread_t dothread;
+ pthread_create(&dothread, NULL, fn, v);
+ pthread_detach(dothread);
+}
+
+// Destroy a ppr - aranged s.t. it has the correct prototype for a pthread
+static void * kill_ppr(void * v)
+{
+ hw_mmal_port_pool_ref_t * const ppr = v;
+ if (ppr->port->is_enabled)
+ mmal_port_disable(ppr->port); // Avoid annoyed messages from MMAL when we kill the pool
+ mmal_port_pool_destroy(ppr->port, ppr->pool);
+ free(ppr);
+ return NULL;
+}
+
+void hw_mmal_port_pool_ref_release(hw_mmal_port_pool_ref_t * const ppr, const bool in_cb)
+{
+ if (ppr == NULL)
+ return;
+ if (atomic_fetch_sub(&ppr->refs, 1) != 1)
+ return;
+ if (in_cb)
+ do_detached(kill_ppr, ppr);
+ else
+ kill_ppr(ppr);
+}
+
+// Put buffer in port if possible - if not then release to pool
+// Returns true if sent, false if recycled
+bool hw_mmal_port_pool_ref_recycle(hw_mmal_port_pool_ref_t * const ppr, MMAL_BUFFER_HEADER_T * const buf)
+{
+ mmal_buffer_header_reset(buf);
+ buf->user_data = NULL;
-int mmal_picture_lock(picture_t *picture)
+ if (mmal_port_send_buffer(ppr->port, buf) == MMAL_SUCCESS)
+ return true;
+ mmal_buffer_header_release(buf);
+ return false;
+}
+
+MMAL_STATUS_T hw_mmal_port_pool_ref_fill(hw_mmal_port_pool_ref_t * const ppr)
+{
+ MMAL_BUFFER_HEADER_T * buf;
+ MMAL_STATUS_T err = MMAL_SUCCESS;
+
+ while ((buf = mmal_queue_get(ppr->pool->queue)) != NULL) {
+ if ((err = mmal_port_send_buffer(ppr->port, buf)) != MMAL_SUCCESS)
+ {
+ mmal_queue_put_back(ppr->pool->queue, buf);
+ break;
+ }
+ }
+ return err;
+}
+
+
+MMAL_STATUS_T hw_mmal_opaque_output(vlc_object_t * const obj,
+ hw_mmal_port_pool_ref_t ** pppr,
+ MMAL_PORT_T * const port,
+ const unsigned int extra_buffers, MMAL_PORT_BH_CB_T callback)
+{
+ MMAL_STATUS_T status;
+
+ port->userdata = (struct MMAL_PORT_USERDATA_T *)obj;
+
+ status = port_parameter_set_uint32(port, MMAL_PARAMETER_EXTRA_BUFFERS, extra_buffers);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(obj, "Failed to set MMAL_PARAMETER_EXTRA_BUFFERS on output port (status=%"PRIx32" %s)",
+ status, mmal_status_to_string(status));
+ return status;
+ }
+
+ status = port_parameter_set_bool(port, MMAL_PARAMETER_ZERO_COPY, 1);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(obj, "Failed to set zero copy on port %s (status=%"PRIx32" %s)",
+ port->name, status, mmal_status_to_string(status));
+ return status;
+ }
+
+ port->format->encoding = MMAL_ENCODING_OPAQUE;
+ port->format->encoding_variant = 0;
+ if ((status = mmal_port_format_commit(port)) != MMAL_SUCCESS)
+ {
+ msg_Err(obj, "Failed to commit format on port %s (status=%"PRIx32" %s)",
+ port->name, status, mmal_status_to_string(status));
+ return status;
+ }
+
+ port->buffer_num = 30;
+ port->buffer_size = port->buffer_size_recommended;
+
+ if ((*pppr = hw_mmal_port_pool_ref_create(port, port->buffer_num, port->buffer_size)) == NULL) {
+ msg_Err(obj, "Failed to create output pool");
+ return status;
+ }
+
+ status = mmal_port_enable(port, callback);
+ if (status != MMAL_SUCCESS) {
+ hw_mmal_port_pool_ref_release(*pppr, false);
+ *pppr = NULL;
+ msg_Err(obj, "Failed to enable output port %s (status=%"PRIx32" %s)",
+ port->name, status, mmal_status_to_string(status));
+ return status;
+ }
+
+ return MMAL_SUCCESS;
+}
+
+
+void hw_mmal_pic_ctx_destroy(picture_context_t * pic_ctx_cmn)
+{
+ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic_ctx_cmn;
+ unsigned int i;
+
+ for (i = 0; i != ctx->buf_count; ++i) {
+ if (ctx->bufs[i] != NULL)
+ mmal_buffer_header_release(ctx->bufs[i]);
+ }
+
+ cma_buf_end_flight(ctx->cb);
+ cma_buf_unref(ctx->cb);
+
+ free(ctx);
+}
+
+picture_context_t * hw_mmal_pic_ctx_copy(picture_context_t * pic_ctx_cmn)
+{
+ const pic_ctx_mmal_t * const src_ctx = (pic_ctx_mmal_t *)pic_ctx_cmn;
+ pic_ctx_mmal_t * const dst_ctx = calloc(1, sizeof(*dst_ctx));
+ unsigned int i;
+
+ if (dst_ctx == NULL)
+ return NULL;
+
+ // Copy
+ dst_ctx->cmn = src_ctx->cmn;
+
+ dst_ctx->cb = cma_buf_ref(src_ctx->cb);
+
+ dst_ctx->buf_count = src_ctx->buf_count;
+ for (i = 0; i != src_ctx->buf_count; ++i) {
+ dst_ctx->bufs[i] = src_ctx->bufs[i];
+ if (dst_ctx->bufs[i] != NULL)
+ mmal_buffer_header_acquire(dst_ctx->bufs[i]);
+ }
+
+ return &dst_ctx->cmn;
+}
+
+static MMAL_BOOL_T
+buf_pre_release_cb(MMAL_BUFFER_HEADER_T * buf, void *userdata)
+{
+ hw_mmal_port_pool_ref_t * const ppr = userdata;
+
+ // Kill the callback - otherwise we will go in circles!
+ mmal_buffer_header_pre_release_cb_set(buf, (MMAL_BH_PRE_RELEASE_CB_T)0, NULL);
+ mmal_buffer_header_acquire(buf); // Ref it again
+
+ // As we have re-acquired the buffer we need a full release
+ // (not continue) to zap the ref count back to zero
+ // This is "safe" 'cos we have already reset the cb
+ hw_mmal_port_pool_ref_recycle(ppr, buf);
+ hw_mmal_port_pool_ref_release(ppr, true); // Assume in callback
+
+ return MMAL_TRUE;
+}
+
+// Buffer belongs to context on successful return from this fn
+// is still valid on failure
+picture_context_t *
+hw_mmal_gen_context(MMAL_BUFFER_HEADER_T * buf, hw_mmal_port_pool_ref_t * const ppr)
{
- picture_sys_t *pic_sys = picture->p_sys;
- MMAL_BUFFER_HEADER_T *buffer = pic_sys->buffer;
+ pic_ctx_mmal_t * const ctx = calloc(1, sizeof(pic_ctx_mmal_t));
- int offset = 0;
- picture->p[0].p_pixels = buffer->data;
- for (int i = 1; i < picture->i_planes; i++) {
- offset = offset + picture->p[i - 1].i_pitch * picture->p[i - 1].i_lines;
- picture->p[i].p_pixels = (ptrdiff_t)buffer->data + offset;
+ if (ctx == NULL)
+ return NULL;
+
+ // If we have an associated ppr then ref & set appropriate callbacks
+ if (ppr != NULL) {
+ hw_mmal_port_pool_ref_acquire(ppr);
+ mmal_buffer_header_pre_release_cb_set(buf, buf_pre_release_cb, ppr);
+ buf->user_data = NULL;
}
- pic_sys->displayed = false;
+ ctx->cmn.copy = hw_mmal_pic_ctx_copy;
+ ctx->cmn.destroy = hw_mmal_pic_ctx_destroy;
+
+ ctx->buf_count = 1;
+ ctx->bufs[0] = buf;
+
+ return &ctx->cmn;
+}
+
+// n is els
+// * Make NEON!
+typedef void piccpy_fn(void * dest, const void * src, size_t n);
+
+extern piccpy_fn mmal_piccpy_10_to_8_neon;
+static void piccpy_10_to_8_c(void * dest, const void * src, size_t n)
+{
+ uint8_t * d = dest;
+ const uint16_t * s = src;
+ while (n-- != 0)
+ *d++ = *s++ >> 2;
+}
+
+// Do a stride converting copy - if the strides are the same and line_len is
+// close then do a single block copy - we don't expect to have to preserve
+// pixels in the output frame
+static void mem_copy_2d(uint8_t * d_ptr, const size_t d_stride,
+ const uint8_t * s_ptr, const size_t s_stride,
+ size_t lines, const size_t line_len)
+{
+ if (s_stride == d_stride && d_stride < line_len + 32)
+ {
+ memcpy(d_ptr, s_ptr, d_stride * lines);
+ }
+ else
+ {
+ while (lines-- != 0) {
+ memcpy(d_ptr, s_ptr, line_len);
+ d_ptr += d_stride;
+ s_ptr += s_stride;
+ }
+ }
+}
+
+// line_len in D units
+static void mem_copy_2d_10_to_8(uint8_t * d_ptr, const size_t d_stride,
+ const uint8_t * s_ptr, const size_t s_stride,
+ size_t lines, const size_t line_len)
+{
+ piccpy_fn * const docpy = vlc_CPU_ARM_NEON() ? mmal_piccpy_10_to_8_neon : piccpy_10_to_8_c;
+ if (s_stride == d_stride * 2 && d_stride < line_len + 32)
+ {
+ docpy(d_ptr, s_ptr, d_stride * lines);
+ }
+ else
+ {
+ while (lines-- != 0) {
+ docpy(d_ptr, s_ptr, line_len);
+ d_ptr += d_stride;
+ s_ptr += s_stride;
+ }
+ }
+}
+
+
+int hw_mmal_copy_pic_to_buf(void * const buf_data,
+ uint32_t * const pLength,
+ const MMAL_ES_FORMAT_T * const fmt,
+ const picture_t * const pic)
+{
+ const MMAL_VIDEO_FORMAT_T *const video = &fmt->es->video;
+ uint8_t * const dest = buf_data;
+ size_t length = 0;
+
+ //**** Worry about x/y_offsets
+
+ assert(fmt->encoding == MMAL_ENCODING_I420);
+
+ switch (pic->format.i_chroma) {
+ case VLC_CODEC_I420:
+ {
+ const size_t y_size = video->width * video->height;
+ mem_copy_2d(dest, video->width,
+ pic->p[0].p_pixels, pic->p[0].i_pitch,
+ video->crop.height,
+ video->crop.width);
+
+ mem_copy_2d(dest + y_size, video->width / 2,
+ pic->p[1].p_pixels, pic->p[1].i_pitch,
+ video->crop.height / 2,
+ video->crop.width / 2);
+
+ mem_copy_2d(dest + y_size + y_size / 4, video->width / 2,
+ pic->p[2].p_pixels, pic->p[2].i_pitch,
+ video->crop.height / 2,
+ video->crop.width / 2);
+
+ // And make sure it is actually in memory
+ length = y_size + y_size / 2;
+ break;
+ }
+
+ case VLC_CODEC_I420_10L:
+ {
+ const size_t y_size = video->width * video->height;
+ mem_copy_2d_10_to_8(dest, video->width,
+ pic->p[0].p_pixels, pic->p[0].i_pitch,
+ video->crop.height,
+ video->crop.width);
+
+ mem_copy_2d_10_to_8(dest + y_size, video->width / 2,
+ pic->p[1].p_pixels, pic->p[1].i_pitch,
+ video->crop.height / 2,
+ video->crop.width / 2);
+
+ mem_copy_2d_10_to_8(dest + y_size + y_size / 4, video->width / 2,
+ pic->p[2].p_pixels, pic->p[2].i_pitch,
+ video->crop.height / 2,
+ video->crop.width / 2);
+
+ // And make sure it is actually in memory
+ length = y_size + y_size / 2;
+ break;
+ }
+
+ default:
+ if (pLength != NULL)
+ *pLength = 0;
+ return VLC_EBADVAR;
+ }
+
+ if (cma_vcsm_type() == VCSM_INIT_LEGACY) { // ** CMA is currently always uncached
+ flush_range(dest, length);
+ }
+
+ if (pLength != NULL)
+ *pLength = (uint32_t)length;
+
+ return VLC_SUCCESS;
+}
+
+
+static MMAL_BOOL_T rep_buf_free_cb(MMAL_BUFFER_HEADER_T *header, void *userdata)
+{
+ cma_buf_t * const cb = userdata;
+ VLC_UNUSED(header);
+
+ cma_buf_unref(cb);
+ return MMAL_FALSE;
+}
+
+static int cma_buf_buf_attach(MMAL_BUFFER_HEADER_T * const buf, cma_buf_t * const cb)
+{
+ // Just a CMA buffer - fill in new buffer
+ const uintptr_t vc_h = cma_buf_vc_handle(cb);
+ if (vc_h == 0)
+ return VLC_EGENERIC;
+
+ mmal_buffer_header_reset(buf);
+ buf->data = (uint8_t *)vc_h;
+ buf->alloc_size = cma_buf_size(cb);
+ buf->length = buf->alloc_size;
+ // Ensure cb remains valid for the duration of this buffer
+ mmal_buffer_header_pre_release_cb_set(buf, rep_buf_free_cb, cma_buf_ref(cb));
return VLC_SUCCESS;
}
+
+MMAL_BUFFER_HEADER_T * hw_mmal_pic_buf_copied(const picture_t *const pic,
+ MMAL_POOL_T * const rep_pool,
+ MMAL_PORT_T * const port,
+ cma_buf_pool_t * const cbp)
+{
+ MMAL_BUFFER_HEADER_T *const buf = mmal_queue_wait(rep_pool->queue);
+ if (buf == NULL)
+ goto fail0;
+
+ cma_buf_t * const cb = cma_buf_pool_alloc_buf(cbp, port->buffer_size);
+ if (cb == NULL)
+ goto fail1;
+
+ if (cma_buf_buf_attach(buf, cb) != VLC_SUCCESS)
+ goto fail2;
+
+ pic_to_buf_copy_props(buf, pic);
+
+ if (hw_mmal_copy_pic_to_buf(cma_buf_addr(cb), &buf->length, port->format, pic) != VLC_SUCCESS)
+ goto fail2;
+ buf->flags = MMAL_BUFFER_HEADER_FLAG_FRAME_END;
+
+ cma_buf_unref(cb);
+ return buf;
+
+fail2:
+ cma_buf_unref(cb);
+fail1:
+ mmal_buffer_header_release(buf);
+fail0:
+ return NULL;
+}
+
+MMAL_BUFFER_HEADER_T * hw_mmal_pic_buf_replicated(const picture_t *const pic, MMAL_POOL_T * const rep_pool)
+{
+ pic_ctx_mmal_t *const ctx = (pic_ctx_mmal_t *)pic->context;
+ MMAL_BUFFER_HEADER_T *const rep_buf = mmal_queue_wait(rep_pool->queue);
+
+ if (rep_buf == NULL)
+ return NULL;
+
+ if (ctx->bufs[0] != NULL)
+ {
+ // Existing buffer - replicate it
+ if (mmal_buffer_header_replicate(rep_buf, ctx->bufs[0]) != MMAL_SUCCESS)
+ goto fail;
+ }
+ else if (ctx->cb != NULL)
+ {
+ // Just a CMA buffer - fill in new buffer
+ if (cma_buf_buf_attach(rep_buf, ctx->cb) != 0)
+ goto fail;
+ }
+ else
+ goto fail;
+
+ pic_to_buf_copy_props(rep_buf, pic);
+ return rep_buf;
+
+fail:
+ mmal_buffer_header_release(rep_buf);
+ return NULL;
+}
+
+
+
+
+int hw_mmal_get_gpu_mem(void) {
+ static int stashed_val = -2;
+ VCHI_INSTANCE_T vchi_instance;
+ VCHI_CONNECTION_T *vchi_connection = NULL;
+ char rbuf[1024] = { 0 };
+
+ if (stashed_val >= -1)
+ return stashed_val;
+
+ if (vchi_initialise(&vchi_instance) != 0)
+ goto fail0;
+
+ //create a vchi connection
+ if (vchi_connect(NULL, 0, vchi_instance) != 0)
+ goto fail0;
+
+ vc_vchi_gencmd_init(vchi_instance, &vchi_connection, 1);
+
+ //send the gencmd for the argument
+ if (vc_gencmd_send("get_mem gpu") != 0)
+ goto fail;
+
+ if (vc_gencmd_read_response(rbuf, sizeof(rbuf) - 1) != 0)
+ goto fail;
+
+ if (strncmp(rbuf, "gpu=", 4) != 0)
+ goto fail;
+
+ char *p;
+ unsigned long m = strtoul(rbuf + 4, &p, 10);
+
+ if (p[0] != 'M' || p[1] != '\0')
+ stashed_val = -1;
+ else
+ stashed_val = (int)m << 20;
+
+ vc_gencmd_stop();
+
+ //close the vchi connection
+ vchi_disconnect(vchi_instance);
+
+ return stashed_val;
+
+fail:
+ vc_gencmd_stop();
+ vchi_disconnect(vchi_instance);
+fail0:
+ stashed_val = -1;
+ return -1;
+};
+
+// ===========================================================================
+
+typedef struct pool_ent_s
+{
+ struct pool_ent_s * next;
+ struct pool_ent_s * prev;
+
+ atomic_int ref_count;
+ unsigned int seq;
+
+ size_t size;
+
+ int vcsm_hdl;
+ int vc_hdl;
+ void * buf;
+
+ unsigned int width;
+ unsigned int height;
+ MMAL_FOURCC_T enc_type;
+
+ picture_t * pic;
+} pool_ent_t;
+
+
+typedef struct ent_list_hdr_s
+{
+ pool_ent_t * ents;
+ pool_ent_t * tail;
+ unsigned int n;
+} ent_list_hdr_t;
+
+#define ENT_LIST_HDR_INIT (ent_list_hdr_t){ \
+ .ents = NULL, \
+ .tail = NULL, \
+ .n = 0 \
+}
+
+struct vzc_pool_ctl_s
+{
+ atomic_int ref_count;
+
+ ent_list_hdr_t ent_pool;
+ ent_list_hdr_t ents_cur;
+ ent_list_hdr_t ents_prev;
+
+ unsigned int max_n;
+ unsigned int seq;
+
+ vlc_mutex_t lock;
+
+ MMAL_POOL_T * buf_pool;
+
+ vcsm_init_type_t vcsm_init_type;
+};
+
+typedef struct vzc_subbuf_ent_s
+{
+ pool_ent_t * ent;
+ MMAL_RECT_T pic_rect;
+ MMAL_RECT_T orig_dest_rect;
+ MMAL_DISPLAYREGION_T dreg;
+} vzc_subbuf_ent_t;
+
+
+static pool_ent_t * ent_extract(ent_list_hdr_t * const elh, pool_ent_t * const ent)
+{
+// printf("List %p [%d]: Ext %p\n", elh, elh->n, ent);
+
+ if (ent == NULL)
+ return NULL;
+
+ if (ent->next == NULL)
+ elh->tail = ent->prev;
+ else
+ ent->next->prev = ent->prev;
+
+ if (ent->prev == NULL)
+ elh->ents = ent->next;
+ else
+ ent->prev->next = ent->next;
+
+ ent->prev = ent->next = NULL;
+
+ --elh->n;
+
+ return ent; // For convienience
+}
+
+static inline pool_ent_t * ent_extract_tail(ent_list_hdr_t * const elh)
+{
+ return ent_extract(elh, elh->tail);
+}
+
+static void ent_add_head(ent_list_hdr_t * const elh, pool_ent_t * const ent)
+{
+// printf("List %p [%d]: Add %p\n", elh, elh->n, ent);
+
+ if ((ent->next = elh->ents) == NULL)
+ elh->tail = ent;
+ else
+ ent->next->prev = ent;
+
+ ent->prev = NULL;
+ elh->ents = ent;
+ ++elh->n;
+}
+
+static void ent_free(pool_ent_t * const ent)
+{
+// printf("Free ent: %p\n", ent);
+ if (ent != NULL) {
+ // If we still have a ref to a pic - kill it now
+ if (ent->pic != NULL)
+ picture_Release(ent->pic);
+
+ // Free contents
+ vcsm_unlock_hdl(ent->vcsm_hdl);
+
+ vcsm_free(ent->vcsm_hdl);
+
+ free(ent);
+ }
+}
+
+static void ent_free_list(ent_list_hdr_t * const elh)
+{
+ pool_ent_t * ent = elh->ents;
+
+// printf("Free list: %p [%d]\n", elh, elh->n);
+
+ *elh = ENT_LIST_HDR_INIT;
+
+ while (ent != NULL) {
+ pool_ent_t * const t = ent;
+ ent = t->next;
+ ent_free(t);
+ }
+}
+
+static void ent_list_move(ent_list_hdr_t * const dst, ent_list_hdr_t * const src)
+{
+// printf("Move %p->%p\n", src, dst);
+
+ *dst = *src;
+ *src = ENT_LIST_HDR_INIT;
+}
+
+// Scans "backwards" as that should give us the fastest match if we are
+// presented with pics in the same order each time
+static pool_ent_t * ent_list_extract_pic_ent(ent_list_hdr_t * const elh, picture_t * const pic)
+{
+ pool_ent_t *ent = elh->tail;
+
+// printf("Find list: %p [%d]; pic:%p\n", elh, elh->n, pic);
+
+ while (ent != NULL) {
+// printf("Check ent: %p, pic:%p\n", ent, ent->pic);
+
+ if (ent->pic == pic)
+ return ent_extract(elh, ent);
+ ent = ent->prev;
+ }
+ return NULL;
+}
+
+#define POOL_ENT_ALLOC_BLOCK 0x10000
+
+static pool_ent_t * pool_ent_alloc_new(size_t req_size)
+{
+ pool_ent_t * ent = calloc(1, sizeof(*ent));
+ const size_t alloc_size = (req_size + POOL_ENT_ALLOC_BLOCK - 1) & ~(POOL_ENT_ALLOC_BLOCK - 1);
+
+ if (ent == NULL)
+ return NULL;
+
+ ent->next = ent->prev = NULL;
+
+ // Alloc from vcsm
+ if ((ent->vcsm_hdl = vcsm_malloc_cache(alloc_size, VCSM_CACHE_TYPE_HOST, (char *)"vlc-subpic")) == -1)
+ goto fail1;
+ if ((ent->vc_hdl = vcsm_vc_hdl_from_hdl(ent->vcsm_hdl)) == 0)
+ goto fail2;
+ if ((ent->buf = vcsm_lock(ent->vcsm_hdl)) == NULL)
+ goto fail2;
+
+ ent->size = alloc_size;
+ return ent;
+
+fail2:
+ vcsm_free(ent->vcsm_hdl);
+fail1:
+ free(ent);
+ return NULL;
+}
+
+static inline pool_ent_t * pool_ent_ref(pool_ent_t * const ent)
+{
+// int n = atomic_fetch_add(&ent->ref_count, 1) + 1;
+// printf("Ref: %p: %d\n", ent, n);
+ atomic_fetch_add(&ent->ref_count, 1);
+ return ent;
+}
+
+static void pool_recycle(vzc_pool_ctl_t * const pc, pool_ent_t * const ent)
+{
+ pool_ent_t * xs = NULL;
+ int n;
+
+ if (ent == NULL)
+ return;
+
+ n = atomic_fetch_sub(&ent->ref_count, 1) - 1;
+
+// printf("%s: Pool: %p: Ent: %p: %d\n", __func__, &pc->ent_pool, ent, n);
+
+ if (n != 0)
+ return;
+
+ if (ent->pic != NULL) {
+ picture_Release(ent->pic);
+ ent->pic = NULL;
+ }
+
+ vlc_mutex_lock(&pc->lock);
+
+ // If we have a full pool then extract the LRU and free it
+ // Free done outside mutex
+ if (pc->ent_pool.n >= pc->max_n)
+ xs = ent_extract_tail(&pc->ent_pool);
+
+ ent_add_head(&pc->ent_pool, ent);
+
+ vlc_mutex_unlock(&pc->lock);
+
+ ent_free(xs);
+}
+
+// * This could be made more efficient, but this is easy
+static void pool_recycle_list(vzc_pool_ctl_t * const pc, ent_list_hdr_t * const elh)
+{
+ pool_ent_t * ent;
+ while ((ent = ent_extract_tail(elh)) != NULL) {
+ pool_recycle(pc, ent);
+ }
+}
+
+static pool_ent_t * pool_best_fit(vzc_pool_ctl_t * const pc, size_t req_size)
+{
+ pool_ent_t * best = NULL;
+
+ vlc_mutex_lock(&pc->lock);
+
+ {
+ pool_ent_t * ent = pc->ent_pool.ents;
+
+ // Simple scan
+ while (ent != NULL) {
+ if (ent->size >= req_size && ent->size <= req_size * 2 + POOL_ENT_ALLOC_BLOCK &&
+ (best == NULL || best->size > ent->size))
+ best = ent;
+ ent = ent->next;
+ }
+
+ // extract best from chain if we've found it
+ ent_extract(&pc->ent_pool, best);
+ }
+
+ vlc_mutex_unlock(&pc->lock);
+
+ if (best == NULL)
+ best = pool_ent_alloc_new(req_size);
+
+ if ((best->seq = ++pc->seq) == 0)
+ best->seq = ++pc->seq; // Never allow to be zero
+
+ atomic_store(&best->ref_count, 1);
+ return best;
+}
+
+
+const vlc_fourcc_t hw_mmal_vzc_subpicture_chromas[] = { VLC_CODEC_RGBA, VLC_CODEC_BGRA, VLC_CODEC_ARGB, 0 };
+
+void hw_mmal_vzc_buf_get_wh(MMAL_BUFFER_HEADER_T * const buf, int * const pW, int * const pH)
+{
+ const pool_ent_t *const ent = ((vzc_subbuf_ent_t *)buf->user_data)->ent;
+ *pW = ent->width;
+ *pH = ent->height;
+}
+
+bool hw_mmal_vzc_buf_set_format(MMAL_BUFFER_HEADER_T * const buf, MMAL_ES_FORMAT_T * const es_fmt)
+{
+ const pool_ent_t *const ent = ((vzc_subbuf_ent_t *)buf->user_data)->ent;
+ MMAL_VIDEO_FORMAT_T * const v_fmt = &es_fmt->es->video;
+
+ es_fmt->type = MMAL_ES_TYPE_VIDEO;
+ es_fmt->encoding = ent->enc_type;
+ es_fmt->encoding_variant = 0;
+
+ v_fmt->width = ent->width;
+ v_fmt->height = ent->height;
+ v_fmt->crop.x = 0;
+ v_fmt->crop.y = 0;
+ v_fmt->crop.width = ent->width;
+ v_fmt->crop.height = ent->height;
+
+ return true;
+}
+
+void hw_mmal_vzc_buf_frame_size(MMAL_BUFFER_HEADER_T * const buf,
+ uint32_t * const pWidth, uint32_t * const pHeight)
+{
+ const pool_ent_t *const ent = ((vzc_subbuf_ent_t *)buf->user_data)->ent;
+ *pWidth = ent->width;
+ *pHeight = ent->height;
+}
+
+
+MMAL_DISPLAYREGION_T * hw_mmal_vzc_buf_region(MMAL_BUFFER_HEADER_T * const buf)
+{
+ vzc_subbuf_ent_t * sb = buf->user_data;
+ return &sb->dreg;
+}
+
+static inline int rescale_x(int x, int mul, int div)
+{
+ return div == 0 ? x * mul : (x * mul + div/2) / div;
+}
+
+static void rescale_rect(MMAL_RECT_T * const d, const MMAL_RECT_T * const s, const MMAL_RECT_T * mul_rect, const MMAL_RECT_T * div_rect)
+{
+ d->x = rescale_x(s->x - div_rect->x, mul_rect->width, div_rect->width) + mul_rect->x;
+ d->y = rescale_x(s->y - div_rect->y, mul_rect->height, div_rect->height) + mul_rect->y;
+ d->width = rescale_x(s->width, mul_rect->width, div_rect->width);
+ d->height = rescale_x(s->height, mul_rect->height, div_rect->height);
+#if TRACE_TRANSFORMS
+ fprintf(stderr, "(%d,%d %dx%d) * (%d,%d %dx%d) / (%d,%d %dx%d) -> (%d,%d %dx%d)\n",
+ s->x, s->y, s->width, s->height,
+ mul_rect->x, mul_rect->y, mul_rect->width, mul_rect->height,
+ div_rect->x, div_rect->y, div_rect->width, div_rect->height,
+ d->x, d->y, d->width, d->height);
+#endif
+}
+
+static MMAL_RECT_T
+rect_untransform(MMAL_RECT_T s, const MMAL_RECT_T c, const MMAL_DISPLAYTRANSFORM_T t)
+{
+#if TRACE_TRANSFORMS
+ fprintf(stderr, "t=%d, s=%d,%d:%dx%d, c=%d,%d:%dx%d -> ", (int)t,
+ s.x,s.y,s.width,s.height,
+ c.x,c.y,c.width,c.height);
+#endif
+ if (is_transform_hflip(t))
+ s = rect_hflip(s, c);
+ if (is_transform_vflip(t) != 0)
+ s = rect_vflip(s, c);
+ if (is_transform_transpose(t) != 0)
+ s = rect_transpose(s);
+#if TRACE_TRANSFORMS
+ fprintf(stderr, "s=%d,%d:%dx%d\n",
+ s.x,s.y,s.width,s.height);
+#endif
+ return s;
+}
+
+void hw_mmal_vzc_buf_scale_dest_rect(MMAL_BUFFER_HEADER_T * const buf, const MMAL_RECT_T * const scale_rect, const MMAL_DISPLAYTRANSFORM_T scale_transform)
+{
+ vzc_subbuf_ent_t * sb = buf->user_data;
+ if (scale_rect == NULL) {
+ sb->dreg.dest_rect = sb->orig_dest_rect;
+ sb->dreg.transform = MMAL_DISPLAY_ROT0;
+ }
+ else
+ {
+ // The scale rect has been transposed if we have a transposing
+ // transform - untranspose so we are the same way up as the source
+ const MMAL_RECT_T c = (scale_transform & 4) == 0 ? *scale_rect : rect_transpose(*scale_rect);
+ rescale_rect(&sb->dreg.dest_rect, &sb->orig_dest_rect,
+ &c, &sb->pic_rect);
+ sb->dreg.dest_rect = rect_untransform(sb->dreg.dest_rect, c, scale_transform);
+ sb->dreg.transform = scale_transform;
+ }
+}
+
+unsigned int hw_mmal_vzc_buf_seq(MMAL_BUFFER_HEADER_T * const buf)
+{
+ vzc_subbuf_ent_t * sb = buf->user_data;
+ return sb->ent->seq;
+}
+
+
+// The intent with the ents_cur & ents_last stuff is to remember the buffers
+// we used on the last frame and reuse them on the current one if they are the
+// same. Unfortunately detection of "is_first" is only a heuristic (there are
+// no rules governing the order in which things are blended) so we must deal
+// (fairly) gracefully with it never (or always) being set.
+
+// dst_fmt gives the number space in which the destination pixels are specified
+
+MMAL_BUFFER_HEADER_T * hw_mmal_vzc_buf_from_pic(vzc_pool_ctl_t * const pc,
+ picture_t * const pic,
+ const video_format_t * src_fmt,
+ const MMAL_RECT_T dst_pic_rect,
+ const int x_offset, const int y_offset,
+ const unsigned int alpha,
+ const bool is_first)
+{
+ MMAL_BUFFER_HEADER_T * const buf = mmal_queue_get(pc->buf_pool->queue);
+ vzc_subbuf_ent_t * sb;
+
+ if (buf == NULL)
+ return NULL;
+
+ if ((sb = calloc(1, sizeof(*sb))) == NULL)
+ goto fail1;
+
+ // If first or we've had a lot of stuff move everything to the last list
+ // (we could deal more gracefully with the "too many" case but it shouldn't
+ // really happen)
+ if (is_first || pc->ents_cur.n >= CTX_BUFS_MAX) {
+ pool_recycle_list(pc, &pc->ents_prev);
+ ent_list_move(&pc->ents_prev, &pc->ents_cur);
+ }
+
+ sb->dreg.hdr.id = MMAL_PARAMETER_DISPLAYREGION;
+ sb->dreg.hdr.size = sizeof(sb->dreg);
+ buf->user_data = sb;
+
+ {
+ // ?? Round start offset as well as length
+ const video_format_t *const fmt = &pic->format;
+
+ const unsigned int bpp = (fmt->i_bits_per_pixel + 7) >> 3;
+ const unsigned int xl = (fmt->i_x_offset & ~15);
+ const unsigned int xr = (fmt->i_x_offset + fmt->i_visible_width + 15) & ~15;
+ const size_t dst_stride = (xr - xl) * bpp;
+ const size_t dst_lines = ((fmt->i_visible_height + 15) & ~15);
+ const size_t dst_size = dst_stride * dst_lines;
+
+ pool_ent_t * ent = ent_list_extract_pic_ent(&pc->ents_prev, pic);
+ bool needs_copy = false;
+
+ // If we didn't find ent in last then look in cur in case is_first
+ // isn't working
+ if (ent == NULL)
+ ent = ent_list_extract_pic_ent(&pc->ents_cur, pic);
+
+// printf("ent_found: %p\n", ent);
+
+ if (ent == NULL)
+ {
+ // Need a new ent
+ needs_copy = true;
+
+ if ((ent = pool_best_fit(pc, dst_size)) == NULL)
+ goto fail2;
+ if ((ent->enc_type = vlc_to_mmal_video_fourcc(&pic->format)) == 0)
+ goto fail2;
+
+ ent->pic = picture_Hold(pic);
+ }
+
+ ent_add_head(&pc->ents_cur, ent);
+
+ sb->ent = pool_ent_ref(ent);
+ hw_mmal_vzc_pool_ref(pc);
+
+ // Copy data
+ buf->next = NULL;
+ buf->cmd = 0;
+ buf->data = (uint8_t *)(ent->vc_hdl);
+ buf->alloc_size = buf->length = dst_size;
+ buf->offset = 0;
+ buf->flags = MMAL_BUFFER_HEADER_FLAG_FRAME_END;
+ buf->pts = buf->dts = pic->date != VLC_TICK_INVALID ? pic->date : MMAL_TIME_UNKNOWN;
+ buf->type->video = (MMAL_BUFFER_HEADER_VIDEO_SPECIFIC_T){
+ .planes = 1,
+ .pitch = { dst_stride }
+ };
+
+ // Remember offsets
+ sb->dreg.set = MMAL_DISPLAY_SET_SRC_RECT |
+ MMAL_DISPLAY_SET_DEST_RECT |
+ MMAL_DISPLAY_SET_FULLSCREEN |
+ MMAL_DISPLAY_SET_TRANSFORM |
+ MMAL_DISPLAY_SET_ALPHA;
+
+ sb->dreg.fullscreen = 0;
+
+ // Will be set later - zero now to avoid any confusion
+ sb->dreg.transform = MMAL_DISPLAY_ROT0;
+ sb->dreg.dest_rect = (MMAL_RECT_T){0, 0, 0, 0};
+
+ sb->dreg.alpha = (uint32_t)(alpha & 0xff) | MMAL_DISPLAY_ALPHA_FLAGS_MIX;
+
+// printf("+++ bpp:%d, vis:%dx%d wxh:%dx%d, d:%dx%d\n", bpp, fmt->i_visible_width, fmt->i_visible_height, fmt->i_width, fmt->i_height, dst_stride, dst_lines);
+
+ sb->dreg.src_rect = (MMAL_RECT_T){
+ .x = (fmt->i_x_offset - xl) + src_fmt->i_x_offset,
+ .y = src_fmt->i_y_offset,
+ .width = src_fmt->i_visible_width,
+ .height = src_fmt->i_visible_height
+ };
+
+ sb->pic_rect = dst_pic_rect;
+
+ sb->orig_dest_rect = (MMAL_RECT_T){
+ .x = x_offset,
+ .y = y_offset,
+ .width = src_fmt->i_visible_width,
+ .height = src_fmt->i_visible_height
+ };
+
+ if (needs_copy)
+ {
+ ent->width = dst_stride / bpp;
+ ent->height = dst_lines;
+
+ // 2D copy
+ {
+ uint8_t *d = ent->buf;
+ const uint8_t *s = pic->p[0].p_pixels + xl * bpp + fmt->i_y_offset * pic->p[0].i_pitch;
+
+ mem_copy_2d(d, dst_stride, s, pic->p[0].i_pitch, fmt->i_visible_height, dst_stride);
+
+ // And make sure it is actually in memory
+ if (pc->vcsm_init_type != VCSM_INIT_CMA) { // ** CMA is currently always uncached
+ flush_range(ent->buf, dst_stride * fmt->i_visible_height);
+ }
+ }
+ }
+ }
+
+ return buf;
+
+fail2:
+ free(sb);
+fail1:
+ mmal_buffer_header_release(buf);
+ return NULL;
+}
+
+void hw_mmal_vzc_pool_flush(vzc_pool_ctl_t * const pc)
+{
+ pool_recycle_list(pc, &pc->ents_prev);
+ pool_recycle_list(pc, &pc->ents_cur);
+}
+
+static void hw_mmal_vzc_pool_delete(vzc_pool_ctl_t * const pc)
+{
+
+// printf("<<< %s\n", __func__);
+
+ hw_mmal_vzc_pool_flush(pc);
+
+ ent_free_list(&pc->ent_pool);
+
+ if (pc->buf_pool != NULL)
+ mmal_pool_destroy(pc->buf_pool);
+
+ vlc_mutex_destroy(&pc->lock);
+
+ cma_vcsm_exit(pc->vcsm_init_type);
+
+// memset(pc, 0xba, sizeof(*pc)); // Zap for (hopefully) faster crash
+ free (pc);
+
+ // printf(">>> %s\n", __func__);
+}
+
+void hw_mmal_vzc_pool_release(vzc_pool_ctl_t * const pc)
+{
+ int n;
+
+ if (pc == NULL)
+ return;
+
+ n = atomic_fetch_sub(&pc->ref_count, 1) - 1;
+
+ if (n != 0)
+ return;
+
+ hw_mmal_vzc_pool_delete(pc);
+}
+
+void hw_mmal_vzc_pool_ref(vzc_pool_ctl_t * const pc)
+{
+ atomic_fetch_add(&pc->ref_count, 1);
+}
+
+static MMAL_BOOL_T vcz_pool_release_cb(MMAL_POOL_T * buf_pool, MMAL_BUFFER_HEADER_T *buf, void *userdata)
+{
+ vzc_pool_ctl_t * const pc = userdata;
+ vzc_subbuf_ent_t * const sb = buf->user_data;
+
+ VLC_UNUSED(buf_pool);
+
+// printf("<<< %s\n", __func__);
+
+ if (sb != NULL) {
+ buf->user_data = NULL;
+ pool_recycle(pc, sb->ent);
+ hw_mmal_vzc_pool_release(pc);
+ free(sb);
+ }
+
+// printf(">>> %s\n", __func__);
+
+ return MMAL_TRUE;
+}
+
+vzc_pool_ctl_t * hw_mmal_vzc_pool_new()
+{
+ vzc_pool_ctl_t * const pc = calloc(1, sizeof(*pc));
+
+ if (pc == NULL)
+ return NULL;
+
+ if ((pc->vcsm_init_type = cma_vcsm_init()) == VCSM_INIT_NONE)
+ {
+ free(pc);
+ return NULL;
+ }
+
+ pc->max_n = 8;
+ vlc_mutex_init(&pc->lock); // Must init before potential destruction
+
+ if ((pc->buf_pool = mmal_pool_create(64, 0)) == NULL)
+ {
+ hw_mmal_vzc_pool_delete(pc);
+ return NULL;
+ }
+
+ atomic_store(&pc->ref_count, 1);
+
+ mmal_pool_callback_set(pc->buf_pool, vcz_pool_release_cb, pc);
+
+ return pc;
+}
+
+//----------------------------------------------------------------------------
+
+
+static const uint8_t shift_00[] = {0,0,0,0};
+static const uint8_t shift_01[] = {0,1,1,1};
+
+int cma_pic_set_data(picture_t * const pic,
+ const MMAL_ES_FORMAT_T * const mm_esfmt,
+ const MMAL_BUFFER_HEADER_T * const buf)
+{
+ const MMAL_VIDEO_FORMAT_T * const mm_fmt = &mm_esfmt->es->video;
+ const MMAL_BUFFER_HEADER_VIDEO_SPECIFIC_T *const buf_vid = (buf == NULL) ? NULL : &buf->type->video;
+ cma_buf_t *const cb = cma_buf_pic_get(pic);
+ unsigned int planes = 1;
+
+ uint8_t * const data = cma_buf_addr(cb);
+ if (data == NULL) {
+ return VLC_ENOMEM;
+ }
+
+ const uint8_t * ws = shift_00;
+ const uint8_t * hs = shift_00;
+ int pb = 1;
+
+ switch (mm_esfmt->encoding)
+ {
+ case MMAL_ENCODING_ARGB:
+ case MMAL_ENCODING_ABGR:
+ case MMAL_ENCODING_RGBA:
+ case MMAL_ENCODING_BGRA:
+ case MMAL_ENCODING_RGB32:
+ case MMAL_ENCODING_BGR32:
+ pb = 4;
+ break;
+ case MMAL_ENCODING_RGB16:
+ pb = 2;
+ break;
+
+ case MMAL_ENCODING_I420:
+ ws = shift_01;
+ hs = shift_01;
+ planes = 3;
+ break;
+
+ case MMAL_ENCODING_YUVUV128:
+ hs = shift_01;
+ planes = 2;
+ break;
+
+ default:
+// msg_Err(p_filter, "%s: Unexpected format", __func__);
+ return VLC_EGENERIC;
+ }
+
+ // Fix up SAR if unset
+ if (pic->format.i_sar_den == 0 || pic->format.i_sar_num == 0) {
+ pic->format.i_sar_den = mm_fmt->par.den;
+ pic->format.i_sar_num = mm_fmt->par.num;
+ }
+
+ pic->i_planes = planes;
+ unsigned int offset = 0;
+ for (unsigned int i = 0; i != planes; ++i) {
+ pic->p[i] = (plane_t){
+ .p_pixels = data + (buf_vid != NULL ? buf_vid->offset[i] : offset),
+ .i_lines = mm_fmt->height >> hs[i],
+ .i_pitch = buf_vid != NULL ? buf_vid->pitch[i] : mm_fmt->width * pb,
+ .i_pixel_pitch = pb,
+ .i_visible_lines = mm_fmt->crop.height >> hs[i],
+ .i_visible_pitch = mm_fmt->crop.width >> ws[i]
+ };
+ offset += pic->p[i].i_pitch * pic->p[i].i_lines;
+ }
+ return VLC_SUCCESS;
+}
+
+int cma_buf_pic_attach(cma_buf_t * const cb, picture_t * const pic)
+{
+ if (!is_cma_buf_pic_chroma(pic->format.i_chroma))
+ return VLC_EGENERIC;
+ if (pic->context != NULL)
+ return VLC_EBADVAR;
+
+ pic_ctx_mmal_t * const ctx = calloc(1, sizeof(pic_ctx_mmal_t));
+
+ if (ctx == NULL)
+ return VLC_ENOMEM;
+
+ ctx->cmn.copy = hw_mmal_pic_ctx_copy;
+ ctx->cmn.destroy = hw_mmal_pic_ctx_destroy;
+ ctx->buf_count = 1; // cb takes the place of the 1st buf
+ ctx->cb = cb;
+
+ cma_buf_in_flight(cb);
+
+ pic->context = &ctx->cmn;
+ return VLC_SUCCESS;
+}
+
+
+//----------------------------------------------------------------------------
+
+/* Returns the type of the Pi being used
+*/
+bool rpi_is_model_pi4(void)
+{
+ return bcm_host_is_model_pi4();
+}
+
+// Board types that support PI3 hybrid HEVC accel
+bool rpi_use_pi3_hevc(void)
+{
+ const int t = bcm_host_get_model_type();
+ return
+ t == BCM_HOST_BOARD_TYPE_PI3MODELB ||
+ t == BCM_HOST_BOARD_TYPE_CM3 ||
+ t == BCM_HOST_BOARD_TYPE_PI3MODELBPLUS ||
+ t == BCM_HOST_BOARD_TYPE_PI3MODELBPLUS ||
+ t == BCM_HOST_BOARD_TYPE_PI3MODELAPLUS ||
+ t == BCM_HOST_BOARD_TYPE_CM3PLUS;
+}
+
+// Board types that support qpu adv deinterlace
+bool rpi_use_qpu_deinterlace(void)
+{
+ const int t = bcm_host_get_model_type();
+ return
+ t == BCM_HOST_BOARD_TYPE_MODELA ||
+ t == BCM_HOST_BOARD_TYPE_MODELB ||
+ t == BCM_HOST_BOARD_TYPE_MODELAPLUS ||
+ t == BCM_HOST_BOARD_TYPE_MODELBPLUS ||
+ t == BCM_HOST_BOARD_TYPE_PI2MODELB ||
+ t == BCM_HOST_BOARD_TYPE_CM ||
+ t == BCM_HOST_BOARD_TYPE_CM2 ||
+ t == BCM_HOST_BOARD_TYPE_PI3MODELB ||
+ t == BCM_HOST_BOARD_TYPE_PI0 ||
+ t == BCM_HOST_BOARD_TYPE_CM3 ||
+ t == BCM_HOST_BOARD_TYPE_PI0W ||
+ t == BCM_HOST_BOARD_TYPE_PI3MODELBPLUS ||
+ t == BCM_HOST_BOARD_TYPE_PI3MODELAPLUS ||
+ t == BCM_HOST_BOARD_TYPE_CM3PLUS;
+}
+
+
+// Preferred mode - none->cma on Pi4 otherwise legacy
+static volatile vcsm_init_type_t last_vcsm_type = VCSM_INIT_NONE;
+
+vcsm_init_type_t cma_vcsm_type(void)
+{
+ return last_vcsm_type;
+}
+
+vcsm_init_type_t cma_vcsm_init(void)
+{
+ vcsm_init_type_t rv = VCSM_INIT_NONE;
+ // We don't bother locking - taking a copy here should be good enough
+ vcsm_init_type_t try_type = last_vcsm_type;
+
+ if (try_type == VCSM_INIT_NONE) {
+ if (bcm_host_is_fkms_active() ||
+ bcm_host_is_kms_active())
+ try_type = VCSM_INIT_CMA;
+ else
+ try_type = VCSM_INIT_LEGACY;
+ }
+
+ if (try_type == VCSM_INIT_CMA) {
+ if (vcsm_init_ex(1, -1) == 0)
+ rv = VCSM_INIT_CMA;
+ else if (vcsm_init_ex(0, -1) == 0)
+ rv = VCSM_INIT_LEGACY;
+ }
+ else
+ {
+ if (vcsm_init_ex(0, -1) == 0)
+ rv = VCSM_INIT_LEGACY;
+ else if (vcsm_init_ex(1, -1) == 0)
+ rv = VCSM_INIT_CMA;
+ }
+
+ // Just in case this affects vcsm init do after that
+ if (rv != VCSM_INIT_NONE)
+ bcm_host_init();
+
+ last_vcsm_type = rv;
+ return rv;
+}
+
+void cma_vcsm_exit(const vcsm_init_type_t init_mode)
+{
+ if (init_mode != VCSM_INIT_NONE)
+ {
+ vcsm_exit();
+ bcm_host_deinit(); // Does nothing but add in case it ever does
+ }
+}
+
+const char * cma_vcsm_init_str(const vcsm_init_type_t init_mode)
+{
+ switch (init_mode)
+ {
+ case VCSM_INIT_CMA:
+ return "CMA";
+ case VCSM_INIT_LEGACY:
+ return "Legacy";
+ case VCSM_INIT_NONE:
+ return "none";
+ default:
+ break;
+ }
+ return "???";
+}
+
+
diff --git a/modules/hw/mmal/mmal_picture.h b/modules/hw/mmal/mmal_picture.h
index 3539f2cfc8..8d04517e08 100644
--- a/modules/hw/mmal/mmal_picture.h
+++ b/modules/hw/mmal/mmal_picture.h
@@ -24,19 +24,278 @@
#ifndef VLC_MMAL_MMAL_PICTURE_H_
#define VLC_MMAL_MMAL_PICTURE_H_
+#include <stdatomic.h>
+
#include <vlc_common.h>
#include <interface/mmal/mmal.h>
+#include "mmal_cma_pic.h"
+#include "mmal_cma.h"
+
/* Think twice before changing this. Incorrect values cause havoc. */
#define NUM_ACTUAL_OPAQUE_BUFFERS 30
-struct picture_sys_t {
- vlc_object_t *owner;
+#ifndef VLC_TICK_INVALID
+#define VLC_TICK_INVALID VLC_TS_INVALID
+#define VLC_VER_3 1
+#else
+#define VLC_VER_3 0
+#endif
+
+typedef struct mmal_port_pool_ref_s
+{
+ atomic_uint refs;
+ MMAL_POOL_T * pool;
+ MMAL_PORT_T * port;
+} hw_mmal_port_pool_ref_t;
+
+typedef struct pic_ctx_subpic_s {
+ picture_t * subpic;
+ int x, y;
+ int alpha;
+} pic_ctx_subpic_t;
+
+
+const char * str_fourcc(char * const buf, const unsigned int fcc);
+
+MMAL_FOURCC_T vlc_to_mmal_video_fourcc(const video_frame_format_t * const vf_vlc);
+MMAL_FOURCC_T vlc_to_mmal_color_space(const video_color_space_t vlc_cs);
+void hw_mmal_vlc_fmt_to_mmal_fmt(MMAL_ES_FORMAT_T *const es_fmt, const video_frame_format_t * const vf_vlc);
+// Returns true if fmt_changed
+// frame_rate ignored for compare, but is set if something else is updated
+bool hw_mmal_vlc_pic_to_mmal_fmt_update(MMAL_ES_FORMAT_T *const es_fmt, const picture_t * const pic);
+
+// Copy pic contents into an existing buffer
+int hw_mmal_copy_pic_to_buf(void * const buf_data, uint32_t * const pLength,
+ const MMAL_ES_FORMAT_T * const fmt, const picture_t * const pic);
+
+hw_mmal_port_pool_ref_t * hw_mmal_port_pool_ref_create(MMAL_PORT_T * const port,
+ const unsigned int headers, const uint32_t payload_size);
+void hw_mmal_port_pool_ref_release(hw_mmal_port_pool_ref_t * const ppr, const bool in_cb);
+bool hw_mmal_port_pool_ref_recycle(hw_mmal_port_pool_ref_t * const ppr, MMAL_BUFFER_HEADER_T * const buf);
+MMAL_STATUS_T hw_mmal_port_pool_ref_fill(hw_mmal_port_pool_ref_t * const ppr);
+static inline void hw_mmal_port_pool_ref_acquire(hw_mmal_port_pool_ref_t * const ppr)
+{
+ atomic_fetch_add(&ppr->refs, 1);
+}
+MMAL_STATUS_T hw_mmal_opaque_output(vlc_object_t * const obj,
+ hw_mmal_port_pool_ref_t ** pppr,
+ MMAL_PORT_T * const port,
+ const unsigned int extra_buffers, MMAL_PORT_BH_CB_T callback);
+
+static inline int hw_mmal_pic_has_sub_bufs(picture_t * const pic)
+{
+ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic->context;
+ return ctx->buf_count > 1;
+}
+
+static inline void hw_mmal_pic_sub_buf_add(picture_t * const pic, MMAL_BUFFER_HEADER_T * const sub)
+{
+ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic->context;
+
+ if (ctx->buf_count >= CTX_BUFS_MAX) {
+ mmal_buffer_header_release(sub);
+ return;
+ }
+
+ ctx->bufs[ctx->buf_count++] = sub;
+}
+
+static inline MMAL_BUFFER_HEADER_T * hw_mmal_pic_sub_buf_get(picture_t * const pic, const unsigned int n)
+{
+ pic_ctx_mmal_t * const ctx = (pic_ctx_mmal_t *)pic->context;
+
+ return n + 1 > ctx->buf_count ? NULL : ctx->bufs[n + 1];
+}
+
+static inline bool hw_mmal_chroma_is_mmal(const vlc_fourcc_t chroma)
+{
+ return
+ chroma == VLC_CODEC_MMAL_OPAQUE ||
+ chroma == VLC_CODEC_MMAL_ZC_SAND8 ||
+ chroma == VLC_CODEC_MMAL_ZC_SAND10 ||
+ chroma == VLC_CODEC_MMAL_ZC_SAND30 ||
+ chroma == VLC_CODEC_MMAL_ZC_I420 ||
+ chroma == VLC_CODEC_MMAL_ZC_RGB32;
+}
+
+static inline bool hw_mmal_pic_is_mmal(const picture_t * const pic)
+{
+ return hw_mmal_chroma_is_mmal(pic->format.i_chroma);
+}
+
+picture_context_t * hw_mmal_pic_ctx_copy(picture_context_t * pic_ctx_cmn);
+void hw_mmal_pic_ctx_destroy(picture_context_t * pic_ctx_cmn);
+picture_context_t * hw_mmal_gen_context(
+ MMAL_BUFFER_HEADER_T * buf, hw_mmal_port_pool_ref_t * const ppr);
+
+int hw_mmal_get_gpu_mem(void);
+
+
+static inline MMAL_STATUS_T port_parameter_set_uint32(MMAL_PORT_T * port, uint32_t id, uint32_t val)
+{
+ const MMAL_PARAMETER_UINT32_T param = {
+ .hdr = {.id = id, .size = sizeof(MMAL_PARAMETER_UINT32_T)},
+ .value = val
+ };
+ return mmal_port_parameter_set(port, &param.hdr);
+}
+
+static inline MMAL_STATUS_T port_parameter_set_bool(MMAL_PORT_T * const port, const uint32_t id, const bool val)
+{
+ const MMAL_PARAMETER_BOOLEAN_T param = {
+ .hdr = {.id = id, .size = sizeof(MMAL_PARAMETER_BOOLEAN_T)},
+ .enable = val
+ };
+ return mmal_port_parameter_set(port, &param.hdr);
+}
+
+static inline MMAL_STATUS_T port_send_replicated(MMAL_PORT_T * const port, MMAL_POOL_T * const rep_pool,
+ MMAL_BUFFER_HEADER_T * const src_buf,
+ const uint64_t seq)
+{
+ MMAL_STATUS_T err;
+ MMAL_BUFFER_HEADER_T *const rep_buf = mmal_queue_wait(rep_pool->queue);
+
+ if (rep_buf == NULL)
+ return MMAL_ENOSPC;
+
+ if ((err = mmal_buffer_header_replicate(rep_buf, src_buf)) != MMAL_SUCCESS)
+ return err;
+
+ rep_buf->pts = seq;
+
+ if ((err = mmal_port_send_buffer(port, rep_buf)) != MMAL_SUCCESS)
+ {
+ mmal_buffer_header_release(rep_buf);
+ return err;
+ }
+
+ return MMAL_SUCCESS;
+}
+
+
+static inline void pic_to_buf_copy_props(MMAL_BUFFER_HEADER_T * const buf, const picture_t * const pic)
+{
+ if (!pic->b_progressive)
+ {
+ buf->flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED;
+ buf->type->video.flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED;
+ }
+ else
+ {
+ buf->flags &= ~MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED;
+ buf->type->video.flags &= ~MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED;
+ }
+ if (pic->b_top_field_first)
+ {
+ buf->flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST;
+ buf->type->video.flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST;
+ }
+ else
+ {
+ buf->flags &= ~MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST;
+ buf->type->video.flags &= ~MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST;
+ }
+ buf->pts = pic->date != VLC_TICK_INVALID ? pic->date : MMAL_TIME_UNKNOWN;
+ buf->dts = buf->pts;
+}
+
+static inline void buf_to_pic_copy_props(picture_t * const pic, const MMAL_BUFFER_HEADER_T * const buf)
+{
+ // Contrary to docn the interlace & tff flags turn up in the header flags rather than the
+ // video specific flags (which appear to be currently unused).
+ pic->b_progressive = (buf->flags & MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED) == 0;
+ pic->b_top_field_first = (buf->flags & MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST) != 0;
+
+ pic->date = buf->pts != MMAL_TIME_UNKNOWN ? buf->pts :
+ buf->dts != MMAL_TIME_UNKNOWN ? buf->dts :
+ VLC_TICK_INVALID;
+}
+
+MMAL_BUFFER_HEADER_T * hw_mmal_pic_buf_copied(const picture_t *const pic,
+ MMAL_POOL_T * const rep_pool,
+ MMAL_PORT_T * const port,
+ cma_buf_pool_t * const cbp);
+
+MMAL_BUFFER_HEADER_T * hw_mmal_pic_buf_replicated(const picture_t *const pic, MMAL_POOL_T * const rep_pool);
+
+struct vzc_pool_ctl_s;
+typedef struct vzc_pool_ctl_s vzc_pool_ctl_t;
+
+// At the moment we cope with any mono-planar RGBA thing
+// We could cope with many other things but they currently don't occur
+extern const vlc_fourcc_t hw_mmal_vzc_subpicture_chromas[];
+static inline bool hw_mmal_vzc_subpic_fmt_valid(const video_frame_format_t * const vf_vlc)
+{
+ const vlc_fourcc_t vfcc_src = vf_vlc->i_chroma;
+ for (const vlc_fourcc_t * p = hw_mmal_vzc_subpicture_chromas; *p != 0; ++p)
+ if (*p == vfcc_src)
+ return true;
+
+ return false;
+}
+
+bool hw_mmal_vzc_buf_set_format(MMAL_BUFFER_HEADER_T * const buf, MMAL_ES_FORMAT_T * const es_fmt);
+MMAL_DISPLAYREGION_T * hw_mmal_vzc_buf_region(MMAL_BUFFER_HEADER_T * const buf);
+void hw_mmal_vzc_buf_scale_dest_rect(MMAL_BUFFER_HEADER_T * const buf, const MMAL_RECT_T * const scale_rect, const MMAL_DISPLAYTRANSFORM_T scale_transform);
+void hw_mmal_vzc_buf_get_wh(MMAL_BUFFER_HEADER_T * const buf, int * const pW, int * const pH);
+unsigned int hw_mmal_vzc_buf_seq(MMAL_BUFFER_HEADER_T * const buf);
+MMAL_BUFFER_HEADER_T * hw_mmal_vzc_buf_from_pic(vzc_pool_ctl_t * const pc, picture_t * const pic,
+ const video_format_t * src_fmt,
+ const MMAL_RECT_T dst_pic_rect,
+ const int x_offset, const int y_offset,
+ const unsigned int alpha, const bool is_first);
+void hw_mmal_vzc_buf_frame_size(MMAL_BUFFER_HEADER_T * const buf,
+ uint32_t * const pWidth, uint32_t * const pHeight);
+
+void hw_mmal_vzc_pool_flush(vzc_pool_ctl_t * const pc);
+void hw_mmal_vzc_pool_release(vzc_pool_ctl_t * const pc);
+void hw_mmal_vzc_pool_ref(vzc_pool_ctl_t * const pc);
+vzc_pool_ctl_t * hw_mmal_vzc_pool_new(void);
+
+
+static inline MMAL_RECT_T vis_mmal_rect(const video_format_t * const fmt)
+{
+ return (MMAL_RECT_T){
+ .x = fmt->i_x_offset,
+ .y = fmt->i_y_offset,
+ .width = fmt->i_visible_width,
+ .height = fmt->i_visible_height
+ };
+}
+
+int cma_pic_set_data(picture_t * const pic,
+ const MMAL_ES_FORMAT_T * const mm_esfmt,
+ const MMAL_BUFFER_HEADER_T * const buf);
+
+// Attaches cma buf to pic
+// Marks in_flight if not all_in_flight anyway
+int cma_buf_pic_attach(cma_buf_t * const cb, picture_t * const pic);
+
+int rpi_get_model_type(void);
+bool rpi_is_model_pi4(void);
+bool rpi_use_pi3_hevc(void);
+bool rpi_use_qpu_deinterlace(void);
+bool rpi_is_fkms_active(void);
+
+typedef enum vcsm_init_type_e {
+ VCSM_INIT_NONE = 0,
+ VCSM_INIT_LEGACY,
+ VCSM_INIT_CMA
+} vcsm_init_type_t;
+
+vcsm_init_type_t cma_vcsm_init(void);
+void cma_vcsm_exit(const vcsm_init_type_t init_mode);
+vcsm_init_type_t cma_vcsm_type(void);
+const char * cma_vcsm_init_str(const vcsm_init_type_t init_mode);
+
- MMAL_BUFFER_HEADER_T *buffer;
- bool displayed;
-};
+#define VOUT_DISPLAY_CHANGE_MMAL_BASE 1024
+#define VOUT_DISPLAY_CHANGE_MMAL_HIDE (VOUT_DISPLAY_CHANGE_MMAL_BASE + 0)
-int mmal_picture_lock(picture_t *picture);
+#define MMAL_COMPONENT_DEFAULT_RESIZER "vc.ril.resize"
+#define MMAL_COMPONENT_ISP_RESIZER "vc.ril.isp"
+#define MMAL_COMPONENT_HVS "vc.ril.hvs"
#endif
diff --git a/modules/hw/mmal/rpi_prof.h b/modules/hw/mmal/rpi_prof.h
new file mode 100644
index 0000000000..bae488b623
--- /dev/null
+++ b/modules/hw/mmal/rpi_prof.h
@@ -0,0 +1,110 @@
+#ifndef RPI_PROFILE_H
+#define RPI_PROFILE_H
+
+#include <stdint.h>
+#include <inttypes.h>
+
+#ifndef RPI_PROFILE
+#define RPI_PROFILE 0
+#endif
+
+#if RPI_PROFILE
+
+#include "v7_pmu.h"
+
+#ifdef RPI_PROC_ALLOC
+#define X volatile
+#define Z =0
+#else
+#define X extern volatile
+#define Z
+#endif
+
+X uint64_t av_rpi_prof0_cycles Z;
+X unsigned int av_rpi_prof0_cnt Z;
+#define RPI_prof0_MAX_DURATION 100000
+
+X uint64_t av_rpi_prof1_cycles Z;
+X unsigned int av_rpi_prof1_cnt Z;
+#define RPI_prof1_MAX_DURATION 100000
+
+X uint64_t av_rpi_prof2_cycles Z;
+X unsigned int av_rpi_prof2_cnt Z;
+#define RPI_prof2_MAX_DURATION 10000
+
+X uint64_t av_rpi_prof_n_cycles[128];
+X unsigned int av_rpi_prof_n_cnt[128];
+#define RPI_prof_n_MAX_DURATION 10000
+
+
+#undef X
+#undef Z
+
+#define PROFILE_INIT()\
+do {\
+ enable_pmu();\
+ enable_ccnt();\
+} while (0)
+
+#define PROFILE_START()\
+do {\
+ volatile uint32_t perf_1 = read_ccnt();\
+ volatile uint32_t perf_2
+
+
+#define PROFILE_ACC(x)\
+ perf_2 = read_ccnt();\
+ {\
+ const uint32_t duration = perf_2 - perf_1;\
+ if (duration < RPI_##x##_MAX_DURATION)\
+ {\
+ av_rpi_##x##_cycles += duration;\
+ av_rpi_##x##_cnt += 1;\
+ }\
+ }\
+} while(0)
+
+
+#define PROFILE_ACC_N(n)\
+ if ((n) >= 0) {\
+ perf_2 = read_ccnt();\
+ {\
+ const uint32_t duration = perf_2 - perf_1;\
+ if (duration < RPI_prof_n_MAX_DURATION)\
+ {\
+ av_rpi_prof_n_cycles[n] += duration;\
+ av_rpi_prof_n_cnt[n] += 1;\
+ }\
+ }\
+ }\
+} while(0)
+
+#define PROFILE_PRINTF(x)\
+ printf("%-20s cycles=%14" PRIu64 "; cnt=%8u; avg=%5" PRIu64 "\n", #x, av_rpi_##x##_cycles, av_rpi_##x##_cnt,\
+ av_rpi_##x##_cnt == 0 ? (uint64_t)0 : av_rpi_##x##_cycles / (uint64_t)av_rpi_##x##_cnt)
+
+#define PROFILE_PRINTF_N(n)\
+ printf("prof[%d] cycles=%14" PRIu64 "; cnt=%8u; avg=%5" PRIu64 "\n", (n), av_rpi_prof_n_cycles[n], av_rpi_prof_n_cnt[n],\
+ av_rpi_prof_n_cnt[n] == 0 ? (uint64_t)0 : av_rpi_prof_n_cycles[n] / (uint64_t)av_rpi_prof_n_cnt[n])
+
+#define PROFILE_CLEAR_N(n) \
+do {\
+ av_rpi_prof_n_cycles[n] = 0;\
+ av_rpi_prof_n_cnt[n] = 0;\
+} while(0)
+
+#else
+
+// No profile
+#define PROFILE_INIT()
+#define PROFILE_START()
+#define PROFILE_ACC(x)
+#define PROFILE_ACC_N(x)
+#define PROFILE_PRINTF(x)
+#define PROFILE_PRINTF_N(x)
+#define PROFILE_CLEAR_N(n)
+
+#endif
+
+#endif
+
diff --git a/modules/hw/mmal/subpic.c b/modules/hw/mmal/subpic.c
new file mode 100644
index 0000000000..42770eaac9
--- /dev/null
+++ b/modules/hw/mmal/subpic.c
@@ -0,0 +1,257 @@
+/*****************************************************************************
+ * mmal.c: MMAL-based decoder plugin for Raspberry Pi
+ *****************************************************************************
+ * Authors: jc@kynesim.co.uk
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdatomic.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_codec.h>
+#include <vlc_filter.h>
+#include <vlc_threads.h>
+
+#include <bcm_host.h>
+#include <interface/mmal/mmal.h>
+#include <interface/mmal/util/mmal_util.h>
+#include <interface/mmal/util/mmal_default_components.h>
+
+#include "mmal_picture.h"
+#include "subpic.h"
+
+
+#define TRACE_ALL 0
+
+static inline bool cmp_rect(const MMAL_RECT_T * const a, const MMAL_RECT_T * const b)
+{
+ return a->x == b->x && a->y == b->y && a->width == b->width && a->height == b->height;
+}
+
+void hw_mmal_subpic_flush(vlc_object_t * const p_filter, subpic_reg_stash_t * const sub)
+{
+ VLC_UNUSED(p_filter);
+ if (sub->port != NULL && sub->port->is_enabled)
+ mmal_port_disable(sub->port);
+ sub->seq = 0;
+}
+
+void hw_mmal_subpic_close(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe)
+{
+ hw_mmal_subpic_flush(p_filter, spe);
+
+ if (spe->pool != NULL)
+ mmal_pool_destroy(spe->pool);
+
+ // Zap to avoid any accidental reuse
+ *spe = (subpic_reg_stash_t){NULL};
+}
+
+MMAL_STATUS_T hw_mmal_subpic_open(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe, MMAL_PORT_T * const port,
+ const int display_id, const unsigned int layer)
+{
+ MMAL_STATUS_T err;
+
+ // Start by zapping all to zero
+ *spe = (subpic_reg_stash_t){NULL};
+
+ if ((err = port_parameter_set_bool(port, MMAL_PARAMETER_ZERO_COPY, true)) != MMAL_SUCCESS)
+ {
+ msg_Err(p_filter, "Failed to set sub port zero copy");
+ return err;
+ }
+
+ if ((spe->pool = mmal_pool_create(30, 0)) == NULL)
+ {
+ msg_Err(p_filter, "Failed to create sub pool");
+ return MMAL_ENOMEM;
+ }
+
+ port->userdata = (void *)p_filter;
+ spe->port = port;
+ spe->display_id = display_id;
+ spe->layer = layer;
+
+ return MMAL_SUCCESS;
+}
+
+static void conv_subpic_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf)
+{
+#if TRACE_ALL
+ msg_Dbg((filter_t *)port->userdata, "<<< %s cmd=%d, user=%p, buf=%p, flags=%#x, len=%d/%d, pts=%lld",
+ __func__, buf->cmd, buf->user_data, buf, buf->flags, buf->length, buf->alloc_size, (long long)buf->pts);
+#else
+ VLC_UNUSED(port);
+#endif
+
+ mmal_buffer_header_release(buf); // Will extract & release pic in pool callback
+}
+
+static int
+subpic_send_empty(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe, const uint64_t pts)
+{
+ MMAL_BUFFER_HEADER_T *const buf = mmal_queue_wait(spe->pool->queue);
+ MMAL_STATUS_T err;
+
+ if (buf == NULL) {
+ msg_Err(p_filter, "Buffer get for subpic failed");
+ return -1;
+ }
+#if TRACE_ALL
+ msg_Dbg(p_filter, "Remove pic for sub %d", spe->seq);
+#endif
+ buf->cmd = 0;
+ buf->data = NULL;
+ buf->alloc_size = 0;
+ buf->offset = 0;
+ buf->flags = MMAL_BUFFER_HEADER_FLAG_FRAME_END;
+ buf->pts = pts;
+ buf->dts = MMAL_TIME_UNKNOWN;
+ buf->user_data = NULL;
+
+ if ((err = mmal_port_send_buffer(spe->port, buf)) != MMAL_SUCCESS)
+ {
+ msg_Err(p_filter, "Send buffer to subput failed");
+ mmal_buffer_header_release(buf);
+ return -1;
+ }
+ return 0;
+}
+
+// < 0 Error
+// 0 Done & stop
+// 1 Done & continue
+
+int hw_mmal_subpic_update(vlc_object_t * const p_filter,
+ MMAL_BUFFER_HEADER_T * const sub_buf,
+ subpic_reg_stash_t * const spe,
+ const video_format_t * const fmt,
+ const MMAL_RECT_T * const scale_out,
+ const MMAL_DISPLAYTRANSFORM_T transform_out,
+ const uint64_t pts)
+{
+ MMAL_STATUS_T err;
+
+ if (sub_buf == NULL)
+ {
+ if (spe->port->is_enabled && spe->seq != 0)
+ {
+ subpic_send_empty(p_filter, spe, pts);
+ spe->seq = 0;
+ }
+ }
+ else
+ {
+ const unsigned int seq = hw_mmal_vzc_buf_seq(sub_buf);
+ bool needs_update = (spe->seq != seq);
+
+ hw_mmal_vzc_buf_scale_dest_rect(sub_buf, scale_out, transform_out);
+
+ if (hw_mmal_vzc_buf_set_format(sub_buf, spe->port->format))
+ {
+ MMAL_DISPLAYREGION_T * const dreg = hw_mmal_vzc_buf_region(sub_buf);
+ MMAL_VIDEO_FORMAT_T *const v_fmt = &spe->port->format->es->video;
+
+ v_fmt->frame_rate.den = fmt->i_frame_rate_base;
+ v_fmt->frame_rate.num = fmt->i_frame_rate;
+ v_fmt->par.den = fmt->i_sar_den;
+ v_fmt->par.num = fmt->i_sar_num;
+ v_fmt->color_space = MMAL_COLOR_SPACE_UNKNOWN;
+
+ if (needs_update || dreg->alpha != spe->alpha || !cmp_rect(&dreg->dest_rect, &spe->dest_rect)) {
+
+ spe->alpha = dreg->alpha;
+ spe->dest_rect = dreg->dest_rect;
+ needs_update = true;
+
+ if (spe->display_id >= 0)
+ {
+ dreg->display_num = spe->display_id;
+ dreg->set |= MMAL_DISPLAY_SET_NUM;
+ }
+ dreg->layer = spe->layer;
+ dreg->set |= MMAL_DISPLAY_SET_LAYER;
+
+#if TRACE_ALL
+ msg_Dbg(p_filter, "%s: Update region: Set=%x, dest=%dx%d @ (%d,%d), src=%dx%d @ (%d,%d), layer=%d, alpha=%#x",
+ __func__, dreg->set,
+ dreg->dest_rect.width, dreg->dest_rect.height, dreg->dest_rect.x, dreg->dest_rect.y,
+ dreg->src_rect.width, dreg->src_rect.height, dreg->src_rect.x, dreg->src_rect.y,
+ dreg->layer, dreg->alpha);
+#endif
+
+ // If now completely offscreen just flush this & return
+ // We only do -ve as (a) that is easy and (b) it seems to be
+ // something that can confuse mmal
+ if (dreg->dest_rect.y + dreg->dest_rect.height <= 0 ||
+ dreg->dest_rect.x + dreg->dest_rect.width <= 0)
+ {
+ if (spe->port->is_enabled)
+ subpic_send_empty(p_filter, spe, pts);
+ spe->seq = seq;
+ return 1;
+ }
+
+ if ((err = mmal_port_parameter_set(spe->port, &dreg->hdr)) != MMAL_SUCCESS)
+ {
+ msg_Err(p_filter, "Set display region on subput failed");
+ return -1;
+ }
+
+ if ((err = mmal_port_format_commit(spe->port)) != MMAL_SUCCESS)
+ {
+ msg_Dbg(p_filter, "%s: Subpic commit fail: %d", __func__, err);
+ return -1;
+ }
+ }
+ }
+
+ if (!spe->port->is_enabled)
+ {
+ spe->port->buffer_num = 30;
+ spe->port->buffer_size = spe->port->buffer_size_recommended; // Not used but shuts up the error checking
+
+ if ((err = mmal_port_enable(spe->port, conv_subpic_cb)) != MMAL_SUCCESS)
+ {
+ msg_Dbg(p_filter, "%s: Subpic enable fail: %d", __func__, err);
+ return -1;
+ }
+ }
+
+ if (needs_update)
+ {
+#if TRACE_ALL
+ msg_Dbg(p_filter, "Update pic for sub %d", spe->seq);
+#endif
+ if ((err = port_send_replicated(spe->port, spe->pool, sub_buf, pts)) != MMAL_SUCCESS)
+ {
+ msg_Err(p_filter, "Send buffer to subput failed");
+ return -1;
+ }
+
+ spe->seq = seq;
+ }
+ }
+ return 1;
+}
+
+
+
diff --git a/modules/hw/mmal/subpic.h b/modules/hw/mmal/subpic.h
new file mode 100644
index 0000000000..4599f8a5da
--- /dev/null
+++ b/modules/hw/mmal/subpic.h
@@ -0,0 +1,33 @@
+#ifndef VLC_HW_MMAL_SUBPIC_H_
+#define VLC_HW_MMAL_SUBPIC_H_
+
+typedef struct subpic_reg_stash_s
+{
+ MMAL_PORT_T * port;
+ MMAL_POOL_T * pool;
+ int display_id; // -1 => do not set
+ unsigned int layer;
+ // Shadow vars so we can tell if stuff has changed
+ MMAL_RECT_T dest_rect;
+ unsigned int alpha;
+ unsigned int seq;
+} subpic_reg_stash_t;
+
+int hw_mmal_subpic_update(vlc_object_t * const p_filter,
+ MMAL_BUFFER_HEADER_T * const sub_buf,
+ subpic_reg_stash_t * const spe,
+ const video_format_t * const fmt,
+ const MMAL_RECT_T * const scale_out,
+ const MMAL_DISPLAYTRANSFORM_T transform_out,
+ const uint64_t pts);
+
+void hw_mmal_subpic_flush(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe);
+
+void hw_mmal_subpic_close(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe);
+
+// If display id is -1 it will be unset
+MMAL_STATUS_T hw_mmal_subpic_open(vlc_object_t * const p_filter, subpic_reg_stash_t * const spe, MMAL_PORT_T * const port,
+ const int display_id, const unsigned int layer);
+
+#endif
+
diff --git a/modules/hw/mmal/transform_ops.h b/modules/hw/mmal/transform_ops.h
new file mode 100644
index 0000000000..acfb5c254b
--- /dev/null
+++ b/modules/hw/mmal/transform_ops.h
@@ -0,0 +1,99 @@
+#ifndef VLC_MMAL_TRANSFORM_OPS_H
+#define VLC_MMAL_TRANSFORM_OPS_H
+
+#include <vlc_common.h>
+#include <vlc_picture.h>
+#include <interface/mmal/mmal.h>
+
+
+// These are enums with the same order so simply coerce
+static inline MMAL_DISPLAYTRANSFORM_T vlc_to_mmal_transform(const video_orientation_t orientation){
+ return (MMAL_DISPLAYTRANSFORM_T)orientation;
+}
+
+// MMAL headers comment these (getting 2 a bit wrong) but do not give
+// defines
+#define XFORM_H_SHIFT 0 // Hflip
+#define XFORM_V_SHIFT 1 // Vflip
+#define XFORM_T_SHIFT 2 // Transpose
+#define XFORM_H_BIT (1 << XFORM_H_SHIFT)
+#define XFORM_V_BIT (1 << XFORM_V_SHIFT)
+#define XFORM_T_BIT (1 << XFORM_T_SHIFT)
+
+static inline bool
+is_transform_transpose(const MMAL_DISPLAYTRANSFORM_T t)
+{
+ return ((unsigned int)t & XFORM_T_BIT) != 0;
+}
+
+static inline bool
+is_transform_hflip(const MMAL_DISPLAYTRANSFORM_T t)
+{
+ return ((unsigned int)t & XFORM_H_BIT) != 0;
+}
+
+static inline bool
+is_transform_vflip(const MMAL_DISPLAYTRANSFORM_T t)
+{
+ return ((unsigned int)t & XFORM_V_BIT) != 0;
+}
+
+static inline MMAL_DISPLAYTRANSFORM_T
+swap_transform_hv(const MMAL_DISPLAYTRANSFORM_T x)
+{
+ return (((x >> XFORM_H_SHIFT) & 1) << XFORM_V_SHIFT) |
+ (((x >> XFORM_V_SHIFT) & 1) << XFORM_H_SHIFT) |
+ (x & XFORM_T_BIT);
+}
+
+static inline MMAL_DISPLAYTRANSFORM_T
+transform_inverse(const MMAL_DISPLAYTRANSFORM_T x)
+{
+ return is_transform_transpose(x) ? swap_transform_hv(x) : x;
+}
+
+// Transform generated by A then B
+// All ops are self inverse so can simply be XORed on their own
+// H & V flips after a transpose need to be swapped
+static inline MMAL_DISPLAYTRANSFORM_T
+combine_transform(const MMAL_DISPLAYTRANSFORM_T a, const MMAL_DISPLAYTRANSFORM_T b)
+{
+ return a ^ (is_transform_transpose(a) ? swap_transform_hv(b) : b);
+}
+
+static inline MMAL_RECT_T
+rect_transpose(const MMAL_RECT_T s)
+{
+ return (MMAL_RECT_T){
+ .x = s.y,
+ .y = s.x,
+ .width = s.height,
+ .height = s.width
+ };
+}
+
+// hflip s in c
+static inline MMAL_RECT_T rect_hflip(const MMAL_RECT_T s, const MMAL_RECT_T c)
+{
+ return (MMAL_RECT_T){
+ .x = c.x + (c.x + c.width) - (s.x + s.width),
+ .y = s.y,
+ .width = s.width,
+ .height = s.height
+ };
+}
+
+// vflip s in c
+static inline MMAL_RECT_T rect_vflip(const MMAL_RECT_T s, const MMAL_RECT_T c)
+{
+ return (MMAL_RECT_T){
+ .x = s.x,
+ .y = (c.y + c.height) - (s.y - c.y) - s.height,
+ .width = s.width,
+ .height = s.height
+ };
+}
+
+
+#endif
+
diff --git a/modules/hw/mmal/v7_pmu.S b/modules/hw/mmal/v7_pmu.S
new file mode 100644
index 0000000000..dc82357bf3
--- /dev/null
+++ b/modules/hw/mmal/v7_pmu.S
@@ -0,0 +1,263 @@
+/*------------------------------------------------------------
+Performance Monitor Block
+------------------------------------------------------------*/
+ .arm @ Make sure we are in ARM mode.
+ .text
+ .align 2
+ .global getPMN @ export this function for the linker
+
+/* Returns the number of progammable counters uint32_t getPMN(void) */
+
+getPMN:
+ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC Register */
+ MOV r0, r0, LSR #11 /* Shift N field down to bit 0 */
+ AND r0, r0, #0x1F /* Mask to leave just the 5 N bits */
+ BX lr
+
+
+
+ .global pmn_config @ export this function for the linker
+ /* Sets the event for a programmable counter to record */
+ /* void pmn_config(unsigned counter, uint32_t event) */
+ /* counter = r0 = Which counter to program (e.g. 0 for PMN0, 1 for PMN1 */
+ /* event = r1 = The event code */
+pmn_config:
+ AND r0, r0, #0x1F /* Mask to leave only bits 4:0 */
+ MCR p15, 0, r0, c9, c12, 5 /* Write PMNXSEL Register */
+ MCR p15, 0, r1, c9, c13, 1 /* Write EVTSELx Register */
+ BX lr
+
+
+
+ .global ccnt_divider @ export this function for the linker
+ /* Enables/disables the divider (1/64) on CCNT */
+ /* void ccnt_divider(int divider) */
+ /* divider = r0 = If 0 disable divider, else enable dvider */
+ccnt_divider:
+ MRC p15, 0, r1, c9, c12, 0 /* Read PMNC */
+
+ CMP r0, #0x0 /* IF (r0 == 0) */
+ BICEQ r1, r1, #0x08 /* THEN: Clear the D bit (disables the */
+ ORRNE r1, r1, #0x08 /* ELSE: Set the D bit (enables the di */
+
+ MCR p15, 0, r1, c9, c12, 0 /* Write PMNC */
+ BX lr
+
+
+ /* --------------------------------------------------------------- */
+ /* Enable/Disable */
+ /* --------------------------------------------------------------- */
+
+ .global enable_pmu @ export this function for the linker
+ /* Global PMU enable */
+ /* void enable_pmu(void) */
+enable_pmu:
+ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */
+ ORR r0, r0, #0x01 /* Set E bit */
+ MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */
+ BX lr
+
+
+
+ .global disable_pmu @ export this function for the linker
+ /* Global PMU disable */
+ /* void disable_pmu(void) */
+disable_pmu:
+ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */
+ BIC r0, r0, #0x01 /* Clear E bit */
+ MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */
+ BX lr
+
+
+
+ .global enable_ccnt @ export this function for the linker
+ /* Enable the CCNT */
+ /* void enable_ccnt(void) */
+enable_ccnt:
+ MOV r0, #0x80000000 /* Set C bit */
+ MCR p15, 0, r0, c9, c12, 1 /* Write CNTENS Register */
+ BX lr
+
+
+
+ .global disable_ccnt @ export this function for the linker
+ /* Disable the CCNT */
+ /* void disable_ccnt(void) */
+disable_ccnt:
+ MOV r0, #0x80000000 /* Clear C bit */
+ MCR p15, 0, r0, c9, c12, 2 /* Write CNTENC Register */
+ BX lr
+
+
+
+ .global enable_pmn @ export this function for the linker
+ /* Enable PMN{n} */
+ /* void enable_pmn(uint32_t counter) */
+ /* counter = r0 = The counter to enable (e.g. 0 for PMN0, 1 for PMN1)
+enable_pmn: */
+ MOV r1, #0x1 /* Use arg (r0) to set which counter t */
+ MOV r1, r1, LSL r0
+
+ MCR p15, 0, r1, c9, c12, 1 /* Write CNTENS Register */
+ BX lr
+
+
+
+ .global disable_pmn @ export this function for the linker
+ /* Enable PMN{n} */
+ /* void disable_pmn(uint32_t counter) */
+ /* counter = r0 = The counter to enable (e.g. 0 for PMN0, 1 for PMN1)
+disable_pmn: */
+ MOV r1, #0x1 /* Use arg (r0) to set which counter t */
+ MOV r1, r1, LSL r0
+
+ MCR p15, 0, r1, c9, c12, 1 /* Write CNTENS Register */
+ BX lr
+
+
+
+ .global enable_pmu_user_access @ export this function for the linker
+ /* Enables User mode access to the PMU (must be called in a priviledge */
+ /* void enable_pmu_user_access(void) */
+enable_pmu_user_access:
+ MRC p15, 0, r0, c9, c14, 0 /* Read PMUSERENR Register */
+ ORR r0, r0, #0x01 /* Set EN bit (bit 0) */
+ MCR p15, 0, r0, c9, c14, 0 /* Write PMUSERENR Register */
+ BX lr
+
+
+
+ .global disable_pmu_user_access @ export this function for the linke
+ /* Disables User mode access to the PMU (must be called in a priviledg */
+ /* void disable_pmu_user_access(void) */
+disable_pmu_user_access:
+ MRC p15, 0, r0, c9, c14, 0 /* Read PMUSERENR Register */
+ BIC r0, r0, #0x01 /* Clear EN bit (bit 0) */
+ MCR p15, 0, r0, c9, c14, 0 /* Write PMUSERENR Register */
+ BX lr
+
+
+ /* --------------------------------------------------------------- */
+ /* Counter read registers */
+ /* --------------------------------------------------------------- */
+
+ .global read_ccnt @ export this function for the linker
+ /* Returns the value of CCNT */
+ /* uint32_t read_ccnt(void) */
+read_ccnt:
+ MRC p15, 0, r0, c9, c13, 0 /* Read CCNT Register */
+ BX lr
+
+
+ .global read_pmn @ export this function for the linker
+ /* Returns the value of PMN{n} */
+ /* uint32_t read_pmn(uint32_t counter) */
+ /* counter = r0 = The counter to read (e.g. 0 for PMN0, 1 for PMN1) *
+read_pmn: */
+ AND r0, r0, #0x1F /* Mask to leave only bits 4:0 */
+ MCR p15, 0, r0, c9, c12, 5 /* Write PMNXSEL Register */
+ MRC p15, 0, r0, c9, c13, 2 /* Read current PMNx Register */
+ BX lr
+
+
+ /* --------------------------------------------------------------- */
+ /* Software Increment */
+ /* --------------------------------------------------------------- */
+
+ .global pmu_software_increment @ export this function for the linker
+ /* Writes to software increment register */
+ /* void pmu_software_increment(uint32_t counter) */
+ /* counter = r0 = The counter to increment (e.g. 0 for PMN0, 1 for PMN
+pmu_software_increment: */
+ MOV r1, #0x01
+ MOV r1, r1, LSL r0
+ MCR p15, 0, r1, c9, c12, 4 /* Write SWINCR Register */
+ BX lr
+
+ /* --------------------------------------------------------------- */
+ /* Overflow & Interrupt Generation */
+ /* --------------------------------------------------------------- */
+
+ .global read_flags @ export this function for the linker
+ /* Returns the value of the overflow flags */
+ /* uint32_t read_flags(void) */
+read_flags:
+ MRC p15, 0, r0, c9, c12, 3 /* Read FLAG Register */
+ BX lr
+
+
+ .global write_flags @ export this function for the linker
+ /* Writes the overflow flags */
+ /* void write_flags(uint32_t flags) */
+write_flags:
+ MCR p15, 0, r0, c9, c12, 3 /* Write FLAG Register */
+ BX lr
+
+
+ .global enable_ccnt_irq @ export this function for the linker
+ /* Enables interrupt generation on overflow of the CCNT */
+ /* void enable_ccnt_irq(void) */
+enable_ccnt_irq:
+ MOV r0, #0x80000000
+ MCR p15, 0, r0, c9, c14, 1 /* Write INTENS Register */
+ BX lr
+
+ .global disable_ccnt_irq @ export this function for the linker
+ /* Disables interrupt generation on overflow of the CCNT */
+ /* void disable_ccnt_irq(void) */
+disable_ccnt_irq:
+ MOV r0, #0x80000000
+ MCR p15, 0, r0, c9, c14, 2 /* Write INTENC Register */
+ BX lr
+
+
+ .global enable_pmn_irq @ export this function for the linker
+ /* Enables interrupt generation on overflow of PMN{x} */
+ /* void enable_pmn_irq(uint32_t counter) */
+ /* counter = r0 = The counter to enable the interrupt for (e.g. 0 for
+enable_pmn_irq: */
+ MOV r1, #0x1 /* Use arg (r0) to set which counter */
+ MOV r0, r1, LSL r0
+ MCR p15, 0, r0, c9, c14, 1 /* Write INTENS Register */
+ BX lr
+
+ .global disable_pmn_irq @ export this function for the linker
+ /* Disables interrupt generation on overflow of PMN{x} */
+ /* void disable_pmn_irq(uint32_t counter) */
+ /* counter = r0 = The counter to disable the interrupt for (e.g. 0 fo
+disable_pmn_irq: */
+ MOV r1, #0x1 /* Use arg (r0) to set which counter t */
+ MOV r0, r1, LSL r0
+ MCR p15, 0, r0, c9, c14, 2 /* Write INTENC Register */
+ BX lr
+
+ /* --------------------------------------------------------------- */
+ /* Reset Functions */
+ /* --------------------------------------------------------------- */
+
+ .global reset_pmn @ export this function for the linker
+ /* Resets the programmable counters */
+ /* void reset_pmn(void) */
+reset_pmn:
+ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */
+ ORR r0, r0, #0x02 /* Set P bit (Event Counter Reset) */
+ MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */
+ BX lr
+
+
+ .global reset_ccnt @ export this function for the linker
+ /* Resets the CCNT */
+ /* void reset_ccnt(void) */
+reset_ccnt:
+ MRC p15, 0, r0, c9, c12, 0 /* Read PMNC */
+ ORR r0, r0, #0x04 /* Set C bit (Event Counter Reset) */
+ MCR p15, 0, r0, c9, c12, 0 /* Write PMNC */
+ BX lr
+
+
+ .end @end of code, this line is optional.
+/* ------------------------------------------------------------ */
+/* End of v7_pmu.s */
+/* ------------------------------------------------------------ */
+
+
diff --git a/modules/hw/mmal/v7_pmu.h b/modules/hw/mmal/v7_pmu.h
new file mode 100644
index 0000000000..55c2bdf885
--- /dev/null
+++ b/modules/hw/mmal/v7_pmu.h
@@ -0,0 +1,113 @@
+// ------------------------------------------------------------
+// PMU for Cortex-A/R (v7-A/R)
+// ------------------------------------------------------------
+
+#ifndef _V7_PMU_H
+#define _V7_PMU_H
+
+// Returns the number of progammable counters
+unsigned int getPMN(void);
+
+// Sets the event for a programmable counter to record
+// counter = r0 = Which counter to program (e.g. 0 for PMN0, 1 for PMN1)
+// event = r1 = The event code (from appropiate TRM or ARM Architecture Reference Manual)
+void pmn_config(unsigned int counter, unsigned int event);
+
+// Enables/disables the divider (1/64) on CCNT
+// divider = r0 = If 0 disable divider, else enable dvider
+void ccnt_divider(int divider);
+
+//
+// Enables and disables
+//
+
+// Global PMU enable
+// On ARM11 this enables the PMU, and the counters start immediately
+// On Cortex this enables the PMU, there are individual enables for the counters
+void enable_pmu(void);
+
+// Global PMU disable
+// On Cortex, this overrides the enable state of the individual counters
+void disable_pmu(void);
+
+// Enable the CCNT
+void enable_ccnt(void);
+
+// Disable the CCNT
+void disable_ccnt(void);
+
+// Enable PMN{n}
+// counter = The counter to enable (e.g. 0 for PMN0, 1 for PMN1)
+void enable_pmn(unsigned int counter);
+
+// Enable PMN{n}
+// counter = The counter to enable (e.g. 0 for PMN0, 1 for PMN1)
+void disable_pmn(unsigned int counter);
+
+//
+// Read counter values
+//
+
+// Returns the value of CCNT
+unsigned int read_ccnt(void);
+
+// Returns the value of PMN{n}
+// counter = The counter to read (e.g. 0 for PMN0, 1 for PMN1)
+unsigned int read_pmn(unsigned int counter);
+
+//
+// Overflow and interrupts
+//
+
+// Returns the value of the overflow flags
+unsigned int read_flags(void);
+
+// Writes the overflow flags
+void write_flags(unsigned int flags);
+
+// Enables interrupt generation on overflow of the CCNT
+void enable_ccnt_irq(void);
+
+// Disables interrupt generation on overflow of the CCNT
+void disable_ccnt_irq(void);
+
+// Enables interrupt generation on overflow of PMN{x}
+// counter = The counter to enable the interrupt for (e.g. 0 for PMN0, 1 for PMN1)
+void enable_pmn_irq(unsigned int counter);
+
+// Disables interrupt generation on overflow of PMN{x}
+// counter = r0 = The counter to disable the interrupt for (e.g. 0 for PMN0, 1 for PMN1)
+void disable_pmn_irq(unsigned int counter);
+
+//
+// Counter reset functions
+//
+
+// Resets the programmable counters
+void reset_pmn(void);
+
+// Resets the CCNT
+void reset_ccnt(void);
+
+//
+// Software Increment
+
+// Writes to software increment register
+// counter = The counter to increment (e.g. 0 for PMN0, 1 for PMN1)
+void pmu_software_increment(unsigned int counter);
+
+//
+// User mode access
+//
+
+// Enables User mode access to the PMU (must be called in a priviledged mode)
+void enable_pmu_user_access(void);
+
+// Disables User mode access to the PMU (must be called in a priviledged mode)
+void disable_pmu_user_access(void);
+
+#endif
+// ------------------------------------------------------------
+// End of v7_pmu.h
+// ------------------------------------------------------------
+
diff --git a/modules/hw/mmal/vout.c b/modules/hw/mmal/vout.c
index 76188a457c..b9fef71e3c 100644
--- a/modules/hw/mmal/vout.c
+++ b/modules/hw/mmal/vout.c
@@ -27,21 +27,28 @@
#endif
#include <math.h>
+#include <stdatomic.h>
#include <vlc_common.h>
-#include <vlc_atomic.h>
#include <vlc_plugin.h>
#include <vlc_threads.h>
#include <vlc_vout_display.h>
+#include <vlc_modules.h>
-#include "mmal_picture.h"
-
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wbad-function-cast"
#include <bcm_host.h>
+#pragma GCC diagnostic pop
#include <interface/mmal/mmal.h>
#include <interface/mmal/util/mmal_util.h>
#include <interface/mmal/util/mmal_default_components.h>
#include <interface/vmcs_host/vc_tvservice.h>
-#include <interface/vmcs_host/vc_dispmanx.h>
+
+#include "mmal_picture.h"
+#include "subpic.h"
+#include "transform_ops.h"
+
+#define TRACE_ALL 0
#define MAX_BUFFERS_IN_TRANSIT 1
#define VC_TV_MAX_MODE_IDS 127
@@ -50,10 +57,28 @@
#define MMAL_LAYER_TEXT N_("VideoCore layer where the video is displayed.")
#define MMAL_LAYER_LONGTEXT N_("VideoCore layer where the video is displayed. Subpictures are displayed directly above and a black background directly below.")
-#define MMAL_BLANK_BACKGROUND_NAME "mmal-blank-background"
-#define MMAL_BLANK_BACKGROUND_TEXT N_("Blank screen below video.")
-#define MMAL_BLANK_BACKGROUND_LONGTEXT N_("Render blank screen below video. " \
- "Increases VideoCore load.")
+#define MMAL_DISPLAY_NAME "mmal-display"
+#define MMAL_DISPLAY_TEXT N_("Output device for Rpi fullscreen.")
+#define MMAL_DISPLAY_LONGTEXT N_("Output device for Rpi fullscreen. " \
+"Valid values are HDMI-1,HDMI-2. By default if qt-fullscreen-screennumber " \
+"is specified (or set by Fullscreen Output Device in Preferences) " \
+"HDMI-<qt-fullscreen-screennumber+1> will be used, otherwise HDMI-1.")
+
+#define MMAL_VOUT_TRANSFORM_NAME "mmal-vout-transform"
+#define MMAL_VOUT_TRANSFORM_TEXT N_("Video transform for Rpi fullscreen.")
+#define MMAL_VOUT_TRANSFORM_LONGTEXT N_("Video transform for Rpi fullscreen."\
+"Transforms availible: auto, 0, 90, 180, 270, hflip, vflip, transpose, antitranspose")
+
+#define MMAL_VOUT_WINDOW_NAME "mmal-vout-window"
+#define MMAL_VOUT_WINDOW_TEXT N_("Display window for Rpi fullscreen")
+#define MMAL_VOUT_WINDOW_LONGTEXT N_("Display window for Rpi fullscreen."\
+"fullscreen|<width>x<height>+<x>+<y>")
+
+#define MMAL_VOUT_TRANSPARENT_NAME "mmal-vout-transparent"
+#define MMAL_VOUT_TRANSPARENT_TEXT N_("Enable layers beneeth the vodeo layer.")
+#define MMAL_VOUT_TRANSPARENT_LONGTEXT N_("Enable layers beneath the video layer."\
+" By default these are disabled."\
+" Having the lower layers enabled can impact video performance")
#define MMAL_ADJUST_REFRESHRATE_NAME "mmal-adjust-refreshrate"
#define MMAL_ADJUST_REFRESHRATE_TEXT N_("Adjust HDMI refresh rate to the video.")
@@ -68,332 +93,628 @@
#define PHASE_OFFSET_TARGET ((double)0.25)
#define PHASE_CHECK_INTERVAL 100
-static int Open(vlc_object_t *);
-static void Close(vlc_object_t *);
+#define SUBS_MAX 4
-vlc_module_begin()
- set_shortname(N_("MMAL vout"))
- set_description(N_("MMAL-based vout plugin for Raspberry Pi"))
- set_capability("vout display", 90)
- add_shortcut("mmal_vout")
- add_integer(MMAL_LAYER_NAME, 1, MMAL_LAYER_TEXT, MMAL_LAYER_LONGTEXT, false)
- add_bool(MMAL_BLANK_BACKGROUND_NAME, true, MMAL_BLANK_BACKGROUND_TEXT,
- MMAL_BLANK_BACKGROUND_LONGTEXT, true);
- add_bool(MMAL_ADJUST_REFRESHRATE_NAME, false, MMAL_ADJUST_REFRESHRATE_TEXT,
- MMAL_ADJUST_REFRESHRATE_LONGTEXT, false)
- add_bool(MMAL_NATIVE_INTERLACED, false, MMAL_NATIVE_INTERLACE_TEXT,
- MMAL_NATIVE_INTERLACE_LONGTEXT, false)
- set_callbacks(Open, Close)
-vlc_module_end()
-
-struct dmx_region_t {
- struct dmx_region_t *next;
- picture_t *picture;
- VC_RECT_T bmp_rect;
- VC_RECT_T src_rect;
- VC_RECT_T dst_rect;
- VC_DISPMANX_ALPHA_T alpha;
- DISPMANX_ELEMENT_HANDLE_T element;
- DISPMANX_RESOURCE_HANDLE_T resource;
- int32_t pos_x;
- int32_t pos_y;
-};
+typedef struct vout_subpic_s {
+ MMAL_COMPONENT_T *component;
+ subpic_reg_stash_t sub;
+} vout_subpic_t;
struct vout_display_sys_t {
- vlc_cond_t buffer_cond;
- vlc_mutex_t buffer_mutex;
vlc_mutex_t manage_mutex;
- plane_t planes[3]; /* Depending on video format up to 3 planes are used */
- picture_t **pictures; /* Actual list of alloced pictures passed into picture_pool */
- picture_pool_t *picture_pool;
-
+ vcsm_init_type_t init_type;
MMAL_COMPONENT_T *component;
MMAL_PORT_T *input;
MMAL_POOL_T *pool; /* mmal buffer headers, used for pushing pictures to component*/
- struct dmx_region_t *dmx_region;
int i_planes; /* Number of actually used planes, 1 for opaque, 3 for i420 */
- uint32_t buffer_size; /* size of actual mmal buffers */
int buffers_in_transit; /* number of buffers currently pushed to mmal component */
unsigned num_buffers; /* number of buffers allocated at mmal port */
- DISPMANX_DISPLAY_HANDLE_T dmx_handle;
- DISPMANX_ELEMENT_HANDLE_T bkg_element;
- DISPMANX_RESOURCE_HANDLE_T bkg_resource;
- unsigned display_width;
- unsigned display_height;
+ int display_id;
+ MMAL_RECT_T win_rect; // Window rect after transform(s)
+ MMAL_RECT_T display_rect; // Actual shape of display (x, y always 0)
+ MMAL_RECT_T req_win; // User requested window (w=0 => fullscreen)
- int i_frame_rate_base; /* cached framerate to detect changes for rate adjustment */
- int i_frame_rate;
+ MMAL_RECT_T spu_rect; // Output rectangle in cfg coords (for subpic placement)
+ MMAL_RECT_T dest_rect; // Output rectangle in display coords
+ MMAL_DISPLAYTRANSFORM_T dest_transform; // Dest window coord transform
+ MMAL_DISPLAYTRANSFORM_T display_transform; // "Native" display transform
+ MMAL_DISPLAYTRANSFORM_T video_transform; // Combined config+native transform
+
+ unsigned int i_frame_rate_base; /* cached framerate to detect changes for rate adjustment */
+ unsigned int i_frame_rate;
int next_phase_check; /* lowpass for phase check frequency */
int phase_offset; /* currently applied offset to presentation time in ns */
int layer; /* the dispman layer (z-index) used for video rendering */
+ bool transparent; // Do not disable layers beneath ours
bool need_configure_display; /* indicates a required display reconfigure to main thread */
bool adjust_refresh_rate;
bool native_interlaced;
bool b_top_field_first; /* cached interlaced settings to detect changes for native mode */
bool b_progressive;
- bool opaque; /* indicated use of opaque picture format (zerocopy) */
+ bool force_config;
+
+ vout_subpic_t subs[SUBS_MAX];
+ // Stash for subpics derived from the passed subpicture rather than
+ // included with the main pic
+ MMAL_BUFFER_HEADER_T * subpic_bufs[SUBS_MAX];
+
+ picture_pool_t * pic_pool;
+
+ struct vout_isp_conf_s {
+ MMAL_COMPONENT_T *component;
+ MMAL_PORT_T * input;
+ MMAL_PORT_T * output;
+ MMAL_QUEUE_T * out_q;
+ MMAL_POOL_T * in_pool;
+ MMAL_POOL_T * out_pool;
+ bool pending;
+ } isp;
+
+ MMAL_POOL_T * copy_pool;
+ MMAL_BUFFER_HEADER_T * copy_buf;
+
+ // Subpic blend if we have to do it here
+ vzc_pool_ctl_t * vzc;
};
-static const vlc_fourcc_t subpicture_chromas[] = {
- VLC_CODEC_RGBA,
- 0
-};
-/* Utility functions */
-static inline uint32_t align(uint32_t x, uint32_t y);
-static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg,
- const video_format_t *fmt);
+// ISP setup
+
+static inline bool want_isp(const vout_display_t * const vd)
+{
+ return (vd->fmt.i_chroma == VLC_CODEC_MMAL_ZC_SAND10);
+}
-/* VLC vout display callbacks */
-static picture_pool_t *vd_pool(vout_display_t *vd, unsigned count);
-static void vd_prepare(vout_display_t *vd, picture_t *picture,
- subpicture_t *subpicture);
-static void vd_display(vout_display_t *vd, picture_t *picture,
- subpicture_t *subpicture);
-static int vd_control(vout_display_t *vd, int query, va_list args);
-static void vd_manage(vout_display_t *vd);
+static inline bool want_copy(const vout_display_t * const vd)
+{
+ return (vd->fmt.i_chroma == VLC_CODEC_I420 || vd->fmt.i_chroma == VLC_CODEC_I420_10L);
+}
-/* MMAL callbacks */
-static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
-static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
+static inline vlc_fourcc_t req_chroma(const vout_display_t * const vd)
+{
+ return !hw_mmal_chroma_is_mmal(vd->fmt.i_chroma) && !want_copy(vd) ?
+ VLC_CODEC_I420 :
+ vd->fmt.i_chroma;
+}
-/* TV service */
-static int query_resolution(vout_display_t *vd, unsigned *width, unsigned *height);
-static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1,
- uint32_t param2);
-static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt);
-static int set_latency_target(vout_display_t *vd, bool enable);
+static MMAL_FOURCC_T vout_vlc_to_mmal_pic_fourcc(const unsigned int fcc)
+{
+ switch (fcc){
+ case VLC_CODEC_MMAL_OPAQUE:
+ return MMAL_ENCODING_OPAQUE;
+ case VLC_CODEC_MMAL_ZC_SAND8:
+ return MMAL_ENCODING_YUVUV128;
+ case VLC_CODEC_MMAL_ZC_SAND10:
+ return MMAL_ENCODING_YUVUV64_10;
+ case VLC_CODEC_MMAL_ZC_SAND30:
+ return MMAL_ENCODING_YUV10_COL;
+ case VLC_CODEC_MMAL_ZC_I420:
+ case VLC_CODEC_I420:
+ return MMAL_ENCODING_I420;
+ default:
+ break;
+ }
+ return MMAL_ENCODING_I420;
+}
-/* DispManX */
-static void display_subpicture(vout_display_t *vd, subpicture_t *subpicture);
-static void close_dmx(vout_display_t *vd);
-static struct dmx_region_t *dmx_region_new(vout_display_t *vd,
- DISPMANX_UPDATE_HANDLE_T update, subpicture_region_t *region);
-static void dmx_region_update(struct dmx_region_t *dmx_region,
- DISPMANX_UPDATE_HANDLE_T update, picture_t *picture);
-static void dmx_region_delete(struct dmx_region_t *dmx_region,
- DISPMANX_UPDATE_HANDLE_T update);
-static void show_background(vout_display_t *vd, bool enable);
-static void maintain_phase_sync(vout_display_t *vd);
+static void display_set_format(const vout_display_t * const vd, MMAL_ES_FORMAT_T *const es_fmt, const bool is_intermediate)
+{
+ const unsigned int w = is_intermediate ? vd->fmt.i_visible_width : vd->fmt.i_width ;
+ const unsigned int h = is_intermediate ? vd->fmt.i_visible_height : vd->fmt.i_height;
+ MMAL_VIDEO_FORMAT_T * const v_fmt = &es_fmt->es->video;
+
+ es_fmt->type = MMAL_ES_TYPE_VIDEO;
+ es_fmt->encoding = is_intermediate ? MMAL_ENCODING_I420 : vout_vlc_to_mmal_pic_fourcc(vd->fmt.i_chroma);
+ es_fmt->encoding_variant = 0;
+
+ v_fmt->width = (w + 31) & ~31;
+ v_fmt->height = (h + 15) & ~15;
+ v_fmt->crop.x = 0;
+ v_fmt->crop.y = 0;
+ v_fmt->crop.width = w;
+ v_fmt->crop.height = h;
+ if (vd->fmt.i_sar_num == 0 || vd->fmt.i_sar_den == 0) {
+ v_fmt->par.num = 1;
+ v_fmt->par.den = 1;
+ } else {
+ v_fmt->par.num = vd->fmt.i_sar_num;
+ v_fmt->par.den = vd->fmt.i_sar_den;
+ }
+ v_fmt->frame_rate.num = vd->fmt.i_frame_rate;
+ v_fmt->frame_rate.den = vd->fmt.i_frame_rate_base;
+ v_fmt->color_space = vlc_to_mmal_color_space(vd->fmt.space);
-static int Open(vlc_object_t *object)
+ msg_Dbg(vd, "WxH: %dx%d, Crop: %dx%d", v_fmt->width, v_fmt->height, v_fmt->crop.width, v_fmt->crop.height);
+}
+
+static MMAL_RECT_T
+display_src_rect(const vout_display_t * const vd, const video_format_t * const src)
{
- vout_display_t *vd = (vout_display_t *)object;
- vout_display_sys_t *sys;
- uint32_t buffer_pitch, buffer_height;
- vout_display_place_t place;
- MMAL_DISPLAYREGION_T display_region;
+ const bool wants_isp = want_isp(vd);
+
+ // Scale source derived cropping to actual picture shape
+ return (MMAL_RECT_T){
+ .x = wants_isp ? 0 : src->i_x_offset * vd->fmt.i_width / src->i_width,
+ .y = wants_isp ? 0 : src->i_y_offset * vd->fmt.i_height / src->i_height,
+ .width = src->i_visible_width * vd->fmt.i_width / src->i_width,
+ .height = src->i_visible_height * vd->fmt.i_height / src->i_height
+ };
+}
+
+static void isp_input_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf)
+{
+#if TRACE_ALL
+ vout_display_t * const vd = (vout_display_t *)port->userdata;
+ pic_ctx_mmal_t * ctx = buf->user_data;
+ msg_Dbg(vd, "<<< %s: cmd=%d, ctx=%p, buf=%p, flags=%#x, pts=%lld", __func__, buf->cmd, ctx, buf,
+ buf->flags, (long long)buf->pts);
+#else
+ VLC_UNUSED(port);
+#endif
+
+ mmal_buffer_header_release(buf);
+
+#if TRACE_ALL
+ msg_Dbg(vd, ">>> %s", __func__);
+#endif
+}
+
+static void isp_control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+{
+ vout_display_t *vd = (vout_display_t *)port->userdata;
MMAL_STATUS_T status;
- int ret = VLC_SUCCESS;
- unsigned i;
- if (vout_display_IsWindowed(vd))
- return VLC_EGENERIC;
+ if (buffer->cmd == MMAL_EVENT_ERROR) {
+ status = *(uint32_t *)buffer->data;
+ msg_Err(vd, "MMAL error %"PRIx32" \"%s\"", status, mmal_status_to_string(status));
+ }
- sys = calloc(1, sizeof(struct vout_display_sys_t));
- if (!sys)
- return VLC_ENOMEM;
- vd->sys = sys;
+ mmal_buffer_header_release(buffer);
+}
- sys->layer = var_InheritInteger(vd, MMAL_LAYER_NAME);
- bcm_host_init();
+static void isp_output_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf)
+{
+ if (buf->cmd == 0 && buf->length != 0)
+ {
+ // The filter structure etc. should always exist if we have contents
+ // but might not on later flushes as we shut down
+ vout_display_t * const vd = (vout_display_t *)port->userdata;
+ struct vout_isp_conf_s *const isp = &vd->sys->isp;
+
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s: cmd=%d; flags=%#x, pts=%lld", __func__, buf->cmd, buf->flags, (long long) buf->pts);
+#endif
+ mmal_queue_put(isp->out_q, buf);
+#if TRACE_ALL
+ msg_Dbg(vd, ">>> %s: out Q len=%d", __func__, mmal_queue_length(isp->out_q));
+#endif
+ }
+ else
+ {
+ mmal_buffer_header_reset(buf);
+ mmal_buffer_header_release(buf);
+ }
+}
- sys->opaque = vd->fmt.i_chroma == VLC_CODEC_MMAL_OPAQUE;
+static void isp_empty_out_q(struct vout_isp_conf_s * const isp)
+{
+ MMAL_BUFFER_HEADER_T * buf;
+ // We can be called as part of error recovery so allow for missing Q
+ if (isp->out_q == NULL)
+ return;
- status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &sys->component);
- if (status != MMAL_SUCCESS) {
- msg_Err(vd, "Failed to create MMAL component %s (status=%"PRIx32" %s)",
- MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
+ while ((buf = mmal_queue_get(isp->out_q)) != NULL)
+ mmal_buffer_header_release(buf);
+}
+
+static void isp_flush(struct vout_isp_conf_s * const isp)
+{
+ if (!isp->input->is_enabled)
+ mmal_port_disable(isp->input);
+
+ if (isp->output->is_enabled)
+ mmal_port_disable(isp->output);
+
+ isp_empty_out_q(isp);
+ isp->pending = false;
+}
+
+static MMAL_STATUS_T isp_prepare(vout_display_t * const vd, struct vout_isp_conf_s * const isp)
+{
+ MMAL_STATUS_T err;
+ MMAL_BUFFER_HEADER_T * buf;
+
+ if (!isp->output->is_enabled) {
+ if ((err = mmal_port_enable(isp->output, isp_output_cb)) != MMAL_SUCCESS)
+ {
+ msg_Err(vd, "ISP output port enable failed");
+ return err;
+ }
}
- sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)vd;
- status = mmal_port_enable(sys->component->control, control_port_cb);
- if (status != MMAL_SUCCESS) {
- msg_Err(vd, "Failed to enable control port %s (status=%"PRIx32" %s)",
- sys->component->control->name, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
+ while ((buf = mmal_queue_get(isp->out_pool->queue)) != NULL) {
+ if ((err = mmal_port_send_buffer(isp->output, buf)) != MMAL_SUCCESS)
+ {
+ msg_Err(vd, "ISP output port stuff failed");
+ return err;
+ }
}
- sys->input = sys->component->input[0];
- sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)vd;
+ if (!isp->input->is_enabled) {
+ if ((err = mmal_port_enable(isp->input, isp_input_cb)) != MMAL_SUCCESS)
+ {
+ msg_Err(vd, "ISP input port enable failed");
+ return err;
+ }
+ }
+ return MMAL_SUCCESS;
+}
- if (sys->opaque) {
- sys->input->format->encoding = MMAL_ENCODING_OPAQUE;
- sys->i_planes = 1;
- sys->buffer_size = sys->input->buffer_size_recommended;
- } else {
- sys->input->format->encoding = MMAL_ENCODING_I420;
- vd->fmt.i_chroma = VLC_CODEC_I420;
- buffer_pitch = align(vd->fmt.i_width, 32);
- buffer_height = align(vd->fmt.i_height, 16);
- sys->i_planes = 3;
- sys->buffer_size = 3 * buffer_pitch * buffer_height / 2;
- }
-
- sys->input->format->es->video.width = vd->fmt.i_width;
- sys->input->format->es->video.height = vd->fmt.i_height;
- sys->input->format->es->video.crop.x = 0;
- sys->input->format->es->video.crop.y = 0;
- sys->input->format->es->video.crop.width = vd->fmt.i_width;
- sys->input->format->es->video.crop.height = vd->fmt.i_height;
- sys->input->format->es->video.par.num = vd->source.i_sar_num;
- sys->input->format->es->video.par.den = vd->source.i_sar_den;
+static void isp_close(vout_display_t * const vd, vout_display_sys_t * const vd_sys)
+{
+ struct vout_isp_conf_s * const isp = &vd_sys->isp;
+ VLC_UNUSED(vd);
- status = mmal_port_format_commit(sys->input);
- if (status != MMAL_SUCCESS) {
- msg_Err(vd, "Failed to commit format for input port %s (status=%"PRIx32" %s)",
- sys->input->name, status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
+ if (isp->component == NULL)
+ return;
+
+ isp_flush(isp);
+
+ if (isp->component->control->is_enabled)
+ mmal_port_disable(isp->component->control);
+
+ if (isp->out_q != NULL) {
+ // 1st junk anything lying around
+ isp_empty_out_q(isp);
+
+ mmal_queue_destroy(isp->out_q);
+ isp->out_q = NULL;
}
- sys->input->buffer_size = sys->input->buffer_size_recommended;
- vout_display_PlacePicture(&place, &vd->source, vd->cfg, false);
- display_region.hdr.id = MMAL_PARAMETER_DISPLAYREGION;
- display_region.hdr.size = sizeof(MMAL_DISPLAYREGION_T);
- display_region.fullscreen = MMAL_FALSE;
- display_region.src_rect.x = vd->fmt.i_x_offset;
- display_region.src_rect.y = vd->fmt.i_y_offset;
- display_region.src_rect.width = vd->fmt.i_visible_width;
- display_region.src_rect.height = vd->fmt.i_visible_height;
- display_region.dest_rect.x = place.x;
- display_region.dest_rect.y = place.y;
- display_region.dest_rect.width = place.width;
- display_region.dest_rect.height = place.height;
- display_region.layer = sys->layer;
- display_region.set = MMAL_DISPLAY_SET_FULLSCREEN | MMAL_DISPLAY_SET_SRC_RECT |
- MMAL_DISPLAY_SET_DEST_RECT | MMAL_DISPLAY_SET_LAYER;
- status = mmal_port_parameter_set(sys->input, &display_region.hdr);
- if (status != MMAL_SUCCESS) {
- msg_Err(vd, "Failed to set display region (status=%"PRIx32" %s)",
- status, mmal_status_to_string(status));
- ret = VLC_EGENERIC;
- goto out;
+ if (isp->out_pool != NULL) {
+ mmal_port_pool_destroy(isp->output, isp->out_pool);
+ isp->out_pool = NULL;
}
- for (i = 0; i < sys->i_planes; ++i) {
- sys->planes[i].i_lines = buffer_height;
- sys->planes[i].i_pitch = buffer_pitch;
- sys->planes[i].i_visible_lines = vd->fmt.i_visible_height;
- sys->planes[i].i_visible_pitch = vd->fmt.i_visible_width;
+ isp->input = NULL;
+ isp->output = NULL;
- if (i > 0) {
- sys->planes[i].i_lines /= 2;
- sys->planes[i].i_pitch /= 2;
- sys->planes[i].i_visible_lines /= 2;
- sys->planes[i].i_visible_pitch /= 2;
- }
+ mmal_component_release(isp->component);
+ isp->component = NULL;
+
+ return;
+}
+
+// Restuff into output rather than return to pool is we can
+static MMAL_BOOL_T isp_out_pool_cb(MMAL_POOL_T *pool, MMAL_BUFFER_HEADER_T *buffer, void *userdata)
+{
+ struct vout_isp_conf_s * const isp = userdata;
+ VLC_UNUSED(pool);
+ if (isp->output->is_enabled) {
+ mmal_buffer_header_reset(buffer);
+ if (mmal_port_send_buffer(isp->output, buffer) == MMAL_SUCCESS)
+ return MMAL_FALSE;
}
+ return MMAL_TRUE;
+}
- vlc_mutex_init(&sys->buffer_mutex);
- vlc_cond_init(&sys->buffer_cond);
- vlc_mutex_init(&sys->manage_mutex);
+static MMAL_STATUS_T isp_setup(vout_display_t * const vd, vout_display_sys_t * const vd_sys)
+{
+ struct vout_isp_conf_s * const isp = &vd_sys->isp;
+ MMAL_STATUS_T err;
- vd->pool = vd_pool;
- vd->prepare = vd_prepare;
- vd->display = vd_display;
- vd->control = vd_control;
- vd->manage = vd_manage;
+ if ((err = mmal_component_create(MMAL_COMPONENT_ISP_RESIZER, &isp->component)) != MMAL_SUCCESS) {
+ msg_Err(vd, "Cannot create ISP component");
+ return err;
+ }
+ isp->input = isp->component->input[0];
+ isp->output = isp->component->output[0];
- vc_tv_register_callback(tvservice_cb, vd);
+ isp->component->control->userdata = (void *)vd;
+ if ((err = mmal_port_enable(isp->component->control, isp_control_port_cb)) != MMAL_SUCCESS) {
+ msg_Err(vd, "Failed to enable ISP control port");
+ goto fail;
+ }
- if (query_resolution(vd, &sys->display_width, &sys->display_height) >= 0) {
- vout_display_SendEventDisplaySize(vd, sys->display_width, sys->display_height);
- } else {
- sys->display_width = vd->cfg->display.width;
- sys->display_height = vd->cfg->display.height;
+ isp->input->userdata = (void *)vd;
+ display_set_format(vd, isp->input->format, false);
+
+ if ((err = port_parameter_set_bool(isp->input, MMAL_PARAMETER_ZERO_COPY, true)) != MMAL_SUCCESS)
+ goto fail;
+
+ if ((err = mmal_port_format_commit(isp->input)) != MMAL_SUCCESS) {
+ msg_Err(vd, "Failed to set ISP input format");
+ goto fail;
+ }
+
+ isp->input->buffer_size = isp->input->buffer_size_recommended;
+ isp->input->buffer_num = 30;
+
+ if ((isp->in_pool = mmal_pool_create(isp->input->buffer_num, 0)) == NULL)
+ {
+ msg_Err(vd, "Failed to create input pool");
+ goto fail;
+ }
+
+ if ((isp->out_q = mmal_queue_create()) == NULL)
+ {
+ err = MMAL_ENOMEM;
+ goto fail;
+ }
+
+ display_set_format(vd, isp->output->format, true);
+
+ if ((err = port_parameter_set_bool(isp->output, MMAL_PARAMETER_ZERO_COPY, true)) != MMAL_SUCCESS)
+ goto fail;
+
+ if ((err = mmal_port_format_commit(isp->output)) != MMAL_SUCCESS) {
+ msg_Err(vd, "Failed to set ISP input format");
+ goto fail;
+ }
+
+ isp->output->buffer_size = isp->output->buffer_size_recommended;
+ isp->output->buffer_num = 2;
+ isp->output->userdata = (void *)vd;
+
+ if ((isp->out_pool = mmal_port_pool_create(isp->output, isp->output->buffer_num, isp->output->buffer_size)) == NULL)
+ {
+ msg_Err(vd, "Failed to make ISP port pool");
+ goto fail;
+ }
+
+ mmal_pool_callback_set(isp->out_pool, isp_out_pool_cb, isp);
+
+ if ((err = isp_prepare(vd, isp)) != MMAL_SUCCESS)
+ goto fail;
+
+ return MMAL_SUCCESS;
+
+fail:
+ isp_close(vd, vd_sys);
+ return err;
+}
+
+static MMAL_STATUS_T isp_check(vout_display_t * const vd, vout_display_sys_t * const vd_sys)
+{
+ struct vout_isp_conf_s *const isp = &vd_sys->isp;
+ const bool has_isp = (isp->component != NULL);
+ const bool wants_isp = want_isp(vd);
+
+ if (has_isp == wants_isp)
+ {
+ // All OK - do nothing
+ }
+ else if (has_isp)
+ {
+ // ISP active but we don't want it
+ isp_flush(isp);
+
+ // Check we have everything back and then kill it
+ if (mmal_queue_length(isp->out_pool->queue) == isp->output->buffer_num)
+ isp_close(vd, vd_sys);
+ }
+ else
+ {
+ // ISP closed but we want it
+ return isp_setup(vd, vd_sys);
}
- sys->dmx_handle = vc_dispmanx_display_open(0);
- vd->info.subpicture_chromas = subpicture_chromas;
+ return MMAL_SUCCESS;
+}
- vout_display_DeleteWindow(vd, NULL);
+/* TV service */
+static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1,
+ uint32_t param2);
+static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt);
+static int set_latency_target(vout_display_t *vd, bool enable);
+
+// Mmal
+static void maintain_phase_sync(vout_display_t *vd);
-out:
- if (ret != VLC_SUCCESS)
- Close(object);
+
+
+static void vd_input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf)
+{
+#if TRACE_ALL
+ vout_display_t * const vd = (vout_display_t *)port->userdata;
+ pic_ctx_mmal_t * ctx = buf->user_data;
+ msg_Dbg(vd, "<<< %s: cmd=%d, ctx=%p, buf=%p, flags=%#x, pts=%lld", __func__, buf->cmd, ctx, buf,
+ buf->flags, (long long)buf->pts);
+#else
+ VLC_UNUSED(port);
+#endif
+
+ mmal_buffer_header_release(buf);
+
+#if TRACE_ALL
+ msg_Dbg(vd, ">>> %s", __func__);
+#endif
+}
+
+static int query_resolution(vout_display_t *vd, const int display_id, unsigned *width, unsigned *height)
+{
+ TV_DISPLAY_STATE_T display_state = {0};
+ int ret = 0;
+
+ if (vc_tv_get_display_state_id(display_id, &display_state) == 0) {
+ msg_Dbg(vd, "State=%#x", display_state.state);
+ if (display_state.state & 0xFF) {
+ msg_Dbg(vd, "HDMI: %dx%d", display_state.display.hdmi.width, display_state.display.hdmi.height);
+ *width = display_state.display.hdmi.width;
+ *height = display_state.display.hdmi.height;
+ } else if (display_state.state & 0xFF00) {
+ msg_Dbg(vd, "SDTV: %dx%d", display_state.display.sdtv.width, display_state.display.sdtv.height);
+ *width = display_state.display.sdtv.width;
+ *height = display_state.display.sdtv.height;
+ } else {
+ msg_Warn(vd, "Invalid display state %"PRIx32, display_state.state);
+ ret = -1;
+ }
+ } else {
+ msg_Warn(vd, "Failed to query display resolution");
+ ret = -1;
+ }
return ret;
}
-static void Close(vlc_object_t *object)
+static inline MMAL_RECT_T
+place_to_mmal_rect(const vout_display_place_t place)
{
- vout_display_t *vd = (vout_display_t *)object;
- vout_display_sys_t *sys = vd->sys;
- char response[20]; /* answer is hvs_update_fields=%1d */
- unsigned i;
+ return (MMAL_RECT_T){
+ .x = place.x,
+ .y = place.y,
+ .width = place.width,
+ .height = place.height
+ };
+}
- vc_tv_unregister_callback_full(tvservice_cb, vd);
+static MMAL_RECT_T
+place_out(const vout_display_cfg_t * cfg,
+ const video_format_t * fmt,
+ const MMAL_RECT_T r)
+{
+ video_format_t tfmt;
+ vout_display_cfg_t tcfg;
+ vout_display_place_t place;
- if (sys->dmx_handle)
- close_dmx(vd);
+ // Fix SAR if unknown
+ if (fmt->i_sar_den == 0 || fmt->i_sar_num == 0) {
+ tfmt = *fmt;
+ tfmt.i_sar_den = 1;
+ tfmt.i_sar_num = 1;
+ fmt = &tfmt;
+ }
- if (sys->component && sys->component->control->is_enabled)
- mmal_port_disable(sys->component->control);
+ // Override what VLC thinks might be going on with display size
+ // if we know better
+ if (r.width != 0 && r.height != 0)
+ {
+ tcfg = *cfg;
+ tcfg.display.width = r.width;
+ tcfg.display.height = r.height;
+ cfg = &tcfg;
+ }
- if (sys->input && sys->input->is_enabled)
- mmal_port_disable(sys->input);
+ vout_display_PlacePicture(&place, fmt, cfg, false);
- if (sys->component && sys->component->is_enabled)
- mmal_component_disable(sys->component);
+ place.x += r.x;
+ place.y += r.y;
- if (sys->pool)
- mmal_port_pool_destroy(sys->input, sys->pool);
+ return place_to_mmal_rect(place);
+}
- if (sys->component)
- mmal_component_release(sys->component);
+static MMAL_RECT_T
+rect_transform(MMAL_RECT_T s, const MMAL_RECT_T c, const MMAL_DISPLAYTRANSFORM_T t)
+{
+ if (is_transform_transpose(t))
+ s = rect_transpose(s);
+ if (is_transform_hflip(t))
+ s = rect_hflip(s, c);
+ if (is_transform_vflip(t) != 0)
+ s = rect_vflip(s, c);
+ return s;
+}
- if (sys->picture_pool)
- picture_pool_Release(sys->picture_pool);
- else
- for (i = 0; i < sys->num_buffers; ++i)
- if (sys->pictures[i]) {
- mmal_buffer_header_release(sys->pictures[i]->p_sys->buffer);
- picture_Release(sys->pictures[i]);
- }
+static void
+place_dest_rect(vout_display_t * const vd,
+ const vout_display_cfg_t * const cfg,
+ const video_format_t * fmt)
+{
+ vout_display_sys_t * const sys = vd->sys;
+ sys->dest_rect = rect_transform(place_out(cfg, fmt, sys->win_rect),
+ sys->display_rect, sys->dest_transform);
+}
- vlc_mutex_destroy(&sys->buffer_mutex);
- vlc_cond_destroy(&sys->buffer_cond);
- vlc_mutex_destroy(&sys->manage_mutex);
+static void
+place_spu_rect(vout_display_t * const vd,
+ const vout_display_cfg_t * const cfg,
+ const video_format_t * fmt)
+{
+ vout_display_sys_t * const sys = vd->sys;
+ static const MMAL_RECT_T r0 = {0};
- if (sys->native_interlaced) {
- if (vc_gencmd(response, sizeof(response), "hvs_update_fields 0") < 0 ||
- response[18] != '0')
- msg_Warn(vd, "Could not reset hvs field mode");
+ sys->spu_rect = place_out(cfg, fmt, r0);
+ sys->spu_rect.x = 0;
+ sys->spu_rect.y = 0;
+
+ // Copy place override logic for spu pos from video_output.c
+ // This info doesn't appear to reside anywhere natively
+
+ if (fmt->i_width * fmt->i_height >= (unsigned int)(sys->spu_rect.width * sys->spu_rect.height)) {
+ sys->spu_rect.width = fmt->i_visible_width;
+ sys->spu_rect.height = fmt->i_visible_height;
}
- free(sys->pictures);
- free(sys);
+ if (ORIENT_IS_SWAP(fmt->orientation))
+ sys->spu_rect = rect_transpose(sys->spu_rect);
+}
- bcm_host_deinit();
+static void
+place_rects(vout_display_t * const vd,
+ const vout_display_cfg_t * const cfg,
+ const video_format_t * fmt)
+{
+ place_dest_rect(vd, cfg, fmt);
+ place_spu_rect(vd, cfg, fmt);
}
-static inline uint32_t align(uint32_t x, uint32_t y) {
- uint32_t mod = x % y;
- if (mod == 0)
- return x;
- else
- return x + y - mod;
+static int
+set_input_region(vout_display_t * const vd, const video_format_t * const fmt)
+{
+ const vout_display_sys_t * const sys = vd->sys;
+ MMAL_DISPLAYREGION_T display_region = {
+ .hdr = {
+ .id = MMAL_PARAMETER_DISPLAYREGION,
+ .size = sizeof(MMAL_DISPLAYREGION_T)
+ },
+ .display_num = sys->display_id,
+ .fullscreen = MMAL_FALSE,
+ .transform = sys->video_transform,
+ .dest_rect = sys->dest_rect,
+ .src_rect = display_src_rect(vd, fmt),
+ .noaspect = MMAL_TRUE,
+ .mode = MMAL_DISPLAY_MODE_FILL,
+ .layer = sys->layer,
+ .alpha = 0xff | (sys->transparent ? 0 : (1 << 29)),
+ .set =
+ MMAL_DISPLAY_SET_NUM |
+ MMAL_DISPLAY_SET_FULLSCREEN |
+ MMAL_DISPLAY_SET_TRANSFORM |
+ MMAL_DISPLAY_SET_DEST_RECT |
+ MMAL_DISPLAY_SET_SRC_RECT |
+ MMAL_DISPLAY_SET_NOASPECT |
+ MMAL_DISPLAY_SET_MODE |
+ MMAL_DISPLAY_SET_LAYER |
+ MMAL_DISPLAY_SET_ALPHA
+ };
+ MMAL_STATUS_T status = mmal_port_parameter_set(sys->input, &display_region.hdr);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(vd, "Failed to set display region (status=%"PRIx32" %s)",
+ status, mmal_status_to_string(status));
+ return -EINVAL;
+ }
+ return 0;
}
static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg,
const video_format_t *fmt)
{
- vout_display_sys_t *sys = vd->sys;
- vout_display_place_t place;
- MMAL_DISPLAYREGION_T display_region;
+ vout_display_sys_t * const sys = vd->sys;
MMAL_STATUS_T status;
if (!cfg && !fmt)
+ {
+ msg_Err(vd, "%s: Missing cfg & fmt", __func__);
return -EINVAL;
+ }
+
+ isp_check(vd, sys);
if (fmt) {
sys->input->format->es->video.par.num = fmt->i_sar_num;
@@ -412,30 +733,14 @@ static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg,
if (!cfg)
cfg = vd->cfg;
- vout_display_PlacePicture(&place, fmt, cfg, false);
+ sys->video_transform = combine_transform(
+ vlc_to_mmal_transform(fmt->orientation), sys->display_transform);
- display_region.hdr.id = MMAL_PARAMETER_DISPLAYREGION;
- display_region.hdr.size = sizeof(MMAL_DISPLAYREGION_T);
- display_region.fullscreen = MMAL_FALSE;
- display_region.src_rect.x = fmt->i_x_offset;
- display_region.src_rect.y = fmt->i_y_offset;
- display_region.src_rect.width = fmt->i_visible_width;
- display_region.src_rect.height = fmt->i_visible_height;
- display_region.dest_rect.x = place.x;
- display_region.dest_rect.y = place.y;
- display_region.dest_rect.width = place.width;
- display_region.dest_rect.height = place.height;
- display_region.layer = sys->layer;
- display_region.set = MMAL_DISPLAY_SET_FULLSCREEN | MMAL_DISPLAY_SET_SRC_RECT |
- MMAL_DISPLAY_SET_DEST_RECT | MMAL_DISPLAY_SET_LAYER;
- status = mmal_port_parameter_set(sys->input, &display_region.hdr);
- if (status != MMAL_SUCCESS) {
- msg_Err(vd, "Failed to set display region (status=%"PRIx32" %s)",
- status, mmal_status_to_string(status));
+ place_rects(vd, cfg, fmt);
+
+ if (set_input_region(vd, fmt) != 0)
return -EINVAL;
- }
- show_background(vd, var_InheritBool(vd, MMAL_BLANK_BACKGROUND_NAME));
sys->adjust_refresh_rate = var_InheritBool(vd, MMAL_ADJUST_REFRESHRATE_NAME);
sys->native_interlaced = var_InheritBool(vd, MMAL_NATIVE_INTERLACED);
if (sys->adjust_refresh_rate) {
@@ -446,204 +751,217 @@ static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg,
return 0;
}
-static picture_pool_t *vd_pool(vout_display_t *vd, unsigned count)
+static void kill_pool(vout_display_sys_t * const sys)
{
- vout_display_sys_t *sys = vd->sys;
- picture_resource_t picture_res;
- picture_pool_configuration_t picture_pool_cfg;
- video_format_t fmt = vd->fmt;
- MMAL_STATUS_T status;
- unsigned i;
-
- if (sys->picture_pool) {
- if (sys->num_buffers < count)
- msg_Warn(vd, "Picture pool with %u pictures requested, but we already have one with %u pictures",
- count, sys->num_buffers);
-
- goto out;
+ if (sys->pic_pool != NULL) {
+ picture_pool_Release(sys->pic_pool);
+ sys->pic_pool = NULL;
}
+}
- if (sys->opaque) {
- if (count <= NUM_ACTUAL_OPAQUE_BUFFERS)
- count = NUM_ACTUAL_OPAQUE_BUFFERS;
+// Actual picture pool for MMAL opaques is just a set of trivial containers
+static picture_pool_t *vd_pool(vout_display_t *vd, unsigned count)
+{
+ vout_display_sys_t * const sys = vd->sys;
- MMAL_PARAMETER_BOOLEAN_T zero_copy = {
- { MMAL_PARAMETER_ZERO_COPY, sizeof(MMAL_PARAMETER_BOOLEAN_T) },
- 1
- };
+ msg_Dbg(vd, "%s: fmt:%dx%d,sar:%d/%d; source:%dx%d", __func__,
+ vd->fmt.i_width, vd->fmt.i_height, vd->fmt.i_sar_num, vd->fmt.i_sar_den, vd->source.i_width, vd->source.i_height);
- status = mmal_port_parameter_set(sys->input, &zero_copy.hdr);
- if (status != MMAL_SUCCESS) {
- msg_Err(vd, "Failed to set zero copy on port %s (status=%"PRIx32" %s)",
- sys->input->name, status, mmal_status_to_string(status));
- goto out;
- }
+ if (sys->pic_pool == NULL) {
+ sys->pic_pool = picture_pool_NewFromFormat(&vd->fmt, count);
}
+ return sys->pic_pool;
+}
- if (count < sys->input->buffer_num_recommended)
- count = sys->input->buffer_num_recommended;
+static inline bool
+check_shape(vout_display_t * const vd, const picture_t * const p_pic)
+{
+ if (vd->fmt.i_width == p_pic->format.i_width &&
+ vd->fmt.i_height == p_pic->format.i_height)
+ return true;
+ return false;
+}
-#ifndef NDEBUG
- msg_Dbg(vd, "Creating picture pool with %u pictures", count);
+static void vd_display(vout_display_t *vd, picture_t *p_pic,
+ subpicture_t *subpicture)
+{
+ vout_display_sys_t * const sys = vd->sys;
+ MMAL_STATUS_T err;
+
+#if TRACE_ALL
+ {
+ char dbuf0[5];
+ msg_Dbg(vd, "<<< %s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d -> %dx%d@%d,%d", __func__,
+ str_fourcc(dbuf0, p_pic->format.i_chroma), p_pic->format.i_width, p_pic->format.i_height,
+ p_pic->format.i_x_offset, p_pic->format.i_y_offset,
+ p_pic->format.i_visible_width, p_pic->format.i_visible_height,
+ p_pic->format.i_sar_num, p_pic->format.i_sar_den,
+ sys->dest_rect.width, sys->dest_rect.height, sys->dest_rect.x, sys->dest_rect.y);
+ }
#endif
- sys->input->buffer_num = count;
- status = mmal_port_enable(sys->input, input_port_cb);
- if (status != MMAL_SUCCESS) {
- msg_Err(vd, "Failed to enable input port %s (status=%"PRIx32" %s)",
- sys->input->name, status, mmal_status_to_string(status));
- goto out;
+ // If we had subpics then we have attached them to the main pic in prepare
+ // so all we have to do here is delete the refs
+ if (subpicture != NULL) {
+ subpicture_Delete(subpicture);
}
- status = mmal_component_enable(sys->component);
- if (status != MMAL_SUCCESS) {
- msg_Err(vd, "Failed to enable component %s (status=%"PRIx32" %s)",
- sys->component->name, status, mmal_status_to_string(status));
- goto out;
- }
-
- sys->num_buffers = count;
- sys->pool = mmal_port_pool_create(sys->input, sys->num_buffers,
- sys->input->buffer_size);
- if (!sys->pool) {
- msg_Err(vd, "Failed to create MMAL pool for %u buffers of size %"PRIu32,
- count, sys->input->buffer_size);
- goto out;
- }
-
- memset(&picture_res, 0, sizeof(picture_resource_t));
- sys->pictures = calloc(sys->num_buffers, sizeof(picture_t *));
- for (i = 0; i < sys->num_buffers; ++i) {
- picture_res.p_sys = calloc(1, sizeof(picture_sys_t));
- picture_res.p_sys->owner = (vlc_object_t *)vd;
- picture_res.p_sys->buffer = mmal_queue_get(sys->pool->queue);
-
- sys->pictures[i] = picture_NewFromResource(&fmt, &picture_res);
- if (!sys->pictures[i]) {
- msg_Err(vd, "Failed to create picture");
- free(picture_res.p_sys);
- goto out;
- }
-
- sys->pictures[i]->i_planes = sys->i_planes;
- memcpy(sys->pictures[i]->p, sys->planes, sys->i_planes * sizeof(plane_t));
+ if (!check_shape(vd, p_pic))
+ {
+ msg_Err(vd, "Pic/fmt shape mismatch");
+ goto fail;
}
- memset(&picture_pool_cfg, 0, sizeof(picture_pool_configuration_t));
- picture_pool_cfg.picture_count = sys->num_buffers;
- picture_pool_cfg.picture = sys->pictures;
- picture_pool_cfg.lock = mmal_picture_lock;
-
- sys->picture_pool = picture_pool_NewExtended(&picture_pool_cfg);
- if (!sys->picture_pool) {
- msg_Err(vd, "Failed to create picture pool");
- goto out;
+ if (!sys->input->is_enabled &&
+ (err = mmal_port_enable(sys->input, vd_input_port_cb)) != MMAL_SUCCESS)
+ {
+ msg_Err(vd, "Input port enable failed");
+ goto fail;
}
+ // Stuff into input
+ // We assume the BH is already set up with values reflecting pic date etc.
+ if (sys->copy_buf != NULL) {
+ MMAL_BUFFER_HEADER_T *const buf = sys->copy_buf;
+ sys->copy_buf = NULL;
+#if TRACE_ALL
+ msg_Dbg(vd, "--- %s: Copy stuff", __func__);
+#endif
+ if (mmal_port_send_buffer(sys->input, buf) != MMAL_SUCCESS)
+ {
+ mmal_buffer_header_release(buf);
+ msg_Err(vd, "Send copy buffer to render input failed");
+ goto fail;
+ }
+ }
+ else if (sys->isp.pending) {
+ MMAL_BUFFER_HEADER_T *const buf = mmal_queue_wait(sys->isp.out_q);
+ sys->isp.pending = false;
+#if TRACE_ALL
+ msg_Dbg(vd, "--- %s: ISP stuff", __func__);
+#endif
+ if (mmal_port_send_buffer(sys->input, buf) != MMAL_SUCCESS)
+ {
+ mmal_buffer_header_release(buf);
+ msg_Err(vd, "Send ISP buffer to render input failed");
+ goto fail;
+ }
+ }
+ else
+ {
+ MMAL_BUFFER_HEADER_T *const pic_buf = hw_mmal_pic_buf_replicated(p_pic, sys->pool);
+ if (pic_buf == NULL)
+ {
+ msg_Err(vd, "Replicated buffer get fail");
+ goto fail;
+ }
-out:
- return sys->picture_pool;
-}
-
-static void vd_prepare(vout_display_t *vd, picture_t *picture,
- subpicture_t *subpicture)
-{
- vout_display_sys_t *sys = vd->sys;
- picture_sys_t *pic_sys = picture->p_sys;
- if (!sys->adjust_refresh_rate || pic_sys->displayed)
- return;
+ // If dimensions have chnaged then fix that
+ if (hw_mmal_vlc_pic_to_mmal_fmt_update(sys->input->format, p_pic))
+ {
+ msg_Dbg(vd, "Reset port format");
- /* Apply the required phase_offset to the picture, so that vd_display()
- * will be called at the corrected time from the core */
- picture->date += sys->phase_offset;
-}
-
-static void vd_display(vout_display_t *vd, picture_t *picture,
- subpicture_t *subpicture)
-{
- vout_display_sys_t *sys = vd->sys;
- picture_sys_t *pic_sys = picture->p_sys;
- MMAL_BUFFER_HEADER_T *buffer = pic_sys->buffer;
- MMAL_STATUS_T status;
+ // HVS can deal with on-line dimension changes
+ if (mmal_port_format_commit(sys->input) != MMAL_SUCCESS)
+ msg_Warn(vd, "Input format commit failed");
+ }
- if (picture->format.i_frame_rate != sys->i_frame_rate ||
- picture->format.i_frame_rate_base != sys->i_frame_rate_base ||
- picture->b_progressive != sys->b_progressive ||
- picture->b_top_field_first != sys->b_top_field_first) {
- sys->b_top_field_first = picture->b_top_field_first;
- sys->b_progressive = picture->b_progressive;
- sys->i_frame_rate = picture->format.i_frame_rate;
- sys->i_frame_rate_base = picture->format.i_frame_rate_base;
- configure_display(vd, NULL, &picture->format);
+ if ((err = mmal_port_send_buffer(sys->input, pic_buf)) != MMAL_SUCCESS)
+ {
+ mmal_buffer_header_release(pic_buf);
+ msg_Err(vd, "Send buffer to input failed");
+ goto fail;
+ }
}
- if (!pic_sys->displayed || !sys->opaque) {
- buffer->cmd = 0;
- buffer->length = sys->input->buffer_size;
- buffer->user_data = picture;
-
- status = mmal_port_send_buffer(sys->input, buffer);
- if (status == MMAL_SUCCESS)
- atomic_fetch_add(&sys->buffers_in_transit, 1);
-
- if (status != MMAL_SUCCESS) {
- msg_Err(vd, "Failed to send buffer to input port. Frame dropped");
- picture_Release(picture);
+ {
+ unsigned int sub_no = 0;
+ MMAL_BUFFER_HEADER_T **psub_bufs2 = sys->subpic_bufs;
+ const bool is_mmal_pic = hw_mmal_pic_is_mmal(p_pic);
+
+ for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) {
+ int rv;
+ MMAL_BUFFER_HEADER_T * const sub_buf = !is_mmal_pic ? NULL :
+ hw_mmal_pic_sub_buf_get(p_pic, sub_no);
+
+ if ((rv = hw_mmal_subpic_update(VLC_OBJECT(vd),
+ sub_buf != NULL ? sub_buf : *psub_bufs2++,
+ &sys->subs[sub_no].sub,
+ &p_pic->format,
+ &sys->dest_rect,
+ sys->display_transform,
+ p_pic->date)) == 0)
+ break;
+ else if (rv < 0)
+ goto fail;
}
-
- pic_sys->displayed = true;
- } else {
- picture_Release(picture);
}
- display_subpicture(vd, subpicture);
+fail:
+ for (unsigned int i = 0; i != SUBS_MAX && sys->subpic_bufs[i] != NULL; ++i) {
+ mmal_buffer_header_release(sys->subpic_bufs[i]);
+ sys->subpic_bufs[i] = NULL;
+ }
- if (subpicture)
- subpicture_Delete(subpicture);
+ picture_Release(p_pic);
if (sys->next_phase_check == 0 && sys->adjust_refresh_rate)
maintain_phase_sync(vd);
sys->next_phase_check = (sys->next_phase_check + 1) % PHASE_CHECK_INTERVAL;
-
- if (sys->opaque) {
- vlc_mutex_lock(&sys->buffer_mutex);
- while (atomic_load(&sys->buffers_in_transit) >= MAX_BUFFERS_IN_TRANSIT)
- vlc_cond_wait(&sys->buffer_cond, &sys->buffer_mutex);
- vlc_mutex_unlock(&sys->buffer_mutex);
- }
}
static int vd_control(vout_display_t *vd, int query, va_list args)
{
- vout_display_sys_t *sys = vd->sys;
- vout_display_cfg_t cfg;
- const vout_display_cfg_t *tmp_cfg;
+ vout_display_sys_t * const sys = vd->sys;
int ret = VLC_EGENERIC;
+ VLC_UNUSED(args);
switch (query) {
- case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
- tmp_cfg = va_arg(args, const vout_display_cfg_t *);
- if (tmp_cfg->display.width == sys->display_width &&
- tmp_cfg->display.height == sys->display_height) {
- cfg = *vd->cfg;
- cfg.display.width = sys->display_width;
- cfg.display.height = sys->display_height;
- if (configure_display(vd, &cfg, NULL) >= 0)
- ret = VLC_SUCCESS;
- }
- break;
-
case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
- if (configure_display(vd, NULL, &vd->source) >= 0)
+ if (configure_display(vd, vd->cfg, &vd->source) >= 0)
ret = VLC_SUCCESS;
break;
- case VOUT_DISPLAY_RESET_PICTURES:
- vlc_assert_unreachable();
case VOUT_DISPLAY_CHANGE_ZOOM:
- msg_Warn(vd, "Unsupported control query %d", query);
+ case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
+ case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
+ {
+ const vout_display_cfg_t * const cfg = va_arg(args, const vout_display_cfg_t *);
+
+ if (configure_display(vd, cfg, &vd->source) >= 0)
+ ret = VLC_SUCCESS;
+ break;
+ }
+
+ case VOUT_DISPLAY_RESET_PICTURES:
+ msg_Warn(vd, "Reset Pictures");
+ kill_pool(sys);
+ vd->fmt = vd->source; // Take (nearly) whatever source wants to give us
+ vd->fmt.i_chroma = req_chroma(vd); // Adjust chroma to something we can actaully deal with
+ ret = VLC_SUCCESS;
+ break;
+
+ case VOUT_DISPLAY_CHANGE_MMAL_HIDE:
+ {
+ MMAL_STATUS_T err;
+ unsigned int i;
+
+ msg_Dbg(vd, "Hide display");
+
+ for (i = 0; i != SUBS_MAX; ++i)
+ hw_mmal_subpic_flush(VLC_OBJECT(vd), &sys->subs[i].sub);
+
+ if (sys->input->is_enabled &&
+ (err = mmal_port_disable(sys->input)) != MMAL_SUCCESS)
+ {
+ msg_Err(vd, "Unable to disable port: err=%d", err);
+ break;
+ }
+ sys->force_config = true;
+ ret = VLC_SUCCESS;
break;
+ }
default:
msg_Warn(vd, "Unknown control query %d", query);
@@ -653,30 +971,198 @@ static int vd_control(vout_display_t *vd, int query, va_list args)
return ret;
}
+static void set_display_windows(vout_display_t *const vd, vout_display_sys_t *const sys)
+{
+ unsigned int width, height;
+ if (query_resolution(vd, sys->display_id, &width, &height) < 0) {
+ width = vd->cfg->display.width;
+ height = vd->cfg->display.height;
+ }
+ sys->display_rect = (MMAL_RECT_T){0, 0, width, height};
+
+ sys->win_rect = (sys->req_win.width != 0) ?
+ sys->req_win :
+ is_transform_transpose(sys->display_transform) ?
+ rect_transpose(sys->display_rect) : sys->display_rect;
+}
+
static void vd_manage(vout_display_t *vd)
{
- vout_display_sys_t *sys = vd->sys;
- unsigned width, height;
+ vout_display_sys_t *const sys = vd->sys;
vlc_mutex_lock(&sys->manage_mutex);
if (sys->need_configure_display) {
- close_dmx(vd);
- sys->dmx_handle = vc_dispmanx_display_open(0);
+ sys->need_configure_display = false;
+ set_display_windows(vd, sys);
+ }
+
+ vlc_mutex_unlock(&sys->manage_mutex);
+}
- if (query_resolution(vd, &width, &height) >= 0) {
- sys->display_width = width;
- sys->display_height = height;
- vout_display_SendEventDisplaySize(vd, width, height);
+
+static int attach_subpics(vout_display_t * const vd, vout_display_sys_t * const sys,
+ subpicture_t * const subpicture)
+{
+ unsigned int n = 0;
+
+ if (sys->vzc == NULL) {
+ if ((sys->vzc = hw_mmal_vzc_pool_new()) == NULL)
+ {
+ msg_Err(vd, "Failed to allocate VZC");
+ return VLC_ENOMEM;
}
+ }
- sys->need_configure_display = false;
+ // Attempt to import the subpics
+ for (subpicture_t * spic = subpicture; spic != NULL; spic = spic->p_next)
+ {
+ for (subpicture_region_t *sreg = spic->p_region; sreg != NULL; sreg = sreg->p_next) {
+ picture_t *const src = sreg->p_picture;
+
+#if TRACE_ALL
+ char dbuf0[5];
+ msg_Dbg(vd, " [%p:%p] Pos=%d,%d max=%dx%d, src=%dx%d/%dx%d o:%d, spu=%d,%d:%dx%d, vd->fmt=%dx%d/%dx%d, vd->source=%dx%d/%dx%d, cfg=%dx%d, zoom=%d/%d, Alpha=%d, Fmt=%s", src, src->p[0].p_pixels,
+ sreg->i_x, sreg->i_y,
+ sreg->i_max_width, sreg->i_max_height,
+ src->format.i_visible_width, src->format.i_visible_height,
+ src->format.i_width, src->format.i_height,
+ src->format.orientation,
+ sys->spu_rect.x, sys->spu_rect.y, sys->spu_rect.width, sys->spu_rect.height,
+ vd->fmt.i_visible_width, vd->fmt.i_visible_height,
+ vd->fmt.i_width, vd->fmt.i_height,
+ vd->source.i_visible_width, vd->source.i_visible_height,
+ vd->source.i_width, vd->source.i_height,
+ vd->cfg->display.width, vd->cfg->display.height,
+ vd->cfg->zoom.num, vd->cfg->zoom.den,
+ sreg->i_alpha,
+ str_fourcc(dbuf0, src->format.i_chroma));
+#endif
+
+ // At this point I think the subtitles are being placed in the
+ // coord space of the placed rectangle in the cfg display space
+ if ((sys->subpic_bufs[n] = hw_mmal_vzc_buf_from_pic(sys->vzc,
+ src,
+ &sreg->fmt,
+ (MMAL_RECT_T){.width = sys->spu_rect.width, .height=sys->spu_rect.height},
+ sreg->i_x, sreg->i_y,
+ sreg->i_alpha,
+ n == 0)) == NULL)
+ {
+ msg_Err(vd, "Failed to allocate vzc buffer for subpic");
+ return VLC_ENOMEM;
+ }
+
+ if (++n == SUBS_MAX)
+ return VLC_SUCCESS;
+ }
}
+ return VLC_SUCCESS;
+}
+
+
+static void vd_prepare(vout_display_t *vd, picture_t *p_pic,
+#if VLC_VER_3
+ subpicture_t *subpicture
+#else
+ subpicture_t *subpicture, vlc_tick_t date
+#endif
+ )
+{
+ MMAL_STATUS_T err;
+ vout_display_sys_t * const sys = vd->sys;
+
+ vd_manage(vd);
+
+ if (!check_shape(vd, p_pic))
+ return;
+
+ if (sys->force_config ||
+ p_pic->format.i_frame_rate != sys->i_frame_rate ||
+ p_pic->format.i_frame_rate_base != sys->i_frame_rate_base ||
+ p_pic->b_progressive != sys->b_progressive ||
+ p_pic->b_top_field_first != sys->b_top_field_first)
+ {
+ sys->force_config = false;
+ sys->b_top_field_first = p_pic->b_top_field_first;
+ sys->b_progressive = p_pic->b_progressive;
+ sys->i_frame_rate = p_pic->format.i_frame_rate;
+ sys->i_frame_rate_base = p_pic->format.i_frame_rate_base;
+ configure_display(vd, NULL, &vd->source);
+ }
+
+ // Subpics can either turn up attached to the main pic or in the
+ // subpic list here - if they turn up here then process into temp
+ // buffers
+ if (subpicture != NULL) {
+ attach_subpics(vd, sys, subpicture);
+ }
+
+ // *****
+ if (want_copy(vd)) {
+ if (sys->copy_buf != NULL) {
+ msg_Err(vd, "Copy buf not NULL");
+ mmal_buffer_header_release(sys->copy_buf);
+ sys->copy_buf = NULL;
+ }
+
+ MMAL_BUFFER_HEADER_T * const buf = mmal_queue_wait(sys->copy_pool->queue);
+ // Copy 2d
+ hw_mmal_copy_pic_to_buf(buf->data, &buf->length, sys->input->format, p_pic);
+ buf->flags = MMAL_BUFFER_HEADER_FLAG_FRAME_END;
+
+ sys->copy_buf = buf;
+ }
+
+ if (isp_check(vd, sys) != MMAL_SUCCESS) {
+ return;
+ }
+
+ if (want_isp(vd))
+ {
+ struct vout_isp_conf_s * const isp = &sys->isp;
+ MMAL_BUFFER_HEADER_T * buf;
+
+ // This should be empty - make it so if it isn't
+ isp_empty_out_q(isp);
+ isp->pending = false;
+
+ // Stuff output
+ if (isp_prepare(vd, isp) != MMAL_SUCCESS)
+ return;
+
+ if ((buf = hw_mmal_pic_buf_replicated(p_pic, isp->in_pool)) == NULL)
+ {
+ msg_Err(vd, "Pic has no attached buffer");
+ return;
+ }
+
+ if ((err = mmal_port_send_buffer(isp->input, buf)) != MMAL_SUCCESS)
+ {
+ msg_Err(vd, "Send buffer to input failed");
+ mmal_buffer_header_release(buf);
+ return;
+ }
+
+ isp->pending = true;
+ }
+
+#if 0
+ VLC_UNUSED(date);
+ vout_display_sys_t *sys = vd->sys;
+ picture_sys_t *pic_sys = picture->p_sys;
+
+ if (!sys->adjust_refresh_rate || pic_sys->displayed)
+ return;
- vlc_mutex_unlock(&sys->manage_mutex);
+ /* Apply the required phase_offset to the picture, so that vd_display()
+ * will be called at the corrected time from the core */
+ picture->date += sys->phase_offset;
+#endif
}
-static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+
+static void vd_control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
{
vout_display_t *vd = (vout_display_t *)port->userdata;
MMAL_STATUS_T status;
@@ -689,45 +1175,6 @@ static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
mmal_buffer_header_release(buffer);
}
-static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
-{
- vout_display_t *vd = (vout_display_t *)port->userdata;
- vout_display_sys_t *sys = vd->sys;
- picture_t *picture = (picture_t *)buffer->user_data;
-
- if (picture)
- picture_Release(picture);
-
- vlc_mutex_lock(&sys->buffer_mutex);
- atomic_fetch_sub(&sys->buffers_in_transit, 1);
- vlc_cond_signal(&sys->buffer_cond);
- vlc_mutex_unlock(&sys->buffer_mutex);
-}
-
-static int query_resolution(vout_display_t *vd, unsigned *width, unsigned *height)
-{
- TV_DISPLAY_STATE_T display_state;
- int ret = 0;
-
- if (vc_tv_get_display_state(&display_state) == 0) {
- if (display_state.state & 0xFF) {
- *width = display_state.display.hdmi.width;
- *height = display_state.display.hdmi.height;
- } else if (display_state.state & 0xFF00) {
- *width = display_state.display.sdtv.width;
- *height = display_state.display.sdtv.height;
- } else {
- msg_Warn(vd, "Invalid display state %"PRIx32, display_state.state);
- ret = -1;
- }
- } else {
- msg_Warn(vd, "Failed to query display resolution");
- ret = -1;
- }
-
- return ret;
-}
-
static void tvservice_cb(void *callback_data, uint32_t reason, uint32_t param1, uint32_t param2)
{
VLC_UNUSED(reason);
@@ -777,12 +1224,12 @@ static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt)
int num_modes;
double frame_rate = (double)fmt->i_frame_rate / fmt->i_frame_rate_base;
int best_id = -1;
- double best_score, score;
+ double best_score = 0.0, score;
int i;
- vc_tv_get_display_state(&display_state);
+ vc_tv_get_display_state_id(sys->display_id, &display_state);
if(display_state.display.hdmi.mode != HDMI_MODE_OFF) {
- num_modes = vc_tv_hdmi_get_supported_modes_new(display_state.display.hdmi.group,
+ num_modes = vc_tv_hdmi_get_supported_modes_new_id(sys->display_id, display_state.display.hdmi.group,
supported_modes, VC_TV_MAX_MODE_IDS, NULL, NULL);
for (i = 0; i < num_modes; ++i) {
@@ -810,7 +1257,7 @@ static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt)
if((best_id >= 0) && (display_state.display.hdmi.mode != supported_modes[best_id].code)) {
msg_Info(vd, "Setting HDMI refresh rate to %"PRIu32,
supported_modes[best_id].frame_rate);
- vc_tv_hdmi_power_on_explicit_new(HDMI_MODE_HDMI,
+ vc_tv_hdmi_power_on_explicit_new_id(sys->display_id, HDMI_MODE_HDMI,
supported_modes[best_id].group,
supported_modes[best_id].code);
}
@@ -828,148 +1275,12 @@ static void adjust_refresh_rate(vout_display_t *vd, const video_format_t *fmt)
}
}
-static void display_subpicture(vout_display_t *vd, subpicture_t *subpicture)
-{
- vout_display_sys_t *sys = vd->sys;
- struct dmx_region_t **dmx_region = &sys->dmx_region;
- struct dmx_region_t *unused_dmx_region;
- DISPMANX_UPDATE_HANDLE_T update = 0;
- picture_t *picture;
- video_format_t *fmt;
- struct dmx_region_t *dmx_region_next;
-
- if(subpicture) {
- subpicture_region_t *region = subpicture->p_region;
- while(region) {
- picture = region->p_picture;
- fmt = &region->fmt;
-
- if(!*dmx_region) {
- if(!update)
- update = vc_dispmanx_update_start(10);
- *dmx_region = dmx_region_new(vd, update, region);
- } else if(((*dmx_region)->bmp_rect.width != (int32_t)fmt->i_visible_width) ||
- ((*dmx_region)->bmp_rect.height != (int32_t)fmt->i_visible_height) ||
- ((*dmx_region)->pos_x != region->i_x) ||
- ((*dmx_region)->pos_y != region->i_y) ||
- ((*dmx_region)->alpha.opacity != (uint32_t)region->i_alpha)) {
- dmx_region_next = (*dmx_region)->next;
- if(!update)
- update = vc_dispmanx_update_start(10);
- dmx_region_delete(*dmx_region, update);
- *dmx_region = dmx_region_new(vd, update, region);
- (*dmx_region)->next = dmx_region_next;
- } else if((*dmx_region)->picture != picture) {
- if(!update)
- update = vc_dispmanx_update_start(10);
- dmx_region_update(*dmx_region, update, picture);
- }
-
- dmx_region = &(*dmx_region)->next;
- region = region->p_next;
- }
- }
-
- /* Remove remaining regions */
- unused_dmx_region = *dmx_region;
- while(unused_dmx_region) {
- dmx_region_next = unused_dmx_region->next;
- if(!update)
- update = vc_dispmanx_update_start(10);
- dmx_region_delete(unused_dmx_region, update);
- unused_dmx_region = dmx_region_next;
- }
- *dmx_region = NULL;
-
- if(update)
- vc_dispmanx_update_submit_sync(update);
-}
-
-static void close_dmx(vout_display_t *vd)
-{
- vout_display_sys_t *sys = vd->sys;
- DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(10);
- struct dmx_region_t *dmx_region = sys->dmx_region;
- struct dmx_region_t *dmx_region_next;
-
- while(dmx_region) {
- dmx_region_next = dmx_region->next;
- dmx_region_delete(dmx_region, update);
- dmx_region = dmx_region_next;
- }
-
- vc_dispmanx_update_submit_sync(update);
- sys->dmx_region = NULL;
-
- show_background(vd, false);
-
- vc_dispmanx_display_close(sys->dmx_handle);
- sys->dmx_handle = DISPMANX_NO_HANDLE;
-}
-
-static struct dmx_region_t *dmx_region_new(vout_display_t *vd,
- DISPMANX_UPDATE_HANDLE_T update, subpicture_region_t *region)
-{
- vout_display_sys_t *sys = vd->sys;
- video_format_t *fmt = &region->fmt;
- struct dmx_region_t *dmx_region = malloc(sizeof(struct dmx_region_t));
- uint32_t image_handle;
-
- dmx_region->pos_x = region->i_x;
- dmx_region->pos_y = region->i_y;
-
- vc_dispmanx_rect_set(&dmx_region->bmp_rect, 0, 0, fmt->i_visible_width,
- fmt->i_visible_height);
- vc_dispmanx_rect_set(&dmx_region->src_rect, 0, 0, fmt->i_visible_width << 16,
- fmt->i_visible_height << 16);
- vc_dispmanx_rect_set(&dmx_region->dst_rect, region->i_x, region->i_y,
- fmt->i_visible_width, fmt->i_visible_height);
-
- dmx_region->resource = vc_dispmanx_resource_create(VC_IMAGE_RGBA32,
- dmx_region->bmp_rect.width | (region->p_picture->p[0].i_pitch << 16),
- dmx_region->bmp_rect.height | (dmx_region->bmp_rect.height << 16),
- &image_handle);
- vc_dispmanx_resource_write_data(dmx_region->resource, VC_IMAGE_RGBA32,
- region->p_picture->p[0].i_pitch,
- region->p_picture->p[0].p_pixels, &dmx_region->bmp_rect);
-
- dmx_region->alpha.flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE | DISPMANX_FLAGS_ALPHA_MIX;
- dmx_region->alpha.opacity = region->i_alpha;
- dmx_region->alpha.mask = DISPMANX_NO_HANDLE;
- dmx_region->element = vc_dispmanx_element_add(update, sys->dmx_handle,
- sys->layer + 1, &dmx_region->dst_rect, dmx_region->resource,
- &dmx_region->src_rect, DISPMANX_PROTECTION_NONE,
- &dmx_region->alpha, NULL, VC_IMAGE_ROT0);
-
- dmx_region->next = NULL;
- dmx_region->picture = region->p_picture;
-
- return dmx_region;
-}
-
-static void dmx_region_update(struct dmx_region_t *dmx_region,
- DISPMANX_UPDATE_HANDLE_T update, picture_t *picture)
-{
- vc_dispmanx_resource_write_data(dmx_region->resource, VC_IMAGE_RGBA32,
- picture->p[0].i_pitch, picture->p[0].p_pixels, &dmx_region->bmp_rect);
- vc_dispmanx_element_change_source(update, dmx_region->element, dmx_region->resource);
- dmx_region->picture = picture;
-}
-
-static void dmx_region_delete(struct dmx_region_t *dmx_region,
- DISPMANX_UPDATE_HANDLE_T update)
-{
- vc_dispmanx_element_remove(update, dmx_region->element);
- vc_dispmanx_resource_delete(dmx_region->resource);
- free(dmx_region);
-}
-
static void maintain_phase_sync(vout_display_t *vd)
{
MMAL_PARAMETER_VIDEO_RENDER_STATS_T render_stats = {
.hdr = { MMAL_PARAMETER_VIDEO_RENDER_STATS, sizeof(render_stats) },
};
- int32_t frame_duration = 1000000 /
+ int32_t frame_duration = CLOCK_FREQ /
((double)vd->sys->i_frame_rate /
vd->sys->i_frame_rate_base);
vout_display_sys_t *sys = vd->sys;
@@ -1012,32 +1323,441 @@ static void maintain_phase_sync(vout_display_t *vd)
}
}
-static void show_background(vout_display_t *vd, bool enable)
+static void CloseMmalVout(vlc_object_t *object)
{
- vout_display_sys_t *sys = vd->sys;
- uint32_t image_ptr, color = 0xFF000000;
- VC_RECT_T dst_rect, src_rect;
- DISPMANX_UPDATE_HANDLE_T update;
-
- if (enable && !sys->bkg_element) {
- sys->bkg_resource = vc_dispmanx_resource_create(VC_IMAGE_RGBA32, 1, 1,
- &image_ptr);
- vc_dispmanx_rect_set(&dst_rect, 0, 0, 1, 1);
- vc_dispmanx_resource_write_data(sys->bkg_resource, VC_IMAGE_RGBA32,
- sizeof(color), &color, &dst_rect);
- vc_dispmanx_rect_set(&src_rect, 0, 0, 1 << 16, 1 << 16);
- vc_dispmanx_rect_set(&dst_rect, 0, 0, 0, 0);
- update = vc_dispmanx_update_start(0);
- sys->bkg_element = vc_dispmanx_element_add(update, sys->dmx_handle,
- sys->layer - 1, &dst_rect, sys->bkg_resource, &src_rect,
- DISPMANX_PROTECTION_NONE, NULL, NULL, VC_IMAGE_ROT0);
- vc_dispmanx_update_submit_sync(update);
- } else if (!enable && sys->bkg_element) {
- update = vc_dispmanx_update_start(0);
- vc_dispmanx_element_remove(update, sys->bkg_element);
- vc_dispmanx_resource_delete(sys->bkg_resource);
- vc_dispmanx_update_submit_sync(update);
- sys->bkg_element = DISPMANX_NO_HANDLE;
- sys->bkg_resource = DISPMANX_NO_HANDLE;
+ vout_display_t * const vd = (vout_display_t *)object;
+ vout_display_sys_t * const sys = vd->sys;
+ char response[20]; /* answer is hvs_update_fields=%1d */
+
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s", __func__);
+#endif
+
+ kill_pool(sys);
+
+ vc_tv_unregister_callback_full(tvservice_cb, vd);
+
+ // Shouldn't be anything here - but just in case
+ for (unsigned int i = 0; i != SUBS_MAX; ++i)
+ if (sys->subpic_bufs[i] != NULL)
+ mmal_buffer_header_release(sys->subpic_bufs[i]);
+
+ for (unsigned int i = 0; i != SUBS_MAX; ++i) {
+ vout_subpic_t * const sub = sys->subs + i;
+ if (sub->component != NULL) {
+ hw_mmal_subpic_close(VLC_OBJECT(vd), &sub->sub);
+ if (sub->component->control->is_enabled)
+ mmal_port_disable(sub->component->control);
+ if (sub->component->is_enabled)
+ mmal_component_disable(sub->component);
+ mmal_component_release(sub->component);
+ sub->component = NULL;
+ }
+ }
+
+ if (sys->input && sys->input->is_enabled)
+ mmal_port_disable(sys->input);
+
+ if (sys->component && sys->component->control->is_enabled)
+ mmal_port_disable(sys->component->control);
+
+ if (sys->copy_buf != NULL)
+ mmal_buffer_header_release(sys->copy_buf);
+
+ if (sys->input != NULL && sys->copy_pool != NULL)
+ mmal_port_pool_destroy(sys->input, sys->copy_pool);
+
+ if (sys->component && sys->component->is_enabled)
+ mmal_component_disable(sys->component);
+
+ if (sys->pool)
+ mmal_pool_destroy(sys->pool);
+
+ if (sys->component)
+ mmal_component_release(sys->component);
+
+ isp_close(vd, sys);
+
+ hw_mmal_vzc_pool_release(sys->vzc);
+
+ vlc_mutex_destroy(&sys->manage_mutex);
+
+ if (sys->native_interlaced) {
+ if (vc_gencmd(response, sizeof(response), "hvs_update_fields 0") < 0 ||
+ response[18] != '0')
+ msg_Warn(vd, "Could not reset hvs field mode");
+ }
+
+ cma_vcsm_exit(sys->init_type);;
+
+ free(sys);
+
+#if TRACE_ALL
+ msg_Dbg(vd, ">>> %s", __func__);
+#endif
+}
+
+
+static const struct {
+ const char * name;
+ int num;
+} display_name_to_num[] = {
+ {"auto", -1},
+ {"hdmi-1", DISPMANX_ID_HDMI0},
+ {"hdmi-2", DISPMANX_ID_HDMI1},
+ {NULL, -2}
+};
+
+static const struct {
+ const char * name;
+ int transform_num;
+} transform_name_to_num[] = {
+ {"auto", -1},
+ {"0", MMAL_DISPLAY_ROT0},
+ {"hflip", MMAL_DISPLAY_MIRROR_ROT0},
+ {"vflip", MMAL_DISPLAY_MIRROR_ROT180},
+ {"180", MMAL_DISPLAY_ROT180},
+ {"transpose", MMAL_DISPLAY_MIRROR_ROT90},
+ {"270", MMAL_DISPLAY_ROT270},
+ {"90", MMAL_DISPLAY_ROT90},
+ {"antitranspose", MMAL_DISPLAY_MIRROR_ROT270},
+ {NULL, -2}
+};
+
+static int find_display_num(const char * const name)
+{
+ unsigned int i;
+ for (i = 0; display_name_to_num[i].name != NULL && strcasecmp(display_name_to_num[i].name, name) != 0; ++i)
+ /* Loop */;
+ return display_name_to_num[i].num;
+}
+
+static int find_transform_num(const char * const name)
+{
+ unsigned int i;
+ for (i = 0; transform_name_to_num[i].name != NULL && strcasecmp(transform_name_to_num[i].name, name) != 0; ++i)
+ /* Loop */;
+ return transform_name_to_num[i].transform_num;
+}
+
+#if HAVE_X11_XLIB_H
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrandr.h>
+static MMAL_DISPLAYTRANSFORM_T get_xrandr_rotation(vout_display_t * const vd)
+{
+ Display * const x = XOpenDisplay(NULL);
+ Rotation cur_rot = 0;
+ MMAL_DISPLAYTRANSFORM_T trans;
+
+ if (x == NULL)
+ return MMAL_DISPLAY_ROT0;
+
+ XRRRotations(x, 0, &cur_rot);
+ XCloseDisplay(x);
+
+ // Convert to MMAL
+ // xrandr seems to rotate the other way to mmal
+
+ switch (cur_rot)
+ {
+ case 0:
+ case RR_Rotate_0:
+ trans = MMAL_DISPLAY_ROT0;
+ break;
+ case RR_Rotate_90:
+ trans = MMAL_DISPLAY_ROT270;
+ break;
+ case RR_Rotate_180:
+ trans = MMAL_DISPLAY_ROT180;
+ break;
+ case RR_Rotate_270:
+ trans = MMAL_DISPLAY_ROT90;
+ break;
+ case RR_Reflect_X:
+ trans = MMAL_DISPLAY_MIRROR_ROT0;
+ break;
+ case RR_Reflect_Y:
+ trans = MMAL_DISPLAY_MIRROR_ROT180;
+ break;
+ default:
+ msg_Info(vd, "Unexpected X rotation value: %#x", cur_rot);
+ trans = MMAL_DISPLAY_ROT0;
+ break;
+ }
+
+ return trans;
+}
+#else
+static MMAL_DISPLAYTRANSFORM_T get_xrandr_rotation(vout_display_t * const vd)
+{
+ VLC_UNUSED(vd);
+ return MMAL_DISPLAY_ROT0;
+}
+#endif
+
+static MMAL_RECT_T str_to_rect(const char * s)
+{
+ MMAL_RECT_T rect = {0};
+ rect.width = strtoul(s, (char**)&s, 0);
+ if (*s == '\0')
+ return rect;
+ if (*s++ != 'x')
+ goto fail;
+ rect.height = strtoul(s, (char**)&s, 0);
+ if (*s == '\0')
+ return rect;
+ if (*s++ != '+')
+ goto fail;
+ rect.x = strtoul(s, (char**)&s, 0);
+ if (*s == '\0')
+ return rect;
+ if (*s++ != '+')
+ goto fail;
+ rect.y = strtoul(s, (char**)&s, 0);
+ if (*s != '\0')
+ goto fail;
+ return rect;
+
+fail:
+ return (MMAL_RECT_T){0,0,0,0};
+}
+
+static int OpenMmalVout(vlc_object_t *object)
+{
+ vout_display_t *vd = (vout_display_t *)object;
+ vout_display_sys_t *sys;
+ MMAL_STATUS_T status;
+ int ret = VLC_EGENERIC;
+ // At the moment all copy is via I420
+ const bool needs_copy = !hw_mmal_chroma_is_mmal(vd->fmt.i_chroma);
+ const MMAL_FOURCC_T enc_in = needs_copy ? MMAL_ENCODING_I420 :
+ vout_vlc_to_mmal_pic_fourcc(vd->fmt.i_chroma);
+
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s: o:%d", __func__, (int)vd->fmt.orientation);
+#endif
+
+ if (bcm_host_is_kms_active()) {
+ msg_Dbg(vd, "KMS active - mmal vout disabled");
+ return VLC_EGENERIC;
+ }
+
+ get_xrandr_rotation(vd);
+
+ sys = calloc(1, sizeof(struct vout_display_sys_t));
+ if (!sys)
+ return VLC_ENOMEM;
+ vd->sys = sys;
+
+ vlc_mutex_init(&sys->manage_mutex);
+
+ if ((sys->init_type = cma_vcsm_init()) == VCSM_INIT_NONE)
+ {
+ msg_Err(vd, "VCSM init fail");
+ goto fail;
+ }
+
+ vc_tv_register_callback(tvservice_cb, vd);
+
+ sys->layer = var_InheritInteger(vd, MMAL_LAYER_NAME);
+ sys->transparent = var_InheritBool(vd, MMAL_VOUT_TRANSPARENT_NAME);
+
+ {
+ const char *display_name = var_InheritString(vd, MMAL_DISPLAY_NAME);
+ int qt_num = var_InheritInteger(vd, "qt-fullscreen-screennumber" );
+ int display_id = find_display_num(display_name);
+// sys->display_id = display_id < 0 ? vc_tv_get_default_display_id() : display_id;
+ sys->display_id = display_id >= 0 ? display_id :
+ qt_num == 1 ? DISPMANX_ID_HDMI1 : DISPMANX_ID_HDMI;
+ if (display_id < -1)
+ msg_Warn(vd, "Unknown display device: '%s'", display_name);
+ else
+ msg_Dbg(vd, "Display device: %s, qt=%d id=%d display=%d", display_name,
+ qt_num, display_id, sys->display_id);
+ }
+
+ {
+ const char *window_str = var_InheritString(vd, MMAL_VOUT_WINDOW_NAME);
+ sys->req_win = str_to_rect(window_str);
+ if (sys->req_win.width != 0)
+ msg_Dbg(vd, "Window: %dx%d @ %d,%d",
+ sys->req_win.width, sys->req_win.height,
+ sys->req_win.x, sys->req_win.y);
+ }
+
+ {
+ const char *transform_name = var_InheritString(vd, MMAL_VOUT_TRANSFORM_NAME);
+ int transform_num = find_transform_num(transform_name);
+ sys->display_transform = transform_num < 0 ?
+ get_xrandr_rotation(vd) :
+ (MMAL_DISPLAYTRANSFORM_T)transform_num;
+
+ if (transform_num < -1)
+ msg_Warn(vd, "Unknown vout transform: '%s'", transform_name);
+ else
+ msg_Dbg(vd, "Display transform: %s, mmal_display_transform=%d",
+ transform_name, (int)sys->display_transform);
+
+ sys->video_transform = combine_transform(
+ vlc_to_mmal_transform(vd->fmt.orientation), sys->display_transform);
+ sys->dest_transform = transform_inverse(sys->display_transform);
+ }
+
+ status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &sys->component);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(vd, "Failed to create MMAL component %s (status=%"PRIx32" %s)",
+ MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, status, mmal_status_to_string(status));
+ goto fail;
+ }
+
+ sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)vd;
+ status = mmal_port_enable(sys->component->control, vd_control_port_cb);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(vd, "Failed to enable control port %s (status=%"PRIx32" %s)",
+ sys->component->control->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
+
+ sys->input = sys->component->input[0];
+ sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)vd;
+
+ sys->input->format->encoding = enc_in;
+ sys->input->format->encoding_variant = 0;
+ sys->i_planes = 1;
+
+ display_set_format(vd, sys->input->format, want_isp(vd));
+
+ status = port_parameter_set_bool(sys->input, MMAL_PARAMETER_ZERO_COPY, true);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(vd, "Failed to set zero copy on port %s (status=%"PRIx32" %s)",
+ sys->input->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
+
+ status = mmal_port_format_commit(sys->input);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(vd, "Failed to commit format for input port %s (status=%"PRIx32" %s)",
+ sys->input->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
+
+ sys->input->buffer_size = sys->input->buffer_size_recommended;
+
+ if (!needs_copy) {
+ sys->input->buffer_num = 30;
+ }
+ else {
+ sys->input->buffer_num = 2;
+ if ((sys->copy_pool = mmal_port_pool_create(sys->input, 2, sys->input->buffer_size)) == NULL)
+ {
+ msg_Err(vd, "Cannot create copy pool");
+ goto fail;
+ }
+ }
+
+ set_display_windows(vd, sys);
+
+ configure_display(vd, vd->cfg, &vd->source);
+
+ status = mmal_port_enable(sys->input, vd_input_port_cb);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(vd, "Failed to enable input port %s (status=%"PRIx32" %s)",
+ sys->input->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
+
+ status = mmal_component_enable(sys->component);
+ if (status != MMAL_SUCCESS) {
+ msg_Err(vd, "Failed to enable component %s (status=%"PRIx32" %s)",
+ sys->component->name, status, mmal_status_to_string(status));
+ goto fail;
+ }
+
+ if ((sys->pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL)
+ {
+ msg_Err(vd, "Failed to create input pool");
+ goto fail;
+ }
+
+ for (unsigned int i = 0; i != SUBS_MAX; ++i) {
+ vout_subpic_t * const sub = sys->subs + i;
+ if ((status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &sub->component)) != MMAL_SUCCESS)
+ {
+ msg_Dbg(vd, "Failed to create subpic component %d", i);
+ goto fail;
+ }
+ sub->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)vd;
+ if ((status = mmal_port_enable(sub->component->control, vd_control_port_cb)) != MMAL_SUCCESS) {
+ msg_Err(vd, "Failed to enable control port %s on sub %d (status=%"PRIx32" %s)",
+ sys->component->control->name, i, status, mmal_status_to_string(status));
+ goto fail;
+ }
+ if ((status = hw_mmal_subpic_open(VLC_OBJECT(vd), &sub->sub, sub->component->input[0],
+ sys->display_id, sys->layer + i + 1)) != MMAL_SUCCESS) {
+ msg_Dbg(vd, "Failed to open subpic %d", i);
+ goto fail;
+ }
+ if ((status = mmal_component_enable(sub->component)) != MMAL_SUCCESS)
+ {
+ msg_Dbg(vd, "Failed to enable subpic component %d", i);
+ goto fail;
+ }
}
+
+ // If we can't deal with it directly ask for I420
+ vd->fmt.i_chroma = req_chroma(vd);
+
+ vd->info = (vout_display_info_t){
+ .is_slow = false,
+ .has_double_click = false,
+ .needs_hide_mouse = false,
+ .has_pictures_invalid = true,
+ .subpicture_chromas = hw_mmal_vzc_subpicture_chromas
+ };
+
+ vd->pool = vd_pool;
+ vd->prepare = vd_prepare;
+ vd->display = vd_display;
+ vd->control = vd_control;
+
+
+ msg_Dbg(vd, ">>> %s: ok", __func__);
+ return VLC_SUCCESS;
+
+fail:
+ CloseMmalVout(object);
+
+ msg_Dbg(vd, ">>> %s: rv=%d", __func__, ret);
+
+ return ret == VLC_SUCCESS ? VLC_EGENERIC : ret;
}
+
+vlc_module_begin()
+
+ add_submodule()
+
+ set_shortname(N_("MMAL vout"))
+ set_description(N_("MMAL-based vout plugin for Raspberry Pi"))
+ set_capability("vout display", 16) // 1 point better than ASCII art
+ add_shortcut("mmal_vout")
+ set_category( CAT_VIDEO )
+ set_subcategory( SUBCAT_VIDEO_VOUT )
+
+ add_integer(MMAL_LAYER_NAME, 1, MMAL_LAYER_TEXT, MMAL_LAYER_LONGTEXT, false)
+ add_bool(MMAL_ADJUST_REFRESHRATE_NAME, false, MMAL_ADJUST_REFRESHRATE_TEXT,
+ MMAL_ADJUST_REFRESHRATE_LONGTEXT, false)
+ add_bool(MMAL_NATIVE_INTERLACED, false, MMAL_NATIVE_INTERLACE_TEXT,
+ MMAL_NATIVE_INTERLACE_LONGTEXT, false)
+ add_string(MMAL_DISPLAY_NAME, "auto", MMAL_DISPLAY_TEXT,
+ MMAL_DISPLAY_LONGTEXT, false)
+ add_string(MMAL_VOUT_TRANSFORM_NAME, "auto", MMAL_VOUT_TRANSFORM_TEXT,
+ MMAL_VOUT_TRANSFORM_LONGTEXT, false)
+ add_string(MMAL_VOUT_WINDOW_NAME, "fullscreen", MMAL_VOUT_WINDOW_TEXT,
+ MMAL_VOUT_WINDOW_LONGTEXT, false)
+ add_bool(MMAL_VOUT_TRANSPARENT_NAME, false, MMAL_VOUT_TRANSPARENT_TEXT,
+ MMAL_VOUT_TRANSPARENT_LONGTEXT, false)
+ set_callbacks(OpenMmalVout, CloseMmalVout)
+
+vlc_module_end()
+
+
diff --git a/modules/hw/mmal/xsplitter.c b/modules/hw/mmal/xsplitter.c
new file mode 100644
index 0000000000..65fcc6b41e
--- /dev/null
+++ b/modules/hw/mmal/xsplitter.c
@@ -0,0 +1,662 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdatomic.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_threads.h>
+#include <vlc_vout_display.h>
+#include <vlc_modules.h>
+
+#define TRACE_ALL 0
+
+// If set will reduce requested size on X
+// A good idea if we have h/w scaler before X
+// as it limits the work needed by GL but a disaster if we don't
+#define LIMIT_X_PELS 0
+
+#define VOUT_DISPLAY_CHANGE_MMAL_BASE 1024
+#define VOUT_DISPLAY_CHANGE_MMAL_HIDE (VOUT_DISPLAY_CHANGE_MMAL_BASE + 0)
+
+struct mmal_x11_sys_s;
+
+typedef struct display_desc_s
+{
+ vout_display_t * vout;
+ unsigned int max_pels;
+ void (* on_swap_away)(vout_display_t *const vd, struct mmal_x11_sys_s *const sys, struct display_desc_s * const x_desc);
+} display_desc_t;
+
+typedef struct mmal_x11_sys_s
+{
+ bool drm_fail; // We tried DRM but it didn't work
+ display_desc_t * cur_desc;
+ display_desc_t mmal_desc;
+ display_desc_t x_desc;
+ display_desc_t drm_desc;
+ uint32_t changed;
+ vlc_fourcc_t subpicture_chromas[16];
+} mmal_x11_sys_t;
+
+#define MAX_GL_PELS (1920*1080)
+#define MAX_MMAL_PELS (4096*4096) // Should never be hit
+#define MAX_DRM_PELS (4096*4096) // Should never be hit
+
+static inline char drmu_log_safechar(int c)
+{
+ return (c < ' ' || c >=0x7f) ? '?' : c;
+}
+
+static inline const char * str_fourcc(char buf[5], uint32_t fcc)
+{
+ if (fcc == 0)
+ return "----";
+ buf[0] = drmu_log_safechar((fcc >> 0) & 0xff);
+ buf[1] = drmu_log_safechar((fcc >> 8) & 0xff);
+ buf[2] = drmu_log_safechar((fcc >> 16) & 0xff);
+ buf[3] = drmu_log_safechar((fcc >> 24) & 0xff);
+ buf[4] = 0;
+ return buf;
+}
+
+#if 0
+// Gen prog for the following table
+// Not done inline in case we end up pulling in FP libs we don't want
+#include <math.h>
+#include <stdio.h>
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ for (i = 0; i != 64; ++i)
+ {
+ printf(" [%2u]=%5u,", i, (unsigned int)(0.5 + (1/sqrt((i + 5)/4.0) * 65536.0)));
+ if (i % 4 == 3)
+ printf("\n");
+ }
+}
+#endif
+
+#if LIMIT_X_PELS
+static const uint16_t sqrt_tab[64] = {
+ [ 0]=58617, [ 1]=53510, [ 2]=49541, [ 3]=46341,
+ [ 4]=43691, [ 5]=41449, [ 6]=39520, [ 7]=37837,
+ [ 8]=36353, [ 9]=35030, [10]=33843, [11]=32768,
+ [12]=31790, [13]=30894, [14]=30070, [15]=29309,
+ [16]=28602, [17]=27945, [18]=27330, [19]=26755,
+ [20]=26214, [21]=25705, [22]=25225, [23]=24770,
+ [24]=24339, [25]=23930, [26]=23541, [27]=23170,
+ [28]=22817, [29]=22479, [30]=22155, [31]=21845,
+ [32]=21548, [33]=21263, [34]=20988, [35]=20724,
+ [36]=20470, [37]=20225, [38]=19988, [39]=19760,
+ [40]=19539, [41]=19326, [42]=19119, [43]=18919,
+ [44]=18725, [45]=18536, [46]=18354, [47]=18176,
+ [48]=18004, [49]=17837, [50]=17674, [51]=17515,
+ [52]=17361, [53]=17211, [54]=17064, [55]=16921,
+ [56]=16782, [57]=16646, [58]=16514, [59]=16384,
+ [60]=16257, [61]=16134, [62]=16013, [63]=15895
+};
+#define SQRT_MAX (sizeof(sqrt_tab)/sizeof(sqrt_tab[0]) - 1)
+#endif
+
+static bool cpy_fmt_limit_size(const display_desc_t * const dd,
+ video_format_t * const dst,
+ const video_format_t * const src)
+{
+#if !LIMIT_X_PELS
+ VLC_UNUSED(dd);
+ *dst = *src;
+ return false;
+#else
+ const unsigned int src_pel = src->i_visible_width * src->i_visible_height;
+
+ *dst = *src;
+
+ if (src_pel <= dd->max_pels)
+ return false;
+
+ // scaling factor sqrt(max_pel/cur_pel)
+ // sqrt done by lookup & 16 bit fixed-point maths - not exactly accurate but
+ // easily good enough & avoids floating point (which may be slow)
+ // src_pel > max_pel so n >= 0
+ // Rounding should be such that exact sqrts work and everything else rounds
+ // down
+ unsigned int n = ((src_pel * 4 - 1) / dd->max_pels) - 4;
+ unsigned int scale = sqrt_tab[n >= SQRT_MAX ? SQRT_MAX : n];
+
+ // Rescale width - rounding up to 16
+ unsigned int width = ((src->i_visible_width * scale + (16 << 16) - 1) >> 16) & ~15;
+ // Rescale height based on new width
+ unsigned int height = (src->i_visible_height * width + src->i_visible_width/2) / src->i_visible_width;
+
+// fprintf(stderr, "%dx%d -> %dx%d\n", src->i_visible_width, src->i_visible_height, width, height);
+
+ dst->i_width = width;
+ dst->i_visible_width = width;
+ dst->i_height = height;
+ dst->i_visible_height = height;
+ return true;
+#endif
+}
+
+static void unload_display_module(vout_display_t * const x_vout)
+{
+ if (x_vout != NULL) {
+ if (x_vout->module != NULL) {
+ module_unneed(x_vout, x_vout->module);
+ }
+ vlc_object_release(x_vout);
+ }
+}
+
+static void stop_drm(vout_display_t *const vd, mmal_x11_sys_t *const sys)
+{
+ VLC_UNUSED(vd);
+
+ unload_display_module(sys->drm_desc.vout);
+ sys->drm_desc.vout = NULL;
+}
+
+static void CloseMmalX11(vlc_object_t *object)
+{
+ vout_display_t * const vd = (vout_display_t *)object;
+ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys;
+
+ msg_Dbg(vd, "<<< %s", __func__);
+
+ if (sys == NULL)
+ return;
+
+ unload_display_module(sys->x_desc.vout);
+
+ unload_display_module(sys->mmal_desc.vout);
+
+ stop_drm(vd, sys);
+
+ free(sys);
+
+ msg_Dbg(vd, ">>> %s", __func__);
+}
+
+static void mmal_x11_event(vout_display_t * x_vd, int cmd, va_list args)
+{
+ vout_display_t * const vd = x_vd->owner.sys;
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s (cmd=%d)", __func__, cmd);
+#endif
+
+ // Do not fall into the display assert if Invalid not supported
+ if (cmd == VOUT_DISPLAY_EVENT_PICTURES_INVALID &&
+ !vd->info.has_pictures_invalid)
+ return;
+
+ vd->owner.event(vd, cmd, args);
+}
+
+static vout_window_t * mmal_x11_window_new(vout_display_t * x_vd, unsigned type)
+{
+ vout_display_t * const vd = x_vd->owner.sys;
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s (type=%d)", __func__, type);
+#endif
+ return vd->owner.window_new(vd, type);
+}
+
+static void mmal_x11_window_del(vout_display_t * x_vd, vout_window_t * win)
+{
+ vout_display_t * const vd = x_vd->owner.sys;
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s", __func__);
+#endif
+ vd->owner.window_del(vd, win);
+}
+
+
+static int load_display_module(vout_display_t * const vd,
+ display_desc_t * const dd,
+ const char * const cap,
+ const char * const module_name)
+{
+ vout_display_t * const x_vout = vlc_object_create(vd, sizeof(*x_vout));
+
+ dd->vout = NULL;
+ if (!x_vout)
+ return -1;
+
+ x_vout->owner.sys = vd;
+ x_vout->owner.event = mmal_x11_event;
+ x_vout->owner.window_new = mmal_x11_window_new;
+ x_vout->owner.window_del = mmal_x11_window_del;
+
+ x_vout->cfg = vd->cfg;
+ x_vout->info = vd->info;
+ cpy_fmt_limit_size(dd, &x_vout->source, &vd->source);
+ cpy_fmt_limit_size(dd, &x_vout->fmt, &vd->fmt);
+
+ if ((x_vout->module = module_need(x_vout, cap, module_name, true)) == NULL)
+ {
+ msg_Dbg(vd, "Failed to open Xsplitter:%s module", module_name);
+ goto fail;
+ }
+
+ msg_Dbg(vd, "R/G/B: %08x/%08x/%08x", x_vout->fmt.i_rmask, x_vout->fmt.i_gmask, x_vout->fmt.i_bmask);
+
+ dd->vout = x_vout;
+ return 0;
+
+fail:
+ vlc_object_release(x_vout);
+ return -1;
+}
+
+static int start_drm(vout_display_t *const vd, mmal_x11_sys_t *const sys)
+{
+ if (sys->drm_desc.vout != NULL)
+ return 0;
+ msg_Info(vd, "Try drm");
+ return load_display_module(vd, &sys->drm_desc, "vout display", "drm_vout");
+}
+
+
+/* Return a pointer over the current picture_pool_t* (mandatory).
+ *
+ * For performance reasons, it is best to provide at least count
+ * pictures but it is not mandatory.
+ * You can return NULL when you cannot/do not want to allocate
+ * pictures.
+ * The vout display module keeps the ownership of the pool and can
+ * destroy it only when closing or on invalid pictures control.
+ */
+static picture_pool_t * mmal_x11_pool(vout_display_t * vd, unsigned count)
+{
+ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys;
+ vout_display_t * const x_vd = sys->cur_desc->vout;
+#if TRACE_ALL
+ char buf0[5];
+ char buf1[5];
+ msg_Dbg(vd, "<<< %s (count=%d) %s:%dx%d->%s:%dx%d", __func__, count,
+ str_fourcc(buf0, vd->fmt.i_chroma),
+ vd->fmt.i_width, vd->fmt.i_height,
+ str_fourcc(buf1, x_vd->fmt.i_chroma),
+ x_vd->fmt.i_width, x_vd->fmt.i_height);
+#endif
+ picture_pool_t * pool = x_vd->pool(x_vd, count);
+#if TRACE_ALL
+ msg_Dbg(vd, ">>> %s: %p", __func__, pool);
+#endif
+ return pool;
+}
+
+/* Prepare a picture and an optional subpicture for display (optional).
+ *
+ * It is called before the next pf_display call to provide as much
+ * time as possible to prepare the given picture and the subpicture
+ * for display.
+ * You are guaranted that pf_display will always be called and using
+ * the exact same picture_t and subpicture_t.
+ * You cannot change the pixel content of the picture_t or of the
+ * subpicture_t.
+ */
+static void mmal_x11_prepare(vout_display_t * vd, picture_t * pic, subpicture_t * sub)
+{
+ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys;
+ vout_display_t * const x_vd = sys->cur_desc->vout;
+#if TRACE_ALL
+ char buf0[5];
+ msg_Dbg(vd, "<<< %s: fmt=%s, %dx%d", __func__, str_fourcc(buf0, pic->format.i_chroma), pic->format.i_width, pic->format.i_height);
+#endif
+ if (x_vd->prepare)
+ x_vd->prepare(x_vd, pic, sub);
+}
+
+/* Display a picture and an optional subpicture (mandatory).
+ *
+ * The picture and the optional subpicture must be displayed as soon as
+ * possible.
+ * You cannot change the pixel content of the picture_t or of the
+ * subpicture_t.
+ *
+ * This function gives away the ownership of the picture and of the
+ * subpicture, so you must release them as soon as possible.
+ */
+static void mmal_x11_display(vout_display_t * vd, picture_t * pic, subpicture_t * sub)
+{
+ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys;
+ vout_display_t * const x_vd = sys->cur_desc->vout;
+
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s: fmt: %dx%d/%dx%d, pic:%dx%d, pts=%lld", __func__, vd->fmt.i_width, vd->fmt.i_height, x_vd->fmt.i_width, x_vd->fmt.i_height, pic->format.i_width, pic->format.i_height, (long long)pic->date);
+#endif
+
+ if (x_vd->fmt.i_chroma != pic->format.i_chroma ||
+ x_vd->fmt.i_width != pic->format.i_width ||
+ x_vd->fmt.i_height != pic->format.i_height)
+ {
+ msg_Dbg(vd, "%s: Picture dropped", __func__);
+ picture_Release(pic);
+ if (sub != NULL)
+ subpicture_Delete(sub);
+ return;
+ }
+
+ x_vd->display(x_vd, pic, sub);
+}
+
+
+static int vout_display_Control(const display_desc_t * const dd, int query, ...)
+{
+ va_list args;
+ int result;
+
+ va_start(args, query);
+ result = dd->vout->control(dd->vout, query, args);
+ va_end(args);
+
+ return result;
+}
+
+static display_desc_t* wanted_display(vout_display_t *const vd, mmal_x11_sys_t *const sys)
+{
+ if (sys->x_desc.vout != NULL && !var_InheritBool(vd, "fullscreen"))
+ return &sys->x_desc;
+
+ // Full screen or no X
+
+ if (sys->mmal_desc.vout != NULL)
+ return &sys->mmal_desc;
+
+ if (!sys->drm_fail) {
+ if (start_drm(vd, sys) == 0)
+ return &sys->drm_desc;
+ msg_Info(vd, "Drm no go");
+ sys->drm_fail = true; // Never try again
+ }
+
+ return sys->x_desc.vout != NULL ? &sys->x_desc : NULL;
+}
+
+static inline int
+up_rv(const int a, const int b)
+{
+ return a != 0 ? a : b;
+}
+
+static int
+reset_pictures(vout_display_t * const vd, const display_desc_t * const desc)
+{
+ int rv = 0;
+ VLC_UNUSED(vd);
+ if (desc->vout)
+ {
+ // If the display doesn't have has_pictures_invalid then it doesn't
+ // expect RESET_PICTURES
+ if (desc->vout->info.has_pictures_invalid)
+ vout_display_Control(desc, VOUT_DISPLAY_RESET_PICTURES);
+ }
+ return rv;
+}
+
+static int
+replay_controls(vout_display_t * const vd, const display_desc_t * const desc, const int32_t changed)
+{
+ if ((changed & (1 << VOUT_DISPLAY_CHANGE_DISPLAY_FILLED)) != 0)
+ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_DISPLAY_FILLED, vd->cfg);
+ if ((changed & (1 << VOUT_DISPLAY_CHANGE_ZOOM)) != 0)
+ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_ZOOM, vd->cfg);
+ if ((changed & ((1 << VOUT_DISPLAY_CHANGE_SOURCE_CROP) |
+ (1 << VOUT_DISPLAY_CHANGE_SOURCE_ASPECT))) != 0)
+ cpy_fmt_limit_size(desc, &desc->vout->source, &vd->source);
+ if ((changed & (1 << VOUT_DISPLAY_CHANGE_SOURCE_ASPECT)) != 0)
+ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_SOURCE_ASPECT);
+ if ((changed & (1 << VOUT_DISPLAY_CHANGE_SOURCE_CROP)) != 0)
+ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_SOURCE_CROP);
+ if ((changed & (1 << VOUT_DISPLAY_CHANGE_VIEWPOINT)) != 0)
+ vout_display_Control(desc, VOUT_DISPLAY_CHANGE_VIEWPOINT, vd->cfg);
+ return 0;
+}
+
+static void swap_away_null(vout_display_t *const vd, mmal_x11_sys_t *const sys, display_desc_t * const x_desc)
+{
+ VLC_UNUSED(vd);
+ VLC_UNUSED(sys);
+ VLC_UNUSED(x_desc);
+}
+
+static void swap_away_mmal(vout_display_t *const vd, mmal_x11_sys_t *const sys, display_desc_t * const x_desc)
+{
+ VLC_UNUSED(vd);
+ VLC_UNUSED(sys);
+ vout_display_Control(x_desc, VOUT_DISPLAY_CHANGE_MMAL_HIDE);
+}
+
+static void swap_away_drm(vout_display_t *const vd, mmal_x11_sys_t *const sys, display_desc_t * const x_desc)
+{
+ VLC_UNUSED(x_desc);
+ stop_drm(vd, sys);
+}
+
+
+/* Control on the module (mandatory) */
+static int mmal_x11_control(vout_display_t * vd, int ctl, va_list va)
+{
+ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys;
+ display_desc_t *x_desc = sys->cur_desc;
+ int rv;
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s[%s] (ctl=%d)", __func__, x_desc->vout->obj.object_type, ctl);
+#endif
+ // Remember what we've told this vd - unwanted ctls ignored on replay
+ if (ctl >= 0 && ctl <= 31)
+ sys->changed |= (1 << ctl);
+
+ switch (ctl) {
+ case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
+ {
+ const vout_display_cfg_t * const cfg = va_arg(va, const vout_display_cfg_t *);
+ display_desc_t * const new_desc = wanted_display(vd, sys);
+ const bool swap_vout = (new_desc != sys->cur_desc);
+
+ msg_Dbg(vd, "Change size: %d, %d: fs=%d",
+ cfg->display.width, cfg->display.height, var_InheritBool(vd, "fullscreen"));
+
+ // Repeat any control calls that we sent to the previous vd
+ if (swap_vout && sys->changed != 0) {
+ const uint32_t changed = sys->changed;
+ sys->changed = 0;
+ replay_controls(vd, new_desc, changed);
+ }
+
+ if (swap_vout) {
+ vout_display_SendEventPicturesInvalid(vd);
+ x_desc->on_swap_away(vd, sys, x_desc);
+ }
+
+ rv = vout_display_Control(new_desc, ctl, cfg);
+ if (rv == VLC_SUCCESS) {
+ vd->fmt = new_desc->vout->fmt;
+ sys->cur_desc = new_desc;
+ // Strictly this is illegal but subpic chromas are consulted
+ // on every render so it is in fact safe.
+ vd->info.subpicture_chromas = sys->cur_desc->vout->info.subpicture_chromas;
+ }
+
+ break;
+ }
+
+ case VOUT_DISPLAY_RESET_PICTURES:
+ {
+ char dbuf0[5], dbuf1[5], dbuf2[5];
+ msg_Dbg(vd, "<<< %s: Pic reset: fmt: %s,%dx%d<-%s,%dx%d, source: %s,%dx%d/%dx%d", __func__,
+ str_fourcc(dbuf0, vd->fmt.i_chroma), vd->fmt.i_width, vd->fmt.i_height,
+ str_fourcc(dbuf1, x_desc->vout->fmt.i_chroma), x_desc->vout->fmt.i_width, x_desc->vout->fmt.i_height,
+ str_fourcc(dbuf2, vd->source.i_chroma), vd->source.i_width, vd->source.i_height, x_desc->vout->source.i_width,
+ x_desc->vout->source.i_height);
+ }
+ rv = reset_pictures(vd, &sys->x_desc);
+ rv = up_rv(rv, reset_pictures(vd, &sys->mmal_desc));
+ rv = up_rv(rv, reset_pictures(vd, &sys->drm_desc));
+
+ vd->fmt = x_desc->vout->fmt;
+ break;
+
+ case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
+ case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
+ cpy_fmt_limit_size(x_desc, &x_desc->vout->source, &vd->source);
+
+ /* FALLTHRU */
+ default:
+ rv = x_desc->vout->control(x_desc->vout, ctl, va);
+// vd->fmt = x_vd->fmt;
+ break;
+ }
+#if TRACE_ALL
+ msg_Dbg(vd, ">>> %s (rv=%d)", __func__, rv);
+#endif
+ return rv;
+}
+
+#define DO_MANAGE 0
+
+#if DO_MANAGE
+/* Manage pending event (optional) */
+static void mmal_x11_manage(vout_display_t * vd)
+{
+ mmal_x11_sys_t * const sys = (mmal_x11_sys_t *)vd->sys;
+ vout_display_t * const x_vd = sys->cur_desc->vout;
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s", __func__);
+#endif
+ x_vd->manage(x_vd);
+}
+#endif
+
+static int OpenMmalX11(vlc_object_t *object)
+{
+ vout_display_t * const vd = (vout_display_t *)object;
+ mmal_x11_sys_t * const sys = calloc(1, sizeof(*sys));
+ int ret = VLC_SUCCESS;
+
+ if (sys == NULL) {
+ return VLC_EGENERIC;
+ }
+ vd->sys = (vout_display_sys_t *)sys;
+
+ vd->info = (vout_display_info_t){
+ .is_slow = false,
+ .has_double_click = false,
+ .needs_hide_mouse = false,
+ .has_pictures_invalid = true,
+ .subpicture_chromas = NULL
+ };
+
+ {
+ char dbuf0[5];
+ msg_Dbg(vd, ">>> %s: %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d", __func__,
+ str_fourcc(dbuf0, vd->fmt.i_chroma),
+ vd->fmt.i_width, vd->fmt.i_height,
+ vd->fmt.i_x_offset, vd->fmt.i_y_offset,
+ vd->fmt.i_visible_width, vd->fmt.i_visible_height,
+ vd->fmt.i_sar_num, vd->fmt.i_sar_den);
+ }
+
+ sys->x_desc.max_pels = MAX_GL_PELS;
+ sys->x_desc.on_swap_away = swap_away_null;
+ sys->mmal_desc.max_pels = MAX_MMAL_PELS;
+ sys->mmal_desc.on_swap_away = swap_away_mmal;
+ sys->drm_desc.max_pels = MAX_DRM_PELS;
+ sys->drm_desc.on_swap_away = swap_away_drm;
+
+ if (load_display_module(vd, &sys->x_desc, "vout display", "opengles2") == 0)
+ {
+ msg_Dbg(vd, "Opengles2 output found");
+ }
+ else
+ {
+ sys->x_desc.max_pels = MAX_MMAL_PELS;
+ if (load_display_module(vd, &sys->x_desc, "vout display", "xcb_x11") == 0)
+ msg_Dbg(vd, "X11 XCB output found");
+ }
+
+ if ((load_display_module(vd, &sys->mmal_desc, "vout display", "mmal_vout")) == 0)
+ msg_Dbg(vd, "MMAL output found");
+
+ vd->pool = mmal_x11_pool;
+ vd->prepare = mmal_x11_prepare;
+ vd->display = mmal_x11_display;
+ vd->control = mmal_x11_control;
+#if DO_MANAGE
+ vd->manage = mmal_x11_manage;
+#endif
+
+ sys->cur_desc = wanted_display(vd, sys);
+ if (sys->cur_desc == NULL) {
+ char dbuf0[5], dbuf1[5];
+ msg_Warn(vd, "No valid output found for vout (%s/%s)", str_fourcc(dbuf0, vd->fmt.i_chroma), str_fourcc(dbuf1, vd->source.i_chroma));
+ goto fail;
+ }
+
+ if (sys->mmal_desc.vout == NULL || sys->x_desc.vout == NULL) {
+ vd->info = sys->cur_desc->vout->info;
+ vd->info.has_pictures_invalid = true; // Should make this unwanted
+ }
+ else {
+ // We have both - construct a combination
+ vd->info = (vout_display_info_t){
+ .is_slow = false,
+ .has_double_click = sys->mmal_desc.vout->info.has_double_click || sys->x_desc.vout->info.has_double_click,
+ .needs_hide_mouse = sys->mmal_desc.vout->info.needs_hide_mouse || sys->x_desc.vout->info.needs_hide_mouse,
+ .has_pictures_invalid = true,
+ };
+ // Construct intersection of subpicture chromas
+ // sys calloced so no need to add the terminating zero
+ if (sys->mmal_desc.vout->info.subpicture_chromas != NULL && sys->x_desc.vout->info.subpicture_chromas != NULL) {
+ unsigned int n = 0;
+ // N^2 - fix if we ever care
+ for (const vlc_fourcc_t * p1 = sys->mmal_desc.vout->info.subpicture_chromas; *p1 != 0 && n != 15; ++p1) {
+ for (const vlc_fourcc_t * p2 = sys->x_desc.vout->info.subpicture_chromas; *p2 != 0; ++p2) {
+ if (*p1 == *p2) {
+ sys->subpicture_chromas[n++] = *p1;
+ break;
+ }
+ }
+ }
+ if (n != 0)
+ vd->info.subpicture_chromas = sys->subpicture_chromas;
+ }
+ }
+ vd->fmt = sys->cur_desc->vout->fmt;
+
+#if TRACE_ALL
+ {
+ char dbuf0[5];
+ msg_Dbg(vd, ">>> %s: (%s) %s,%dx%d [(%d,%d) %d/%d] sar:%d/%d", __func__,
+ module_get_name(sys->cur_desc->vout->module, false),
+ str_fourcc(dbuf0, vd->fmt.i_chroma),
+ vd->fmt.i_width, vd->fmt.i_height,
+ vd->fmt.i_x_offset, vd->fmt.i_y_offset,
+ vd->fmt.i_visible_width, vd->fmt.i_visible_height,
+ vd->fmt.i_sar_num, vd->fmt.i_sar_den);
+ }
+#endif
+ return VLC_SUCCESS;
+
+fail:
+ CloseMmalX11(VLC_OBJECT(vd));
+ return ret == VLC_SUCCESS ? VLC_EGENERIC : ret;
+}
+
+
+
+
+vlc_module_begin()
+ set_shortname(N_("MMAL x11 splitter"))
+ set_description(N_("MMAL x11 splitter for Raspberry Pi"))
+ set_capability("vout display", 300) // Between GLES & GL
+ add_shortcut("mmal_x11")
+ set_category( CAT_VIDEO )
+ set_subcategory( SUBCAT_VIDEO_VOUT )
+ set_callbacks(OpenMmalX11, CloseMmalX11)
+vlc_module_end()
+
diff --git a/modules/video_chroma/chain.c b/modules/video_chroma/chain.c
index 2709425255..5ac384e633 100644
--- a/modules/video_chroma/chain.c
+++ b/modules/video_chroma/chain.c
@@ -280,8 +280,9 @@ static int BuildTransformChain( filter_t *p_filter )
return VLC_SUCCESS;
/* Lets try resize+chroma first, then transform */
- msg_Dbg( p_filter, "Trying to build chroma+resize" );
- EsFormatMergeSize( &fmt_mid, &p_filter->fmt_out, &p_filter->fmt_in );
+ msg_Dbg( p_filter, "Trying to build chroma+resize, then transform" );
+ es_format_Copy( &fmt_mid, &p_filter->fmt_out );
+ video_format_TransformTo(&fmt_mid.video, p_filter->fmt_in.video.orientation);
i_ret = CreateChain( p_filter, &fmt_mid );
es_format_Clean( &fmt_mid );
if( i_ret == VLC_SUCCESS )
diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am
index ae48c8e062..270f1a63b8 100644
--- a/modules/video_output/Makefile.am
+++ b/modules/video_output/Makefile.am
@@ -187,6 +187,29 @@ vout_LTLIBRARIES += libglx_plugin.la
endif
endif
+### DRM ###
+
+libdrm_vout_plugin_la_SOURCES = video_output/drmu/drm_vout.c \
+ video_output/drmu/drmu_vlc.c video_output/drmu/drmu_vlc.h \
+ video_output/drmu/drmu_vlc_fmts.c video_output/drmu/drmu_vlc_fmts.h \
2023-03-29 17:41:09 +00:00
+ video_output/drmu/drmu_fmts.c video_output/drmu/drmu_fmts.h \
+ video_output/drmu/drmu_chroma.h \
+ video_output/drmu/drmu_log.h video_output/drmu/drmu.c \
+ video_output/drmu/drmu_xlease.c video_output/drmu/drmu_atomic.c \
+ video_output/drmu/drmu_util.c video_output/drmu/drmu_util.h \
+ video_output/drmu/drmu_output.c video_output/drmu/drmu_output.h \
+ video_output/drmu/drmu.h\
+ video_output/drmu/pollqueue.c video_output/drmu/pollqueue.h
+libdrm_vout_plugin_la_CFLAGS = $(AM_CFLAGS) -pthread -I/usr/include/libdrm
+libdrm_vout_plugin_la_LDFLAGS = $(AM_LDFLAGS) -pthread
+libdrm_vout_plugin_la_LIBADD = -ldrm -lxcb-randr -lxcb
+if HAVE_MMAL
+libdrm_vout_plugin_la_CFLAGS += -DHAS_ZC_CMA=1
+libdrm_vout_plugin_la_SOURCES += hw/mmal/mmal_cma_pic.h
+endif
+if HAVE_DRM
+vout_LTLIBRARIES += libdrm_vout_plugin.la
+endif
### Wayland ###
libwl_shm_plugin_la_SOURCES = video_output/wayland/shm.c
@@ -205,7 +228,38 @@ video_output/wayland/viewporter-client-protocol.h: \
video_output/wayland/viewporter-protocol.c: \
$(WAYLAND_PROTOCOLS)/stable/viewporter/viewporter.xml
- $(AM_V_GEN)$(WAYLAND_SCANNER) code $< $@
+ $(AM_V_GEN)$(WAYLAND_SCANNER) private-code $< $@
+
+video_output/wayland/linux-dmabuf-unstable-v1-client-protocol.h: \
+ $(WAYLAND_PROTOCOLS)/unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml
+ $(AM_V_GEN)$(WAYLAND_SCANNER) client-header $< $@
+
+video_output/wayland/linux-dmabuf-unstable-v1-protocol.c: \
+ $(WAYLAND_PROTOCOLS)/unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml
+ $(AM_V_GEN)$(WAYLAND_SCANNER) private-code $< $@
+
+libwl_dmabuf_plugin_la_SOURCES = video_output/wayland/dmabuf.c\
+ video_output/wayland/picpool.c video_output/wayland/picpool.h\
+ video_output/wayland/dmabuf_alloc.c video_output/wayland/dmabuf_alloc.h\
+ video_output/wayland/rgba_premul.c video_output/wayland/rgba_premul.h\
+ video_output/drmu/pollqueue.c video_output/drmu/pollqueue.h\
+ video_output/drmu/drmu_vlc_fmts.c video_output/drmu/drmu_vlc_fmts.h
+nodist_libwl_dmabuf_plugin_la_SOURCES = \
+ video_output/wayland/viewporter-client-protocol.h \
+ video_output/wayland/viewporter-protocol.c \
+ video_output/wayland/linux-dmabuf-unstable-v1-client-protocol.h \
+ video_output/wayland/linux-dmabuf-unstable-v1-protocol.c
+libwl_dmabuf_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) \
+ -I$(builddir)/video_output/wayland -pthread
+libwl_dmabuf_plugin_la_CFLAGS = $(WAYLAND_CLIENT_CFLAGS) -pthread
+libwl_dmabuf_plugin_la_LIBADD = $(WAYLAND_CLIENT_LIBS)
+libwl_dmabuf_plugin_la_LDFLAGS = $(AM_LDFLAGS) -pthread
+if HAVE_ARM64
+libwl_dmabuf_plugin_la_SOURCES += video_output/wayland/rgba_premul_aarch64.S\
+ video_output/wayland/rgba_premul_aarch64.h
+libwl_dmabuf_plugin_la_CFLAGS += -DHAVE_AARCH64_ASM=1
+endif
+CLEANFILES += $(nodist_libwl_dmabuf_plugin_la_SOURCES)
libwl_shell_plugin_la_SOURCES = video_output/wayland/shell.c
libwl_shell_plugin_la_CFLAGS = $(WAYLAND_CLIENT_CFLAGS)
@@ -244,6 +298,8 @@ libegl_wl_plugin_la_LIBADD = $(EGL_LIBS) $(WAYLAND_EGL_LIBS)
if HAVE_WAYLAND
BUILT_SOURCES += $(nodist_libwl_shm_plugin_la_SOURCES)
vout_LTLIBRARIES += libwl_shm_plugin.la
+BUILT_SOURCES += $(nodist_libwl_dmabuf_plugin_la_SOURCES)
+vout_LTLIBRARIES += libwl_dmabuf_plugin.la
vout_LTLIBRARIES += libwl_shell_plugin.la
BUILT_SOURCES += $(nodist_libxdg_shell_plugin_la_SOURCES)
vout_LTLIBRARIES += libxdg_shell_plugin.la
diff --git a/modules/video_output/caca.c b/modules/video_output/caca.c
index 3440401082..9297823267 100644
--- a/modules/video_output/caca.c
+++ b/modules/video_output/caca.c
@@ -160,7 +160,11 @@ static int Open(vlc_object_t *object)
}
const char *driver = NULL;
-#ifdef __APPLE__
+// RPI: If driver is NULL then if we have X but DISPLAY is unset then somehow
+// the GL module becomes unloaded without anything noticing and that then
+// causes a segfault.
+//#ifdef __APPLE__
+#if 1
// Make sure we don't try to open a window.
driver = "ncurses";
#endif
diff --git a/modules/video_output/drmu/drm_vout.c b/modules/video_output/drmu/drm_vout.c
new file mode 100644
index 0000000000..728cc0744a
--- /dev/null
+++ b/modules/video_output/drmu/drm_vout.c
@@ -0,0 +1,1060 @@
+/*****************************************************************************
+ * drm_vout.c: DRM based output device
+ *****************************************************************************
+ * Copyright <20> 2014 jusst technologies GmbH
+ *
+ * Authors: Dennis Hamester <dennis.hamester@gmail.com>
+ * Julian Scheel <julian@jusst.de>
+ * John Cox <jc@kynesim.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <pthread.h>
+
+#include "drmu.h"
+#include "drmu_log.h"
+#include "drmu_output.h"
+#include "drmu_util.h"
+#include "drmu_vlc.h"
+
+#include <vlc_common.h>
+
+#include <vlc_codec.h>
+#include <vlc_picture.h>
+#include <vlc_plugin.h>
+#include <vlc_vout_display.h>
+
+#include <libdrm/drm.h>
+#include <libdrm/drm_mode.h>
+#include <libdrm/drm_fourcc.h>
+
2023-03-28 20:02:15 +00:00
+#define TRACE_ALL 0
+
+#define SUBPICS_MAX 4
+
+#define DRM_MODULE "vc4"
+
+
+#define DRM_VOUT_SOURCE_MODESET_NAME "drm-vout-source-modeset"
+#define DRM_VOUT_SOURCE_MODESET_TEXT N_("Attempt to match display to source")
+#define DRM_VOUT_SOURCE_MODESET_LONGTEXT N_("Attempt to match display resolution and refresh rate to source.\
+ Defaults to the 'preferred' mode if no good enough match found. \
+ If unset then resolution & refresh will not be set.")
+
+#define DRM_VOUT_MODE_NAME "drm-vout-mode"
+#define DRM_VOUT_MODE_TEXT N_("Set this mode for display")
+#define DRM_VOUT_MODE_LONGTEXT N_("arg: <w>x<h>@<hz> Force mode to arg")
+
+#define DRM_VOUT_NO_MODESET_NAME "drm-vout-no-modeset"
+#define DRM_VOUT_NO_MODESET_TEXT N_("Do not modeset")
+#define DRM_VOUT_NO_MODESET_LONGTEXT N_("Do no operation that would cause a modeset.\
+ This overrides the operation of all other flags.")
+
+#define DRM_VOUT_NO_MAX_BPC "drm-vout-no-max-bpc"
+#define DRM_VOUT_NO_MAX_BPC_TEXT N_("Do not set bpc on output")
+#define DRM_VOUT_NO_MAX_BPC_LONGTEXT N_("Do not try to switch from 8-bit RGB to 12-bit YCC on UHD frames.\
+ 12 bit is dependant on kernel and display support so may not be availible")
+
2023-03-28 20:02:15 +00:00
+#define DRM_VOUT_WINDOW_NAME "drm-vout-window"
+#define DRM_VOUT_WINDOW_TEXT N_("Display window for Rpi fullscreen")
+#define DRM_VOUT_WINDOW_LONGTEXT N_("Display window for Rpi fullscreen."\
+"fullscreen|<width>x<height>+<x>+<y>")
+
2023-03-28 20:02:15 +00:00
+#define DRM_VOUT_DISPLAY_NAME "drm-vout-display"
+#define DRM_VOUT_DISPLAY_TEXT N_("Output device for Rpi fullscreen.")
+#define DRM_VOUT_DISPLAY_LONGTEXT N_("Output device for Rpi fullscreen. " \
+"Valid values are HDMI-1,HDMI-2 or a drm connector name. By default if qt-fullscreen-screennumber " \
2023-03-28 20:02:15 +00:00
+"is specified (or set by Fullscreen Output Device in Preferences) " \
+"HDMI-<qt-fullscreen-screennumber+1> will be used, otherwise HDMI-1.")
+
2023-03-28 20:02:15 +00:00
+#define DRM_VOUT_MODULE_NAME "drm-vout-module"
+#define DRM_VOUT_MODULE_TEXT N_("DRM module to use")
+#define DRM_VOUT_MODULE_LONGTEXT N_("DRM module for Rpi fullscreen")
+
+
+typedef struct subpic_ent_s {
+ drmu_fb_t * fb;
+ drmu_rect_t pos;
+ drmu_rect_t space; // display space of pos
+ picture_t * pic;
+ unsigned int alpha; // out of 0xff * 0xff
+} subpic_ent_t;
+
+typedef struct vout_display_sys_t {
+ drmu_env_t * du;
+ drmu_output_t * dout;
+ drmu_plane_t * dp;
+ drmu_pool_t * pic_pool;
+ drmu_pool_t * sub_fb_pool;
+ drmu_plane_t * subplanes[SUBPICS_MAX];
+ subpic_ent_t subpics[SUBPICS_MAX];
+ vlc_fourcc_t * subpic_chromas;
+
+ drmu_atomic_t * display_set;
+
+ vout_display_place_t req_win;
+ vout_display_place_t spu_rect;
+ vout_display_place_t dest_rect;
+ vout_display_place_t win_rect;
+ vout_display_place_t display_rect;
+
+ video_transform_t display_transform;
+ video_transform_t video_transform;
+ video_transform_t dest_transform;
+
+ uint32_t con_id;
+ int mode_id;
+
+ picture_pool_t * vlc_pic_pool;
+} vout_display_sys_t;
+
+static drmu_fb_t *
+copy_pic_to_fb(vout_display_t *vd, drmu_pool_t * const pool, picture_t * const src)
+{
+ const uint32_t drm_fmt = drmu_format_vlc_to_drm(&src->format);
+ drmu_fb_t * fb;
+ int i;
+
+ if (drm_fmt == 0) {
+ msg_Warn(vd, "Failed vlc->drm format for copy_pic: %s", drmu_log_fourcc(src->format.i_chroma));
+ return NULL;
+ }
+
+ fb = drmu_pool_fb_new_dumb(pool, src->format.i_width, src->format.i_height, drm_fmt);
+ if (fb == NULL) {
+ msg_Warn(vd, "Failed alloc for copy_pic: %dx%d", src->format.i_width, src->format.i_height);
+ return NULL;
+ }
+
+ for (i = 0; i != src->i_planes; ++i) {
+ plane_t dst_plane;
+ dst_plane = drmu_fb_vlc_plane(fb, i);
+ plane_CopyPixels(&dst_plane, src->p + i);
+ }
+
+ drmu_fb_vlc_pic_set_metadata(fb, src);
+
+ return fb;
+}
+
+
2023-03-28 20:02:15 +00:00
+static vout_display_place_t str_to_rect(const char * s)
+{
+ vout_display_place_t rect = {0};
+ rect.width = strtoul(s, (char**)&s, 0);
+ if (*s == '\0')
+ return rect;
+ if (*s++ != 'x')
+ goto fail;
+ rect.height = strtoul(s, (char**)&s, 0);
+ if (*s == '\0')
+ return rect;
+ if (*s++ != '+')
+ goto fail;
+ rect.x = strtoul(s, (char**)&s, 0);
+ if (*s == '\0')
+ return rect;
+ if (*s++ != '+')
+ goto fail;
+ rect.y = strtoul(s, (char**)&s, 0);
+ if (*s != '\0')
+ goto fail;
+ return rect;
+
+fail:
+ return (vout_display_place_t){0,0,0,0};
+}
+
+// MMAL headers comment these (getting 2 a bit wrong) but do not give
+// defines
+#define VXF_H_SHIFT 0 // Hflip
+#define VXF_V_SHIFT 1 // Vflip
+#define VXF_T_SHIFT 2 // Transpose
+#define VXF_H_BIT (1 << VXF_H_SHIFT)
+#define VXF_V_BIT (1 << VXF_V_SHIFT)
+#define VXF_T_BIT (1 << VXF_T_SHIFT)
+
+static inline bool
+is_vxf_transpose(const video_transform_t t)
+{
+ return ((unsigned int)t & VXF_T_BIT) != 0;
+}
+
+static inline bool
+is_vxf_hflip(const video_transform_t t)
+{
+ return ((unsigned int)t & VXF_H_BIT) != 0;
+}
+
+static inline bool
+is_vxf_vflip(const video_transform_t t)
+{
+ return ((unsigned int)t & VXF_V_BIT) != 0;
+}
+
+static inline video_transform_t
+swap_vxf_hv(const video_transform_t x)
+{
+ return (((x >> VXF_H_SHIFT) & 1) << VXF_V_SHIFT) |
+ (((x >> VXF_V_SHIFT) & 1) << VXF_H_SHIFT) |
+ (x & VXF_T_BIT);
+}
+
+static inline video_transform_t
+vxf_inverse(const video_transform_t x)
+{
+ return is_vxf_transpose(x) ? swap_vxf_hv(x) : x;
+}
+
+// Transform generated by A then B
+// All ops are self inverse so can simply be XORed on their own
+// H & V flips after a transpose need to be swapped
+static inline video_transform_t
+combine_vxf(const video_transform_t a, const video_transform_t b)
+{
+ return a ^ (is_vxf_transpose(a) ? swap_vxf_hv(b) : b);
+}
+
+static inline vout_display_place_t
+vplace_transpose(const vout_display_place_t s)
+{
+ return (vout_display_place_t){
+ .x = s.y,
+ .y = s.x,
+ .width = s.height,
+ .height = s.width
+ };
+}
+
+// hflip s in c
+static inline vout_display_place_t vplace_hflip(const vout_display_place_t s, const vout_display_place_t c)
+{
+ return (vout_display_place_t){
+ .x = c.x + (c.x + c.width) - (s.x + s.width),
+ .y = s.y,
+ .width = s.width,
+ .height = s.height
+ };
+}
+
+// vflip s in c
+static inline vout_display_place_t vplace_vflip(const vout_display_place_t s, const vout_display_place_t c)
+{
+ return (vout_display_place_t){
+ .x = s.x,
+ .y = (c.y + c.height) - (s.y - c.y) - s.height,
+ .width = s.width,
+ .height = s.height
+ };
+}
+
+static vout_display_place_t
+place_out(const vout_display_cfg_t * cfg,
+ const video_format_t * fmt,
+ const vout_display_place_t r)
+{
+ video_format_t tfmt;
+ vout_display_cfg_t tcfg;
+ vout_display_place_t place;
+
+ // Fix SAR if unknown
+ if (fmt->i_sar_den == 0 || fmt->i_sar_num == 0) {
+ tfmt = *fmt;
+ tfmt.i_sar_den = 1;
+ tfmt.i_sar_num = 1;
+ fmt = &tfmt;
+ }
+
+ // Override what VLC thinks might be going on with display size
+ // if we know better
+ if (r.width != 0 && r.height != 0)
+ {
+ tcfg = *cfg;
+ tcfg.display.width = r.width;
+ tcfg.display.height = r.height;
+ cfg = &tcfg;
+ }
+
+ vout_display_PlacePicture(&place, fmt, cfg, false);
+
+ place.x += r.x;
+ place.y += r.y;
+
+ return place;
+}
+
+static vout_display_place_t
+rect_transform(vout_display_place_t s, const vout_display_place_t c, const video_transform_t t)
+{
+ if (is_vxf_transpose(t))
+ s = vplace_transpose(s);
+ if (is_vxf_hflip(t))
+ s = vplace_hflip(s, c);
+ if (is_vxf_vflip(t) != 0)
+ s = vplace_vflip(s, c);
+ return s;
+}
+
+static void
+place_dest_rect(vout_display_t * const vd,
+ const vout_display_cfg_t * const cfg,
+ const video_format_t * fmt)
+{
+ vout_display_sys_t * const sys = vd->sys;
+ sys->dest_rect = rect_transform(place_out(cfg, fmt, sys->win_rect),
+ sys->display_rect, sys->dest_transform);
+}
+
+static void
+place_spu_rect(vout_display_t * const vd,
+ const vout_display_cfg_t * const cfg,
+ const video_format_t * fmt)
+{
+ vout_display_sys_t * const sys = vd->sys;
+ static const vout_display_place_t r0 = {0};
+
+ sys->spu_rect = place_out(cfg, fmt, r0);
+ sys->spu_rect.x = 0;
+ sys->spu_rect.y = 0;
+
+ // Copy place override logic for spu pos from video_output.c
+ // This info doesn't appear to reside anywhere natively
+
+ if (fmt->i_width * fmt->i_height >= (unsigned int)(sys->spu_rect.width * sys->spu_rect.height)) {
+ sys->spu_rect.width = fmt->i_visible_width;
+ sys->spu_rect.height = fmt->i_visible_height;
+ }
+
+ if (ORIENT_IS_SWAP(fmt->orientation))
+ sys->spu_rect = vplace_transpose(sys->spu_rect);
+}
+
+static void
+place_rects(vout_display_t * const vd,
+ const vout_display_cfg_t * const cfg,
+ const video_format_t * fmt)
+{
+ place_dest_rect(vd, cfg, fmt);
+ place_spu_rect(vd, cfg, fmt);
+}
+
+static int configure_display(vout_display_t *vd, const vout_display_cfg_t *cfg,
+ const video_format_t *fmt)
+{
+ vout_display_sys_t * const sys = vd->sys;
+
+ if (!cfg && !fmt)
+ {
+ msg_Err(vd, "%s: Missing cfg & fmt", __func__);
+ return -EINVAL;
+ }
+
+ if (!fmt)
+ fmt = &vd->source;
+
+ if (!cfg)
+ cfg = vd->cfg;
+
+ sys->video_transform = combine_vxf((video_transform_t)fmt->orientation, sys->display_transform);
+
+ place_rects(vd, cfg, fmt);
+ return 0;
+}
+
+static void set_display_windows(vout_display_t *const vd, vout_display_sys_t *const sys)
+{
+ const drmu_mode_simple_params_t * const mode = drmu_output_mode_simple_params(sys->dout);
+ VLC_UNUSED(vd);
+
+ sys->display_rect = (vout_display_place_t) {0, 0, mode->width, mode->height};
+
+ sys->win_rect = (sys->req_win.width != 0) ?
+ sys->req_win :
+ is_vxf_transpose(sys->display_transform) ?
+ vplace_transpose(sys->display_rect) : sys->display_rect;
+}
+
+static void vd_drm_prepare(vout_display_t *vd, picture_t *pic,
+ subpicture_t *subpicture)
+{
+ vout_display_sys_t * const sys = vd->sys;
+ unsigned int n = 0;
+ drmu_atomic_t * da = drmu_atomic_new(sys->du);
+ drmu_fb_t * dfb = NULL;
+ drmu_rect_t r;
+ unsigned int i;
+ int ret;
+
+ if (da == NULL)
+ goto fail;
+
+ if (sys->display_set != NULL) {
+ msg_Warn(vd, "sys->display_set != NULL");
+ drmu_atomic_unref(&sys->display_set);
+ }
+
+ // * Mode (currently) doesn't change whilst running so no need to set here
+
+ // Attempt to import the subpics
+ for (subpicture_t * spic = subpicture; spic != NULL; spic = spic->p_next)
+ {
+ for (subpicture_region_t *sreg = spic->p_region; sreg != NULL; sreg = sreg->p_next) {
+ picture_t * const src = sreg->p_picture;
+ subpic_ent_t * const dst = sys->subpics + n;
+
+ // If we've run out of subplanes we could allocate - give up now
+ if (!sys->subplanes[n])
+ goto subpics_done;
+
+ // If the same picture then assume the same contents
+ // We keep a ref to the previous pic to ensure that the same picture
+ // structure doesn't get reused and confuse us.
+ if (src != dst->pic) {
+ drmu_fb_unref(&dst->fb);
+ if (dst->pic != NULL) {
+ picture_Release(dst->pic);
+ dst->pic = NULL;
+ }
+
+ dst->fb = copy_pic_to_fb(vd, sys->sub_fb_pool, src);
+ if (dst->fb == NULL)
+ continue;
+ drmu_fb_pixel_blend_mode_set(dst->fb, DRMU_FB_PIXEL_BLEND_COVERAGE);
+
+ dst->pic = picture_Hold(src);
+ }
+ drmu_fb_crop_frac_set(dst->fb, drmu_rect_shl16(drmu_rect_vlc_format_crop(&sreg->fmt)));
+
+ // *** More transform required
+ dst->pos = (drmu_rect_t){
+ .x = sreg->i_x,
+ .y = sreg->i_y,
+ .w = sreg->fmt.i_visible_width,
+ .h = sreg->fmt.i_visible_height,
+ };
+ dst->alpha = spic->i_alpha * sreg->i_alpha;
+
+// msg_Info(vd, "Orig: %dx%d, (%d,%d) %dx%d; offset %d,%d", spic->i_original_picture_width, spic->i_original_picture_height,
+// sreg->i_x, sreg->i_y, src->format.i_visible_width, src->format.i_visible_height,
+// sreg->fmt.i_x_offset, sreg->fmt.i_y_offset);
+ dst->space = drmu_rect_vlc_place(&sys->spu_rect);
+
+ if (++n == SUBPICS_MAX)
+ goto subpics_done;
+ }
+ }
+subpics_done:
+
+ // Clear any other entries
+ for (; n != SUBPICS_MAX; ++n) {
+ subpic_ent_t * const dst = sys->subpics + n;
+ if (dst->pic != NULL) {
+ picture_Release(dst->pic);
+ dst->pic = NULL;
+ }
+ drmu_fb_unref(&dst->fb);
+ }
+
2023-03-28 20:02:15 +00:00
+ r = drmu_rect_vlc_place(&sys->dest_rect);
+
+#if 0
2023-03-28 20:02:15 +00:00
+ {
+ static int z = 0;
+ if (--z < 0) {
+ z = 200;
+ msg_Info(vd, "Pic: %d,%d %dx%d/%dx%d %d/%d Fmt: %d,%d %dx%d/%dx%d %d/%d Src: %d,%d %dx%d/%dx%d %d/%d Display: %dx%d %d/%d Place: %d,%d %dx%d",
+ pic->format.i_x_offset, pic->format.i_y_offset,
+ pic->format.i_width, pic->format.i_height,
+ pic->format.i_visible_width, pic->format.i_visible_height,
+ pic->format.i_sar_num, pic->format.i_sar_den,
+ vd->fmt.i_x_offset, vd->fmt.i_y_offset,
+ vd->fmt.i_width, vd->fmt.i_height,
+ vd->fmt.i_visible_width, vd->fmt.i_visible_height,
+ vd->fmt.i_sar_num, vd->fmt.i_sar_den,
+ vd->source.i_x_offset, vd->source.i_y_offset,
+ vd->source.i_width, vd->source.i_height,
+ vd->source.i_visible_width, vd->source.i_visible_height,
+ vd->source.i_sar_num, vd->source.i_sar_den,
+ vd->cfg->display.width, vd->cfg->display.height,
+ vd->cfg->display.sar.num, vd->cfg->display.sar.den,
+ r.x, r.y, r.w, r.h);
+ }
+ }
+#endif
+
+#if HAS_ZC_CMA
+ if (drmu_format_vlc_to_drm_cma(pic->format.i_chroma) != 0) {
+ dfb = drmu_fb_vlc_new_pic_cma_attach(sys->du, pic);
+ }
+ else
+#endif
+#if HAS_DRMPRIME
+ if (drmu_format_vlc_to_drm_prime(pic->format.i_chroma, NULL) != 0) {
+ dfb = drmu_fb_vlc_new_pic_attach(sys->du, pic);
+ }
+ else
+#endif
+ {
+ dfb = copy_pic_to_fb(vd, sys->pic_pool, pic);
+ }
+
+ if (dfb == NULL) {
+ msg_Err(vd, "Failed to create frme buffer from pic");
+ return;
+ }
2023-03-28 20:02:15 +00:00
+ // * Maybe scale cropping by vd->fmt.i_width/height / vd->source.i_width/height
+ // to get pic coord cropping
+ // Wait until we have a bad test case before doing this as I'm worried
+ // that we may get unexpected w/h mismatches that do unwanted scaling
+#if 0
+ drmu_fb_crop_frac_set(dfb,
+ drmu_rect_rescale(
+ drmu_rect_vlc_format_crop(&vd->source),
+ drmu_rect_shl16(drmu_rect_wh(vd->fmt.i_width, vd->fmt.i_height)),
+ drmu_rect_wh(vd->source.i_width, vd->source.i_height)));
+#else
+ drmu_fb_crop_frac_set(dfb, drmu_rect_shl16(drmu_rect_vlc_format_crop(&vd->source)));
+#endif
+ drmu_output_fb_info_set(sys->dout, dfb);
+
2022-12-08 21:49:23 +00:00
+ ret = drmu_atomic_plane_add_fb(da, sys->dp, dfb, r);
+ drmu_atomic_output_add_props(da, sys->dout);
+ drmu_fb_unref(&dfb);
+
+ if (ret != 0) {
+ msg_Err(vd, "Failed to set video plane: %s", strerror(-ret));
+ goto fail;
+ }
+
+ for (i = 0; i != SUBPICS_MAX; ++i) {
+ subpic_ent_t * const spe = sys->subpics + i;
+
+// msg_Info(vd, "pic=%dx%d @ %d,%d, r=%dx%d @ %d,%d, space=%dx%d @ %d,%d",
+// spe->pos.w, spe->pos.h, spe->pos.x, spe->pos.y,
+// r.w, r.h, r.x, r.y,
+// spe->space.w, spe->space.h, spe->space.x, spe->space.y);
+
+ // Rescale from sub-space
+ if (sys->subplanes[i])
+ {
2022-12-08 21:49:23 +00:00
+ if ((ret = drmu_atomic_plane_add_fb(da, sys->subplanes[i], spe->fb,
+ drmu_rect_rescale(spe->pos, r, spe->space))) != 0) {
+ msg_Err(vd, "drmModeSetPlane for subplane %d failed: %s", i, strerror(-ret));
+ }
2022-12-08 21:49:23 +00:00
+ drmu_atomic_plane_add_alpha(da, sys->subplanes[i], (spe->alpha * DRMU_PLANE_ALPHA_OPAQUE) / (0xff * 0xff));
+ }
+ }
+
+ sys->display_set = da;
+
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s", __func__);
+#endif
+ return;
+
+fail:
+ drmu_fb_unref(&dfb);
+ drmu_atomic_unref(&da);
+}
+
+static void vd_drm_display(vout_display_t *vd, picture_t *p_pic,
+ subpicture_t *subpicture)
+{
+ vout_display_sys_t *const sys = vd->sys;
+
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s", __func__);
+#endif
+
+ drmu_atomic_queue(&sys->display_set);
+
+ if (subpicture)
+ subpicture_Delete(subpicture);
+ picture_Release(p_pic);
+ return;
+}
+
+static void subpic_cache_flush(vout_display_sys_t * const sys)
+{
+ for (unsigned int i = 0; i != SUBPICS_MAX; ++i) {
+ if (sys->subpics[i].pic != NULL) {
+ picture_Release(sys->subpics[i].pic);
+ sys->subpics[i].pic = NULL;
+ }
+ drmu_fb_unref(&sys->subpics[i].fb);
+ }
+}
+
+static void kill_pool(vout_display_sys_t * const sys)
+{
+ // Drop all cached subpics
+ subpic_cache_flush(sys);
+
+ if (sys->vlc_pic_pool != NULL) {
+ picture_pool_Release(sys->vlc_pic_pool);
+ sys->vlc_pic_pool = NULL;
+ }
+}
+
+// Actual picture pool for MMAL opaques is just a set of trivial containers
+static picture_pool_t *vd_drm_pool(vout_display_t *vd, unsigned count)
+{
+ vout_display_sys_t * const sys = vd->sys;
+
+#if TRACE_ALL
+ msg_Dbg(vd, "%s: fmt:%dx%d,sar:%d/%d; source:%dx%d", __func__,
+ vd->fmt.i_width, vd->fmt.i_height, vd->fmt.i_sar_num, vd->fmt.i_sar_den, vd->source.i_width, vd->source.i_height);
+#endif
+
+ if (sys->vlc_pic_pool == NULL) {
+ sys->vlc_pic_pool = picture_pool_NewFromFormat(&vd->fmt, count);
+ }
+ return sys->vlc_pic_pool;
+}
+
+// Copy format from *fmtp into vd->fmt and make any necessary adjustments to
+// ensure display (tweak chroma)
+static void
+set_format(vout_display_t * const vd, vout_display_sys_t * const sys, const video_format_t *const fmtp)
+{
+#if HAS_DRMPRIME
+ uint32_t drm_fmt;
+ uint64_t drm_mod;
+
+ // We think we can deal with the source format so set requested
+ // input format to source
+ vd->fmt = *fmtp;
+
+ if ((drm_fmt = drmu_format_vlc_to_drm_prime(fmtp->i_chroma, &drm_mod)) != 0 &&
+ drmu_plane_format_check(sys->dp, drm_fmt, drm_mod)) {
+ // Hurrah!
+ }
+ else
+#endif
+#if HAS_ZC_CMA
+ if (fmtp->i_chroma == VLC_CODEC_MMAL_OPAQUE) {
+ // Can't deal directly with opaque - but we can always convert it to ZC I420
+ vd->fmt.i_chroma = VLC_CODEC_MMAL_ZC_I420;
+ }
+ else
+#endif
+ if ((drm_fmt = drmu_format_vlc_to_drm(fmtp)) != 0 &&
+ drmu_plane_format_check(sys->dp, drm_fmt, 0)) {
+ // It is a format where simple byte copying works
+ }
+ else {
+ const vlc_fourcc_t *fallback = vlc_fourcc_IsYUV(fmtp->i_chroma) ?
+ vlc_fourcc_GetYUVFallback(fmtp->i_chroma) :
+ vlc_fourcc_GetRGBFallback(fmtp->i_chroma);
+
+ for (; *fallback; ++fallback) {
+ if (drmu_plane_format_check(sys->dp, drmu_format_vlc_chroma_to_drm(*fallback), 0))
+ break;
+ }
+
+ // no conversion - ask for something we know we can deal with
+ vd->fmt.i_chroma = *fallback ? *fallback : VLC_CODEC_I420;
+ }
+}
+
+
+static int vd_drm_control(vout_display_t *vd, int query, va_list args)
+{
+ vout_display_sys_t * const sys = vd->sys;
+ int ret = VLC_EGENERIC;
+
+ switch (query) {
+ case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
+ case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
+ if (configure_display(vd, vd->cfg, &vd->source) >= 0)
+ ret = VLC_SUCCESS;
+ break;
+
+ case VOUT_DISPLAY_CHANGE_ZOOM:
+ case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
+ case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
+ {
+ const vout_display_cfg_t * const cfg = va_arg(args, const vout_display_cfg_t *);
+
+ if (configure_display(vd, cfg, &vd->source) >= 0)
+ ret = VLC_SUCCESS;
+ break;
+ }
+
+ case VOUT_DISPLAY_RESET_PICTURES:
+ msg_Warn(vd, "Reset Pictures");
+ kill_pool(sys);
+ set_format(vd, sys, &vd->source);
+ ret = VLC_SUCCESS;
+ break;
+
+ default:
+ msg_Warn(vd, "Unknown control query %d", query);
+ break;
+ }
+
+ return ret;
+}
+
+static void CloseDrmVout(vout_display_t *vd)
+{
+ vout_display_sys_t *const sys = vd->sys;
+ unsigned int i;
+
+ msg_Dbg(vd, "<<< %s", __func__);
+
+ drmu_pool_delete(&sys->sub_fb_pool);
+ drmu_pool_delete(&sys->pic_pool);
+
+ for (i = 0; i != SUBPICS_MAX; ++i)
+ drmu_plane_unref(sys->subplanes + i);
+
+ kill_pool(sys);
+
+ drmu_plane_unref(&sys->dp);
+ drmu_output_unref(&sys->dout);
+ drmu_env_unref(&sys->du);
+
+ free(sys->subpic_chromas);
+ vd->info.subpicture_chromas = NULL;
+
+ vd->sys = NULL;
+ free(sys);
+#if TRACE_ALL
+ msg_Dbg(vd, ">>> %s", __func__);
+#endif
+}
+
+// VLC will take a list of subpic formats but it then ignores the fact it is a
+// list and picks the 1st one whether it is 'best' or indeed whether or not it
+// can use it. So we have to sort ourselves & have checked usablity.
+// Higher number, higher priority. 0 == Do not use.
+static int subpic_fourcc_usability(const vlc_fourcc_t fcc)
+{
+ switch (fcc) {
+ case VLC_CODEC_ARGB:
+ return 20;
+ case VLC_CODEC_RGBA:
+ return 22;
+ case VLC_CODEC_BGRA:
+ return 21;
+ case VLC_CODEC_YUVA:
+ return 40;
+ default:
+ break;
+ }
+ return 0;
+}
+
+// Sort in descending priority number
+static int subpic_fourcc_sort_cb(const void * a, const void * b)
+{
+ return subpic_fourcc_usability(*(vlc_fourcc_t *)b) - subpic_fourcc_usability(*(vlc_fourcc_t *)a);
+}
+
+static vlc_fourcc_t *
+subpic_make_chromas_from_drm(const uint32_t * const drm_chromas, const unsigned int n)
+{
+ vlc_fourcc_t * const c = (n == 0) ? NULL : calloc(n + 1, sizeof(*c));
+ vlc_fourcc_t * p = c;
+
+ if (c == NULL)
+ return NULL;
+
+ for (unsigned int j = 0; j != n; ++j) {
+ if ((*p = drmu_format_vlc_to_vlc(drm_chromas[j])) != 0)
+ ++p;
+ }
+
+ // Sort for our preferred order & remove any that would confuse VLC
+ qsort(c, p - c, sizeof(*c), subpic_fourcc_sort_cb);
+ while (p != c) {
+ if (subpic_fourcc_usability(p[-1]) != 0)
+ break;
+ *--p = 0;
+ }
+
+ if (p == c) {
+ free(c);
+ return NULL;
+ }
+
+ return c;
+}
+
2022-12-08 21:49:23 +00:00
+static int
+test_simple_plane_set(vout_display_t * const vd, vout_display_sys_t * const sys)
+{
+ drmu_atomic_t *da = drmu_atomic_new(sys->du);
+ drmu_fb_t *fb;
+ int rv = -ENOMEM;
+
+ if (da == NULL) {
+ msg_Warn(vd, "Failed to alloc test atomic");
+ goto fail;
+ }
+
+ if ((fb = drmu_pool_fb_new_dumb(sys->sub_fb_pool, 128, 128, drmu_format_vlc_chroma_to_drm(sys->subpic_chromas[0]))) == NULL) {
+ msg_Warn(vd, "Failed to alloc test FB");
+ goto fail;
+ }
+
+ if ((rv = drmu_atomic_plane_add_fb(da, sys->subplanes[0], fb, drmu_rect_wh(128, 128))) != 0) {
+ msg_Warn(vd, "Failed to add test FB to atomic");
+ goto fail;
+ }
+
+ if ((rv = drmu_atomic_commit(da, DRM_MODE_ATOMIC_TEST_ONLY)) != 0) {
+ msg_Warn(vd, "Failed to commit test FB");
+ goto fail;
+ }
+
+fail:
+ drmu_atomic_unref(&da);
+ drmu_fb_unref(&fb);
+ return rv;
+}
+
+static int OpenDrmVout(vlc_object_t *object)
+{
+ vout_display_t * const vd = (vout_display_t *)object;
+// video_format_t * const fmtp = &vd->fmt;
+ const video_format_t *const fmtp = &vd->source;
2023-03-29 17:41:09 +00:00
+ const uint32_t src_chroma = fmtp->i_chroma;
+ vout_display_sys_t *sys;
+ int ret = VLC_EGENERIC;
+ int rv;
+ msg_Info(vd, "<<< %s: Fmt=%4.4s", __func__, (const char *)&fmtp->i_chroma);
+
+// if (!var_InheritBool(vd, "fullscreen")) {
+// msg_Dbg(vd, ">>> %s: Not fullscreen", __func__);
+// return ret;
+// }
+
+ sys = calloc(1, sizeof(*sys));
+ if (!sys)
+ return VLC_ENOMEM;
+ vd->sys = sys;
+
+ sys->mode_id = -1;
+
+ {
+ const drmu_log_env_t log = {
+ .fn = drmu_log_vlc_cb,
+ .v = vd,
+ .max_level = DRMU_LOG_LEVEL_ALL
+ };
+
+ sys->du = drmu_env_new_xlease(&log);
+ if (sys->du == NULL) {
+ char * module_name = var_InheritString(vd, DRM_VOUT_MODULE_NAME);
+ sys->du = drmu_env_new_open(module_name, &log);
+ free(module_name);
+ if (sys->du == NULL)
+ goto fail;
+ }
+ }
+
+ drmu_env_restore_enable(sys->du);
+
+ if ((sys->dout = drmu_output_new(sys->du)) == NULL) {
+ msg_Err(vd, "Failed to allocate new drmu output");
+ goto fail;
+ }
+
+ drmu_output_modeset_allow(sys->dout, !var_InheritBool(vd, DRM_VOUT_NO_MODESET_NAME));
+ drmu_output_max_bpc_allow(sys->dout, !var_InheritBool(vd, DRM_VOUT_NO_MAX_BPC));
+
2023-03-28 20:02:15 +00:00
+ {
+ char * const display_name = var_InheritString(vd, DRM_VOUT_DISPLAY_NAME);
2023-03-28 20:02:15 +00:00
+ int qt_num = var_InheritInteger(vd, "qt-fullscreen-screennumber");
+ const char * conn_name = qt_num == 0 ? "HDMI-A-1" : qt_num == 1 ? "HDMI-A-2" : NULL;
+ const char * dname;
+
+ if (display_name && strcasecmp(display_name, "auto") != 0) {
+ if (strcasecmp(display_name, "hdmi-1") == 0)
+ conn_name = "HDMI-A-1";
+ else if (strcasecmp(display_name, "hdmi-2") == 0)
+ conn_name = "HDMI-A-2";
+ else
+ conn_name = display_name;
2023-03-28 20:02:15 +00:00
+ }
+
+ dname = conn_name != NULL ? conn_name : "<auto>";
+
+ if ((rv = drmu_output_add_output(sys->dout, conn_name)) != 0)
2023-03-28 20:02:15 +00:00
+ msg_Err(vd, "Failed to find output %s: %s", dname, strerror(-rv));
+ else
+ msg_Dbg(vd, "Using conn %s", dname);
+
+ free(display_name);
+
+ if (rv != 0)
2023-03-28 20:02:15 +00:00
+ goto fail;
+ }
+
+ if ((sys->sub_fb_pool = drmu_pool_new(sys->du, 10)) == NULL)
+ goto fail;
+ if ((sys->pic_pool = drmu_pool_new(sys->du, 5)) == NULL)
+ goto fail;
+
+ // This wants to be the primary
+ if ((sys->dp = drmu_output_plane_ref_primary(sys->dout)) == NULL)
+ goto fail;
+
+ for (unsigned int i = 0; i != SUBPICS_MAX; ++i) {
+ if ((sys->subplanes[i] = drmu_output_plane_ref_other(sys->dout)) == NULL) {
+ msg_Warn(vd, "Cannot allocate subplane %d", i);
+ break;
+ }
+ if (sys->subpic_chromas == NULL) {
+ unsigned int n;
+ const uint32_t * const drm_chromas = drmu_plane_formats(sys->subplanes[i], &n);
+ sys->subpic_chromas = subpic_make_chromas_from_drm(drm_chromas, n);
+ }
+ }
+
2022-12-08 21:49:23 +00:00
+ if (test_simple_plane_set(vd, sys) != 0) {
+ msg_Warn(vd, "Failed simple pic test");
+ goto fail;
+ }
+
+ vd->info = (vout_display_info_t){
+ .is_slow = false,
+ .has_double_click = false,
+ .needs_hide_mouse = false,
+ .has_pictures_invalid = true,
+ .subpicture_chromas = sys->subpic_chromas
+ };
+
+ vd->pool = vd_drm_pool;
+ vd->prepare = vd_drm_prepare;
+ vd->display = vd_drm_display;
+ vd->control = vd_drm_control;
+
+ sys->mode_id = -1;
+
+ char * mode_name = NULL;
+ const char * modestr;
+
+ if (var_InheritBool(vd, DRM_VOUT_SOURCE_MODESET_NAME))
+ modestr = "source";
+ else {
+ mode_name = var_InheritString(vd, DRM_VOUT_MODE_NAME);
+ modestr = mode_name;
+ }
+
+ if (modestr != NULL && strcmp(modestr, "none") != 0) {
+ drmu_mode_simple_params_t pick = {
+ .width = fmtp->i_visible_width,
+ .height = fmtp->i_visible_height,
+ .hz_x_1000 = fmtp->i_frame_rate_base == 0 ? 0 :
+ (unsigned int)(((uint64_t)fmtp->i_frame_rate * 1000) / fmtp->i_frame_rate_base),
+ };
+
+ if (strcmp(modestr, "source") != 0) {
+ unsigned int w, h, hz;
+ if (*drmu_util_parse_mode(modestr, &w, &h, &hz) != 0) {
+ msg_Err(vd, "Bad mode string: '%s'", modestr);
+ ret = VLC_EGENERIC;
+ goto fail;
+ }
+ if (w && h) {
+ pick.width = w;
+ pick.height = h;
+ }
+ if (hz)
+ pick.hz_x_1000 = hz;
+ }
+
+ sys->mode_id = drmu_output_mode_pick_simple(sys->dout, drmu_mode_pick_simple_cb, &pick);
+
+ msg_Dbg(vd, "Mode id=%d", sys->mode_id);
+
+ // This will set the mode on the crtc var but won't actually change the output
+ if (sys->mode_id >= 0) {
+ const drmu_mode_simple_params_t * mode;
+
+ drmu_output_mode_id_set(sys->dout, sys->mode_id);
+ mode = drmu_output_mode_simple_params(sys->dout);
+ msg_Info(vd, "Mode %d: %dx%d@%d.%03d %d/%d - req %dx%d@%d.%d", sys->mode_id,
+ mode->width, mode->height, mode->hz_x_1000 / 1000, mode->hz_x_1000 % 1000,
+ mode->sar.num, mode->sar.den, pick.width, pick.height, pick.hz_x_1000 / 1000, pick.hz_x_1000 % 1000);
+ }
+ }
+ free(mode_name);
+
+ set_format(vd, sys, fmtp);
+
+// vout_display_SetSizeAndSar(vd, drmu_crtc_width(sys->dc), drmu_crtc_height(sys->dc),
+// drmu_ufrac_vlc_to_rational(drmu_crtc_sar(sys->dc)));
+
2023-03-28 20:02:15 +00:00
+ {
+ char * const window_str = var_InheritString(vd, DRM_VOUT_WINDOW_NAME);
2023-03-28 20:02:15 +00:00
+ if (strcmp(window_str, "fullscreen") == 0) {
+ /* Leave req_win null */
+ msg_Dbg(vd, "Window: fullscreen");
+ }
+ else {
+ sys->req_win = str_to_rect(window_str);
+ if (sys->req_win.width != 0)
+ msg_Dbg(vd, "Window: %dx%d @ %d,%d",
+ sys->req_win.width, sys->req_win.height,
+ sys->req_win.x, sys->req_win.y);
+ else
+ msg_Warn(vd, "Window: '%s': cannot parse (usage: <w>x<h>+<x>+<y>) - using fullscreen", window_str);
+ }
+ free(window_str);
2023-03-28 20:02:15 +00:00
+ }
+
2023-03-29 17:41:09 +00:00
+ if (src_chroma != vd->fmt.i_chroma)
+ msg_Warn(vd, "Cannot display %s directly trying %s", drmu_log_fourcc(src_chroma), drmu_log_fourcc(vd->fmt.i_chroma));
+
+ set_display_windows(vd, sys);
+
+ configure_display(vd, vd->cfg, &vd->source);
+
+ return VLC_SUCCESS;
+
+fail:
+ CloseDrmVout(vd);
+ return ret;
+}
+
+vlc_module_begin()
+ set_shortname(N_("DRM vout"))
+ set_description(N_("DRM vout plugin"))
+ set_capability("vout display", 16) // 1 point better than ASCII art
+ add_shortcut("drm_vout")
+ set_category(CAT_VIDEO)
+ set_subcategory(SUBCAT_VIDEO_VOUT)
+
+ add_bool(DRM_VOUT_SOURCE_MODESET_NAME, false, DRM_VOUT_SOURCE_MODESET_TEXT, DRM_VOUT_SOURCE_MODESET_LONGTEXT, false)
+ add_bool(DRM_VOUT_NO_MODESET_NAME, false, DRM_VOUT_NO_MODESET_TEXT, DRM_VOUT_NO_MODESET_LONGTEXT, false)
+ add_bool(DRM_VOUT_NO_MAX_BPC, false, DRM_VOUT_NO_MAX_BPC_TEXT, DRM_VOUT_NO_MAX_BPC_LONGTEXT, false)
+ add_string(DRM_VOUT_MODE_NAME, "none", DRM_VOUT_MODE_TEXT, DRM_VOUT_MODE_LONGTEXT, false)
2023-03-28 20:02:15 +00:00
+ add_string(DRM_VOUT_WINDOW_NAME, "fullscreen", DRM_VOUT_WINDOW_TEXT, DRM_VOUT_WINDOW_LONGTEXT, false)
+ add_string(DRM_VOUT_DISPLAY_NAME, "auto", DRM_VOUT_DISPLAY_TEXT, DRM_VOUT_DISPLAY_LONGTEXT, false)
+ add_string(DRM_VOUT_MODULE_NAME, DRM_MODULE, DRM_VOUT_MODULE_TEXT, DRM_VOUT_MODULE_LONGTEXT, false)
+
+ set_callbacks(OpenDrmVout, CloseDrmVout)
+vlc_module_end()
+
diff --git a/modules/video_output/drmu/drmu.c b/modules/video_output/drmu/drmu.c
new file mode 100644
index 0000000000..e090feec5b
--- /dev/null
+++ b/modules/video_output/drmu/drmu.c
@@ -0,0 +1,3924 @@
2023-03-29 17:41:09 +00:00
+// Needed to ensure we get a 64-bit offset to mmap when mapping BOs
+#undef _FILE_OFFSET_BITS
+#define _FILE_OFFSET_BITS 64
+
+#include "drmu.h"
2023-03-29 17:41:09 +00:00
+#include "drmu_fmts.h"
+#include "drmu_log.h"
+
+#include <pthread.h>
+
+#include "pollqueue.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdatomic.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include <libdrm/drm.h>
+#include <libdrm/drm_mode.h>
+#include <libdrm/drm_fourcc.h>
+#include <xf86drm.h>
+
+#define TRACE_PROP_NEW 0
+
+#ifndef OPT_IO_CALLOC
+#define OPT_IO_CALLOC 0
+#endif
+
+#ifndef DRM_FORMAT_P030
+#define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0')
+#endif
+
+struct drmu_bo_env_s;
+struct drmu_atomic_q_s;
+static struct drmu_bo_env_s * env_boe(drmu_env_t * const du);
+static struct pollqueue * env_pollqueue(const drmu_env_t * const du);
+static struct drmu_atomic_q_s * env_atomic_q(drmu_env_t * const du);
+static int env_object_state_save(drmu_env_t * const du, const uint32_t obj_id, const uint32_t obj_type);
+
+// Update return value with a new one for cases where we don't stop on error
+static inline int rvup(int rv1, int rv2)
+{
+ return rv2 ? rv2 : rv1;
+}
+
+// Use io_alloc when allocating arrays to pass into ioctls.
+//
+// When debugging with valgrind use calloc rather than malloc otherwise arrays
+// set by ioctls that valgrind doesn't know about (e.g. all drm ioctls) will
+// still be full of 'undefined'.
+// For normal use malloc should be fine
+#if OPT_IO_CALLOC
+#define io_alloc(p, n) (uintptr_t)((p) = calloc((n), sizeof(*(p))))
+#else
+#define io_alloc(p, n) (uintptr_t)((p) = malloc((n) * sizeof(*(p))))
+#endif
+
+// Alloc retry helper
+static inline int
+retry_alloc_u32(uint32_t ** const pp, uint32_t * const palloc_count, uint32_t const new_count)
+{
+ if (new_count <= *palloc_count)
+ return 0;
+ free(*pp);
+ *palloc_count = 0;
+ if (io_alloc(*pp, new_count) == 0)
+ return -ENOMEM;
+ *palloc_count = new_count;
+ return 1;
+}
+
+drmu_ufrac_t
+drmu_ufrac_reduce(drmu_ufrac_t x)
+{
+ static const unsigned int primes[] = {2,3,5,7,11,13,17,19,23,29,31,UINT_MAX};
+ const unsigned int * p;
+
+ // Deal with specials
+ if (x.den == 0) {
+ x.num = 0;
+ return x;
+ }
+ if (x.num == 0) {
+ x.den = 1;
+ return x;
+ }
+
+ // Shortcut the 1:1 common case - also ensures the default loop terminates
+ if (x.num == x.den) {
+ x.num = 1;
+ x.den = 1;
+ return x;
+ }
+
+ // As num != den, (num/UINT_MAX == 0 || den/UINT_MAX == 0) must be true
+ // so loop will terminate
+ for (p = primes;; ++p) {
+ const unsigned int n = *p;
+ for (;;) {
+ const unsigned int xd = x.den / n;
+ const unsigned int xn = x.num / n;
+ if (xn == 0 || xd == 0)
+ return x;
+ if (xn * n != x.num || xd * n != x.den)
+ break;
+ x.num = xn;
+ x.den = xd;
+ }
+ }
+}
+
+//----------------------------------------------------------------------------
+//
+// propinfo
+
+typedef struct drmu_propinfo_s {
+ uint64_t val;
+ struct drm_mode_get_property prop;
+} drmu_propinfo_t;
+
+static uint64_t
+propinfo_val(const drmu_propinfo_t * const pi)
+{
+ return pi == NULL ? 0 : pi->val;
+}
+
+static uint32_t
+propinfo_prop_id(const drmu_propinfo_t * const pi)
+{
+ return pi == NULL ? 0 : pi->prop.prop_id;
+}
+
+
+//----------------------------------------------------------------------------
+//
+// Blob fns
+
+typedef struct drmu_blob_s {
+ atomic_int ref_count; // 0 == 1 ref for ease of init
+ struct drmu_env_s * du;
+ uint32_t blob_id;
+ // Copy of blob data as we nearly always want to keep a copy to compare
+ size_t len;
+ void * data;
+} drmu_blob_t;
+
+static void
+blob_free(drmu_blob_t * const blob)
+{
+ drmu_env_t * const du = blob->du;
+
+ if (blob->blob_id != 0) {
+ struct drm_mode_destroy_blob dblob = {
+ .blob_id = blob->blob_id
+ };
+ if (drmu_ioctl(du, DRM_IOCTL_MODE_DESTROYPROPBLOB, &dblob) != 0)
+ drmu_err(du, "%s: Failed to destroy blob: %s", __func__, strerror(errno));
+ }
+ free(blob->data);
+ free(blob);
+}
+
+void
+drmu_blob_unref(drmu_blob_t ** const ppBlob)
+{
+ drmu_blob_t * const blob = *ppBlob;
+
+ if (blob == NULL)
+ return;
+ *ppBlob = NULL;
+
+ if (atomic_fetch_sub(&blob->ref_count, 1) != 0)
+ return;
+
+ blob_free(blob);
+}
+
+uint32_t
+drmu_blob_id(const drmu_blob_t * const blob)
+{
+ return blob == NULL ? 0 : blob->blob_id;
+}
+
+drmu_blob_t *
+drmu_blob_ref(drmu_blob_t * const blob)
+{
+ if (blob != NULL)
+ atomic_fetch_add(&blob->ref_count, 1);
+ return blob;
+}
+
+const void *
+drmu_blob_data(const drmu_blob_t * const blob)
+{
+ return blob->data;
+}
+
+size_t
+drmu_blob_len(const drmu_blob_t * const blob)
+{
+ return blob->len;
+}
+
+drmu_blob_t *
+drmu_blob_new(drmu_env_t * const du, const void * const data, const size_t len)
+{
+ int rv;
+ drmu_blob_t * blob = calloc(1, sizeof(*blob));
+ struct drm_mode_create_blob cblob = {
+ .data = (uintptr_t)data,
+ .length = (uint32_t)len,
+ .blob_id = 0
+ };
+
+ if (blob == NULL) {
+ drmu_err(du, "%s: Unable to alloc blob", __func__);
+ return NULL;
+ }
+ blob->du = du;
+
+ if ((blob->data = malloc(len)) == NULL) {
+ drmu_err(du, "%s: Unable to alloc blob data", __func__);
+ goto fail;
+ }
+ blob->len = len;
+ memcpy(blob->data, data, len);
+
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_CREATEPROPBLOB, &cblob)) != 0) {
+ drmu_err(du, "%s: Unable to create blob: data=%p, len=%zu: %s", __func__,
+ data, len, strerror(-rv));
+ goto fail;
+ }
+
+ atomic_init(&blob->ref_count, 0);
+ blob->blob_id = cblob.blob_id;
+ return blob;
+
+fail:
+ blob_free(blob);
+ return NULL;
+}
+
+int
+drmu_blob_update(drmu_env_t * const du, drmu_blob_t ** const ppblob, const void * const data, const size_t len)
+{
+ drmu_blob_t * blob = *ppblob;
+
+ if (data == NULL || len == 0) {
+ drmu_blob_unref(ppblob);
+ return 0;
+ }
+
+ if (blob && len == blob->len && memcmp(data, blob->data, len) == 0)
+ return 0;
+
+ if ((blob = drmu_blob_new(du, data, len)) == NULL)
+ return -ENOMEM;
+ drmu_blob_unref(ppblob);
+ *ppblob = blob;
+ return 0;
+}
+
+// Data alloced here needs freeing later
+static int
+blob_data_read(drmu_env_t * const du, uint32_t blob_id, void ** const ppdata, size_t * plen)
+{
+ uint8_t * data;
+ struct drm_mode_get_blob gblob = {.blob_id = blob_id};
+ int rv;
+
+ *ppdata = NULL;
+ *plen = 0;
+
+ if (blob_id == 0)
+ return 0;
+
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPBLOB, &gblob)) != 0)
+ return rv;
+
+ if (gblob.length == 0)
+ return 0;
+
+ if ((gblob.data = io_alloc(data, gblob.length)) == 0)
+ return -ENOMEM;
+
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPBLOB, &gblob)) != 0) {
+ free(data);
+ return rv;
+ }
+
+ *ppdata = data;
+ *plen = gblob.length;
+ return 0;
+}
+
+// Copy existing blob into a new one
+// Useful when saving preexisiting values
+drmu_blob_t *
+drmu_blob_copy_id(drmu_env_t * const du, uint32_t blob_id)
+{
+ void * data;
+ size_t len;
+ drmu_blob_t * blob = NULL;
+
+ if (blob_data_read(du, blob_id, &data, &len) == 0)
+ blob = drmu_blob_new(du, data, len); // * This copies data - could just get it to take the malloc
+
+ free(data);
+ return blob;
+}
+
+static void
+atomic_prop_blob_unref(void * v)
+{
+ drmu_blob_t * blob = v;
+ drmu_blob_unref(&blob);
+}
+
+static void
+atomic_prop_blob_ref(void * v)
+{
+ drmu_blob_ref(v);
+}
+
+int
+drmu_atomic_add_prop_blob(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_blob_t * const blob)
+{
+ int rv;
+ static const drmu_atomic_prop_fns_t fns = {
+ .ref = atomic_prop_blob_ref,
+ .unref = atomic_prop_blob_unref,
+ .commit = drmu_prop_fn_null_commit
+ };
+
+ if (blob == NULL)
+ return drmu_atomic_add_prop_value(da, obj_id, prop_id, 0);
+
+ rv = drmu_atomic_add_prop_generic(da, obj_id, prop_id, drmu_blob_id(blob), &fns, blob);
+ if (rv != 0)
+ drmu_warn(drmu_atomic_env(da), "%s: Failed to add blob obj_id=%#x, prop_id=%#x: %s", __func__, obj_id, prop_id, strerror(-rv));
+
+ return rv;
+}
+
+//----------------------------------------------------------------------------
+//
+// Enum fns
+
+typedef struct drmu_prop_enum_s {
+ uint32_t id;
+ uint32_t flags;
+ unsigned int n;
+ const struct drm_mode_property_enum * enums;
+ char name[DRM_PROP_NAME_LEN];
+} drmu_prop_enum_t;
+
+static void
+prop_enum_free(drmu_prop_enum_t * const pen)
+{
+ free((void*)pen->enums); // Cast away const
+ free(pen);
+}
+
+static int
+prop_enum_qsort_cb(const void * va, const void * vb)
+{
+ const struct drm_mode_property_enum * a = va;
+ const struct drm_mode_property_enum * b = vb;
+ return strcmp(a->name, b->name);
+}
+
+// NULL if not found
+const uint64_t *
+drmu_prop_enum_value(const drmu_prop_enum_t * const pen, const char * const name)
+{
+ if (pen != NULL && name != NULL) {
+ unsigned int i = pen->n / 2;
+ unsigned int a = 0;
+ unsigned int b = pen->n;
+
+ if (name == NULL)
+ return NULL;
+
+ while (a < b) {
+ const int r = strcmp(name, pen->enums[i].name);
+
+ if (r == 0)
+ return (const uint64_t *)&pen->enums[i].value; // __u64 defn != uint64_t defn always :-(
+
+ if (r < 0) {
+ b = i;
+ i = (i + a) / 2;
+ } else {
+ a = i + 1;
+ i = (i + b) / 2;
+ }
+ }
+ }
+ return NULL;
+}
+
+uint64_t
+drmu_prop_bitmask_value(const drmu_prop_enum_t * const pen, const char * const name)
+{
+ const uint64_t *const p = drmu_prop_enum_value(pen, name);
+ return p == NULL || *p >= 64 || (pen->flags & DRM_MODE_PROP_BITMASK) == 0 ?
+ (uint64_t)0 : (uint64_t)1 << *p;
+}
+
+uint32_t
+drmu_prop_enum_id(const drmu_prop_enum_t * const pen)
+{
+ return pen == NULL ? 0 : pen->id;
+}
+
+void
+drmu_prop_enum_delete(drmu_prop_enum_t ** const pppen)
+{
+ drmu_prop_enum_t * const pen = *pppen;
+ if (pen == NULL)
+ return;
+ *pppen = NULL;
+
+ prop_enum_free(pen);
+}
+
+drmu_prop_enum_t *
+drmu_prop_enum_new(drmu_env_t * const du, const uint32_t id)
+{
+ drmu_prop_enum_t * pen;
+ struct drm_mode_property_enum * enums = NULL;
+ unsigned int retries;
+ int rv;
+
+ // If id 0 return without warning for ease of getting props on init
+ if (id == 0 || (pen = calloc(1, sizeof(*pen))) == NULL)
+ return NULL;
+ pen->id = id;
+
+ // Docn says we must loop till stable as there may be hotplug races
+ for (retries = 0; retries < 8; ++retries) {
+ struct drm_mode_get_property prop = {
+ .prop_id = id,
+ .count_enum_blobs = pen->n,
+ .enum_blob_ptr = (uintptr_t)enums
+ };
+
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPERTY, &prop)) != 0) {
+ drmu_err(du, "%s: get property failed: %s", __func__, strerror(-rv));
+ goto fail;
+ }
+
+ if (prop.count_enum_blobs == 0 ||
+ (prop.flags & (DRM_MODE_PROP_ENUM | DRM_MODE_PROP_BITMASK)) == 0) {
+ drmu_err(du, "%s: not an enum: flags=%#x, enums=%d", __func__, prop.flags, prop.count_enum_blobs);
+ goto fail;
+ }
+
+ if (pen->n >= prop.count_enum_blobs) {
+ pen->flags = prop.flags;
+ pen->n = prop.count_enum_blobs;
+ memcpy(pen->name, prop.name, sizeof(pen->name));
+ break;
+ }
+
+ free(enums);
+
+ pen->n = prop.count_enum_blobs;
+ if (io_alloc(enums, pen->n) == 0)
+ goto fail;
+ }
+ if (retries >= 8) {
+ drmu_err(du, "%s: Too many retries", __func__);
+ goto fail;
+ }
+
+ qsort(enums, pen->n, sizeof(*enums), prop_enum_qsort_cb);
+ pen->enums = enums;
+
+#if TRACE_PROP_NEW
+ if (!pen->n) {
+ drmu_info(du, "%32s %2d: no properties");
+ }
+ else {
+ unsigned int i;
+ for (i = 0; i != pen->n; ++i) {
+ drmu_info(du, "%32s %2d:%02d: %32s %#"PRIx64, pen->name, pen->id, i, pen->enums[i].name, pen->enums[i].value);
+ }
+ }
+#endif
+
+ return pen;
+
+fail:
+ free(enums);
+ prop_enum_free(pen);
+ return NULL;
+}
+
+int
+drmu_atomic_add_prop_enum(drmu_atomic_t * const da, const uint32_t obj_id, const drmu_prop_enum_t * const pen, const char * const name)
+{
+ const uint64_t * const pval = drmu_prop_enum_value(pen, name);
+ int rv;
+
+ rv = (pval == NULL) ? -EINVAL :
+ drmu_atomic_add_prop_generic(da, obj_id, drmu_prop_enum_id(pen), *pval, NULL, NULL);
+
+ if (rv != 0 && name != NULL)
+ drmu_warn(drmu_atomic_env(da), "%s: Failed to add enum obj_id=%#x, prop_id=%#x, name='%s': %s", __func__,
+ obj_id, drmu_prop_enum_id(pen), name, strerror(-rv));
+
+ return rv;
+}
+
+int
+drmu_atomic_add_prop_bitmask(struct drmu_atomic_s * const da, const uint32_t obj_id, const drmu_prop_enum_t * const pen, const uint64_t val)
+{
+ int rv;
+
+ rv = !pen ? -ENOENT :
+ ((pen->flags & DRM_MODE_PROP_BITMASK) == 0) ? -EINVAL :
+ drmu_atomic_add_prop_generic(da, obj_id, drmu_prop_enum_id(pen), val, NULL, NULL);
+
+ if (rv != 0)
+ drmu_warn(drmu_atomic_env(da), "%s: Failed to add bitmask obj_id=%#x, prop_id=%#x, val=%#"PRIx64": %s", __func__,
+ obj_id, drmu_prop_enum_id(pen), val, strerror(-rv));
+
+ return rv;
+}
+
+//----------------------------------------------------------------------------
+//
+// Range
+
+typedef struct drmu_prop_range_s {
+ uint32_t id;
+ uint32_t flags;
+ uint64_t range[2];
+ char name[DRM_PROP_NAME_LEN];
+} drmu_prop_range_t;
+
+static void
+prop_range_free(drmu_prop_range_t * const pra)
+{
+ free(pra);
+}
+
+void
+drmu_prop_range_delete(drmu_prop_range_t ** pppra)
+{
+ drmu_prop_range_t * const pra = *pppra;
+
+ if (pra == NULL)
+ return;
+ *pppra = NULL;
+
+ prop_range_free(pra);
+}
+
+bool
+drmu_prop_range_validate(const drmu_prop_range_t * const pra, const uint64_t x)
+{
+ if (pra == NULL)
+ return false;
+ if ((pra->flags & DRM_MODE_PROP_EXTENDED_TYPE) == DRM_MODE_PROP_SIGNED_RANGE) {
+ return (int64_t)pra->range[0] <= (int64_t)x && (int64_t)pra->range[1] >= (int64_t)x;
+ }
+ return pra->range[0] <= x && pra->range[1] >= x;
+}
+
2023-03-29 17:41:09 +00:00
+bool
+drmu_prop_range_immutable(const drmu_prop_range_t * const pra)
+{
+ return !pra || (pra->flags & DRM_MODE_PROP_IMMUTABLE) != 0;
+}
+
+uint64_t
+drmu_prop_range_max(const drmu_prop_range_t * const pra)
+{
+ return pra == NULL ? 0 : pra->range[1];
+}
+
+uint64_t
+drmu_prop_range_min(const drmu_prop_range_t * const pra)
+{
+ return pra == NULL ? 0 : pra->range[0];
+}
+
+uint32_t
+drmu_prop_range_id(const drmu_prop_range_t * const pra)
+{
+ return pra == NULL ? 0 : pra->id;
+}
+
2023-03-29 17:41:09 +00:00
+const char *
+drmu_prop_range_name(const drmu_prop_range_t * const pra)
+{
+ return pra == NULL ? "{norange}" : pra->name;
+}
+
+drmu_prop_range_t *
+drmu_prop_range_new(drmu_env_t * const du, const uint32_t id)
+{
+ drmu_prop_range_t * pra;
+ int rv;
+
+ // If id 0 return without warning for ease of getting props on init
+ if (id == 0 || (pra = calloc(1, sizeof(*pra))) == NULL)
+ return NULL;
+ pra->id = id;
+
+ // We are expecting exactly 2 values so no need to loop
+ {
+ struct drm_mode_get_property prop = {
+ .prop_id = id,
+ .count_values = 2,
+ .values_ptr = (uintptr_t)pra->range
+ };
+
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPERTY, &prop)) != 0) {
+ drmu_err(du, "%s: get property failed: %s", __func__, strerror(-rv));
+ goto fail;
+ }
+
+ if ((prop.flags & DRM_MODE_PROP_RANGE) == 0 &&
+ (prop.flags & DRM_MODE_PROP_EXTENDED_TYPE) != DRM_MODE_PROP_SIGNED_RANGE) {
+ drmu_err(du, "%s: not an signed range: flags=%#x", __func__, prop.flags);
+ goto fail;
+ }
+ if ((prop.count_values != 2)) {
+ drmu_err(du, "%s: unexpected count values: %d", __func__, prop.count_values);
+ goto fail;
+ }
+
+ pra->flags = prop.flags;
+ memcpy(pra->name, prop.name, sizeof(pra->name));
+ }
+
+#if TRACE_PROP_NEW
+ drmu_info(du, "%32s %2d: %"PRId64"->%"PRId64, pra->name, pra->id, pra->range[0], pra->range[1]);
+#endif
+
+ return pra;
+
+fail:
+ prop_range_free(pra);
+ return NULL;
+}
+
+int
+drmu_atomic_add_prop_range(drmu_atomic_t * const da, const uint32_t obj_id, const drmu_prop_range_t * const pra, const uint64_t x)
+{
+ int rv;
+
+ rv = !pra ? -ENOENT :
+ !drmu_prop_range_validate(pra, x) ? -EINVAL :
2023-03-29 17:41:09 +00:00
+ drmu_prop_range_immutable(pra) ? -EPERM :
+ drmu_atomic_add_prop_generic(da, obj_id, drmu_prop_range_id(pra), x, NULL, NULL);
+
+ if (rv != 0)
2023-03-29 17:41:09 +00:00
+ drmu_warn(drmu_atomic_env(da),
+ "%s: Failed to add range %s obj_id=%#x, prop_id=%#x, val=%"PRId64", range=%"PRId64"->%"PRId64": %s",
+ __func__, drmu_prop_range_name(pra),
+ obj_id, drmu_prop_range_id(pra), x, drmu_prop_range_min(pra), drmu_prop_range_max(pra), strerror(-rv));
+
+ return rv;
+}
+
+//----------------------------------------------------------------------------
+//
+// Object ID (tracked)
+
+typedef struct drmu_prop_object_s {
+ atomic_int ref_count;
+ uint32_t obj_id;
+ uint32_t prop_id;
+ uint32_t value;
+} drmu_prop_object_t;
+
+uint32_t
+drmu_prop_object_value(const drmu_prop_object_t * const obj)
+{
+ return !obj ? 0 : obj->value;
+}
+
+void
+drmu_prop_object_unref(drmu_prop_object_t ** ppobj)
+{
+ drmu_prop_object_t * const obj = *ppobj;
+
+ if (!obj)
+ return;
+ *ppobj = NULL;
+
+ if (atomic_fetch_sub(&obj->ref_count, 1) != 0)
+ return;
+
+ free(obj);
+}
+
+drmu_prop_object_t *
+drmu_prop_object_new_propinfo(drmu_env_t * const du, const uint32_t obj_id, const drmu_propinfo_t * const pi)
+{
+ const uint64_t val = propinfo_val(pi);
+ const uint32_t prop_id = propinfo_prop_id(pi);
+
+ if (obj_id == 0 || prop_id == 0)
+ return NULL;
+
+ if ((val >> 32) != 0) { // We expect 32-bit values
+ drmu_err(du, "Bad object id value: %#"PRIx64, val);
+ return NULL;
+ }
+ else {
+ drmu_prop_object_t *const obj = calloc(1, sizeof(*obj));
+
+ if (obj == NULL)
+ return obj;
+
+ obj->obj_id = obj_id;
+ obj->prop_id = prop_id;
+ obj->value = (uint32_t)val;
+ return obj;
+ }
+}
+
+static void
+atomic_prop_object_unref(void * v)
+{
+ drmu_prop_object_t * obj = v;
+ drmu_prop_object_unref(&obj);
+}
+static void
+atomic_prop_object_ref(void * v)
+{
+ drmu_prop_object_t * obj = v;
+ atomic_fetch_add(&obj->ref_count, 1);
+}
+static void
+atomic_prop_object_commit(void * v, uint64_t val)
+{
+ drmu_prop_object_t * obj = v;
+ obj->value = (uint32_t)val;
+}
+
+int
+drmu_atomic_add_prop_object(drmu_atomic_t * const da, drmu_prop_object_t * obj, uint32_t val)
+{
+ static const drmu_atomic_prop_fns_t fns = {
+ .ref = atomic_prop_object_ref,
+ .unref = atomic_prop_object_unref,
+ .commit = atomic_prop_object_commit,
+ };
+
+ return drmu_atomic_add_prop_generic(da, obj->obj_id, obj->prop_id, val, &fns, obj);
+}
+
+//----------------------------------------------------------------------------
+//
+// BO fns
+
+enum drmu_bo_type_e {
+ BO_TYPE_NONE = 0,
+ BO_TYPE_FD,
+ BO_TYPE_DUMB
+};
+
+// BO handles come in 2 very distinct types: DUMB and FD
+// They need very different alloc & free but BO usage is the same for both
+// so it is better to have a single type.
+typedef struct drmu_bo_s {
+ // Arguably could be non-atomic for FD as then it is always protected by mutex
+ atomic_int ref_count;
+ struct drmu_env_s * du;
+ enum drmu_bo_type_e bo_type;
+ uint32_t handle;
+
+ // FD only els - FD BOs need to be tracked globally
+ struct drmu_bo_s * next;
+ struct drmu_bo_s * prev;
+} drmu_bo_t;
+
+typedef struct drmu_bo_env_s {
+ pthread_mutex_t lock;
+ drmu_bo_t * fd_head;
+} drmu_bo_env_t;
+
+static int
+bo_close(drmu_env_t * const du, uint32_t * const ph)
+{
+ struct drm_gem_close gem_close = {.handle = *ph};
+
+ if (gem_close.handle == 0)
+ return 0;
+ *ph = 0;
+
+ return drmu_ioctl(du, DRM_IOCTL_GEM_CLOSE, &gem_close);
+}
+
+// BOE lock expected
+static void
+bo_free_dumb(drmu_bo_t * const bo)
+{
+ if (bo->handle != 0) {
+ drmu_env_t * const du = bo->du;
+ struct drm_mode_destroy_dumb destroy_env = {.handle = bo->handle};
+
+ if (drmu_ioctl(du, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_env) != 0)
+ drmu_warn(du, "%s: Failed to destroy dumb handle %d", __func__, bo->handle);
+ }
+ free(bo);
+}
+
+static void
+bo_free_fd(drmu_bo_t * const bo)
+{
+ if (bo->handle != 0) {
+ drmu_env_t * const du = bo->du;
+ drmu_bo_env_t *const boe = env_boe(du);
+ const uint32_t h = bo->handle;
+
+ if (bo_close(du, &bo->handle) != 0)
+ drmu_warn(du, "%s: Failed to close BO handle %d", __func__, h);
+ if (bo->next != NULL)
+ bo->next->prev = bo->prev;
+ if (bo->prev != NULL)
+ bo->prev->next = bo->next;
+ else
+ boe->fd_head = bo->next;
+ }
+ free(bo);
+}
+
+
+void
+drmu_bo_unref(drmu_bo_t ** const ppbo)
+{
+ drmu_bo_t * const bo = *ppbo;
+
+ if (bo == NULL)
+ return;
+ *ppbo = NULL;
+
+ switch (bo->bo_type) {
+ case BO_TYPE_FD:
+ {
+ drmu_bo_env_t * const boe = env_boe(bo->du);
+
+ pthread_mutex_lock(&boe->lock);
+ if (atomic_fetch_sub(&bo->ref_count, 1) == 0)
+ bo_free_fd(bo);
+ pthread_mutex_unlock(&boe->lock);
+ break;
+ }
+ case BO_TYPE_DUMB:
+ if (atomic_fetch_sub(&bo->ref_count, 1) == 0)
+ bo_free_dumb(bo);
+ break;
+ case BO_TYPE_NONE:
+ default:
+ free(bo);
+ break;
+ }
+}
+
+
+drmu_bo_t *
+drmu_bo_ref(drmu_bo_t * const bo)
+{
+ if (bo != NULL)
+ atomic_fetch_add(&bo->ref_count, 1);
+ return bo;
+}
+
+static drmu_bo_t *
+bo_alloc(drmu_env_t *const du, enum drmu_bo_type_e bo_type)
+{
+ drmu_bo_t * const bo = calloc(1, sizeof(*bo));
+ if (bo == NULL) {
+ drmu_err(du, "Failed to alloc BO");
+ return NULL;
+ }
+
+ bo->du = du;
+ bo->bo_type = bo_type;
+ atomic_init(&bo->ref_count, 0);
+ return bo;
+}
+
+drmu_bo_t *
+drmu_bo_new_fd(drmu_env_t *const du, const int fd)
+{
+ drmu_bo_env_t * const boe = env_boe(du);
+ drmu_bo_t * bo = NULL;
+ struct drm_prime_handle ph = { .fd = fd };
+ int rv;
+
+ pthread_mutex_lock(&boe->lock);
+
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_PRIME_FD_TO_HANDLE, &ph)) != 0) {
+ drmu_err(du, "Failed to convert fd %d to BO: %s", __func__, fd, strerror(-rv));
+ goto unlock;
+ }
+
+ bo = boe->fd_head;
+ while (bo != NULL && bo->handle != ph.handle)
+ bo = bo->next;
+
+ if (bo != NULL) {
+ drmu_bo_ref(bo);
+ }
+ else {
+ if ((bo = bo_alloc(du, BO_TYPE_FD)) == NULL) {
+ bo_close(du, &ph.handle);
+ }
+ else {
+ bo->handle = ph.handle;
+
+ if ((bo->next = boe->fd_head) != NULL)
+ bo->next->prev = bo;
+ boe->fd_head = bo;
+ }
+ }
+
+unlock:
+ pthread_mutex_unlock(&boe->lock);
+ return bo;
+}
+
+// Updates the passed dumb structure with the results of creation
+drmu_bo_t *
+drmu_bo_new_dumb(drmu_env_t *const du, struct drm_mode_create_dumb * const d)
+{
+ drmu_bo_t *bo = bo_alloc(du, BO_TYPE_DUMB);
+ int rv;
+
+ if (bo == NULL)
+ return NULL;
+
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_CREATE_DUMB, d)) != 0)
+ {
+ drmu_err(du, "%s: Create dumb %dx%dx%d failed: %s", __func__,
+ d->width, d->height, d->bpp, strerror(-rv));
+ drmu_bo_unref(&bo); // After this point aux is bound to dfb and gets freed with it
+ return NULL;
+ }
+
+ bo->handle = d->handle;
+ return bo;
+}
+
+void
+drmu_bo_env_uninit(drmu_bo_env_t * const boe)
+{
+ if (boe->fd_head != NULL)
+ drmu_warn(boe->fd_head->du, "%s: fd chain not null", __func__);
+ boe->fd_head = NULL;
+ pthread_mutex_destroy(&boe->lock);
+}
+
+void
+drmu_bo_env_init(drmu_bo_env_t * boe)
+{
+ boe->fd_head = NULL;
+ pthread_mutex_init(&boe->lock, NULL);
+}
+
+//----------------------------------------------------------------------------
+//
+// FB fns
+
+typedef struct drmu_fb_s {
+ atomic_int ref_count; // 0 == 1 ref for ease of init
+ struct drmu_fb_s * prev;
+ struct drmu_fb_s * next;
+
+ struct drmu_env_s * du;
+
2023-03-29 17:41:09 +00:00
+ const struct drmu_fmt_info_s * fmt_info;
+
+ struct drm_mode_fb_cmd2 fb;
+
+ drmu_rect_t active; // Area that was asked for inside the buffer; pixels
+ drmu_rect_t crop; // Cropping inside that; fractional pels (16.16, 16.16)
+
+ void * map_ptr;
+ size_t map_size;
+ size_t map_pitch;
+
+ drmu_bo_t * bo_list[4];
+
+ drmu_color_encoding_t color_encoding; // Assumed to be constant strings that don't need freeing
+ drmu_color_range_t color_range;
+ drmu_colorspace_t colorspace;
+ const char * pixel_blend_mode;
+ drmu_chroma_siting_t chroma_siting;
+ drmu_isset_t hdr_metadata_isset;
+ struct hdr_output_metadata hdr_metadata;
+
+ void * pre_delete_v;
+ drmu_fb_pre_delete_fn pre_delete_fn;
+
+ void * on_delete_v;
+ drmu_fb_on_delete_fn on_delete_fn;
+
+ // We pass a pointer to this to DRM which defines it as s32 so do not use
+ // int that might be s64.
+ int32_t fence_fd;
+} drmu_fb_t;
+
+int
+drmu_fb_out_fence_wait(drmu_fb_t * const fb, const int timeout_ms)
+{
+ struct pollfd pf;
+ int rv;
+
+ if (fb->fence_fd == -1)
+ return -EINVAL;
+
+ do {
+ pf.fd = fb->fence_fd;
+ pf.events = POLLIN;
+ pf.revents = 0;
+
+ rv = poll(&pf, 1, timeout_ms);
+ if (rv >= 0)
+ break;
+
+ rv = -errno;
+ } while (rv == -EINTR);
+
+ if (rv == 0)
+ return 0;
+
+ // Both on error & success close the fd
+ close(fb->fence_fd);
+ fb->fence_fd = -1;
+ return rv;
+}
+
+void
+drmu_fb_int_free(drmu_fb_t * const dfb)
+{
+ drmu_env_t * const du = dfb->du;
+ unsigned int i;
+
+ if (dfb->pre_delete_fn && dfb->pre_delete_fn(dfb, dfb->pre_delete_v) != 0)
+ return;
+
+ // * If we implement callbacks this logic will want revision
+ if (dfb->fence_fd != -1) {
+ drmu_warn(du, "Out fence still set on FB on delete");
+ if (drmu_fb_out_fence_wait(dfb, 500) == 0) {
+ drmu_err(du, "Out fence stuck in FB free");
+ close(dfb->fence_fd);
+ }
+ }
+
+ if (dfb->fb.fb_id != 0)
+ drmu_ioctl(du, DRM_IOCTL_MODE_RMFB, &dfb->fb.fb_id);
+
+ if (dfb->map_ptr != NULL && dfb->map_ptr != MAP_FAILED)
+ munmap(dfb->map_ptr, dfb->map_size);
+
+ for (i = 0; i != 4; ++i)
+ drmu_bo_unref(dfb->bo_list + i);
+
+ // Call on_delete last so we have stopped using anything that might be
+ // freed by it
2023-03-29 17:41:09 +00:00
+ {
+ void * const v = dfb->on_delete_v;
+ const drmu_fb_on_delete_fn fn = dfb->on_delete_fn;
+
2023-03-29 17:41:09 +00:00
+ free(dfb);
+
+ if (fn)
+ fn(v);
+ }
+}
+
+void
+drmu_fb_unref(drmu_fb_t ** const ppdfb)
+{
+ drmu_fb_t * const dfb = *ppdfb;
+
+ if (dfb == NULL)
+ return;
+ *ppdfb = NULL;
+
+ if (atomic_fetch_sub(&dfb->ref_count, 1) > 0)
+ return;
+
+ drmu_fb_int_free(dfb);
+}
+
+static void
+atomic_prop_fb_unref_cb(void * v)
+{
+ drmu_fb_t * dfb = v;
+ drmu_fb_unref(&dfb);
+}
+
+drmu_fb_t *
+drmu_fb_ref(drmu_fb_t * const dfb)
+{
+ if (dfb != NULL)
+ atomic_fetch_add(&dfb->ref_count, 1);
+ return dfb;
+}
+
+static void
+atomic_prop_fb_ref_cb(void * v)
+{
+ drmu_fb_ref(v);
+}
+
+// Beware: used by pool fns
+void
+drmu_fb_pre_delete_set(drmu_fb_t *const dfb, drmu_fb_pre_delete_fn fn, void * v)
+{
+ dfb->pre_delete_fn = fn;
+ dfb->pre_delete_v = v;
+}
+
+void
+drmu_fb_pre_delete_unset(drmu_fb_t *const dfb)
+{
+ dfb->pre_delete_fn = (drmu_fb_pre_delete_fn)0;
+ dfb->pre_delete_v = NULL;
+}
+
+int
+drmu_fb_pixel_blend_mode_set(drmu_fb_t *const dfb, const char * const mode)
+{
+ dfb->pixel_blend_mode = mode;
+ return 0;
+}
+
+uint32_t
+drmu_fb_pitch(const drmu_fb_t *const dfb, const unsigned int layer)
+{
+ return layer >= 4 ? 0 : dfb->fb.pitches[layer];
+}
+
+uint32_t
+drmu_fb_pitch2(const drmu_fb_t *const dfb, const unsigned int layer)
+{
+ if (layer < 4){
+ const uint64_t m = dfb->fb.modifier[layer];
+ const uint64_t s2 = fourcc_mod_broadcom_param(m);
+
+ // No good masks to check modifier so check if we convert back it matches
+ if (m != 0 && m != DRM_FORMAT_MOD_INVALID &&
+ DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(s2) == m)
+ return (uint32_t)s2;
+ }
+ return 0;
+}
+
+void *
+drmu_fb_data(const drmu_fb_t *const dfb, const unsigned int layer)
+{
+ return (layer >= 4 || dfb->map_ptr == NULL) ? NULL : (uint8_t * )dfb->map_ptr + dfb->fb.offsets[layer];
+}
+
+uint32_t
+drmu_fb_width(const drmu_fb_t *const dfb)
+{
+ return dfb->fb.width;
+}
+
+uint32_t
+drmu_fb_height(const drmu_fb_t *const dfb)
+{
+ return dfb->fb.height;
+}
+
+// Set cropping (fractional) - x, y, relative to active x, y (and must be +ve)
+int
+drmu_fb_crop_frac_set(drmu_fb_t *const dfb, drmu_rect_t crop_frac)
+{
+ // Sanity check
+ if (crop_frac.x + crop_frac.w > (dfb->active.w << 16) ||
+ crop_frac.y + crop_frac.h > (dfb->active.h << 16))
+ return -EINVAL;
+
+ dfb->crop = (drmu_rect_t){
+ .x = crop_frac.x,
+ .y = crop_frac.y,
+ .w = crop_frac.w,
+ .h = crop_frac.h
+ };
+ return 0;
+}
+
+drmu_rect_t
+drmu_fb_crop_frac(const drmu_fb_t *const dfb)
+{
+ return dfb->crop;
+}
+
+drmu_rect_t
+drmu_fb_active(const drmu_fb_t *const dfb)
+{
+ return dfb->active;
+}
+
+
+// active is in pixels
+void
+drmu_fb_int_fmt_size_set(drmu_fb_t *const dfb, uint32_t fmt, uint32_t w, uint32_t h, const drmu_rect_t active)
+{
2023-03-29 17:41:09 +00:00
+ dfb->fmt_info = drmu_fmt_info_find_fmt(fmt);
+ dfb->fb.pixel_format = fmt;
+ dfb->fb.width = w;
+ dfb->fb.height = h;
+ dfb->active = active;
2023-03-28 20:02:15 +00:00
+ dfb->crop = drmu_rect_shl16(active);
2023-03-29 17:41:09 +00:00
+ dfb->chroma_siting = drmu_fmt_info_chroma_siting(dfb->fmt_info);
+}
+
+void
+drmu_fb_int_color_set(drmu_fb_t *const dfb, const drmu_color_encoding_t enc, const drmu_color_range_t range, const drmu_colorspace_t space)
+{
+ dfb->color_encoding = enc;
+ dfb->color_range = range;
+ dfb->colorspace = space;
+}
+
+void
+drmu_fb_int_chroma_siting_set(drmu_fb_t *const dfb, const drmu_chroma_siting_t siting)
+{
+ dfb->chroma_siting = siting;
+}
+
+void
+drmu_fb_int_on_delete_set(drmu_fb_t *const dfb, drmu_fb_on_delete_fn fn, void * v)
+{
+ dfb->on_delete_fn = fn;
+ dfb->on_delete_v = v;
+}
+
+void
+drmu_fb_int_bo_set(drmu_fb_t *const dfb, unsigned int i, drmu_bo_t * const bo)
+{
+ dfb->bo_list[i] = bo;
+}
+
+void
+drmu_fb_int_layer_mod_set(drmu_fb_t *const dfb, unsigned int i, unsigned int obj_idx, uint32_t pitch, uint32_t offset, uint64_t modifier)
+{
+ dfb->fb.handles[i] = dfb->bo_list[obj_idx]->handle;
+ dfb->fb.pitches[i] = pitch;
+ dfb->fb.offsets[i] = offset;
+ // We should be able to have "invalid" modifiers and not set the flag
+ // but that produces EINVAL - so don't do that
+ dfb->fb.modifier[i] = (modifier == DRM_FORMAT_MOD_INVALID) ? 0 : modifier;
+}
+
+void
+drmu_fb_int_layer_set(drmu_fb_t *const dfb, unsigned int i, unsigned int obj_idx, uint32_t pitch, uint32_t offset)
+{
+ drmu_fb_int_layer_mod_set(dfb, i, obj_idx, pitch, offset, DRM_FORMAT_MOD_INVALID);
+}
+
+int
+drmu_fb_int_make(drmu_fb_t *const dfb)
+{
+ drmu_env_t * du = dfb->du;
+ int rv;
+
+ dfb->fb.flags = (dfb->fb.modifier[0] == DRM_FORMAT_MOD_INVALID || dfb->fb.modifier[0] == 0) ? 0 : DRM_MODE_FB_MODIFIERS;
+
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_ADDFB2, &dfb->fb)) != 0)
+ drmu_err(du, "AddFB2 failed: %s", strerror(-rv));
+ return rv;
+}
+
+void
+drmu_fb_hdr_metadata_set(drmu_fb_t *const dfb, const struct hdr_output_metadata * meta)
+{
+ if (meta == NULL) {
+ dfb->hdr_metadata_isset = DRMU_ISSET_NULL;
+ }
+ else {
+ dfb->hdr_metadata_isset = DRMU_ISSET_SET;
+ dfb->hdr_metadata = *meta;
+ }
+}
+
+drmu_isset_t
+drmu_fb_hdr_metadata_isset(const drmu_fb_t *const dfb)
+{
+ return dfb->hdr_metadata_isset;
+}
+
+const struct hdr_output_metadata *
+drmu_fb_hdr_metadata_get(const drmu_fb_t *const dfb)
+{
+ return dfb->hdr_metadata_isset == DRMU_ISSET_SET ? &dfb->hdr_metadata : NULL;
+}
+
+drmu_colorspace_t
+drmu_fb_colorspace_get(const drmu_fb_t * const dfb)
+{
+ return dfb->colorspace;
+}
+
+const char *
+drmu_color_range_to_broadcast_rgb(const drmu_color_range_t range)
+{
+ if (!drmu_color_range_is_set(range))
+ return DRMU_BROADCAST_RGB_UNSET;
+ else if (strcmp(range, DRMU_COLOR_RANGE_YCBCR_FULL_RANGE) == 0)
+ return DRMU_BROADCAST_RGB_FULL;
+ else if (strcmp(range, DRMU_COLOR_RANGE_YCBCR_LIMITED_RANGE) == 0)
+ return DRMU_BROADCAST_RGB_LIMITED_16_235;
+ return NULL;
+}
+
+drmu_color_range_t
+drmu_fb_color_range_get(const drmu_fb_t * const dfb)
+{
+ return dfb->color_range;
+}
+
2023-03-29 17:41:09 +00:00
+const struct drmu_fmt_info_s *
+drmu_fb_format_info_get(const drmu_fb_t * const dfb)
+{
+ return dfb->fmt_info;
+}
+
+drmu_fb_t *
+drmu_fb_int_alloc(drmu_env_t * const du)
+{
+ drmu_fb_t * const dfb = calloc(1, sizeof(*dfb));
+ if (dfb == NULL)
+ return NULL;
+
+ dfb->du = du;
+ dfb->chroma_siting = DRMU_CHROMA_SITING_UNSPECIFIED;
+ dfb->fence_fd = -1;
+ return dfb;
+}
+
+// Bits per pixel on plane 0
+unsigned int
+drmu_fb_pixel_bits(const drmu_fb_t * const dfb)
+{
2023-03-29 17:41:09 +00:00
+ return drmu_fmt_info_pixel_bits(dfb->fmt_info);
+}
+
2023-03-28 20:02:15 +00:00
+uint32_t
+drmu_fb_pixel_format(const drmu_fb_t * const dfb)
+{
+ return dfb->fb.pixel_format;
+}
+
+uint64_t
+drmu_fb_modifier(const drmu_fb_t * const dfb, const unsigned int plane)
+{
+ return plane >= 4 ? DRM_FORMAT_MOD_INVALID : dfb->fb.modifier[plane];
+}
+
+
+// Writeback fence
+// Must be unset before set again
+// (This is as a handy hint that you must wait for the previous fence
+// to go ready before you set a new one)
+static int
+atomic_fb_add_out_fence(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_fb_t * const dfb)
+{
+ static const drmu_atomic_prop_fns_t fns = {
+ .ref = atomic_prop_fb_ref_cb,
+ .unref = atomic_prop_fb_unref_cb,
+ .commit = drmu_prop_fn_null_commit,
+ };
+
+ if (!dfb)
+ return -EINVAL;
+ if (dfb->fence_fd != -1)
+ return -EBUSY;
+
+ return drmu_atomic_add_prop_generic(da, obj_id, prop_id, (uintptr_t)&dfb->fence_fd, &fns, dfb);
+}
+
+// For allocation purposes given fb_pixel bits how tall
+// does the frame have to be to fit all planes if constant width
+static unsigned int
+fb_total_height(const drmu_fb_t * const dfb, const unsigned int h)
+{
+ unsigned int i;
2023-03-29 17:41:09 +00:00
+ const drmu_fmt_info_t *const f = dfb->fmt_info;
+ unsigned int t = 0;
2023-03-29 17:41:09 +00:00
+ unsigned int h0 = h * drmu_fmt_info_wdiv(f, 0);
+ const unsigned int c = drmu_fmt_info_plane_count(f);
+
2023-03-29 17:41:09 +00:00
+ for (i = 0; i != c; ++i)
+ t += h0 / (drmu_fmt_info_hdiv(f, i) * drmu_fmt_info_wdiv(f, i));
+
+ return t;
+}
+
+static void
+fb_pitches_set_mod(drmu_fb_t * const dfb, uint64_t mod)
+{
2023-03-29 17:41:09 +00:00
+ const drmu_fmt_info_t *const f = dfb->fmt_info;
+ const uint32_t pitch0 = dfb->map_pitch * drmu_fmt_info_wdiv(f, 0);
+ const uint32_t h = drmu_fb_height(dfb);
2023-03-29 17:41:09 +00:00
+ const unsigned int c = drmu_fmt_info_plane_count(f);
+ uint32_t t = 0;
+ unsigned int i;
+
+ // This should be true for anything we've allocated
+ if (mod == DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(0)) {
+ // Cope with the joy that is sand
+ mod = DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(h * 3/2);
+ drmu_fb_int_layer_mod_set(dfb, 0, 0, dfb->map_pitch, 0, mod);
+ drmu_fb_int_layer_mod_set(dfb, 1, 0, dfb->map_pitch, h * 128, mod);
+ return;
+ }
+
2023-03-29 17:41:09 +00:00
+ for (i = 0; i != c; ++i) {
+ const unsigned int wdiv = drmu_fmt_info_wdiv(f, i);
+ drmu_fb_int_layer_mod_set(dfb, i, 0, pitch0 / wdiv, t, mod);
+ t += (pitch0 * h) / (drmu_fmt_info_hdiv(f, i) * wdiv);
+ }
+}
+
+drmu_fb_t *
+drmu_fb_new_dumb_mod(drmu_env_t * const du, uint32_t w, uint32_t h,
+ const uint32_t format, const uint64_t mod)
+{
+ drmu_fb_t * const dfb = drmu_fb_int_alloc(du);
+ uint32_t bpp;
+ int rv;
+ uint32_t w2;
+ const uint32_t s30_cw = 128 / 4 * 3;
+
+ if (dfb == NULL) {
+ drmu_err(du, "%s: Alloc failure", __func__);
+ return NULL;
+ }
+
+ if (mod != DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(0))
+ w2 = (w + 15) & ~15;
+ else if (format == DRM_FORMAT_NV12)
+ w2 = (w + 127) & ~127;
+ else if (format == DRM_FORMAT_P030)
+ w2 = ((w + s30_cw - 1) / s30_cw) * s30_cw;
+ else {
+ // Unknown form of sand128
+ drmu_err(du, "Sand modifier on unexpected format");
+ goto fail;
+ }
+
+ drmu_fb_int_fmt_size_set(dfb, format, w2, (h + 15) & ~15, drmu_rect_wh(w, h));
+
+ if ((bpp = drmu_fb_pixel_bits(dfb)) == 0) {
+ drmu_err(du, "%s: Unexpected format %#x", __func__, format);
+ goto fail;
+ }
+
+ {
+ struct drm_mode_create_dumb dumb = {
+ .height = fb_total_height(dfb, dfb->fb.height),
2023-03-29 17:41:09 +00:00
+ .width = dfb->fb.width / drmu_fmt_info_wdiv(dfb->fmt_info, 0),
+ .bpp = bpp
+ };
+
+ drmu_bo_t * bo = drmu_bo_new_dumb(du, &dumb);
+ if (bo == NULL)
+ goto fail;
+ drmu_fb_int_bo_set(dfb, 0, bo);
+
+ dfb->map_pitch = dumb.pitch;
+ dfb->map_size = (size_t)dumb.size;
+ }
+
+ {
+ struct drm_mode_map_dumb map_dumb = {
+ .handle = dfb->bo_list[0]->handle
+ };
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb)) != 0)
+ {
+ drmu_err(du, "%s: map dumb failed: %s", __func__, strerror(-rv));
+ goto fail;
+ }
+
+ if ((dfb->map_ptr = mmap(NULL, dfb->map_size,
+ PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE,
2023-03-29 17:41:09 +00:00
+ drmu_fd(du), map_dumb.offset)) == MAP_FAILED) {
+ drmu_err(du, "%s: mmap failed (size=%zd, fd=%d, off=%#"PRIx64"): %s", __func__,
+ dfb->map_size, drmu_fd(du), map_dumb.offset, strerror(errno));
+ goto fail;
+ }
+ }
+
+ fb_pitches_set_mod(dfb, mod);
+
+ if (drmu_fb_int_make(dfb))
+ goto fail;
+
+ drmu_debug(du, "Create dumb %p %s %dx%d / %dx%d size: %zd", dfb,
+ drmu_log_fourcc(format), dfb->fb.width, dfb->fb.height, dfb->active.w, dfb->active.h, dfb->map_size);
+ return dfb;
+
+fail:
+ drmu_fb_int_free(dfb);
+ return NULL;
+}
+
+drmu_fb_t *
+drmu_fb_new_dumb(drmu_env_t * const du, uint32_t w, uint32_t h, const uint32_t format)
+{
+ return drmu_fb_new_dumb_mod(du, w, h, format, DRM_FORMAT_MOD_INVALID);
+}
+
+static int
+fb_try_reuse(drmu_fb_t * dfb, uint32_t w, uint32_t h, const uint32_t format)
+{
+ if (w > dfb->fb.width || h > dfb->fb.height || format != dfb->fb.pixel_format)
+ return 0;
+
+ dfb->active = drmu_rect_wh(w, h);
2023-03-28 20:02:15 +00:00
+ dfb->crop = drmu_rect_shl16(dfb->active);
+ return 1;
+}
+
+drmu_fb_t *
+drmu_fb_realloc_dumb(drmu_env_t * const du, drmu_fb_t * dfb, uint32_t w, uint32_t h, const uint32_t format)
+{
+ if (dfb == NULL)
+ return drmu_fb_new_dumb(du, w, h, format);
+
+ if (fb_try_reuse(dfb, w, h, format))
+ return dfb;
+
+ drmu_fb_unref(&dfb);
+ return drmu_fb_new_dumb(du, w, h, format);
+}
+
+static void
+atomic_prop_fb_unref(void * v)
+{
+ drmu_fb_t * fb = v;
+ drmu_fb_unref(&fb);
+}
+
+static void
+atomic_prop_fb_ref(void * v)
+{
+ drmu_fb_ref(v);
+}
+
+int
+drmu_atomic_add_prop_fb(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_fb_t * const dfb)
+{
+ int rv;
+ static const drmu_atomic_prop_fns_t fns = {
+ .ref = atomic_prop_fb_ref,
+ .unref = atomic_prop_fb_unref,
+ .commit = drmu_prop_fn_null_commit,
+ };
+
+ if (dfb == NULL)
+ return drmu_atomic_add_prop_value(da, obj_id, prop_id, 0);
+
+ rv = drmu_atomic_add_prop_generic(da, obj_id, prop_id, dfb->fb.fb_id, &fns, dfb);
+ if (rv != 0)
+ drmu_warn(drmu_atomic_env(da), "%s: Failed to add fb obj_id=%#x, prop_id=%#x: %s", __func__, obj_id, prop_id, strerror(-rv));
+
+ return rv;
+}
+
+//----------------------------------------------------------------------------
+//
+// props fns (internal)
+
+typedef struct drmu_props_s {
+ struct drmu_env_s * du;
+ unsigned int n;
+ drmu_propinfo_t * info;
+ const drmu_propinfo_t ** by_name;
+} drmu_props_t;
+
+static void
+props_free(drmu_props_t * const props)
+{
+ free(props->info); // As yet nothing else is allocated off this
+ free(props->by_name);
+ free(props);
+}
+
+static const drmu_propinfo_t *
+props_name_to_propinfo(const drmu_props_t * const props, const char * const name)
+{
+ unsigned int i = props->n / 2;
+ unsigned int a = 0;
+ unsigned int b = props->n;
+
+ while (a < b) {
+ const int r = strcmp(name, props->by_name[i]->prop.name);
+
+ if (r == 0)
+ return props->by_name[i];
+
+ if (r < 0) {
+ b = i;
+ i = (i + a) / 2;
+ } else {
+ a = i + 1;
+ i = (i + b) / 2;
+ }
+ }
+ return NULL;
+}
+
+static uint32_t
+props_name_to_id(const drmu_props_t * const props, const char * const name)
+{
+ return propinfo_prop_id(props_name_to_propinfo(props, name));
+}
+
+// Data must be freed later
+static int
+props_name_get_blob(const drmu_props_t * const props, const char * const name, void ** const ppdata, size_t * const plen)
+{
+ const drmu_propinfo_t * const pinfo = props_name_to_propinfo(props, name);
+
+ *ppdata = 0;
+ *plen = 0;
+
+ if (!pinfo)
+ return -ENOENT;
+ if ((pinfo->prop.flags & DRM_MODE_PROP_BLOB) == 0)
+ return -EINVAL;
+
+ return blob_data_read(props->du, (uint32_t)pinfo->val, ppdata, plen);
+}
+
+#if TRACE_PROP_NEW
+static void
+props_dump(const drmu_props_t * const props)
+{
+ if (props != NULL) {
+ unsigned int i;
+ drmu_env_t * const du = props->du;
+
+ for (i = 0; i != props->n; ++i) {
+ const drmu_propinfo_t * const inf = props->info + i;
+ const struct drm_mode_get_property * const p = &inf->prop;
+ char flagbuf[256];
+
+ flagbuf[0] = 0;
+ if (p->flags & DRM_MODE_PROP_PENDING)
+ strcat(flagbuf, ":pending");
+ if (p->flags & DRM_MODE_PROP_RANGE)
+ strcat(flagbuf, ":urange");
+ if (p->flags & DRM_MODE_PROP_IMMUTABLE)
+ strcat(flagbuf, ":immutable");
+ if (p->flags & DRM_MODE_PROP_ENUM)
+ strcat(flagbuf, ":enum");
+ if (p->flags & DRM_MODE_PROP_BLOB)
+ strcat(flagbuf, ":blob");
+ if (p->flags & DRM_MODE_PROP_BITMASK)
+ strcat(flagbuf, ":bitmask");
+ if ((p->flags & DRM_MODE_PROP_EXTENDED_TYPE) == DRM_MODE_PROP_OBJECT)
+ strcat(flagbuf, ":object");
+ else if ((p->flags & DRM_MODE_PROP_EXTENDED_TYPE) == DRM_MODE_PROP_SIGNED_RANGE)
+ strcat(flagbuf, ":srange");
+ else if ((p->flags & DRM_MODE_PROP_EXTENDED_TYPE) != 0)
+ strcat(flagbuf, ":?xtype?");
+ if (p->flags & ~(DRM_MODE_PROP_LEGACY_TYPE |
+ DRM_MODE_PROP_EXTENDED_TYPE |
+ DRM_MODE_PROP_PENDING |
+ DRM_MODE_PROP_IMMUTABLE |
+ DRM_MODE_PROP_ATOMIC))
+ strcat(flagbuf, ":?other?");
+ if (p->flags & DRM_MODE_PROP_ATOMIC)
+ strcat(flagbuf, ":atomic");
+
+
+ drmu_info(du, "Prop%02d/%02d: %#-4x %-16s val=%#-4"PRIx64" flags=%#x%s, values=%d, blobs=%d",
+ i, props->n, p->prop_id,
+ p->name, inf->val,
+ p->flags, flagbuf,
+ p->count_values,
+ p->count_enum_blobs);
+ }
+ }
+}
+#endif
+
+static int
+props_qsort_by_name_cb(const void * va, const void * vb)
+{
+ const drmu_propinfo_t * const a = *(drmu_propinfo_t **)va;
+ const drmu_propinfo_t * const b = *(drmu_propinfo_t **)vb;
+ return strcmp(a->prop.name, b->prop.name);
+}
+
+// At the moment we don't need / want to fill in the values / blob arrays
+// we just want the name - will get the extra info if we need it
+static int
+propinfo_fill(drmu_env_t * const du, drmu_propinfo_t * const inf, uint32_t propid, uint64_t val)
+{
+ int rv;
+
+ inf->val = val;
+ inf->prop.prop_id = propid;
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPERTY, &inf->prop)) != 0)
+ drmu_err(du, "Failed to get property %d: %s", propid, strerror(-rv));
+ return rv;
+}
+
+static int
+props_get_properties(drmu_env_t * const du, const uint32_t objid, const uint32_t objtype,
+ uint32_t ** const ppPropids, uint64_t ** const ppValues)
+{
+ struct drm_mode_obj_get_properties obj_props = {
+ .obj_id = objid,
+ .obj_type = objtype,
+ };
+ uint64_t * values = NULL;
+ uint32_t * propids = NULL;
+ unsigned int n = 0;
+ int rv;
+
+ for (;;) {
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &obj_props)) != 0) {
+ drmu_err(du, "drmModeObjectGetProperties failed: %s", strerror(-rv));
+ goto fail;
+ }
+
+ if (obj_props.count_props <= n)
+ break;
+
+ free(values);
+ values = NULL;
+ free(propids);
+ propids = NULL;
+ n = obj_props.count_props;
+ if ((obj_props.prop_values_ptr = io_alloc(values, n)) == 0 ||
+ (obj_props.props_ptr = io_alloc(propids, n)) == 0) {
+ drmu_err(du, "obj/value array alloc failed");
+ rv = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ *ppValues = values;
+ *ppPropids = propids;
+ return (int)n;
+
+fail:
+ free(values);
+ free(propids);
+ *ppPropids = NULL;
+ *ppValues = NULL;
+ return rv;
+}
+
+static drmu_props_t *
+props_new(drmu_env_t * const du, const uint32_t objid, const uint32_t objtype)
+{
+ drmu_props_t * const props = calloc(1, sizeof(*props));
+ int rv;
+ uint64_t * values;
+ uint32_t * propids;
+
+ if (props == NULL) {
+ drmu_err(du, "%s: Failed struct alloc", __func__);
+ return NULL;
+ }
+ props->du = du;
+
+ if ((rv = props_get_properties(du, objid, objtype, &propids, &values)) < 0)
+ goto fail;
+
+ props->n = rv;
+ if ((props->info = calloc(props->n, sizeof(*props->info))) == NULL ||
+ (props->by_name = malloc(props->n * sizeof(*props->by_name))) == NULL) {
+ drmu_err(du, "info/name array alloc failed");
+ goto fail;
+ }
+
+ for (unsigned int i = 0; i < props->n; ++i) {
+ drmu_propinfo_t * const inf = props->info + i;
+
+ props->by_name[i] = inf;
+ if ((rv = propinfo_fill(du, inf, propids[i], values[i])) != 0)
+ goto fail;
+ }
+
+ // Sort into name order for faster lookup
+ qsort(props->by_name, props->n, sizeof(*props->by_name), props_qsort_by_name_cb);
+
+ free(values);
+ free(propids);
+ return props;
+
+fail:
+ props_free(props);
+ free(values);
+ free(propids);
+ return NULL;
+}
+
+int
+drmu_atomic_obj_add_snapshot(drmu_atomic_t * const da, const uint32_t objid, const uint32_t objtype)
+{
+#if 0
+ drmu_env_t * const du = drmu_atomic_env(da);
+ drmu_props_t * props = NULL;
+ unsigned int i;
+ int rv;
+
+ if (!du)
+ return -EINVAL;
+
+ if ((props = props_new(du, objid, objtype)) == NULL)
+ return -ENOENT;
+
+ for (i = 0; i != props->n; ++i) {
+ if ((props->info[i].prop.flags & (DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_ATOMIC)) != 0 || props->info[i].prop.prop_id == 2)
+ continue;
+ if ((rv = drmu_atomic_add_prop_generic(da, objid, props->info[i].prop.prop_id, props->info[i].val, 0, 0, NULL)) != 0)
+ goto fail;
+ }
+ rv = 0;
+
+fail:
+ props_free(props);
+ return rv;
+#else
+ uint64_t * values;
+ uint32_t * propids;
+ int rv;
+ unsigned int i, n;
+ drmu_env_t * const du = drmu_atomic_env(da);
+
+ if (!du)
+ return -EINVAL;
+
+ if ((rv = props_get_properties(du, objid, objtype, &propids, &values)) < 0)
+ goto fail;
+ n = rv;
+
+ for (i = 0; i != n; ++i) {
+ if ((rv = drmu_atomic_add_prop_value(da, objid, propids[i], values[i])) != 0)
+ goto fail;
+ }
+
+fail:
+ free(values);
+ free(propids);
+ return rv;
+#endif
+}
+
+static int
+drmu_atomic_props_add_save(drmu_atomic_t * const da, const uint32_t objid, const drmu_props_t * props)
+{
+ unsigned int i;
+ int rv;
+ drmu_env_t * const du = drmu_atomic_env(da);
+
+ if (props == NULL)
+ return 0;
+ if (du == NULL)
+ return -EINVAL;
+
+ for (i = 0; i != props->n; ++i) {
+ if ((props->info[i].prop.flags & DRM_MODE_PROP_IMMUTABLE) != 0)
+ continue;
+
+ // Blobs, if set, are prone to running out of refs and vanishing, so we
+ // must copy. If we fail to copy the blob for any reason drop through
+ // to the generic add and hope that that will do
+ if ((props->info[i].prop.flags & DRM_MODE_PROP_BLOB) != 0 && props->info[i].val != 0) {
+ drmu_blob_t * b = drmu_blob_copy_id(du, (uint32_t)props->info[i].val);
+ if (b != NULL) {
+ rv = drmu_atomic_add_prop_blob(da, objid, props->info[i].prop.prop_id, b);
+ drmu_blob_unref(&b);
+ if (rv == 0)
+ continue;
+ }
+ }
+
+ // Generic value
+ if ((rv = drmu_atomic_add_prop_value(da, objid, props->info[i].prop.prop_id, props->info[i].val)) != 0)
+ return rv;
+ }
+ return 0;
+}
+
+//----------------------------------------------------------------------------
+//
+// CRTC fns
+
+typedef struct drmu_crtc_s {
+ struct drmu_env_s * du;
+ int crtc_idx;
+
+ atomic_int ref_count;
+ bool saved;
+
+ struct drm_mode_crtc crtc;
+
+ struct {
+ drmu_prop_range_t * active;
+ uint32_t mode_id;
+ } pid;
+
+ drmu_blob_t * mode_id_blob;
+
+} drmu_crtc_t;
+
+static void
+crtc_uninit(drmu_crtc_t * const dc)
+{
+ drmu_prop_range_delete(&dc->pid.active);
+ drmu_blob_unref(&dc->mode_id_blob);
+}
+
+static void
+crtc_free(drmu_crtc_t * const dc)
+{
+ crtc_uninit(dc);
+ free(dc);
+}
+
+
+#if 0
+// Set misc derived vars from mode
+static void
+crtc_mode_set_vars(drmu_crtc_t * const dc)
+{
+ switch (dc->crtc.mode.flags & DRM_MODE_FLAG_PIC_AR_MASK) {
+ case DRM_MODE_FLAG_PIC_AR_4_3:
+ dc->par = (drmu_ufrac_t){4,3};
+ break;
+ case DRM_MODE_FLAG_PIC_AR_16_9:
+ dc->par = (drmu_ufrac_t){16,9};
+ break;
+ case DRM_MODE_FLAG_PIC_AR_64_27:
+ dc->par = (drmu_ufrac_t){64,27};
+ break;
+ case DRM_MODE_FLAG_PIC_AR_256_135:
+ dc->par = (drmu_ufrac_t){256,135};
+ break;
+ default:
+ case DRM_MODE_FLAG_PIC_AR_NONE:
+ dc->par = (drmu_ufrac_t){0,0};
+ break;
+ }
+
+ if (dc->par.den == 0) {
+ // Assume 1:1
+ dc->sar = (drmu_ufrac_t){1,1};
+ }
+ else {
+ dc->sar = drmu_ufrac_reduce((drmu_ufrac_t) {dc->par.num * dc->crtc.mode.vdisplay, dc->par.den * dc->crtc.mode.hdisplay});
+ }
+}
+
+static int
+atomic_crtc_bpc_set(drmu_atomic_t * const da, drmu_crtc_t * const dc,
+ const char * const colorspace,
+ const unsigned int max_bpc)
+{
+ const uint32_t con_id = dc->con->connector_id;
+ int rv = 0;
+
+ if (!dc->du->modeset_allow)
+ return 0;
+
+ if ((dc->pid.colorspace &&
+ (rv = drmu_atomic_add_prop_enum(da, con_id, dc->pid.colorspace, colorspace)) != 0) ||
+ (dc->pid.max_bpc &&
+ (rv = drmu_atomic_add_prop_range(da, con_id, dc->pid.max_bpc, max_bpc)) != 0))
+ return rv;
+ return 0;
+}
+
+
+static int
+atomic_crtc_hi_bpc_set(drmu_atomic_t * const da, drmu_crtc_t * const dc)
+{
+ return atomic_crtc_bpc_set(da, dc, "BT2020_YCC", 12);
+}
+#endif
+
+void
+drmu_crtc_delete(drmu_crtc_t ** ppdc)
+{
+ drmu_crtc_t * const dc = * ppdc;
+
+ if (dc == NULL)
+ return;
+ *ppdc = NULL;
+
+ crtc_free(dc);
+}
+
+drmu_env_t *
+drmu_crtc_env(const drmu_crtc_t * const dc)
+{
+ return dc == NULL ? NULL : dc->du;
+}
+
+uint32_t
+drmu_crtc_id(const drmu_crtc_t * const dc)
+{
+ return dc->crtc.crtc_id;
+}
+
+int
+drmu_crtc_idx(const drmu_crtc_t * const dc)
+{
+ return dc->crtc_idx;
+}
+
+static int
+crtc_init(drmu_env_t * const du, drmu_crtc_t * const dc, const unsigned int idx, const uint32_t crtc_id)
+{
+ int rv;
+ drmu_props_t * props;
+
+ memset(dc, 0, sizeof(*dc));
+ dc->du = du;
+ dc->crtc_idx = idx;
+ dc->crtc.crtc_id = crtc_id;
+
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETCRTC, &dc->crtc)) != 0) {
+ drmu_err(du, "Failed to get crtc id %d: %s", crtc_id, strerror(-rv));
+ return rv;
+ }
+
+ props = props_new(du, dc->crtc.crtc_id, DRM_MODE_OBJECT_CRTC);
+
+ if (props != NULL) {
+#if TRACE_PROP_NEW
+ drmu_info(du, "CRTC id=%#x, idx=%d:", dc->crtc.crtc_id, dc->crtc_idx);
+ props_dump(props);
+#endif
+ dc->pid.mode_id = props_name_to_id(props, "MODE_ID");
+ dc->pid.active = drmu_prop_range_new(du, props_name_to_id(props, "ACTIVE"));
+
+ props_free(props);
+ }
+
+ return 0;
+}
+
+static drmu_ufrac_t
+modeinfo_par(const struct drm_mode_modeinfo * const mode)
+{
+ switch (mode->flags & DRM_MODE_FLAG_PIC_AR_MASK) {
+ case DRM_MODE_FLAG_PIC_AR_4_3:
+ return (drmu_ufrac_t){4,3};
+ case DRM_MODE_FLAG_PIC_AR_16_9:
+ return (drmu_ufrac_t){16,9};
+ case DRM_MODE_FLAG_PIC_AR_64_27:
+ return (drmu_ufrac_t){64,27};
+ case DRM_MODE_FLAG_PIC_AR_256_135:
+ return (drmu_ufrac_t){256,135};
+ default:
+ case DRM_MODE_FLAG_PIC_AR_NONE:
+ break;
+ }
+ return (drmu_ufrac_t){0,0};
+}
+
+static drmu_mode_simple_params_t
+modeinfo_simple_params(const struct drm_mode_modeinfo * const mode)
+{
+ if (!mode)
+ return (drmu_mode_simple_params_t){ 0 };
+ else {
+ drmu_mode_simple_params_t rv = {
+ .width = mode->hdisplay,
+ .height = mode->vdisplay,
+ .hz_x_1000 = (uint32_t)(((uint64_t)mode->clock * 1000000) / (mode->htotal * mode->vtotal)),
+ .par = modeinfo_par(mode),
+ .sar = {1, 1},
+ .type = mode->type,
+ .flags = mode->flags,
+ };
+
+ if (rv.par.den != 0)
+ rv.sar = drmu_ufrac_reduce((drmu_ufrac_t) {rv.par.num * rv.height, rv.par.den * rv.width});
+
+ return rv;
+ }
+}
+
+drmu_crtc_t *
+drmu_env_crtc_find_id(drmu_env_t * const du, const uint32_t crtc_id)
+{
+ unsigned int i;
+ drmu_crtc_t * dc;
+
+ for (i = 0; (dc = drmu_env_crtc_find_n(du, i)) != NULL; ++i) {
+ if (dc->crtc.crtc_id == crtc_id)
+ return dc;
+ }
+ return NULL;
+}
+
+const struct drm_mode_modeinfo *
+drmu_crtc_modeinfo(const drmu_crtc_t * const dc)
+{
+ if (!dc || !dc->crtc.mode_valid)
+ return NULL;
+ return &dc->crtc.mode;
+}
+
+drmu_mode_simple_params_t
+drmu_crtc_mode_simple_params(const drmu_crtc_t * const dc)
+{
+ return modeinfo_simple_params(drmu_crtc_modeinfo(dc));
+}
+
+int
+drmu_atomic_crtc_add_modeinfo(struct drmu_atomic_s * const da, drmu_crtc_t * const dc, const struct drm_mode_modeinfo * const modeinfo)
+{
+ drmu_env_t * const du = drmu_atomic_env(da);
+ int rv;
+
+ if (!modeinfo || dc->pid.mode_id == 0)
+ return 0;
+
+ if ((rv = drmu_blob_update(du, &dc->mode_id_blob, modeinfo, sizeof(*modeinfo))) != 0)
+ return rv;
+
+ return drmu_atomic_add_prop_blob(da, dc->crtc.crtc_id, dc->pid.mode_id, dc->mode_id_blob);
+}
+
+int
+drmu_atomic_crtc_add_active(struct drmu_atomic_s * const da, drmu_crtc_t * const dc, unsigned int val)
+{
+ return drmu_atomic_add_prop_range(da, dc->crtc.crtc_id, dc->pid.active, val);
+}
+
+// Use the same claim logic as we do for planes
+// As it stands we don't do anything much on final unref so the logic
+// isn't really needed but it doesn't cost us much so do this way against
+// future need
+
+bool
+drmu_crtc_is_claimed(const drmu_crtc_t * const dc)
+{
+ return atomic_load(&dc->ref_count) != 0;
+}
+
+void
+drmu_crtc_unref(drmu_crtc_t ** const ppdc)
+{
+ drmu_crtc_t * const dc = *ppdc;
+
+ if (dc == NULL)
+ return;
+ *ppdc = NULL;
+
+ if (atomic_fetch_sub(&dc->ref_count, 1) != 2)
+ return;
+ atomic_store(&dc->ref_count, 0);
+}
+
+drmu_crtc_t *
+drmu_crtc_ref(drmu_crtc_t * const dc)
+{
+ if (!dc)
+ return NULL;
+ atomic_fetch_add(&dc->ref_count, 1);
+ return dc;
+}
+
+// A Conn should be claimed before any op that might change its state
+int
+drmu_crtc_claim_ref(drmu_crtc_t * const dc)
+{
+ drmu_env_t * const du = dc->du;
+ static const int ref0 = 0;
+ if (!atomic_compare_exchange_strong(&dc->ref_count, &ref0, 2))
+ return -EBUSY;
+
+ // 1st time through save state
+ if (!dc->saved && env_object_state_save(du, dc->crtc.crtc_id, DRM_MODE_OBJECT_CRTC) == 0)
+ dc->saved = true;
+
+ return 0;
+}
+
+//----------------------------------------------------------------------------
+//
+// CONN functions
+
+static const char * conn_type_names[32] = {
+ [DRM_MODE_CONNECTOR_Unknown] = "Unknown",
+ [DRM_MODE_CONNECTOR_VGA] = "VGA",
+ [DRM_MODE_CONNECTOR_DVII] = "DVI-I",
+ [DRM_MODE_CONNECTOR_DVID] = "DVI-D",
+ [DRM_MODE_CONNECTOR_DVIA] = "DVI-A",
+ [DRM_MODE_CONNECTOR_Composite] = "Composite",
+ [DRM_MODE_CONNECTOR_SVIDEO] = "SVIDEO",
+ [DRM_MODE_CONNECTOR_LVDS] = "LVDS",
+ [DRM_MODE_CONNECTOR_Component] = "Component",
+ [DRM_MODE_CONNECTOR_9PinDIN] = "9PinDIN",
+ [DRM_MODE_CONNECTOR_DisplayPort] = "DisplayPort",
+ [DRM_MODE_CONNECTOR_HDMIA] = "HDMI-A",
+ [DRM_MODE_CONNECTOR_HDMIB] = "HDMI-B",
+ [DRM_MODE_CONNECTOR_TV] = "TV",
+ [DRM_MODE_CONNECTOR_eDP] = "eDP",
+ [DRM_MODE_CONNECTOR_VIRTUAL] = "VIRTUAL",
+ [DRM_MODE_CONNECTOR_DSI] = "DSI",
+ [DRM_MODE_CONNECTOR_DPI] = "DPI",
+ [DRM_MODE_CONNECTOR_WRITEBACK] = "WRITEBACK",
+ [DRM_MODE_CONNECTOR_SPI] = "SPI",
+#ifdef DRM_MODE_CONNECTOR_USB
+ [DRM_MODE_CONNECTOR_USB] = "USB",
+#endif
+};
+
+struct drmu_conn_s {
+ drmu_env_t * du;
+ unsigned int conn_idx;
+
+ atomic_int ref_count;
+ bool saved;
+
+ struct drm_mode_get_connector conn;
+ bool probed;
+ unsigned int modes_size;
+ unsigned int enc_ids_size;
+ struct drm_mode_modeinfo * modes;
+ uint32_t * enc_ids;
+
+ uint32_t avail_crtc_mask;
+
+ struct {
+ drmu_prop_object_t * crtc_id;
+ drmu_prop_range_t * max_bpc;
+ drmu_prop_enum_t * colorspace;
+ drmu_prop_enum_t * broadcast_rgb;
+ uint32_t hdr_output_metadata;
+ uint32_t writeback_out_fence_ptr;
+ uint32_t writeback_fb_id;
+ uint32_t writeback_pixel_formats;
+ } pid;
+
+ drmu_blob_t * hdr_metadata_blob;
+
+ char name[32];
+};
+
+
+int
2022-12-08 21:49:23 +00:00
+drmu_atomic_conn_add_hdr_metadata(drmu_atomic_t * const da, drmu_conn_t * const dn, const struct hdr_output_metadata * const m)
+{
+ drmu_env_t * const du = drmu_atomic_env(da);
+ int rv;
+
+ if (!du || !dn) // du will be null if da is null
+ return -ENOENT;
+
+ if (dn->pid.hdr_output_metadata == 0)
+ return 0;
+
+ if ((rv = drmu_blob_update(du, &dn->hdr_metadata_blob, m, sizeof(*m))) != 0)
+ return rv;
+
+ rv = drmu_atomic_add_prop_blob(da, dn->conn.connector_id, dn->pid.hdr_output_metadata, dn->hdr_metadata_blob);
+ if (rv != 0)
+ drmu_err(du, "Set property fail: %s", strerror(errno));
+
+ return rv;
+}
+
+int
2022-12-08 21:49:23 +00:00
+drmu_atomic_conn_add_hi_bpc(drmu_atomic_t * const da, drmu_conn_t * const dn, bool hi_bpc)
+{
+ return drmu_atomic_add_prop_range(da, dn->conn.connector_id, dn->pid.max_bpc, !hi_bpc ? 8 :
+ drmu_prop_range_max(dn->pid.max_bpc));
+}
+
+int
2022-12-08 21:49:23 +00:00
+drmu_atomic_conn_add_colorspace(drmu_atomic_t * const da, drmu_conn_t * const dn, const drmu_colorspace_t colorspace)
+{
+ if (!dn->pid.colorspace)
+ return 0;
+
+ return drmu_atomic_add_prop_enum(da, dn->conn.connector_id, dn->pid.colorspace, colorspace);
+}
+
+int
2022-12-08 21:49:23 +00:00
+drmu_atomic_conn_add_broadcast_rgb(drmu_atomic_t * const da, drmu_conn_t * const dn, const drmu_broadcast_rgb_t bcrgb)
+{
+ if (!dn->pid.broadcast_rgb)
+ return 0;
+
+ return drmu_atomic_add_prop_enum(da, dn->conn.connector_id, dn->pid.broadcast_rgb, bcrgb);
+}
+
+int
+drmu_atomic_conn_add_crtc(drmu_atomic_t * const da, drmu_conn_t * const dn, drmu_crtc_t * const dc)
+{
+ return drmu_atomic_add_prop_object(da, dn->pid.crtc_id, drmu_crtc_id(dc));
+}
+
+int
+drmu_atomic_conn_add_writeback_fb(drmu_atomic_t * const da_out, drmu_conn_t * const dn,
+ drmu_fb_t * const dfb)
+{
+ // Add both or neither, so build a temp atomic to store the intermediate result
+ drmu_atomic_t * da = drmu_atomic_new(drmu_atomic_env(da_out));
+ int rv;
+
+ if (!da)
+ return -ENOMEM;
+
+ if ((rv = atomic_fb_add_out_fence(da, dn->conn.connector_id, dn->pid.writeback_out_fence_ptr, dfb)) != 0)
+ goto fail;
+
+ if ((rv = drmu_atomic_add_prop_fb(da, dn->conn.connector_id, dn->pid.writeback_fb_id, dfb)) != 0)
+ goto fail;
+
+ return drmu_atomic_merge(da_out, &da);
+
+fail:
+ drmu_atomic_unref(&da);
+ return rv;
+}
+
+const struct drm_mode_modeinfo *
+drmu_conn_modeinfo(const drmu_conn_t * const dn, const int mode_id)
+{
+ return !dn || mode_id < 0 || (unsigned int)mode_id >= dn->conn.count_modes ? NULL :
+ dn->modes + mode_id;
+}
+
+drmu_mode_simple_params_t
+drmu_conn_mode_simple_params(const drmu_conn_t * const dn, const int mode_id)
+{
+ return modeinfo_simple_params(drmu_conn_modeinfo(dn, mode_id));
+}
+
+bool
+drmu_conn_is_output(const drmu_conn_t * const dn)
+{
+ return dn->conn.connector_type != DRM_MODE_CONNECTOR_WRITEBACK;
+}
+
+bool
+drmu_conn_is_writeback(const drmu_conn_t * const dn)
+{
+ return dn->conn.connector_type == DRM_MODE_CONNECTOR_WRITEBACK;
+}
+
+const char *
+drmu_conn_name(const drmu_conn_t * const dn)
+{
+ return dn->name;
+}
+
+uint32_t
+drmu_conn_crtc_id_get(const drmu_conn_t * const dn)
+{
+ return drmu_prop_object_value(dn->pid.crtc_id);
+}
+
+uint32_t
+drmu_conn_possible_crtcs(const drmu_conn_t * const dn)
+{
+ return dn->avail_crtc_mask;
+}
+
+unsigned int
+drmu_conn_idx_get(const drmu_conn_t * const dn)
+{
+ return dn->conn_idx;
+}
+
+static void
+conn_uninit(drmu_conn_t * const dn)
+{
+ drmu_prop_object_unref(&dn->pid.crtc_id);
+ drmu_prop_range_delete(&dn->pid.max_bpc);
+ drmu_prop_enum_delete(&dn->pid.colorspace);
+ drmu_prop_enum_delete(&dn->pid.broadcast_rgb);
+
+ drmu_blob_unref(&dn->hdr_metadata_blob);
+
+ free(dn->modes);
+ free(dn->enc_ids);
+ dn->modes = NULL;
+ dn->enc_ids = NULL;
+ dn->modes_size = 0;
+ dn->enc_ids_size = 0;
+}
+
+// Assumes zeroed before entry
+static int
+conn_init(drmu_env_t * const du, drmu_conn_t * const dn, unsigned int conn_idx, const uint32_t conn_id)
+{
+ int rv;
+ drmu_props_t * props;
+ uint32_t modes_req = 0;
+ uint32_t encs_req = 0;
+
+ dn->du = du;
+ dn->conn_idx = conn_idx;
+ // * As count_modes == 0 this probes - do we really want this?
+
+ do {
+ memset(&dn->conn, 0, sizeof(dn->conn));
+ dn->conn.connector_id = conn_id;
+
+ if (modes_req > dn->modes_size) {
+ free(dn->modes);
+ if (io_alloc(dn->modes, modes_req) == 0) {
+ drmu_err(du, "Failed to alloc modes array");
+ goto fail;
+ }
+ dn->modes_size = modes_req;
+ }
+ dn->conn.modes_ptr = (uintptr_t)dn->modes;
+ dn->conn.count_modes = modes_req;
+
+ if (encs_req > dn->enc_ids_size) {
+ free(dn->enc_ids);
+ if (io_alloc(dn->enc_ids, encs_req) == 0) {
+ drmu_err(du, "Failed to alloc encs array");
+ goto fail;
+ }
+ dn->enc_ids_size = encs_req;
+ }
+ dn->conn.encoders_ptr = (uintptr_t)dn->enc_ids;
+ dn->conn.count_encoders = encs_req;
+
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETCONNECTOR, &dn->conn)) != 0) {
+ drmu_err(du, "Get connector id %d failed: %s", dn->conn.connector_id, strerror(-rv));
+ goto fail;
+ }
+ modes_req = dn->conn.count_modes;
+ encs_req = dn->conn.count_encoders;
+
+ } while (dn->modes_size < modes_req || dn->enc_ids_size < encs_req);
+
+ dn->probed = true;
+
+ if (dn->conn.connector_type >= sizeof(conn_type_names) / sizeof(conn_type_names[0]))
+ snprintf(dn->name, sizeof(dn->name)-1, "CT%"PRIu32"-%"PRIu32,
+ dn->conn.connector_type, dn->conn.connector_type_id);
+ else
+ snprintf(dn->name, sizeof(dn->name)-1, "%s-%"PRIu32,
+ conn_type_names[dn->conn.connector_type], dn->conn.connector_type_id);
+
+ props = props_new(du, dn->conn.connector_id, DRM_MODE_OBJECT_CONNECTOR);
+
+ // Spin over encoders to create a crtc mask
+ dn->avail_crtc_mask = 0;
+ for (unsigned int i = 0; i != dn->conn.count_encoders; ++i) {
+ struct drm_mode_get_encoder enc = {.encoder_id = dn->enc_ids[i]};
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETENCODER, &enc)) != 0) {
+ drmu_warn(du, "Failed to get encoder: id: %#x", enc.encoder_id);
+ continue;
+ }
+ dn->avail_crtc_mask |= enc.possible_crtcs;
+ }
+
+ if (props != NULL) {
+#if TRACE_PROP_NEW
+ drmu_info(du, "Connector id=%d, type=%d.%d (%s), crtc_mask=%#x:",
+ dn->conn.connector_id, dn->conn.connector_type, dn->conn.connector_type_id, drmu_conn_name(dn),
+ dn->avail_crtc_mask);
+ props_dump(props);
+#endif
+ dn->pid.crtc_id = drmu_prop_object_new_propinfo(du, dn->conn.connector_id, props_name_to_propinfo(props, "CRTC_ID"));
+ dn->pid.max_bpc = drmu_prop_range_new(du, props_name_to_id(props, "max bpc"));
+ dn->pid.colorspace = drmu_prop_enum_new(du, props_name_to_id(props, "Colorspace"));
+ dn->pid.broadcast_rgb = drmu_prop_enum_new(du, props_name_to_id(props, "Broadcast RGB"));
+ dn->pid.hdr_output_metadata = props_name_to_id(props, "HDR_OUTPUT_METADATA");
+ dn->pid.writeback_fb_id = props_name_to_id(props, "WRITEBACK_FB_ID");
+ dn->pid.writeback_out_fence_ptr = props_name_to_id(props, "WRITEBACK_OUT_FENCE_PTR");
+ dn->pid.writeback_pixel_formats = props_name_to_id(props, "WRITEBACK_PIXEL_FORMATS"); // Blob of fourccs (no modifier info)
+
+ props_free(props);
+ }
+
+ return 0;
+
+fail:
+ conn_uninit(dn);
+ return rv;
+}
+
+// Use the same claim logic as we do for planes
+// As it stands we don't do anything much on final unref so the logic
+// isn't really needed but it doesn't cost us much so do this way against
+// future need
+
+bool
+drmu_conn_is_claimed(const drmu_conn_t * const dn)
+{
+ return atomic_load(&dn->ref_count) != 0;
+}
+
+void
+drmu_conn_unref(drmu_conn_t ** const ppdn)
+{
+ drmu_conn_t * const dn = *ppdn;
+
+ if (dn == NULL)
+ return;
+ *ppdn = NULL;
+
+ if (atomic_fetch_sub(&dn->ref_count, 1) != 2)
+ return;
+ atomic_store(&dn->ref_count, 0);
+}
+
+drmu_conn_t *
+drmu_conn_ref(drmu_conn_t * const dn)
+{
+ if (!dn)
+ return NULL;
+ atomic_fetch_add(&dn->ref_count, 1);
+ return dn;
+}
+
+// A Conn should be claimed before any op that might change its state
+int
+drmu_conn_claim_ref(drmu_conn_t * const dn)
+{
+ drmu_env_t * const du = dn->du;
+ static const int ref0 = 0;
+ if (!atomic_compare_exchange_strong(&dn->ref_count, &ref0, 2))
+ return -EBUSY;
+
+ // 1st time through save state
+ if (!dn->saved && env_object_state_save(du, dn->conn.connector_id, DRM_MODE_OBJECT_CONNECTOR) == 0)
+ dn->saved = true;
+
+ return 0;
+}
+
+//----------------------------------------------------------------------------
+//
+// Atomic Q fns (internal)
+
+typedef struct drmu_atomic_q_s {
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+ drmu_atomic_t * next_flip;
+ drmu_atomic_t * cur_flip;
+ drmu_atomic_t * last_flip;
+ unsigned int retry_count;
+ struct polltask * retry_task;
+} drmu_atomic_q_t;
+
+static void atomic_q_retry(drmu_atomic_q_t * const aq, drmu_env_t * const du);
+
+// Needs locked
+static int
+atomic_q_attempt_commit_next(drmu_atomic_q_t * const aq)
+{
+ drmu_env_t * const du = drmu_atomic_env(aq->next_flip);
+ uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_ALLOW_MODESET;
+ int rv;
+
+ if ((rv = drmu_atomic_commit(aq->next_flip, flags)) == 0) {
+ if (aq->retry_count != 0)
+ drmu_warn(du, "%s: Atomic commit OK", __func__);
+ aq->cur_flip = aq->next_flip;
+ aq->next_flip = NULL;
+ aq->retry_count = 0;
+ }
+ else if (rv == -EBUSY && ++aq->retry_count < 16) {
+ // This really shouldn't happen but we observe that the 1st commit after
+ // a modeset often fails with BUSY. It seems to be fine on a 10ms retry
+ // but allow some more in case ww need a bit longer in some cases
+ drmu_warn(du, "%s: Atomic commit BUSY", __func__);
+ atomic_q_retry(aq, du);
+ rv = 0;
+ }
+ else {
+ drmu_err(du, "%s: Atomic commit failed: %s", __func__, strerror(-rv));
+ drmu_atomic_dump(aq->next_flip);
+ drmu_atomic_unref(&aq->next_flip);
+ aq->retry_count = 0;
+ }
+
+ return rv;
+}
+
+static void
+atomic_q_retry_cb(void * v, short revents)
+{
+ drmu_atomic_q_t * const aq = v;
+ (void)revents;
+
+ pthread_mutex_lock(&aq->lock);
+
+ // If we need a retry then next != NULL && cur == NULL
+ // if not that then we've fixed ourselves elsewhere
+
+ if (aq->next_flip != NULL && aq->cur_flip == NULL)
+ atomic_q_attempt_commit_next(aq);
+
+ pthread_mutex_unlock(&aq->lock);
+}
+
+static void
+atomic_q_retry(drmu_atomic_q_t * const aq, drmu_env_t * const du)
+{
+ if (aq->retry_task == NULL)
+ aq->retry_task = polltask_new_timer(env_pollqueue(du), atomic_q_retry_cb, aq);
+ pollqueue_add_task(aq->retry_task, 20);
+}
+
+// Called after an atomic commit has completed
+// not called on every vsync, so if we haven't committed anything this won't be called
+static void
+drmu_atomic_page_flip_cb(drmu_env_t * const du, void *user_data)
+{
+ drmu_atomic_t * const da = user_data;
+ drmu_atomic_q_t * const aq = env_atomic_q(du);
+
+ // At this point:
+ // next The atomic we are about to commit
+ // cur The last atomic we committed, now in use (must be != NULL)
+ // last The atomic that has just become obsolete
+
+ pthread_mutex_lock(&aq->lock);
+
+ if (da != aq->cur_flip) {
+ drmu_err(du, "%s: User data el (%p) != cur (%p)", __func__, da, aq->cur_flip);
+ }
+
+ drmu_atomic_unref(&aq->last_flip);
+ aq->last_flip = aq->cur_flip;
+ aq->cur_flip = NULL;
+
+ if (aq->next_flip != NULL)
+ atomic_q_attempt_commit_next(aq);
+
+ pthread_cond_broadcast(&aq->cond);
+ pthread_mutex_unlock(&aq->lock);
+}
+
+static int
+atomic_q_flush(drmu_atomic_q_t * const aq)
+{
+ struct timespec ts;
+ int rv = 0;
+
+ pthread_mutex_lock(&aq->lock);
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ ts.tv_sec += 1; // We should never timeout if all is well - 1 sec is plenty
+
+ // Can flush next safely
+ drmu_atomic_unref(&aq->next_flip);
+
+ // Wait for cur to finish - seems to confuse the world otherwise
+ while (aq->cur_flip != NULL) {
+ if ((rv = pthread_cond_timedwait(&aq->cond, &aq->lock, &ts)) != 0)
+ break;
+ }
+
+ pthread_mutex_unlock(&aq->lock);
+ return rv;
+}
+
+// 'consumes' da
+static int
+atomic_q_queue(drmu_atomic_q_t * const aq, drmu_atomic_t * da)
+{
+ int rv = 0;
+
+ pthread_mutex_lock(&aq->lock);
+
+ if (aq->next_flip != NULL) {
+ // We already have something pending or retrying - merge the new with it
+ rv = drmu_atomic_merge(aq->next_flip, &da);
+ }
+ else {
+ aq->next_flip = da;
+
+ // No pending commit?
+ if (aq->cur_flip == NULL)
+ rv = atomic_q_attempt_commit_next(aq);
+ }
+
+ pthread_mutex_unlock(&aq->lock);
+ return rv;
+}
+
+// Consumes the passed atomic structure as it isn't copied
+// * arguably should copy & unref if ref count != 0
+int
+drmu_atomic_queue(drmu_atomic_t ** ppda)
+{
+ drmu_atomic_t * da = *ppda;
+
+ if (da == NULL)
+ return 0;
+ *ppda = NULL;
+
+ return atomic_q_queue(env_atomic_q(drmu_atomic_env(da)), da);
+}
+
+int
+drmu_env_queue_wait(drmu_env_t * const du)
+{
+
+ drmu_atomic_q_t *const aq = env_atomic_q(du);
+ struct timespec ts;
+ int rv = 0;
+
+ pthread_mutex_lock(&aq->lock);
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ ts.tv_sec += 1; // We should never timeout if all is well - 1 sec is plenty
+
+ // Next should clear quickly
+ while (aq->next_flip != NULL) {
+ if ((rv = pthread_cond_timedwait(&aq->cond, &aq->lock, &ts)) != 0)
+ break;
+ }
+
+ pthread_mutex_unlock(&aq->lock);
+ return rv;
+}
+
+static void
+atomic_q_uninit(drmu_atomic_q_t * const aq)
+{
+ polltask_delete(&aq->retry_task);
+ drmu_atomic_unref(&aq->next_flip);
+ drmu_atomic_unref(&aq->cur_flip);
+ drmu_atomic_unref(&aq->last_flip);
+ pthread_cond_destroy(&aq->cond);
+ pthread_mutex_destroy(&aq->lock);
+}
+
+static void
+atomic_q_init(drmu_atomic_q_t * const aq)
+{
+ pthread_condattr_t condattr;
+
+ aq->next_flip = NULL;
+ pthread_mutex_init(&aq->lock, NULL);
+
+ pthread_condattr_init(&condattr);
+ pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC);
+ pthread_cond_init(&aq->cond, &condattr);
+ pthread_condattr_destroy(&condattr);
+}
+
+//----------------------------------------------------------------------------
+//
+// Pool fns
+
+typedef struct drmu_fb_list_s {
+ drmu_fb_t * head;
+ drmu_fb_t * tail;
+} drmu_fb_list_t;
+
+typedef struct drmu_pool_s {
+ atomic_int ref_count; // 0 == 1 ref for ease of init
+
+ struct drmu_env_s * du;
+
+ pthread_mutex_t lock;
+ bool dead;
+
+ unsigned int seq; // debug
+
+ unsigned int fb_count;
+ unsigned int fb_max;
+
+ drmu_fb_list_t free_fbs;
+} drmu_pool_t;
+
+static void
+fb_list_add_tail(drmu_fb_list_t * const fbl, drmu_fb_t * const dfb)
+{
+ assert(dfb->prev == NULL && dfb->next == NULL);
+
+ if (fbl->tail == NULL)
+ fbl->head = dfb;
+ else
+ fbl->tail->next = dfb;
+ dfb->prev = fbl->tail;
+ fbl->tail = dfb;
+}
+
+static drmu_fb_t *
+fb_list_extract(drmu_fb_list_t * const fbl, drmu_fb_t * const dfb)
+{
+ if (dfb == NULL)
+ return NULL;
+
+ if (dfb->prev == NULL)
+ fbl->head = dfb->next;
+ else
+ dfb->prev->next = dfb->next;
+
+ if (dfb->next == NULL)
+ fbl->tail = dfb->prev;
+ else
+ dfb->next->prev = dfb->prev;
+
+ dfb->next = NULL;
+ dfb->prev = NULL;
+ return dfb;
+}
+
+static drmu_fb_t *
+fb_list_extract_head(drmu_fb_list_t * const fbl)
+{
+ return fb_list_extract(fbl, fbl->head);
+}
+
+static drmu_fb_t *
+fb_list_peek_head(drmu_fb_list_t * const fbl)
+{
+ return fbl->head;
+}
+
+static bool
+fb_list_is_empty(drmu_fb_list_t * const fbl)
+{
+ return fbl->head == NULL;
+}
+
+static void
+pool_free_pool(drmu_pool_t * const pool)
+{
+ drmu_fb_t * dfb;
+ while ((dfb = fb_list_extract_head(&pool->free_fbs)) != NULL)
+ drmu_fb_unref(&dfb);
+}
+
+static void
+pool_free(drmu_pool_t * const pool)
+{
+ pool_free_pool(pool);
+ pthread_mutex_destroy(&pool->lock);
+ free(pool);
+}
+
+void
+drmu_pool_unref(drmu_pool_t ** const pppool)
+{
+ drmu_pool_t * const pool = *pppool;
+
+ if (pool == NULL)
+ return;
+ *pppool = NULL;
+
+ if (atomic_fetch_sub(&pool->ref_count, 1) != 0)
+ return;
+
+ pool_free(pool);
+}
+
+drmu_pool_t *
+drmu_pool_ref(drmu_pool_t * const pool)
+{
+ atomic_fetch_add(&pool->ref_count, 1);
+ return pool;
+}
+
+drmu_pool_t *
+drmu_pool_new(drmu_env_t * const du, unsigned int total_fbs_max)
+{
+ drmu_pool_t * const pool = calloc(1, sizeof(*pool));
+
+ if (pool == NULL) {
+ drmu_err(du, "Failed pool env alloc");
+ return NULL;
+ }
+
+ pool->du = du;
+ pool->fb_max = total_fbs_max;
+ pthread_mutex_init(&pool->lock, NULL);
+
+ return pool;
+}
+
+static int
+pool_fb_pre_delete_cb(drmu_fb_t * dfb, void * v)
+{
+ drmu_pool_t * pool = v;
+
+ // Ensure we cannot end up in a delete loop
+ drmu_fb_pre_delete_unset(dfb);
+
+ // If dead set then might as well delete now
+ // It should all work without this shortcut but this reclaims
+ // storage quicker
+ if (pool->dead) {
+ drmu_pool_unref(&pool);
+ return 0;
+ }
+
+ drmu_fb_ref(dfb); // Restore ref
+
+ pthread_mutex_lock(&pool->lock);
+ fb_list_add_tail(&pool->free_fbs, dfb);
+ pthread_mutex_unlock(&pool->lock);
+
+ // May cause suicide & recursion on fb delete, but that should be OK as
+ // the 1 we return here should cause simple exit of fb delete
+ drmu_pool_unref(&pool);
+ return 1; // Stop delete
+}
+
+drmu_fb_t *
+drmu_pool_fb_new_dumb(drmu_pool_t * const pool, uint32_t w, uint32_t h, const uint32_t format)
+{
+ drmu_env_t * const du = pool->du;
+ drmu_fb_t * dfb;
+
+ pthread_mutex_lock(&pool->lock);
+
+ dfb = fb_list_peek_head(&pool->free_fbs);
+ while (dfb != NULL) {
+ if (fb_try_reuse(dfb, w, h, format)) {
+ fb_list_extract(&pool->free_fbs, dfb);
+ break;
+ }
+ dfb = dfb->next;
+ }
+
+ if (dfb == NULL) {
+ if (pool->fb_count >= pool->fb_max && !fb_list_is_empty(&pool->free_fbs)) {
+ --pool->fb_count;
+ dfb = fb_list_extract_head(&pool->free_fbs);
+ }
+ ++pool->fb_count;
+ pthread_mutex_unlock(&pool->lock);
+
+ drmu_fb_unref(&dfb); // Will free the dfb as pre-delete CB will be unset
+ if ((dfb = drmu_fb_realloc_dumb(du, NULL, w, h, format)) == NULL) {
+ --pool->fb_count; // ??? lock
+ return NULL;
+ }
+ }
+ else {
+ pthread_mutex_unlock(&pool->lock);
+ }
+
+ drmu_fb_pre_delete_set(dfb, pool_fb_pre_delete_cb, pool);
+ drmu_pool_ref(pool);
+ return dfb;
+}
+
+// Mark pool as dead (i.e. no new allocs) and unref it
+// Simple unref will also work but this reclaims storage faster
+// Actual pool structure will persist until all referencing fbs are deleted too
+void
+drmu_pool_delete(drmu_pool_t ** const pppool)
+{
+ drmu_pool_t * pool = *pppool;
+
+ if (pool == NULL)
+ return;
+ *pppool = NULL;
+
+ pool->dead = true;
+ pthread_mutex_lock(&pool->lock);
+ pool_free_pool(pool);
+ pthread_mutex_unlock(&pool->lock);
+
+ drmu_pool_unref(&pool);
+}
+
+//----------------------------------------------------------------------------
+//
+// Plane fns
+
+typedef struct drmu_plane_s {
+ struct drmu_env_s * du;
+
+ // Unlike most ref counts in drmu this is 0 for unrefed, 2 for single ref
+ // and 1 for whilst unref cleanup is in progress. Guards dc
+ atomic_int ref_count;
+ struct drmu_crtc_s * dc; // NULL if not in use
+ bool saved;
+
+ int plane_type;
+ struct drm_mode_get_plane plane;
+
+ void * formats_in;
+ size_t formats_in_len;
+ const struct drm_format_modifier_blob * fmts_hdr;
+
+ struct {
+ uint32_t crtc_id;
+ uint32_t fb_id;
+ uint32_t crtc_h;
+ uint32_t crtc_w;
+ uint32_t crtc_x;
+ uint32_t crtc_y;
+ uint32_t src_h;
+ uint32_t src_w;
+ uint32_t src_x;
+ uint32_t src_y;
+ drmu_prop_range_t * alpha;
+ drmu_prop_enum_t * color_encoding;
+ drmu_prop_enum_t * color_range;
+ drmu_prop_enum_t * pixel_blend_mode;
+ drmu_prop_bitmask_t * rotation;
+ drmu_prop_range_t * chroma_siting_h;
+ drmu_prop_range_t * chroma_siting_v;
2023-03-28 20:02:15 +00:00
+ drmu_prop_range_t * zpos;
+ } pid;
+ uint64_t rot_vals[8];
+
+} drmu_plane_t;
+
+static int
+plane_set_atomic(drmu_atomic_t * const da,
+ drmu_plane_t * const dp,
+ drmu_fb_t * const dfb,
+ int32_t crtc_x, int32_t crtc_y,
+ uint32_t crtc_w, uint32_t crtc_h,
+ uint32_t src_x, uint32_t src_y,
+ uint32_t src_w, uint32_t src_h)
+{
+ const uint32_t plid = dp->plane.plane_id;
+ drmu_atomic_add_prop_value(da, plid, dp->pid.crtc_id, dfb == NULL ? 0 : drmu_crtc_id(dp->dc));
+ drmu_atomic_add_prop_fb(da, plid, dp->pid.fb_id, dfb);
+ drmu_atomic_add_prop_value(da, plid, dp->pid.crtc_x, crtc_x);
+ drmu_atomic_add_prop_value(da, plid, dp->pid.crtc_y, crtc_y);
+ drmu_atomic_add_prop_value(da, plid, dp->pid.crtc_w, crtc_w);
+ drmu_atomic_add_prop_value(da, plid, dp->pid.crtc_h, crtc_h);
+ drmu_atomic_add_prop_value(da, plid, dp->pid.src_x, src_x);
+ drmu_atomic_add_prop_value(da, plid, dp->pid.src_y, src_y);
+ drmu_atomic_add_prop_value(da, plid, dp->pid.src_w, src_w);
+ drmu_atomic_add_prop_value(da, plid, dp->pid.src_h, src_h);
+ return 0;
+}
+
+int
2022-12-08 21:49:23 +00:00
+drmu_atomic_plane_add_alpha(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int alpha)
+{
+ if (alpha == DRMU_PLANE_ALPHA_UNSET)
+ return 0;
+ return drmu_atomic_add_prop_range(da, dp->plane.plane_id, dp->pid.alpha, alpha);
+}
+
+int
2023-03-28 20:02:15 +00:00
+drmu_atomic_plane_add_zpos(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int zpos)
+{
+ return drmu_atomic_add_prop_range(da, dp->plane.plane_id, dp->pid.zpos, zpos);
+}
+
+int
2022-12-08 21:49:23 +00:00
+drmu_atomic_plane_add_rotation(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int rot)
+{
+ if (!dp->pid.rotation)
+ return rot == DRMU_PLANE_ROTATION_0 ? 0 : -EINVAL;
+ if (rot < 0 || rot >= 8 || !dp->rot_vals[rot])
+ return -EINVAL;
+ return drmu_atomic_add_prop_bitmask(da, dp->plane.plane_id, dp->pid.rotation, dp->rot_vals[rot]);
+}
+
+int
+drmu_atomic_plane_add_chroma_siting(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const drmu_chroma_siting_t siting)
+{
+ int rv = 0;
+
+ if (!dp->pid.chroma_siting_h || !dp->pid.chroma_siting_v)
+ return -ENOENT;
+
+ if (!drmu_chroma_siting_eq(siting, DRMU_CHROMA_SITING_UNSPECIFIED)) {
+ const uint32_t plid = dp->plane.plane_id;
+ rv = drmu_atomic_add_prop_range(da, plid, dp->pid.chroma_siting_h, siting.x);
+ rv = rvup(rv, drmu_atomic_add_prop_range(da, plid, dp->pid.chroma_siting_v, siting.y));
+ }
+ return rv;
+}
+
+int
2022-12-08 21:49:23 +00:00
+drmu_atomic_plane_add_fb(drmu_atomic_t * const da, drmu_plane_t * const dp,
+ drmu_fb_t * const dfb, const drmu_rect_t pos)
+{
+ int rv;
+ const uint32_t plid = dp->plane.plane_id;
+
+ if (dfb == NULL) {
+ rv = plane_set_atomic(da, dp, NULL,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0);
+ }
+ else {
+ rv = plane_set_atomic(da, dp, dfb,
+ pos.x, pos.y,
+ pos.w, pos.h,
+ dfb->crop.x + (dfb->active.x << 16), dfb->crop.y + (dfb->active.y << 16),
+ dfb->crop.w, dfb->crop.h);
+ }
+ if (rv != 0 || dfb == NULL)
+ return rv;
+
+ drmu_atomic_add_prop_enum(da, plid, dp->pid.pixel_blend_mode, dfb->pixel_blend_mode);
+ drmu_atomic_add_prop_enum(da, plid, dp->pid.color_encoding, dfb->color_encoding);
+ drmu_atomic_add_prop_enum(da, plid, dp->pid.color_range, dfb->color_range);
+ drmu_atomic_plane_add_chroma_siting(da, dp, dfb->chroma_siting);
+ return rv != 0 ? -errno : 0;
+}
+
+uint32_t
+drmu_plane_id(const drmu_plane_t * const dp)
+{
+ return dp->plane.plane_id;
+}
+
2023-03-28 20:02:15 +00:00
+unsigned int
+drmu_plane_type(const drmu_plane_t * const dp)
+{
+ return dp->plane_type;
+}
+
+const uint32_t *
+drmu_plane_formats(const drmu_plane_t * const dp, unsigned int * const pCount)
+{
+ *pCount = dp->fmts_hdr->count_formats;
+ return (const uint32_t *)((const uint8_t *)dp->formats_in + dp->fmts_hdr->formats_offset);
+}
+
+bool
+drmu_plane_format_check(const drmu_plane_t * const dp, const uint32_t format, const uint64_t modifier)
+{
+ const struct drm_format_modifier * const mods = (const struct drm_format_modifier *)((const uint8_t *)dp->formats_in + dp->fmts_hdr->modifiers_offset);
+ const uint32_t * const fmts = (const uint32_t *)((const uint8_t *)dp->formats_in + dp->fmts_hdr->formats_offset);
2023-03-29 17:41:09 +00:00
+ uint64_t modbase = modifier;
+ unsigned int i;
+
2023-03-29 17:41:09 +00:00
+ if (!format)
+ return false;
+
+ // If broadcom then remove parameters before checking
+ if ((modbase >> 56) == DRM_FORMAT_MOD_VENDOR_BROADCOM)
+ modbase = fourcc_mod_broadcom_mod(modbase);
+
+ // * Simplistic lookup; Could be made much faster
+
+ for (i = 0; i != dp->fmts_hdr->count_modifiers; ++i) {
+ const struct drm_format_modifier * const mod = mods + i;
+ uint64_t fbits;
+ unsigned int j;
+
2023-03-29 17:41:09 +00:00
+ if (mod->modifier != modbase)
+ continue;
+
+ for (fbits = mod->formats, j = mod->offset; fbits; fbits >>= 1, ++j) {
+ if ((fbits & 1) != 0 && fmts[j] == format)
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+drmu_plane_unref(drmu_plane_t ** const ppdp)
+{
+ drmu_plane_t * const dp = *ppdp;
+
+ if (dp == NULL)
+ return;
+ *ppdp = NULL;
+
+ if (atomic_fetch_sub(&dp->ref_count, 1) != 2)
+ return;
+ dp->dc = NULL;
+ atomic_store(&dp->ref_count, 0);
+}
+
+drmu_plane_t *
+drmu_plane_ref(drmu_plane_t * const dp)
+{
+ if (dp)
+ atomic_fetch_add(&dp->ref_count, 1);
+ return dp;
+}
+
+// Associate a plane with a crtc and ref it
+// Returns -EBUSY if plane already associated
+int
+drmu_plane_ref_crtc(drmu_plane_t * const dp, drmu_crtc_t * const dc)
+{
+ drmu_env_t * const du = dp->du;
+
+ static const int ref0 = 0;
+ if (!atomic_compare_exchange_strong(&dp->ref_count, &ref0, 2))
+ return -EBUSY;
+ dp->dc = dc;
+
+ // 1st time through save state
+ if (!dp->saved && env_object_state_save(du, drmu_plane_id(dp), DRM_MODE_OBJECT_PLANE) == 0)
+ dp->saved = true;
+
+ return 0;
+}
+
+drmu_plane_t *
2023-03-28 20:02:15 +00:00
+drmu_plane_new_find(drmu_crtc_t * const dc, const drmu_plane_new_find_ok_fn cb, void * const v)
+{
+ uint32_t i;
+ drmu_env_t * const du = drmu_crtc_env(dc);
+ drmu_plane_t * dp = NULL;
+ drmu_plane_t * dp_t;
+ const uint32_t crtc_mask = (uint32_t)1 << drmu_crtc_idx(dc);
+
+ for (i = 0; (dp_t = drmu_env_plane_find_n(du, i)) != NULL; ++i) {
2023-03-28 20:02:15 +00:00
+ // Is unused?
+ // Availible for this crtc?
2023-03-28 20:02:15 +00:00
+ if (dp_t->dc != NULL ||
+ (dp_t->plane.possible_crtcs & crtc_mask) == 0)
+ continue;
+
2023-03-28 20:02:15 +00:00
+ if (cb(dp_t, v)) {
+ dp = dp_t;
+ break;
+ }
+ }
2023-03-28 20:02:15 +00:00
+ return dp;
+}
+
+static bool plane_find_type_cb(const drmu_plane_t * dp, void * v)
+{
+ const unsigned int * const pReq = v;
+ return (*pReq & drmu_plane_type(dp)) != 0;
+}
+
+drmu_plane_t *
+drmu_plane_new_find_type(drmu_crtc_t * const dc, const unsigned int req_type)
+{
+ drmu_env_t * const du = drmu_crtc_env(dc);
+ drmu_plane_t * const dp = drmu_plane_new_find(dc, plane_find_type_cb, (void*)&req_type);
+ if (dp == NULL) {
2023-03-28 20:02:15 +00:00
+ drmu_err(du, "%s: No plane found for types %#x", __func__, req_type);
+ return NULL;
+ }
+ return dp;
+}
+
+static void
+plane_uninit(drmu_plane_t * const dp)
+{
+ drmu_prop_range_delete(&dp->pid.alpha);
+ drmu_prop_range_delete(&dp->pid.chroma_siting_h);
+ drmu_prop_range_delete(&dp->pid.chroma_siting_v);
+ drmu_prop_enum_delete(&dp->pid.color_encoding);
+ drmu_prop_enum_delete(&dp->pid.color_range);
+ drmu_prop_enum_delete(&dp->pid.pixel_blend_mode);
+ drmu_prop_enum_delete(&dp->pid.rotation);
+ drmu_prop_range_delete(&dp->pid.zpos);
+ free(dp->formats_in);
+ dp->formats_in = NULL;
+}
+
+
+static int
+plane_init(drmu_env_t * const du, drmu_plane_t * const dp, const uint32_t plane_id)
+{
+ drmu_props_t *props;
+ int rv;
+
+ memset(dp, 0, sizeof(*dp));
+ dp->du = du;
+
+ dp->plane.plane_id = plane_id;
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPLANE, &dp->plane)) != 0) {
+ drmu_err(du, "%s: drmModeGetPlane failed: %s", __func__, strerror(-rv));
+ return rv;
+ }
+
+ if ((props = props_new(du, dp->plane.plane_id, DRM_MODE_OBJECT_PLANE)) == NULL)
+ return -EINVAL;
+
+#if TRACE_PROP_NEW
2023-03-28 20:02:15 +00:00
+ drmu_info(du, "Plane id %d:", plane_id);
+ props_dump(props);
+#endif
+
+ if ((dp->pid.crtc_id = props_name_to_id(props, "CRTC_ID")) == 0 ||
+ (dp->pid.fb_id = props_name_to_id(props, "FB_ID")) == 0 ||
+ (dp->pid.crtc_h = props_name_to_id(props, "CRTC_H")) == 0 ||
+ (dp->pid.crtc_w = props_name_to_id(props, "CRTC_W")) == 0 ||
+ (dp->pid.crtc_x = props_name_to_id(props, "CRTC_X")) == 0 ||
+ (dp->pid.crtc_y = props_name_to_id(props, "CRTC_Y")) == 0 ||
+ (dp->pid.src_h = props_name_to_id(props, "SRC_H")) == 0 ||
+ (dp->pid.src_w = props_name_to_id(props, "SRC_W")) == 0 ||
+ (dp->pid.src_x = props_name_to_id(props, "SRC_X")) == 0 ||
+ (dp->pid.src_y = props_name_to_id(props, "SRC_Y")) == 0 ||
+ props_name_get_blob(props, "IN_FORMATS", &dp->formats_in, &dp->formats_in_len) != 0)
+ {
+ drmu_err(du, "%s: failed to find required id", __func__);
+ props_free(props);
+ return -EINVAL;
+ }
+ dp->fmts_hdr = dp->formats_in;
+
+ dp->pid.alpha = drmu_prop_range_new(du, props_name_to_id(props, "alpha"));
+ dp->pid.color_encoding = drmu_prop_enum_new(du, props_name_to_id(props, "COLOR_ENCODING"));
+ dp->pid.color_range = drmu_prop_enum_new(du, props_name_to_id(props, "COLOR_RANGE"));
+ dp->pid.pixel_blend_mode = drmu_prop_enum_new(du, props_name_to_id(props, "pixel blend mode"));
+ dp->pid.rotation = drmu_prop_enum_new(du, props_name_to_id(props, "rotation"));
+ dp->pid.chroma_siting_h = drmu_prop_range_new(du, props_name_to_id(props, "CHROMA_SITING_H"));
+ dp->pid.chroma_siting_v = drmu_prop_range_new(du, props_name_to_id(props, "CHROMA_SITING_V"));
2023-03-28 20:02:15 +00:00
+ dp->pid.zpos = drmu_prop_range_new(du, props_name_to_id(props, "zpos"));
+
+ dp->rot_vals[DRMU_PLANE_ROTATION_0] = drmu_prop_bitmask_value(dp->pid.rotation, "rotate-0");
+ if (dp->rot_vals[DRMU_PLANE_ROTATION_0]) {
+ // Flips MUST be combined with a rotate
+ if ((dp->rot_vals[DRMU_PLANE_ROTATION_X_FLIP] = drmu_prop_bitmask_value(dp->pid.rotation, "reflect-x")) != 0)
+ dp->rot_vals[DRMU_PLANE_ROTATION_X_FLIP] |= dp->rot_vals[DRMU_PLANE_ROTATION_0];
+ if ((dp->rot_vals[DRMU_PLANE_ROTATION_Y_FLIP] = drmu_prop_bitmask_value(dp->pid.rotation, "reflect-y")) != 0)
+ dp->rot_vals[DRMU_PLANE_ROTATION_Y_FLIP] |= dp->rot_vals[DRMU_PLANE_ROTATION_0];
+ }
+ dp->rot_vals[DRMU_PLANE_ROTATION_180] = drmu_prop_bitmask_value(dp->pid.rotation, "rotate-180");
+ if (!dp->rot_vals[DRMU_PLANE_ROTATION_180] && dp->rot_vals[DRMU_PLANE_ROTATION_X_FLIP] && dp->rot_vals[DRMU_PLANE_ROTATION_Y_FLIP])
+ dp->rot_vals[DRMU_PLANE_ROTATION_180] = dp->rot_vals[DRMU_PLANE_ROTATION_X_FLIP] | dp->rot_vals[DRMU_PLANE_ROTATION_Y_FLIP];
+
+ {
+ const drmu_propinfo_t * const pinfo = props_name_to_propinfo(props, "type");
+ drmu_prop_enum_t * etype = drmu_prop_enum_new(du, props_name_to_id(props, "type"));
+ const uint64_t * p;
+
+ if ((p = drmu_prop_enum_value(etype, "Primary")) && *p == pinfo->val)
+ dp->plane_type = DRMU_PLANE_TYPE_PRIMARY;
+ else if ((p = drmu_prop_enum_value(etype, "Cursor")) && *p == pinfo->val)
+ dp->plane_type = DRMU_PLANE_TYPE_CURSOR;
+ else if ((p = drmu_prop_enum_value(etype, "Overlay")) && *p == pinfo->val)
+ dp->plane_type = DRMU_PLANE_TYPE_OVERLAY;
+ else {
+ drmu_debug(du, "Unexpected plane type: %"PRId64, pinfo->val);
+ dp->plane_type = DRMU_PLANE_TYPE_UNKNOWN;
+ }
+ drmu_prop_enum_delete(&etype);
+ }
+
+ props_free(props);
+ return 0;
+}
+
+//----------------------------------------------------------------------------
+//
+// Env fns
+
+typedef struct drmu_env_s {
+ atomic_int ref_count; // 0 == 1 ref for ease of init
+ int fd;
+ uint32_t plane_count;
+ uint32_t conn_count;
+ uint32_t crtc_count;
+ drmu_plane_t * planes;
+ drmu_conn_t * conns;
+ drmu_crtc_t * crtcs;
+
+ drmu_log_env_t log;
+
+ // global env for atomic flip
+ drmu_atomic_q_t aq;
+ // global env for bo tracking
+ drmu_bo_env_t boe;
+ // global atomic for restore op
+ drmu_atomic_t * da_restore;
+
+ struct pollqueue * pq;
+ struct polltask * pt;
+} drmu_env_t;
+
+// Retrieve the the n-th conn
+// Use for iteration
+// Returns NULL when none left
+drmu_crtc_t *
+drmu_env_crtc_find_n(drmu_env_t * const du, const unsigned int n)
+{
+ return n >= du->crtc_count ? NULL : du->crtcs + n;
+}
+
+// Retrieve the the n-th conn
+// Use for iteration
+// Returns NULL when none left
+drmu_conn_t *
+drmu_env_conn_find_n(drmu_env_t * const du, const unsigned int n)
+{
+ return n >= du->conn_count ? NULL : du->conns + n;
+}
+
+drmu_plane_t *
+drmu_env_plane_find_n(drmu_env_t * const du, const unsigned int n)
+{
+ return n >= du->plane_count ? NULL : du->planes + n;
+}
+
+int
+drmu_ioctl(const drmu_env_t * const du, unsigned long req, void * arg)
+{
+ while (ioctl(du->fd, req, arg)) {
+ const int err = errno;
+ // DRM docn suggests we should try again on EAGAIN as well as EINTR
+ // and drm userspace does this.
+ if (err != EINTR && err != EAGAIN)
+ return -err;
+ }
+ return 0;
+}
+
+static void
+env_free_planes(drmu_env_t * const du)
+{
+ uint32_t i;
+ for (i = 0; i != du->plane_count; ++i)
+ plane_uninit(du->planes + i);
+ free(du->planes);
+ du->plane_count = 0;
+ du->planes = NULL;
+}
+
+static void
+env_free_conns(drmu_env_t * const du)
+{
+ uint32_t i;
+ for (i = 0; i != du->conn_count; ++i)
+ conn_uninit(du->conns + i);
+ free(du->conns);
+ du->conn_count = 0;
+ du->conns = NULL;
+}
+
+static void
+env_free_crtcs(drmu_env_t * const du)
+{
+ uint32_t i;
+ for (i = 0; i != du->crtc_count; ++i)
+ crtc_uninit(du->crtcs + i);
+ free(du->crtcs);
+ du->crtc_count = 0;
+ du->crtcs = NULL;
+}
+
+
+static int
+env_planes_populate(drmu_env_t * const du, unsigned int n, const uint32_t * const ids)
+{
+ uint32_t i;
+ int rv;
+
+ if ((du->planes = calloc(n, sizeof(*du->planes))) == NULL) {
+ drmu_err(du, "Plane array alloc failed");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i != n; ++i) {
+ if ((rv = plane_init(du, du->planes + i, ids[i])) != 0)
+ goto fail2;
+ du->plane_count = i + 1;
+ }
+ return 0;
+
+fail2:
+ env_free_planes(du);
+ return rv;
+}
+
+// Doesn't clean up on error - assumes that env construction will abort and
+// that will tidy up for us
+static int
+env_conn_populate(drmu_env_t * const du, unsigned int n, const uint32_t * const ids)
+{
+ unsigned int i;
+ int rv;
+
+ if (n == 0) {
+ drmu_err(du, "No connectors");
+ return -EINVAL;
+ }
+
+ if ((du->conns = calloc(n, sizeof(*du->conns))) == NULL) {
+ drmu_err(du, "Failed to malloc conns");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i != n; ++i) {
+ if ((rv = conn_init(du, du->conns + i, i, ids[i])) != 0)
+ return rv;
+ du->conn_count = i + 1;
+ }
+
+ return 0;
+}
+
+static int
+env_crtc_populate(drmu_env_t * const du, unsigned int n, const uint32_t * const ids)
+{
+ unsigned int i;
+ int rv;
+
+ if (n == 0) {
+ drmu_err(du, "No crtcs");
+ return -EINVAL;
+ }
+
+ if ((du->crtcs = malloc(n * sizeof(*du->crtcs))) == NULL) {
+ drmu_err(du, "Failed to malloc conns");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i != n; ++i) {
+ if ((rv = crtc_init(du, du->crtcs + i, i, ids[i])) != 0)
+ return rv;
+ du->crtc_count = i + 1;
+ }
+
+ return 0;
+}
+
+
+
+int
+drmu_fd(const drmu_env_t * const du)
+{
+ return du->fd;
+}
+
+const struct drmu_log_env_s *
+drmu_env_log(const drmu_env_t * const du)
+{
+ return &du->log;
+}
+
+static struct drmu_bo_env_s *
+env_boe(drmu_env_t * const du)
+{
+ return &du->boe;
+}
+
+static struct pollqueue *
+env_pollqueue(const drmu_env_t * const du)
+{
+ return du->pq;
+}
+
+static struct drmu_atomic_q_s *
+env_atomic_q(drmu_env_t * const du)
+{
+ return &du->aq;
+}
+
+
+static void
+env_free(drmu_env_t * const du)
+{
+ if (!du)
+ return;
+
+ atomic_q_flush(&du->aq);
+
+ pollqueue_unref(&du->pq);
+ polltask_delete(&du->pt);
+
+ atomic_q_uninit(&du->aq);
+
+ if (du->da_restore) {
+ int rv;
+ if ((rv = drmu_atomic_commit(du->da_restore, DRM_MODE_ATOMIC_ALLOW_MODESET)) != 0) {
+ drmu_atomic_t * bad = drmu_atomic_new(du);
+ drmu_atomic_commit_test(du->da_restore, DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET, bad);
+ drmu_atomic_sub(du->da_restore, bad);
+ if ((rv = drmu_atomic_commit(du->da_restore, DRM_MODE_ATOMIC_ALLOW_MODESET)) != 0)
+ drmu_err(du, "Failed to restore old mode on exit: %s", strerror(-rv));
+ else
+ drmu_err(du, "Failed to completely restore old mode on exit");
+
+ if (drmu_env_log(du)->max_level >= DRMU_LOG_LEVEL_DEBUG)
+ drmu_atomic_dump(bad);
+ drmu_atomic_unref(&bad);
+ }
+ drmu_atomic_unref(&du->da_restore);
+ }
+
+ env_free_planes(du);
+ env_free_conns(du);
+ env_free_crtcs(du);
+ drmu_bo_env_uninit(&du->boe);
+
+ close(du->fd);
+ free(du);
+}
+
+void
+drmu_env_unref(drmu_env_t ** const ppdu)
+{
+ drmu_env_t * const du = *ppdu;
+ if (!du)
+ return;
+ *ppdu = NULL;
+
+ if (atomic_fetch_sub(&du->ref_count, 1) != 0)
+ return;
+
+ env_free(du);
+}
+
+drmu_env_t *
+drmu_env_ref(drmu_env_t * const du)
+{
+ if (du)
+ atomic_fetch_add(&du->ref_count, 1);
+ return du;
+}
+
+static int
+env_object_state_save(drmu_env_t * const du, const uint32_t obj_id, const uint32_t obj_type)
+{
+ drmu_props_t * props;
+ drmu_atomic_t * da;
+ int rv;
+
+ if (!du->da_restore)
+ return -EINVAL;
+
+ if ((props = props_new(du, obj_id, obj_type)) == NULL)
+ return -ENOENT;
+
+ if ((da = drmu_atomic_new(du)) == NULL) {
+ rv = -ENOMEM;
+ goto fail;
+ }
+
+ if ((rv = drmu_atomic_props_add_save(da, obj_id, props)) != 0)
+ goto fail;
+
+ props_free(props);
+ return drmu_atomic_env_restore_add_snapshot(&da);
+
+fail:
+ if (props)
+ props_free(props);
+ return rv;
+}
+
+int
+drmu_env_restore_enable(drmu_env_t * const du)
+{
+ if (du->da_restore)
+ return 0;
+ if ((du->da_restore = drmu_atomic_new(du)) == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+bool
+drmu_env_restore_is_enabled(const drmu_env_t * const du)
+{
+ return du->da_restore != NULL;
+}
+
+int
+drmu_atomic_env_restore_add_snapshot(drmu_atomic_t ** const ppda)
+{
+ drmu_atomic_t * da = *ppda;
+ drmu_atomic_t * fails = NULL;
+ drmu_env_t * const du = drmu_atomic_env(da);
+
+ *ppda = NULL;
+
+ if (!du || !du->da_restore) {
+ drmu_atomic_unref(&da);
+ return 0;
+ }
+
+ // Lose anything we can't restore
+ fails = drmu_atomic_new(du);
+ if (drmu_atomic_commit_test(da, DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_ATOMIC_TEST_ONLY, fails) != 0)
+ drmu_atomic_sub(da, fails);
+ drmu_atomic_unref(&fails);
+
+ return drmu_atomic_merge(du->da_restore, &da);
+}
+
+#define EVT(p) ((const struct drm_event *)(p))
+static int
+evt_read(drmu_env_t * const du)
+{
+ uint8_t buf[128];
+ const ssize_t rlen = read(drmu_fd(du), buf, sizeof(buf));
+ size_t i;
+
+ if (rlen < 0) {
+ const int err = errno;
+ drmu_err(du, "Event read failure: %s", strerror(err));
+ return -err;
+ }
+
+ for (i = 0;
+ i + sizeof(struct drm_event) <= (size_t)rlen && EVT(buf + i)->length <= (size_t)rlen - i;
+ i += EVT(buf + i)->length) {
+ switch (EVT(buf + i)->type) {
+ case DRM_EVENT_FLIP_COMPLETE:
+ {
+ const struct drm_event_vblank * const vb = (struct drm_event_vblank*)(buf + i);
+ if (EVT(buf + i)->length < sizeof(*vb))
+ break;
+
+ drmu_atomic_page_flip_cb(du, (void *)(uintptr_t)vb->user_data);
+ break;
+ }
+ default:
+ drmu_warn(du, "Unexpected DRM event #%x", EVT(buf + i)->type);
+ break;
+ }
+ }
+
+ if (i != (size_t)rlen)
+ drmu_warn(du, "Partial event received: len=%zd, processed=%zd", rlen, i);
+
+ return 0;
+}
+#undef EVT
+
+static void
+drmu_env_polltask_cb(void * v, short revents)
+{
+ drmu_env_t * const du = v;
+
+ if (revents == 0) {
+ drmu_debug(du, "%s: Timeout", __func__);
+ }
+ else {
+ evt_read(du);
+ }
+
+ pollqueue_add_task(du->pt, 1000);
+}
+
+static int
+env_set_client_cap(drmu_env_t * const du, uint64_t cap_id, uint64_t cap_val)
+{
+ struct drm_set_client_cap cap = {
+ .capability = cap_id,
+ .value = cap_val
+ };
+ return drmu_ioctl(du, DRM_IOCTL_SET_CLIENT_CAP, &cap);
+}
+
+// Closes fd on failure
+drmu_env_t *
+drmu_env_new_fd(const int fd, const struct drmu_log_env_s * const log)
+{
+ drmu_env_t * const du = calloc(1, sizeof(*du));
+ int rv;
+ uint32_t * conn_ids = NULL;
+ uint32_t * crtc_ids = NULL;
+ uint32_t * plane_ids = NULL;
+
+ if (!du) {
+ drmu_err_log(log, "Failed to create du: No memory");
+ close(fd);
+ return NULL;
+ }
+
+ du->log = (log == NULL) ? drmu_log_env_none : *log;
+ du->fd = fd;
+
+ drmu_bo_env_init(&du->boe);
+ atomic_q_init(&du->aq);
+
+ if ((du->pq = pollqueue_new()) == NULL) {
+ drmu_err(du, "Failed to create pollqueue");
+ goto fail1;
+ }
+ if ((du->pt = polltask_new(du->pq, du->fd, POLLIN | POLLPRI, drmu_env_polltask_cb, du)) == NULL) {
+ drmu_err(du, "Failed to create polltask");
+ goto fail1;
+ }
+
+ // We want the primary plane for video
+ if ((rv = env_set_client_cap(du, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) != 0)
+ drmu_debug(du, "Failed to set universal planes cap");
+ // We need atomic for almost everything we do
+ if ((rv = env_set_client_cap(du, DRM_CLIENT_CAP_ATOMIC, 1)) != 0) {
+ drmu_err(du, "Failed to set atomic cap");
+ goto fail1;
+ }
+ // We can understand AR info
+ if ((rv = env_set_client_cap(du, DRM_CLIENT_CAP_ASPECT_RATIO, 1)) != 0)
+ drmu_debug(du, "Failed to set AR cap");
+ // We would like to see writeback connectors
+ if ((rv = env_set_client_cap(du, DRM_CLIENT_CAP_WRITEBACK_CONNECTORS, 1)) != 0)
+ drmu_debug(du, "Failed to set writeback cap");
+
+ {
+ struct drm_mode_get_plane_res res;
+ uint32_t req_planes = 0;
+
+ do {
+ memset(&res, 0, sizeof(res));
+ res.plane_id_ptr = (uintptr_t)plane_ids;
+ res.count_planes = req_planes;
+
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETPLANERESOURCES, &res)) != 0) {
+ drmu_err(du, "Failed to get resources: %s", strerror(-rv));
+ goto fail1;
+ }
+ } while ((rv = retry_alloc_u32(&plane_ids, &req_planes, res.count_planes)) == 1);
+ if (rv < 0)
+ goto fail1;
+
+ if (env_planes_populate(du, res.count_planes, plane_ids) != 0)
+ goto fail1;
+
+ free(plane_ids);
+ plane_ids = NULL;
+ }
+
+ {
+ struct drm_mode_card_res res;
+ uint32_t req_conns = 0;
+ uint32_t req_crtcs = 0;
+
+ for (;;) {
+ memset(&res, 0, sizeof(res));
+ res.crtc_id_ptr = (uintptr_t)crtc_ids;
+ res.connector_id_ptr = (uintptr_t)conn_ids;
+ res.count_crtcs = req_crtcs;
+ res.count_connectors = req_conns;
+
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_GETRESOURCES, &res)) != 0) {
+ drmu_err(du, "Failed to get resources: %s", strerror(-rv));
+ goto fail1;
+ }
+
+ if (res.count_crtcs <= req_crtcs && res.count_connectors <= req_conns)
+ break;
+
+ if (retry_alloc_u32(&conn_ids, &req_conns, res.count_connectors) < 0 ||
+ retry_alloc_u32(&crtc_ids, &req_crtcs, res.count_crtcs) < 0)
+ goto fail1;
+ }
+
+ if (env_conn_populate(du, res.count_connectors, conn_ids) != 0)
+ goto fail1;
+ if (env_crtc_populate(du, res.count_crtcs, crtc_ids) != 0)
+ goto fail1;
+
+ free(conn_ids);
+ free(crtc_ids);
+ conn_ids = NULL;
+ crtc_ids = NULL;
+ }
+
+ pollqueue_add_task(du->pt, 1000);
+
+ free(plane_ids);
+ return du;
+
+fail1:
+ env_free(du);
+ free(conn_ids);
+ free(crtc_ids);
+ free(plane_ids);
+ return NULL;
+}
+
+drmu_env_t *
+drmu_env_new_open(const char * name, const struct drmu_log_env_s * const log2)
+{
+ const struct drmu_log_env_s * const log = (log2 == NULL) ? &drmu_log_env_none : log2;
+ int fd = drmOpen(name, NULL);
+ if (fd == -1) {
+ drmu_err_log(log, "Failed to open %s", name);
+ return NULL;
+ }
+ return drmu_env_new_fd(fd, log);
+}
+
+//----------------------------------------------------------------------------
+//
+// Logging
+
+static void
+log_none_cb(void * v, enum drmu_log_level_e level, const char * fmt, va_list vl)
+{
+ (void)v;
+ (void)level;
+ (void)fmt;
+ (void)vl;
+}
+
+const struct drmu_log_env_s drmu_log_env_none = {
+ .fn = log_none_cb,
+ .v = NULL,
+ .max_level = DRMU_LOG_LEVEL_NONE
+};
+
+void
+drmu_log_generic(const struct drmu_log_env_s * const log, const enum drmu_log_level_e level,
+ const char * const fmt, ...)
+{
+ va_list vl;
+ va_start(vl, fmt);
+ log->fn(log->v, level, fmt, vl);
+ va_end(vl);
+}
+
+
diff --git a/modules/video_output/drmu/drmu.h b/modules/video_output/drmu/drmu.h
new file mode 100644
index 0000000000..42782594d9
--- /dev/null
+++ b/modules/video_output/drmu/drmu.h
2023-03-29 17:41:09 +00:00
@@ -0,0 +1,591 @@
+#ifndef _DRMU_DRMU_H
+#define _DRMU_DRMU_H
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
2023-03-29 17:41:09 +00:00
+#include "drmu_chroma.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct drmu_blob_s;
+typedef struct drmu_blob_s drmu_blob_t;
+
+struct drmu_prop_enum_s;
+typedef struct drmu_prop_enum_s drmu_prop_enum_t;
+
+struct drmu_prop_range_s;
+typedef struct drmu_prop_range_s drmu_prop_range_t;
+
+struct drmu_bo_s;
+typedef struct drmu_bo_s drmu_bo_t;
+struct drmu_bo_env_s;
+typedef struct drmu_bo_env_s drmu_bo_env_t;
+
+struct drmu_fb_s;
+typedef struct drmu_fb_s drmu_fb_t;
+
+struct drmu_prop_object_s;
+typedef struct drmu_prop_object_s drmu_prop_object_t;
+
+struct drmu_pool_s;
+typedef struct drmu_pool_s drmu_pool_t;
+
+struct drmu_crtc_s;
+typedef struct drmu_crtc_s drmu_crtc_t;
+
+struct drmu_conn_s;
+typedef struct drmu_conn_s drmu_conn_t;
+
+struct drmu_plane_s;
+typedef struct drmu_plane_s drmu_plane_t;
+
+struct drmu_atomic_s;
+
+struct drmu_env_s;
+typedef struct drmu_env_s drmu_env_t;
+
+typedef struct drmu_rect_s {
+ int32_t x, y;
+ uint32_t w, h;
+} drmu_rect_t;
+
+typedef struct drmu_ufrac_s {
+ unsigned int num;
+ unsigned int den;
+} drmu_ufrac_t;
+
+// HDR enums is copied from linux include/linux/hdmi.h (strangely not part of uapi)
+enum hdmi_metadata_type
+{
+ HDMI_STATIC_METADATA_TYPE1 = 0,
+};
+enum hdmi_eotf
+{
+ HDMI_EOTF_TRADITIONAL_GAMMA_SDR,
+ HDMI_EOTF_TRADITIONAL_GAMMA_HDR,
+ HDMI_EOTF_SMPTE_ST2084,
+ HDMI_EOTF_BT_2100_HLG,
+};
+
+typedef enum drmu_isset_e {
+ DRMU_ISSET_UNSET = 0, // Thing unset
+ DRMU_ISSET_NULL, // Thing is empty
+ DRMU_ISSET_SET, // Thing has valid data
+} drmu_isset_t;
+
+drmu_ufrac_t drmu_ufrac_reduce(drmu_ufrac_t x);
+
+static inline int_fast32_t
+drmu_rect_rescale_1s(int_fast32_t x, uint_fast32_t mul, uint_fast32_t div)
+{
+ const int_fast64_t m = x * (int_fast64_t)mul;
+ const uint_fast32_t d2 = div/2;
+ return div == 0 ? (int_fast32_t)m :
+ m >= 0 ? (int_fast32_t)(((uint_fast64_t)m + d2) / div) :
+ -(int_fast32_t)(((uint_fast64_t)(-m) + d2) / div);
+}
+
+static inline uint_fast32_t
+drmu_rect_rescale_1u(uint_fast32_t x, uint_fast32_t mul, uint_fast32_t div)
+{
+ const uint_fast64_t m = x * (uint_fast64_t)mul;
+ return (uint_fast32_t)(div == 0 ? m : (m + div/2) / div);
+}
+
+static inline drmu_rect_t
+drmu_rect_rescale(const drmu_rect_t s, const drmu_rect_t mul, const drmu_rect_t div)
+{
+ return (drmu_rect_t){
+ .x = drmu_rect_rescale_1s(s.x - div.x, mul.w, div.w) + mul.x,
+ .y = drmu_rect_rescale_1s(s.y - div.y, mul.h, div.h) + mul.y,
+ .w = drmu_rect_rescale_1u(s.w, mul.w, div.w),
+ .h = drmu_rect_rescale_1u(s.h, mul.h, div.h)
+ };
+}
+
+static inline drmu_rect_t
+drmu_rect_add_xy(const drmu_rect_t a, const drmu_rect_t b)
+{
+ return (drmu_rect_t){
+ .x = a.x + b.x,
+ .y = a.y + b.y,
+ .w = a.w,
+ .h = a.h
+ };
+}
+
+static inline drmu_rect_t
+drmu_rect_wh(const unsigned int w, const unsigned int h)
+{
+ return (drmu_rect_t){
+ .w = w,
+ .h = h
+ };
+}
+
+static inline drmu_rect_t
+drmu_rect_shl16(const drmu_rect_t a)
+{
+ return (drmu_rect_t){
+ .x = a.x << 16,
+ .y = a.y << 16,
+ .w = a.w << 16,
+ .h = a.h << 16
+ };
+}
+
+// Blob
+
+void drmu_blob_unref(drmu_blob_t ** const ppBlob);
+uint32_t drmu_blob_id(const drmu_blob_t * const blob);
+// blob data & length
+const void * drmu_blob_data(const drmu_blob_t * const blob);
+size_t drmu_blob_len(const drmu_blob_t * const blob);
+
+drmu_blob_t * drmu_blob_ref(drmu_blob_t * const blob);
+// Make a new blob - keeps a copy of the data
+drmu_blob_t * drmu_blob_new(drmu_env_t * const du, const void * const data, const size_t len);
+// Update a blob with new data
+// Creates if it didn't exist before, unrefs if data NULL
+int drmu_blob_update(drmu_env_t * const du, drmu_blob_t ** const ppblob, const void * const data, const size_t len);
+// Create a new blob from an existing blob_id
+drmu_blob_t * drmu_blob_copy_id(drmu_env_t * const du, uint32_t blob_id);
+int drmu_atomic_add_prop_blob(struct drmu_atomic_s * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_blob_t * const blob);
+
+// Enum & bitmask
+// These are very close to the same thing so we use the same struct
+typedef drmu_prop_enum_t drmu_prop_bitmask_t;
+
+// Ptr to value of the named enum/bit, NULL if not found or pen == NULL. If bitmask then bit number
+const uint64_t * drmu_prop_enum_value(const drmu_prop_enum_t * const pen, const char * const name);
+// Bitmask only - value as a (single-bit) bitmask - 0 if not found or not bitmask or pen == NULL
+uint64_t drmu_prop_bitmask_value(const drmu_prop_enum_t * const pen, const char * const name);
+
+uint32_t drmu_prop_enum_id(const drmu_prop_enum_t * const pen);
+#define drmu_prop_bitmask_id drmu_prop_enum_id
+void drmu_prop_enum_delete(drmu_prop_enum_t ** const pppen);
+#define drmu_prop_bitmask_delete drmu_prop_enum_delete
+drmu_prop_enum_t * drmu_prop_enum_new(drmu_env_t * const du, const uint32_t id);
+#define drmu_prop_bitmask_new drmu_prop_enum_new
+int drmu_atomic_add_prop_enum(struct drmu_atomic_s * const da, const uint32_t obj_id, const drmu_prop_enum_t * const pen, const char * const name);
+int drmu_atomic_add_prop_bitmask(struct drmu_atomic_s * const da, const uint32_t obj_id, const drmu_prop_enum_t * const pen, const uint64_t value);
+
+// Range
+
+void drmu_prop_range_delete(drmu_prop_range_t ** pppra);
+bool drmu_prop_range_validate(const drmu_prop_range_t * const pra, const uint64_t x);
2023-03-29 17:41:09 +00:00
+bool drmu_prop_range_immutable(const drmu_prop_range_t * const pra);
+uint64_t drmu_prop_range_max(const drmu_prop_range_t * const pra);
+uint64_t drmu_prop_range_min(const drmu_prop_range_t * const pra);
+uint32_t drmu_prop_range_id(const drmu_prop_range_t * const pra);
2023-03-29 17:41:09 +00:00
+const char * drmu_prop_range_name(const drmu_prop_range_t * const pra);
+drmu_prop_range_t * drmu_prop_range_new(drmu_env_t * const du, const uint32_t id);
+int drmu_atomic_add_prop_range(struct drmu_atomic_s * const da, const uint32_t obj_id, const drmu_prop_range_t * const pra, const uint64_t x);
+
+// BO
+
+struct drm_mode_create_dumb;
+
+void drmu_bo_unref(drmu_bo_t ** const ppbo);
+drmu_bo_t * drmu_bo_ref(drmu_bo_t * const bo);
+drmu_bo_t * drmu_bo_new_fd(drmu_env_t *const du, const int fd);
+drmu_bo_t * drmu_bo_new_dumb(drmu_env_t *const du, struct drm_mode_create_dumb * const d);
+void drmu_bo_env_uninit(drmu_bo_env_t * const boe);
+void drmu_bo_env_init(drmu_bo_env_t * boe);
+
+// fb
+struct hdr_output_metadata;
2023-03-29 17:41:09 +00:00
+struct drmu_fmt_info_s;
+
+// Called pre delete.
+// Zero returned means continue delete.
+// Non-zero means stop delete - fb will have zero refs so will probably want a new ref
+// before next use
+typedef int (* drmu_fb_pre_delete_fn)(struct drmu_fb_s * dfb, void * v);
2023-03-29 17:41:09 +00:00
+// Called after an fb has been deleted and therefore has ceased using any
+// user resources
+typedef void (* drmu_fb_on_delete_fn)(void * v);
+
+void drmu_fb_pre_delete_set(drmu_fb_t *const dfb, drmu_fb_pre_delete_fn fn, void * v);
+void drmu_fb_pre_delete_unset(drmu_fb_t *const dfb);
+unsigned int drmu_fb_pixel_bits(const drmu_fb_t * const dfb);
2023-03-28 20:02:15 +00:00
+uint32_t drmu_fb_pixel_format(const drmu_fb_t * const dfb);
+uint64_t drmu_fb_modifier(const drmu_fb_t * const dfb, const unsigned int plane);
+drmu_fb_t * drmu_fb_new_dumb(drmu_env_t * const du, uint32_t w, uint32_t h, const uint32_t format);
+drmu_fb_t * drmu_fb_new_dumb_mod(drmu_env_t * const du, uint32_t w, uint32_t h, const uint32_t format, const uint64_t mod);
+drmu_fb_t * drmu_fb_realloc_dumb(drmu_env_t * const du, drmu_fb_t * dfb, uint32_t w, uint32_t h, const uint32_t format);
+void drmu_fb_unref(drmu_fb_t ** const ppdfb);
+drmu_fb_t * drmu_fb_ref(drmu_fb_t * const dfb);
+
+#define DRMU_FB_PIXEL_BLEND_UNSET NULL
+#define DRMU_FB_PIXEL_BLEND_PRE_MULTIPLIED "Pre-multiplied" // Default
+#define DRMU_FB_PIXEL_BLEND_COVERAGE "Coverage" // Not premultipled
+#define DRMU_FB_PIXEL_BLEND_NONE "None" // Ignore pixel alpha (opaque)
+int drmu_fb_pixel_blend_mode_set(drmu_fb_t *const dfb, const char * const mode);
+
+uint32_t drmu_fb_pitch(const drmu_fb_t *const dfb, const unsigned int layer);
+// Pitch2 is only a sand thing
+uint32_t drmu_fb_pitch2(const drmu_fb_t *const dfb, const unsigned int layer);
+void * drmu_fb_data(const drmu_fb_t *const dfb, const unsigned int layer);
+uint32_t drmu_fb_width(const drmu_fb_t *const dfb);
+uint32_t drmu_fb_height(const drmu_fb_t *const dfb);
+// Set cropping (fractional) - x, y, relative to active x, y (and must be +ve)
+int drmu_fb_crop_frac_set(drmu_fb_t *const dfb, drmu_rect_t crop_frac);
+// get cropping (fractional 16.16) x, y relative to active area
+drmu_rect_t drmu_fb_crop_frac(const drmu_fb_t *const dfb);
+// get active area (all valid pixels - buffer can/will contain padding outside this)
+// rect in pixels (not fractional)
+drmu_rect_t drmu_fb_active(const drmu_fb_t *const dfb);
+
+int drmu_atomic_add_prop_fb(struct drmu_atomic_s * const da, const uint32_t obj_id, const uint32_t prop_id, drmu_fb_t * const dfb);
+
+// FB creation helpers - only use for creatino of new FBs
+drmu_fb_t * drmu_fb_int_alloc(drmu_env_t * const du);
+void drmu_fb_int_free(drmu_fb_t * const dfb);
+// Set size
+// w, h are buffer sizes, active is the valid pixel area inside that
+// crop will be set to the whole active area
+void drmu_fb_int_fmt_size_set(drmu_fb_t *const dfb, uint32_t fmt, uint32_t w, uint32_t h, const drmu_rect_t active);
+// All assumed to be const strings that do not need freed
+typedef const char * drmu_color_encoding_t;
+#define DRMU_COLOR_ENCODING_UNSET NULL
+#define DRMU_COLOR_ENCODING_BT2020 "ITU-R BT.2020 YCbCr"
+#define DRMU_COLOR_ENCODING_BT709 "ITU-R BT.709 YCbCr"
+#define DRMU_COLOR_ENCODING_BT601 "ITU-R BT.601 YCbCr"
+static inline bool drmu_color_encoding_is_set(const drmu_color_encoding_t x) {return x != NULL;}
+// Note: Color range only applies to YCbCr planes - ignored for RGB
+typedef const char * drmu_color_range_t;
+#define DRMU_COLOR_RANGE_UNSET NULL
+#define DRMU_COLOR_RANGE_YCBCR_FULL_RANGE "YCbCr full range"
+#define DRMU_COLOR_RANGE_YCBCR_LIMITED_RANGE "YCbCr limited range"
+static inline bool drmu_color_range_is_set(const drmu_color_range_t x) {return x != NULL;}
+typedef const char * drmu_colorspace_t;
+#define DRMU_COLORSPACE_UNSET NULL
+#define DRMU_COLORSPACE_DEFAULT "Default"
2022-12-08 21:49:23 +00:00
+#define DRMU_COLORSPACE_BT2020_CYCC "BT2020_CYCC"
+#define DRMU_COLORSPACE_BT2020_RGB "BT2020_RGB"
2022-12-08 21:49:23 +00:00
+#define DRMU_COLORSPACE_BT2020_YCC "BT2020_YCC"
+#define DRMU_COLORSPACE_BT709_YCC "BT709_YCC"
+#define DRMU_COLORSPACE_DCI_P3_RGB_D65 "DCI-P3_RGB_D65"
+#define DRMU_COLORSPACE_DCI_P3_RGB_THEATER "DCI-P3_RGB_Theater"
+#define DRMU_COLORSPACE_SMPTE_170M_YCC "SMPTE_170M_YCC"
+#define DRMU_COLORSPACE_SYCC_601 "SYCC_601"
+#define DRMU_COLORSPACE_XVYCC_601 "XVYCC_601"
+#define DRMU_COLORSPACE_XVYCC_709 "XVYCC_709"
+static inline bool drmu_colorspace_is_set(const drmu_colorspace_t x) {return x != NULL;}
+typedef const char * drmu_broadcast_rgb_t;
+#define DRMU_BROADCAST_RGB_UNSET NULL
+#define DRMU_BROADCAST_RGB_AUTOMATIC "Automatic"
+#define DRMU_BROADCAST_RGB_FULL "Full"
+#define DRMU_BROADCAST_RGB_LIMITED_16_235 "Limited 16:235"
+static inline bool drmu_broadcast_rgb_is_set(const drmu_broadcast_rgb_t x) {return x != NULL;}
+void drmu_fb_int_color_set(drmu_fb_t *const dfb, const drmu_color_encoding_t enc, const drmu_color_range_t range, const drmu_colorspace_t space);
+void drmu_fb_int_chroma_siting_set(drmu_fb_t *const dfb, const drmu_chroma_siting_t siting);
+void drmu_fb_int_on_delete_set(drmu_fb_t *const dfb, drmu_fb_on_delete_fn fn, void * v);
+void drmu_fb_int_bo_set(drmu_fb_t *const dfb, unsigned int i, drmu_bo_t * const bo);
+void drmu_fb_int_layer_set(drmu_fb_t *const dfb, unsigned int i, unsigned int obj_idx, uint32_t pitch, uint32_t offset);
+void drmu_fb_int_layer_mod_set(drmu_fb_t *const dfb, unsigned int i, unsigned int obj_idx, uint32_t pitch, uint32_t offset, uint64_t modifier);
+drmu_isset_t drmu_fb_hdr_metadata_isset(const drmu_fb_t *const dfb);
+const struct hdr_output_metadata * drmu_fb_hdr_metadata_get(const drmu_fb_t *const dfb);
+drmu_broadcast_rgb_t drmu_color_range_to_broadcast_rgb(const drmu_color_range_t range);
+drmu_colorspace_t drmu_fb_colorspace_get(const drmu_fb_t * const dfb);
+drmu_color_range_t drmu_fb_color_range_get(const drmu_fb_t * const dfb);
2023-03-29 17:41:09 +00:00
+const struct drmu_fmt_info_s * drmu_fb_format_info_get(const drmu_fb_t * const dfb);
+void drmu_fb_hdr_metadata_set(drmu_fb_t *const dfb, const struct hdr_output_metadata * meta);
+int drmu_fb_int_make(drmu_fb_t *const dfb);
+
+// Wait for data to become ready when fb used as destination of writeback
+// Returns:
+// -ve error
+// 0 timeout
+// 1 ready
+int drmu_fb_out_fence_wait(drmu_fb_t * const fb, const int timeout_ms);
+
+// fb pool
+
+void drmu_pool_unref(drmu_pool_t ** const pppool);
+drmu_pool_t * drmu_pool_ref(drmu_pool_t * const pool);
+drmu_pool_t * drmu_pool_new(drmu_env_t * const du, unsigned int total_fbs_max);
+drmu_fb_t * drmu_pool_fb_new_dumb(drmu_pool_t * const pool, uint32_t w, uint32_t h, const uint32_t format);
+void drmu_pool_delete(drmu_pool_t ** const pppool);
+
+// Object Id
+
+struct drmu_propinfo_s;
+
+uint32_t drmu_prop_object_value(const drmu_prop_object_t * const obj);
+void drmu_prop_object_unref(drmu_prop_object_t ** ppobj);
+drmu_prop_object_t * drmu_prop_object_new_propinfo(drmu_env_t * const du, const uint32_t obj_id, const struct drmu_propinfo_s * const pi);
+int drmu_atomic_add_prop_object(struct drmu_atomic_s * const da, drmu_prop_object_t * obj, uint32_t val);
+
+// Props
+
+// Grab all the props of an object and add to an atomic
+// * Does not add references to any properties (BO or FB) currently, it maybe
+// should but if so we need to avoid accidentally closing BOs that we inherit
+// from outside when we delete the atomic.
+int drmu_atomic_obj_add_snapshot(struct drmu_atomic_s * const da, const uint32_t objid, const uint32_t objtype);
+
+// CRTC
+
+struct _drmModeModeInfo;
+struct hdr_output_metadata;
+
+void drmu_crtc_delete(drmu_crtc_t ** ppdc);
+drmu_env_t * drmu_crtc_env(const drmu_crtc_t * const dc);
+uint32_t drmu_crtc_id(const drmu_crtc_t * const dc);
+int drmu_crtc_idx(const drmu_crtc_t * const dc);
+
+drmu_crtc_t * drmu_env_crtc_find_id(drmu_env_t * const du, const uint32_t crtc_id);
+drmu_crtc_t * drmu_env_crtc_find_n(drmu_env_t * const du, const unsigned int n);
+
+typedef struct drmu_mode_simple_params_s {
+ unsigned int width;
+ unsigned int height;
+ unsigned int hz_x_1000; // Frame rate * 1000 i.e. 50Hz = 50000 (N.B. frame not field rate if interlaced)
+ drmu_ufrac_t par; // Picture Aspect Ratio (0:0 if unknown)
+ drmu_ufrac_t sar; // Sample Aspect Ratio
+ uint32_t type;
+ uint32_t flags;
+} drmu_mode_simple_params_t;
+
+const struct drm_mode_modeinfo * drmu_crtc_modeinfo(const drmu_crtc_t * const dc);
+// Get simple properties of initial crtc mode
+drmu_mode_simple_params_t drmu_crtc_mode_simple_params(const drmu_crtc_t * const dc);
+
+int drmu_atomic_crtc_add_modeinfo(struct drmu_atomic_s * const da, drmu_crtc_t * const dc, const struct drm_mode_modeinfo * const modeinfo);
+int drmu_atomic_crtc_add_active(struct drmu_atomic_s * const da, drmu_crtc_t * const dc, unsigned int val);
+
+bool drmu_crtc_is_claimed(const drmu_crtc_t * const dc);
+void drmu_crtc_unref(drmu_crtc_t ** const ppdc);
+drmu_crtc_t * drmu_crtc_ref(drmu_crtc_t * const dc);
+// A Conn should be claimed before any op that might change its state
+int drmu_crtc_claim_ref(drmu_crtc_t * const dc);
+
+// Connector
+
+// Set none if m=NULL
2022-12-08 21:49:23 +00:00
+int drmu_atomic_conn_add_hdr_metadata(struct drmu_atomic_s * const da, drmu_conn_t * const dn, const struct hdr_output_metadata * const m);
+
+// False set max_bpc to 8, true max value
2022-12-08 21:49:23 +00:00
+int drmu_atomic_conn_add_hi_bpc(struct drmu_atomic_s * const da, drmu_conn_t * const dn, bool hi_bpc);
+
2022-12-08 21:49:23 +00:00
+int drmu_atomic_conn_add_colorspace(struct drmu_atomic_s * const da, drmu_conn_t * const dn, const drmu_colorspace_t colorspace);
+int drmu_atomic_conn_add_broadcast_rgb(struct drmu_atomic_s * const da, drmu_conn_t * const dn, const drmu_broadcast_rgb_t bcrgb);
+
+// Add crtc id
+int drmu_atomic_conn_add_crtc(struct drmu_atomic_s * const da, drmu_conn_t * const dn, drmu_crtc_t * const dc);
+
+// Add writeback fb & fence
+// Neither makes sense without the other so do together
+int drmu_atomic_conn_add_writeback_fb(struct drmu_atomic_s * const da, drmu_conn_t * const dn, drmu_fb_t * const dfb);
+
+
+const struct drm_mode_modeinfo * drmu_conn_modeinfo(const drmu_conn_t * const dn, const int mode_id);
+drmu_mode_simple_params_t drmu_conn_mode_simple_params(const drmu_conn_t * const dn, const int mode_id);
+
+// Beware: this refects initial value or the last thing set, but currently
+// has no way of guessing if the atomic from the set was ever committed
+// successfully
+uint32_t drmu_conn_crtc_id_get(const drmu_conn_t * const dn);
+
+// Bitmask of CRTCs that might be able to use this Conn
+uint32_t drmu_conn_possible_crtcs(const drmu_conn_t * const dn);
+
+bool drmu_conn_is_output(const drmu_conn_t * const dn);
+bool drmu_conn_is_writeback(const drmu_conn_t * const dn);
+const char * drmu_conn_name(const drmu_conn_t * const dn);
+unsigned int drmu_conn_idx_get(const drmu_conn_t * const dn);
+
+// Retrieve the the n-th conn. Use for iteration. Returns NULL when none left
+drmu_conn_t * drmu_env_conn_find_n(drmu_env_t * const du, const unsigned int n);
+
+bool drmu_conn_is_claimed(const drmu_conn_t * const dn);
+void drmu_conn_unref(drmu_conn_t ** const ppdn);
+drmu_conn_t * drmu_conn_ref(drmu_conn_t * const dn);
+// A Conn should be claimed before any op that might change its state
+int drmu_conn_claim_ref(drmu_conn_t * const dn);
+
+
+// Plane
+
+uint32_t drmu_plane_id(const drmu_plane_t * const dp);
2023-03-28 20:02:15 +00:00
+
+#define DRMU_PLANE_TYPE_CURSOR 4
+#define DRMU_PLANE_TYPE_PRIMARY 2
+#define DRMU_PLANE_TYPE_OVERLAY 1
+#define DRMU_PLANE_TYPE_UNKNOWN 0
+unsigned int drmu_plane_type(const drmu_plane_t * const dp);
+
+const uint32_t * drmu_plane_formats(const drmu_plane_t * const dp, unsigned int * const pCount);
+bool drmu_plane_format_check(const drmu_plane_t * const dp, const uint32_t format, const uint64_t modifier);
+
+// Alpha: -1 = no not set, 0 = transparent, 0xffff = opaque
+#define DRMU_PLANE_ALPHA_UNSET (-1)
+#define DRMU_PLANE_ALPHA_TRANSPARENT 0
+#define DRMU_PLANE_ALPHA_OPAQUE 0xffff
2022-12-08 21:49:23 +00:00
+int drmu_atomic_plane_add_alpha(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int alpha);
+
2023-03-28 20:02:15 +00:00
+int drmu_atomic_plane_add_zpos(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int zpos);
+
+// X, Y & TRANSPOSE can be ORed to get all others
+#define DRMU_PLANE_ROTATION_0 0
+#define DRMU_PLANE_ROTATION_X_FLIP 1
+#define DRMU_PLANE_ROTATION_Y_FLIP 2
+#define DRMU_PLANE_ROTATION_180 3
+// *** These don't exist on Pi - no inherent transpose
+#define DRMU_PLANE_ROTATION_TRANSPOSE 4
+#define DRMU_PLANE_ROTATION_90 5 // Rotate 90 clockwise
+#define DRMU_PLANE_ROTATION_270 6 // Rotate 90 anti-cockwise
+#define DRMU_PLANE_ROTATION_180_TRANSPOSE 7 // Rotate 180 & transpose
2022-12-08 21:49:23 +00:00
+int drmu_atomic_plane_add_rotation(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const int rot);
+
+int drmu_atomic_plane_add_chroma_siting(struct drmu_atomic_s * const da, const drmu_plane_t * const dp, const drmu_chroma_siting_t siting);
+
2022-12-08 21:49:23 +00:00
+// Adds the fb to the plane along with all fb properties that apply to a plane
+// pos is dest rect on the plane in full pixels (not frac)
+int drmu_atomic_plane_add_fb(struct drmu_atomic_s * const da, drmu_plane_t * const dp, drmu_fb_t * const dfb, const drmu_rect_t pos);
+
+// Unref a plane
+void drmu_plane_unref(drmu_plane_t ** const ppdp);
+
+// Ref a plane - expects it is already associated
+drmu_plane_t * drmu_plane_ref(drmu_plane_t * const dp);
+
+// Associate a plane with a crtc and ref it
+// Returns -EBUSY if plane already associated
+int drmu_plane_ref_crtc(drmu_plane_t * const dp, drmu_crtc_t * const dc);
+
2023-03-28 20:02:15 +00:00
+typedef bool (*drmu_plane_new_find_ok_fn)(const drmu_plane_t * dp, void * v);
+
2023-03-28 20:02:15 +00:00
+// Find a "free" plane that satisfies (returns true) the ok callback
+// Does not ref
+drmu_plane_t * drmu_plane_new_find(drmu_crtc_t * const dc, const drmu_plane_new_find_ok_fn cb, void * const v);
+// Find a "free" plane of the given type. Types can be ORed
+// Does not ref
+drmu_plane_t * drmu_plane_new_find_type(drmu_crtc_t * const dc, const unsigned int req_type);
+
+drmu_plane_t * drmu_env_plane_find_n(drmu_env_t * const du, const unsigned int n);
+
+
+// Env
+struct drmu_log_env_s;
+
+// Q the atomic on its associated env
+//
+// in-progress = The commit has been done but no ack yet
+// pending = Commit Qed to be done when the in-progress commit has
+// completed
+//
+// If there is a pending commit this atomic wiill be merged with it
+int drmu_atomic_queue(struct drmu_atomic_s ** ppda);
+// Wait for there to be no pending commit (there may be a commit in
+// progress)
+int drmu_env_queue_wait(drmu_env_t * const du);
+
+// Do ioctl - returns -errno on error, 0 on success
+// deals with recalling the ioctl when required
+int drmu_ioctl(const drmu_env_t * const du, unsigned long req, void * arg);
+int drmu_fd(const drmu_env_t * const du);
+const struct drmu_log_env_s * drmu_env_log(const drmu_env_t * const du);
+void drmu_env_unref(drmu_env_t ** const ppdu);
+drmu_env_t * drmu_env_ref(drmu_env_t * const du);
+// Restore state on env close
+int drmu_env_restore_enable(drmu_env_t * const du);
+bool drmu_env_restore_is_enabled(const drmu_env_t * const du);
+// Add an object snapshot to the restore state
+// Tests for commitability and removes any props that won't commit
+int drmu_atomic_env_restore_add_snapshot(struct drmu_atomic_s ** const ppda);
+
+// Open a drmu environment with the drm fd
+// Takes a logging structure so early errors can be reported.
+// If log = NULL logging is disabled (set to drmu_log_env_none).
+drmu_env_t * drmu_env_new_fd(const int fd, const struct drmu_log_env_s * const log);
+drmu_env_t * drmu_env_new_open(const char * name, const struct drmu_log_env_s * const log);
+
+// Logging
+
+enum drmu_log_level_e {
+ DRMU_LOG_LEVEL_NONE = -1, // Max level specifier for nothing (not a real level)
+ DRMU_LOG_LEVEL_MESSAGE = 0, // (Nearly) always printed info
+ DRMU_LOG_LEVEL_ERROR, // Error
+ DRMU_LOG_LEVEL_WARNING,
+ DRMU_LOG_LEVEL_INFO, // Interesting but not critical info
+ DRMU_LOG_LEVEL_DEBUG, // Info only useful for debug
+ DRMU_LOG_LEVEL_ALL, // Max level specifier for everything (not a real level)
+};
+
+typedef void drmu_log_fn(void * v, enum drmu_log_level_e level, const char * fmt, va_list vl);
+
+typedef struct drmu_log_env_s {
+ drmu_log_fn * fn;
+ void * v;
+ enum drmu_log_level_e max_level;
+} drmu_log_env_t;
+
+extern const struct drmu_log_env_s drmu_log_env_none; // pre-built do-nothing log structure
+
+// drmu_atomic
+
+struct drmu_atomic_s;
+typedef struct drmu_atomic_s drmu_atomic_t;
+
+void drmu_atomic_dump(const drmu_atomic_t * const da);
+drmu_env_t * drmu_atomic_env(const drmu_atomic_t * const da);
+void drmu_atomic_unref(drmu_atomic_t ** const ppda);
+drmu_atomic_t * drmu_atomic_ref(drmu_atomic_t * const da);
+drmu_atomic_t * drmu_atomic_new(drmu_env_t * const du);
+int drmu_atomic_merge(drmu_atomic_t * const a, drmu_atomic_t ** const ppb);
+
+// Remove all els in a that are also in b
+// b may be sorted (if not already) but is otherwise unchanged
+void drmu_atomic_sub(drmu_atomic_t * const a, drmu_atomic_t * const b);
+
+// flags are DRM_MODE_ATOMIC_xxx (e.g. DRM_MODE_ATOMIC_TEST_ONLY) and DRM_MODE_PAGE_FLIP_xxx
+int drmu_atomic_commit(const drmu_atomic_t * const da, uint32_t flags);
+// Attempt commit - if it fails add failing members to da_fail
+// This does NOT remove failing props from da. If da_fail == NULL then same as _commit
+int drmu_atomic_commit_test(const drmu_atomic_t * const da, uint32_t flags, drmu_atomic_t * const da_fail);
+
+typedef void drmu_prop_unref_fn(void * v);
+typedef void drmu_prop_ref_fn(void * v);
+typedef void drmu_prop_commit_fn(void * v, uint64_t value);
+
+typedef struct drmu_atomic_prop_fns_s {
+ drmu_prop_ref_fn * ref;
+ drmu_prop_unref_fn * unref;
+ drmu_prop_commit_fn * commit;
+} drmu_atomic_prop_fns_t;
+
+drmu_prop_ref_fn drmu_prop_fn_null_unref;
+drmu_prop_unref_fn drmu_prop_fn_null_ref;
+drmu_prop_commit_fn drmu_prop_fn_null_commit;
+
+int drmu_atomic_add_prop_generic(drmu_atomic_t * const da,
+ const uint32_t obj_id, const uint32_t prop_id, const uint64_t value,
+ const drmu_atomic_prop_fns_t * const fns, void * const v);
+int drmu_atomic_add_prop_value(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, const uint64_t value);
+
+// drmu_xlease
+
+drmu_env_t * drmu_env_new_xlease(const struct drmu_log_env_s * const log);
+
+// drmu_xdri3
+
+drmu_env_t * drmu_env_new_xdri3(const drmu_log_env_t * const log);
+
2023-03-29 17:41:09 +00:00
+// drmu_waylease
+
+drmu_env_t * drmu_env_new_waylease(const struct drmu_log_env_s * const log);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/modules/video_output/drmu/drmu_atomic.c b/modules/video_output/drmu/drmu_atomic.c
new file mode 100644
index 0000000000..fcf48f2d1f
--- /dev/null
+++ b/modules/video_output/drmu/drmu_atomic.c
@@ -0,0 +1,831 @@
+#include "drmu.h"
+#include "drmu_log.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdatomic.h>
+#include <string.h>
+
+#include <libdrm/drm.h>
+#include <libdrm/drm_mode.h>
+
+// Atomic property chain structures - no external visibility
+typedef struct aprop_prop_s {
+ uint32_t id;
+ uint64_t value;
+ void * v;
+ const drmu_atomic_prop_fns_t * fns;
+} aprop_prop_t;
+
+typedef struct aprop_obj_s {
+ uint32_t id;
+ unsigned int n;
+ unsigned int size;
+ bool unsorted;
+ aprop_prop_t * props;
+} aprop_obj_t;
+
+typedef struct aprop_hdr_s {
+ unsigned int n;
+ unsigned int size;
+ bool unsorted;
+ aprop_obj_t * objs;
+} aprop_hdr_t;
+
+typedef struct drmu_atomic_s {
+ atomic_int ref_count; // 0 == 1 ref for ease of init
+
+ struct drmu_env_s * du;
+
+ aprop_hdr_t props;
+} drmu_atomic_t;
+
+static inline unsigned int
+max_uint(const unsigned int a, const unsigned int b)
+{
+ return a < b ? b : a;
+}
+
+static void
+aprop_prop_unref(aprop_prop_t * const pp)
+{
+ pp->fns->unref(pp->v);
+}
+
+static void
+aprop_prop_ref(aprop_prop_t * const pp)
+{
+ pp->fns->ref(pp->v);
+}
+
+static void
+aprop_obj_uninit(aprop_obj_t * const po)
+{
+ unsigned int i;
+ for (i = 0; i != po->n; ++i)
+ aprop_prop_unref(po->props + i);
+ free(po->props);
+ memset(po, 0, sizeof(*po));
+}
+
+static int
+aprop_obj_copy(aprop_obj_t * const po_c, const aprop_obj_t * const po_a)
+{
+ unsigned int i;
+ aprop_prop_t * props;
+
+ aprop_obj_uninit(po_c);
+ if (po_a->n == 0)
+ return 0;
+
+ if ((props = calloc(po_a->size, sizeof(*props))) == NULL)
+ return -ENOMEM;
+ memcpy(props, po_a->props, po_a->n * sizeof(*po_a->props));
+
+ *po_c = *po_a;
+ po_c->props = props;
+
+ for (i = 0; i != po_a->n; ++i)
+ aprop_prop_ref(props + i);
+ return 0;
+}
+
+static void
+aprop_obj_move(aprop_obj_t * const po_c, aprop_obj_t * const po_a)
+{
+ *po_c = *po_a;
+ memset(po_a, 0, sizeof(*po_a));
+}
+
+static int
+aprop_prop_qsort_cb(const void * va, const void * vb)
+{
+ const aprop_prop_t * const a = va;
+ const aprop_prop_t * const b = vb;
+ return a->id == b->id ? 0 : a->id < b->id ? -1 : 1;
+}
+
+static void
+aprop_obj_props_sort(aprop_obj_t * const po)
+{
+ if (!po->unsorted)
+ return;
+ qsort(po->props, po->n, sizeof(po->props[0]), aprop_prop_qsort_cb);
+ po->unsorted = false;
+}
+
+// Merge b into a and put the result in c. a & b are uninit on exit
+// Could (easily) merge into a but its more convienient for the caller to create new
+static int
+aprop_obj_merge(aprop_obj_t * const po_c, aprop_obj_t * const po_a, aprop_obj_t * const po_b)
+{
+ unsigned int i, j, k;
+ unsigned int c_size;
+ aprop_prop_t * c;
+ aprop_prop_t * const a = po_a->props;
+ aprop_prop_t * const b = po_b->props;
+
+ // As we should have no identical els we don't care that qsort is unstable
+ aprop_obj_props_sort(po_a);
+ aprop_obj_props_sort(po_b);
+
+ // Pick a size
+ c_size = max_uint(po_a->size, po_b->size);
+ if (c_size < po_a->n + po_b->n)
+ c_size *= 2;
+ if ((c = calloc(c_size, sizeof(*c))) == NULL)
+ return -ENOMEM;
+
+ for (i = 0, j = 0, k = 0; i < po_a->n && j < po_b->n; ++k) {
+ if (a[i].id < b[j].id)
+ c[k] = a[i++];
+ else if (a[i].id > b[j].id)
+ c[k] = b[j++];
+ else {
+ c[k] = b[j++];
+ aprop_prop_unref(a + i++);
+ }
+ }
+ for (; i < po_a->n; ++i, ++k)
+ c[k] = a[i];
+ for (; j < po_b->n; ++j, ++k)
+ c[k] = b[j];
+
+ *po_c = (aprop_obj_t){
+ .id = po_a->id,
+ .n = k,
+ .size = c_size,
+ .unsorted = false,
+ .props = c
+ };
+
+ // We have avoided excess ref / unref by simple copy so just free the props array
+ free(a);
+ free(b);
+
+ memset(po_a, 0, sizeof(*po_a));
+ memset(po_b, 0, sizeof(*po_b));
+
+ return 0;
+}
+
+// Remove any props in a that are also in b
+// b must be sorted
+// Returns count of props remaining in a
+static unsigned int
+aprop_obj_sub(aprop_obj_t * const po_a, const aprop_obj_t * const po_b)
+{
+ unsigned int i = 0, j = 0, k;
+ aprop_prop_t * const a = po_a->props;
+ const aprop_prop_t * const b = po_b->props;
+
+ if (po_a->n == 0 || po_b->n == 0)
+ return po_a->n;
+
+ // As we should have no identical els we don't care that qsort is unstable
+ aprop_obj_props_sort(po_a);
+ assert(!po_b->unsorted);
+
+ // Skip initial non-matches, returning if no match found
+ while (a[i].id != b[j].id) {
+ if (a[i].id < b[j].id) {
+ if (++i == po_a->n)
+ return po_a->n;
+ }
+ else {
+ if (++j == po_b->n)
+ return po_a->n;
+ }
+ }
+ // We have a match - next loop will do the unref
+ k = i;
+
+ do {
+ if (a[i].id < b[j].id)
+ a[k++] = a[i++];
+ else {
+ if (a[i].id == b[j].id)
+ aprop_prop_unref(a + i++);
+ j++;
+ }
+ } while (i != po_a->n && j != po_b->n);
+
+ for (; i < po_a->n; ++i, ++k)
+ a[k] = a[i];
+ po_a->n = k;
+
+ return po_a->n;
+}
+
+
+static aprop_prop_t *
+aprop_obj_prop_get(aprop_obj_t * const po, const uint32_t id)
+{
+ unsigned int i;
+ aprop_prop_t * pp = po->props;
+
+ static const drmu_atomic_prop_fns_t null_fns = {
+ .ref = drmu_prop_fn_null_ref,
+ .unref = drmu_prop_fn_null_unref,
+ .commit = drmu_prop_fn_null_commit
+ };
+
+ for (i = 0; i != po->n; ++i, ++pp) {
+ if (pp->id == id)
+ return pp;
+ }
+
+ if (po->n >= po->size) {
+ size_t newsize = po->size < 16 ? 16 : po->size * 2;
+ if ((pp = realloc(po->props, newsize * sizeof(*pp))) == NULL)
+ return NULL;
+ memset(pp + po->size, 0, (newsize - po->size) * sizeof(*pp));
+
+ po->props = pp;
+ po->size = newsize;
+ pp += po->n;
+ }
+ if (!po->unsorted && po->n != 0 && pp[-1].id > id)
+ po->unsorted = true;
+ ++po->n;
+
+ pp->id = id;
+ pp->fns = &null_fns;
+ return pp;
+}
+
+static void
+aprop_obj_atomic_fill(const aprop_obj_t * const po, uint32_t * prop_ids, uint64_t * prop_values)
+{
+ unsigned int i;
+ for (i = 0; i != po->n; ++i) {
+ *prop_ids++ = po->props[i].id;
+ *prop_values++ = po->props[i].value;
+ }
+}
+
+static void
+aprop_obj_dump(drmu_env_t * const du, const aprop_obj_t * const po)
+{
+ unsigned int i;
+ drmu_info(du, "Obj: %02x: size %d n %d", po->id, po->size, po->n);
+ for (i = 0; i != po->n; ++i) {
+ struct drm_mode_get_property pattr = {.prop_id = po->props[i].id};
+ drmu_ioctl(du, DRM_IOCTL_MODE_GETPROPERTY, &pattr);
+
+ drmu_info(du, "Obj %02x: Prop %02x (%s) Value %"PRIx64" v %p", po->id, po->props[i].id, pattr.name, po->props[i].value, po->props[i].v);
+ }
+}
+
+static void
+aprop_hdr_dump(drmu_env_t * const du, const aprop_hdr_t * const ph)
+{
+ unsigned int i;
+ drmu_info(du, "Header: size %d n %d", ph->size, ph->n);
+ for (i = 0; i != ph->n; ++i)
+ aprop_obj_dump(du, ph->objs + i);
+}
+
+static aprop_obj_t *
+aprop_hdr_obj_get(aprop_hdr_t * const ph, const uint32_t id)
+{
+ unsigned int i;
+ aprop_obj_t * po = ph->objs;
+
+ for (i = 0; i != ph->n; ++i, ++po) {
+ if (po->id == id)
+ return po;
+ }
+
+ if (ph->n >= ph->size) {
+ size_t newsize = ph->size < 16 ? 16 : ph->size * 2;
+ if ((po = realloc(ph->objs, newsize * sizeof(*po))) == NULL)
+ return NULL;
+ memset(po + ph->size, 0, (newsize - ph->size) * sizeof(*po));
+
+ ph->objs = po;
+ ph->size = newsize;
+ po += ph->n;
+ }
+ if (!ph->unsorted && ph->n != 0 && po[-1].id > id)
+ ph->unsorted = true;
+ ++ph->n;
+
+ po->id = id;
+ return po;
+}
+
+static void
+aprop_hdr_uninit(aprop_hdr_t * const ph)
+{
+ unsigned int i;
+ for (i = 0; i != ph->n; ++i)
+ aprop_obj_uninit(ph->objs + i);
+ free(ph->objs);
+ memset(ph, 0, sizeof(*ph));
+}
+
+static int
+aprop_hdr_copy(aprop_hdr_t * const ph_c, const aprop_hdr_t * const ph_a)
+{
+ unsigned int i;
+
+ aprop_hdr_uninit(ph_c);
+
+ if (ph_a->n == 0)
+ return 0;
+
+ if ((ph_c->objs = calloc(ph_a->size, sizeof(*ph_c->objs))) == NULL)
+ return -ENOMEM;
+
+ ph_c->n = ph_a->n;
+ ph_c->size = ph_a->size;
+ ph_c->unsorted = ph_a->unsorted;
+
+ for (i = 0; i != ph_a->n; ++i)
+ aprop_obj_copy(ph_c->objs + i, ph_a->objs + i);
+ return 0;
+}
+
+// Move b to a. a must be empty
+static int
+aprop_hdr_move(aprop_hdr_t * const ph_a, aprop_hdr_t * const ph_b)
+{
+ *ph_a = *ph_b;
+ *ph_b = (aprop_hdr_t){0};
+ return 0;
+}
+
+static int
+aprop_obj_qsort_cb(const void * va, const void * vb)
+{
+ const aprop_obj_t * const a = va;
+ const aprop_obj_t * const b = vb;
+ return a->id == b->id ? 0 : a->id < b->id ? -1 : 1;
+}
+
+// As we should have no identical els we don't care that qsort is unstable
+// Doesn't sort props
+static void
+aprop_hdr_sort(aprop_hdr_t * const ph)
+{
+ if (!ph->unsorted)
+ return;
+ qsort(ph->objs, ph->n, sizeof(ph->objs[0]), aprop_obj_qsort_cb);
+ ph->unsorted = false;
+}
+
+// Merge b into a. b will be uninited
+static int
+aprop_hdr_merge(aprop_hdr_t * const ph_a, aprop_hdr_t * const ph_b)
+{
+ unsigned int i, j, k;
+ unsigned int c_size;
+ aprop_obj_t * c;
+ aprop_obj_t * const a = ph_a->objs;
+ aprop_obj_t * const b = ph_b->objs;
+
+ if (ph_b->n == 0)
+ return 0;
+ if (ph_a->n == 0)
+ return aprop_hdr_move(ph_a, ph_b);
+
+ aprop_hdr_sort(ph_a);
+ aprop_hdr_sort(ph_b);
+
+ // Pick a size
+ c_size = max_uint(ph_a->size, ph_b->size);
+ if (c_size < ph_a->n + ph_b->n)
+ c_size *= 2;
+ if ((c = calloc(c_size, sizeof(*c))) == NULL)
+ return -ENOMEM;
+
+ for (i = 0, j = 0, k = 0; i < ph_a->n && j < ph_b->n; ++k) {
+ if (a[i].id < b[j].id)
+ aprop_obj_move(c + k, a + i++);
+ else if (a[i].id > b[j].id)
+ aprop_obj_move(c + k, b + j++);
+ else
+ aprop_obj_merge(c + k, a + i++, b + j++);
+ }
+ for (; i < ph_a->n; ++i, ++k)
+ aprop_obj_move(c + k, a + i);
+ for (; j < ph_b->n; ++j, ++k)
+ aprop_obj_move(c + k, b + j);
+
+ aprop_hdr_uninit(ph_a);
+ aprop_hdr_uninit(ph_b);
+
+ ph_a->n = k;
+ ph_a->size = c_size;
+ ph_a->objs = c;
+ // Merge will maintain sort so leave unsorted false
+
+ return 0;
+}
+
+// Remove any props in a that are also in b
+// b must be sorted
+static void
+aprop_hdr_sub(aprop_hdr_t * const ph_a, const aprop_hdr_t * const ph_b)
+{
+ unsigned int i = 0, j = 0, k;
+ aprop_obj_t * const a = ph_a->objs;
+ const aprop_obj_t * const b = ph_b->objs;
+
+ aprop_hdr_sort(ph_a);
+ assert(!ph_b->unsorted);
+
+ // Scan whilst we haven't deleted anything
+ for (;;) {
+ // If we run out of either array then nothing more needed
+ if (i == ph_a->n || j == ph_b->n)
+ return;
+
+ if (a[i].id < b[j].id)
+ ++i;
+ else if (a[i].id > b[j].id)
+ ++j;
+ else {
+ k = i;
+ if (aprop_obj_sub(a + i++, b + j++) == 0) {
+ aprop_obj_uninit(a + k);
+ break;
+ }
+ }
+ }
+
+ // Move & scan
+ while (i < ph_a->n && j < ph_b->n) {
+ if (a[i].id < b[j].id)
+ aprop_obj_move(a + k++, a + i++);
+ else if (a[i].id > b[j].id)
+ j++;
+ else {
+ if (aprop_obj_sub(a + i, b + j) == 0)
+ aprop_obj_uninit(a + i);
+ else
+ aprop_obj_move(a + k++, a + i);
+ i++;
+ j++;
+ }
+ }
+
+ // Move any remaining entries
+ for (; i < ph_a->n; ++i, ++k)
+ aprop_obj_move(a + k, a + i);
+ ph_a->n = k;
+
+ return;
+}
+
+// Sort header objs & obj props
+static void
+aprop_hdr_props_sort(aprop_hdr_t * const ph)
+{
+ aprop_hdr_sort(ph);
+ for (unsigned int i = 0; i != ph->n; ++i)
+ aprop_obj_props_sort(ph->objs + i);
+}
+
+static aprop_prop_t *
+aprop_hdr_prop_get(aprop_hdr_t * const ph, const uint32_t obj_id, const uint32_t prop_id)
+{
+ aprop_obj_t * const po = aprop_hdr_obj_get(ph, obj_id);
+ return po == NULL ? NULL : aprop_obj_prop_get(po, prop_id);
+}
+
+// Total props
+static unsigned int
+aprop_hdr_props_count(const aprop_hdr_t * const ph)
+{
+ unsigned int i;
+ unsigned int n = 0;
+
+ for (i = 0; i != ph->n; ++i)
+ n += ph->objs[i].n;
+ return n;
+}
+
+static unsigned int
+aprop_hdr_objs_count(const aprop_hdr_t * const ph)
+{
+ return ph->n;
+}
+
+static void
+aprop_hdr_atomic_fill(const aprop_hdr_t * const ph,
+ uint32_t * obj_ids,
+ uint32_t * prop_counts,
+ uint32_t * prop_ids,
+ uint64_t * prop_values)
+{
+ unsigned int i;
+ for (i = 0; i != ph->n; ++i) {
+ const unsigned int n = ph->objs[i].n;
+ *obj_ids++ = ph->objs[i].id;
+ *prop_counts++ = n;
+ aprop_obj_atomic_fill(ph->objs +i, prop_ids, prop_values);
+ prop_ids += n;
+ prop_values += n;
+ }
+}
+
+void
+drmu_prop_fn_null_unref(void * v)
+{
+ (void)v;
+}
+
+void
+drmu_prop_fn_null_ref(void * v)
+{
+ (void)v;
+}
+
+void
+drmu_prop_fn_null_commit(void * v, uint64_t value)
+{
+ (void)v;
+ (void)value;
+}
+
+int
+drmu_atomic_add_prop_generic(drmu_atomic_t * const da,
+ const uint32_t obj_id, const uint32_t prop_id, const uint64_t value,
+ const drmu_atomic_prop_fns_t * const fns, void * const v)
+{
+ aprop_hdr_t * const ph = &da->props;
+
+ if (obj_id == 0 || prop_id == 0)
+ {
+ return -EINVAL;
+ }
+ else
+ {
+ aprop_prop_t *const pp = aprop_hdr_prop_get(ph, obj_id, prop_id);
+ if (pp == NULL)
+ return -ENOMEM;
+
+ aprop_prop_unref(pp);
+ pp->value = value;
+ if (fns) {
+ pp->fns = fns;
+ pp->v = v;
+ }
+ aprop_prop_ref(pp);
+ return 0;
+ }
+}
+
+int
+drmu_atomic_add_prop_value(drmu_atomic_t * const da, const uint32_t obj_id, const uint32_t prop_id, const uint64_t value)
+{
+ if (drmu_atomic_add_prop_generic(da, obj_id, prop_id, value, NULL, NULL) < 0)
+ drmu_warn(drmu_atomic_env(da), "%s: Failed to set obj_id=%#x, prop_id=%#x, val=%" PRId64, __func__,
+ obj_id, prop_id, value);
+ return 0;
+}
+
+void
+drmu_atomic_dump(const drmu_atomic_t * const da)
+{
+ drmu_info(da->du, "Atomic %p: refs %d", da, atomic_load(&da->ref_count)+1);
+ aprop_hdr_dump(da->du, &da->props);
+}
+
+drmu_env_t *
+drmu_atomic_env(const drmu_atomic_t * const da)
+{
+ return da == NULL ? NULL : da->du;
+}
+
+static void
+drmu_atomic_free(drmu_atomic_t * const da)
+{
+ aprop_hdr_uninit(&da->props);
+ free(da);
+}
+
+void
+drmu_atomic_unref(drmu_atomic_t ** const ppda)
+{
+ drmu_atomic_t * const da = *ppda;
+
+ if (da == NULL)
+ return;
+ *ppda = NULL;
+
+ if (atomic_fetch_sub(&da->ref_count, 1) == 0)
+ drmu_atomic_free(da);
+}
+
+drmu_atomic_t *
+drmu_atomic_ref(drmu_atomic_t * const da)
+{
+ atomic_fetch_add(&da->ref_count, 1);
+ return da;
+}
+
+drmu_atomic_t *
+drmu_atomic_new(drmu_env_t * const du)
+{
+ drmu_atomic_t * const da = calloc(1, sizeof(*da));
+
+ if (da == NULL) {
+ drmu_err(du, "%s: Failed to alloc struct", __func__);
+ return NULL;
+ }
+ da->du = du;
+
+ return da;
+}
+
+// Merge b into a. b is unrefed (inc on error)
+int
+drmu_atomic_merge(drmu_atomic_t * const a, drmu_atomic_t ** const ppb)
+{
+ drmu_atomic_t * b = *ppb;
+ aprop_hdr_t bh = {0};
+ int rv = -EINVAL;
+
+ if (b == NULL)
+ return 0;
+ *ppb = NULL;
+
+ if (a == NULL) {
+ drmu_atomic_unref(&b);
+ return -EINVAL;
+ }
+ // We expect this to be the sole ref to a
+ assert(atomic_load(&a->ref_count) == 0);
+
+ // If this is the only copy of b then use it directly otherwise
+ // copy before (probably) making it unusable
+ if (atomic_load(&b->ref_count) == 0)
+ rv = aprop_hdr_move(&bh, &b->props);
+ else
+ rv = aprop_hdr_copy(&bh, &b->props);
+ drmu_atomic_unref(&b);
+
+ if (rv != 0) {
+ drmu_err(a->du, "%s: Copy Failed", __func__);
+ return rv;
+ }
+
+ rv = aprop_hdr_merge(&a->props, &bh);
+ aprop_hdr_uninit(&bh);
+
+ if (rv != 0) {
+ drmu_err(a->du, "%s: Merge Failed", __func__);
+ return rv;
+ }
+
+ return 0;
+}
+
+void
+drmu_atomic_sub(drmu_atomic_t * const a, drmu_atomic_t * const b)
+{
+ aprop_hdr_props_sort(&b->props);
+ aprop_hdr_sub(&a->props, &b->props);
+}
+
+static void
+atomic_props_crop(struct drm_mode_atomic * const f, const unsigned int n, uint32_t ** const undo_p, uint32_t * const undo_v)
+{
+ unsigned int i;
+ unsigned int t = 0;
+ uint32_t * const c = (uint32_t *)(uintptr_t)f->count_props_ptr;
+
+ for (i = 0; i != f->count_objs; ++i) {
+ t += c[i];
+ if (t >= n) {
+ f->count_objs = i + 1;
+ *undo_p = c + i;
+ *undo_v = c[i];
+ c[i] -= t - n;
+ break;
+ }
+ }
+}
+
+static void
+atomic_props_del(struct drm_mode_atomic * const f, const unsigned int n, const unsigned int cp,
+ uint32_t * const objid, uint32_t * const propid, uint64_t * const val)
+{
+ unsigned int i;
+ unsigned int t = 0;
+ uint32_t * const c = (uint32_t *)(uintptr_t)f->count_props_ptr;
+ uint32_t * const o = (uint32_t *)(uintptr_t)f->objs_ptr;
+ uint32_t * const p = (uint32_t *)(uintptr_t)f->props_ptr;
+ uint64_t * const v = (uint64_t *)(uintptr_t)f->prop_values_ptr;
+
+ for (i = 0; i != f->count_objs; ++i) {
+ t += c[i];
+ if (t > n) {
+ // Copy out what we are going to delete
+ *objid = o[i];
+ *propid = p[n];
+ *val = v[n];
+
+ memmove(p + n, p + n + 1, (cp - n - 1) * sizeof(*p));
+ memmove(v + n, v + n + 1, (cp - n - 1) * sizeof(*v));
+
+ if (--c[i] == 0) {
+ memmove(c + i, c + i + 1, (f->count_objs - i - 1) * sizeof(*c));
+ memmove(o + i, o + i + 1, (f->count_objs - i - 1) * sizeof(*o));
+ --f->count_objs;
+ }
+ break;
+ }
+ }
+}
+
+// Returns count of initial good els (i.e. n of 1st bad)
+static unsigned int
+commit_find_good(drmu_env_t * const du, const struct drm_mode_atomic * const atomic, const unsigned int n_props)
+{
+ unsigned int a = 0; // N known good
+ unsigned int b = n_props + 1; // N maybe good + 1
+
+ while (a + 1 < b) {
+ struct drm_mode_atomic at = *atomic;
+ unsigned int n = (a + b) / 2;
+ int rv;
+ uint32_t * undo_p = NULL;
+ uint32_t undo_v = 0;
+
+ at.flags = DRM_MODE_ATOMIC_TEST_ONLY | (DRM_MODE_ATOMIC_ALLOW_MODESET & atomic->flags);
+ atomic_props_crop(&at, n, &undo_p, &undo_v);
+
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_ATOMIC, &at)) == 0) {
+ a = n;
+ }
+ else {
+ b = n;
+ }
+
+ *undo_p = undo_v; // Should always be set
+ }
+
+ return a;
+}
+
+// da_fail does not keep refs to its values - for info only
+int
+drmu_atomic_commit_test(const drmu_atomic_t * const da, uint32_t flags, drmu_atomic_t * const da_fail)
+{
+ drmu_env_t * const du = da->du;
+ const unsigned int n_objs = aprop_hdr_objs_count(&da->props);
+ unsigned int n_props = aprop_hdr_props_count(&da->props);
+ int rv = 0;
+
+ if (n_props != 0) {
+ uint32_t obj_ids[n_objs];
+ uint32_t prop_counts[n_objs];
+ uint32_t prop_ids[n_props];
+ uint64_t prop_values[n_props];
+ struct drm_mode_atomic atomic = {
+ .flags = flags,
+ .count_objs = n_objs,
+ .objs_ptr = (uintptr_t)obj_ids,
+ .count_props_ptr = (uintptr_t)prop_counts,
+ .props_ptr = (uintptr_t)prop_ids,
+ .prop_values_ptr = (uintptr_t)prop_values,
+ .user_data = (uintptr_t)da
+ };
+
+ aprop_hdr_atomic_fill(&da->props, obj_ids, prop_counts, prop_ids, prop_values);
+
+ if ((rv = drmu_ioctl(du, DRM_IOCTL_MODE_ATOMIC, &atomic)) == 0 || !da_fail)
+ return rv;
+
+ for (;;) {
+ unsigned int a = commit_find_good(du, &atomic, n_props);
+ uint32_t objid = 0;
+ uint32_t propid = 0;
+ uint64_t val = 0;
+
+ if (a >= n_props)
+ break;
+
+ atomic_props_del(&atomic, a, n_props, &objid, &propid, &val);
+ --n_props;
+
+ drmu_atomic_add_prop_value(da_fail, objid, propid, val);
+ }
+ }
+
+ return rv;
+}
+
+
+int
+drmu_atomic_commit(const drmu_atomic_t * const da, uint32_t flags)
+{
+ return drmu_atomic_commit_test(da, flags, NULL);
+}
+
+
diff --git a/modules/video_output/drmu/drmu_chroma.h b/modules/video_output/drmu/drmu_chroma.h
new file mode 100644
index 0000000000..3e3f7e9735
--- /dev/null
2023-03-29 17:41:09 +00:00
+++ b/modules/video_output/drmu/drmu_chroma.h
@@ -0,0 +1,47 @@
+#ifndef _DRMU_DRMU_CHROMA_H
+#define _DRMU_DRMU_CHROMA_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "drmu_chroma.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct drmu_chroma_siting_s {
+ int32_t x, y;
+} drmu_chroma_siting_t;
+
+// Init constants - C winges if the struct is specified in a const init (which seems like a silly error)
+#define drmu_chroma_siting_float_i(_x, _y) {.x = (int32_t)((double)(_x) * 65536 + .5), .y = (int32_t)((double)(_y) * 65536 + .5)}
+#define DRMU_CHROMA_SITING_BOTTOM_I drmu_chroma_siting_float_i(0.5, 1.0)
+#define DRMU_CHROMA_SITING_BOTTOM_LEFT_I drmu_chroma_siting_float_i(0.0, 1.0)
+#define DRMU_CHROMA_SITING_CENTER_I drmu_chroma_siting_float_i(0.5, 0.5)
+#define DRMU_CHROMA_SITING_LEFT_I drmu_chroma_siting_float_i(0.0, 0.5)
+#define DRMU_CHROMA_SITING_TOP_I drmu_chroma_siting_float_i(0.5, 0.0)
+#define DRMU_CHROMA_SITING_TOP_LEFT_I drmu_chroma_siting_float_i(0.0, 0.0)
+#define DRMU_CHROMA_SITING_UNSPECIFIED_I {INT32_MIN, INT32_MIN}
+// Inline constants
+#define drmu_chroma_siting_float(_x, _y) (drmu_chroma_siting_t)drmu_chroma_siting_float_i(_x, _y)
+#define DRMU_CHROMA_SITING_BOTTOM drmu_chroma_siting_float(0.5, 1.0)
+#define DRMU_CHROMA_SITING_BOTTOM_LEFT drmu_chroma_siting_float(0.0, 1.0)
+#define DRMU_CHROMA_SITING_CENTER drmu_chroma_siting_float(0.5, 0.5)
+#define DRMU_CHROMA_SITING_LEFT drmu_chroma_siting_float(0.0, 0.5)
+#define DRMU_CHROMA_SITING_TOP drmu_chroma_siting_float(0.5, 0.0)
+#define DRMU_CHROMA_SITING_TOP_LEFT drmu_chroma_siting_float(0.0, 0.0)
+#define DRMU_CHROMA_SITING_UNSPECIFIED (drmu_chroma_siting_t){INT32_MIN, INT32_MIN}
+
+static inline bool
+drmu_chroma_siting_eq(const drmu_chroma_siting_t a, const drmu_chroma_siting_t b)
+{
+ return a.x == b.x && a.y == b.y;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/modules/video_output/drmu/drmu_fmts.c b/modules/video_output/drmu/drmu_fmts.c
new file mode 100644
index 0000000000..c5769a2ad5
2023-03-29 17:41:09 +00:00
--- /dev/null
+++ b/modules/video_output/drmu/drmu_fmts.c
@@ -0,0 +1,249 @@
+#include "drmu_fmts.h"
+
+#include <stddef.h>
+
+#include <libdrm/drm_fourcc.h>
+
+#ifndef HAS_SORTED_FMTS
+#define HAS_SORTED_FMTS 0
+#endif
+#ifndef BUILD_MK_SORTED_FMTS_H
+#define BUILD_MK_SORTED_FMTS_H 0
+#endif
+
+#ifndef DRM_FORMAT_P030
+#define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0')
+#endif
+
+// Format properties
+
+typedef struct drmu_fmt_info_s {
+ uint32_t fourcc;
+ uint8_t bpp; // For dumb BO alloc
+ uint8_t bit_depth; // For display
+ uint8_t plane_count;
+ struct {
+ uint8_t wdiv;
+ uint8_t hdiv;
+ } planes[4];
+ drmu_chroma_siting_t chroma_siting; // Default for this format (YUV420 = (0.0, 0.5), otherwise (0, 0)
+} drmu_fmt_info_t;
+
+#if BUILD_MK_SORTED_FMTS_H || !HAS_SORTED_FMTS
+
+#define P_ONE {{.wdiv = 1, .hdiv = 1}}
+#define P_YC420 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 1, .hdiv = 2}}
+#define P_YC422 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 1, .hdiv = 1}}
+#define P_YC444 {{.wdiv = 2, .hdiv = 1}, {.wdiv = 1, .hdiv = 1}} // Assumes doubled .bpp
+#define P_YUV420 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 2, .hdiv = 2}, {.wdiv = 2, .hdiv = 2}}
+#define P_YUV422 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 2, .hdiv = 1}, {.wdiv = 2, .hdiv = 1}}
+#define P_YUV444 {{.wdiv = 1, .hdiv = 1}, {.wdiv = 1, .hdiv = 1}, {.wdiv = 1, .hdiv = 1}}
+
+// Not const 'cos we sort in place when creating the sorted version
+static drmu_fmt_info_t format_info[] = {
+ { .fourcc = DRM_FORMAT_XRGB1555, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_XBGR1555, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_RGBX5551, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_BGRX5551, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_ARGB1555, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_ABGR1555, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_RGBA5551, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_BGRA5551, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_BGR565, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_RGB565, .bpp = 16, .bit_depth = 5, .plane_count = 1, .planes = P_ONE},
+
+ { .fourcc = DRM_FORMAT_RGB888, .bpp = 24, .bit_depth = 8, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_BGR888, .bpp = 24, .bit_depth = 8, .plane_count = 1, .planes = P_ONE},
+
+ { .fourcc = DRM_FORMAT_XRGB8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_XBGR8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_RGBX8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_BGRX8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_ARGB8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_ABGR8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_RGBA8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_BGRA8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE},
+
+ { .fourcc = DRM_FORMAT_XRGB2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_XBGR2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_RGBX1010102, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_BGRX1010102, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_ARGB2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_ABGR2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_RGBA1010102, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_BGRA1010102, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE},
+
+ { .fourcc = DRM_FORMAT_AYUV, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_XYUV8888, .bpp = 32, .bit_depth = 8, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_VUY888, .bpp = 24, .bit_depth = 8, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_XVYU2101010, .bpp = 32, .bit_depth = 10, .plane_count = 1, .planes = P_ONE},
+
+ { .fourcc = DRM_FORMAT_XVYU12_16161616, .bpp = 64, .bit_depth = 12, .plane_count = 1, .planes = P_ONE},
+ { .fourcc = DRM_FORMAT_XVYU16161616, .bpp = 64, .bit_depth = 16, .plane_count = 1, .planes = P_ONE},
+
+ { .fourcc = DRM_FORMAT_YUYV, .bpp = 16, .bit_depth = 8, .plane_count = 1, .planes = P_ONE,
+ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I },
+ { .fourcc = DRM_FORMAT_YVYU, .bpp = 16, .bit_depth = 8, .plane_count = 1, .planes = P_ONE,
+ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I },
+ { .fourcc = DRM_FORMAT_VYUY, .bpp = 16, .bit_depth = 8, .plane_count = 1, .planes = P_ONE,
+ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I },
+ { .fourcc = DRM_FORMAT_UYVY, .bpp = 16, .bit_depth = 8, .plane_count = 1, .planes = P_ONE,
+ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I },
+
+ { .fourcc = DRM_FORMAT_NV12, .bpp = 8, .bit_depth = 8, .plane_count = 2, .planes = P_YC420,
+ .chroma_siting = DRMU_CHROMA_SITING_LEFT_I },
+ { .fourcc = DRM_FORMAT_NV21, .bpp = 8, .bit_depth = 8, .plane_count = 2, .planes = P_YC420,
+ .chroma_siting = DRMU_CHROMA_SITING_LEFT_I },
+ { .fourcc = DRM_FORMAT_P010, .bpp = 16, .bit_depth = 10, .plane_count = 2, .planes = P_YC420,
+ .chroma_siting = DRMU_CHROMA_SITING_LEFT_I },
+ { .fourcc = DRM_FORMAT_NV16, .bpp = 8, .bit_depth = 8, .plane_count = 2, .planes = P_YC422,
+ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I },
+ { .fourcc = DRM_FORMAT_NV61, .bpp = 8, .bit_depth = 8, .plane_count = 2, .planes = P_YC422,
+ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I },
+ { .fourcc = DRM_FORMAT_NV24, .bpp = 16, .bit_depth = 8, .plane_count = 2, .planes = P_YC444,
+ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I },
+ { .fourcc = DRM_FORMAT_NV42, .bpp = 16, .bit_depth = 8, .plane_count = 2, .planes = P_YC444,
+ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I },
+
+ { .fourcc = DRM_FORMAT_YUV420, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV420,
+ .chroma_siting = DRMU_CHROMA_SITING_LEFT_I },
+ { .fourcc = DRM_FORMAT_YVU420, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV420,
+ .chroma_siting = DRMU_CHROMA_SITING_LEFT_I },
+ { .fourcc = DRM_FORMAT_YUV422, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV422,
+ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I },
+ { .fourcc = DRM_FORMAT_YUV422, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV422,
+ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I },
+ { .fourcc = DRM_FORMAT_YUV444, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV444,
+ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I },
+ { .fourcc = DRM_FORMAT_YUV444, .bpp = 8, .bit_depth = 8, .plane_count = 3, .planes = P_YUV444,
+ .chroma_siting = DRMU_CHROMA_SITING_TOP_LEFT_I },
+
+ // 3 pel in 32 bits. So code as 32bpp with wdiv 3.
+ { .fourcc = DRM_FORMAT_P030, .bpp = 32, .bit_depth = 10, .plane_count = 2,
+ .planes = {{.wdiv = 3, .hdiv = 1}, {.wdiv = 3, .hdiv = 2}},
+ .chroma_siting = DRMU_CHROMA_SITING_LEFT_I },
+
+ { .fourcc = 0 }
+};
+#endif
+
+#if BUILD_MK_SORTED_FMTS_H
+// ---------------------------------------------------------------------------
+//
+// Sort & emit format table (not part of the lib)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+static const unsigned int format_count = sizeof(format_info)/sizeof(format_info[0]) - 1; // Ignore null term in count
+
+static int sort_fn(const void * va, const void * vb)
+{
+ const drmu_fmt_info_t * a = va;
+ const drmu_fmt_info_t * b = vb;
+ return a->fourcc < b->fourcc ? -1 : a->fourcc == b->fourcc ? 0 : 1;
+}
+
+int
+main(int argc, char * argv[])
+{
+ FILE * f;
+ unsigned int i;
+
+ if (argc != 2) {
+ fprintf(stderr, "Needs output file only\n");
+ return 1;
+ }
+ if ((f = fopen(argv[1], "wt")) == NULL) {
+ fprintf(stderr, "Failed to open'%s'\n", argv[1]);
+ return 1;
+ }
+ qsort(format_info, format_count, sizeof(format_info[0]), sort_fn);
+
+ fprintf(f, "static const drmu_fmt_info_t format_info[] = {\n");
+ for (i = 0; i != format_count; ++i) {
+ const drmu_fmt_info_t * x = format_info + i;
+ unsigned int j;
+ fprintf(f, "{%#"PRIx32",%d,%d,%d,{", x->fourcc, x->bpp, x->bit_depth, x->plane_count);
+ for (j = 0; j != sizeof(x->planes)/sizeof(x->planes[0]); ++j) {
+ fprintf(f, "{%d,%d},", x->planes[j].wdiv, x->planes[j].hdiv);
+ }
+ fprintf(f, "},");
+ fprintf(f, "{%d,%d},", x->chroma_siting.x, x->chroma_siting.y);
+ fprintf(f, "},\n");
+ }
+ fprintf(f, "{0}\n};\n");
+ fprintf(f, "static const unsigned int format_count = %d;\n", format_count);
+
+ fclose(f);
+ return 0;
+}
+
+#else
+// ---------------------------------------------------------------------------
+//
+// Include sorted format table
+#if HAS_SORTED_FMTS
+#include "sorted_fmts.h"
+#endif
+
+const drmu_fmt_info_t *
+drmu_fmt_info_find_fmt(const uint32_t fourcc)
+{
+ if (!fourcc)
+ return NULL;
+#if HAS_SORTED_FMTS
+ unsigned int lo = 0;
+ unsigned int hi = format_count;
+
+ while (lo < hi) {
+ unsigned int x = (hi + lo) / 2;
+ if (format_info[x].fourcc == fourcc)
+ return &format_info[x];
+ if (format_info[x].fourcc < fourcc)
+ lo = x + 1;
+ else
+ hi = x;
+ }
+#else
+ for (const drmu_fmt_info_t * p = format_info; p->fourcc; ++p) {
+ if (p->fourcc == fourcc)
+ return p;
+ }
+#endif
+ return NULL;
+}
+
+unsigned int
+drmu_fmt_info_bit_depth(const drmu_fmt_info_t * const fmt_info)
+{
+ return !fmt_info ? 0 : fmt_info->bit_depth;
+}
+uint32_t drmu_fmt_info_fourcc(const drmu_fmt_info_t * const fmt_info)
+{
+ return fmt_info->fourcc;
+}
+unsigned int drmu_fmt_info_pixel_bits(const drmu_fmt_info_t * const fmt_info)
+{
+ return !fmt_info ? 0 : fmt_info->bpp;
+}
+unsigned int drmu_fmt_info_plane_count(const drmu_fmt_info_t * const fmt_info)
+{
+ return !fmt_info ? 0 : fmt_info->plane_count;
+}
+unsigned int drmu_fmt_info_wdiv(const drmu_fmt_info_t * const fmt_info, const unsigned int plane_n)
+{
+ return fmt_info->planes[plane_n].wdiv;
+}
+unsigned int drmu_fmt_info_hdiv(const drmu_fmt_info_t * const fmt_info, const unsigned int plane_n)
+{
+ return fmt_info->planes[plane_n].hdiv;
+}
+drmu_chroma_siting_t drmu_fmt_info_chroma_siting(const drmu_fmt_info_t * const fmt_info)
+{
+ return !fmt_info ? DRMU_CHROMA_SITING_TOP_LEFT : fmt_info->chroma_siting;
+}
+
+#endif
+
diff --git a/modules/video_output/drmu/drmu_fmts.h b/modules/video_output/drmu/drmu_fmts.h
new file mode 100644
index 0000000000..a2400357e4
2023-03-29 17:41:09 +00:00
--- /dev/null
+++ b/modules/video_output/drmu/drmu_fmts.h
@@ -0,0 +1,31 @@
+#ifndef _DRMU_DRMU_FMTS_H
+#define _DRMU_DRMU_FMTS_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "drmu_chroma.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct drmu_fmt_info_s;
+typedef struct drmu_fmt_info_s drmu_fmt_info_t;
+
+const drmu_fmt_info_t * drmu_fmt_info_find_fmt(const uint32_t fourcc);
+
+unsigned int drmu_fmt_info_bit_depth(const drmu_fmt_info_t * const fmt_info);
+uint32_t drmu_fmt_info_fourcc(const drmu_fmt_info_t * const fmt_info);
+unsigned int drmu_fmt_info_pixel_bits(const drmu_fmt_info_t * const fmt_info);
+unsigned int drmu_fmt_info_plane_count(const drmu_fmt_info_t * const fmt_info);
+unsigned int drmu_fmt_info_wdiv(const drmu_fmt_info_t * const fmt_info, const unsigned int plane_n);
+unsigned int drmu_fmt_info_hdiv(const drmu_fmt_info_t * const fmt_info, const unsigned int plane_n);
+drmu_chroma_siting_t drmu_fmt_info_chroma_siting(const drmu_fmt_info_t * const fmt_info);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/modules/video_output/drmu/drmu_log.h b/modules/video_output/drmu/drmu_log.h
new file mode 100644
index 0000000000..00fa9c3d12
2023-03-29 17:41:09 +00:00
--- /dev/null
+++ b/modules/video_output/drmu/drmu_log.h
@@ -0,0 +1,53 @@
+#ifndef _DRMU_DRMU_LOG_H
+#define _DRMU_DRMU_LOG_H
+
+#include <stdarg.h>
+
+struct drmu_env_s;
+
+void drmu_log_generic(const struct drmu_log_env_s * const log, const enum drmu_log_level_e level,
+ const char * const fmt, ...);
+
+#define drmu_log_macro(_log, _level, _fmt, ...) do {\
+ const drmu_log_env_t * const _log2 = (_log);\
+ if ((_level) <= _log2->max_level)\
+ drmu_log_generic(_log2, (_level), "%s:%u:%s: " _fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);\
+} while (0)
+
+// Char offset if file, line extracted - func still in format
+#define DRMU_LOG_FMT_OFFSET_FUNC 6
+// Char offset if file, line & fn extracted
+#define DRMU_LOG_FMT_OFFSET_FMT 10
+
+#define drmu_err_log(_log, ...) drmu_log_macro((_log), DRMU_LOG_LEVEL_ERROR, __VA_ARGS__)
+#define drmu_warn_log(_log, ...) drmu_log_macro((_log), DRMU_LOG_LEVEL_WARNING, __VA_ARGS__)
+#define drmu_info_log(_log, ...) drmu_log_macro((_log), DRMU_LOG_LEVEL_INFO, __VA_ARGS__)
+#define drmu_debug_log(_log, ...) drmu_log_macro((_log), DRMU_LOG_LEVEL_DEBUG, __VA_ARGS__)
+
+#define drmu_err(_du, ...) drmu_err_log(drmu_env_log(_du), __VA_ARGS__)
+#define drmu_warn(_du, ...) drmu_warn_log(drmu_env_log(_du), __VA_ARGS__)
+#define drmu_info(_du, ...) drmu_info_log(drmu_env_log(_du), __VA_ARGS__)
+#define drmu_debug(_du, ...) drmu_debug_log(drmu_env_log(_du), __VA_ARGS__)
+
+static inline char drmu_log_safechar(int c)
+{
+ return (c < ' ' || c >=0x7f) ? '?' : c;
+}
+
+static inline const char * drmu_log_fourcc_to_str(char buf[5], uint32_t fcc)
+{
+ if (fcc == 0)
+ return "----";
+ buf[0] = drmu_log_safechar((fcc >> 0) & 0xff);
+ buf[1] = drmu_log_safechar((fcc >> 8) & 0xff);
+ buf[2] = drmu_log_safechar((fcc >> 16) & 0xff);
+ buf[3] = drmu_log_safechar((fcc >> 24) & 0xff);
+ buf[4] = 0;
+ return buf;
+}
+
+#define drmu_log_fourcc(fcc) drmu_log_fourcc_to_str((char[5]){0}, fcc)
+
+
+#endif
+
diff --git a/modules/video_output/drmu/drmu_output.c b/modules/video_output/drmu/drmu_output.c
new file mode 100644
index 0000000000..b4709aeb1e
--- /dev/null
+++ b/modules/video_output/drmu/drmu_output.c
@@ -0,0 +1,616 @@
+#include "drmu_output.h"
2023-03-29 17:41:09 +00:00
+
+#include "drmu_fmts.h"
+#include "drmu_log.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include <libdrm/drm.h>
+#include <libdrm/drm_mode.h>
+
+// Update return value with a new one for cases where we don't stop on error
+static inline int rvup(int rv1, int rv2)
+{
+ return rv2 ? rv2 : rv1;
+}
+
+struct drmu_output_s {
+ drmu_env_t * du;
+ drmu_crtc_t * dc;
+ unsigned int conn_n;
+ unsigned int conn_size;
+ drmu_conn_t ** dns;
+ bool has_max_bpc;
+ bool max_bpc_allow;
+ bool modeset_allow;
+ int mode_id;
+ drmu_mode_simple_params_t mode_params;
+
+ // These are expected to be static consts so no copy / no free
2023-03-29 17:41:09 +00:00
+ const drmu_fmt_info_t * fmt_info;
+ drmu_colorspace_t colorspace;
+ drmu_broadcast_rgb_t broadcast_rgb;
+
+ // HDR metadata
+ drmu_isset_t hdr_metadata_isset;
+ struct hdr_output_metadata hdr_metadata;
+};
+
+drmu_plane_t *
+drmu_output_plane_ref_primary(drmu_output_t * const dout)
+{
+ drmu_plane_t * const dp = drmu_plane_new_find_type(dout->dc, DRMU_PLANE_TYPE_PRIMARY);
+
+ if (dp == NULL || drmu_plane_ref_crtc(dp, dout->dc) != 0)
+ return NULL;
+
+ return dp;
+}
+
+drmu_plane_t *
+drmu_output_plane_ref_other(drmu_output_t * const dout)
+{
+ drmu_plane_t *const dp = drmu_plane_new_find_type(dout->dc, DRMU_PLANE_TYPE_CURSOR | DRMU_PLANE_TYPE_OVERLAY);
+
+ if (dp == NULL || drmu_plane_ref_crtc(dp, dout->dc) != 0)
+ return NULL;
+
+ return dp;
+}
+
2023-03-28 20:02:15 +00:00
+struct plane_format_s {
+ unsigned int types;
+ uint32_t fmt;
+ uint64_t mod;
+};
+
+static bool plane_find_format_cb(const drmu_plane_t * dp, void * v)
+{
+ const struct plane_format_s * const f = v;
+ return (f->types & drmu_plane_type(dp)) != 0 &&
+ drmu_plane_format_check(dp, f->fmt, f->mod);
+}
+
+drmu_plane_t *
+drmu_output_plane_ref_format(drmu_output_t * const dout, const unsigned int types, const uint32_t format, const uint64_t mod)
+{
+ struct plane_format_s fm = {
+ .types = (types != 0) ? types : (DRMU_PLANE_TYPE_PRIMARY | DRMU_PLANE_TYPE_CURSOR | DRMU_PLANE_TYPE_OVERLAY),
+ .fmt = format,
+ .mod = mod
+ };
+
+ drmu_plane_t *const dp = drmu_plane_new_find(dout->dc, plane_find_format_cb, &fm);
+
+ if (dp == NULL || drmu_plane_ref_crtc(dp, dout->dc) != 0)
+ return NULL;
+
+ return dp;
+}
+
+
+int
2022-12-08 21:49:23 +00:00
+drmu_atomic_output_add_props(drmu_atomic_t * const da, drmu_output_t * const dout)
+{
+ int rv = 0;
+ unsigned int i;
+
+ if (!dout->modeset_allow)
+ return 0;
+
+ rv = drmu_atomic_crtc_add_modeinfo(da, dout->dc, drmu_conn_modeinfo(dout->dns[0], dout->mode_id));
+
+ for (i = 0; i != dout->conn_n; ++i) {
+ drmu_conn_t * const dn = dout->dns[i];
+
+ if (dout->fmt_info && dout->max_bpc_allow)
2023-03-29 17:41:09 +00:00
+ rv = rvup(rv, drmu_atomic_conn_add_hi_bpc(da, dn, (drmu_fmt_info_bit_depth(dout->fmt_info) > 8)));
+ if (drmu_colorspace_is_set(dout->colorspace))
2022-12-08 21:49:23 +00:00
+ rv = rvup(rv, drmu_atomic_conn_add_colorspace(da, dn, dout->colorspace));
+ if (drmu_broadcast_rgb_is_set(dout->broadcast_rgb))
2022-12-08 21:49:23 +00:00
+ rv = rvup(rv, drmu_atomic_conn_add_broadcast_rgb(da, dn, dout->broadcast_rgb));
+ if (dout->hdr_metadata_isset != DRMU_ISSET_UNSET)
2022-12-08 21:49:23 +00:00
+ rv = rvup(rv, drmu_atomic_conn_add_hdr_metadata(da, dn,
+ dout->hdr_metadata_isset == DRMU_ISSET_NULL ? NULL : &dout->hdr_metadata));
+ }
+
+ return rv;
+}
+
+// Set all the fb info props that might apply to a crtc on the crtc
+// (e.g. hdr_metadata, colorspace) but do not set the mode (resolution
+// and refresh)
+//
+// N.B. Only changes those props that are set in the fb. If unset in the fb
+// then their value is unchanged.
+int
+drmu_output_fb_info_set(drmu_output_t * const dout, const drmu_fb_t * const fb)
+{
+ const drmu_isset_t hdr_isset = drmu_fb_hdr_metadata_isset(fb);
2023-03-29 17:41:09 +00:00
+ const drmu_fmt_info_t * fmt_info = drmu_fb_format_info_get(fb);
+ const drmu_colorspace_t colorspace = drmu_fb_colorspace_get(fb);
+ const drmu_broadcast_rgb_t broadcast_rgb = drmu_color_range_to_broadcast_rgb(drmu_fb_color_range_get(fb));
+
+ if (fmt_info)
+ dout->fmt_info = fmt_info;
+ if (drmu_colorspace_is_set(colorspace))
+ dout->colorspace = colorspace;
+ if (drmu_broadcast_rgb_is_set(broadcast_rgb))
+ dout->broadcast_rgb = broadcast_rgb;
+
+ if (hdr_isset != DRMU_ISSET_UNSET) {
+ dout->hdr_metadata_isset = hdr_isset;
+ if (hdr_isset == DRMU_ISSET_SET)
+ dout->hdr_metadata = *drmu_fb_hdr_metadata_get(fb);
+ }
+
+ return 0;
+}
+
+void
+drmu_output_fb_info_unset(drmu_output_t * const dout)
+{
+ dout->fmt_info = NULL;
+ dout->colorspace = DRMU_COLORSPACE_UNSET;
+ dout->broadcast_rgb = DRMU_BROADCAST_RGB_UNSET;
+ dout->hdr_metadata_isset = DRMU_ISSET_UNSET;
+}
+
+
+int
+drmu_output_mode_id_set(drmu_output_t * const dout, const int mode_id)
+{
+ drmu_info(dout->du, "%s: mode_id=%d", __func__, mode_id);
+
+ if (mode_id != dout->mode_id) {
+ drmu_mode_simple_params_t sp = drmu_conn_mode_simple_params(dout->dns[0], mode_id);
+ if (sp.width == 0)
+ return -EINVAL;
+
+ dout->mode_id = mode_id;
+ dout->mode_params = sp;
+ }
+ return 0;
+}
+
+const drmu_mode_simple_params_t *
+drmu_output_mode_simple_params(const drmu_output_t * const dout)
+{
+ return &dout->mode_params;
+}
+
+static int
+score_freq(const drmu_mode_simple_params_t * const mode, const drmu_mode_simple_params_t * const p)
+{
+ const int pref = (mode->type & DRM_MODE_TYPE_PREFERRED) != 0;
+ const unsigned int r_m = (mode->flags & DRM_MODE_FLAG_INTERLACE) != 0 ?
+ mode->hz_x_1000 * 2: mode->hz_x_1000;
+ const unsigned int r_f = (p->flags & DRM_MODE_FLAG_INTERLACE) != 0 ?
+ p->hz_x_1000 * 2 : p->hz_x_1000;
+
+ // If we haven't been given any hz then pick pref or fastest
+ // Max out at 300Hz (=300,0000)
+ if (r_f == 0)
+ return pref ? 83000000 : 80000000 + (r_m >= 2999999 ? 2999999 : r_m);
+ // Prefer a good match to 29.97 / 30 but allow the other
+ else if ((r_m + 10 >= r_f && r_m <= r_f + 10))
+ return 100000000;
+ else if ((r_m + 100 >= r_f && r_m <= r_f + 100))
+ return 95000000;
+ // Double isn't bad
+ else if ((r_m + 10 >= r_f * 2 && r_m <= r_f * 2 + 10))
+ return 90000000;
+ else if ((r_m + 100 >= r_f * 2 && r_m <= r_f * 2 + 100))
+ return 85000000;
+ return -1;
+}
+
+// Avoid interlace no matter what our source
+int
+drmu_mode_pick_simple_cb(void * v, const drmu_mode_simple_params_t * mode)
+{
+ const drmu_mode_simple_params_t * const p = v;
+ const int pref = (mode->type & DRM_MODE_TYPE_PREFERRED) != 0;
+ int score = -1;
+
+ if (p->width == mode->width && p->height == mode->height &&
+ (mode->flags & DRM_MODE_FLAG_INTERLACE) == 0)
+ score = score_freq(mode, p);
+
+ if (score > 0 && (p->width != mode->width || p->height != mode->height))
+ score -= 30000000;
+
+ if (score <= 0 && pref)
+ score = 10000000;
+
+ return score;
+}
+
+// Try to match interlace as well as everything else
+int
+drmu_mode_pick_simple_interlace_cb(void * v, const drmu_mode_simple_params_t * mode)
+{
+ const drmu_mode_simple_params_t * const p = v;
+
+ const int pref = (mode->type & DRM_MODE_TYPE_PREFERRED) != 0;
+ int score = -1;
+
+ if (p->width == mode->width && p->height == mode->height)
+ score = score_freq(mode, p);
+
+ if (score > 0 && (p->width != mode->width || p->height != mode->height))
+ score -= 30000000;
+ if (((mode->flags ^ p->flags) & DRM_MODE_FLAG_INTERLACE) != 0)
+ score -= 20000000;
+
+ if (score <= 0 && pref)
+ score = 10000000;
+
+ return score;
+}
+
+
+int
+drmu_output_mode_pick_simple(drmu_output_t * const dout, drmu_mode_score_fn * const score_fn, void * const score_v)
+{
+ int best_score = -1;
+ int best_mode = -1;
+ int i;
+
+ for (i = 0;; ++i) {
+ const drmu_mode_simple_params_t sp = drmu_conn_mode_simple_params(dout->dns[0], i);
+ int score;
+
+ if (sp.width == 0)
+ break;
+
+ score = score_fn(score_v, &sp);
+ if (score > best_score) {
+ best_score = score;
+ best_mode = i;
+ }
+ }
+
+ return best_mode;
+}
+
+int
+drmu_output_max_bpc_allow(drmu_output_t * const dout, const bool allow)
+{
+ dout->max_bpc_allow = allow && dout->has_max_bpc;
+ return allow && !dout->has_max_bpc ? -ENOENT : 0;
+}
+
+int
+drmu_output_modeset_allow(drmu_output_t * const dout, const bool allow)
+{
+ dout->modeset_allow = allow;
+ return 0;
+}
+
+static int
+check_conns_size(drmu_output_t * const dout)
+{
+ if (dout->conn_n >= dout->conn_size) {
+ unsigned int n = !dout->conn_n ? 4 : dout->conn_n * 2;
+ drmu_conn_t ** dns = realloc(dout->dns, sizeof(*dout->dns) * n);
+ if (dns == NULL) {
+ drmu_err(dout->du, "Failed conn array realloc");
+ return -ENOMEM;
+ }
+ dout->dns = dns;
+ dout->conn_size = n;
+ }
+ return 0;
+}
+
+int
+drmu_output_add_output(drmu_output_t * const dout, const char * const conn_name)
+{
+ const size_t nlen = !conn_name ? 0 : strlen(conn_name);
+ unsigned int i;
+ unsigned int retries = 0;
+ drmu_env_t * const du = dout->du;
+ drmu_conn_t * dn;
+ drmu_conn_t * dn_t;
+ drmu_crtc_t * dc_t;
+ uint32_t crtc_id;
+ int rv;
+
+ // *****
+ // This logic fatally flawed for anything other than adding a single
+ // conn already attached to a single crtc
+
+retry:
+ if (++retries > 16) {
+ drmu_err(du, "Retry count exceeded");
+ return -EBUSY;
+ }
+ dn = NULL;
+ dc_t = NULL;
+
+ for (i = 0; (dn_t = drmu_env_conn_find_n(du, i)) != NULL; ++i) {
+ if (!drmu_conn_is_output(dn_t) || drmu_conn_is_claimed(dn_t))
+ continue;
+ if (nlen && strncmp(conn_name, drmu_conn_name(dn_t), nlen) != 0)
+ continue;
+ // This prefers conns that are already attached to crtcs
+ if ((crtc_id = drmu_conn_crtc_id_get(dn_t)) == 0 ||
+ (dc_t = drmu_env_crtc_find_id(du, crtc_id)) == NULL) {
+ dn = dn_t;
+ continue;
+ }
+ if (drmu_crtc_is_claimed(dc_t)) {
+ dc_t = NULL;
+ continue;
+ }
+ dn = dn_t;
+ break;
+ }
+
+ if (!dn)
+ return -ENOENT;
+
+ if (!dc_t) {
+ drmu_warn(du, "Adding unattached conns NIF");
+ return -EINVAL;
+ }
+
+ if ((rv = check_conns_size(dout)) != 0)
+ return rv;
+
+ if (drmu_crtc_claim_ref(dc_t)) {
+ drmu_debug(du, "Crtc already claimed");
+ goto retry;
+ }
+ if (drmu_conn_claim_ref(dn)) {
+ drmu_debug(du, "Conn already claimed");
+ drmu_crtc_unref(&dc_t);
+ goto retry;
+ }
+
+ // Test features
+ {
+ drmu_atomic_t * da = drmu_atomic_new(du);
+ if (!da)
+ return -ENOMEM;
2022-12-08 21:49:23 +00:00
+ dout->has_max_bpc = (drmu_atomic_conn_add_hi_bpc(da, dn, true) == 0);
+ drmu_atomic_unref(&da);
+ }
+
+ dout->dns[dout->conn_n++] = dn;
+ dout->dc = dc_t;
+
+ dout->mode_params = drmu_crtc_mode_simple_params(dout->dc);
+
+ return 0;
+}
+
+static struct drm_mode_modeinfo
+modeinfo_fake(unsigned int w, unsigned int h)
+{
+ return (struct drm_mode_modeinfo){
+ .clock = (h + 30)*(w + 20)*60,
+ .hdisplay = w,
+ .hsync_start = w + 10,
+ .hsync_end = w + 20,
+ .htotal = w + 30,
+ .hskew = 0,
+ .vdisplay = h,
+ .vsync_start = h + 10,
+ .vsync_end = h + 12,
+ .vtotal = h + 20,
+ .vscan = 0,
+ .vrefresh = 60,
+ .type = DRM_MODE_TYPE_USERDEF,
+ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+ .name = {"fake"},
+ };
+}
+
+static int
+try_conn_crtc(drmu_env_t * du, drmu_conn_t * dn, drmu_crtc_t * dc)
+{
+ int rv;
+
+#if 1
+ const struct drm_mode_modeinfo test_mode = modeinfo_fake(128,128);
+#else
+ // A real mode for testing
+ static const struct drm_mode_modeinfo test_mode = {
+ .clock = 25175,
+ .hdisplay = 640,
+ .hsync_start = 656,
+ .hsync_end = 752,
+ .htotal = 800,
+ .hskew = 0,
+ .vdisplay = 480,
+ .vsync_start = 490,
+ .vsync_end = 492,
+ .vtotal = 525,
+ .vscan = 0,
+ .vrefresh = 60,
+ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+ .name = {"640x480-60"},
+ };
+#endif
+
+ drmu_atomic_t * da = drmu_atomic_new(du);
+
+ if (!da)
+ return -ENOMEM;
+
+ if ((rv = drmu_atomic_conn_add_crtc(da, dn, dc)) != 0) {
+ drmu_warn(du, "Failed to add writeback connector to crtc: %s", strerror(-rv));
+ goto fail;
+ }
+ if ((rv = drmu_atomic_crtc_add_modeinfo(da, dc, &test_mode)) != 0) {
+ drmu_warn(du, "Failed to add modeinfo: %s", strerror(-rv));
+ goto fail;
+ }
+
+ if ((rv = drmu_atomic_crtc_add_active(da, dc, 1)) != 0) {
+ drmu_warn(du, "Failed to add active to crtc: %s", strerror(-rv));
+ goto fail;
+ }
+
+ if ((rv = drmu_atomic_commit(da, DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET)) != 0) {
+ drmu_warn(du, "Failed test commit of writeback connector to crtc: %s", strerror(-rv));
+ goto fail;
+ }
+
+ drmu_atomic_unref(&da);
+ return 0;
+
+fail:
+ drmu_atomic_unref(&da);
+ return rv;
+}
+
+int
+drmu_atomic_output_add_writeback_fb(drmu_atomic_t * const da_out, drmu_output_t * const dout,
+ drmu_fb_t * const dfb)
+{
+ drmu_env_t * const du = dout->du;
+ drmu_atomic_t * da = drmu_atomic_new(drmu_atomic_env(da_out));
+ int rv = -ENOMEM;
+ struct drm_mode_modeinfo mode = modeinfo_fake(drmu_fb_width(dfb), drmu_fb_height(dfb));
+ drmu_conn_t * const dn = dout->dns[0];
+
+ if (da == NULL)
+ return -ENOMEM;
+
+ if ((rv = drmu_atomic_conn_add_writeback_fb(da, dn, dfb)) != 0) {
+ drmu_err(du, "Failed to add FB to conn");
+ goto fail;
+ }
+ if ((rv = drmu_atomic_crtc_add_modeinfo(da, dout->dc, &mode)) != 0) {
+ drmu_err(du, "Failed to add modeinfo to CRTC");
+ goto fail;
+ }
+ if ((rv = drmu_atomic_conn_add_crtc(da, dn, dout->dc)) != 0) {
+ drmu_err(du, "Failed to add CRTC to Conn");
+ goto fail;
+ }
+ if ((rv = drmu_atomic_crtc_add_active(da, dout->dc, 1)) != 0) {
+ drmu_err(du, "Failed to add Active to Conn");
+ goto fail;
+ }
+
+ return drmu_atomic_merge(da_out, &da);
+
+fail:
+ drmu_atomic_unref(&da);
+ return rv;
+}
+
+int
+drmu_output_add_writeback(drmu_output_t * const dout)
+{
+ drmu_env_t * const du = dout->du;
+ drmu_conn_t * dn = NULL;
+ drmu_crtc_t * dc = NULL;
+ drmu_conn_t * dn_t;
+ int rv;
+ uint32_t possible_crtcs;
+
+ if (!dout->modeset_allow) {
+ drmu_debug(du, "modeset_allow required for writeback");
+ return -EINVAL;
+ }
+
+ for (unsigned int i = 0; (dn_t = drmu_env_conn_find_n(du, i)) != NULL; ++i) {
+ drmu_info(du, "%d: try %s", i, drmu_conn_name(dn_t));
+ if (!drmu_conn_is_writeback(dn_t))
+ continue;
+ dn = dn_t;
+ break;
+ }
+
+ if (!dn) {
+ drmu_err(du, "no writeback conn found");
+ return -ENOENT;
+ }
+
+ possible_crtcs = drmu_conn_possible_crtcs(dn);
+
+ for (unsigned int i = 0; possible_crtcs != 0; ++i, possible_crtcs >>= 1) {
+ drmu_crtc_t *dc_t;
+
+ if ((possible_crtcs & 1) == 0)
+ continue;
+
+ drmu_info(du, "try Crtc %d", i);
+
+ if ((dc_t = drmu_env_crtc_find_n(du, i)) == NULL)
+ break;
+
+ if (try_conn_crtc(du, dn, dc_t) == 0) {
+ dc = dc_t;
+ break;
+ }
+ }
+
+ if (!dc) {
+ drmu_err(du, "No crtc for writeback found");
+ return -ENOENT;
+ }
+
+ if ((rv = check_conns_size(dout)) != 0)
+ return rv;
+
+ dout->dns[dout->conn_n++] = dn;
+ dout->dc = dc;
+ return 0;
+}
+
+drmu_crtc_t *
+drmu_output_crtc(const drmu_output_t * const dout)
+{
+ return !dout ? NULL : dout->dc;
+}
+
+drmu_conn_t *
+drmu_output_conn(const drmu_output_t * const dout, const unsigned int n)
+{
+ return !dout || n >= dout->conn_n ? NULL : dout->dns[n];
+}
+
+static void
+output_free(drmu_output_t * const dout)
+{
+ unsigned int i;
+ for (i = 0; i != dout->conn_n; ++i)
+ drmu_conn_unref(dout->dns + i);
+ free(dout->dns);
+ drmu_crtc_unref(&dout->dc);
+ free(dout);
+}
+
+void
+drmu_output_unref(drmu_output_t ** const ppdout)
+{
+ drmu_output_t * const dout = *ppdout;
+ if (dout == NULL)
+ return;
+ *ppdout = NULL;
+
+ output_free(dout);
+}
+
+drmu_output_t *
+drmu_output_new(drmu_env_t * const du)
+{
+ drmu_output_t * const dout = calloc(1, sizeof(*dout));
+
+ if (dout == NULL) {
+ drmu_err(du, "Failed to alloc memory for drmu_output");
+ return NULL;
+ }
+
+ dout->du = du;
+ dout->mode_id = -1;
+ return dout;
+}
+
diff --git a/modules/video_output/drmu/drmu_output.h b/modules/video_output/drmu/drmu_output.h
new file mode 100644
index 0000000000..c977b24721
--- /dev/null
+++ b/modules/video_output/drmu/drmu_output.h
2023-03-28 20:02:15 +00:00
@@ -0,0 +1,81 @@
+#ifndef _DRMU_DRMU_OUTPUT_H
+#define _DRMU_DRMU_OUTPUT_H
+
+#include "drmu.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct drmu_output_s;
+typedef struct drmu_output_s drmu_output_t;
+
+drmu_plane_t * drmu_output_plane_ref_primary(drmu_output_t * const dout);
+drmu_plane_t * drmu_output_plane_ref_other(drmu_output_t * const dout);
2023-03-28 20:02:15 +00:00
+// Find and ref a plane that supports the given format & mod on the current crtc
+// Types is a bit field of acceptable plane types (DRMU_PLANE_TYPE_xxx), 0 => any
+//
+// add_output must be called before this (so we have a crtc to check against)
+drmu_plane_t * drmu_output_plane_ref_format(drmu_output_t * const dout, const unsigned int types, const uint32_t format, const uint64_t mod);
+
+// Add all props accumulated on the output to the atomic
2022-12-08 21:49:23 +00:00
+int drmu_atomic_output_add_props(drmu_atomic_t * const da, drmu_output_t * const dout);
+
+// Set FB info (bit-depth, HDR metadata etc.)
+// Only sets properties that are set in the fb - retains previous value otherwise
+int drmu_output_fb_info_set(drmu_output_t * const dout, const drmu_fb_t * const fb);
+// Unset all FB info
+// (set only sets stuff that is set in the fb, so will never clear anything)
+void drmu_output_fb_info_unset(drmu_output_t * const dout);
+
+// Set output mode
+int drmu_output_mode_id_set(drmu_output_t * const dout, const int mode_id);
+
+// Width/height of the currebnt mode
+const drmu_mode_simple_params_t * drmu_output_mode_simple_params(const drmu_output_t * const dout);
+
+typedef int drmu_mode_score_fn(void * v, const drmu_mode_simple_params_t * mode);
+
+int drmu_output_mode_pick_simple(drmu_output_t * const dout, drmu_mode_score_fn * const score_fn, void * const score_v);
+
+// Simple mode picker cb - looks for width / height and then refresh
+// If nothing "plausible" defaults to EDID preferred mode
+drmu_mode_score_fn drmu_mode_pick_simple_cb;
+// As above but may choose an interlaced mode
+drmu_mode_score_fn drmu_mode_pick_simple_interlace_cb;
+
+// Allow fb max_bpc info to set the output mode (default false)
+int drmu_output_max_bpc_allow(drmu_output_t * const dout, const bool allow);
+
+// Allow fb to set modes generally
+int drmu_output_modeset_allow(drmu_output_t * const dout, const bool allow);
+
+// Add a CONN/CRTC pair to an output
+// If conn_name == NULL then 1st connected connector is used
+// If != NULL then 1st conn with prefix-matching name is used
+int drmu_output_add_output(drmu_output_t * const dout, const char * const conn_name);
+
+// Set writeback fb on output
+int drmu_atomic_output_add_writeback_fb(drmu_atomic_t * const da_req, drmu_output_t * const dout,
+ drmu_fb_t * const dfb);
+
+// Add a writeback connector & find a crtc for it
+int drmu_output_add_writeback(drmu_output_t * const dout);
+
+// Conn & CRTC for when output isn't fine grained enough
+drmu_crtc_t * drmu_output_crtc(const drmu_output_t * const dout);
+drmu_conn_t * drmu_output_conn(const drmu_output_t * const dout, const unsigned int n);
+
+// Create a new empty output - has no crtc or conn
+drmu_output_t * drmu_output_new(drmu_env_t * const du);
+
+// Unref an output
+void drmu_output_unref(drmu_output_t ** const ppdout);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+
diff --git a/modules/video_output/drmu/drmu_util.c b/modules/video_output/drmu/drmu_util.c
new file mode 100644
index 0000000000..12a088216d
--- /dev/null
+++ b/modules/video_output/drmu/drmu_util.c
@@ -0,0 +1,125 @@
+#include "drmu.h"
+#include "drmu_util.h"
+
+#include <ctype.h>
+#include <error.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libdrm/drm_mode.h>
+
+static unsigned long
+h_to_w(const unsigned long h)
+{
+ switch (h) {
+ case 480:
+ case 576:
+ return 720;
+ case 720:
+ return 1280;
+ case 1080:
+ return 1920;
+ case 2160:
+ return 3840;
+ default:
+ break;
+ }
+ return 0;
+}
+
+char *
+drmu_util_parse_mode_simple_params(const char * s, drmu_mode_simple_params_t * const p)
+{
+ unsigned long w = 0, h = 0, hz = 0;
+ bool il = false;
+ bool drmhz = false;
+
+ memset(p , 0, sizeof(*p));
+
+ if (isdigit(*s)) {
+ h = strtoul(s, (char **)&s, 10);
+
+ if (*s == 'p' || *s == 'i') {
+ w = h_to_w(h);
+ }
+ else if (*s == 'x') {
+ w = h;
+ h = strtoul(s + 1, (char **)&s, 10);
+ }
+ else {
+ return (char *)s;
+ }
+ }
+
+ // Consume 'i' or 'p'
+ // Can still have (now) optional hz separator after
+ if (*s == 'p') {
+ ++s;
+ }
+ else if (*s == 'i') {
+ il = true;
+ ++s;
+ }
+
+ // I've used '@' in the past to separate size from hz
+ // DRM uses '-' in modetest so accept that
+ if (*s == '@') {
+ ++s;
+ }
+ else if (*s == '-') {
+ drmhz = true;
+ ++s;
+ }
+
+ if (isdigit(*s)) {
+ hz = strtoul(s, (char **)&s, 10) * 1000;
+
+ if (*s == '.') {
+ unsigned int m = 100;
+ while (isdigit(*++s)) {
+ hz += (*s - '0') * m;
+ m /= 10;
+ }
+ }
+ }
+
+ // DRM thinks in frame rate, rest of the world specifies as field rate
+ if (il && !drmhz)
+ hz /= 2;
+
+ p->width = (unsigned int)w;
+ p->height = (unsigned int)h;
+ p->hz_x_1000 = (unsigned int)hz;
+ p->flags = !il ? 0 : DRM_MODE_FLAG_INTERLACE;
+
+ return (char *)s;
+}
+
+char *
+drmu_util_simple_param_to_mode_str(char * buf, size_t buflen, const drmu_mode_simple_params_t * const p)
+{
+ int hz = p->hz_x_1000;
+
+ if ((p->flags & DRM_MODE_FLAG_INTERLACE))
+ hz *= 2;
+
+ snprintf(buf, buflen, "%dx%d%c%d.%03d",
+ p->width, p->height,
+ (p->flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p',
+ hz / 1000, hz % 1000);
+ return buf;
+}
+
+char *
+drmu_util_parse_mode(const char * s, unsigned int * pw, unsigned int * ph, unsigned int * phz)
+{
+ drmu_mode_simple_params_t p;
+ char * r = drmu_util_parse_mode_simple_params(s, &p);
+ *pw = p.width;
+ *ph = p.height;
+ *phz = p.hz_x_1000;
+ return r;
+}
+
+
diff --git a/modules/video_output/drmu/drmu_util.h b/modules/video_output/drmu/drmu_util.h
new file mode 100644
index 0000000000..9c4ea46e4c
--- /dev/null
+++ b/modules/video_output/drmu/drmu_util.h
@@ -0,0 +1,30 @@
+#ifndef _DRMU_DRMU_UTIL_H
+#define _DRMU_DRMU_UTIL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct drmu_mode_simple_params_s;
+
+// Parse a string of the form [<w>x<h>][i][@<hz>[.<mHz>]]
+// Returns pointer to terminating char
+// Missing fields are zero
+char * drmu_util_parse_mode(const char * s, unsigned int * pw, unsigned int * ph, unsigned int * pHzx1000);
+
+// As above but place results into a simple params structure
+// (Unused fields zeroed)
+// Also copes with interlace
+char * drmu_util_parse_mode_simple_params(const char * s, struct drmu_mode_simple_params_s * const p);
+
+// Simple params to mode string
+char * drmu_util_simple_param_to_mode_str(char * buf, size_t buflen, const drmu_mode_simple_params_t * const p);
+
+#define drmu_util_simple_mode(p) drmu_util_simple_param_to_mode_str((char[64]){0}, 64, (p))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/modules/video_output/drmu/drmu_vlc.c b/modules/video_output/drmu/drmu_vlc.c
new file mode 100644
index 0000000000..f2efd30c21
--- /dev/null
+++ b/modules/video_output/drmu/drmu_vlc.c
@@ -0,0 +1,351 @@
+#include "drmu_vlc.h"
2023-03-29 17:41:09 +00:00
+#include "drmu_fmts.h"
+#include "drmu_log.h"
+
+#if HAS_ZC_CMA
+#include "../../hw/mmal/mmal_cma_pic.h"
+#endif
+#if HAS_DRMPRIME
+#include "../../codec/avcodec/drm_pic.h"
+#endif
+
+#include <errno.h>
+
+#include <libavutil/buffer.h>
+#include <libavutil/hwcontext_drm.h>
+
+#include <libdrm/drm_fourcc.h>
+
+typedef struct fb_aux_pic_s {
+ picture_context_t * pic_ctx;
+} fb_aux_pic_t;
+
+static void
+pic_fb_delete_cb(void * v)
+{
+ fb_aux_pic_t * const aux = v;
+
+ aux->pic_ctx->destroy(aux->pic_ctx);
+ free(aux);
+}
+
+static int
+pic_hdr_metadata(struct hdr_output_metadata * const m, const struct video_format_t * const fmt)
+{
+ struct hdr_metadata_infoframe * const inf = &m->hdmi_metadata_type1;
+ unsigned int i;
+
+ memset(m, 0, sizeof(*m));
+ m->metadata_type = HDMI_STATIC_METADATA_TYPE1;
+ inf->metadata_type = HDMI_STATIC_METADATA_TYPE1;
+
+ switch (fmt->transfer) {
+ case TRANSFER_FUNC_SMPTE_ST2084:
+ inf->eotf = HDMI_EOTF_SMPTE_ST2084;
+ break;
+ case TRANSFER_FUNC_ARIB_B67:
+ inf->eotf = HDMI_EOTF_BT_2100_HLG;
+ break;
+ default:
+ // HDMI_EOTF_TRADITIONAL_GAMMA_HDR for 10bit?
+ inf->eotf = HDMI_EOTF_TRADITIONAL_GAMMA_SDR;
+ return -ENOENT;
+ }
+
+ // VLC & HDMI use the same scales for everything but max_luma
+ for (i = 0; i != 3; ++i) {
+ inf->display_primaries[i].x = fmt->mastering.primaries[i * 2 + 0];
+ inf->display_primaries[i].y = fmt->mastering.primaries[i * 2 + 1];
+ }
+ inf->white_point.x = fmt->mastering.white_point[0];
+ inf->white_point.y = fmt->mastering.white_point[1];
+ inf->max_display_mastering_luminance = (uint16_t)(fmt->mastering.max_luminance / 10000);
+ inf->min_display_mastering_luminance = (uint16_t)fmt->mastering.min_luminance;
+
+ inf->max_cll = fmt->lighting.MaxCLL;
+ inf->max_fall = fmt->lighting.MaxFALL;
+
+ return 0;
+}
+
+static drmu_color_encoding_t
+fb_vlc_color_encoding(const video_format_t * const fmt)
+{
+ switch (fmt->space)
+ {
+ case COLOR_SPACE_BT2020:
+ return DRMU_COLOR_ENCODING_BT2020;
+ case COLOR_SPACE_BT601:
+ return DRMU_COLOR_ENCODING_BT601;
+ case COLOR_SPACE_BT709:
+ return DRMU_COLOR_ENCODING_BT709;
+ case COLOR_SPACE_UNDEF:
+ default:
+ break;
+ }
+
+ return (fmt->i_visible_width > 1024 || fmt->i_visible_height > 600) ?
+ DRMU_COLOR_ENCODING_BT709 :
+ DRMU_COLOR_ENCODING_BT601;
+}
+
+static drmu_color_range_t
+fb_vlc_color_range(const video_format_t * const fmt)
+{
+#if HAS_VLC4
+ switch (fmt->color_range)
+ {
+ case COLOR_RANGE_FULL:
+ return DRMU_COLOR_RANGE_YCBCR_FULL_RANGE;
+ case COLOR_RANGE_UNDEF:
+ case COLOR_RANGE_LIMITED:
+ default:
+ break;
+ }
+#else
+ if (fmt->b_color_range_full)
+ return DRMU_COLOR_RANGE_YCBCR_FULL_RANGE;
+#endif
+ return DRMU_COLOR_RANGE_YCBCR_LIMITED_RANGE;
+}
+
+
+static const char *
+fb_vlc_colorspace(const video_format_t * const fmt)
+{
+ switch (fmt->space) {
+ case COLOR_SPACE_BT2020:
+ return DRMU_COLORSPACE_BT2020_RGB;
+ default:
+ break;
+ }
+ return DRMU_COLORSPACE_DEFAULT;
+}
+
+static drmu_chroma_siting_t
+fb_vlc_chroma_siting(const video_format_t * const fmt)
+{
+ switch (fmt->chroma_location) {
+ case CHROMA_LOCATION_LEFT:
+ return DRMU_CHROMA_SITING_LEFT;
+ case CHROMA_LOCATION_CENTER:
+ return DRMU_CHROMA_SITING_CENTER;
+ case CHROMA_LOCATION_TOP_LEFT:
+ return DRMU_CHROMA_SITING_TOP_LEFT;
+ case CHROMA_LOCATION_TOP_CENTER:
+ return DRMU_CHROMA_SITING_TOP;
+ case CHROMA_LOCATION_BOTTOM_LEFT:
+ return DRMU_CHROMA_SITING_BOTTOM_LEFT;
+ case CHROMA_LOCATION_BOTTOM_CENTER:
+ return DRMU_CHROMA_SITING_BOTTOM;
+ default:
+ case CHROMA_LOCATION_UNDEF:
+ break;
+ }
+ return DRMU_CHROMA_SITING_UNSPECIFIED;
+}
+
+void
+drmu_fb_vlc_pic_set_metadata(drmu_fb_t * const dfb, const picture_t * const pic)
+{
+ struct hdr_output_metadata meta;
+
+ drmu_fb_int_color_set(dfb,
+ fb_vlc_color_encoding(&pic->format),
+ fb_vlc_color_range(&pic->format),
+ fb_vlc_colorspace(&pic->format));
+
+ drmu_fb_int_chroma_siting_set(dfb, fb_vlc_chroma_siting(&pic->format));
+
+ drmu_fb_hdr_metadata_set(dfb, pic_hdr_metadata(&meta, &pic->format) == 0 ? &meta : NULL);
+}
+
+#if HAS_DRMPRIME
+// Create a new fb from a VLC DRM_PRIME picture.
+// Picture is held reffed by the fb until the fb is deleted
+drmu_fb_t *
+drmu_fb_vlc_new_pic_attach(drmu_env_t * const du, picture_t * const pic)
+{
+ int i, j, n;
+ drmu_fb_t * const dfb = drmu_fb_int_alloc(du);
+ const AVDRMFrameDescriptor * const desc = drm_prime_get_desc(pic);
+ fb_aux_pic_t * aux = NULL;
+
+ if (dfb == NULL) {
+ drmu_err(du, "%s: Alloc failure", __func__);
+ return NULL;
+ }
+
+ if (desc == NULL) {
+ drmu_err(du, "%s: Missing descriptor", __func__);
+ goto fail;
+ }
+ if (desc->nb_objects > 4) {
+ drmu_err(du, "%s: Bad descriptor", __func__);
+ goto fail;
+ }
+
+ drmu_fb_int_fmt_size_set(dfb,
+ desc->layers[0].format,
+ pic->format.i_width,
+ pic->format.i_height,
+ drmu_rect_vlc_pic_crop(pic));
+
+
+ // Set delete callback & hold this pic
+ // Aux attached to dfb immediately so no fail cleanup required
+ if ((aux = calloc(1, sizeof(*aux))) == NULL) {
+ drmu_err(du, "%s: Aux alloc failure", __func__);
+ goto fail;
+ }
+ aux->pic_ctx = pic->context->copy(pic->context);
+ drmu_fb_int_on_delete_set(dfb, pic_fb_delete_cb, aux);
+
+ for (i = 0; i < desc->nb_objects; ++i)
+ {
+ drmu_bo_t * bo = drmu_bo_new_fd(du, desc->objects[i].fd);
+ if (bo == NULL)
+ goto fail;
+ drmu_fb_int_bo_set(dfb, i, bo);
+ }
+
+ n = 0;
+ for (i = 0; i < desc->nb_layers; ++i)
+ {
+ for (j = 0; j < desc->layers[i].nb_planes; ++j)
+ {
+ const AVDRMPlaneDescriptor *const p = desc->layers[i].planes + j;
+ const AVDRMObjectDescriptor *const obj = desc->objects + p->object_index;
+
+ drmu_fb_int_layer_mod_set(dfb, n++, p->object_index, p->pitch, p->offset, obj->format_modifier);
+ }
+ }
+
+ drmu_fb_vlc_pic_set_metadata(dfb, pic);
+
+ if (drmu_fb_int_make(dfb) != 0)
+ goto fail;
+ return dfb;
+
+fail:
+ drmu_fb_int_free(dfb);
+ return NULL;
+}
+#endif
+
+#if HAS_ZC_CMA
+drmu_fb_t *
+drmu_fb_vlc_new_pic_cma_attach(drmu_env_t * const du, picture_t * const pic)
+{
+ int i;
+ drmu_fb_t * const dfb = drmu_fb_int_alloc(du);
+ fb_aux_pic_t * aux = NULL;
+ uint32_t fmt = drmu_format_vlc_to_drm_cma(pic->format.i_chroma);
+ const bool is_sand = (pic->format.i_chroma == VLC_CODEC_MMAL_ZC_SAND8 ||
+ pic->format.i_chroma == VLC_CODEC_MMAL_ZC_SAND30);
+ cma_buf_t * const cb = cma_buf_pic_get(pic);
+
+ if (dfb == NULL) {
+ drmu_err(du, "%s: Alloc failure", __func__);
+ return NULL;
+ }
+
+ if (fmt == 0) {
+ drmu_err(du, "Pic bad format for cma");
+ goto fail;
+ }
+
+ if (cb == NULL) {
+ drmu_err(du, "Pic missing cma block");
+ goto fail;
+ }
+
+ drmu_fb_int_fmt_size_set(dfb,
+ fmt,
+ pic->format.i_width,
+ pic->format.i_height,
+ drmu_rect_vlc_pic_crop(pic));
+
+ // Set delete callback & hold this pic
+ // Aux attached to dfb immediately so no fail cleanup required
+ if ((aux = calloc(1, sizeof(*aux))) == NULL) {
+ drmu_err(du, "%s: Aux alloc failure", __func__);
+ goto fail;
+ }
+ aux->pic_ctx = pic->context->copy(pic->context);
+ drmu_fb_int_on_delete_set(dfb, pic_fb_delete_cb, aux);
+
+ {
+ drmu_bo_t * bo = drmu_bo_new_fd(du, cma_buf_fd(cb));
+ if (bo == NULL)
+ goto fail;
+ drmu_fb_int_bo_set(dfb, 0, bo);
+ }
+
+ {
+ uint8_t * const base_addr = cma_buf_addr(cb);
+ for (i = 0; i < pic->i_planes; ++i) {
+ if (is_sand)
+ drmu_fb_int_layer_mod_set(dfb, i, 0, pic->format.i_width, pic->p[i].p_pixels - base_addr,
+ DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(pic->p[i].i_pitch));
+ else
+ drmu_fb_int_layer_mod_set(dfb, i, 0, pic->p[i].i_pitch, pic->p[i].p_pixels - base_addr, 0);
+ }
+ }
+
+ drmu_fb_vlc_pic_set_metadata(dfb, pic);
+
+ if (drmu_fb_int_make(dfb) != 0)
+ goto fail;
+ return dfb;
+
+fail:
+ drmu_fb_int_free(dfb);
+ return NULL;
+}
+#endif
+
+plane_t
+drmu_fb_vlc_plane(drmu_fb_t * const dfb, const unsigned int plane_n)
+{
2023-03-29 17:41:09 +00:00
+ const drmu_fmt_info_t *const f = drmu_fb_format_info_get(dfb);
+ unsigned int hdiv = drmu_fmt_info_hdiv(f, plane_n);
+ unsigned int wdiv = drmu_fmt_info_wdiv(f, plane_n);
+ const unsigned int bpp = drmu_fmt_info_pixel_bits(f);
+ const uint32_t pitch_n = drmu_fb_pitch(dfb, plane_n);
+ const drmu_rect_t crop = drmu_fb_crop_frac(dfb);
+
+ if (pitch_n == 0) {
+ return (plane_t) {.p_pixels = NULL };
+ }
+
+ return (plane_t){
+ .p_pixels = drmu_fb_data(dfb, plane_n),
+ .i_lines = drmu_fb_height(dfb) / hdiv,
+ .i_pitch = pitch_n,
+ .i_pixel_pitch = bpp / 8,
+ .i_visible_lines = (crop.h >> 16) / hdiv,
+ .i_visible_pitch = ((crop.w >> 16) * bpp / 8) / wdiv
+ };
+}
+
+#if !HAS_VLC4
+#define vlc_object_vaLog vlc_vaLog
+#endif
+
+void
+drmu_log_vlc_cb(void * v, enum drmu_log_level_e level_drmu, const char * fmt, va_list vl)
+{
+ const char * const file_name = va_arg(vl, const char *);
+ const unsigned int line_no = va_arg(vl, unsigned int);
+ const char * const function_name = va_arg(vl, const char *);
+ const int level_vlc =
+ level_drmu <= DRMU_LOG_LEVEL_MESSAGE ? VLC_MSG_INFO :
+ level_drmu <= DRMU_LOG_LEVEL_ERROR ? VLC_MSG_ERR :
+ level_drmu <= DRMU_LOG_LEVEL_WARNING ? VLC_MSG_WARN :
+ VLC_MSG_DBG;
+
+ vlc_object_vaLog((vlc_object_t *)v, level_vlc, vlc_module_name, file_name, line_no,
+ function_name, fmt + DRMU_LOG_FMT_OFFSET_FMT, vl);
+}
+
diff --git a/modules/video_output/drmu/drmu_vlc.h b/modules/video_output/drmu/drmu_vlc.h
new file mode 100644
index 0000000000..5c19141930
--- /dev/null
+++ b/modules/video_output/drmu/drmu_vlc.h
@@ -0,0 +1,81 @@
+#ifndef _DRMU_DRMU_VLC_H
+#define _DRMU_DRMU_VLC_H
+
+#include "config.h"
+
+#ifndef HAS_VLC4
+#define HAS_VLC4 0
+#endif
+#ifndef HAS_ZC_CMA
+#define HAS_ZC_CMA 0
+#endif
+#define HAS_DRMPRIME 1
+
+#include <stdint.h>
+
+#include <vlc_common.h>
+#include <vlc_picture.h>
+#include <vlc_vout_display.h>
+
+#include "drmu.h"
+#include "drmu_vlc_fmts.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Get cropping rectangle from a vlc format
+static inline drmu_rect_t
+drmu_rect_vlc_format_crop(const video_frame_format_t * const format)
+{
+ return (drmu_rect_t){
+ .x = format->i_x_offset,
+ .y = format->i_y_offset,
+ .w = format->i_visible_width,
+ .h = format->i_visible_height};
+}
+
+// Get cropping rectangle from a vlc pic
+static inline drmu_rect_t
+drmu_rect_vlc_pic_crop(const picture_t * const pic)
+{
+ return drmu_rect_vlc_format_crop(&pic->format);
+}
+
+// Get rect from vlc place
+static inline drmu_rect_t
+drmu_rect_vlc_place(const vout_display_place_t * const place)
+{
+ return (drmu_rect_t){
+ .x = place->x,
+ .y = place->y,
+ .w = place->width,
+ .h = place->height
+ };
+}
+
+static inline vlc_rational_t
+drmu_ufrac_vlc_to_rational(const drmu_ufrac_t x)
+{
+ return (vlc_rational_t) {.num = x.num, .den = x.den};
+}
+
+drmu_fb_t * drmu_fb_vlc_new_pic_attach(drmu_env_t * const du, picture_t * const pic);
+plane_t drmu_fb_vlc_plane(drmu_fb_t * const dfb, const unsigned int plane_n);
+
+#if HAS_ZC_CMA
+drmu_fb_t * drmu_fb_vlc_new_pic_cma_attach(drmu_env_t * const du, picture_t * const pic);
+#endif
+
+// Copy properties like colour_space, hdr_metadata into the fb
+void drmu_fb_vlc_pic_set_metadata(drmu_fb_t * const dfb, const picture_t * const pic);
+
+// Logging function callback for VLC
+enum drmu_log_level_e;
+void drmu_log_vlc_cb(void * v, enum drmu_log_level_e level_drmu, const char * fmt, va_list vl);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+
diff --git a/modules/video_output/drmu/drmu_vlc_fmts.c b/modules/video_output/drmu/drmu_vlc_fmts.c
new file mode 100644
index 0000000000..335a78e5ac
--- /dev/null
+++ b/modules/video_output/drmu/drmu_vlc_fmts.c
@@ -0,0 +1,229 @@
+#include "drmu_vlc_fmts.h"
+
+#include <libdrm/drm_fourcc.h>
+
+#ifndef DRM_FORMAT_P030
+#define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0')
+#endif
+
+// N.B. DRM seems to order its format descriptor names the opposite way round to VLC
+// DRM is hi->lo within a little-endian word, VLC is byte order
+
+#if HAS_ZC_CMA
+uint32_t
+drmu_format_vlc_to_drm_cma(const vlc_fourcc_t chroma_in)
+{
+ switch (chroma_in) {
+ case VLC_CODEC_MMAL_ZC_I420:
+ return DRM_FORMAT_YUV420;
+ case VLC_CODEC_MMAL_ZC_SAND8:
+ return DRM_FORMAT_NV12;
+ case VLC_CODEC_MMAL_ZC_SAND30:
+ return DRM_FORMAT_P030;
+ case VLC_CODEC_MMAL_ZC_RGB32:
+ return DRM_FORMAT_RGBX8888;
+ }
+ return 0;
+}
+#endif
+
+#if HAS_DRMPRIME
+uint32_t
+drmu_format_vlc_to_drm_prime(const vlc_fourcc_t chroma_in, uint64_t * const pmod)
+{
+ uint32_t fmt = 0;
+ uint64_t mod = DRM_FORMAT_MOD_LINEAR;
+
+ switch (chroma_in) {
+ case VLC_CODEC_DRM_PRIME_I420:
+ fmt = DRM_FORMAT_YUV420;
+ break;
+ case VLC_CODEC_DRM_PRIME_NV12:
+ fmt = DRM_FORMAT_NV12;
+ break;
+ case VLC_CODEC_DRM_PRIME_SAND8:
+ fmt = DRM_FORMAT_NV12;
+ mod = DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(0);
+ break;
+ case VLC_CODEC_DRM_PRIME_SAND30:
+ fmt = DRM_FORMAT_P030;
+ mod = DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(0);
+ break;
+ }
+ if (pmod)
+ *pmod = !fmt ? DRM_FORMAT_MOD_INVALID : mod;
+ return fmt;
+}
+#endif
+
+// Convert chroma to drm - can't cope with RGB32 or RGB16 as they require
+// more info
+uint32_t
+drmu_format_vlc_chroma_to_drm(const vlc_fourcc_t chroma)
+{
+ switch (chroma) {
+ case VLC_CODEC_RGBA:
+ return DRM_FORMAT_ABGR8888;
+ case VLC_CODEC_BGRA:
+ return DRM_FORMAT_ARGB8888;
+ case VLC_CODEC_ARGB:
+ return DRM_FORMAT_BGRA8888;
+ // VLC_CODEC_ABGR does not exist in VLC
+ case VLC_CODEC_VUYA:
+ return DRM_FORMAT_AYUV;
+ // AYUV appears to be the only DRM YUVA-like format
+ case VLC_CODEC_VYUY:
+ return DRM_FORMAT_YUYV;
+ case VLC_CODEC_UYVY:
+ return DRM_FORMAT_YVYU;
+ case VLC_CODEC_YUYV:
+ return DRM_FORMAT_VYUY;
+ case VLC_CODEC_YVYU:
+ return DRM_FORMAT_UYVY;
+ case VLC_CODEC_NV12:
+ return DRM_FORMAT_NV12;
+ case VLC_CODEC_NV21:
+ return DRM_FORMAT_NV21;
+ case VLC_CODEC_NV16:
+ return DRM_FORMAT_NV16;
+ case VLC_CODEC_NV61:
+ return DRM_FORMAT_NV61;
+ case VLC_CODEC_NV24:
+ return DRM_FORMAT_NV24;
+ case VLC_CODEC_NV42:
+ return DRM_FORMAT_NV42;
+ case VLC_CODEC_P010:
+ return DRM_FORMAT_P010;
+ case VLC_CODEC_J420:
+ case VLC_CODEC_I420:
+ return DRM_FORMAT_YUV420;
+ case VLC_CODEC_YV12:
+ return DRM_FORMAT_YVU420;
+ case VLC_CODEC_J422:
+ case VLC_CODEC_I422:
+ return DRM_FORMAT_YUV422;
+ case VLC_CODEC_J444:
+ case VLC_CODEC_I444:
+ return DRM_FORMAT_YUV444;
+ default:
+ break;
+ }
+
+#if HAS_ZC_CMA
+ return drmu_format_vlc_to_drm_cma(chroma);
+#else
+ return 0;
+#endif
+}
+
+uint32_t
+drmu_format_vlc_to_drm(const video_frame_format_t * const vf_vlc)
+{
+ switch (vf_vlc->i_chroma) {
+ case VLC_CODEC_RGB32:
+ {
+ // VLC RGB32 aka RV32 means we have to look at the mask values
+ const uint32_t r = vf_vlc->i_rmask;
+ const uint32_t g = vf_vlc->i_gmask;
+ const uint32_t b = vf_vlc->i_bmask;
+ if (r == 0xff0000 && g == 0xff00 && b == 0xff)
+ return DRM_FORMAT_XRGB8888;
+ if (r == 0xff && g == 0xff00 && b == 0xff0000)
+ return DRM_FORMAT_XBGR8888;
+ if (r == 0xff000000 && g == 0xff0000 && b == 0xff00)
+ return DRM_FORMAT_RGBX8888;
+ if (r == 0xff00 && g == 0xff0000 && b == 0xff000000)
+ return DRM_FORMAT_BGRX8888;
+ break;
+ }
+ case VLC_CODEC_RGB24:
+ {
+ // VLC RGB24 aka RV24 means we have to look at the mask values
+ const uint32_t r = vf_vlc->i_rmask;
+ const uint32_t g = vf_vlc->i_gmask;
+ const uint32_t b = vf_vlc->i_bmask;
+ if (r == 0xff0000 && g == 0xff00 && b == 0xff)
+ return DRM_FORMAT_RGB888;
+ if (r == 0xff && g == 0xff00 && b == 0xff0000)
+ return DRM_FORMAT_BGR888;
+ break;
+ }
+ case VLC_CODEC_RGB16:
+ {
+ // VLC RGB16 aka RV16 means we have to look at the mask values
+ const uint32_t r = vf_vlc->i_rmask;
+ const uint32_t g = vf_vlc->i_gmask;
+ const uint32_t b = vf_vlc->i_bmask;
+ if (r == 0xf800 && g == 0x7e0 && b == 0x1f)
+ return DRM_FORMAT_RGB565;
+ if (r == 0x1f && g == 0x7e0 && b == 0xf800)
+ return DRM_FORMAT_BGR565;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return drmu_format_vlc_chroma_to_drm(vf_vlc->i_chroma);
+}
+
+vlc_fourcc_t
+drmu_format_vlc_to_vlc(const uint32_t vf_drm)
+{
+ switch (vf_drm) {
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_RGBX8888:
+ case DRM_FORMAT_BGRX8888:
+ return VLC_CODEC_RGB32;
+ case DRM_FORMAT_RGB888:
+ case DRM_FORMAT_BGR888:
+ return VLC_CODEC_RGB24;
+ case DRM_FORMAT_BGR565:
+ case DRM_FORMAT_RGB565:
+ return VLC_CODEC_RGB16;
+ case DRM_FORMAT_ABGR8888:
+ return VLC_CODEC_RGBA;
+ case DRM_FORMAT_ARGB8888:
+ return VLC_CODEC_BGRA;
+ case DRM_FORMAT_BGRA8888:
+ return VLC_CODEC_ARGB;
+ // VLC_CODEC_ABGR does not exist in VLC
+ case DRM_FORMAT_AYUV:
+ return VLC_CODEC_VUYA;
+ case DRM_FORMAT_YUYV:
+ return VLC_CODEC_VYUY;
+ case DRM_FORMAT_YVYU:
+ return VLC_CODEC_UYVY;
+ case DRM_FORMAT_VYUY:
+ return VLC_CODEC_YUYV;
+ case DRM_FORMAT_UYVY:
+ return VLC_CODEC_YVYU;
+ case DRM_FORMAT_NV12:
+ return VLC_CODEC_NV12;
+ case DRM_FORMAT_NV21:
+ return VLC_CODEC_NV21;
+ case DRM_FORMAT_NV16:
+ return VLC_CODEC_NV16;
+ case DRM_FORMAT_NV61:
+ return VLC_CODEC_NV61;
+ case DRM_FORMAT_NV24:
+ return VLC_CODEC_NV24;
+ case DRM_FORMAT_NV42:
+ return VLC_CODEC_NV42;
+ case DRM_FORMAT_P010:
+ return VLC_CODEC_P010;
+ case DRM_FORMAT_YUV420:
+ return VLC_CODEC_I420;
+ case DRM_FORMAT_YVU420:
+ return VLC_CODEC_YV12;
+ case DRM_FORMAT_YUV422:
+ return VLC_CODEC_I422;
+ case DRM_FORMAT_YUV444:
+ return VLC_CODEC_I444;
+ default:
+ break;
+ }
+ return 0;
+}
+
diff --git a/modules/video_output/drmu/drmu_vlc_fmts.h b/modules/video_output/drmu/drmu_vlc_fmts.h
new file mode 100644
index 0000000000..ebdb0c8499
--- /dev/null
+++ b/modules/video_output/drmu/drmu_vlc_fmts.h
@@ -0,0 +1,38 @@
+#ifndef _DRMU_DRMU_VLC_FMTS_H
+#define _DRMU_DRMU_VLC_FMTS_H
+
+#include "config.h"
+
+#ifndef HAS_ZC_CMA
+#define HAS_ZC_CMA 0
+#endif
+#define HAS_DRMPRIME 1
+
+#include <stdint.h>
+
+#include <vlc_common.h>
+#include <vlc_picture.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Convert chroma to drm - can't cope with RGB32 or RGB16 as they require
+// more info. returns 0 if unknown.
+uint32_t drmu_format_vlc_chroma_to_drm(const vlc_fourcc_t chroma);
+// Convert format to drm fourcc - does cope with RGB32 & RGB16
+uint32_t drmu_format_vlc_to_drm(const video_frame_format_t * const vf_vlc);
+vlc_fourcc_t drmu_format_vlc_to_vlc(const uint32_t vf_drm);
+
+#if HAS_DRMPRIME
+// pmod may be NULL
+uint32_t drmu_format_vlc_to_drm_prime(const vlc_fourcc_t chroma_in, uint64_t * const pmod);
+#endif
+#if HAS_ZC_CMA
+uint32_t drmu_format_vlc_to_drm_cma(const vlc_fourcc_t chroma_in);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/modules/video_output/drmu/drmu_xlease.c b/modules/video_output/drmu/drmu_xlease.c
new file mode 100644
index 0000000000..9bf2d7b25b
--- /dev/null
+++ b/modules/video_output/drmu/drmu_xlease.c
@@ -0,0 +1,143 @@
+#include "drmu.h"
+#include "drmu_log.h"
+
+#include <xcb/xcb.h>
+#include <xcb/randr.h>
+
+static int
+get_lease_fd(const drmu_log_env_t * const log)
+{
+ xcb_generic_error_t *xerr;
+
+ int screen = 0;
+ xcb_connection_t * const connection = xcb_connect(NULL, &screen);
+ if (!connection) {
+ drmu_warn_log(log, "Connection to X server failed");
+ return -1;
+ }
+
+ {
+ xcb_randr_query_version_cookie_t rqv_c = xcb_randr_query_version(connection,
+ XCB_RANDR_MAJOR_VERSION,
+ XCB_RANDR_MINOR_VERSION);
+ xcb_randr_query_version_reply_t *rqv_r = xcb_randr_query_version_reply(connection, rqv_c, NULL);
+
+ if (!rqv_r) {
+ drmu_warn_log(log, "Failed to get XCB RandR version");
+ return -1;
+ }
+
+ uint32_t major = rqv_r->major_version;
+ uint32_t minor = rqv_r->minor_version;
+ free(rqv_r);
+
+ if (minor < 6) {
+ drmu_warn_log(log, "XCB RandR version %d.%d too low for lease support", major, minor);
+ return -1;
+ }
+ }
+
+ xcb_window_t root;
+
+ {
+ xcb_screen_iterator_t s_i = xcb_setup_roots_iterator(xcb_get_setup(connection));
+ int i;
+
+ for (i = 0; i != screen && s_i.rem != 0; ++i) {
+ xcb_screen_next(&s_i);
+ }
+
+ if (s_i.rem == 0) {
+ drmu_err_log(log, "Failed to get root for screen %d", screen);
+ return -1;
+ }
+
+ drmu_debug_log(log, "index %d screen %d rem %d", s_i.index, screen, s_i.rem);
+ root = s_i.data->root;
+ }
+
+ xcb_randr_output_t output = 0;
+ xcb_randr_crtc_t crtc = 0;
+
+ /* Find a connected in-use output */
+ {
+ xcb_randr_get_screen_resources_cookie_t gsr_c = xcb_randr_get_screen_resources(connection, root);
+
+ xcb_randr_get_screen_resources_reply_t *gsr_r = xcb_randr_get_screen_resources_reply(connection, gsr_c, NULL);
+ int o;
+
+ if (!gsr_r) {
+ drmu_err_log(log, "get_screen_resources failed");
+ return -1;
+ }
+
+ xcb_randr_output_t * const ro = xcb_randr_get_screen_resources_outputs(gsr_r);
+
+ for (o = 0; output == 0 && o < gsr_r->num_outputs; o++) {
+ xcb_randr_get_output_info_cookie_t goi_c = xcb_randr_get_output_info(connection, ro[o], gsr_r->config_timestamp);
+
+ xcb_randr_get_output_info_reply_t *goi_r = xcb_randr_get_output_info_reply(connection, goi_c, NULL);
+
+ drmu_debug_log(log, "output[%d/%d] %d: conn %d/%d crtc %d", o, gsr_r->num_outputs, ro[o], goi_r->connection, XCB_RANDR_CONNECTION_CONNECTED, goi_r->crtc);
+
+ /* Find the first connected and used output */
+ if (goi_r->connection == XCB_RANDR_CONNECTION_CONNECTED &&
+ goi_r->crtc != 0) {
+ output = ro[o];
+ crtc = goi_r->crtc;
+ }
+
+ free(goi_r);
+ }
+
+ free(gsr_r);
+
+ if (output == 0) {
+ drmu_warn_log(log, "Failed to find active output (outputs=%d)", o);
+ return -1;
+ }
+ }
+
+ int fd = -1;
+
+ {
+ xcb_randr_lease_t lease = xcb_generate_id(connection);
+
+ xcb_randr_create_lease_cookie_t rcl_c = xcb_randr_create_lease(connection,
+ root,
+ lease,
+ 1,
+ 1,
+ &crtc,
+ &output);
+ xcb_randr_create_lease_reply_t *rcl_r = xcb_randr_create_lease_reply(connection, rcl_c, &xerr);
+
+ if (!rcl_r) {
+ drmu_err_log(log, "create_lease failed: Xerror %d", xerr->error_code);
+ return -1;
+ }
+
+ int *rcl_f = xcb_randr_create_lease_reply_fds(connection, rcl_r);
+
+ fd = rcl_f[0];
+
+ free(rcl_r);
+ }
+
+ drmu_debug_log(log, "%s OK: fd=%d", __func__, fd);
+ return fd;
+}
+
+drmu_env_t *
+drmu_env_new_xlease(const drmu_log_env_t * const log2)
+{
+ const struct drmu_log_env_s * const log = (log2 == NULL) ? &drmu_log_env_none : log2;
+ const int fd = get_lease_fd(log);
+
+ if (fd == -1) {
+ drmu_err_log(log, "Failed to get xlease");
+ return NULL;
+ }
+ return drmu_env_new_fd(fd, log);
+}
+
diff --git a/modules/video_output/drmu/pollqueue.c b/modules/video_output/drmu/pollqueue.c
new file mode 100644
index 0000000000..912afec9a7
--- /dev/null
+++ b/modules/video_output/drmu/pollqueue.c
@@ -0,0 +1,417 @@
+#include "pollqueue.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <poll.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/eventfd.h>
+
+#define request_log(...) fprintf(stderr, __VA_ARGS__)
+
+struct pollqueue;
+
+enum polltask_state {
+ POLLTASK_UNQUEUED = 0,
+ POLLTASK_QUEUED,
+ POLLTASK_RUNNING,
+ POLLTASK_Q_KILL,
+ POLLTASK_Q_DEAD,
+ POLLTASK_RUN_KILL,
+};
+
+struct polltask {
+ struct polltask *next;
+ struct polltask *prev;
+ struct pollqueue *q;
+ enum polltask_state state;
+
+ int fd;
+ short events;
+
+ void (*fn)(void *v, short revents);
+ void * v;
+
+ uint64_t timeout; /* CLOCK_MONOTONIC time, 0 => never */
+};
+
+struct pollqueue {
+ atomic_int ref_count;
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+
+ struct polltask *head;
+ struct polltask *tail;
+
+ bool kill;
+ bool no_prod;
+ int prod_fd;
+ struct polltask *prod_pt;
+ pthread_t worker;
+};
+
+struct polltask *polltask_new(struct pollqueue *const pq,
+ const int fd, const short events,
+ void (*const fn)(void *v, short revents),
+ void *const v)
+{
+ struct polltask *pt;
+
+ if (!events && fd != -1)
+ return NULL;
+
+ pt = malloc(sizeof(*pt));
+ if (!pt)
+ return NULL;
+
+ *pt = (struct polltask){
+ .next = NULL,
+ .prev = NULL,
+ .q = pollqueue_ref(pq),
+ .fd = fd,
+ .events = events,
+ .fn = fn,
+ .v = v
+ };
+
+ return pt;
+}
+
+struct polltask *polltask_new_timer(struct pollqueue *const pq,
+ void (*const fn)(void *v, short revents),
+ void *const v)
+{
+ return polltask_new(pq, -1, 0, fn, v);
+}
+
+static void pollqueue_rem_task(struct pollqueue *const pq, struct polltask *const pt)
+{
+ if (pt->prev)
+ pt->prev->next = pt->next;
+ else
+ pq->head = pt->next;
+ if (pt->next)
+ pt->next->prev = pt->prev;
+ else
+ pq->tail = pt->prev;
+ pt->next = NULL;
+ pt->prev = NULL;
+}
+
+static void polltask_free(struct polltask * const pt)
+{
+ free(pt);
+}
+
+static void polltask_kill(struct polltask * const pt)
+{
+ struct pollqueue * pq = pt->q;
+ polltask_free(pt);
+ pollqueue_unref(&pq);
+}
+
+static void polltask_dead(struct polltask * const pt)
+{
+ pt->state = POLLTASK_Q_DEAD;
+ pthread_cond_broadcast(&pt->q->cond);
+}
+
+static int pollqueue_prod(const struct pollqueue *const pq)
+{
+ static const uint64_t one = 1;
+ return write(pq->prod_fd, &one, sizeof(one));
+}
+
+void polltask_delete(struct polltask **const ppt)
+{
+ struct polltask *const pt = *ppt;
+ struct pollqueue * pq;
+ enum polltask_state state;
+ bool prodme;
+ bool inthread;
+
+ if (!pt)
+ return;
+
+ pq = pt->q;
+ inthread = pthread_equal(pthread_self(), pq->worker);
+
+ pthread_mutex_lock(&pq->lock);
+ state = pt->state;
+ pt->state = inthread ? POLLTASK_RUN_KILL : POLLTASK_Q_KILL;
+ prodme = !pq->no_prod;
+ pthread_mutex_unlock(&pq->lock);
+
+ switch (state) {
+ case POLLTASK_UNQUEUED:
+ *ppt = NULL;
+ polltask_kill(pt);
+ break;
+
+ case POLLTASK_QUEUED:
+ case POLLTASK_RUNNING:
+ {
+ int rv = 0;
+
+ if (inthread) {
+ // We are in worker thread - kill in main loop to avoid confusion or deadlock
+ *ppt = NULL;
+ break;
+ }
+
+ if (prodme)
+ pollqueue_prod(pq);
+
+ pthread_mutex_lock(&pq->lock);
+ while (rv == 0 && pt->state != POLLTASK_Q_DEAD)
+ rv = pthread_cond_wait(&pq->cond, &pq->lock);
+ pthread_mutex_unlock(&pq->lock);
+
+ // Leave zapping the ref until we have DQed the PT as might well be
+ // legitimately used in it
+ *ppt = NULL;
+ polltask_kill(pt);
+ break;
+ }
+ default:
+ request_log("%s: Unexpected task state: %d\n", __func__, state);
+ *ppt = NULL;
+ break;
+ }
+}
+
+static uint64_t pollqueue_now(int timeout)
+{
+ struct timespec now;
+ uint64_t now_ms;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &now))
+ return 0;
+ now_ms = (now.tv_nsec / 1000000) + (uint64_t)now.tv_sec * 1000 + timeout;
+ return now_ms ? now_ms : (uint64_t)1;
+}
+
+void pollqueue_add_task(struct polltask *const pt, const int timeout)
+{
+ bool prodme = false;
+ struct pollqueue * const pq = pt->q;
+ const uint64_t timeout_time = timeout < 0 ? 0 : pollqueue_now(timeout);
+
+ pthread_mutex_lock(&pq->lock);
+ if (pt->state == POLLTASK_UNQUEUED || pt->state == POLLTASK_RUNNING) {
+ if (pq->tail)
+ pq->tail->next = pt;
+ else
+ pq->head = pt;
+ pt->prev = pq->tail;
+ pt->next = NULL;
+ pt->state = POLLTASK_QUEUED;
+ pt->timeout = timeout_time;
+ pq->tail = pt;
+ prodme = !pq->no_prod;
+ }
+ pthread_mutex_unlock(&pq->lock);
+ if (prodme)
+ pollqueue_prod(pq);
+}
+
+static void *poll_thread(void *v)
+{
+ struct pollqueue *const pq = v;
+
+ pthread_mutex_lock(&pq->lock);
+ do {
+ struct pollfd a[POLLQUEUE_MAX_QUEUE];
+ unsigned int i, j;
+ unsigned int nall = 0;
+ unsigned int npoll = 0;
+ struct polltask *pt;
+ struct polltask *pt_next;
+ uint64_t now = pollqueue_now(0);
+ int timeout = -1;
+ int rv;
+
+ for (pt = pq->head; pt; pt = pt_next) {
+ int64_t t;
+
+ pt_next = pt->next;
+
+ if (pt->state == POLLTASK_Q_KILL) {
+ pollqueue_rem_task(pq, pt);
+ polltask_dead(pt);
+ continue;
+ }
+ if (pt->state == POLLTASK_RUN_KILL) {
+ pollqueue_rem_task(pq, pt);
+ polltask_kill(pt);
+ continue;
+ }
+
+ if (pt->fd != -1) {
+ assert(npoll < POLLQUEUE_MAX_QUEUE);
+ a[npoll++] = (struct pollfd){
+ .fd = pt->fd,
+ .events = pt->events
+ };
+ }
+
+ t = (int64_t)(pt->timeout - now);
+ if (pt->timeout && t < INT_MAX &&
+ (timeout < 0 || (int)t < timeout))
+ timeout = (t < 0) ? 0 : (int)t;
+ ++nall;
+ }
+ pthread_mutex_unlock(&pq->lock);
+
+ if ((rv = poll(a, npoll, timeout)) == -1) {
+ if (errno != EINTR) {
+ request_log("Poll error: %s\n", strerror(errno));
+ goto fail_unlocked;
+ }
+ }
+
+ now = pollqueue_now(0);
+
+ pthread_mutex_lock(&pq->lock);
+ /* Prodding in this loop is pointless and might lead to
+ * infinite looping
+ */
+ pq->no_prod = true;
+ for (i = 0, j = 0, pt = pq->head; i < nall; ++i, pt = pt_next) {
+ const short r = pt->fd == -1 ? 0 : a[j++].revents;
+ pt_next = pt->next;
+
+ if (pt->state != POLLTASK_QUEUED)
+ continue;
+
+ /* Pending? */
+ if (r || (pt->timeout && (int64_t)(now - pt->timeout) >= 0)) {
+ pollqueue_rem_task(pq, pt);
+ pt->state = POLLTASK_RUNNING;
+ pthread_mutex_unlock(&pq->lock);
+
+ /* This can add new entries to the Q but as
+ * those are added to the tail our existing
+ * chain remains intact
+ */
+ pt->fn(pt->v, r);
+
+ pthread_mutex_lock(&pq->lock);
+ if (pt->state == POLLTASK_RUNNING)
+ pt->state = POLLTASK_UNQUEUED;
+ if (pt->state == POLLTASK_RUN_KILL)
+ polltask_kill(pt);
+ }
+ }
+ pq->no_prod = false;
+
+ } while (!pq->kill);
+
+ pthread_mutex_unlock(&pq->lock);
+fail_unlocked:
+
+ polltask_free(pq->prod_pt);
+ pthread_cond_destroy(&pq->cond);
+ pthread_mutex_destroy(&pq->lock);
+ close(pq->prod_fd);
+ free(pq);
+
+ return NULL;
+}
+
+static void prod_fn(void *v, short revents)
+{
+ struct pollqueue *const pq = v;
+ char buf[8];
+ if (revents)
+ read(pq->prod_fd, buf, 8);
+ if (!pq->kill)
+ pollqueue_add_task(pq->prod_pt, -1);
+}
+
+struct pollqueue * pollqueue_new(void)
+{
+ struct pollqueue *pq = malloc(sizeof(*pq));
+ if (!pq)
+ return NULL;
+ *pq = (struct pollqueue){
+ .ref_count = ATOMIC_VAR_INIT(0),
+ .lock = PTHREAD_MUTEX_INITIALIZER,
+ .cond = PTHREAD_COND_INITIALIZER,
+ .head = NULL,
+ .tail = NULL,
+ .kill = false,
+ .prod_fd = -1
+ };
+
+ pq->prod_fd = eventfd(0, EFD_NONBLOCK);
+ if (pq->prod_fd == 1)
+ goto fail1;
+ pq->prod_pt = polltask_new(pq, pq->prod_fd, POLLIN, prod_fn, pq);
+ if (!pq->prod_pt)
+ goto fail2;
+ pollqueue_add_task(pq->prod_pt, -1);
+ if (pthread_create(&pq->worker, NULL, poll_thread, pq))
+ goto fail3;
+ // Reset ref count which will have been inced by the add_task
+ atomic_store(&pq->ref_count, 0);
+ return pq;
+
+fail3:
+ polltask_free(pq->prod_pt);
+fail2:
+ close(pq->prod_fd);
+fail1:
+ free(pq);
+ return NULL;
+}
+
+static void pollqueue_free(struct pollqueue *const pq)
+{
+ const pthread_t worker = pq->worker;
+
+ if (pthread_equal(worker, pthread_self())) {
+ pq->kill = true;
+ pollqueue_prod(pq);
+ pthread_detach(worker);
+ }
+ else
+ {
+ pthread_mutex_lock(&pq->lock);
+ pq->kill = true;
+ pollqueue_prod(pq);
+ pthread_mutex_unlock(&pq->lock);
+ pthread_join(worker, NULL);
+ }
+}
+
+struct pollqueue * pollqueue_ref(struct pollqueue *const pq)
+{
+ atomic_fetch_add(&pq->ref_count, 1);
+ return pq;
+}
+
+void pollqueue_unref(struct pollqueue **const ppq)
+{
+ struct pollqueue * const pq = *ppq;
+
+ if (!pq)
+ return;
+ *ppq = NULL;
+
+ if (atomic_fetch_sub(&pq->ref_count, 1) != 0)
+ return;
+
+ pollqueue_free(pq);
+}
+
+
+
diff --git a/modules/video_output/drmu/pollqueue.h b/modules/video_output/drmu/pollqueue.h
new file mode 100644
index 0000000000..922b6e3d0b
--- /dev/null
+++ b/modules/video_output/drmu/pollqueue.h
@@ -0,0 +1,61 @@
+#ifndef POLLQUEUE_H_
+#define POLLQUEUE_H_
+
+#include <poll.h>
+
+struct polltask;
+struct pollqueue;
+
+// Max number of tasks that can be Qed
+#define POLLQUEUE_MAX_QUEUE 128
+
+// Create a new polltask
+// Holds a reference on the pollqueue until the polltask is deleted
+//
+// pq pollqueue this task belongs to
+// fd fd to poll
+// events Events to wait for (POLLxxx)
+// revents Event that triggered the callback
+// 0 => timeout
+// v User pointer to callback
+struct polltask *polltask_new(struct pollqueue *const pq,
+ const int fd, const short events,
+ void (*const fn)(void *v, short revents),
+ void *const v);
+// polltask suitable for timing (i.e. has no trigger event)
+struct polltask *polltask_new_timer(struct pollqueue *const pq,
+ void (*const fn)(void *v, short revents),
+ void *const v);
+
+// deletes the task
+// Safe to call if *ppt == NULL
+// It is safe to call whilst a polltask is queued (and may be triggered)
+// Callback may occur whilst this is in progress but will not occur
+// once it is done. (*ppt is nulled only once the callback can not occur)
+// May be called in a polltask callback
+// If called from outside the polltask thread and this causes the pollqueue
+// to be deleted then it will wait for the polltask thread to terminate
+// before returning.
+void polltask_delete(struct polltask **const ppt);
+
+// Queue a polltask
+// timeout_ms == -1 => never
+// May be called from the polltask callback
+// May only be added once (currently)
+void pollqueue_add_task(struct polltask *const pt, const int timeout);
+
+// Create a pollqueue
+// Generates a new thread to do the polltask callbacks
+struct pollqueue * pollqueue_new(void);
+
+// Unref a pollqueue
+// Will be deleted once all polltasks (Qed or otherwise) are deleted too
+// If called from outside the polltask thread and this causes the pollqueue
+// to be deleted then it will wait for the polltask thread to terminate
+// before returning.
+void pollqueue_unref(struct pollqueue **const ppq);
+
+// Add a reference to a pollqueue
+struct pollqueue * pollqueue_ref(struct pollqueue *const pq);
+
+#endif /* POLLQUEUE_H_ */
diff --git a/modules/video_output/opengl/display.c b/modules/video_output/opengl/display.c
index ec671109bc..46f693a13a 100644
--- a/modules/video_output/opengl/display.c
+++ b/modules/video_output/opengl/display.c
@@ -48,7 +48,7 @@ vlc_module_begin ()
# define MODULE_VARNAME "gles2"
set_shortname (N_("OpenGL ES2"))
set_description (N_("OpenGL for Embedded Systems 2 video output"))
- set_capability ("vout display", 265)
+ set_capability ("vout display", 271)
set_callbacks (Open, Close)
add_shortcut ("opengles2", "gles2")
add_module ("gles2", "opengl es2", NULL,
diff --git a/modules/video_output/opengl/egl.c b/modules/video_output/opengl/egl.c
index ab20fb4c9c..dbff2e5efe 100644
--- a/modules/video_output/opengl/egl.c
+++ b/modules/video_output/opengl/egl.c
@@ -43,6 +43,8 @@
# include "../android/utils.h"
#endif
+#define REQUIRE_DMA_BUF_IMPORT 1
+
typedef struct vlc_gl_sys_t
{
EGLDisplay display;
@@ -58,6 +60,7 @@ typedef struct vlc_gl_sys_t
#endif
PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;
PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;
+ PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT;
} vlc_gl_sys_t;
static int MakeCurrent (vlc_gl_t *gl)
@@ -129,6 +132,15 @@ static bool DestroyImageKHR(vlc_gl_t *gl, void *image)
return sys->eglDestroyImageKHR(sys->display, image);
}
+static bool QueryDmaBufModifiersEXT(vlc_gl_t *gl, uint32_t format,
+ unsigned int max_modifiers, uint64_t *modifiers,
+ unsigned int *external_only, int32_t *num_modifiers)
+{
+ vlc_gl_sys_t *sys = gl->sys;
+
+ return sys->eglQueryDmaBufModifiersEXT(sys->display, format, max_modifiers, modifiers, external_only, num_modifiers);
+}
+
static bool CheckToken(const char *haystack, const char *needle)
{
size_t len = strlen(needle);
@@ -371,6 +383,14 @@ static int Open (vlc_object_t *obj, const struct gl_api *api)
goto error;
}
+#if REQUIRE_DMA_BUF_IMPORT
+ if (!CheckToken(ext, "EGL_EXT_image_dma_buf_import"))
+ {
+ msg_Dbg(obj, "No dma_buf_import - fall back to X");
+ goto error;
+ }
+#endif
+
const EGLint conf_attr[] = {
EGL_RED_SIZE, 5,
EGL_GREEN_SIZE, 5,
@@ -427,6 +447,9 @@ static int Open (vlc_object_t *obj, const struct gl_api *api)
gl->egl.createImageKHR = CreateImageKHR;
gl->egl.destroyImageKHR = DestroyImageKHR;
}
+ sys->eglQueryDmaBufModifiersEXT = (void *)eglGetProcAddress("eglQueryDmaBufModifiersEXT");
+ if (sys->eglQueryDmaBufModifiersEXT)
+ gl->egl.queryDmaBufModifiersEXT = QueryDmaBufModifiersEXT;
return VLC_SUCCESS;
@@ -463,7 +486,7 @@ vlc_module_begin ()
add_shortcut ("egl")
add_submodule ()
- set_capability ("opengl es2", 50)
+ set_capability ("opengl es2", 51)
set_callbacks (OpenGLES2, Close)
add_shortcut ("egl")
diff --git a/modules/video_output/wayland/dmabuf.c b/modules/video_output/wayland/dmabuf.c
new file mode 100644
index 0000000000..03b71f7d7f
--- /dev/null
+++ b/modules/video_output/wayland/dmabuf.c
@@ -0,0 +1,1554 @@
+/**
+ * @file shm.c
+ * @brief Wayland shared memory video output module for VLC media player
+ */
+/*****************************************************************************
+ * Copyright © 2014, 2017 Rémi Denis-Courmont
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <wayland-client.h>
+#include "viewporter-client-protocol.h"
+#include "linux-dmabuf-unstable-v1-client-protocol.h"
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_vout_display.h>
+#include <vlc_picture_pool.h>
+#include <vlc_fs.h>
+
+// *** Avoid this include if possible
+#include <libdrm/drm_fourcc.h>
+
+#include "dmabuf_alloc.h"
+#include "picpool.h"
+#include "rgba_premul.h"
+#include "../drmu/drmu_vlc_fmts.h"
+#include "../drmu/pollqueue.h"
+#include "../../codec/avcodec/drm_pic.h"
+#include <libavutil/hwcontext_drm.h>
+
+#define TRACE_ALL 0
+
+#define MAX_PICTURES 4
+#define MAX_SUBPICS 6
+
+#define VIDEO_ON_SUBSURFACE 1
+
+typedef struct fmt_ent_s {
+ uint32_t fmt;
+ int32_t pri;
+ uint64_t mod;
+} fmt_ent_t;
+
+typedef struct fmt_list_s {
+ fmt_ent_t * fmts;
+ unsigned int size;
+ unsigned int len;
+} fmt_list_t;
+
+typedef struct subplane_s {
+ struct wl_surface * surface;
+ struct wl_subsurface * subsurface;
+ struct wp_viewport * viewport;
+} subplane_t;
+
+typedef struct subpic_ent_s {
+ struct wl_buffer * wb;
+ struct dmabuf_h * dh;
+ picture_t * pic;
+ int alpha;
+ vout_display_place_t dst_rect;
+ vout_display_place_t src_rect;
+ bool update;
+} subpic_ent_t;
+
+typedef struct video_dmabuf_release_env_ss
+{
+ struct picture_context_t * ctx;
+ unsigned int rel_count;
+ unsigned int pt_count;
+ struct polltask * pt[AV_DRM_MAX_PLANES];
+} video_dmabuf_release_env_t;
+
+struct vout_display_sys_t
+{
+ vout_window_t *embed; /* VLC window */
+ struct wp_viewporter *viewporter;
+ struct wp_viewport *viewport;
+ struct zwp_linux_dmabuf_v1 * linux_dmabuf_v1;
+ struct wl_compositor *compositor;
+ struct wl_subcompositor *subcompositor;
+
+ picture_pool_t *vlc_pic_pool; /* picture pool */
+
+ struct wl_surface * last_embed_surface;
+
+ int x;
+ int y;
+ bool video_attached;
+ bool viewport_set;
+
+ vout_display_place_t spu_rect; // Window that subpic coords orignate from
+ vout_display_place_t dst_rect; // Window in the display size that holds the video
+
+ video_format_t curr_aspect;
+
+#if VIDEO_ON_SUBSURFACE
+ struct wl_surface * video_surface;
+ struct wl_subsurface * video_subsurface;
+
+ struct wp_viewport * bkg_viewport;
+ unsigned int bkg_w;
+ unsigned int bkg_h;
+#endif
+
+ struct pollqueue * pollq;
+
+ picpool_ctl_t * subpic_pool;
+ subplane_t subplanes[MAX_SUBPICS];
+ subpic_ent_t subpics[MAX_SUBPICS];
+ subpic_ent_t piccpy;
+ video_dmabuf_release_env_t * pic_vdre;
+ vlc_fourcc_t * subpic_chromas;
+
+ fmt_list_t dmabuf_fmts;
+};
+
+static inline struct wl_display *
+video_display(const vout_display_sys_t * const sys)
+{
+ return sys->embed->display.wl;
+}
+
+static inline struct wl_surface *
+video_surface(const vout_display_sys_t * const sys)
+{
+#if VIDEO_ON_SUBSURFACE
+ return sys->video_surface;
+#else
+ return sys->embed->handle.wl;
+#endif
+}
+
+static inline struct wl_compositor *
+video_compositor(const vout_display_sys_t * const sys)
+{
+ return sys->compositor;
+}
+
+#if VIDEO_ON_SUBSURFACE
+static inline struct wl_surface *
+bkg_surface(const vout_display_sys_t * const sys)
+{
+ return sys->embed->handle.wl;
+}
+#endif
+
+static int
+check_embed(vout_display_t * const vd, vout_display_sys_t * const sys, const char * const func)
+{
+ if (!sys->embed) {
+ msg_Err(vd, "%s: Embed NULL", func);
+ return -1;
+ }
+ if (sys->embed->handle.wl != sys->last_embed_surface) {
+ msg_Warn(vd, "%s: Embed surface changed %p->%p", func, sys->last_embed_surface, sys->embed->handle.wl);
+ sys->last_embed_surface = sys->embed->handle.wl;
+ return 1;
+ }
+ return 0;
+}
+
+static inline void
+roundtrip(const vout_display_sys_t * const sys)
+{
+ wl_display_roundtrip(video_display(sys));
+}
+
+static void
+buffer_destroy(struct wl_buffer ** ppbuffer)
+{
+ struct wl_buffer * const buffer = *ppbuffer;
+ if (buffer == NULL)
+ return;
+ *ppbuffer = NULL;
+ wl_buffer_destroy(buffer);
+}
+
+static void
+subsurface_destroy(struct wl_subsurface ** const ppsubsurface)
+{
+ if (*ppsubsurface == NULL)
+ return;
+ wl_subsurface_destroy(*ppsubsurface);
+ *ppsubsurface = NULL;
+}
+
+static void
+surface_destroy(struct wl_surface ** const ppsurface)
+{
+ if (*ppsurface == NULL)
+ return;
+ wl_surface_destroy(*ppsurface);
+ *ppsurface = NULL;
+}
+
+static void
+viewport_destroy(struct wp_viewport ** const ppviewport)
+{
+ if (*ppviewport == NULL)
+ return;
+ wp_viewport_destroy(*ppviewport);
+ *ppviewport = NULL;
+}
+
+static inline int_fast32_t
+place_rescale_1s(int_fast32_t x, uint_fast32_t mul, uint_fast32_t div)
+{
+ const int_fast64_t m = x * (int_fast64_t)mul;
+ const uint_fast32_t d2 = div/2;
+ return div == 0 ? (int_fast32_t)m :
+ m >= 0 ? (int_fast32_t)(((uint_fast64_t)m + d2) / div) :
+ -(int_fast32_t)(((uint_fast64_t)(-m) + d2) / div);
+}
+
+static inline uint_fast32_t
+place_rescale_1u(uint_fast32_t x, uint_fast32_t mul, uint_fast32_t div)
+{
+ const uint_fast64_t m = x * (uint_fast64_t)mul;
+ return (uint_fast32_t)(div == 0 ? m : (m + div/2) / div);
+}
+
+static inline vout_display_place_t
+place_rescale(const vout_display_place_t s, const vout_display_place_t mul, const vout_display_place_t div)
+{
+ return (vout_display_place_t){
+ .x = place_rescale_1s(s.x - div.x, mul.width, div.width) + mul.x,
+ .y = place_rescale_1s(s.y - div.y, mul.height, div.height) + mul.y,
+ .width = place_rescale_1u(s.width, mul.width, div.width),
+ .height = place_rescale_1u(s.height, mul.height, div.height)
+ };
+}
+
+
+// MMAL headers comment these (getting 2 a bit wrong) but do not give
+// defines
+#define VXF_H_SHIFT 0 // Hflip
+#define VXF_V_SHIFT 1 // Vflip
+#define VXF_T_SHIFT 2 // Transpose
+#define VXF_H_BIT (1 << VXF_H_SHIFT)
+#define VXF_V_BIT (1 << VXF_V_SHIFT)
+#define VXF_T_BIT (1 << VXF_T_SHIFT)
+
+static inline bool
+is_vxf_transpose(const video_transform_t t)
+{
+ return ((unsigned int)t & VXF_T_BIT) != 0;
+}
+
+static inline bool
+is_vxf_hflip(const video_transform_t t)
+{
+ return ((unsigned int)t & VXF_H_BIT) != 0;
+}
+
+static inline bool
+is_vxf_vflip(const video_transform_t t)
+{
+ return ((unsigned int)t & VXF_V_BIT) != 0;
+}
+
+static inline video_transform_t
+swap_vxf_hv(const video_transform_t x)
+{
+ return (((x >> VXF_H_SHIFT) & 1) << VXF_V_SHIFT) |
+ (((x >> VXF_V_SHIFT) & 1) << VXF_H_SHIFT) |
+ (x & VXF_T_BIT);
+}
+
+static inline video_transform_t
+vxf_inverse(const video_transform_t x)
+{
+ return is_vxf_transpose(x) ? swap_vxf_hv(x) : x;
+}
+
+// Transform generated by A then B
+// All ops are self inverse so can simply be XORed on their own
+// H & V flips after a transpose need to be swapped
+static inline video_transform_t
+combine_vxf(const video_transform_t a, const video_transform_t b)
+{
+ return a ^ (is_vxf_transpose(a) ? swap_vxf_hv(b) : b);
+}
+
+static inline vout_display_place_t
+vplace_transpose(const vout_display_place_t s)
+{
+ return (vout_display_place_t){
+ .x = s.y,
+ .y = s.x,
+ .width = s.height,
+ .height = s.width
+ };
+}
+
+// hflip s in c
+static inline vout_display_place_t vplace_hflip(const vout_display_place_t s, const vout_display_place_t c)
+{
+ return (vout_display_place_t){
+ .x = c.x + (c.x + c.width) - (s.x + s.width),
+ .y = s.y,
+ .width = s.width,
+ .height = s.height
+ };
+}
+
+// vflip s in c
+static inline vout_display_place_t vplace_vflip(const vout_display_place_t s, const vout_display_place_t c)
+{
+ return (vout_display_place_t){
+ .x = s.x,
+ .y = (c.y + c.height) - (s.y - c.y) - s.height,
+ .width = s.width,
+ .height = s.height
+ };
+}
+
+static vout_display_place_t
+place_out(const vout_display_cfg_t * cfg,
+ const video_format_t * fmt,
+ const vout_display_place_t r)
+{
+ video_format_t tfmt;
+ vout_display_cfg_t tcfg;
+ vout_display_place_t place;
+
+ // Fix SAR if unknown
+ if (fmt->i_sar_den == 0 || fmt->i_sar_num == 0) {
+ tfmt = *fmt;
+ tfmt.i_sar_den = 1;
+ tfmt.i_sar_num = 1;
+ fmt = &tfmt;
+ }
+
+ // Override what VLC thinks might be going on with display size
+ // if we know better
+ if (r.width != 0 && r.height != 0)
+ {
+ tcfg = *cfg;
+ tcfg.display.width = r.width;
+ tcfg.display.height = r.height;
+ cfg = &tcfg;
+ }
+
+ vout_display_PlacePicture(&place, fmt, cfg, false);
+
+ place.x += r.x;
+ place.y += r.y;
+
+ return place;
+}
+
+#if 0
+static vout_display_place_t
+rect_transform(vout_display_place_t s, const vout_display_place_t c, const video_transform_t t)
+{
+ if (is_vxf_transpose(t))
+ s = vplace_transpose(s);
+ if (is_vxf_hflip(t))
+ s = vplace_hflip(s, c);
+ if (is_vxf_vflip(t) != 0)
+ s = vplace_vflip(s, c);
+ return s;
+}
+
+static void
+place_dest_rect(vout_display_t * const vd,
+ const vout_display_cfg_t * const cfg,
+ const video_format_t * fmt)
+{
+ vout_display_sys_t * const sys = vd->sys;
+ sys->dest_rect = rect_transform(place_out(cfg, fmt, sys->win_rect),
+ sys->display_rect, sys->dest_transform);
+}
+#endif
+
+static void
+place_spu_rect(vout_display_t * const vd,
+ const vout_display_cfg_t * const cfg,
+ const video_format_t * fmt)
+{
+ vout_display_sys_t * const sys = vd->sys;
+ static const vout_display_place_t r0 = {0};
+
+ sys->spu_rect = place_out(cfg, fmt, r0);
+ sys->spu_rect.x = 0;
+ sys->spu_rect.y = 0;
+
+ // Copy place override logic for spu pos from video_output.c
+ // This info doesn't appear to reside anywhere natively
+
+ if (fmt->i_width * fmt->i_height >= (unsigned int)(sys->spu_rect.width * sys->spu_rect.height)) {
+ sys->spu_rect.width = fmt->i_visible_width;
+ sys->spu_rect.height = fmt->i_visible_height;
+ }
+
+ if (ORIENT_IS_SWAP(fmt->orientation))
+ sys->spu_rect = vplace_transpose(sys->spu_rect);
+}
+
+
+static int
+fmt_list_add(fmt_list_t * const fl, uint32_t fmt, uint64_t mod, int32_t pri)
+{
+ if (fl->len >= fl->size)
+ {
+ unsigned int n = fl->len == 0 ? 64 : fl->len * 2;
+ fmt_ent_t * t = realloc(fl->fmts, n * sizeof(*t));
+ if (t == NULL)
+ return VLC_ENOMEM;
+ fl->fmts = t;
+ fl->size = n;
+ }
+ fl->fmts[fl->len++] = (fmt_ent_t){
+ .fmt = fmt,
+ .pri = pri,
+ .mod = mod
+ };
+ return 0;
+}
+
+static int
+fmt_sort_cb(const void * va, const void * vb)
+{
+ const fmt_ent_t * const a = va;
+ const fmt_ent_t * const b = vb;
+ return a->fmt < b->fmt ? -1 : a->fmt != b->fmt ? 1 :
+ a->mod < b->mod ? -1 : a->mod != b->mod ? 1 : 0;
+}
+
+static void
+fmt_list_sort(fmt_list_t * const fl)
+{
+ unsigned int n = 0;
+ if (fl->len <= 1)
+ return;
+ qsort(fl->fmts, fl->len, sizeof(*fl->fmts), fmt_sort_cb);
+ // Dedup - in case we have multiple working callbacks
+ for (unsigned int i = 1; i != fl->len; ++i)
+ {
+ if (fl->fmts[i].fmt != fl->fmts[n].fmt || fl->fmts[i].mod != fl->fmts[n].mod)
+ fl->fmts[n++] = fl->fmts[i];
+ }
+ fl->len = n + 1;
+}
+
+static int
+fmt_list_find(const fmt_list_t * const fl, uint32_t fmt, uint64_t mod)
+{
+ const fmt_ent_t x = {
+ .fmt = fmt,
+ .mod = mod
+ };
+ const fmt_ent_t * const fe = (fl->len == 0) ? NULL :
+ bsearch(&x, fl->fmts, fl->len, sizeof(x), fmt_sort_cb);
+ return fe == NULL ? -1 : fe->pri;
+}
+
+static void
+fmt_list_uninit(fmt_list_t * const fl)
+{
+ free(fl->fmts);
+ fl->fmts = NULL;
+ fl->size = 0;
+ fl->len = 0;
+}
+
+static int
+fmt_list_init(fmt_list_t * const fl, const size_t initial_size)
+{
+ fl->size = 0;
+ fl->len = 0;
+ if ((fl->fmts = malloc(initial_size * sizeof(*fl->fmts))) == NULL)
+ return VLC_ENOMEM;
+ fl->size = initial_size;
+ return VLC_SUCCESS;
+}
+
+static void
+chequerboard(uint32_t *const data, unsigned int stride, const unsigned int width, const unsigned int height)
+{
+ stride /= sizeof(uint32_t);
+
+ /* Draw checkerboxed background */
+ for (unsigned int y = 0; y < height; ++y) {
+ for (unsigned int x = 0; x < width; ++x) {
+ if ((x + y / 8 * 8) % 16 < 8)
+ data[y * stride + x] = 0xFF666666;
+ else
+ data[y * stride + x] = 0xFFEEEEEE;
+ }
+ }
+}
+
+/* Sent by the compositor when it's no longer using this buffer */
+static void
+subpic_buffer_release(void *data, struct wl_buffer *wl_buffer)
+{
+ struct dmabuf_h * dh = data;
+
+ buffer_destroy(&wl_buffer);
+ dmabuf_unref(&dh);
+}
+
+static const struct wl_buffer_listener subpic_buffer_listener = {
+ .release = subpic_buffer_release,
+};
+
+static inline size_t cpypic_plane_alloc_size(const plane_t * const p)
+{
+ return p->i_pitch * p->i_lines;
+}
+
+static int
+copy_subpic_to_w_buffer(vout_display_t *vd, vout_display_sys_t * const sys, picture_t * const src,
+ int alpha,
+ struct dmabuf_h ** pDmabuf_h, struct wl_buffer ** pW_buffer)
+{
+ unsigned int w = src->format.i_width;
+ unsigned int h = src->format.i_height;
+ struct zwp_linux_buffer_params_v1 *params = NULL;
+ const uint32_t drm_fmt = drmu_format_vlc_to_drm(&src->format);
+ size_t total_size = 0;
+ size_t offset = 0;
+ struct dmabuf_h * dh = NULL;
+ int i;
+
+ for (i = 0; i != src->i_planes; ++i)
+ total_size += cpypic_plane_alloc_size(src->p + i);
+
+ *pW_buffer = NULL;
+ *pDmabuf_h = NULL;
+
+ if ((dh = picpool_get(sys->subpic_pool, total_size)) == NULL)
+ {
+ msg_Warn(vd, "Failed to alloc dmabuf for subpic");
+ goto error;
+ }
+ *pDmabuf_h = dh;
+
+ if ((params = zwp_linux_dmabuf_v1_create_params(sys->linux_dmabuf_v1)) == NULL)
+ {
+ msg_Err(vd, "zwp_linux_dmabuf_v1_create_params FAILED");
+ goto error;
+ }
+
+ dmabuf_write_start(dh);
+ for (i = 0; i != src->i_planes; ++i)
+ {
+ const size_t stride = src->p[i].i_pitch;
+ const size_t size = cpypic_plane_alloc_size(src->p + i);
+
+ if (src->format.i_chroma == VLC_CODEC_RGBA ||
+ src->format.i_chroma == VLC_CODEC_BGRA)
+ copy_frame_xxxa_with_premul(dmabuf_map(dh), stride, src->p[i].p_pixels, src->p[i].i_pitch, w, h, alpha);
+ else
+ memcpy((char *)dmabuf_map(dh) + offset, src->p[i].p_pixels, size);
+
+ zwp_linux_buffer_params_v1_add(params, dmabuf_fd(dh), i, offset, stride, 0, 0);
+
+ offset += size;
+ }
+ dmabuf_write_end(dh);
+
+ if ((*pW_buffer = zwp_linux_buffer_params_v1_create_immed(params, w, h, drm_fmt, 0)) == NULL)
+ {
+ msg_Err(vd, "zwp_linux_buffer_params_v1_create_immed FAILED");
+ goto error;
+ }
+
+ zwp_linux_buffer_params_v1_destroy(params);
+ wl_buffer_add_listener(*pW_buffer, &subpic_buffer_listener, dh);
+
+ return VLC_SUCCESS;
+
+error:
+ if (params)
+ zwp_linux_buffer_params_v1_destroy(params);
+ dmabuf_unref(pDmabuf_h);
+ return VLC_EGENERIC;
+}
+
+
+static void kill_pool(vout_display_sys_t * const sys)
+{
+ if (sys->vlc_pic_pool != NULL)
+ {
+ picture_pool_Release(sys->vlc_pic_pool);
+ sys->vlc_pic_pool = NULL;
+ }
+}
+
+// Actual picture pool for dmabufs is just a set of trivial containers
+static picture_pool_t *vd_dmabuf_pool(vout_display_t * const vd, unsigned count)
+{
+ vout_display_sys_t * const sys = vd->sys;
+
+#if TRACE_ALL
+ msg_Dbg(vd, "%s: fmt:%dx%d,sar:%d/%d; source:%dx%d", __func__,
+ vd->fmt.i_width, vd->fmt.i_height, vd->fmt.i_sar_num, vd->fmt.i_sar_den, vd->source.i_width, vd->source.i_height);
+#endif
+
+ if (sys->vlc_pic_pool == NULL)
+ sys->vlc_pic_pool = picture_pool_NewFromFormat(&vd->fmt, count);
+ return sys->vlc_pic_pool;
+}
+
+static video_dmabuf_release_env_t *
+vdre_new(struct picture_context_t * ctx)
+{
+ video_dmabuf_release_env_t * const vdre = calloc(1, sizeof(*vdre));
+ if ((vdre->ctx = ctx->copy(ctx)) == NULL)
+ {
+ free(vdre);
+ return NULL;
+ }
+ return vdre;
+}
+
+static void
+vdre_free(video_dmabuf_release_env_t * const vdre)
+{
+ unsigned int i;
+ vdre->ctx->destroy(vdre->ctx);
+ for (i = 0; i != vdre->pt_count; ++i)
+ polltask_delete(vdre->pt + i);
+ free(vdre);
+}
+
+static void
+vdre_delete(video_dmabuf_release_env_t ** const ppvdre)
+{
+ video_dmabuf_release_env_t * const vdre = *ppvdre;
+ if (vdre == NULL)
+ return;
+ *ppvdre = NULL;
+ vdre_free(vdre);
+}
+
+static void
+w_ctx_release(void * v, short revents)
+{
+ video_dmabuf_release_env_t * const vdre = v;
+ VLC_UNUSED(revents);
+ // Wait for all callbacks to come back before releasing buffer
+ if (++vdre->rel_count >= vdre->pt_count)
+ vdre_free(vdre);
+}
+
+static void
+vdre_add_pt(video_dmabuf_release_env_t * const vdre, struct pollqueue * pq, int fd)
+{
+ assert(vdre->pt_count < AV_DRM_MAX_PLANES);
+ vdre->pt[vdre->pt_count++] = polltask_new(pq, fd, POLLOUT, w_ctx_release, vdre);
+}
+
+// Avoid use of vd here as there's a possibility this will be called after
+// it has gone
+static void
+w_buffer_release(void *data, struct wl_buffer *wl_buffer)
+{
+ video_dmabuf_release_env_t * const vdre = data;
+ unsigned int i = vdre->pt_count;
+
+ /* Sent by the compositor when it's no longer using this buffer */
+ buffer_destroy(&wl_buffer);
+
+ // Whilst we can happily destroy the buffer that doesn't mean we can reuse
+ // the dmabufs yet - we have to wait for them to be free of fences.
+ // We don't want to wait in this callback so do the waiting in pollqueue
+ while (i-- != 0)
+ pollqueue_add_task(vdre->pt[i], 1000);
+}
+
+static const struct wl_buffer_listener w_buffer_listener = {
+ .release = w_buffer_release,
+};
+
+static int
+do_display_dmabuf(vout_display_t * const vd, vout_display_sys_t * const sys, picture_t * const pic,
+ video_dmabuf_release_env_t ** const pVdre, struct wl_buffer ** const pWbuffer)
+{
+ struct zwp_linux_buffer_params_v1 *params;
+ const AVDRMFrameDescriptor * const desc = drm_prime_get_desc(pic);
+ const uint32_t format = desc->layers[0].format;
+ const unsigned int width = pic->format.i_visible_width;
+ const unsigned int height = pic->format.i_visible_height;
+ unsigned int n = 0;
+ unsigned int flags = 0;
+ int i;
+ struct wl_buffer * w_buffer;
+ video_dmabuf_release_env_t * const vdre = vdre_new(pic->context);
+
+ assert(*pWbuffer == NULL);
+ assert(*pVdre == NULL);
+
+ if (vdre == NULL) {
+ msg_Err(vd, "Failed to create vdre");
+ return VLC_ENOMEM;
+ }
+
+ for (i = 0; i != desc->nb_objects; ++i)
+ vdre_add_pt(vdre, sys->pollq, desc->objects[i].fd);
+
+ /* Creation and configuration of planes */
+ params = zwp_linux_dmabuf_v1_create_params(sys->linux_dmabuf_v1);
+ if (!params)
+ {
+ msg_Err(vd, "zwp_linux_dmabuf_v1_create_params FAILED");
+ goto error;
+ }
+
+ for (i = 0; i < desc->nb_layers; ++i)
+ {
+ int j;
+ for (j = 0; j < desc->layers[i].nb_planes; ++j)
+ {
+ const AVDRMPlaneDescriptor *const p = desc->layers[i].planes + j;
+ const AVDRMObjectDescriptor *const obj = desc->objects + p->object_index;
+
+ zwp_linux_buffer_params_v1_add(params, obj->fd, n++, p->offset, p->pitch,
+ (unsigned int)(obj->format_modifier >> 32),
+ (unsigned int)(obj->format_modifier & 0xFFFFFFFF));
+ }
+ }
+
+ if (!pic->b_progressive)
+ {
+ flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_INTERLACED;
+ if (!pic->b_top_field_first)
+ flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_BOTTOM_FIRST;
+ }
+
+ /* Request buffer creation */
+ if ((w_buffer = zwp_linux_buffer_params_v1_create_immed(params, width, height, format, flags)) == NULL)
+ {
+ msg_Err(vd, "zwp_linux_buffer_params_v1_create_immed FAILED");
+ goto error;
+ }
+
+ zwp_linux_buffer_params_v1_destroy(params);
+
+ wl_buffer_add_listener(w_buffer, &w_buffer_listener, vdre);
+
+ *pVdre = vdre;
+ *pWbuffer = w_buffer;
+ return VLC_SUCCESS;
+
+error:
+ vdre_free(vdre);
+ return VLC_EGENERIC;
+}
+
+static void
+subpic_ent_flush(subpic_ent_t * const spe)
+{
+ if (spe->pic != NULL) {
+ picture_Release(spe->pic);
+ spe->pic = NULL;
+ }
+ buffer_destroy(&spe->wb);
+ dmabuf_unref(&spe->dh);
+}
+
+static void
+subpic_ent_attach(struct wl_surface * const surface, subpic_ent_t * const spe)
+{
+ wl_surface_attach(surface, spe->wb, 0, 0);
+ spe->dh = NULL;
+ spe->wb = NULL;
+}
+
+static void
+mark_all_surface_opaque(struct wl_compositor * compositor, struct wl_surface * surface)
+{
+ struct wl_region * region_all = wl_compositor_create_region(compositor);
+ wl_region_add(region_all, 0, 0, INT32_MAX, INT32_MAX);
+ wl_surface_set_opaque_region(surface, region_all);
+ wl_region_destroy(region_all);
+}
+
+static int
+make_video_surface(vout_display_t * const vd, vout_display_sys_t * const sys)
+{
+ VLC_UNUSED(vd);
+
+ if (sys->viewport)
+ return VLC_SUCCESS;
+
+#if VIDEO_ON_SUBSURFACE
+ // Make a new subsurface to use for video
+ sys->video_surface = wl_compositor_create_surface(video_compositor(sys));
+ sys->video_subsurface = wl_subcompositor_get_subsurface(sys->subcompositor, sys->video_surface, bkg_surface(sys));
+ wl_subsurface_place_above(sys->video_subsurface, bkg_surface(sys));
+ wl_subsurface_set_desync(sys->video_subsurface); // Video update can be desync from main window
+#endif
+
+ struct wl_surface * const surface = video_surface(sys);
+
+ // Video is opaque
+ mark_all_surface_opaque(video_compositor(sys), surface);
+
+ sys->viewport = wp_viewporter_get_viewport(sys->viewporter, surface);
+
+ /* Determine our pixel format */
+ static const enum wl_output_transform transforms[8] = {
+ [ORIENT_TOP_LEFT] = WL_OUTPUT_TRANSFORM_NORMAL,
+ [ORIENT_TOP_RIGHT] = WL_OUTPUT_TRANSFORM_FLIPPED,
+ [ORIENT_BOTTOM_LEFT] = WL_OUTPUT_TRANSFORM_FLIPPED_180,
+ [ORIENT_BOTTOM_RIGHT] = WL_OUTPUT_TRANSFORM_180,
+ [ORIENT_LEFT_TOP] = WL_OUTPUT_TRANSFORM_FLIPPED_270,
+ [ORIENT_LEFT_BOTTOM] = WL_OUTPUT_TRANSFORM_90,
+ [ORIENT_RIGHT_TOP] = WL_OUTPUT_TRANSFORM_270,
+ [ORIENT_RIGHT_BOTTOM] = WL_OUTPUT_TRANSFORM_FLIPPED_90,
+ };
+
+ wl_surface_set_buffer_transform(surface, transforms[vd->fmt.orientation]);
+ return VLC_SUCCESS;
+}
+
+static int
+make_subpic_surfaces(vout_display_t * const vd, vout_display_sys_t * const sys)
+{
+ unsigned int i;
+ struct wl_surface * const surface = video_surface(sys);
+ struct wl_surface * below = surface;
+ VLC_UNUSED(vd);
+
+ if (sys->subplanes[0].surface)
+ return VLC_SUCCESS;
+
+ for (i = 0; i != MAX_SUBPICS; ++i)
+ {
+ subplane_t *plane = sys->subplanes + i;
+ plane->surface = wl_compositor_create_surface(video_compositor(sys));
+ plane->subsurface = wl_subcompositor_get_subsurface(sys->subcompositor, plane->surface, surface);
+ wl_subsurface_place_above(plane->subsurface, below);
+ below = plane->surface;
+ wl_subsurface_set_sync(plane->subsurface);
+ plane->viewport = wp_viewporter_get_viewport(sys->viewporter, plane->surface);
+ }
+ return VLC_SUCCESS;
+}
+
+static int
+make_background(vout_display_t * const vd, vout_display_sys_t * const sys)
+{
+#if !VIDEO_ON_SUBSURFACE
+ VLC_UNUSED(vd);
+ VLC_UNUSED(sys);
+ return VLC_SUCCESS;
+#else
+ // Build a background
+ // This would be a perfect use of the single_pixel_surface extension
+ // However we don't seem to support it
+ struct dmabuf_h * dh = NULL;
+
+ if (!sys->bkg_viewport)
+ {
+ unsigned int width = 640;
+ unsigned int height = 480;
+ unsigned int stride = 640 * 4;
+ struct zwp_linux_buffer_params_v1 *params;
+ struct wl_buffer * w_buffer;
+
+ if ((dh = picpool_get(sys->subpic_pool, stride * height)) == NULL) {
+ msg_Err(vd, "Failed to get DmaBuf for background");
+ goto error;
+ }
+
+ dmabuf_write_start(dh);
+ chequerboard(dmabuf_map(dh), stride, width, height);
+ dmabuf_write_end(dh);
+
+ params = zwp_linux_dmabuf_v1_create_params(sys->linux_dmabuf_v1);
+ if (!params) {
+ msg_Err(vd, "zwp_linux_dmabuf_v1_create_params FAILED");
+ goto error;
+ }
+ zwp_linux_buffer_params_v1_add(params, dmabuf_fd(dh), 0, 0, stride, 0, 0);
+ w_buffer = zwp_linux_buffer_params_v1_create_immed(params, width, height, DRM_FORMAT_XRGB8888, 0);
+ zwp_linux_buffer_params_v1_destroy(params);
+ if (!w_buffer) {
+ msg_Err(vd, "Failed to create background buffer");
+ goto error;
+ }
+
+ sys->bkg_viewport = wp_viewporter_get_viewport(sys->viewporter, bkg_surface(sys));
+
+ wl_buffer_add_listener(w_buffer, &subpic_buffer_listener, dh);
+ wl_surface_attach(bkg_surface(sys), w_buffer, 0, 0);
+ dh = NULL;
+
+ wp_viewport_set_destination(sys->bkg_viewport, sys->bkg_w, sys->bkg_h);
+ mark_all_surface_opaque(video_compositor(sys), bkg_surface(sys));
+
+ wl_surface_commit(bkg_surface(sys));
+ }
+ return VLC_SUCCESS;
+
+error:
+ dmabuf_unref(&dh);
+ return VLC_ENOMEM;
+#endif
+}
+
+static void
+set_video_viewport(vout_display_t * const vd, vout_display_sys_t * const sys)
+{
+ video_format_t fmt;
+
+ if (!sys->video_attached || sys->viewport_set)
+ return;
+
+ sys->viewport_set = true;
+
+ video_format_ApplyRotation(&fmt, &vd->source);
+ wp_viewport_set_source(sys->viewport,
+ wl_fixed_from_int(fmt.i_x_offset),
+ wl_fixed_from_int(fmt.i_y_offset),
+ wl_fixed_from_int(fmt.i_visible_width),
+ wl_fixed_from_int(fmt.i_visible_height));
+ wp_viewport_set_destination(sys->viewport,
+ sys->dst_rect.width, sys->dst_rect.height);
+ wl_subsurface_set_position(sys->video_subsurface, sys->dst_rect.x, sys->dst_rect.y);
+}
+
+static void Prepare(vout_display_t *vd, picture_t *pic, subpicture_t *subpic)
+{
+ vout_display_sys_t * const sys = vd->sys;
+ unsigned int n = 0;
+
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s: Surface: %p", __func__, sys->embed->handle.wl);
+#endif
+ check_embed(vd, sys, __func__);
+
+ if (drmu_format_vlc_to_drm_prime(pic->format.i_chroma, NULL) == 0) {
+ copy_subpic_to_w_buffer(vd, sys, pic, 0xff, &sys->piccpy.dh, &sys->piccpy.wb);
+ }
+ else {
+ do_display_dmabuf(vd, sys, pic, &sys->pic_vdre, &sys->piccpy.wb);
+ }
+
+ // Attempt to import the subpics
+ for (subpicture_t * spic = subpic; spic != NULL; spic = spic->p_next)
+ {
+ for (subpicture_region_t *sreg = spic->p_region; sreg != NULL; sreg = sreg->p_next) {
+ picture_t * const src = sreg->p_picture;
+ subpic_ent_t * const dst = sys->subpics + n;
+
+ // If the same picture then assume the same contents
+ // We keep a ref to the previous pic to ensure that the same picture
+ // structure doesn't get reused and confuse us.
+ if (src != dst->pic || sreg->i_alpha != dst->alpha) {
+ subpic_ent_flush(dst);
+
+ if (copy_subpic_to_w_buffer(vd, sys, src, sreg->i_alpha, &dst->dh, &dst->wb) != 0)
+ continue;
+
+ dst->pic = picture_Hold(src);
+ dst->alpha = sreg->i_alpha;
+ dst->update = true;
+ }
+
+ dst->src_rect = (vout_display_place_t) {
+ .x = sreg->fmt.i_x_offset,
+ .y = sreg->fmt.i_y_offset,
+ .width = sreg->fmt.i_visible_width,
+ .height = sreg->fmt.i_visible_height,
+ };
+ dst->dst_rect = place_rescale(
+ (vout_display_place_t) {
+ .x = sreg->i_x,
+ .y = sreg->i_y,
+ .width = sreg->fmt.i_visible_width,
+ .height = sreg->fmt.i_visible_height,
+ },
+ (vout_display_place_t) {
+ .x = 0,
+ .y = 0,
+ .width = sys->dst_rect.width,
+ .height = sys->dst_rect.height,
+ },
+ sys->spu_rect);
+
+ if (++n == MAX_SUBPICS)
+ goto subpics_done;
+ }
+ }
+subpics_done:
+
+ // Clear any other entries
+ for (; n != MAX_SUBPICS; ++n) {
+ subpic_ent_t * const dst = sys->subpics + n;
+
+ if (dst->dh != NULL)
+ dst->update = true;
+ subpic_ent_flush(dst);
+ }
+
+ (void)pic;
+
+#if TRACE_ALL
+ msg_Dbg(vd, ">>> %s: Surface: %p", __func__, sys->embed->handle.wl);
+#endif
+}
+
+static void Display(vout_display_t *vd, picture_t *pic, subpicture_t *subpic)
+{
+ vout_display_sys_t * const sys = vd->sys;
+
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s: Surface: %p", __func__, sys->embed->handle.wl);
+#endif
+
+ check_embed(vd, sys, __func__);
+
+ make_video_surface(vd, sys);
+ make_subpic_surfaces(vd, sys);
+ make_background(vd, sys);
+
+ for (unsigned int i = 0; i != MAX_SUBPICS; ++i)
+ {
+ subpic_ent_t * const spe = sys->subpics + i;
+
+ if (!spe->update)
+ continue;
+
+ msg_Dbg(vd, "%s: Update subpic %i: wb=%p alpha=%d", __func__, i, spe->wb, spe->alpha);
+ subpic_ent_attach(sys->subplanes[i].surface, spe);
+
+ wl_subsurface_set_position(sys->subplanes[i].subsurface, spe->dst_rect.x, spe->dst_rect.y);
+ wp_viewport_set_source(sys->subplanes[i].viewport,
+ wl_fixed_from_int(spe->src_rect.x), wl_fixed_from_int(spe->src_rect.y),
+ wl_fixed_from_int(spe->src_rect.width), wl_fixed_from_int(spe->src_rect.height));
+ wp_viewport_set_destination(sys->subplanes[i].viewport, spe->dst_rect.width, spe->dst_rect.height);
+ wl_surface_damage(sys->subplanes[i].surface, 0, 0, INT32_MAX, INT32_MAX);
+
+ wl_surface_commit(sys->subplanes[i].surface);
+ spe->update = false;
+ }
+
+ if (!sys->piccpy.wb) {
+ msg_Warn(vd, "Display called but prepared pic buffer");
+ }
+ else {
+ subpic_ent_attach(video_surface(sys), &sys->piccpy);
+ sys->video_attached = true;
+ // Now up to the buffer callback to free stuff
+ sys->pic_vdre = NULL;
+ }
+
+ set_video_viewport(vd, sys);
+
+ wl_surface_damage(video_surface(sys), 0, 0, INT32_MAX, INT32_MAX);
+ wl_surface_commit(video_surface(sys));
+
+ // With VIDEO_ON_SUBSURFACE we need a commit on the background here
+ // too if the video surface isn't desync. Desync is set, but wayland
+ // can force sync if the bkg surface is a sync subsurface.
+ // Try adding a bkg surface commit here if things freeze with
+ // VIDEO_ON_SUBSURFACE set but don't with it unset
+
+ wl_display_flush(video_display(sys));
+// roundtrip(sys);
+
+ if (subpic)
+ subpicture_Delete(subpic);
+ picture_Release(pic);
+
+#if TRACE_ALL
+ msg_Dbg(vd, ">>> %s: Surface: %p", __func__, sys->embed->handle.wl);
+#endif
+}
+
+static void ResetPictures(vout_display_t *vd)
+{
+ vout_display_sys_t *sys = vd->sys;
+
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s", __func__);
+#endif
+ check_embed(vd, sys, __func__);
+
+ kill_pool(sys);
+
+#if TRACE_ALL
+ msg_Dbg(vd, ">>> %s", __func__);
+#endif
+}
+
+static int Control(vout_display_t *vd, int query, va_list ap)
+{
+ vout_display_sys_t * const sys = vd->sys;
+
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s: Query=%d", __func__, query);
+#endif
+ check_embed(vd, sys, __func__);
+
+ switch (query)
+ {
+ case VOUT_DISPLAY_RESET_PICTURES:
+ {
+ vout_display_place_t place;
+ video_format_t src;
+
+ assert(sys->viewport == NULL);
+
+ vout_display_PlacePicture(&place, &vd->source, vd->cfg, false);
+ video_format_ApplyRotation(&src, &vd->source);
+
+ vd->fmt.i_width = src.i_width * place.width
+ / src.i_visible_width;
+ vd->fmt.i_height = src.i_height * place.height
+ / src.i_visible_height;
+ vd->fmt.i_visible_width = place.width;
+ vd->fmt.i_visible_height = place.height;
+ vd->fmt.i_x_offset = src.i_x_offset * place.width
+ / src.i_visible_width;
+ vd->fmt.i_y_offset = src.i_y_offset * place.height
+ / src.i_visible_height;
+ ResetPictures(vd);
+ sys->curr_aspect = vd->source;
+ break;
+ }
+
+ case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
+ case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
+ case VOUT_DISPLAY_CHANGE_ZOOM:
+ case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
+ case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
+ {
+ const vout_display_cfg_t *cfg;
+
+ if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT
+ || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP)
+ {
+ cfg = vd->cfg;
+ }
+ else
+ {
+ cfg = va_arg(ap, const vout_display_cfg_t *);
+ }
+
+#if !VIDEO_ON_SUBSURFACE
+ vout_display_place_t place;
+
+ vout_display_PlacePicture(&place, &sys->curr_aspect, vd->cfg, false);
+ sys->x += place.width / 2;
+ sys->y += place.height / 2;
+
+ vout_display_PlacePicture(&sys->dst_rect, &vd->source, cfg, false);
+ sys->x -= sys->dst_rect.width / 2;
+ sys->y -= sys->dst_rect.height / 2;
+#else
+ vout_display_PlacePicture(&sys->dst_rect, &vd->source, cfg, true);
+#endif
+
+ place_spu_rect(vd, cfg, &vd->fmt);
+ sys->viewport_set = false;
+
+ if (sys->viewport)
+ set_video_viewport(vd, sys);
+
+#if VIDEO_ON_SUBSURFACE
+ if (sys->bkg_viewport != NULL && (cfg->display.width != sys->bkg_w || cfg->display.height != sys->bkg_h))
+ {
+ sys->bkg_w = cfg->display.width;
+ sys->bkg_h = cfg->display.height;
+
+ msg_Dbg(vd, "Resize background: %dx%d; surface=%p", cfg->display.width, cfg->display.height, bkg_surface(sys));
+ wp_viewport_set_destination(sys->bkg_viewport, cfg->display.width, cfg->display.height);
+ wl_surface_commit(bkg_surface(sys));
+ }
+#endif
+
+ sys->curr_aspect = vd->source;
+ break;
+ }
+ default:
+ msg_Err(vd, "unknown request %d", query);
+ return VLC_EGENERIC;
+ }
+
+#if TRACE_ALL
+ msg_Dbg(vd, ">>> %s: Surface: %p", __func__, sys->embed->handle.wl);
+#endif
+ return VLC_SUCCESS;
+}
+
+static void linux_dmabuf_v1_listener_format(void *data,
+ struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1,
+ uint32_t format)
+{
+ // Superceeded by _modifier
+ vout_display_t * const vd = data;
+ vout_display_sys_t * const sys = vd->sys;
+ (void)zwp_linux_dmabuf_v1;
+
+ msg_Dbg(vd, "%s[%p], %.4s", __func__, (void*)vd, (const char *)&format);
+
+ fmt_list_add(&sys->dmabuf_fmts, format, DRM_FORMAT_MOD_LINEAR, 0);
+}
+
+static void
+linux_dmabuf_v1_listener_modifier(void *data,
+ struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1,
+ uint32_t format,
+ uint32_t modifier_hi,
+ uint32_t modifier_lo)
+{
+ vout_display_t * const vd = data;
+ vout_display_sys_t * const sys = vd->sys;
+ (void)zwp_linux_dmabuf_v1;
+
+ msg_Dbg(vd, "%s[%p], %.4s %08x%08x", __func__, (void*)vd, (const char *)&format, modifier_hi, modifier_lo);
+
+ fmt_list_add(&sys->dmabuf_fmts, format, modifier_lo | ((uint64_t)modifier_hi << 32), 0);
+}
+
+static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_v1_listener = {
+ .format = linux_dmabuf_v1_listener_format,
+ .modifier = linux_dmabuf_v1_listener_modifier,
+};
+
+
+static void registry_global_cb(void *data, struct wl_registry *registry,
+ uint32_t name, const char *iface, uint32_t vers)
+{
+ vout_display_t * const vd = data;
+ vout_display_sys_t * const sys = vd->sys;
+
+ msg_Dbg(vd, "global %3"PRIu32": %s version %"PRIu32, name, iface, vers);
+
+ if (strcmp(iface, wl_subcompositor_interface.name) == 0)
+ sys->subcompositor = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1);
+ else
+ if (!strcmp(iface, "wp_viewporter"))
+ sys->viewporter = wl_registry_bind(registry, name, &wp_viewporter_interface, 1);
+ else
+ if (!strcmp(iface, "wl_compositor"))
+ {
+ if (vers >= 4)
+ sys->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4);
+ else
+ msg_Warn(vd, "Interface %s wanted v 4 got v %d", wl_compositor_interface.name, vers);
+ }
+ else
+ if (strcmp(iface, zwp_linux_dmabuf_v1_interface.name) == 0)
+ {
+ if (vers >= 3)
+ sys->linux_dmabuf_v1 = wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, 3);
+ else
+ msg_Warn(vd, "Interface %s wanted v 3 got v %d", zwp_linux_dmabuf_v1_interface.name, vers);
+ }
+
+}
+
+static void registry_global_remove_cb(void *data, struct wl_registry *registry,
+ uint32_t name)
+{
+ vout_display_t *vd = data;
+
+ msg_Dbg(vd, "global remove %3"PRIu32, name);
+ (void) registry;
+}
+
+static const struct wl_registry_listener registry_cbs =
+{
+ registry_global_cb,
+ registry_global_remove_cb,
+};
+
+
+static void
+clear_surface_buffer(struct wl_surface * surface)
+{
+ if (surface == NULL)
+ return;
+ wl_surface_attach(surface, NULL, 0, 0);
+ wl_surface_commit(surface);
+}
+
+static void
+clear_all_buffers(vout_display_sys_t *sys)
+{
+ for (unsigned int i = 0; i != MAX_SUBPICS; ++i)
+ {
+ subpic_ent_t * const spe = sys->subpics + i;
+ subpic_ent_flush(spe);
+
+ clear_surface_buffer(sys->subplanes[i].surface);
+ }
+
+ clear_surface_buffer(video_surface(sys));
+ sys->video_attached = false;
+
+#if VIDEO_ON_SUBSURFACE
+ clear_surface_buffer(bkg_surface(sys));
+#endif
+
+ subpic_ent_flush(&sys->piccpy);
+ vdre_delete(&sys->pic_vdre);
+}
+
+
+static void Close(vlc_object_t *obj)
+{
+ vout_display_t * const vd = (vout_display_t *)obj;
+ vout_display_sys_t * const sys = vd->sys;
+
+#if TRACE_ALL
+ msg_Dbg(vd, "<<< %s", __func__);
+#endif
+
+ if (sys == NULL)
+ return;
+
+ if (sys->embed == NULL)
+ goto no_window;
+
+ check_embed(vd, sys, __func__);
+
+ clear_all_buffers(sys);
+
+ // Free subpic resources
+ for (unsigned int i = 0; i != MAX_SUBPICS; ++i)
+ {
+ subplane_t * const spl = sys->subplanes + i;
+
+ viewport_destroy(&spl->viewport);
+ subsurface_destroy(&spl->subsurface);
+ surface_destroy(&spl->surface);
+ }
+
+ viewport_destroy(&sys->viewport);
+
+#if VIDEO_ON_SUBSURFACE
+ subsurface_destroy(&sys->video_subsurface);
+ surface_destroy(&sys->video_surface);
+
+ viewport_destroy(&sys->bkg_viewport);
+#endif
+
+ wl_display_flush(video_display(sys));
+
+ if (sys->viewporter != NULL)
+ wp_viewporter_destroy(sys->viewporter);
+ if (sys->linux_dmabuf_v1 != NULL)
+ zwp_linux_dmabuf_v1_destroy(sys->linux_dmabuf_v1);
+ if (sys->subcompositor != NULL)
+ wl_subcompositor_destroy(sys->subcompositor);
+ if (sys->compositor != NULL)
+ wl_compositor_destroy(sys->compositor);
+
+ wl_display_flush(video_display(sys));
+
+ vout_display_DeleteWindow(vd, sys->embed);
+ sys->embed = NULL;
+
+ kill_pool(sys);
+ picpool_unref(&sys->subpic_pool);
+ pollqueue_unref(&sys->pollq);
+
+ free(sys->subpic_chromas);
+
+no_window:
+ fmt_list_uninit(&sys->dmabuf_fmts);
+ free(sys);
+
+#if TRACE_ALL
+ msg_Dbg(vd, ">>> %s", __func__);
+#endif
+}
+
+static int Open(vlc_object_t *obj)
+{
+ vout_display_t * const vd = (vout_display_t *)obj;
+ vout_display_sys_t *sys;
+ uint32_t pic_drm_fmt = 0;
+ uint64_t pic_drm_mod = DRM_FORMAT_MOD_LINEAR;
+
+ msg_Info(vd, "<<< %s: %.4s %dx%d, cfg.display: %dx%d", __func__,
+ (const char*)&vd->fmt.i_chroma, vd->fmt.i_width, vd->fmt.i_height,
+ vd->cfg->display.width, vd->cfg->display.height);
+
+ if (!(pic_drm_fmt = drmu_format_vlc_to_drm(&vd->fmt)) &&
+ !(pic_drm_fmt = drmu_format_vlc_to_drm_prime(vd->fmt.i_chroma, &pic_drm_mod)))
+ return VLC_EGENERIC;
+
+ sys = calloc(1, sizeof(*sys));
+ if (unlikely(sys == NULL))
+ return VLC_ENOMEM;
+
+ vd->sys = sys;
+
+ if (fmt_list_init(&sys->dmabuf_fmts, 128)) {
+ msg_Err(vd, "Failed to allocate format list!");
+ goto error;
+ }
+
+ /* Get window */
+ sys->embed = vout_display_NewWindow(vd, VOUT_WINDOW_TYPE_WAYLAND);
+ if (sys->embed == NULL)
+ goto error;
+ sys->last_embed_surface = sys->embed->handle.wl;
+
+ {
+ struct wl_registry * const registry = wl_display_get_registry(video_display(sys));
+ if (registry == NULL) {
+ msg_Err(vd, "Cannot get registry for display");
+ goto error;
+ }
+
+ wl_registry_add_listener(registry, &registry_cbs, vd);
+ roundtrip(sys);
+ wl_registry_destroy(registry);
+ }
+
+ if (sys->compositor == NULL) {
+ msg_Warn(vd, "Interface %s missing", wl_compositor_interface.name);
+ goto error;
+ }
+ if (sys->subcompositor == NULL) {
+ msg_Warn(vd, "Interface %s missing", wl_subcompositor_interface.name);
+ goto error;
+ }
+ if (sys->viewporter == NULL) {
+ msg_Warn(vd, "Interface %s missing", wp_viewporter_interface.name);
+ goto error;
+ }
+ if (sys->linux_dmabuf_v1 == NULL) {
+ msg_Warn(vd, "Interface %s missing", zwp_linux_dmabuf_v1_interface.name);
+ goto error;
+ }
+
+ // And again for registering formats
+ zwp_linux_dmabuf_v1_add_listener(sys->linux_dmabuf_v1, &linux_dmabuf_v1_listener, vd);
+
+ roundtrip(sys);
+
+ fmt_list_sort(&sys->dmabuf_fmts);
+
+ // Check PIC DRM format here
+ if (fmt_list_find(&sys->dmabuf_fmts, pic_drm_fmt, pic_drm_mod) < 0) {
+ msg_Warn(vd, "Could not find %.4s mod %#"PRIx64" in supported formats", (char*)&pic_drm_fmt, pic_drm_mod);
+ goto error;
+ }
+
+ {
+ static vlc_fourcc_t const tryfmts[] = {
+ VLC_CODEC_RGBA,
+ VLC_CODEC_BGRA,
+ };
+ unsigned int n = 0;
+
+ if ((sys->subpic_chromas = calloc(ARRAY_SIZE(tryfmts) + 1, sizeof(vlc_fourcc_t))) == NULL)
+ goto error;
+ for (unsigned int i = 0; i != ARRAY_SIZE(tryfmts); ++i)
+ {
+ uint32_t drmfmt = drmu_format_vlc_chroma_to_drm(tryfmts[i]);
+ msg_Dbg(vd, "Look for %.4s", (char*)&drmfmt);
+ if (fmt_list_find(&sys->dmabuf_fmts, drmfmt, DRM_FORMAT_MOD_LINEAR) >= 0)
+ sys->subpic_chromas[n++] = tryfmts[i];
+ }
+
+ if (n == 0)
+ msg_Warn(vd, "No compatible subpic formats found");
+ }
+
+ {
+ struct dmabufs_ctl * dbsc = dmabufs_ctl_new();
+ if (dbsc == NULL)
+ {
+ msg_Err(vd, "Failed to create dmabuf ctl");
+ goto error;
+ }
+ sys->subpic_pool = picpool_new(dbsc);
+ dmabufs_ctl_unref(&dbsc);
+ if (sys->subpic_pool == NULL)
+ {
+ msg_Err(vd, "Failed to create picpool");
+ goto error;
+ }
+ }
+
+ if ((sys->pollq = pollqueue_new()) == NULL)
+ {
+ msg_Err(vd, "Failed to create pollqueue");
+ goto error;
+ }
+
+ sys->curr_aspect = vd->source;
+ sys->bkg_w = vd->cfg->display.width;
+ sys->bkg_h = vd->cfg->display.height;
+
+ vd->info.has_pictures_invalid = sys->viewport == NULL;
+ vd->info.subpicture_chromas = sys->subpic_chromas;
+
+ vd->pool = vd_dmabuf_pool;
+ vd->prepare = Prepare;
+ vd->display = Display;
+ vd->control = Control;
+
+#if TRACE_ALL
+ msg_Dbg(vd, ">>> %s: OK", __func__);
+#endif
+ return VLC_SUCCESS;
+
+error:
+ Close(obj);
+#if TRACE_ALL
+ msg_Dbg(vd, ">>> %s: ERROR", __func__);
+#endif
+ return VLC_EGENERIC;
+}
+
+vlc_module_begin()
+ set_shortname(N_("WL DMABUF"))
+ set_description(N_("Wayland dmabuf video output"))
+ set_category(CAT_VIDEO)
+ set_subcategory(SUBCAT_VIDEO_VOUT)
+ set_capability("vout display", 0)
+ set_callbacks(Open, Close)
+ add_shortcut("wl-dmabuf")
+vlc_module_end()
diff --git a/modules/video_output/wayland/dmabuf_alloc.c b/modules/video_output/wayland/dmabuf_alloc.c
new file mode 100644
index 0000000000..31578c3098
--- /dev/null
+++ b/modules/video_output/wayland/dmabuf_alloc.c
@@ -0,0 +1,417 @@
+#include <stdatomic.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <linux/mman.h>
+#include <linux/dma-buf.h>
+#include <linux/dma-heap.h>
+
+#include "dmabuf_alloc.h"
+
+#define DMABUF_NAME1 "/dev/dma_heap/linux,cma"
+#define DMABUF_NAME2 "/dev/dma_heap/reserved"
+
+#define TRACE_ALLOC 0
+
+#define request_log(...)
+#define request_debug(...)
+
+struct dmabufs_ctl;
+struct dmabuf_h;
+
+struct dmabuf_fns {
+ int (*buf_alloc)(struct dmabufs_ctl * dbsc, struct dmabuf_h * dh, size_t size);
+ void (*buf_free)(struct dmabuf_h * dh);
+ int (*ctl_new)(struct dmabufs_ctl * dbsc);
+ void (*ctl_free)(struct dmabufs_ctl * dbsc);
+};
+
+struct dmabufs_ctl {
+ atomic_int ref_count;
+ int fd;
+ size_t page_size;
+ void * v;
+ const struct dmabuf_fns * fns;
+};
+
+struct dmabuf_h {
+ atomic_int ref_count;
+ int fd;
+ size_t size;
+ size_t len;
+ void * mapptr;
+ void * v;
+ const struct dmabuf_fns * fns;
+
+ void * predel_v;
+ int (* predel_fn)(struct dmabuf_h * dh, void * v);
+};
+
+#if TRACE_ALLOC
+static unsigned int total_bufs = 0;
+static size_t total_size = 0;
+#endif
+
+struct dmabuf_h * dmabuf_import_mmap(void * mapptr, size_t size)
+{
+ struct dmabuf_h *dh;
+
+ if (mapptr == MAP_FAILED)
+ return NULL;
+
+ dh = malloc(sizeof(*dh));
+ if (!dh)
+ return NULL;
+
+ *dh = (struct dmabuf_h) {
+ .fd = -1,
+ .size = size,
+ .mapptr = mapptr
+ };
+
+ return dh;
+}
+
+struct dmabuf_h * dmabuf_import(int fd, size_t size)
+{
+ struct dmabuf_h *dh;
+
+ fd = dup(fd);
+ if (fd < 0 || size == 0)
+ return NULL;
+
+ dh = malloc(sizeof(*dh));
+ if (!dh) {
+ close(fd);
+ return NULL;
+ }
+
+ *dh = (struct dmabuf_h) {
+ .fd = fd,
+ .size = size,
+ .mapptr = MAP_FAILED
+ };
+
+#if TRACE_ALLOC
+ ++total_bufs;
+ total_size += dh->size;
+ request_log("%s: Import: %zd, total=%zd, bufs=%d\n", __func__, dh->size, total_size, total_bufs);
+#endif
+
+ return dh;
+}
+
+void dmabuf_free(struct dmabuf_h * dh)
+{
+ if (!dh)
+ return;
+
+#if TRACE_ALLOC
+ --total_bufs;
+ total_size -= dh->size;
+ request_log("%s: Free: %zd, total=%zd, bufs=%d\n", __func__, dh->size, total_size, total_bufs);
+#endif
+
+ dh->fns->buf_free(dh);
+
+ if (dh->mapptr != MAP_FAILED && dh->mapptr != NULL)
+ munmap(dh->mapptr, dh->size);
+ if (dh->fd != -1)
+ while (close(dh->fd) == -1 && errno == EINTR)
+ /* loop */;
+ free(dh);
+}
+
+void dmabuf_unref(struct dmabuf_h ** const ppdh)
+{
+ struct dmabuf_h * dh = *ppdh;
+ int n;
+
+ if (dh == NULL)
+ return;
+ *ppdh = NULL;
+
+ n = atomic_fetch_sub(&dh->ref_count, 1);
+// fprintf(stderr, "%s[%p]: Ref: %d\n", __func__, dh, n);
+ if (n != 0)
+ return;
+
+ if (dh->predel_fn && dh->predel_fn(dh, dh->predel_v) != 0)
+ return;
+
+ dmabuf_free(dh);
+}
+
+struct dmabuf_h * dmabuf_ref(struct dmabuf_h * const dh)
+{
+ if (dh != NULL)
+ {
+ int n = atomic_fetch_add(&dh->ref_count, 1);
+// fprintf(stderr, "%s[%p]: Ref: %d\n", __func__, dh, n);
+ (void)n;
+ }
+ return dh;
+}
+
+void dmabuf_predel_cb_set(struct dmabuf_h * const dh,
+ int (* const predel_fn)(struct dmabuf_h * dh, void * v), void * const predel_v)
+{
+ dh->predel_fn = predel_fn;
+ dh->predel_v = predel_v;
+}
+
+struct dmabuf_h * dmabuf_realloc(struct dmabufs_ctl * dbsc, struct dmabuf_h * old, size_t size)
+{
+ struct dmabuf_h * dh;
+ if (old != NULL) {
+ if (old->size >= size) {
+ return old;
+ }
+ dmabuf_free(old);
+ }
+
+ if (size == 0 ||
+ (dh = malloc(sizeof(*dh))) == NULL)
+ return NULL;
+
+ *dh = (struct dmabuf_h){
+ .fd = -1,
+ .mapptr = MAP_FAILED,
+ .fns = dbsc->fns
+ };
+
+ if (dh->fns->buf_alloc(dbsc, dh, size) != 0)
+ goto fail;
+
+
+#if TRACE_ALLOC
+ ++total_bufs;
+ total_size += dh->size;
+ request_log("%s: Alloc: %zd, total=%zd, bufs=%d\n", __func__, dh->size, total_size, total_bufs);
+#endif
+
+ return dh;
+
+fail:
+ free(dh);
+ return NULL;
+}
+
+int dmabuf_sync(struct dmabuf_h * const dh, unsigned int flags)
+{
+ struct dma_buf_sync sync = {
+ .flags = flags
+ };
+ if (dh->fd == -1)
+ return 0;
+ while (ioctl(dh->fd, DMA_BUF_IOCTL_SYNC, &sync) == -1) {
+ const int err = errno;
+ if (errno == EINTR)
+ continue;
+ request_log("%s: ioctl failed: flags=%#x\n", __func__, flags);
+ return -err;
+ }
+ return 0;
+}
+
+int dmabuf_write_start(struct dmabuf_h * const dh)
+{
+ return dmabuf_sync(dh, DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE);
+}
+
+int dmabuf_write_end(struct dmabuf_h * const dh)
+{
+ return dmabuf_sync(dh, DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE);
+}
+
+int dmabuf_read_start(struct dmabuf_h * const dh)
+{
+ if (!dmabuf_map(dh))
+ return -1;
+ return dmabuf_sync(dh, DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ);
+}
+
+int dmabuf_read_end(struct dmabuf_h * const dh)
+{
+ return dmabuf_sync(dh, DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ);
+}
+
+
+void * dmabuf_map(struct dmabuf_h * const dh)
+{
+ if (!dh)
+ return NULL;
+ if (dh->mapptr != MAP_FAILED)
+ return dh->mapptr;
+ dh->mapptr = mmap(NULL, dh->size,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_POPULATE,
+ dh->fd, 0);
+ if (dh->mapptr == MAP_FAILED) {
+ request_log("%s: Map failed\n", __func__);
+ return NULL;
+ }
+ return dh->mapptr;
+}
+
+int dmabuf_fd(const struct dmabuf_h * const dh)
+{
+ if (!dh)
+ return -1;
+ return dh->fd;
+}
+
+size_t dmabuf_size(const struct dmabuf_h * const dh)
+{
+ if (!dh)
+ return 0;
+ return dh->size;
+}
+
+size_t dmabuf_len(const struct dmabuf_h * const dh)
+{
+ if (!dh)
+ return 0;
+ return dh->len;
+}
+
+void dmabuf_len_set(struct dmabuf_h * const dh, const size_t len)
+{
+ dh->len = len;
+}
+
+static struct dmabufs_ctl * dmabufs_ctl_new2(const struct dmabuf_fns * const fns)
+{
+ struct dmabufs_ctl * dbsc = calloc(1, sizeof(*dbsc));
+
+ if (!dbsc)
+ return NULL;
+
+ dbsc->fd = -1;
+ dbsc->fns = fns;
+ dbsc->page_size = (size_t)sysconf(_SC_PAGE_SIZE);
+
+ if (fns->ctl_new(dbsc) != 0)
+ goto fail;
+
+ return dbsc;
+
+fail:
+ free(dbsc);
+ return NULL;
+}
+
+static void dmabufs_ctl_free(struct dmabufs_ctl * const dbsc)
+{
+ request_debug(NULL, "Free dmabuf ctl\n");
+
+ dbsc->fns->ctl_free(dbsc);
+
+ free(dbsc);
+}
+
+void dmabufs_ctl_unref(struct dmabufs_ctl ** const pDbsc)
+{
+ struct dmabufs_ctl * const dbsc = *pDbsc;
+
+ if (!dbsc)
+ return;
+ *pDbsc = NULL;
+
+ if (atomic_fetch_sub(&dbsc->ref_count, 1) != 0)
+ return;
+
+ dmabufs_ctl_free(dbsc);
+}
+
+struct dmabufs_ctl * dmabufs_ctl_ref(struct dmabufs_ctl * const dbsc)
+{
+ atomic_fetch_add(&dbsc->ref_count, 1);
+ return dbsc;
+}
+
+//-----------------------------------------------------------------------------
+//
+// Alloc dmabuf via CMA
+
+static int ctl_cma_new(struct dmabufs_ctl * dbsc)
+{
+ while ((dbsc->fd = open(DMABUF_NAME1, O_RDWR)) == -1 &&
+ errno == EINTR)
+ /* Loop */;
+
+ if (dbsc->fd == -1) {
+ while ((dbsc->fd = open(DMABUF_NAME2, O_RDWR)) == -1 &&
+ errno == EINTR)
+ /* Loop */;
+ if (dbsc->fd == -1) {
+ request_log("Unable to open either %s or %s\n",
+ DMABUF_NAME1, DMABUF_NAME2);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void ctl_cma_free(struct dmabufs_ctl * dbsc)
+{
+ if (dbsc->fd != -1)
+ while (close(dbsc->fd) == -1 && errno == EINTR)
+ /* loop */;
+
+}
+
+static int buf_cma_alloc(struct dmabufs_ctl * const dbsc, struct dmabuf_h * dh, size_t size)
+{
+ struct dma_heap_allocation_data data = {
+ .len = (size + dbsc->page_size - 1) & ~(dbsc->page_size - 1),
+ .fd = 0,
+ .fd_flags = O_RDWR,
+ .heap_flags = 0
+ };
+
+ while (ioctl(dbsc->fd, DMA_HEAP_IOCTL_ALLOC, &data)) {
+ int err = errno;
+ request_log("Failed to alloc %" PRIu64 " from dma-heap(fd=%d): %d (%s)\n",
+ (uint64_t)data.len,
+ dbsc->fd,
+ err,
+ strerror(err));
+ if (err == EINTR)
+ continue;
+ return -err;
+ }
+
+ dh->fd = data.fd;
+ dh->size = (size_t)data.len;
+
+// fprintf(stderr, "%s: size=%#zx, ftell=%#zx\n", __func__,
+// dh->size, (size_t)lseek(dh->fd, 0, SEEK_END));
+
+ return 0;
+}
+
+static void buf_cma_free(struct dmabuf_h * dh)
+{
+ (void)dh;
+ // Nothing needed
+}
+
+static const struct dmabuf_fns dmabuf_cma_fns = {
+ .buf_alloc = buf_cma_alloc,
+ .buf_free = buf_cma_free,
+ .ctl_new = ctl_cma_new,
+ .ctl_free = ctl_cma_free,
+};
+
+struct dmabufs_ctl * dmabufs_ctl_new(void)
+{
+ request_debug(NULL, "Dmabufs using CMA\n");;
+ return dmabufs_ctl_new2(&dmabuf_cma_fns);
+}
diff --git a/modules/video_output/wayland/dmabuf_alloc.h b/modules/video_output/wayland/dmabuf_alloc.h
new file mode 100644
index 0000000000..4ca02e0567
--- /dev/null
+++ b/modules/video_output/wayland/dmabuf_alloc.h
@@ -0,0 +1,52 @@
+#ifndef _WAYLAND_DMABUF_ALLOC_H
+#define _WAYLAND_DMABUF_ALLOC_H
+
+#include <stddef.h>
+
+struct dmabufs_ctl;
+struct dmabuf_h;
+
+struct dmabufs_ctl * dmabufs_ctl_new(void);
+void dmabufs_ctl_unref(struct dmabufs_ctl ** const pdbsc);
+struct dmabufs_ctl * dmabufs_ctl_ref(struct dmabufs_ctl * const dbsc);
+
+// Need not preserve old contents
+// On NULL return old buffer is freed
+struct dmabuf_h * dmabuf_realloc(struct dmabufs_ctl * dbsc, struct dmabuf_h *, size_t size);
+
+static inline struct dmabuf_h * dmabuf_alloc(struct dmabufs_ctl * dbsc, size_t size) {
+ return dmabuf_realloc(dbsc, NULL, size);
+}
+/* Create from existing fd - dups(fd) */
+struct dmabuf_h * dmabuf_import(int fd, size_t size);
+/* Import an MMAP - return NULL if mapptr = MAP_FAIL */
+struct dmabuf_h * dmabuf_import_mmap(void * mapptr, size_t size);
+
+void * dmabuf_map(struct dmabuf_h * const dh);
+
+/* flags from linux/dmabuf.h DMA_BUF_SYNC_xxx */
+int dmabuf_sync(struct dmabuf_h * const dh, unsigned int flags);
+
+int dmabuf_write_start(struct dmabuf_h * const dh);
+int dmabuf_write_end(struct dmabuf_h * const dh);
+int dmabuf_read_start(struct dmabuf_h * const dh);
+int dmabuf_read_end(struct dmabuf_h * const dh);
+
+int dmabuf_fd(const struct dmabuf_h * const dh);
+/* Allocated size */
+size_t dmabuf_size(const struct dmabuf_h * const dh);
+/* Bytes in use */
+size_t dmabuf_len(const struct dmabuf_h * const dh);
+/* Set bytes in use */
+void dmabuf_len_set(struct dmabuf_h * const dh, const size_t len);
+
+void dmabuf_predel_cb_set(struct dmabuf_h * const dh,
+ int (* const predel_fn)(struct dmabuf_h * dh, void * v), void * const predel_v);
+static inline void dmabuf_predel_cb_unset(struct dmabuf_h * const dh) {dmabuf_predel_cb_set(dh, 0, NULL);}
+
+void dmabuf_unref(struct dmabuf_h ** const ppdh);
+struct dmabuf_h * dmabuf_ref(struct dmabuf_h * const dh);
+
+void dmabuf_free(struct dmabuf_h * dh);
+
+#endif
diff --git a/modules/video_output/wayland/picpool.c b/modules/video_output/wayland/picpool.c
new file mode 100644
index 0000000000..fb3b8e78c1
--- /dev/null
+++ b/modules/video_output/wayland/picpool.c
@@ -0,0 +1,368 @@
+#include <assert.h>
+#include <stdatomic.h>
+#include <stdlib.h>
+
+#include <vlc_common.h>
+#include <vlc_threads.h>
+
+#include "picpool.h"
+#include "dmabuf_alloc.h"
+
+// ===========================================================================
+
+typedef struct pool_ent_s
+{
+ struct pool_ent_s * next;
+ struct pool_ent_s * prev;
+
+ atomic_int ref_count;
+ unsigned int seq;
+
+ struct picpool_ctl_s * pc;
+ struct dmabuf_h * db;
+
+ size_t size;
+ unsigned int width;
+ unsigned int height;
+} pool_ent_t;
+
+
+typedef struct ent_list_hdr_s
+{
+ pool_ent_t * ents;
+ pool_ent_t * tail;
+ unsigned int n;
+} ent_list_hdr_t;
+
+#define ENT_LIST_HDR_INIT (ent_list_hdr_t){ \
+ .ents = NULL, \
+ .tail = NULL, \
+ .n = 0 \
+}
+
+struct picpool_ctl_s
+{
+ atomic_int ref_count;
+
+ ent_list_hdr_t ent_pool;
+ ent_list_hdr_t ents_cur;
+ ent_list_hdr_t ents_prev;
+
+ unsigned int max_n;
+ unsigned int seq;
+
+ vlc_mutex_t lock;
+ struct dmabufs_ctl * dbsc;
+};
+
+
+static pool_ent_t * ent_extract(ent_list_hdr_t * const elh, pool_ent_t * const ent)
+{
+// printf("List %p [%d]: Ext %p\n", elh, elh->n, ent);
+
+ if (ent == NULL)
+ return NULL;
+
+ if (ent->next == NULL)
+ elh->tail = ent->prev;
+ else
+ ent->next->prev = ent->prev;
+
+ if (ent->prev == NULL)
+ elh->ents = ent->next;
+ else
+ ent->prev->next = ent->next;
+
+ ent->prev = ent->next = NULL;
+
+ --elh->n;
+
+ return ent; // For convienience
+}
+
+static inline pool_ent_t * ent_extract_tail(ent_list_hdr_t * const elh)
+{
+ return ent_extract(elh, elh->tail);
+}
+
+static void ent_add_head(ent_list_hdr_t * const elh, pool_ent_t * const ent)
+{
+// printf("List %p [%d]: Add %p\n", elh, elh->n, ent);
+
+ if ((ent->next = elh->ents) == NULL)
+ elh->tail = ent;
+ else
+ ent->next->prev = ent;
+
+ ent->prev = NULL;
+ elh->ents = ent;
+ ++elh->n;
+}
+
+static void ent_free(pool_ent_t * const ent)
+{
+// printf("Free ent: %p\n", ent);
+ if (ent != NULL)
+ {
+ // If we still have a ref to a buffer - kill it now
+ dmabuf_free(ent->db);
+ free(ent);
+ }
+}
+
+static void ent_free_list(ent_list_hdr_t * const elh)
+{
+ pool_ent_t * ent = elh->ents;
+
+// printf("Free list: %p [%d]\n", elh, elh->n);
+
+ *elh = ENT_LIST_HDR_INIT;
+
+ while (ent != NULL) {
+ pool_ent_t * const t = ent;
+ ent = t->next;
+ ent_free(t);
+ }
+}
+
+#if 0
+static void ent_list_move(ent_list_hdr_t * const dst, ent_list_hdr_t * const src)
+{
+// printf("Move %p->%p\n", src, dst);
+
+ *dst = *src;
+ *src = ENT_LIST_HDR_INIT;
+}
+#endif
+
+#if 0
+// Scans "backwards" as that should give us the fastest match if we are
+// presented with pics in the same order each time
+static pool_ent_t * ent_list_extract_pic_ent(ent_list_hdr_t * const elh, picture_t * const pic)
+{
+ pool_ent_t *ent = elh->tail;
+
+// printf("Find list: %p [%d]; pic:%p\n", elh, elh->n, pic);
+
+ while (ent != NULL) {
+// printf("Check ent: %p, pic:%p\n", ent, ent->pic);
+
+ if (ent->pic == pic)
+ return ent_extract(elh, ent);
+ ent = ent->prev;
+ }
+ return NULL;
+}
+#endif
+
+#define POOL_ENT_ALLOC_BLOCK 0x10000
+
+static pool_ent_t * pool_ent_alloc_new(picpool_ctl_t * const pc, size_t req_size)
+{
+ pool_ent_t * ent = calloc(1, sizeof(*ent));
+ const size_t alloc_size = (req_size + POOL_ENT_ALLOC_BLOCK - 1) & ~(POOL_ENT_ALLOC_BLOCK - 1);
+
+ if (ent == NULL)
+ return NULL;
+
+ ent->next = ent->prev = NULL;
+
+ // Alloc
+ if ((ent->db = dmabuf_realloc(pc->dbsc, NULL, alloc_size)) == NULL)
+ goto fail1;
+// fprintf(stderr, "%s: ent %p db %p req=%zd size=%zd\n", __func__, ent, ent->db, req_size, alloc_size);
+ ent->size = dmabuf_size(ent->db);
+ return ent;
+
+fail1:
+ free(ent);
+ return NULL;
+}
+
+static inline pool_ent_t * pool_ent_ref(pool_ent_t * const ent)
+{
+// int n = atomic_fetch_add(&ent->ref_count, 1) + 1;
+// printf("Ref: %p: %d\n", ent, n);
+ atomic_fetch_add(&ent->ref_count, 1);
+ return ent;
+}
+
+static void pool_recycle(picpool_ctl_t * const pc, pool_ent_t * const ent)
+{
+ pool_ent_t * xs = NULL;
+ int n;
+
+ if (ent == NULL)
+ return;
+
+ n = atomic_fetch_sub(&ent->ref_count, 1) - 1;
+
+// fprintf(stderr, "%s: Pool: %p: Ent: %p: %d dh: %p\n", __func__, &pc->ent_pool, ent, n, ent->db);
+
+ if (n != 0)
+ return;
+
+ vlc_mutex_lock(&pc->lock);
+
+ // If we have a full pool then extract the LRU and free it
+ // Free done outside mutex
+ if (pc->ent_pool.n >= pc->max_n)
+ xs = ent_extract_tail(&pc->ent_pool);
+
+ ent_add_head(&pc->ent_pool, ent);
+
+ vlc_mutex_unlock(&pc->lock);
+
+ ent_free(xs);
+}
+
+// * This could be made more efficient, but this is easy
+static void pool_recycle_list(picpool_ctl_t * const pc, ent_list_hdr_t * const elh)
+{
+ pool_ent_t * ent;
+ while ((ent = ent_extract_tail(elh)) != NULL) {
+ pool_recycle(pc, ent);
+ }
+}
+
+static int pool_predel_cb(struct dmabuf_h * dh, void * v)
+{
+ pool_ent_t * const ent = v;
+ picpool_ctl_t * pc = ent->pc;
+
+ assert(ent->db == dh);
+
+ ent->pc = NULL;
+ dmabuf_ref(dh);
+ dmabuf_predel_cb_unset(dh);
+ pool_recycle(pc, ent);
+ picpool_unref(&pc);
+ return 1; // Do not delete
+}
+
+struct dmabuf_h * picpool_get(picpool_ctl_t * const pc, size_t req_size)
+{
+ pool_ent_t * best = NULL;
+
+ vlc_mutex_lock(&pc->lock);
+
+ {
+ pool_ent_t * ent = pc->ent_pool.ents;
+
+ // Simple scan
+ while (ent != NULL) {
+ if (ent->size >= req_size && ent->size <= req_size * 2 + POOL_ENT_ALLOC_BLOCK &&
+ (best == NULL || best->size > ent->size))
+ best = ent;
+ ent = ent->next;
+ }
+
+ // extract best from chain if we've found it
+ ent_extract(&pc->ent_pool, best);
+ }
+
+ vlc_mutex_unlock(&pc->lock);
+
+ if (best == NULL) {
+ if ((best = pool_ent_alloc_new(pc, req_size)) == NULL)
+ return NULL;
+ }
+
+ if ((best->seq = ++pc->seq) == 0)
+ best->seq = ++pc->seq; // Never allow to be zero
+
+ atomic_store(&best->ref_count, 1);
+ best->pc = picpool_ref(pc);
+ dmabuf_predel_cb_set(best->db, pool_predel_cb, best);
+// fprintf(stderr, "%s: find ent %p db %p size %zd\n", __func__, best, best->db, best->size);
+ return best->db;
+}
+
+void picpool_flush(picpool_ctl_t * const pc)
+{
+ pool_recycle_list(pc, &pc->ents_prev);
+ pool_recycle_list(pc, &pc->ents_cur);
+}
+
+static void picpool_delete(picpool_ctl_t * const pc)
+{
+
+// printf("<<< %s\n", __func__);
+
+ picpool_flush(pc);
+
+ ent_free_list(&pc->ent_pool);
+
+ dmabufs_ctl_unref(&pc->dbsc);
+
+ vlc_mutex_destroy(&pc->lock);
+
+// memset(pc, 0xba, sizeof(*pc)); // Zap for (hopefully) faster crash
+ free (pc);
+
+ // printf(">>> %s\n", __func__);
+}
+
+void picpool_unref(picpool_ctl_t ** const pppc)
+{
+ int n;
+ picpool_ctl_t * const pc = *pppc;
+
+ if (pc == NULL)
+ return;
+ *pppc = NULL;
+
+ n = atomic_fetch_sub(&pc->ref_count, 1) - 1;
+
+ if (n != 0)
+ return;
+
+ picpool_delete(pc);
+}
+
+picpool_ctl_t * picpool_ref(picpool_ctl_t * const pc)
+{
+ atomic_fetch_add(&pc->ref_count, 1);
+ return pc;
+}
+
+#if 0
+static MMAL_BOOL_T vcz_pool_release_cb(MMAL_POOL_T * buf_pool, MMAL_BUFFER_HEADER_T *buf, void *userdata)
+{
+ picpool_ctl_t * const pc = userdata;
+ vzc_subbuf_ent_t * const sb = buf->user_data;
+
+ VLC_UNUSED(buf_pool);
+
+// printf("<<< %s\n", __func__);
+
+ if (sb != NULL) {
+ buf->user_data = NULL;
+ pool_recycle(pc, sb->ent);
+ picpool_release(pc);
+ free(sb);
+ }
+
+// printf(">>> %s\n", __func__);
+
+ return MMAL_TRUE;
+}
+#endif
+
+picpool_ctl_t * picpool_new(struct dmabufs_ctl * dbsc)
+{
+ picpool_ctl_t * pc;
+
+ if (dbsc == NULL)
+ return NULL;
+
+ pc = calloc(1, sizeof(*pc));
+ if (pc == NULL)
+ return NULL;
+
+ pc->max_n = 8;
+ pc->dbsc = dmabufs_ctl_ref(dbsc);
+ atomic_store(&pc->ref_count, 1);
+
+ return pc;
+}
diff --git a/modules/video_output/wayland/picpool.h b/modules/video_output/wayland/picpool.h
new file mode 100644
index 0000000000..d188c8b416
--- /dev/null
+++ b/modules/video_output/wayland/picpool.h
@@ -0,0 +1,24 @@
+#ifndef _WAYLAND_PICPOOL_H
+#define _WAYLAND_PICPOOL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct dmabuf_h;
+struct dmabufs_ctl;
+struct picpool_ctl_s;
+typedef struct picpool_ctl_s picpool_ctl_t;
+
+struct dmabuf_h * picpool_get(picpool_ctl_t * const pc, size_t req_size);
+
+void picpool_flush(picpool_ctl_t * const pc);
+void picpool_unref(picpool_ctl_t ** const pppc);
+picpool_ctl_t * picpool_ref(picpool_ctl_t * const pc);
+picpool_ctl_t * picpool_new(struct dmabufs_ctl * dbsc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/modules/video_output/wayland/rgba_premul.c b/modules/video_output/wayland/rgba_premul.c
new file mode 100644
index 0000000000..a60bf78a1c
--- /dev/null
+++ b/modules/video_output/wayland/rgba_premul.c
@@ -0,0 +1,243 @@
+#include <stdint.h>
+
+#ifdef MAKE_TEST
+#define vlc_CPU_ARM64_NEON() (1)
+#define HAVE_AARCH64_ASM 1
+#else
+#include <vlc_common.h>
+#include <vlc_cpu.h>
+#endif
+
+#include "rgba_premul.h"
+
+#ifdef HAVE_AARCH64_ASM
+#include "rgba_premul_aarch64.h"
+#endif
+
+// x, y src offset, not dest
+// This won't be bit exact with aarch64 asm which has slightly different
+// rounding (this is faster when done in C)
+static void
+copy_xxxa_with_premul_c(void * restrict dst_data, int dst_stride,
+ const void * restrict src_data, int src_stride,
+ const unsigned int w, const unsigned int h,
+ const unsigned int global_alpha)
+{
+ uint8_t * dst = (uint8_t*)dst_data;
+ const uint8_t * src = (uint8_t*)src_data;
+ const int src_inc = src_stride - (int)w * 4;
+ const int dst_inc = dst_stride - (int)w * 4;
+
+ for (unsigned int i = 0; i != h; ++i)
+ {
+ for (unsigned int j = 0; j != w; ++j, src+=4, dst += 4)
+ {
+ unsigned int a = src[3] * global_alpha * 258;
+ const unsigned int k = 0x800000;
+ dst[0] = (src[0] * a + k) >> 24;
+ dst[1] = (src[1] * a + k) >> 24;
+ dst[2] = (src[2] * a + k) >> 24;
+ dst[3] = (src[3] * global_alpha * 257 + 0x8000) >> 16;
+ }
+ src += src_inc;
+ dst += dst_inc;
+ }
+}
+
+void
+copy_xxxa_with_premul(void * dst_data, int dst_stride,
+ const void * src_data, int src_stride,
+ const unsigned int w, const unsigned int h,
+ const unsigned int global_alpha)
+{
+#ifdef HAVE_AARCH64_ASM
+ if (vlc_CPU_ARM64_NEON())
+ copy_xxxa_with_premul_aarch64(dst_data, dst_stride, src_data, src_stride, w, h, global_alpha);
+ else
+#endif
+ copy_xxxa_with_premul_c(dst_data, dst_stride, src_data, src_stride, w, h, global_alpha);
+}
+
+// Has the optimization of copying as a single lump if strides are the same
+// and the width is fairly close to the stride
+// at the expense of possibly overwriting some bytes outside the active area
+// (but within the frame)
+void
+copy_frame_xxxa_with_premul(void * dst_data, int dst_stride,
+ const void * src_data, int src_stride,
+ const unsigned int w, const unsigned int h,
+ const unsigned int global_alpha)
+{
+ if (dst_stride == src_stride && (dst_stride & 3) == 0 && (int)w * 4 <= dst_stride && (int)w * 4 + 64 >= dst_stride)
+ copy_xxxa_with_premul(dst_data, dst_stride, src_data, src_stride, h * dst_stride / 4, 1, global_alpha);
+ else
+ copy_xxxa_with_premul(dst_data, dst_stride, src_data, src_stride, w, h, global_alpha);
+}
+
+//============================================================================
+#ifdef MAKE_TEST
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+static bool verbose = false;
+static bool checkfail = false;
+
+static uint64_t
+utime(void)
+{
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ts.tv_nsec / 1000 + (uint64_t)ts.tv_sec * 1000000;
+}
+
+// What the ASM is meant to do exactly
+static void
+copy_xxxa_with_premul_c_asm(void * restrict dst_data, int dst_stride,
+ const void * restrict src_data, int src_stride,
+ const unsigned int w, const unsigned int h,
+ const uint8_t global_alpha)
+{
+ uint8_t * dst = (uint8_t*)dst_data;
+ const uint8_t * src = (uint8_t*)src_data;
+ const int src_inc = src_stride - (int)w * 4;
+ const int dst_inc = dst_stride - (int)w * 4;
+
+ for (unsigned int i = 0; i != h; ++i)
+ {
+ for (unsigned int j = 0; j != w; ++j, src+=4, dst += 4)
+ {
+ // What the ASM is meant to do exactly
+ unsigned int a = global_alpha * 257;
+ const unsigned int k = 0x800000;
+ dst[0] = (((src[0] * src[3] * 257) >> 8) * a + k) >> 24;
+ dst[1] = (((src[1] * src[3] * 257) >> 8) * a + k) >> 24;
+ dst[2] = (((src[2] * src[3] * 257) >> 8) * a + k) >> 24;
+ dst[3] = (src[3] * global_alpha * 257 + 0x8000) >> 16;
+ }
+ src += src_inc;
+ dst += dst_inc;
+ }
+}
+
+#define ALIGN_SIZE 128
+#define ALIGN_PTR(p) ((uint8_t*)(((uintptr_t)p + (ALIGN_SIZE -1)) & ~(ALIGN_SIZE - 1)))
+
+static void
+timetest(const unsigned int w, const unsigned int h, const int stride, bool use_c)
+{
+ uint64_t now;
+ uint64_t done;
+ size_t dsize = h * stride + ALIGN_SIZE;
+
+ uint8_t * src = malloc(dsize);
+ uint8_t * dst = malloc(dsize);
+ uint8_t * s = ALIGN_PTR(src);
+ uint8_t * d = ALIGN_PTR(dst);
+
+ memset(src, 0x80, dsize);
+ memset(dst, 0xff, dsize);
+
+ now = utime();
+ for (unsigned int i = 0; i != 10; ++i)
+ {
+ if (use_c)
+ copy_xxxa_with_premul_c(d, stride, s, stride, w, h, 0xba);
+ else
+ copy_xxxa_with_premul(d, stride, s, stride, w, h, 0xba);
+ }
+ done = utime();
+
+ printf("Time %3s: %dx%d stride %d: %6dus\n", use_c ? "C" : "Asm", w, h, stride,
+ (int)((done - now)/10));
+
+ free(src);
+ free(dst);
+}
+
+static int
+docheck(const uint8_t * const a, const uint8_t * const b, const size_t n)
+{
+ int t = 0;
+
+ if (!verbose)
+ return memcmp(a, b, n);
+
+ for (size_t i = 0; i != n && t < 128; ++i)
+ {
+ if (a[i] != b[i])
+ {
+ printf("@ %zd: %02x %02x\n", i, a[i], b[i]);
+ ++t;
+ }
+ }
+ return t;
+}
+
+static void
+checktest(const unsigned int w, const unsigned int h, const int stride, const int offset)
+{
+ size_t dsize = ((h + 3) * stride + ALIGN_SIZE);
+
+ uint8_t * src = malloc(dsize);
+ uint8_t * dst = malloc(dsize);
+ uint8_t * dst2 = malloc(dsize);
+ uint8_t * s = ALIGN_PTR(src + stride);
+ uint8_t * d = ALIGN_PTR(dst + stride);
+ uint8_t * d2 = ALIGN_PTR(dst2 + stride);
+
+ for (unsigned int i = 0; i != dsize; ++i)
+ src[i] = rand();
+
+ memset(dst2, 0xff, dsize);
+ memset(dst, 0xff, dsize);
+
+ copy_xxxa_with_premul_c_asm(d + offset, stride, s, stride, w, h, 0xba);
+ copy_xxxa_with_premul(d2 + offset, stride, s, stride, w, h, 0xba);
+
+ if (docheck(d - stride, d2 - stride, (h + 2) * stride) != 0)
+ {
+ printf("Check: %dx%d stride %d offset %d: FAIL\n", w, h, stride, offset);
+ checkfail = true;
+ }
+ else if (verbose)
+ {
+ printf("Check: %dx%d stride %d offset %d: ok\n", w, h, stride, offset);
+ }
+
+ free(src);
+ free(dst);
+ free(dst2);
+}
+
+int
+main (int argc, char *argv[])
+{
+ if (argc >= 2 && strcmp(argv[1], "-v") == 0)
+ verbose = true;
+
+ timetest(1920, 1080, 1920 * 4, true);
+ timetest(1920, 1080, 1920 * 4, false);
+ timetest(1917, 1080, 1920 * 4, false);
+ timetest(1917, 1080, 1917 * 4, false);
+ timetest(1920 * 1080, 1, 1920 * 1080 * 4, false);
+
+ checktest(1920, 1080, 1920 * 4, 0);
+
+ // Stride of 65pel will rotate alignment vertically
+ for (unsigned int i = 1; i != 64; ++i)
+ {
+ checktest(i, 32, 65 * 4, 0);
+ }
+
+ if (!checkfail)
+ {
+ printf("All chacks passed\n");
+ }
+
+ return checkfail;
+}
+
+#endif
diff --git a/modules/video_output/wayland/rgba_premul.h b/modules/video_output/wayland/rgba_premul.h
new file mode 100644
index 0000000000..14edfcd173
--- /dev/null
+++ b/modules/video_output/wayland/rgba_premul.h
@@ -0,0 +1,18 @@
+#ifndef _WAYLAND_RGBA_PREMUL_H
+#define _WAYLAND_RGBA_PREMUL_H
+
+void copy_xxxa_with_premul(void * dst_data, int dst_stride,
+ const void * src_data, int src_stride,
+ const unsigned int w, const unsigned int h,
+ const unsigned int global_alpha);
+
+// Has the optimization of copying as a single lump if strides are the same
+// and the width is fairly close to the stride
+// at the expense of possibly overwriting some bytes outside the active area
+// (but within the frame)
+void copy_frame_xxxa_with_premul(void * dst_data, int dst_stride,
+ const void * src_data, int src_stride,
+ const unsigned int w, const unsigned int h,
+ const unsigned int global_alpha);
+
+#endif
diff --git a/modules/video_output/wayland/rgba_premul_aarch64.S b/modules/video_output/wayland/rgba_premul_aarch64.S
new file mode 100644
index 0000000000..7e2ed01503
--- /dev/null
+++ b/modules/video_output/wayland/rgba_premul_aarch64.S
@@ -0,0 +1,170 @@
+#include "../../arm_neon/asm.S"
+
+// copy_xxxa_with_premul(
+// void * dst_data, // x0
+// int dst_stride, // w1
+// const void * src_data, // x2
+// int src_stride, // w3
+// unsigned int w, // w4
+// unsigned int h, // w5
+// unsigned int global_alpha) // w6
+
+// rC R, G or B In/Out
+// rA A In
+// sG Global alpha *257 as h scalar (v7.h[1]) In
+// rT1 tmp
+// rT2 tmp
+//
+// vC = (vC * vA * 257/256 * sG + 0x800000) >> 24
+.macro mul_rgb vC, vA, sG, vT1, vT2
+
+ umull \vT2\().8h, \vC\().8b, \vA\().8b
+ umull2 \vT1\().8h, \vC\().16b, \vA\().16b
+
+ usra \vT2\().8h, \vT2\().8h, #8
+ usra \vT1\().8h, \vT1\().8h, #8
+
+ umull \vC\().4s, \vT2\().4h, \sG
+ umull2 \vT2\().4s, \vT2\().8h, \sG
+ uzp2 \vC\().8h, \vC\().8h, \vT2\().8h
+
+ umull \vT2\().4s, \vT1\().4h, \sG
+ umull2 \vT1\().4s, \vT1\().8h, \sG
+ uzp2 \vT2\().8h, \vT2\().8h, \vT1\().8h
+
+ uqrshrn \vC\().8b, \vC\().8h, #8
+ uqrshrn2 \vC\().16b, \vT2\().8h, #8
+.endm
+
+// rA A In/Out
+// vG Global alpha duped bytewise into vector In
+// rT1 tmp
+//
+// vA = (vA * vG *257/256 + 0x80) >> 8
+.macro mul_a vA, vG, vT1, vT2
+ umull \vT1\().8h, \vA\().8b, \vG\().8b // Alpha
+ umull2 \vT2\().8h, \vA\().16b, \vG\().16b
+ usra \vT1\().8h, \vT1\().8h, #8 // * 257/256
+ usra \vT2\().8h, \vT2\().8h, #8
+ uqrshrn \vA\().8b, \vT1\().8h, #8
+ uqrshrn2 \vA\().16b, \vT2\().8h, #8
+.endm
+
+function copy_xxxa_with_premul_aarch64
+ mov x15, lr // Stash return addr
+ // Sanity check w & h
+ cbz w4, 90f
+ cbz w5, 90f
+
+ // Put alpha values somewhere we can use them
+ mov w7, #257 // Would like 258 but 258*255 overflows h
+ mul w7, w7, w6
+ dup v6.16b, w6
+ mov v7.h[1], w7
+
+ // Calc EOL stride add
+ sub w1, w1, w4, LSL #2
+ sub w3, w3, w4, LSL #2
+
+ // Deal with very short stuff separately
+ // Saves annoying conditionals later
+ cmp w4, #16
+ bge 22f
+
+ // Loop for w < 16
+ mov w6, w4
+1:
+ bl 50f
+ add x2, x2, w3, SXTW
+ add x0, x0, w1, SXTW
+ subs w5, w5, #1
+ bne 1b
+ b 90f
+
+ // Top of height loop
+20:
+ add x2, x2, w3, SXTW
+ add x0, x0, w1, SXTW
+ subs w5, w5, #1
+ beq 90f
+
+22:
+ // Align destination before main loop
+ tst x0, #63
+ mov w6, w4
+ beq 1f
+
+ mov w7, #16
+ ubfm w6, w0, #2, #5
+ sub w6, w7, w6
+ bl 50f
+ sub w6, w4, w6
+1:
+ // If w % 16 != 0 then -16 so the main loop runs 1 fewer times with
+ // the remainder done in the tail
+ tst w6, #15
+ bne 15f
+
+ // Top of width loop
+10:
+ ld4 {v0.16b, v1.16b, v2.16b, v3.16b}, [x2], #64
+
+ mul_rgb v0, v3, v7.h[1], v16, v17
+ mul_rgb v1, v3, v7.h[1], v16, v17
+ mul_rgb v2, v3, v7.h[1], v16, v17
+ mul_a v3, v6, v16, v17
+
+ st4 {v0.16b, v1.16b, v2.16b, v3.16b}, [x0], #64
+
+15:
+ subs w6, w6, #16
+ bgt 10b
+ beq 20b // No tail
+
+ // Tail
+ bl 50f
+ b 20b
+
+ // Return
+90:
+ mov lr, x15
+ ret
2023-03-28 20:02:15 +00:00
+
+// Tail & Head core
+//
+// w6 Noof pixels to convert
+// Only bottom 3 bits considered, left unchanged on exit
+50:
+ tbz w6, #3, 1f
+ ld4 {v0.8b, v1.8b, v2.8b, v3.8b}, [x2], #32
+1: tbz w6, #2, 1f
+ ld4 {v0.b, v1.b, v2.b, v3.b}[8], [x2], #4
+ ld4 {v0.b, v1.b, v2.b, v3.b}[9], [x2], #4
+ ld4 {v0.b, v1.b, v2.b, v3.b}[10], [x2], #4
+ ld4 {v0.b, v1.b, v2.b, v3.b}[11], [x2], #4
+1: tbz w6, #1, 1f
+ ld4 {v0.b, v1.b, v2.b, v3.b}[12], [x2], #4
+ ld4 {v0.b, v1.b, v2.b, v3.b}[13], [x2], #4
+1: tbz w6, #0, 1f
+ ld4 {v0.b, v1.b, v2.b, v3.b}[14], [x2], #4
+1:
2023-03-28 20:02:15 +00:00
+
+ mul_rgb v0, v3, v7.h[1], v16, v17
+ mul_rgb v1, v3, v7.h[1], v16, v17
+ mul_rgb v2, v3, v7.h[1], v16, v17
+ mul_a v3, v6, v16, v17
+
+ tbz w6, #3, 1f
+ st4 {v0.8b, v1.8b, v2.8b, v3.8b}, [x0], #32
+1: tbz w6, #2, 1f
+ st4 {v0.b, v1.b, v2.b, v3.b}[8], [x0], #4
+ st4 {v0.b, v1.b, v2.b, v3.b}[9], [x0], #4
+ st4 {v0.b, v1.b, v2.b, v3.b}[10], [x0], #4
+ st4 {v0.b, v1.b, v2.b, v3.b}[11], [x0], #4
+1: tbz w6, #1, 1f
+ st4 {v0.b, v1.b, v2.b, v3.b}[12], [x0], #4
+ st4 {v0.b, v1.b, v2.b, v3.b}[13], [x0], #4
+1: tbz w6, #0, 1f
+ st4 {v0.b, v1.b, v2.b, v3.b}[14], [x0], #4
+1:
+ ret
diff --git a/modules/video_output/wayland/rgba_premul_aarch64.h b/modules/video_output/wayland/rgba_premul_aarch64.h
new file mode 100644
index 0000000000..829a5d1014
--- /dev/null
+++ b/modules/video_output/wayland/rgba_premul_aarch64.h
@@ -0,0 +1,9 @@
+#ifndef _WAYLAND_RGBA_PREMUL_AARCH64_H
+#define _WAYLAND_RGBA_PREMUL_AARCH64_H
+
+void copy_xxxa_with_premul_aarch64(void * dst_data, int dst_stride,
+ const void * src_data, int src_stride,
+ const unsigned int w, const unsigned int h,
+ const unsigned int global_alpha);
+
+#endif
diff --git a/modules/video_output/wayland/shm.c b/modules/video_output/wayland/shm.c
index 08ad5022f9..b1feb622d8 100644
--- a/modules/video_output/wayland/shm.c
+++ b/modules/video_output/wayland/shm.c
@@ -509,7 +509,7 @@ vlc_module_begin()
set_description(N_("Wayland shared memory video output"))
set_category(CAT_VIDEO)
set_subcategory(SUBCAT_VIDEO_VOUT)
- set_capability("vout display", 170)
+ set_capability("vout display", 0)
set_callbacks(Open, Close)
add_shortcut("wl")
vlc_module_end()
diff --git a/pi-util/README.txt b/pi-util/README.txt
new file mode 100644
index 0000000000..59a8655955
--- /dev/null
+++ b/pi-util/README.txt
@@ -0,0 +1,100 @@
+Release notes
+=============
+
+This version should run with gpu-mem=64 with the default switches. Having
+said that this will only allow for 1 stream. If you are playing >1 stream
+(even transiently) then you will need more (say gpu_mem=128) and you will
+need to set the --mmal-decoders option to the desired max number. The code
+should give up cleanly if it cannot allocate a h/w video decoder and give
+the stream to old-style ffmpeg decode, but as it stands in many cases it
+thinks it has allocated a decoder cleanly only to find that it fails when
+it tries to use it.
+
+Needs firmware from "Sep 13 2016 17:01:56" or later to work properly
+("vcgencmd version" will give the date).
+
+There are a few command-line switches - in general you shouldn't use
+them!
+
+
+Decode and resizer options
+--------------------------
+
+--mmal-decode-opaque Set the decoder to use opaque frames between
+decoder and resizer. This should be faster than i420 but doesn't work
+with old firmware. This is the default with newer firmware (>=
+2016-11-01). (see --mmal-decode-i420)
+
+--mmal-decode-i420 Set the decoder to use I420 frames between
+decoder and resizer. This generates an unnecessary conversion but works
+with all firmware. This is the default with older firmware (<
+2016-11-01). (see --mmal-decode-opaque)
+
+--mmal-low-delay Force "low-delay" mode on the decoder pipe. This
+reduces the number of buffered ES frames before the decoder. It isn't
+exactly low-delay but is definitely lower than otherwise. May have a
+slight performance penalty and increase the risk of stuttering. This mode
+will be automatically set by Chrome for some streams.
+
+--mmal-resize-isp Use ISP resize rather than resizer. Is noticably
+faster but requires --mmal-frame-copy or --mmal-zero-copy and newer
+firmware. This is the default with newer firmware (>= 2016-11-01) and
+enough gpu memory to support --mmal-frame-copy.
+
+--mmal-resize-resizer Use resizer rather than ISP. Slower than ISP
+resize but supports older firmware and --mmal-slice-copy which may be
+needed if GPU memory is very limited (as will be the case on a Pi1 with a
+default setup).
+
+Copy-modes
+----------
+
+These are modes for getting frames out of mmal. Current default is
+--mmal-frame-copy if --mmal-resize-isp is the default resizer or it looks
+like the firmware doesn't support --mmal-slice-copy otherwise
+--mmal-slice-copy is the default. Explicit use of a copy mode option will
+override the default regardless of whether or not we think the firmware
+supports the selected option. Only use one of of these flags.
+
+--mmal-zero-copy Pass gpu frames directly to chrome. Chrome
+buffers some frames and stalls if it doesn't get them. So this option
+needs 6+ gpu frames allocated. This is now a legacy and testing option as
+--mmal-frame-copy is faster and you probably want to have gpu_mem=192 if
+you are going to use it. Default frame-buffers = 6 (8M each)
+
+--mmal-frame-copy Copy frame at a time out of mmal to chrome.
+Currently the fastest option. Needs 2+ gpu frames for plausible
+performance. Default frame-buffers = 2 (8M each). You probably want
+gpu-mem=80 for 1 decoder with this option.
+
+--mmal-slice-copy Copy frames out in 16-line slices. Has the
+lowest memory overhead, but the highest CPU load. If this is selected
+then --mmal-frame-buffers is the number of slice buffers. Default frame
+buffers = 16 (~122k each).
+
+Misc options
+------------
+
+--enable-logging=stderr This is a standard option for chrome but worth
+noting as the mmal code will print out its interpretation of the command
+line options passed to it along with how much GPU memory it has detected
+and the firmware date.
+
+--pi-patch-version Print out the versions of Chromium and Pi
+patches. Chrome will then terminate
+
+--mmal-decoders=<n> Set the number of mmal decoders we wil try to
+create simultainiously. Default=1. If this number is exceeded then decoder
+init will fail and chrome will fallback to ffmpeg decode. There is no
+panalty for setting this to a large number if you wish to have "unlimited"
+decoders. However if it is set too big and there isn't the gpu mem to
+satisfy the requirements of the decode it may fail cleanly and revert to
+software (ffmpeg) decode or init may appear to succeed and decode then
+fails in an undefined manner.
+
+--mmal-frame-buffers=<n> Set the number of gpu "frame" buffers (see
+--mmal-xxx-copy). Change with care.
+
+--mmal-red-pixel Puts a red square in the top left of a frame
+decoded by mmal so you can tell that it is active. Doesn't work if
+zero-copy is set.
diff --git a/pi-util/conf.sh b/pi-util/conf.sh
new file mode 100755
index 0000000000..f3b52b81d2
--- /dev/null
+++ b/pi-util/conf.sh
@@ -0,0 +1,48 @@
+BASE=`pwd`/..
+A=arm-linux-gnueabihf
+TOOLS=$BASE/tools/arm-bcm2708/gcc-arm-8.2-2019.01-x86_64-arm-linux-gnueabihf
+SYSROOT2=$BASE/tools/arm-bcm2708/sysroot-glibc-8.2-2019.01-x86_64-arm-linux-gnueabihf
+#TOOLS=$BASE/tools/arm-bcm2708/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf
+#SYSROOT2=$BASE/tools/arm-bcm2708/sysroot-glibc-linaro-2.23-2017.05-arm-linux-gnueabihf
+SYSROOT=`pwd`/sysroot/raspian_stretch_pi1-sysroot
+HLIB3=/lib/$A
+HLIB1=/usr/lib/$A
+HLIB2=/usr/lib
+HLIB4=/opt/vc/lib
+HLIB5=/usr/local/lib/$A
+
+LIB2=$SYSROOT$HLIB2
+LIB1=$SYSROOT$HLIB1
+LIB3=$SYSROOT$HLIB3
+LIB4=$SYSROOT$HLIB4
+LIB5=$SYSROOT$HLIB5
+
+#INCLUDES="-I$SYSROOT/usr/include -I$SYSROOT/usr/include/$A -I$SYSROOT/opt/vc/include -I$SYSROOT/usr/lib/arm-linux-gnueabihf/dbus-1.0/include -I$SYSROOT/usr/include/libdrm"
+INCLUDES="-I$SYSROOT/usr/include -I$SYSROOT/usr/include/$A -I$SYSROOT/usr/local/include -I$SYSROOT/opt/vc/include -I$SYSROOT/usr/lib/arm-linux-gnueabihf/dbus-1.0/include"
+#DEFINES="-ggdb -D__VCCOREVER__=0x04000000 -DQT_WARNING_DISABLE_DEPRECATED=\"\""
+DEFINES="-ggdb -D__VCCOREVER__=0x04000000"
+ARCH="-march=armv7-a -mfpu=neon-vfpv4"
+PREFIX=$TOOLS/bin/$A-
+
+
+PATH="$TOOLS/bin:$PATH" \
+ PKG_CONFIG="pkg-config --define-prefix" \
+ PKG_CONFIG_PATH="$SYSROOT/usr/lib/pkgconfig:$SYSROOT/usr/local/lib/$A/pkgconfig:$LIB3/pkgconfig" \
+ CC=${PREFIX}gcc \
+ CFLAGS="$ARCH $INCLUDES" \
+ CPP=${PREFIX}cpp \
+ CPPFLAGS="$ARCH $INCLUDES $DEFINES" \
+ CXX=${PREFIX}g++ \
+ CXXFLAGS="$ARCH $INCLUDES" \
+ LDFLAGS="-ggdb -L$TOOLS/lib -L$LIB1 -L$LIB2 -L$LIB3 -L$LIB4 -L$LIB5 -Wl,-rpath=$HLIB1,-rpath-link=$LIB1,-rpath=$HLIB2,-rpath-link=$LIB2,-rpath=$HLIB3,-rpath-link=$LIB3,-rpath=$HLIB4,-rpath-link=$LIB4,-rpath=$HLIB5,-rpath-link=$LIB5,-rpath-link=`pwd`/src/.libs" \
+ MOC="`which moc` -qt=5" \
+ UIC="`which uic` -qt=5" \
+ RCC="`which rcc` -qt=5" \
+ ./configure --host=$A --enable-mmal-avcodec --disable-vdpau --disable-libva --enable-debug --disable-lua --disable-chromecast --disable-wayland --enable-gles2 --disable-opencv --enable-dav1d --disable-aom
+
+# ./configure --host=$A --enable-debug --disable-lua --disable-qt --disable-vdpau --disable-chromecast --disable-wayland --disable-bluray --disable-opencv
+# ./configure --host=$A --enable-debug --disable-wayland
+
+
+
+
diff --git a/pi-util/conf_native.sh b/pi-util/conf_native.sh
new file mode 100755
index 0000000000..54674fa8d0
--- /dev/null
+++ b/pi-util/conf_native.sh
@@ -0,0 +1,96 @@
+set -e
+BASE=`pwd`
+
+DO_BOOTSTRAP=
+DO_MAKE=
+DO_CONFIGURE=1
+
+while [ "$1" != "" ] ; do
+ case $1 in
+ --make)
+ DO_MAKE=1
+ DO_CONFIGURE=
+ ;;
+ --bootstrap)
+ DO_BOOTSTRAP=1
+ ;;
+ *)
+ echo "Usage $0: [--bootstrap] [--make]"
+ echo " bootstrap Do bootstrap before configure"
+ echo " (will always bootstrap if clean)"
+ echo " make Do make after configure"
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+if [ ! -f $BASE/configure ]; then
+ echo "configure not found - will bootstrap"
+ DO_BOOTSTRAP=1
+fi
+
+CONF_MMAL=--disable-mmal
+
+# uname -m gives kernel type which may not have the same
+# 32/64bitness as userspace :-( getconf shoudl provide the answer
+# but use uname to check we are on the right processor
+MC=`uname -m`
+LB=`getconf LONG_BIT`
+if [ "$MC" == "armv7l" ] || [ "$MC" == "aarch64" ]; then
+ if [ "$LB" == "32" ]; then
+ # CONF_MMAL=--enable-mmal-avcodec
+ CONF_MMAL=
+ A=arm-linux-gnueabihf
+ ARM=armv7
+ elif [ "$LB" == "64" ]; then
+ A=aarch64-linux-gnu
+ ARM=arm64
+ else
+ echo "Unknown LONG_BIT name: $LB"
+ exit 1
+ fi
+else
+ echo "Unknown machine name: $MC"
+ exit 1
+fi
+OUT=$BASE/out/$ARM-`lsb_release -sc`-rel
+
+if [ $DO_BOOTSTRAP ]; then
+ echo "==== Bootstrapping & cleaning $OUT"
+ rm -rf $OUT
+ ./bootstrap
+fi
+
+if [ ! -f $OUT/Makefile ]; then
+ DO_CONFIGURE=1
+fi
+
+USR_PREFIX=$OUT/install
+LIB_PREFIX=$USR_PREFIX/lib/$A
+INC_PREFIX=$USR_PREFIX/include/$A
+
+echo "==== Configuring in $OUT"
+mkdir -p $OUT
+# Nothing under here need worry git - including this .gitignore!
+echo "**" > $BASE/out/.gitignore
+
+cd $OUT
+if [ $DO_CONFIGURE ]; then
+ $BASE/configure \
+ --build=$A \
+ --prefix=$USR_PREFIX\
+ --libdir=$LIB_PREFIX\
+ --includedir=$INC_PREFIX\
+ --disable-vdpau\
+ --enable-wayland\
+ --enable-gles2\
+ $CONF_MMAL
+ echo "==== Configured in $OUT"
+fi
+
+if [ $DO_MAKE ]; then
+ echo "==== Making $OUT"
+ make -j8
+ echo "==== Made $OUT"
+fi
diff --git a/pi-util/genpatch.sh b/pi-util/genpatch.sh
new file mode 100755
index 0000000000..13265447cd
--- /dev/null
+++ b/pi-util/genpatch.sh
@@ -0,0 +1,68 @@
+set -e
+
+NOTAG=
+if [ "$1" == "--notag" ]; then
+ shift
+ NOTAG=1
+fi
+
+if [ "$1" == "" ]; then
+ echo Usage: $0 [--notag] \<patch_tag\>
+ echo e.g.: $0 mmal_4
+ exit 1
+fi
+REF=$1
+
+VERSION=`awk '/AC_INIT/{match($0,/[0-9]+(\.[0-9]+)+/);print substr($0,RSTART,RLENGTH)}' configure.ac`
+if [ "$VERSION" == "" ]; then
+ echo Can\'t find version in configure.ac
+ exit 1
+fi
+
+if [ $NOTAG ]; then
+ echo Not tagged
+else
+ # Only continue if we are all comitted
+ git diff --name-status --exit-code
+
+ PATCHTAG=pi/$VERSION/$REF
+ echo Tagging: $PATCHTAG
+
+ git tag $PATCHTAG
+fi
+
+DSTDIR=..
+PATCHNAME=vlc-$VERSION-$REF
+DIFFBASE=$DSTDIR/$PATCHNAME
+ZIPNAME=$PATCHNAME-patch.zip
+
+# We seem to sometimes gain add
+echo Generating patches in: $DSTDIR/$ZIPNAME
+REFNAME=refs/tags/$VERSION
+git diff $REFNAME -- \
+ configure.ac \
+ include \
+ modules/Makefile.am \
+ modules/audio_filter \
+ modules/audio_output \
+ modules/codec \
+ modules/gui/qt/qt.cpp \
+ modules/hw/drm \
+ modules/hw/mmal \
+ modules/video_output/Makefile.am \
+ modules/video_output/drmu \
+ modules/video_output/opengl \
+ modules/video_output/wayland \
+ src/audio_output \
+ src/input \
+ src/misc \
+ > $DIFFBASE-001-rpi.patch
+git diff $REFNAME -- modules/video_chroma/chain.c > $DIFFBASE-002-chain.patch
+git diff $REFNAME -- bin/vlc.c > $DIFFBASE-003-vlc.patch
+git diff $REFNAME -- modules/video_output/caca.c > $DIFFBASE-004-caca.patch
+git diff $REFNAME -- modules/gui/qt/components/interface_widgets.* > $DIFFBASE-005-qt-wayland.patch
+cd $DSTDIR
+zip -m $ZIPNAME $PATCHNAME-*.patch
+
+#echo Copying patch to arm-build
+#scp $PATCHFILE john@arm-build:patches/0002-vlc-3.0.6-mmal_test_4.patch
diff --git a/pi-util/host-install-dev.sh b/pi-util/host-install-dev.sh
new file mode 100755
index 0000000000..e4706312ce
--- /dev/null
+++ b/pi-util/host-install-dev.sh
@@ -0,0 +1,4 @@
+lua5.2
+protobuf-compiler
+# Unsure exactly which gt5 targets are needed ??libqt5-dev-bin
+sudo apt install qt5-default
diff --git a/pi-util/mmvlc.sh b/pi-util/mmvlc.sh
new file mode 100755
index 0000000000..a5f503d88a
--- /dev/null
+++ b/pi-util/mmvlc.sh
@@ -0,0 +1,8 @@
+DSTBASE=/usr/lib/arm-linux-gnueabihf
+DSTPLUGINS=$DSTBASE/vlc/plugins
+DSTDIR=$DSTPLUGINS/mmal
+sudo mkdir -p $DSTDIR
+sudo cp modules/hw/mmal/.libs/*.so $DSTDIR/
+#sudo cp modules/.libs/libxcb_x11_plugin.so $DSTPLUGINS/video_output/
+#sudo cp src/.libs/libvlccore.so.9.0.0 $DSTBASE/
+vlc --no-plugins-cache $*
diff --git a/pi-util/pi-install-dev.sh b/pi-util/pi-install-dev.sh
new file mode 100755
index 0000000000..e99f872065
--- /dev/null
+++ b/pi-util/pi-install-dev.sh
@@ -0,0 +1,75 @@
+# Install set to build appropriate root on a clean pi
+
+sudo apt-get install \
+ libprotobuf-dev \
+ libepoxy-dev \
+ libavutil-dev \
+ libavcodec-dev \
+ libavformat-dev \
+ libswscale-dev \
+ libva-dev \
+ libpostproc-dev \
+ libtwolame-dev \
+ liba52-dev \
+ libflac-dev \
+ libmpeg2-4-dev \
+ libass-dev \
+ libaribb24-dev \
+ libzvbi-dev \
+ libkate-dev \
+ libogg-dev \
+ libdca-dev \
+ libxcb-keysyms1-dev \
+ libsdl2-dev \
+ librsvg2-dev \
+ libsystemd-dev \
+ libarchive-dev \
+ libnfs-dev \
+ libssh2-1-dev \
+ libopencv-dev \
+ libsmbclient-dev \
+ libmodplug-dev \
+ libshine-dev \
+ libvorbis-dev \
+ libxml2-dev \
+comerr-dev \
+libasound2-dev \
+libatk1.0-dev \
+libcap-dev \
+libcups2-dev \
+libexif-dev \
+libffi-dev \
+libgconf2-dev \
+libgl1-mesa-dev \
+libgnome-keyring-dev \
+libgnutls28-dev \
+libidn11-dev \
+libjpeg-dev \
+libkrb5-dev \
+libnspr4-dev \
+libnss3-dev \
+libpam0g-dev \
+libpango1.0-dev \
+libpci-dev \
+libpcre3-dev \
+libssl-dev \
+libudev-dev \
+libx11-dev \
+libx11-xcb-dev \
+libxcb1-dev \
+libxcb-shm0-dev \
+libxcb-composite0-dev \
+libxcb-xv0-dev \
+libxss-dev \
+libxt-dev \
+libxtst-dev \
+mesa-common-dev
+
+# Pulse (hopefully) disabled
+# libpulse-dev \
+
+# Obviously replace paths appropriately below
+# Now run pi-util/syncroot.sh on the compile m/c to grab the appropriate
+# bits of the root and fix up the paths.
+# e.g. ON COMPILE M/C in src dir:
+# pi-util/syncroot.sh my-pi: raspian_jessie_pi1
diff --git a/pi-util/rebase_liblinks.py b/pi-util/rebase_liblinks.py
new file mode 100755
index 0000000000..1f143ed2f6
--- /dev/null
+++ b/pi-util/rebase_liblinks.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+
+import os, sys
+from stat import *
+
+def walktree(top, callback, n, prefix):
+ '''recursively descend the directory tree rooted at top,
+ calling the callback function for each regular file'''
+
+ for f in os.listdir(top):
+ pathname = os.path.join(top, f)
+ mode = os.lstat(pathname).st_mode
+ if S_ISDIR(mode):
+ # It's a directory, recurse into it
+ walktree(pathname, callback, n+1, prefix)
+ elif S_ISLNK(mode):
+ # It's a file, call the callback function
+ callback(pathname, os.readlink(pathname), n, prefix)
+
+def visitfile(file, linkname, n, prefix):
+ if (linkname.startswith(prefix + 'lib/')):
+ newlink = "../" * n + linkname[len(prefix):]
+ print('relinking', file, "->", newlink)
+ os.remove(file)
+ os.symlink(newlink, file)
+
+if __name__ == '__main__':
+ argc = len(sys.argv)
+ if argc == 2:
+ walktree(sys.argv[1], visitfile, 0, "/")
+ elif argc == 3:
+ walktree(sys.argv[1], visitfile, 0, sys.argv[2])
+ else:
+ print("rebase_liblinks.py <local root> [<old sysroot>]")
+
+
+
diff --git a/pi-util/syncroot.sh b/pi-util/syncroot.sh
new file mode 100755
index 0000000000..aedaa28323
--- /dev/null
+++ b/pi-util/syncroot.sh
@@ -0,0 +1,51 @@
+set -e
+
+if [ "$1" == "" ]; then
+ echo Usage: $0 \<src_dir\> [\<rootname\>]
+ echo src_dir is a source for rsync so may contain m/c name.
+ echo rootname will be set to \"raspian_stretch_pi1\" if missing
+ echo e.g.: pi-util/syncroot.sh my-pi: raspian_stretch_pi1
+ exit 1
+fi
+
+SYSROOT_NAME=$2
+if [ "$SYSROOT_NAME" == "" ]; then
+ SYSROOT_NAME=raspian_stretch_pi1
+fi
+
+DST_ROOT=`pwd`
+DST=$DST_ROOT/sysroot/$SYSROOT_NAME-sysroot
+SRC=$1
+
+RPI_BASE=$DST_ROOT/..
+TOOL_BASE=$RPI_BASE/tools
+#FIRMWARE_BASE=$RPI_BASE/firmware4
+
+echo Sync src: $SRC
+echo Sync dest: $DST
+
+mkdir -p $DST/lib
+mkdir -p $DST/opt
+mkdir -p $DST/usr/share
+mkdir -p $DST/usr/local/include
+mkdir -p $DST/usr/local/lib
+
+#ln -sf $FIRMWARE_BASE/opt/vc $DST/opt
+rsync -rl $SRC/opt/vc $DST/opt
+rsync -rl $SRC/lib/arm-linux-gnueabihf $DST/lib
+rsync -rl --exclude "*/cups/backend/*" $SRC/usr/lib $DST/usr
+rsync -rl $SRC/usr/include $DST/usr
+rsync -rl $SRC/usr/share/pkgconfig $DST/usr/share
+rsync -rl $SRC/usr/local/include $DST/usr/local
+rsync -rl $SRC/usr/local/lib $DST/usr/local
+
+rm $DST/usr/lib/arm-linux-gnueabihf/libpthread*
+rm $DST/usr/lib/arm-linux-gnueabihf/libc.*
+
+PUSHDIR=`pwd`
+cd $DST/usr/lib/pkgconfig
+ln -sf ../arm-linux-gnueabihf/pkgconfig/* .
+cd $PUSHDIR
+pi-util/rebase_liblinks.py $DST
+
+
diff --git a/src/audio_output/dec.c b/src/audio_output/dec.c
index 7eca0de2fd..67aad79855 100644
--- a/src/audio_output/dec.c
+++ b/src/audio_output/dec.c
@@ -217,20 +217,27 @@ static void aout_DecSilence (audio_output_t *aout, vlc_tick_t length, vlc_tick_t
{
aout_owner_t *owner = aout_owner (aout);
const audio_sample_format_t *fmt = &owner->mixer_format;
- size_t frames = (fmt->i_rate * length) / CLOCK_FREQ;
-
- block_t *block = block_Alloc (frames * fmt->i_bytes_per_frame
- / fmt->i_frame_length);
- if (unlikely(block == NULL))
- return; /* uho! */
-
- msg_Dbg (aout, "inserting %zu zeroes", frames);
- memset (block->p_buffer, 0, block->i_buffer);
- block->i_nb_samples = frames;
- block->i_pts = pts;
- block->i_dts = pts;
- block->i_length = length;
- aout_OutputPlay (aout, block);
+ size_t frame_count = (fmt->i_rate * length) / CLOCK_FREQ;
+
+ msg_Dbg (aout, "inserting %zu zero frames", frame_count);
+ while (frame_count != 0)
+ {
+ // Block into something that has a multiple of 3 in case this is spdif
+ // and we are going to substitute with pause blocks with a rep count of 3
+ const size_t frames = frame_count > 3072 ? 3072 : frame_count;
+ block_t *block = block_Alloc((size_t)((uint_fast64_t)frames * fmt->i_bytes_per_frame
+ / fmt->i_frame_length));
+ if (unlikely(block == NULL))
+ return; /* uho! */
+
+ memset (block->p_buffer, 0, block->i_buffer);
+ block->i_nb_samples = frames;
+ block->i_pts = pts;
+ block->i_dts = pts;
+ block->i_length = length;
+ aout_OutputPlay (aout, block);
+ frame_count -= frames;
+ }
}
static void aout_DecSynchronize (audio_output_t *aout, vlc_tick_t dec_pts,
diff --git a/src/input/decoder.c b/src/input/decoder.c
index 69488a681c..57858a8020 100644
--- a/src/input/decoder.c
+++ b/src/input/decoder.c
@@ -2000,6 +2000,7 @@ void input_DecoderDelete( decoder_t *p_dec )
vlc_mutex_lock( &p_owner->lock );
p_owner->b_waiting = false;
vlc_cond_signal( &p_owner->wait_request );
+ vlc_mutex_unlock( &p_owner->lock );
/* If the video output is paused or slow, or if the picture pool size was
* under-estimated (e.g. greedy video filter, buggy decoder...), the
@@ -2010,7 +2011,6 @@ void input_DecoderDelete( decoder_t *p_dec )
* worker threads (if any) and the decoder thread to terminate. */
if( p_owner->p_vout != NULL )
vout_Cancel( p_owner->p_vout, true );
- vlc_mutex_unlock( &p_owner->lock );
vlc_join( p_owner->thread, NULL );
diff --git a/src/misc/fourcc.c b/src/misc/fourcc.c
index ebb7707a34..cd202ed74a 100644
--- a/src/misc/fourcc.c
+++ b/src/misc/fourcc.c
@@ -416,6 +416,10 @@ static const vlc_fourcc_t p_D3D11_OPAQUE_10B_fallback[] = {
VLC_CODEC_D3D11_OPAQUE_10B, VLC_CODEC_P010, VLC_CODEC_I420_10L, 0,
};
+static const vlc_fourcc_t p_DRM_PRIME_SAND30_fallback[] = {
+ VLC_CODEC_DRM_PRIME_SAND30, VLC_CODEC_I420_10L, VLC_CODEC_NV12, 0,
+};
+
static const vlc_fourcc_t p_I440_fallback[] = {
VLC_CODEC_I440,
VLC_CODEC_YUV_PLANAR_420,
@@ -506,6 +510,7 @@ static const vlc_fourcc_t *pp_YUV_fallback[] = {
p_D3D9_OPAQUE_10B_fallback,
p_D3D11_OPAQUE_fallback,
p_D3D11_OPAQUE_10B_fallback,
+ p_DRM_PRIME_SAND30_fallback,
NULL,
};
@@ -537,6 +542,15 @@ static const vlc_fourcc_t p_list_YUV[] = {
VLC_CODEC_D3D9_OPAQUE_10B,
VLC_CODEC_D3D11_OPAQUE,
VLC_CODEC_D3D11_OPAQUE_10B,
+ VLC_CODEC_MMAL_OPAQUE,
+ VLC_CODEC_MMAL_ZC_SAND8,
+ VLC_CODEC_MMAL_ZC_SAND10,
+ VLC_CODEC_MMAL_ZC_SAND30,
+ VLC_CODEC_MMAL_ZC_I420,
+ VLC_CODEC_DRM_PRIME_I420,
+ VLC_CODEC_DRM_PRIME_NV12,
+ VLC_CODEC_DRM_PRIME_SAND8,
+ VLC_CODEC_DRM_PRIME_SAND30,
0,
};
2022-12-08 21:49:23 +00:00
@@ -762,11 +776,19 @@ static const struct
{ { VLC_CODEC_VDPAU_VIDEO_420, VLC_CODEC_VDPAU_VIDEO_422,
VLC_CODEC_VDPAU_VIDEO_444, VLC_CODEC_VDPAU_OUTPUT },
FAKE_FMT() },
- { { VLC_CODEC_ANDROID_OPAQUE, VLC_CODEC_MMAL_OPAQUE,
- VLC_CODEC_D3D9_OPAQUE, VLC_CODEC_D3D11_OPAQUE },
+ { { VLC_CODEC_ANDROID_OPAQUE }, FAKE_FMT() },
+ { { VLC_CODEC_MMAL_OPAQUE, VLC_CODEC_MMAL_ZC_SAND30 },
+ FAKE_FMT() },
+ { { VLC_CODEC_MMAL_ZC_I420, VLC_CODEC_MMAL_ZC_SAND8,
+ VLC_CODEC_MMAL_ZC_SAND10, VLC_CODEC_MMAL_ZC_RGB32 },
+ FAKE_FMT() },
+ { { VLC_CODEC_D3D9_OPAQUE, VLC_CODEC_D3D11_OPAQUE },
FAKE_FMT() },
{ { VLC_CODEC_D3D11_OPAQUE_10B, VLC_CODEC_D3D9_OPAQUE_10B },
FAKE_FMT() },
+ { { VLC_CODEC_DRM_PRIME_I420, VLC_CODEC_DRM_PRIME_NV12,
+ VLC_CODEC_DRM_PRIME_SAND8, VLC_CODEC_DRM_PRIME_SAND30 },
+ FAKE_FMT() },
{ { VLC_CODEC_CVPX_NV12, VLC_CODEC_CVPX_UYVY,
VLC_CODEC_CVPX_I420, VLC_CODEC_CVPX_BGRA },
diff --git a/src/misc/picture.c b/src/misc/picture.c
index 892d5b7dac..3c75b6450f 100644
--- a/src/misc/picture.c
+++ b/src/misc/picture.c
@@ -365,10 +365,30 @@ void picture_CopyProperties( picture_t *p_dst, const picture_t *p_src )
p_dst->b_top_field_first = p_src->b_top_field_first;
}
+static inline bool is_zc_chroma(const vlc_fourcc_t i_chroma)
+{
+ return i_chroma == VLC_CODEC_MMAL_OPAQUE ||
+ i_chroma == VLC_CODEC_MMAL_ZC_I420 ||
+ i_chroma == VLC_CODEC_MMAL_ZC_RGB32 ||
+ i_chroma == VLC_CODEC_MMAL_ZC_SAND10 ||
+ i_chroma == VLC_CODEC_MMAL_ZC_SAND30 ||
+ i_chroma == VLC_CODEC_MMAL_ZC_SAND8;
+}
+
void picture_CopyPixels( picture_t *p_dst, const picture_t *p_src )
{
- for( int i = 0; i < p_src->i_planes ; i++ )
- plane_CopyPixels( p_dst->p+i, p_src->p+i );
+ if( is_zc_chroma(p_src->format.i_chroma) )
+ {
+ assert(p_dst->i_planes == 0);
+ p_dst->i_planes = p_src->i_planes;
+ for( int i = 0; i < p_src->i_planes; i++ )
+ p_dst->p[i] = p_src->p[i];
+ }
+ else
+ {
+ for( int i = 0; i < p_src->i_planes; i++ )
+ plane_CopyPixels( p_dst->p+i, p_src->p+i );
+ }
assert( p_dst->context == NULL );