From 847fe2e1201480e33c90ece173f142068da24783 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Mon, 24 Feb 2025 14:54:35 -0600 Subject: [PATCH] drm: Use an intermediate CPU buffer for blit if necessary --- src/backend/drm/DRM.cpp | 12 ++++- src/backend/drm/Renderer.cpp | 95 ++++++++++++++++++++++++++++++++---- src/backend/drm/Renderer.hpp | 9 +++- 3 files changed, 102 insertions(+), 14 deletions(-) diff --git a/src/backend/drm/DRM.cpp b/src/backend/drm/DRM.cpp index 508de51..e4cf95b 100644 --- a/src/backend/drm/DRM.cpp +++ b/src/backend/drm/DRM.cpp @@ -1700,8 +1700,12 @@ bool Aquamarine::CDRMOutput::commitState(bool onlyTest) { } auto NEWAQBUF = mgpu.swapchain->next(nullptr); + SP primaryRenderer; + if (backend->primary) { + primaryRenderer = backend->primary->rendererState.renderer; + } auto blitResult = backend->rendererState.renderer->blit( - STATE.buffer, NEWAQBUF, (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_EXPLICIT_IN_FENCE) ? STATE.explicitInFence : -1); + STATE.buffer, NEWAQBUF, primaryRenderer, (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_EXPLICIT_IN_FENCE) ? STATE.explicitInFence : -1); if (!blitResult.success) { backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but blit failed"); return false; @@ -1882,7 +1886,11 @@ bool Aquamarine::CDRMOutput::setCursor(SP buffer, const Vector2D& hotsp } auto NEWAQBUF = mgpu.cursorSwapchain->next(nullptr); - if (!backend->rendererState.renderer->blit(buffer, NEWAQBUF).success) { + SP primaryRenderer; + if (backend->primary) { + primaryRenderer = backend->primary->rendererState.renderer; + } + if (!backend->rendererState.renderer->blit(buffer, NEWAQBUF, primaryRenderer).success) { backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but cursor blit failed"); return false; } diff --git a/src/backend/drm/Renderer.cpp b/src/backend/drm/Renderer.cpp index 3fc70d5..20cc5e4 100644 --- a/src/backend/drm/Renderer.cpp +++ b/src/backend/drm/Renderer.cpp @@ -341,7 +341,7 @@ void CDRMRenderer::loadEGLAPI() { loadGLProc(&proc.eglWaitSyncKHR, "eglWaitSyncKHR"); loadGLProc(&proc.eglCreateSyncKHR, "eglCreateSyncKHR"); loadGLProc(&proc.eglDupNativeFenceFDANDROID, "eglDupNativeFenceFDANDROID"); - loadGLProc(&proc.glEGLImageTargetRenderbufferStorageOES, "glEGLImageTargetRenderbufferStorageOES"); + loadGLProc(&proc.glReadnPixelsEXT, "glReadnPixelsEXT"); if (EGLEXTENSIONS.contains("EGL_EXT_device_base") || EGLEXTENSIONS.contains("EGL_EXT_device_enumeration")) loadGLProc(&proc.eglQueryDevicesEXT, "eglQueryDevicesEXT"); @@ -698,6 +698,52 @@ SGLTex CDRMRenderer::glTex(Hyprutils::Memory::CSharedPointer buffa) { return tex; } +// TODO: Would another format be more efficient than GL_RGBA? +const GLenum PIXEL_BUFFER_FORMAT = GL_RGBA; + +void CDRMRenderer::readBuffer(Hyprutils::Memory::CSharedPointer buf, uint8_t* out, size_t outlen) { + setEGL(); + + auto hadAttachment = buf->attachments.get(AQ_ATTACHMENT_DRM_RENDERER_DATA); + auto att = (CDRMRendererBufferAttachment*)hadAttachment.get(); + if (!hadAttachment) { + // should never remove anything, but JIC. We'll leak an EGLImage if this removes anything. + buf->attachments.removeByType(AQ_ATTACHMENT_DRM_RENDERER_DATA); + auto newAttachment = makeShared(self, buf, nullptr, 0, 0, SGLTex{}, nullptr, 0); + att = newAttachment.get(); + buf->attachments.add(newAttachment); + } + + auto dma = buf->dmabuf(); + if (!att->eglImage) { + att->eglImage = createEGLImage(dma); + if (att->eglImage == EGL_NO_IMAGE_KHR) { + backend->log(AQ_LOG_ERROR, std::format("EGL (readBuffer): createEGLImage failed: {}", eglGetError())); + return; + } + + GLCALL(glGenRenderbuffers(1, &att->rbo)); + GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, att->rbo)); + GLCALL(proc.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)att->eglImage)); + GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); + + GLCALL(glGenFramebuffers(1, &att->fbo)); + GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, att->fbo)); + GLCALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, att->rbo)); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + backend->log(AQ_LOG_ERROR, std::format("EGL (readBuffer): glCheckFramebufferStatus failed: {}", glGetError())); + return; + } + } + + GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, att->fbo)); + GLCALL(proc.glReadnPixelsEXT(0, 0, dma.size.x, dma.size.y, GL_RGBA, GL_UNSIGNED_BYTE, outlen, out)); + + GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); + restoreEGL(); +} + inline const float fullVerts[] = { 1, 0, // top right 0, 0, // top left @@ -828,7 +874,7 @@ void CDRMRenderer::clearBuffer(IBuffer* buf) { restoreEGL(); } -CDRMRenderer::SBlitResult CDRMRenderer::blit(SP from, SP to, int waitFD) { +CDRMRenderer::SBlitResult CDRMRenderer::blit(SP from, SP to, SP primaryRenderer, int waitFD) { setEGL(); if (from->dmabuf().size != to->dmabuf().size) { @@ -846,22 +892,45 @@ CDRMRenderer::SBlitResult CDRMRenderer::blit(SP from, SP to, i // both from and to have the same AQ_ATTACHMENT_DRM_RENDERER_DATA. // Those buffers always come from different swapchains, so it's OK. - SGLTex fromTex; + SGLTex fromTex; + auto fromDma = from->dmabuf(); + uint8_t* intermediateBuf = nullptr; + size_t intermediateBufLen = 0; { auto attachment = from->attachments.get(AQ_ATTACHMENT_DRM_RENDERER_DATA); if (attachment) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (blit): From attachment found")); - auto att = (CDRMRendererBufferAttachment*)attachment.get(); - fromTex = att->tex; + auto att = (CDRMRendererBufferAttachment*)attachment.get(); + fromTex = att->tex; + intermediateBuf = att->intermediateBuf; + intermediateBufLen = att->intermediateBufLen; } - if (!fromTex.image) { + if (!fromTex.image && !intermediateBuf) { backend->log(AQ_LOG_DEBUG, "EGL (blit): No attachment in from, creating a new image"); fromTex = glTex(from); + if (!fromTex.image && primaryRenderer) { + backend->log(AQ_LOG_DEBUG, "EGL (blit): Failed to create image from source buffer directly, allocating intermediate buffer"); + static_assert(PIXEL_BUFFER_FORMAT == GL_RGBA); // If the pixel buffer format changes, the below size calculation probably needs to as well. + intermediateBufLen = fromDma.size.x * fromDma.size.y * 4; + intermediateBuf = (uint8_t*)malloc(intermediateBufLen); + if (intermediateBuf == nullptr) { + backend->log(AQ_LOG_ERROR, "EGL (blit): Failed to allocate intermediate buffer"); + return {}; + } + fromTex.target = GL_TEXTURE_2D; + GLCALL(glGenTextures(1, &fromTex.texid)); + } + // should never remove anything, but JIC. We'll leak an EGLImage if this removes anything. from->attachments.removeByType(AQ_ATTACHMENT_DRM_RENDERER_DATA); - from->attachments.add(makeShared(self, from, nullptr, 0, 0, fromTex)); + from->attachments.add(makeShared(self, from, nullptr, 0, 0, fromTex, intermediateBuf, intermediateBufLen)); + } + + if (intermediateBuf) { + // Note: this might modify from's attachments + primaryRenderer->readBuffer(from, intermediateBuf, intermediateBufLen); } } @@ -916,7 +985,7 @@ CDRMRenderer::SBlitResult CDRMRenderer::blit(SP from, SP to, i // should never remove anything, but JIC. We'll leak an RBO and FBO if this removes anything. to->attachments.removeByType(AQ_ATTACHMENT_DRM_RENDERER_DATA); - to->attachments.add(makeShared(self, to, rboImage, fboID, rboID, SGLTex{})); + to->attachments.add(makeShared(self, to, rboImage, fboID, rboID, SGLTex{}, nullptr, 0)); } } @@ -964,6 +1033,10 @@ CDRMRenderer::SBlitResult CDRMRenderer::blit(SP from, SP to, i GLCALL(glTexParameteri(fromTex.target, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); GLCALL(glTexParameteri(fromTex.target, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); + if (intermediateBuf) { + GLCALL(glTexImage2D(fromTex.target, 0, PIXEL_BUFFER_FORMAT, fromDma.size.x, fromDma.size.y, 0, PIXEL_BUFFER_FORMAT, GL_UNSIGNED_BYTE, intermediateBuf)); + } + GLCALL(glUseProgram(SHADER.program)); GLCALL(glDisable(GL_BLEND)); GLCALL(glDisable(GL_SCISSOR_TEST)); @@ -1018,6 +1091,8 @@ void CDRMRenderer::onBufferAttachmentDrop(CDRMRendererBufferAttachment* attachme proc.eglDestroyImageKHR(egl.display, attachment->eglImage); if (attachment->tex.image) proc.eglDestroyImageKHR(egl.display, attachment->tex.image); + if (attachment->intermediateBuf) + free(attachment->intermediateBuf); restoreEGL(); } @@ -1043,7 +1118,7 @@ bool CDRMRenderer::verifyDestinationDMABUF(const SDMABUFAttrs& attrs) { } CDRMRendererBufferAttachment::CDRMRendererBufferAttachment(Hyprutils::Memory::CWeakPointer renderer_, Hyprutils::Memory::CSharedPointer buffer, - EGLImageKHR image, GLuint fbo_, GLuint rbo_, SGLTex tex_) : - eglImage(image), fbo(fbo_), rbo(rbo_), tex(tex_), renderer(renderer_) { + EGLImageKHR image, GLuint fbo_, GLuint rbo_, SGLTex tex_, uint8_t* intermediateBuf_, size_t intermediateBufLen_) : + eglImage(image), fbo(fbo_), rbo(rbo_), tex(tex_), intermediateBuf(intermediateBuf_), intermediateBufLen(intermediateBufLen_), renderer(renderer_) { bufferDestroy = buffer->events.destroy.registerListener([this](std::any d) { renderer->onBufferAttachmentDrop(this); }); } diff --git a/src/backend/drm/Renderer.hpp b/src/backend/drm/Renderer.hpp index af560d9..40261bb 100644 --- a/src/backend/drm/Renderer.hpp +++ b/src/backend/drm/Renderer.hpp @@ -24,7 +24,7 @@ namespace Aquamarine { class CDRMRendererBufferAttachment : public IAttachment { public: CDRMRendererBufferAttachment(Hyprutils::Memory::CWeakPointer renderer_, Hyprutils::Memory::CSharedPointer buffer, EGLImageKHR image, GLuint fbo_, - GLuint rbo_, SGLTex tex); + GLuint rbo_, SGLTex tex, uint8_t* intermediateBuf, size_t intermediateBufLen); virtual ~CDRMRendererBufferAttachment() { ; } @@ -36,6 +36,8 @@ namespace Aquamarine { GLuint fbo = 0, rbo = 0; SGLTex tex; Hyprutils::Signal::CHyprSignalListener bufferDestroy; + uint8_t* intermediateBuf = nullptr; + size_t intermediateBufLen = 0; Hyprutils::Memory::CWeakPointer renderer; }; @@ -55,7 +57,8 @@ namespace Aquamarine { std::optional syncFD; }; - SBlitResult blit(Hyprutils::Memory::CSharedPointer from, Hyprutils::Memory::CSharedPointer to, int waitFD = -1); + SBlitResult blit(Hyprutils::Memory::CSharedPointer from, Hyprutils::Memory::CSharedPointer to, + Hyprutils::Memory::CSharedPointer primaryRenderer, int waitFD = -1); // can't be a SP<> because we call it from buf's ctor... void clearBuffer(IBuffer* buf); @@ -86,6 +89,7 @@ namespace Aquamarine { PFNEGLDEBUGMESSAGECONTROLKHRPROC eglDebugMessageControlKHR = nullptr; PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = nullptr; PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = nullptr; + PFNGLREADNPIXELSEXTPROC glReadnPixelsEXT = nullptr; } proc; struct { @@ -114,6 +118,7 @@ namespace Aquamarine { } savedEGLState; SGLTex glTex(Hyprutils::Memory::CSharedPointer buf); + void readBuffer(Hyprutils::Memory::CSharedPointer buf, uint8_t* out, size_t out_len); Hyprutils::Memory::CWeakPointer self; std::vector formats;