This commit is contained in:
Tom Englund 2026-05-06 01:42:56 +09:00 committed by GitHub
commit 4f07bceece
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 230 additions and 164 deletions

View file

@ -148,6 +148,8 @@
---| "ecosystem.enforce_permissions"
---| "ecosystem.no_donation_nag"
---| "ecosystem.no_update_news"
---| "experimental.deadline_client_buffer"
---| "experimental.deadline_main_buffer"
---| "experimental.wp_cm_1_2"
---| "general.allow_tearing"
---| "general.border_size"
@ -984,6 +986,8 @@ hl = {}
---@field ['ecosystem.enforce_permissions'] boolean
---@field ['ecosystem.no_donation_nag'] boolean
---@field ['ecosystem.no_update_news'] boolean
---@field ['experimental.deadline_client_buffer'] boolean
---@field ['experimental.deadline_main_buffer'] boolean
---@field ['experimental.wp_cm_1_2'] boolean
---@field ['general.allow_tearing'] boolean
---@field ['general.border_size'] integer|boolean

View file

@ -674,6 +674,8 @@ std::vector<SP<IValue>> Values::getConfigValues() {
*/
MS<Bool>("experimental:wp_cm_1_2", "Allow wp-cm-v1 version 2", false),
MS<Bool>("experimental:deadline_client_buffer", "set deadline on client buffers", false),
MS<Bool>("experimental:deadline_main_buffer", "set deadline on main buffers", false),
/*
* quirks:

View file

@ -4,8 +4,19 @@
#include <mutex>
#include <optional>
#include <sys/stat.h>
#if defined(__linux__)
#include <linux/dma-buf.h>
#include <linux/sync_file.h>
#endif
#include <libsync.h>
#include <sys/ioctl.h>
#include "Drm.hpp"
using namespace Hyprutils::OS;
namespace {
using SDRMNodePair = std::array<dev_t, 2>;
@ -67,3 +78,61 @@ bool DRM::sameGpu(int fd1, int fd2) {
return same;
}
int DRM::doIoctl(int fd, unsigned long request, void* arg) {
int ret;
do {
ret = ioctl(fd, request, arg);
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
return ret;
}
// https://www.kernel.org/doc/html/latest/driver-api/dma-buf.html#c.dma_buf_export_sync_file
// returns a sync file that will be signalled when dmabuf is ready to be read
CFileDescriptor DRM::exportFence(int fd) {
#ifndef __linux__
return {};
#endif
#ifdef DMA_BUF_IOCTL_EXPORT_SYNC_FILE
if (fd < 0)
return {};
dma_buf_export_sync_file request{
.flags = DMA_BUF_SYNC_READ,
.fd = -1,
};
if (doIoctl(fd, DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &request) == 0)
return CFileDescriptor{request.fd};
#endif
return {};
}
CFileDescriptor DRM::mergeFence(int fence1, int fence2) {
#ifdef __linux__
sync_accumulate("merged release fence", &fence1, fence2);
if (fence2 >= 0)
close(fence2);
return CFileDescriptor{fence1};
#else
return {};
#endif
}
void DRM::setDeadline(const Time::steady_tp& deadline, const Hyprutils::OS::CFileDescriptor& fence) {
#ifdef SYNC_IOC_SET_DEADLINE
if (!fence.isValid())
return;
sync_set_deadline args{
.deadline_ns = uint64_t(duration_cast<std::chrono::nanoseconds>(deadline.time_since_epoch()).count()),
.pad = 0,
};
doIoctl(fence.get(), SYNC_IOC_SET_DEADLINE, &args);
#endif
}

View file

@ -2,8 +2,14 @@
#include <optional>
#include <sys/types.h>
#include <hyprutils/os/FileDescriptor.hpp>
#include "time/Time.hpp"
namespace DRM {
std::optional<dev_t> devIDFromFD(int fd);
bool sameGpu(int fd1, int fd2);
std::optional<dev_t> devIDFromFD(int fd);
bool sameGpu(int fd1, int fd2);
int doIoctl(int fd, unsigned long request, void* arg);
Hyprutils::OS::CFileDescriptor exportFence(int fd);
Hyprutils::OS::CFileDescriptor mergeFence(int fence1, int fence2);
void setDeadline(const Time::steady_tp& deadline, const Hyprutils::OS::CFileDescriptor& fence);
}

View file

@ -155,6 +155,12 @@ void CMonitor::onConnect(bool noRule) {
});
}
if (m_pendingFrame) { // if no pending frame, VFR etc we cant estimate this.
auto minVBlankInterval = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::duration<double>(1.0 / m_refreshRate));
m_estimatedNextVblank = (ts ? Time::fromTimespec(ts) + minVBlankInterval : Time::steadyNow()) + minVBlankInterval;
} else
m_estimatedNextVblank = std::nullopt;
m_frameScheduler->onPresented();
m_events.presented.emit();

View file

@ -106,71 +106,72 @@ class CMonitor {
CMonitor(SP<Aquamarine::IOutput> output);
~CMonitor();
Vector2D m_position = Vector2D(-1, -1); // means unset
Vector2D m_xwaylandPosition = Vector2D(-1, -1); // means unset
Config::eAutoDirs m_autoDir = Config::DIR_AUTO_NONE;
Vector2D m_size = Vector2D(0, 0);
Vector2D m_pixelSize = Vector2D(0, 0);
Vector2D m_transformedSize = Vector2D(0, 0);
Vector2D m_position = Vector2D(-1, -1); // means unset
Vector2D m_xwaylandPosition = Vector2D(-1, -1); // means unset
Config::eAutoDirs m_autoDir = Config::DIR_AUTO_NONE;
Vector2D m_size = Vector2D(0, 0);
Vector2D m_pixelSize = Vector2D(0, 0);
Vector2D m_transformedSize = Vector2D(0, 0);
MONITORID m_id = MONITOR_INVALID;
PHLWORKSPACE m_activeWorkspace = nullptr;
PHLWORKSPACE m_activeSpecialWorkspace = nullptr;
float m_setScale = 1; // scale set by cfg
float m_scale = 1; // real scale
MONITORID m_id = MONITOR_INVALID;
PHLWORKSPACE m_activeWorkspace = nullptr;
PHLWORKSPACE m_activeSpecialWorkspace = nullptr;
float m_setScale = 1; // scale set by cfg
float m_scale = 1; // real scale
std::string m_name = "";
std::string m_description = "";
std::string m_shortDescription = "";
std::string m_name = "";
std::string m_description = "";
std::string m_shortDescription = "";
drmModeModeInfo m_customDrmMode = {};
drmModeModeInfo m_customDrmMode = {};
Desktop::CReservedArea m_reservedArea;
Desktop::CReservedArea m_reservedArea;
CMonitorState m_state;
CDamageRing m_damage;
CMonitorState m_state;
CDamageRing m_damage;
SP<Aquamarine::IOutput> m_output;
float m_refreshRate = 60; // Hz
int m_forceFullFrames = 0;
bool m_scheduledRecalc = false;
wl_output_transform m_transform = WL_OUTPUT_TRANSFORM_NORMAL;
float m_xwaylandScale = 1.f;
SP<Aquamarine::IOutput> m_output;
float m_refreshRate = 60; // Hz
int m_forceFullFrames = 0;
bool m_scheduledRecalc = false;
wl_output_transform m_transform = WL_OUTPUT_TRANSFORM_NORMAL;
float m_xwaylandScale = 1.f;
std::optional<Vector2D> m_forceSize;
SP<Aquamarine::SOutputMode> m_currentMode;
SP<Aquamarine::CSwapchain> m_cursorSwapchain;
uint32_t m_drmFormat = DRM_FORMAT_INVALID;
uint32_t m_prevDrmFormat = DRM_FORMAT_INVALID;
std::optional<Vector2D> m_forceSize;
SP<Aquamarine::SOutputMode> m_currentMode;
SP<Aquamarine::CSwapchain> m_cursorSwapchain;
uint32_t m_drmFormat = DRM_FORMAT_INVALID;
uint32_t m_prevDrmFormat = DRM_FORMAT_INVALID;
CMonitorZoomController m_zoomController;
CMonitorZoomController m_zoomController;
bool m_dpmsStatus = true;
bool m_vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it.
bool m_enabled10bit = false; // as above, this can be TRUE even if 10 bit failed.
NCMType::eCMType m_cmType = NCMType::CM_SRGB;
NTransferFunction::eTF m_sdrEotf = NTransferFunction::TF_DEFAULT;
float m_sdrSaturation = 1.0f;
float m_sdrBrightness = 1.0f;
float m_sdrMinLuminance = 0.2f;
int m_sdrMaxLuminance = 80;
bool m_createdByUser = false;
bool m_isUnsafeFallback = false;
bool m_dpmsStatus = true;
bool m_vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it.
bool m_enabled10bit = false; // as above, this can be TRUE even if 10 bit failed.
NCMType::eCMType m_cmType = NCMType::CM_SRGB;
NTransferFunction::eTF m_sdrEotf = NTransferFunction::TF_DEFAULT;
float m_sdrSaturation = 1.0f;
float m_sdrBrightness = 1.0f;
float m_sdrMinLuminance = 0.2f;
int m_sdrMaxLuminance = 80;
bool m_createdByUser = false;
bool m_isUnsafeFallback = false;
SP<CEventLoopTimer> m_dpmsRetryTimer;
SP<CEventLoopTimer> m_dpmsRetryTimer;
bool m_pendingFrame = false; // if we schedule a frame during rendering, reschedule it after
bool m_renderingActive = false;
bool m_pendingFrame = false; // if we schedule a frame during rendering, reschedule it after
bool m_renderingActive = false;
bool m_ratsScheduled = false;
CTimer m_lastPresentationTimer;
bool m_ratsScheduled = false;
CTimer m_lastPresentationTimer;
std::optional<Time::steady_tp> m_estimatedNextVblank;
bool m_isBeingLeased = false;
bool m_isBeingLeased = false;
Config::CMonitorRule m_activeMonitorRule;
Config::CMonitorRule m_activeMonitorRule;
SP<Render::ITexture> m_splash;
SP<Render::ITexture> m_background;
SP<Render::ITexture> m_splash;
SP<Render::ITexture> m_background;
// explicit sync
Hyprutils::OS::CFileDescriptor m_inFence; // TODO: remove when aq uses CFileDescriptor

View file

@ -1,21 +1,7 @@
#include "SyncReleaser.hpp"
#include "SyncTimeline.hpp"
#include "../../render/OpenGL.hpp"
#include <sys/ioctl.h>
#if defined(__linux__)
#include <linux/sync_file.h>
#else
struct sync_merge_data {
char name[32];
__s32 fd2;
__s32 fence;
__u32 flags;
__u32 pad;
};
#define SYNC_IOC_MAGIC '>'
#define SYNC_IOC_MERGE _IOWR(SYNC_IOC_MAGIC, 3, struct sync_merge_data)
#endif
#include "../../helpers/Drm.hpp"
using namespace Hyprutils::OS;
@ -35,30 +21,11 @@ CSyncReleaser::~CSyncReleaser() {
m_timeline->signal(m_point);
}
static CFileDescriptor mergeSyncFds(const CFileDescriptor& fd1, const CFileDescriptor& fd2) {
// combines the fences of both sync_fds into a dma_fence_array (https://www.kernel.org/doc/html/latest/driver-api/dma-buf.html#c.dma_fence_array_create)
// with the signal_on_any param set to false, so the new sync_fd will "signal when all fences in the array signal."
struct sync_merge_data data{
.name = "merged release fence",
.fd2 = fd2.get(),
.fence = -1,
};
int err = -1;
do {
err = ioctl(fd1.get(), SYNC_IOC_MERGE, &data);
} while (err == -1 && (errno == EINTR || errno == EAGAIN));
if (err < 0)
return CFileDescriptor{};
else
return CFileDescriptor(data.fence);
}
void CSyncReleaser::addSyncFileFd(const Hyprutils::OS::CFileDescriptor& syncFd) {
if (m_fd.isValid())
m_fd = mergeSyncFds(m_fd, syncFd);
else
if (!m_fd.isValid())
m_fd = syncFd.duplicate();
else
m_fd = DRM::mergeFence(m_fd.take(), syncFd.duplicate().take());
}
void CSyncReleaser::drop() {

View file

@ -8,6 +8,7 @@
#include "Subcompositor.hpp"
#include "../Viewporter.hpp"
#include "../../helpers/Monitor.hpp"
#include "../../helpers/Drm.hpp"
#include "../PresentationTime.hpp"
#include "../DRMSyncobj.hpp"
#include "../types/DMABuffer.hpp"
@ -148,10 +149,28 @@ CWLSurfaceResource::CWLSurfaceResource(SP<CWlSurface> resource_) : m_resource(re
// fifo and fences first
m_events.stateCommit.emit(state);
if (state->buffer && state->buffer->type() == Aquamarine::BUFFER_TYPE_DMABUF && state->buffer->dmabuf().success && !state->updated.bits.acquire) {
state->buffer->m_syncFd = dc<CDMABuffer*>(state->buffer.m_buffer.get())->exportSyncFile();
if (state->buffer->m_syncFd.isValid())
m_stateQueue.lock(state, LOCK_REASON_FENCE);
if (state->buffer && state->buffer->type() == Aquamarine::BUFFER_TYPE_DMABUF && state->buffer->dmabuf().success) {
static const auto DEADLINE = CConfigValue<Config::INTEGER>("experimental:deadline_client_buffer");
auto const WINDOW = m_hlSurface ? Desktop::View::CWindow::fromView(m_hlSurface->view()) : nullptr;
if (g_pHyprRenderer->explicitSyncSupported()) {
if (state->updated.bits.acquire && *DEADLINE && WINDOW && WINDOW->m_monitor && WINDOW->m_monitor->m_solitaryClient == WINDOW) {
Time::steady_tp deadline = Time::steadyNow() + std::chrono::microseconds(100);
DRM::setDeadline(deadline, state->acquire.exportAsFD());
} else {
state->buffer->m_syncFds = dc<CDMABuffer*>(state->buffer.m_buffer.get())->exportSyncFiles();
if (!state->buffer->m_syncFds.empty()) {
if (*DEADLINE && WINDOW && WINDOW->m_monitor && WINDOW->m_monitor->m_solitaryClient == WINDOW) {
Time::steady_tp deadline = Time::steadyNow() + std::chrono::microseconds(100);
for (auto& fence : state->buffer->m_syncFds) {
if (fence.isValid())
DRM::setDeadline(deadline, fence);
}
}
m_stateQueue.lock(state, LOCK_REASON_FENCE);
}
}
}
}
// now for timer.
@ -542,9 +561,30 @@ void CWLSurfaceResource::scheduleState(WP<SSurfaceState> state) {
} else if (state->buffer && state->buffer->isSynchronous()) {
// synchronous (shm) buffers can be read immediately
m_stateQueue.unlock(state, LOCK_REASON_FENCE);
} else if (state->buffer && state->buffer->m_syncFd.isValid()) {
} else if (state->buffer && !state->buffer->m_syncFds.empty()) {
// async buffer and is dmabuf, then we can wait on implicit fences
g_pEventLoopManager->doOnReadable(std::move(state->buffer->m_syncFd), [state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); });
auto fence = std::ranges::find_if(state->buffer->m_syncFds, [](auto& f) { return !Hyprutils::OS::CFileDescriptor::isReadable(f.get()); });
if (fence == state->buffer->m_syncFds.end())
m_stateQueue.unlock(state, LOCK_REASON_FENCE);
else {
//#TODO: if multiple fences are not ready, wait for them..
auto checkFences = [state, whenReadable](auto&& self) -> void {
if (!state || !state->buffer.m_buffer)
return;
auto fence = std::ranges::find_if(state->buffer->m_syncFds, [](auto& f) { return !Hyprutils::OS::CFileDescriptor::isReadable(f.get()); });
if (fence == state->buffer->m_syncFds.end()) {
// All fences readable
whenReadable(state, LOCK_REASON_FENCE);
return;
}
g_pEventLoopManager->doOnReadable(fence->duplicate(), [state, whenReadable, self]() mutable { self(self); });
};
checkFences(checkFences);
}
} else {
// state commit without a buffer.
m_stateQueue.tryProcess();

View file

@ -13,24 +13,24 @@ class CHLBufferReference;
class IHLBuffer : public Aquamarine::IBuffer {
public:
virtual ~IHLBuffer();
virtual Aquamarine::eBufferCapability caps() = 0;
virtual Aquamarine::eBufferType type() = 0;
virtual void update(const CRegion& damage) = 0;
virtual bool isSynchronous() = 0; // whether the updates to this buffer are synchronous, aka happen over cpu
virtual bool good() = 0;
virtual void sendRelease();
virtual void lock();
virtual void unlock();
virtual bool locked();
virtual Aquamarine::eBufferCapability caps() = 0;
virtual Aquamarine::eBufferType type() = 0;
virtual void update(const CRegion& damage) = 0;
virtual bool isSynchronous() = 0; // whether the updates to this buffer are synchronous, aka happen over cpu
virtual bool good() = 0;
virtual void sendRelease();
virtual void lock();
virtual void unlock();
virtual bool locked();
void onBackendRelease(const std::function<void()>& fn);
void addReleasePoint(CDRMSyncPointState& point);
void onBackendRelease(const std::function<void()>& fn);
void addReleasePoint(CDRMSyncPointState& point);
SP<Render::ITexture> m_texture;
bool m_opaque = false;
SP<CWLBufferResource> m_resource;
std::vector<UP<CSyncReleaser>> m_syncReleasers;
Hyprutils::OS::CFileDescriptor m_syncFd;
SP<Render::ITexture> m_texture;
bool m_opaque = false;
SP<CWLBufferResource> m_resource;
std::vector<UP<CSyncReleaser>> m_syncReleasers;
std::vector<Hyprutils::OS::CFileDescriptor> m_syncFds;
struct {
CHyprSignalListener backendRelease;

View file

@ -4,12 +4,7 @@
#include "../../render/Renderer.hpp"
#include "../../helpers/Format.hpp"
#include <hyprgraphics/egl/Egl.hpp>
#if defined(__linux__)
#include <linux/dma-buf.h>
#include <linux/sync_file.h>
#endif
#include <sys/ioctl.h>
#include "../../helpers/Drm.hpp"
using namespace Hyprutils::OS;
using namespace Hyprgraphics::Egl;
@ -92,18 +87,9 @@ void CDMABuffer::closeFDs() {
m_attrs.planes = 0;
}
static int doIoctl(int fd, unsigned long request, void* arg) {
int ret;
do {
ret = ioctl(fd, request, arg);
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
return ret;
}
// https://www.kernel.org/doc/html/latest/driver-api/dma-buf.html#c.dma_buf_export_sync_file
// returns a sync file that will be signalled when dmabuf is ready to be read
CFileDescriptor CDMABuffer::exportSyncFile() {
std::vector<CFileDescriptor> CDMABuffer::exportSyncFiles() {
if (!good())
return {};
@ -113,7 +99,8 @@ CFileDescriptor CDMABuffer::exportSyncFile() {
std::vector<CFileDescriptor> syncFds;
syncFds.reserve(m_attrs.fds.size());
for (const auto& fd : m_attrs.fds) {
for (auto i = 0ul; i < m_attrs.fds.size(); i++) {
auto fd = m_attrs.fds[i];
if (fd == -1)
continue;
@ -124,40 +111,11 @@ CFileDescriptor CDMABuffer::exportSyncFile() {
continue;
}
dma_buf_export_sync_file request{
.flags = DMA_BUF_SYNC_READ,
.fd = -1,
};
if (doIoctl(fd, DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &request) == 0)
syncFds.emplace_back(request.fd);
auto fence = DRM::exportFence(fd);
if (fence.isValid())
syncFds.emplace_back(std::move(fence));
}
if (syncFds.empty())
return {};
CFileDescriptor syncFd;
for (auto& fd : syncFds) {
if (!syncFd.isValid()) {
syncFd = std::move(fd);
continue;
}
const std::string name = "merged release fence";
struct sync_merge_data data{
.name = {}, // zero-initialize name[]
.fd2 = fd.get(),
.fence = -1,
};
std::ranges::copy_n(name.c_str(), std::min(name.size() + 1, sizeof(data.name)), data.name);
if (doIoctl(syncFd.get(), SYNC_IOC_MERGE, &data) == 0)
syncFd = CFileDescriptor(data.fence);
else
syncFd = {};
}
return syncFd;
return syncFds;
#endif
}

View file

@ -17,7 +17,7 @@ class CDMABuffer : public IHLBuffer {
virtual void endDataPtr();
bool good();
void closeFDs();
Hyprutils::OS::CFileDescriptor exportSyncFile();
std::vector<Hyprutils::OS::CFileDescriptor> exportSyncFiles();
bool m_success = false;
private:

View file

@ -11,6 +11,7 @@
#include "../protocols/core/Compositor.hpp"
#include "../debug/Overlay.hpp"
#include "../helpers/Monitor.hpp"
#include "../helpers/Drm.hpp"
#include "pass/TexPassElement.hpp"
#include "pass/SurfacePassElement.hpp"
#include "../debug/log/Logger.hpp"
@ -71,7 +72,6 @@ bool CHyprGLRenderer::beginFullFakeRenderInternal(PHLMONITOR pMonitor, CRegion&
}
bool CHyprGLRenderer::beginRenderInternal(PHLMONITOR pMonitor, CRegion& damage, bool simple) {
m_currentRenderbuffer->bind();
if (simple)
g_pHyprOpenGL->beginSimple(pMonitor, damage, m_currentRenderbuffer);

View file

@ -34,6 +34,7 @@
#include "../helpers/CursorShapes.hpp"
#include "../helpers/MainLoopExecutor.hpp"
#include "../helpers/Monitor.hpp"
#include "../helpers/Drm.hpp"
#include "macros.hpp"
#include "pass/TexPassElement.hpp"
#include "pass/ClearPassElement.hpp"
@ -1970,6 +1971,8 @@ void IHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) {
const float ZOOMFACTOR = pMonitor->m_cursorZoom->value();
auto cleanup = CScopeGuard([pMonitor]() { pMonitor->m_estimatedNextVblank = std::nullopt; });
if (pMonitor->m_pixelSize.x < 1 || pMonitor->m_pixelSize.y < 1) {
Log::logger->log(Log::ERR, "Refusing to render a monitor because of an invalid pixel size: {}", pMonitor->m_pixelSize);
return;
@ -2404,6 +2407,16 @@ void IHyprRenderer::handleFullscreenSettings(PHLMONITOR pMonitor) {
bool IHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) {
handleFullscreenSettings(pMonitor);
static const auto DEADLINE = CConfigValue<Config::INTEGER>("experimental:deadline_main_buffer");
if (*DEADLINE && m_currentBuffer) {
auto deadline = pMonitor->isMultiGPU() || pMonitor->m_vrrActive || pMonitor->m_tearingState.activelyTearing || !pMonitor->m_estimatedNextVblank.has_value() ?
Time::steadyNow() + std::chrono::microseconds(100) :
pMonitor->m_estimatedNextVblank.value();
auto fence = DRM::exportFence(m_currentBuffer->dmabuf().fds[0]);
DRM::setDeadline(deadline, fence);
}
bool ok = pMonitor->m_state.commit();
if (!ok) {
if (pMonitor->m_inFence.isValid()) {

View file

@ -271,12 +271,12 @@ namespace Render {
bool m_cursorHidden = false;
bool m_cursorHiddenByCondition = false;
bool m_cursorHasSurface = false;
SP<Aquamarine::IBuffer> m_currentBuffer = nullptr;
eRenderMode m_renderMode = RENDER_MODE_NORMAL;
bool m_nvidia = false;
bool m_intel = false;
bool m_software = false;
bool m_mgpu = false;
SP<Aquamarine::IBuffer> m_currentBuffer = nullptr;
struct {
bool hiddenOnTouch = false;