summaryrefslogtreecommitdiff
path: root/src/render/gl
diff options
context:
space:
mode:
authorUjinT34 <41110182+UjinT34@users.noreply.github.com>2026-03-07 00:44:10 +0300
committerGitHub <noreply@github.com>2026-03-06 21:44:10 +0000
commit4152ac76d0813d9d0f67d2f04653a13fa6e17433 (patch)
treec341f1883cf5a182d32e4ad00192ba8786a9bb75 /src/render/gl
parenta5858018d896e0e0f0db517f893cd9a0936dd881 (diff)
renderer: refactor Texture, Framebuffer and Renderbuffer (#13437)
Part 1 of the renderer refactors
Diffstat (limited to 'src/render/gl')
-rw-r--r--src/render/gl/GLFramebuffer.cpp170
-rw-r--r--src/render/gl/GLFramebuffer.hpp30
-rw-r--r--src/render/gl/GLRenderbuffer.cpp71
-rw-r--r--src/render/gl/GLRenderbuffer.hpp20
-rw-r--r--src/render/gl/GLTexture.cpp223
-rw-r--r--src/render/gl/GLTexture.hpp49
6 files changed, 563 insertions, 0 deletions
diff --git a/src/render/gl/GLFramebuffer.cpp b/src/render/gl/GLFramebuffer.cpp
new file mode 100644
index 00000000..d821f766
--- /dev/null
+++ b/src/render/gl/GLFramebuffer.cpp
@@ -0,0 +1,170 @@
+#include "GLFramebuffer.hpp"
+#include "../OpenGL.hpp"
+#include "../Renderer.hpp"
+#include "macros.hpp"
+#include "render/Framebuffer.hpp"
+
+CGLFramebuffer::CGLFramebuffer() : IFramebuffer() {}
+CGLFramebuffer::CGLFramebuffer(const std::string& name) : IFramebuffer(name) {}
+
+bool CGLFramebuffer::internalAlloc(int w, int h, uint32_t drmFormat) {
+ g_pHyprRenderer->makeEGLCurrent();
+
+ bool firstAlloc = false;
+
+ if (!m_tex) {
+ m_tex = g_pHyprRenderer->createTexture();
+ m_tex->allocate({w, h});
+ m_tex->bind();
+ m_tex->setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ m_tex->setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ m_tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ m_tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ firstAlloc = true;
+ }
+
+ if (!m_fbAllocated) {
+ glGenFramebuffers(1, &m_fb);
+ m_fbAllocated = true;
+ firstAlloc = true;
+ }
+
+ if (firstAlloc) {
+ const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat);
+ m_tex->bind();
+ glTexImage2D(GL_TEXTURE_2D, 0, format->glInternalFormat ? format->glInternalFormat : format->glFormat, w, h, 0, format->glFormat, format->glType, nullptr);
+ glBindFramebuffer(GL_FRAMEBUFFER, m_fb);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_tex->m_texID, 0);
+
+ if (m_stencilTex && m_stencilTex->ok()) {
+ m_stencilTex->bind();
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_stencilTex->m_texID, 0);
+
+ glDisable(GL_DEPTH_TEST);
+ glDepthMask(GL_FALSE);
+ }
+
+ auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Framebuffer incomplete, couldn't create! (FB status: {}, GL Error: 0x{:x})", status, sc<int>(glGetError()));
+
+ if (m_stencilTex && m_stencilTex->ok())
+ m_stencilTex->unbind();
+
+ Log::logger->log(Log::DEBUG, "Framebuffer created, status {}", status);
+ }
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ return true;
+}
+
+void CGLFramebuffer::addStencil(SP<ITexture> tex) {
+ if (m_stencilTex == tex)
+ return;
+
+ RASSERT(!m_fbAllocated, "Should add stencil tex prior to FB allocation")
+ m_stencilTex = tex;
+}
+
+void CGLFramebuffer::bind() {
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fb);
+
+ if (g_pHyprOpenGL)
+ g_pHyprOpenGL->setViewport(0, 0, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize.y);
+ else
+ glViewport(0, 0, m_size.x, m_size.y);
+}
+
+void CGLFramebuffer::unbind() {
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+}
+
+void CGLFramebuffer::release() {
+ if (m_fbAllocated) {
+ glBindFramebuffer(GL_FRAMEBUFFER, m_fb);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ glDeleteFramebuffers(1, &m_fb);
+ m_fbAllocated = false;
+ m_fb = 0;
+ }
+
+ if (m_tex)
+ m_tex.reset();
+
+ m_size = Vector2D();
+}
+
+bool CGLFramebuffer::readPixels(CHLBufferReference buffer, uint32_t offsetX, uint32_t offsetY, uint32_t width, uint32_t height) {
+ auto shm = buffer->shm();
+ auto [pixelData, fmt, bufLen] = buffer->beginDataPtr(0); // no need for end, cuz it's shm
+
+ const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(shm.format);
+ if (!PFORMAT) {
+ LOGM(Log::ERR, "Can't copy: failed to find a pixel format");
+ return false;
+ }
+
+ g_pHyprRenderer->makeEGLCurrent();
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, getFBID());
+ bind();
+
+ glPixelStorei(GL_PACK_ALIGNMENT, 1);
+
+ uint32_t packStride = NFormatUtils::minStride(PFORMAT, m_size.x);
+ int glFormat = PFORMAT->glFormat;
+
+ if (glFormat == GL_RGBA)
+ glFormat = GL_BGRA_EXT;
+
+ if (glFormat != GL_BGRA_EXT && glFormat != GL_RGB) {
+ if (PFORMAT->swizzle.has_value()) {
+ std::array<GLint, 4> RGBA = SWIZZLE_RGBA;
+ std::array<GLint, 4> BGRA = SWIZZLE_BGRA;
+ if (PFORMAT->swizzle == RGBA)
+ glFormat = GL_RGBA;
+ else if (PFORMAT->swizzle == BGRA)
+ glFormat = GL_BGRA_EXT;
+ else {
+ LOGM(Log::ERR, "Copied frame via shm might be broken or color flipped");
+ glFormat = GL_RGBA;
+ }
+ }
+ }
+
+ // This could be optimized by using a pixel buffer object to make this async,
+ // but really clients should just use a dma buffer anyways.
+ if (packStride == sc<uint32_t>(shm.stride)) {
+ glReadPixels(offsetX, offsetY, width > 0 ? width : m_size.x, height > 0 ? height : m_size.y, glFormat, PFORMAT->glType, pixelData);
+ } else {
+ const auto h = height > 0 ? height : m_size.y;
+ for (size_t i = 0; i < h; ++i) {
+ uint32_t y = i;
+ glReadPixels(offsetX, offsetY + y, width > 0 ? width : m_size.x, 1, glFormat, PFORMAT->glType, pixelData + i * shm.stride);
+ }
+ }
+
+ unbind();
+ glPixelStorei(GL_PACK_ALIGNMENT, 4);
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+ return true;
+}
+
+CGLFramebuffer::~CGLFramebuffer() {
+ release();
+}
+
+GLuint CGLFramebuffer::getFBID() {
+ return m_fbAllocated ? m_fb : 0;
+}
+
+void CGLFramebuffer::invalidate(const std::vector<GLenum>& attachments) {
+ if (!isAllocated())
+ return;
+
+ glInvalidateFramebuffer(GL_FRAMEBUFFER, attachments.size(), attachments.data());
+}
diff --git a/src/render/gl/GLFramebuffer.hpp b/src/render/gl/GLFramebuffer.hpp
new file mode 100644
index 00000000..c171444e
--- /dev/null
+++ b/src/render/gl/GLFramebuffer.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "../../defines.hpp"
+#include "../Texture.hpp"
+#include "../Framebuffer.hpp"
+#include <drm_fourcc.h>
+
+class CGLFramebuffer : public IFramebuffer {
+ public:
+ CGLFramebuffer();
+ CGLFramebuffer(const std::string& name);
+ ~CGLFramebuffer();
+
+ void addStencil(SP<ITexture> tex) override;
+ void release() override;
+ bool readPixels(CHLBufferReference buffer, uint32_t offsetX = 0, uint32_t offsetY = 0, uint32_t width = 0, uint32_t height = 0) override;
+
+ void bind() override;
+ void unbind();
+ GLuint getFBID();
+ void invalidate(const std::vector<GLenum>& attachments);
+
+ protected:
+ bool internalAlloc(int w, int h, uint32_t format = DRM_FORMAT_ARGB8888) override;
+
+ private:
+ GLuint m_fb = -1;
+
+ friend class CGLRenderbuffer;
+};
diff --git a/src/render/gl/GLRenderbuffer.cpp b/src/render/gl/GLRenderbuffer.cpp
new file mode 100644
index 00000000..8299d0e4
--- /dev/null
+++ b/src/render/gl/GLRenderbuffer.cpp
@@ -0,0 +1,71 @@
+#include "GLRenderbuffer.hpp"
+#include "../Renderer.hpp"
+#include "../OpenGL.hpp"
+#include "../../Compositor.hpp"
+#include "../Framebuffer.hpp"
+#include "GLFramebuffer.hpp"
+#include "render/Renderbuffer.hpp"
+#include <hyprutils/memory/SharedPtr.hpp>
+#include <hyprutils/signal/Listener.hpp>
+#include <hyprutils/signal/Signal.hpp>
+
+#include <dlfcn.h>
+
+CGLRenderbuffer::~CGLRenderbuffer() {
+ if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer)
+ return;
+
+ g_pHyprRenderer->makeEGLCurrent();
+
+ unbind();
+ m_framebuffer->release();
+
+ if (m_rbo)
+ glDeleteRenderbuffers(1, &m_rbo);
+
+ if (m_image != EGL_NO_IMAGE_KHR)
+ g_pHyprOpenGL->m_proc.eglDestroyImageKHR(g_pHyprOpenGL->m_eglDisplay, m_image);
+}
+
+CGLRenderbuffer::CGLRenderbuffer(SP<Aquamarine::IBuffer> buffer, uint32_t format) : IRenderbuffer(buffer, format) {
+ auto dma = buffer->dmabuf();
+
+ m_image = g_pHyprOpenGL->createEGLImage(dma);
+ if (m_image == EGL_NO_IMAGE_KHR) {
+ Log::logger->log(Log::ERR, "rb: createEGLImage failed");
+ return;
+ }
+
+ glGenRenderbuffers(1, &m_rbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, m_rbo);
+ g_pHyprOpenGL->m_proc.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, m_image);
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+
+ m_framebuffer = makeShared<CGLFramebuffer>();
+ glGenFramebuffers(1, &GLFB(m_framebuffer)->m_fb);
+ GLFB(m_framebuffer)->m_fbAllocated = true;
+ m_framebuffer->m_size = buffer->size;
+ m_framebuffer->m_drmFormat = dma.format;
+ m_framebuffer->bind();
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rbo);
+
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ Log::logger->log(Log::ERR, "rbo: glCheckFramebufferStatus failed");
+ return;
+ }
+
+ GLFB(m_framebuffer)->unbind();
+
+ m_listeners.destroyBuffer = buffer->events.destroy.listen([this] { g_pHyprRenderer->onRenderbufferDestroy(this); });
+
+ m_good = true;
+}
+
+void CGLRenderbuffer::bind() {
+ g_pHyprRenderer->makeEGLCurrent();
+ m_framebuffer->bind();
+}
+
+void CGLRenderbuffer::unbind() {
+ GLFB(m_framebuffer)->unbind();
+}
diff --git a/src/render/gl/GLRenderbuffer.hpp b/src/render/gl/GLRenderbuffer.hpp
new file mode 100644
index 00000000..8367f702
--- /dev/null
+++ b/src/render/gl/GLRenderbuffer.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "../../helpers/memory/Memory.hpp"
+#include "../Renderbuffer.hpp"
+#include <aquamarine/buffer/Buffer.hpp>
+
+class CMonitor;
+
+class CGLRenderbuffer : public IRenderbuffer {
+ public:
+ CGLRenderbuffer(SP<Aquamarine::IBuffer> buffer, uint32_t format);
+ ~CGLRenderbuffer();
+
+ void bind() override;
+ void unbind() override;
+
+ private:
+ void* m_image = nullptr;
+ GLuint m_rbo = 0;
+};
diff --git a/src/render/gl/GLTexture.cpp b/src/render/gl/GLTexture.cpp
new file mode 100644
index 00000000..6a1fb172
--- /dev/null
+++ b/src/render/gl/GLTexture.cpp
@@ -0,0 +1,223 @@
+#include "GLTexture.hpp"
+#include "../Renderer.hpp"
+#include "../../Compositor.hpp"
+#include "../../helpers/Format.hpp"
+#include "render/Texture.hpp"
+#include <cstring>
+
+CGLTexture::CGLTexture(bool opaque) {
+ m_opaque = opaque;
+}
+
+CGLTexture::~CGLTexture() {
+ if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer)
+ return;
+
+ g_pHyprRenderer->makeEGLCurrent();
+ if (m_texID) {
+ GLCALL(glDeleteTextures(1, &m_texID));
+ m_texID = 0;
+ }
+
+ if (m_eglImage)
+ g_pHyprOpenGL->m_proc.eglDestroyImageKHR(g_pHyprOpenGL->m_eglDisplay, m_eglImage);
+ m_eglImage = nullptr;
+ m_cachedStates.fill(std::nullopt);
+}
+
+CGLTexture::CGLTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size_, bool keepDataCopy, bool opaque) :
+ ITexture(drmFormat, pixels, stride, size_, keepDataCopy, opaque) {
+
+ g_pHyprRenderer->makeEGLCurrent();
+
+ const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat);
+ ASSERT(format);
+
+ m_type = format->withAlpha ? TEXTURE_RGBA : TEXTURE_RGBX;
+ m_size = size_;
+ m_isSynchronous = true;
+ m_target = GL_TEXTURE_2D;
+ allocate(size_);
+ bind();
+ setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ if (format->swizzle.has_value())
+ swizzle(format->swizzle.value());
+
+ bool alignmentChanged = false;
+ if (format->bytesPerBlock != 4) {
+ const GLint alignment = (stride % 4 == 0) ? 4 : 1;
+ GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, alignment));
+ alignmentChanged = true;
+ }
+
+ GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / format->bytesPerBlock));
+ GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, format->glInternalFormat ? format->glInternalFormat : format->glFormat, size_.x, size_.y, 0, format->glFormat, format->glType, pixels));
+ GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0));
+ if (alignmentChanged)
+ GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 4));
+
+ unbind();
+}
+
+CGLTexture::CGLTexture(const Aquamarine::SDMABUFAttrs& attrs, void* image, bool opaque) {
+ m_opaque = opaque;
+ if (!g_pHyprOpenGL->m_proc.glEGLImageTargetTexture2DOES) {
+ Log::logger->log(Log::ERR, "Cannot create a dmabuf texture: no glEGLImageTargetTexture2DOES");
+ return;
+ }
+
+ m_opaque = NFormatUtils::isFormatOpaque(attrs.format);
+
+ // #TODO external only formats should be external aswell.
+ // also needs a seperate color shader.
+ /*if (NFormatUtils::isFormatYUV(attrs.format)) {
+ m_target = GL_TEXTURE_EXTERNAL_OES;
+ m_type = TEXTURE_EXTERNAL;
+ } else {*/
+ m_target = GL_TEXTURE_2D;
+ m_type = NFormatUtils::isFormatOpaque(attrs.format) ? TEXTURE_RGBX : TEXTURE_RGBA;
+ //}
+
+ allocate(attrs.size);
+ m_eglImage = image;
+
+ bind();
+ setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ GLCALL(g_pHyprOpenGL->m_proc.glEGLImageTargetTexture2DOES(m_target, image));
+ unbind();
+}
+
+CGLTexture::CGLTexture(std::span<const float> lut3D, size_t N) : ITexture(lut3D, N), m_target(GL_TEXTURE_3D) {
+ allocate({});
+ bind();
+
+ GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
+ setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ setTexParameter(GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+
+ // Expand RGB->RGBA on upload (alpha=1)
+ std::vector<float> rgba;
+ rgba.resize(N * N * N * 4);
+ for (size_t i = 0, j = 0; i < N * N * N; ++i, j += 3) {
+ rgba[i * 4 + 0] = lut3D[j + 0];
+ rgba[i * 4 + 1] = lut3D[j + 1];
+ rgba[i * 4 + 2] = lut3D[j + 2];
+ rgba[i * 4 + 3] = 1.F;
+ }
+
+ GLCALL(glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA16F, N, N, N, 0, GL_RGBA, GL_FLOAT, rgba.data()));
+
+ unbind();
+}
+
+void CGLTexture::update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage) {
+ if (damage.empty())
+ return;
+
+ g_pHyprRenderer->makeEGLCurrent();
+
+ const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat);
+ ASSERT(format);
+
+ bind();
+
+ if (format->swizzle.has_value())
+ swizzle(format->swizzle.value());
+
+ bool alignmentChanged = false;
+ if (format->bytesPerBlock != 4) {
+ const GLint alignment = (stride % 4 == 0) ? 4 : 1;
+ GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, alignment));
+ alignmentChanged = true;
+ }
+
+ GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / format->bytesPerBlock));
+
+ damage.copy().intersect(CBox{{}, m_size}).forEachRect([&format, &pixels](const auto& rect) {
+ GLCALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, rect.x1));
+ GLCALL(glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, rect.y1));
+
+ int width = rect.x2 - rect.x1;
+ int height = rect.y2 - rect.y1;
+ GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x1, rect.y1, width, height, format->glFormat, format->glType, pixels));
+ });
+
+ if (alignmentChanged)
+ GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 4));
+
+ GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0));
+ GLCALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0));
+ GLCALL(glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0));
+
+ unbind();
+
+ if (m_keepDataCopy) {
+ m_dataCopy.resize(stride * m_size.y);
+ memcpy(m_dataCopy.data(), pixels, stride * m_size.y);
+ }
+}
+
+void CGLTexture::allocate(const Vector2D& size, uint32_t drmFormat) {
+ if (!m_texID)
+ GLCALL(glGenTextures(1, &m_texID));
+ m_size = size;
+ m_drmFormat = drmFormat;
+}
+
+void CGLTexture::bind() {
+ GLCALL(glBindTexture(m_target, m_texID));
+}
+
+void CGLTexture::unbind() {
+ GLCALL(glBindTexture(m_target, 0));
+}
+
+bool CGLTexture::ok() {
+ return m_texID > 0;
+}
+
+bool CGLTexture::isDMA() {
+ return m_eglImage;
+}
+
+constexpr std::optional<size_t> CGLTexture::getCacheStateIndex(GLenum pname) {
+ switch (pname) {
+ case GL_TEXTURE_WRAP_S: return TEXTURE_PAR_WRAP_S;
+ case GL_TEXTURE_WRAP_T: return TEXTURE_PAR_WRAP_T;
+ case GL_TEXTURE_MAG_FILTER: return TEXTURE_PAR_MAG_FILTER;
+ case GL_TEXTURE_MIN_FILTER: return TEXTURE_PAR_MIN_FILTER;
+ case GL_TEXTURE_SWIZZLE_R: return TEXTURE_PAR_SWIZZLE_R;
+ case GL_TEXTURE_SWIZZLE_B: return TEXTURE_PAR_SWIZZLE_B;
+ default: return std::nullopt;
+ }
+}
+
+void CGLTexture::setTexParameter(GLenum pname, GLint param) {
+ const auto cacheIndex = getCacheStateIndex(pname);
+
+ if (!cacheIndex) {
+ GLCALL(glTexParameteri(m_target, pname, param));
+ return;
+ }
+
+ const auto idx = cacheIndex.value();
+
+ if (m_cachedStates[idx] == param)
+ return;
+
+ m_cachedStates[idx] = param;
+ GLCALL(glTexParameteri(m_target, pname, param));
+}
+
+void CGLTexture::swizzle(const std::array<GLint, 4>& colors) {
+ setTexParameter(GL_TEXTURE_SWIZZLE_R, colors.at(0));
+ setTexParameter(GL_TEXTURE_SWIZZLE_G, colors.at(1));
+ setTexParameter(GL_TEXTURE_SWIZZLE_B, colors.at(2));
+ setTexParameter(GL_TEXTURE_SWIZZLE_A, colors.at(3));
+}
diff --git a/src/render/gl/GLTexture.hpp b/src/render/gl/GLTexture.hpp
new file mode 100644
index 00000000..34510e90
--- /dev/null
+++ b/src/render/gl/GLTexture.hpp
@@ -0,0 +1,49 @@
+#pragma once
+
+#include "../Texture.hpp"
+#include <aquamarine/buffer/Buffer.hpp>
+#include <hyprutils/math/Misc.hpp>
+
+class CGLTexture : public ITexture {
+ public:
+ using ITexture::ITexture;
+
+ CGLTexture(CGLTexture&) = delete;
+ CGLTexture(CGLTexture&&) = delete;
+ CGLTexture(const CGLTexture&&) = delete;
+ CGLTexture(const CGLTexture&) = delete;
+
+ CGLTexture(bool opaque = false);
+ CGLTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false, bool opaque = false);
+ CGLTexture(const Aquamarine::SDMABUFAttrs&, void* image, bool opaque = false);
+ CGLTexture(std::span<const float> lut3D, size_t N);
+ ~CGLTexture();
+
+ void allocate(const Vector2D& size, uint32_t drmFormat = 0) override;
+ void update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage) override;
+ void bind() override;
+ void unbind() override;
+ void setTexParameter(GLenum pname, GLint param) override;
+ bool ok() override;
+ bool isDMA() override;
+
+ private:
+ void* m_eglImage = nullptr;
+
+ enum eTextureParam : uint8_t {
+ TEXTURE_PAR_WRAP_S = 0,
+ TEXTURE_PAR_WRAP_T,
+ TEXTURE_PAR_MAG_FILTER,
+ TEXTURE_PAR_MIN_FILTER,
+ TEXTURE_PAR_SWIZZLE_R,
+ TEXTURE_PAR_SWIZZLE_B,
+ TEXTURE_PAR_LAST,
+ };
+
+ GLenum m_target = GL_TEXTURE_2D;
+
+ void swizzle(const std::array<GLint, 4>& colors);
+ constexpr std::optional<size_t> getCacheStateIndex(GLenum pname);
+
+ std::array<std::optional<GLint>, TEXTURE_PAR_LAST> m_cachedStates;
+};