Files
Cameron Gutman 7cf8b46c79 Pass the correct renderer to EGLImageFactory
We should be using the EGL renderer's colorspace and color range
rather than the backend renderer's for fallbacks. The former is
what actually gets passed to the host PC during stream init.
2026-08-06 00:29:53 -05:00

844 lines
31 KiB
C++

#pragma once
#include "renderer.h"
#include "swframemapper.h"
#ifdef HAVE_EGL
#include "eglimagefactory.h"
#endif
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <set>
#include <unordered_map>
#include <mutex>
// This is only defined in Linux 6.8+ headers
#ifndef DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP
#define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15
#endif
// Newer libdrm headers have these HDR structs, but some older ones don't.
namespace DrmDefs
{
// HDR structs is copied from linux include/linux/hdmi.h
struct hdr_metadata_infoframe
{
uint8_t eotf;
uint8_t metadata_type;
struct
{
uint16_t x, y;
} display_primaries[3];
struct
{
uint16_t x, y;
} white_point;
uint16_t max_display_mastering_luminance;
uint16_t min_display_mastering_luminance;
uint16_t max_cll;
uint16_t max_fall;
};
struct hdr_output_metadata
{
uint32_t metadata_type;
union {
struct hdr_metadata_infoframe hdmi_metadata_type1;
};
};
}
class DrmRenderer : public IFFmpegRenderer {
class DrmProperty {
public:
DrmProperty(uint32_t objectId, uint32_t objectType, drmModePropertyPtr prop, uint64_t initialValue) :
m_ObjectId(objectId), m_ObjectType(objectType), m_Prop(prop), m_InitialValue(initialValue) {
for (int i = 0; i < m_Prop->count_enums; i++) {
m_Values.emplace(m_Prop->enums[i].name, m_Prop->enums[i].value);
}
}
~DrmProperty() noexcept {
if (m_Prop) {
drmModeFreeProperty(m_Prop);
}
}
DrmProperty(DrmProperty &&other) = delete;
DrmProperty(const DrmProperty &) = delete;
bool isImmutable() const {
return m_Prop->flags & DRM_MODE_PROP_IMMUTABLE;
}
const char* name() const {
return m_Prop->name;
}
uint32_t id() const {
return m_Prop->prop_id;
}
std::optional<std::pair<uint64_t, uint64_t>> range() const {
if ((m_Prop->flags & DRM_MODE_PROP_RANGE) && m_Prop->count_values == 2) {
return std::make_pair(m_Prop->values[0], m_Prop->values[1]);
}
else {
return std::nullopt;
}
}
std::optional<std::pair<int64_t, int64_t>> srange() const {
if ((m_Prop->flags & DRM_MODE_PROP_SIGNED_RANGE) && m_Prop->count_values == 2) {
return std::make_pair((int64_t)m_Prop->values[0], (int64_t)m_Prop->values[1]);
}
else {
return std::nullopt;
}
}
uint64_t clamp(uint64_t value) const {
if (auto srange = this->srange()) {
return (uint64_t)std::clamp((int64_t)value, srange->first, srange->second);
}
else if (auto range = this->range()) {
return std::clamp(value, range->first, range->second);
}
else {
return value;
}
}
bool containsValue(const std::string &name) const {
return m_Values.find(name) != m_Values.end();
}
std::optional<uint64_t> value(const std::string &name) const {
if (auto it = m_Values.find(name); it != m_Values.end()) {
return it->second;
}
else {
return std::nullopt;
}
}
uint32_t objectId() const {
return m_ObjectId;
}
uint32_t objectType() const {
return m_ObjectType;
}
uint64_t initialValue() const {
return m_InitialValue;
}
private:
uint32_t m_ObjectId;
uint32_t m_ObjectType;
drmModePropertyPtr m_Prop;
std::unordered_map<std::string, uint64_t> m_Values;
uint64_t m_InitialValue;
};
class DrmPropertyMap {
public:
DrmPropertyMap() {}
DrmPropertyMap(int fd, uint32_t objectId, uint32_t objectType) {
SDL_assert(m_ObjectType == 0);
SDL_assert(m_ObjectId == 0);
load(fd, objectId, objectType);
}
DrmPropertyMap(const DrmPropertyMap &) = delete;
DrmPropertyMap(DrmPropertyMap &&other) {
m_Props = std::move(other.m_Props);
m_ObjectId = other.m_ObjectId;
m_ObjectType = other.m_ObjectType;
}
bool isValid() const {
return m_ObjectType != 0;
}
void load(int fd, uint32_t objectId, uint32_t objectType) {
drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(fd, objectId, objectType);
if (props) {
for (uint32_t i = 0; i < props->count_props; i++) {
drmModePropertyPtr prop = drmModeGetProperty(fd, props->props[i]);
if (prop) {
// DrmProperty takes ownership of the drmModePropertyPtr
m_Props.try_emplace(prop->name, objectId, objectType, prop, props->prop_values[i]);
}
}
drmModeFreeObjectProperties(props);
}
m_ObjectId = objectId;
m_ObjectType = objectType;
}
bool hasProperty(const std::string& name) const {
return m_Props.find(name) != m_Props.end();
}
const DrmProperty* property(const std::string& name) const {
auto it = m_Props.find(name);
if (it == m_Props.end()) {
return nullptr;
}
return &it->second;
}
uint32_t objectId() const {
return m_ObjectId;
}
uint32_t objectType() const {
return m_ObjectType;
}
const std::unordered_map<std::string, DrmProperty>& properties() const {
return m_Props;
}
private:
uint32_t m_ObjectId = 0;
uint32_t m_ObjectType = 0;
std::unordered_map<std::string, DrmProperty> m_Props;
};
class DrmPropertySetter {
struct PlaneConfiguration {
uint32_t crtcId;
int32_t crtcX, crtcY;
uint32_t crtcW, crtcH, srcX, srcY, srcW, srcH;
};
struct PlaneBuffer {
uint32_t fbId;
uint32_t dumbBufferHandle;
// Atomic only
uint32_t pendingFbId;
uint32_t pendingDumbBuffer;
uint32_t fbDamageClipsPropId;
std::vector<drm_mode_rect> pendingFbDamageRects;
bool modified;
};
public:
DrmPropertySetter() {}
~DrmPropertySetter() {
for (auto it = m_PlaneBuffers.begin(); it != m_PlaneBuffers.end(); it++) {
SDL_assert(!it->second.fbId);
SDL_assert(!it->second.dumbBufferHandle);
if (it->second.pendingFbId) {
drmModeRmFB(m_Fd, it->second.pendingFbId);
}
if (it->second.pendingDumbBuffer) {
struct drm_mode_destroy_dumb destroyBuf = {};
destroyBuf.handle = it->second.pendingDumbBuffer;
drmIoctl(m_Fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroyBuf);
}
}
if (m_AtomicReq) {
drmModeAtomicFree(m_AtomicReq);
}
if (m_TotalCommits != 0) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Flip stats: %.2f%% async | %.2f%% sync",
(100.f * m_AsyncCommits) / m_TotalCommits,
(100.f * (m_TotalCommits - m_AsyncCommits)) / m_TotalCommits);
}
}
DrmPropertySetter(const DrmPropertySetter &) = delete;
DrmPropertySetter(DrmPropertySetter &&) = delete;
void initialize(int drmFd, bool wantsAtomic, bool wantsAsyncFlip) {
m_Fd = drmFd;
m_Atomic = wantsAtomic && drmSetClientCap(drmFd, DRM_CLIENT_CAP_ATOMIC, 1) == 0;
if (wantsAsyncFlip) {
uint64_t val;
if (!m_Atomic) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"V-sync cannot be disabled due to lack of atomic support");
}
else if (drmGetCap(m_Fd, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, &val) < 0 || !val) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"V-sync cannot be disabled due to lack of atomic async page flip support");
}
else {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Using atomic async page flips with V-sync disabled");
m_AsyncFlip = true;
}
}
}
int set(uint32_t objectId, uint32_t objectType, uint32_t propId, uint64_t value) {
int err;
if (m_Atomic) {
// Synchronize with other threads that might be committing or setting properties
std::lock_guard lg { m_Lock };
if (!m_AtomicReq) {
m_AtomicReq = drmModeAtomicAlloc();
}
err = drmModeAtomicAddProperty(m_AtomicReq, objectId, propId, value);
// This returns the new count of properties on success,
// so normalize it to 0 like drmModeObjectSetProperty()
if (err > 0) {
err = 0;
}
}
else {
err = drmModeObjectSetProperty(m_Fd, objectId, objectType, propId, value);
}
return err;
}
bool set(const DrmProperty& prop, uint64_t value, bool verbose = true) {
int err = set(prop.objectId(), prop.objectType(), prop.id(), value);
if (verbose && err == 0) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Set property '%s': %" PRIu64,
prop.name(),
value);
}
else if (err < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to set property '%s': %d",
prop.name(),
err);
}
return err == 0;
}
bool set(const DrmProperty& prop, const std::string &value) {
std::optional<uint64_t> propValue = prop.value(value);
if (!propValue) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Property '%s' has no supported enum value '%s'",
prop.name(),
value.c_str());
return false;
}
if (set(prop, *propValue, false)) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Set property '%s': %s",
prop.name(),
value.c_str());
return true;
}
else {
return false;
}
}
// Unconditionally takes ownership of fbId and dumbBufferHandle (if present)
// If provided, frame is referenced to keep the FB's backing DMA-BUFs around
bool flipPlane(const DrmPropertyMap& plane, uint32_t fbId, uint32_t dumbBufferHandle) {
bool ret;
if (m_Atomic) {
std::lock_guard lg { m_Lock };
ret = set(*plane.property("FB_ID"), fbId, false);
if (ret) {
// If we updated the FB_ID property, free the old pending buffer.
// Otherwise, we'll free the new buffer which was never used.
auto &pb = m_PlaneBuffers[plane.objectId()];
std::swap(fbId, pb.pendingFbId);
std::swap(dumbBufferHandle, pb.pendingDumbBuffer);
pb.modified = true;
}
}
else {
PlaneConfiguration planeConfig;
{
// Latch the plane configuration and release the lock
std::lock_guard lg { m_Lock };
planeConfig = m_PlaneConfigs.at(plane.objectId());
}
int err = drmModeSetPlane(m_Fd, plane.objectId(),
planeConfig.crtcId, fbId, 0,
planeConfig.crtcX, planeConfig.crtcY,
planeConfig.crtcW, planeConfig.crtcH,
planeConfig.srcX, planeConfig.srcY,
planeConfig.srcW, planeConfig.srcH);
// If we succeeded updating the plane, free the old FB state
// Otherwise, we'll free the new data which was never used.
if (err == 0) {
std::lock_guard lg { m_Lock };
auto &pb = m_PlaneBuffers[plane.objectId()];
std::swap(fbId, pb.fbId);
std::swap(dumbBufferHandle, pb.dumbBufferHandle);
ret = true;
}
else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"drmModeSetPlane() failed: %d",
err);
ret = false;
}
}
// Free the unused resources
if (fbId) {
drmModeRmFB(m_Fd, fbId);
}
if (dumbBufferHandle) {
struct drm_mode_destroy_dumb destroyBuf = {};
destroyBuf.handle = dumbBufferHandle;
drmIoctl(m_Fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroyBuf);
}
return ret;
}
bool configurePlane(const DrmPropertyMap& plane,
uint32_t crtcId,
int32_t crtcX, int32_t crtcY,
uint32_t crtcW, uint32_t crtcH,
uint32_t srcX, uint32_t srcY,
uint32_t srcW, uint32_t srcH) {
bool ret = true;
std::lock_guard lg { m_Lock };
if (m_Atomic) {
ret = ret && set(*plane.property("CRTC_ID"), crtcId, false);
ret = ret && set(*plane.property("CRTC_X"), crtcX, false);
ret = ret && set(*plane.property("CRTC_Y"), crtcY, false);
ret = ret && set(*plane.property("CRTC_W"), crtcW, false);
ret = ret && set(*plane.property("CRTC_H"), crtcH, false);
ret = ret && set(*plane.property("SRC_X"), srcX, false);
ret = ret && set(*plane.property("SRC_Y"), srcY, false);
ret = ret && set(*plane.property("SRC_W"), srcW, false);
ret = ret && set(*plane.property("SRC_H"), srcH, false);
}
else {
auto& planeConfig = m_PlaneConfigs[plane.objectId()];
planeConfig.crtcId = crtcId;
planeConfig.crtcX = crtcX;
planeConfig.crtcY = crtcY;
planeConfig.crtcW = crtcW;
planeConfig.crtcH = crtcH;
planeConfig.srcX = srcX;
planeConfig.srcY = srcY;
planeConfig.srcW = srcW;
planeConfig.srcH = srcH;
}
return ret;
}
void disablePlane(const DrmPropertyMap& plane) {
if (plane.isValid()) {
configurePlane(plane, 0, 0, 0, 0, 0, 0, 0, 0, 0);
flipPlane(plane, 0, 0);
}
}
void restorePlane(const DrmPropertyMap& plane) {
if (!plane.isValid()) {
return;
}
if (isAtomic() && plane.property("FB_ID")->initialValue() != 0) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Restoring previously active plane: %u",
plane.objectId());
{
std::lock_guard lg { m_Lock };
auto &pb = m_PlaneBuffers[plane.objectId()];
// Free any pending buffers first
if (pb.pendingFbId) {
drmModeRmFB(m_Fd, pb.pendingFbId);
pb.pendingFbId = 0;
}
if (pb.pendingDumbBuffer) {
struct drm_mode_destroy_dumb destroyBuf = {};
destroyBuf.handle = pb.pendingDumbBuffer;
drmIoctl(m_Fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroyBuf);
pb.pendingDumbBuffer = 0;
}
// Since we restore the FB_ID, remember that we need to swap
// pending buffers to current after committing in apply().
// This would normally be done in flipPlane(), but we can't
// use that here as it takes ownership of the FB.
//
// Since we cleared our pending buffers, the result is that
// apply() will free the current buffers and believe that no
// buffers are currently set. This will ensure it doesn't
// later free the restored FB_ID (which would happen if we
// used flipPlane() normally).
pb.modified = true;
}
// Restore the old plane properties and FB_ID
restoreToInitial(plane);
}
else {
disablePlane(plane);
}
}
// The damage rect is relative to the FB
void damagePlane(const DrmPropertyMap& plane, const SDL_Rect& rect) {
SDL_assert(m_Atomic);
if (auto prop = plane.property("FB_DAMAGE_CLIPS")) {
std::lock_guard lg { m_Lock };
auto& pb = m_PlaneBuffers[plane.objectId()];
// Append this new damage rect to our existing list
drm_mode_rect drmRect;
drmRect.x1 = rect.x;
drmRect.x2 = rect.x + rect.w;
drmRect.y1 = rect.y;
drmRect.y2 = rect.y + rect.h;
pb.pendingFbDamageRects.emplace_back(std::move(drmRect));
pb.fbDamageClipsPropId = prop->id();
}
}
bool testPlane(const DrmPropertyMap& plane,
uint32_t crtcId, uint32_t fbId,
int32_t crtcX, int32_t crtcY,
uint32_t crtcW, uint32_t crtcH,
uint32_t srcX, uint32_t srcY,
uint32_t srcW, uint32_t srcH)
{
// Normally we wouldn't want to hold this lock across a blocking atomic commit,
// but we do in this case because this operation is infrequent and it significantly
// simplifies our state management to avoid racing atomic commits or modifications
// to our atomic request object. It's also only a test commit, so it should be fast.
std::lock_guard lg { m_Lock };
SDL_assert(m_Atomic);
// Store the old atomic request to restore after testing
drmModeAtomicReqPtr oldReq = nullptr;
std::swap(oldReq, m_AtomicReq);
bool ret = true;
ret = ret && set(*plane.property("CRTC_ID"), crtcId, false);
ret = ret && set(*plane.property("FB_ID"), fbId, false);
ret = ret && set(*plane.property("CRTC_X"), crtcX, false);
ret = ret && set(*plane.property("CRTC_Y"), crtcY, false);
ret = ret && set(*plane.property("CRTC_W"), crtcW, false);
ret = ret && set(*plane.property("CRTC_H"), crtcH, false);
ret = ret && set(*plane.property("SRC_X"), srcX, false);
ret = ret && set(*plane.property("SRC_Y"), srcY, false);
ret = ret && set(*plane.property("SRC_W"), srcW, false);
ret = ret && set(*plane.property("SRC_H"), srcH, false);
if (ret) {
SDL_assert(m_AtomicReq);
int err = drmModeAtomicCommit(m_Fd, m_AtomicReq,
DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET,
nullptr);
if (err < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"drmModeAtomicCommit(DRM_MODE_ATOMIC_TEST_ONLY) failed: %d",
err);
ret = false;
}
}
// Swap the old atomic request back and free the test one
std::swap(oldReq, m_AtomicReq);
if (oldReq) {
drmModeAtomicFree(oldReq);
}
return ret;
}
bool apply() {
if (!m_Atomic) {
return true;
}
drmModeAtomicReqPtr req = nullptr;
std::unordered_map<uint32_t, PlaneBuffer> pendingBuffers;
std::vector<uint32_t> damageBlobIds;
{
std::lock_guard lg { m_Lock };
// Create property blobs for any pending FB damage clip rects
for (auto &[planeId, pb] : m_PlaneBuffers) {
uint32_t damageBlob;
if (!pb.pendingFbDamageRects.empty()) {
int err = drmModeCreatePropertyBlob(m_Fd,
pb.pendingFbDamageRects.data(),
pb.pendingFbDamageRects.size() * sizeof(drm_mode_rect),
&damageBlob);
if (err < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"drmModeCreatePropertyBlob(FB_DAMAGE_CLIPS) failed: %d",
err);
continue;
}
err = set(planeId, DRM_MODE_OBJECT_PLANE, pb.fbDamageClipsPropId, damageBlob);
if (err < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to set property 'FB_DAMAGE_CLIPS': %d",
err);
// Fall-through
}
damageBlobIds.emplace_back(damageBlob);
}
}
// Take ownership of the current atomic request to commit it and
// allow other threads to queue up changes for the next one.
std::swap(req, m_AtomicReq);
std::swap(pendingBuffers, m_PlaneBuffers);
}
if (!req) {
// Nothing to apply
return true;
}
// Try an async flip if requested
int err = drmModeAtomicCommit(m_Fd, req,
m_AsyncFlip ? DRM_MODE_PAGE_FLIP_ASYNC : DRM_MODE_ATOMIC_ALLOW_MODESET,
nullptr);
if (m_AsyncFlip) {
if (err == 0) {
m_AsyncCommits++;
}
else {
// The driver may not support async flips (especially if we changed a non-FB_ID property),
// so try again with a regular flip if we get an error from the async flip attempt.
//
// We pass DRM_MODE_ATOMIC_ALLOW_MODESET because changing HDR state may require a modeset.
err = drmModeAtomicCommit(m_Fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr);
}
}
if (err == 0) {
m_TotalCommits++;
}
else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"drmModeAtomicCommit() failed: %d",
err);
}
// Free the damage clip blobs
for (auto blobId : damageBlobIds) {
drmModeDestroyPropertyBlob(m_Fd, blobId);
}
// Update the buffer state for any modified planes
std::lock_guard lg { m_Lock };
for (auto it = pendingBuffers.begin(); it != pendingBuffers.end(); it++) {
if (err == 0 && it->second.modified) {
if (it->second.fbId) {
drmModeRmFB(m_Fd, it->second.fbId);
it->second.fbId = 0;
}
if (it->second.dumbBufferHandle) {
struct drm_mode_destroy_dumb destroyBuf = {};
destroyBuf.handle = it->second.dumbBufferHandle;
drmIoctl(m_Fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroyBuf);
it->second.dumbBufferHandle = 0;
}
// The pending buffers become the active buffers for this FB
auto &pb = m_PlaneBuffers[it->first];
pb.fbId = it->second.pendingFbId;
pb.dumbBufferHandle = it->second.pendingDumbBuffer;
}
else if (err < 0 || it->second.fbId || it->second.dumbBufferHandle) {
// Free the old pending buffers on a failed commit
if (it->second.pendingFbId) {
SDL_assert(err < 0);
drmModeRmFB(m_Fd, it->second.pendingFbId);
}
if (it->second.pendingDumbBuffer) {
SDL_assert(err < 0);
struct drm_mode_destroy_dumb destroyBuf = {};
destroyBuf.handle = it->second.pendingDumbBuffer;
drmIoctl(m_Fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroyBuf);
}
// This FB wasn't modified in this commit, so the current buffers stay around
auto &pb = m_PlaneBuffers[it->first];
pb.fbId = it->second.fbId;
pb.dumbBufferHandle = it->second.dumbBufferHandle;
}
// NB: We swapped in a new plane buffers map which will clear the modified value.
// It's important that we don't try to clear it here because we might stomp on
// a flipPlane() performed by another thread that queued up another modification.
}
drmModeAtomicFree(req);
return err == 0;
}
bool isAtomic() {
return m_Atomic;
}
void restoreToInitial(const DrmPropertyMap& object) {
SDL_assert(m_Atomic);
// Set all mutable properties back to their initial values
for (auto&[id, prop] : object.properties()) {
restorePropertyToInitial(prop);
}
}
void restorePropertyToInitial(const DrmProperty& prop) {
if (!prop.isImmutable()) {
// We clamp() here because some DRM drivers actually initialize certain
// properties (max bpc) to values outside the legal range. Obviously,
// the kernel rejects these values when we later restore them back.
set(prop, prop.clamp(prop.initialValue()));
}
}
private:
int m_Fd = -1;
bool m_Atomic = false;
bool m_AsyncFlip = false;
std::recursive_mutex m_Lock;
std::unordered_map<uint32_t, PlaneBuffer> m_PlaneBuffers;
// Legacy context
std::unordered_map<uint32_t, PlaneConfiguration> m_PlaneConfigs;
// Atomic context
drmModeAtomicReqPtr m_AtomicReq = nullptr;
std::atomic<uint32_t> m_AsyncCommits {0};
std::atomic<uint32_t> m_TotalCommits {0};
};
public:
DrmRenderer(AVHWDeviceType hwDeviceType = AV_HWDEVICE_TYPE_NONE, IFFmpegRenderer *backendRenderer = nullptr);
virtual ~DrmRenderer() override;
virtual bool initialize(PDECODER_PARAMETERS params) override;
virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override;
virtual bool prepareDecoderContextInGetFormat(AVCodecContext*, AVPixelFormat) override;
virtual void prepareToRender() override;
virtual void cleanupRenderContext() override;
virtual void renderFrame(AVFrame* frame) override;
virtual enum AVPixelFormat getPreferredPixelFormat(int videoFormat) override;
virtual bool isPixelFormatSupported(int videoFormat, AVPixelFormat pixelFormat) override;
virtual int getRendererAttributes() override;
virtual bool testRenderFrame(AVFrame* frame) override;
virtual bool isDirectRenderingSupported() override;
virtual int getDecoderColorspace() override;
virtual int getDecoderColorRange() override;
virtual void setHdrMode(bool enabled) override;
virtual void notifyOverlayUpdated(Overlay::OverlayType type) override;
#ifdef HAVE_EGL
virtual bool canExportEGL() override;
virtual AVPixelFormat getEGLImagePixelFormat() override;
virtual bool initializeEGL(IFFmpegRenderer* eglRenderer, EGLDisplay dpy, const EGLExtensions &ext) override;
virtual ssize_t exportEGLImages(AVFrame *frame, EGLDisplay dpy, EGLImage images[EGL_MAX_PLANES]) override;
#endif
private:
const char* getDrmColorEncodingValue(AVFrame* frame);
const char* getDrmColorRangeValue(AVFrame* frame);
bool mapSoftwareFrame(AVFrame* frame, AVDRMFrameDescriptor* mappedFrame);
bool addFbForFrame(AVFrame* frame, uint32_t* newFbId, bool testMode);
bool uploadSurfaceToFb(SDL_Surface *surface, uint32_t* handle, uint32_t* fbId);
bool mapDumbBuffer(uint32_t handle, size_t size, void** mapping);
bool createFbForDumbBuffer(struct drm_mode_create_dumb* createBuf, uint32_t* fbId);
void enterOverlayCompositionMode();
void blitOverlayToCompositionSurface(Overlay::OverlayType type, SDL_Surface* newSurface, SDL_Rect* overlayRect);
static bool drmFormatMatchesVideoFormat(uint32_t drmFormat, int videoFormat);
IFFmpegRenderer* m_BackendRenderer;
SDL_Window* m_Window;
bool m_DrmPrimeBackend;
AVHWDeviceType m_HwDeviceType;
AVBufferRef* m_HwContext;
int m_DrmFd;
bool m_DrmIsMaster;
bool m_DrmStateModified;
bool m_DrmSupportsModifiers;
bool m_MustCloseDrmFd;
bool m_SupportsDirectRendering;
int m_VideoFormat;
bool m_Vsync;
DrmPropertyMap m_Encoder;
DrmPropertyMap m_Connector;
DrmPropertyMap m_Crtc;
std::unordered_map<uint32_t, DrmPropertyMap> m_UnusedActivePlanes;
DrmPropertyMap m_VideoPlane;
uint64_t m_VideoPlaneZpos;
DrmPropertyMap m_OverlayPlanes[Overlay::OverlayMax];
DrmPropertySetter m_PropSetter;
SDL_Surface* m_OverlayCompositionSurface;
std::mutex m_OverlayLock;
SDL_Rect m_OverlayRects[Overlay::OverlayMax];
drmVersionPtr m_Version;
uint32_t m_HdrOutputMetadataBlobId;
SDL_Rect m_OutputRect;
std::set<uint32_t> m_SupportedVideoPlaneFormats;
static constexpr int k_SwFrameCount = 2;
SwFrameMapper m_SwFrameMapper;
int m_CurrentSwFrameIdx;
struct {
int width;
int height;
uint32_t format;
uint32_t handle;
uint32_t pitch;
uint64_t size;
uint8_t* mapping;
int primeFd;
} m_SwFrame[k_SwFrameCount];
#ifdef HAVE_EGL
EglImageFactory m_EglImageFactory;
#endif
};