LazOpenGLContextEx: TOpenGLControl с Qt-бэкендом на QOpenGLWidget

Форк штатного пакета LazOpenGLContext (Lazarus components/opengl).
Qt5/Qt6-бэкенд (glqtnativecontext.pas) использует собственный контекст
настоящего QOpenGLWidget вместо ручного GLX на winId(): EGL на Wayland,
GLX на X11, WGL/CGL на Windows/macOS под ws=qt6.

QOpenGLWidget-наследник с paintGL-хуком живёт в маленькой C++-либе
csrc/qlclglwidget.cpp (QLCLOpenGLWidget из libQt6Pas — не QOpenGLWidget,
а голый QWidget под внешний GLX). Сборка: csrc/Makefile
(linux/macos/windows-кросс mingw-w64/windows-native MSYS2, install,
install-app) + build-msvc.bat для MSVC-Qt. Либа грузится в рантайме
рядом с бинарником приложения.

Бэкенды gtk2/gtk3/win32/cocoa скопированы из стока без изменений
(юниты переименованы с суффиксом Ex). Детали и нюансы — в README.md.

Проверено: EWSDR, спектр/водопад через GL в нативном Wayland (KDE, qt6),
живой эфир QO-100.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 15:00:26 +03:00
co-authored by Claude Fable 5
commit ac46e3d13a
14 changed files with 4041 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
# ----------------------------------------------------------------------------
# libqlclglwidget — QOpenGLWidget helper library for LazOpenGLContextEx
#
# Build variants:
# make / make linux native Linux build -> ../libqlclglwidget.so
# make macos native macOS build (on a Mac) -> ../libqlclglwidget.dylib
# make windows qlclglwidget.dll with a MinGW toolchain:
# * cross-build from Linux (mingw-w64), OR
# * natively on Windows with a Qt MinGW kit that has
# no pkg-config — override the variables:
# make windows MINGW_CXX=g++ \
# WIN_QT_INC=C:/Qt/6.x.x/mingw_64/include \
# WIN_QT_LIB=C:/Qt/6.x.x/mingw_64/lib
# make windows-native natively on Windows in an MSYS2 MinGW64 shell
# (uses pkg-config; auto-selected by plain `make` there)
# build-msvc.bat natively on Windows with MSVC (see the .bat file);
# required when the target Qt6/libQt6Pas is an MSVC
# build — MinGW and MSVC C++ ABIs are incompatible
#
# make install install the host-platform artifact into $(PREFIX)/lib
# (DESTDIR supported; run ldconfig yourself on Linux)
# make install-app APPDIR=/path/to/app/dir
# copy every built artifact next to the app binary
# (the Pascal loader looks there first)
# make clean
#
# Windows cross-build requirements (on the Linux build machine):
# - mingw-w64 toolchain: x86_64-w64-mingw32-g++
# - a *MinGW* build of Qt6 (headers + import libs), one of:
# * Arch AUR: mingw-w64-qt6-base (installs to /usr/x86_64-w64-mingw32)
# * MXE (https://mxe.cc) with qt6 (set MINGW_PREFIX to the MXE usr dir)
# * a Qt "MinGW" kit copied from a Windows Qt install
# (set WIN_QT_INC / WIN_QT_LIB manually)
#
# Windows native (MSYS2 MinGW64 shell) requirements:
# pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-qt6-base pkgconf make
#
# The MinGW-built DLL works only with MinGW Qt6 DLLs on the target machine,
# the MSVC-built DLL only with MSVC Qt6 DLLs (incompatible C++ ABIs).
#
# macOS notes: needs Xcode command line tools + Qt6 with pkg-config files,
# e.g. Homebrew: brew install qt pkgconf
# export PKG_CONFIG_PATH="$(brew --prefix qt)/libexec/lib/pkgconfig"
# ----------------------------------------------------------------------------
NAME := qlclglwidget
SRC := qlclglwidget.cpp
OUTDIR := ..
PREFIX ?= /usr/local
DESTDIR ?=
CXXFLAGS ?= -O2
CXXSTD := -std=c++17
PKG_CONFIG ?= pkg-config
QT_PKGS := Qt6Widgets Qt6OpenGLWidgets
# Windows cross toolchain / Qt location (override as needed)
MINGW_CXX ?= x86_64-w64-mingw32-g++
MINGW_PREFIX ?= /usr/x86_64-w64-mingw32
WIN_QT_INC ?= $(MINGW_PREFIX)/include/qt6
WIN_QT_LIB ?= $(MINGW_PREFIX)/lib
WIN_QT_LIBS ?= -lQt6Widgets -lQt6OpenGLWidgets -lQt6Gui -lQt6Core
LINUX_OUT := $(OUTDIR)/lib$(NAME).so
MACOS_OUT := $(OUTDIR)/lib$(NAME).dylib
WIN_OUT := $(OUTDIR)/$(NAME).dll
UNAME := $(shell uname -s)
ifeq ($(UNAME),Darwin)
HOST_OUT := $(MACOS_OUT)
HOST_TARGET := macos
else ifneq (,$(findstring MINGW,$(UNAME))$(findstring MSYS,$(UNAME)))
# MSYS2 MinGW64 shell on Windows
HOST_OUT := $(WIN_OUT)
HOST_TARGET := windows-native
else
HOST_OUT := $(LINUX_OUT)
HOST_TARGET := linux
endif
.PHONY: all linux macos windows windows-native install install-app clean
all: $(HOST_TARGET)
linux: $(LINUX_OUT)
$(LINUX_OUT): $(SRC)
$(CXX) $(CXXFLAGS) $(CXXSTD) -shared -fPIC \
$(shell $(PKG_CONFIG) --cflags $(QT_PKGS)) \
-o $@ $(SRC) \
$(shell $(PKG_CONFIG) --libs $(QT_PKGS))
macos: $(MACOS_OUT)
$(MACOS_OUT): $(SRC)
$(CXX) $(CXXFLAGS) $(CXXSTD) -dynamiclib -fPIC \
$(shell $(PKG_CONFIG) --cflags $(QT_PKGS)) \
-o $@ $(SRC) \
$(shell $(PKG_CONFIG) --libs $(QT_PKGS)) \
-Wl,-install_name,@rpath/lib$(NAME).dylib
# MinGW build: cross from Linux, or native on Windows with a Qt MinGW kit
# (no pkg-config needed — plain include/lib paths)
windows: $(WIN_OUT)
$(WIN_OUT): $(SRC)
$(MINGW_CXX) $(CXXFLAGS) $(CXXSTD) -shared \
-I$(WIN_QT_INC) \
-I$(WIN_QT_INC)/QtCore \
-I$(WIN_QT_INC)/QtGui \
-I$(WIN_QT_INC)/QtWidgets \
-I$(WIN_QT_INC)/QtOpenGL \
-I$(WIN_QT_INC)/QtOpenGLWidgets \
-o $@ $(SRC) \
-L$(WIN_QT_LIB) $(WIN_QT_LIBS) \
-static-libgcc -static-libstdc++ \
-Wl,--out-implib,$(OUTDIR)/lib$(NAME).dll.a
# native Windows build in an MSYS2 MinGW64 shell (pkg-config available)
windows-native:
$(CXX) $(CXXFLAGS) $(CXXSTD) -shared \
$(shell $(PKG_CONFIG) --cflags $(QT_PKGS)) \
-o $(WIN_OUT) $(SRC) \
$(shell $(PKG_CONFIG) --libs $(QT_PKGS)) \
-static-libgcc -static-libstdc++ \
-Wl,--out-implib,$(OUTDIR)/lib$(NAME).dll.a
install: $(HOST_OUT)
install -d $(DESTDIR)$(PREFIX)/lib
install -m755 $(HOST_OUT) $(DESTDIR)$(PREFIX)/lib/
install-app:
@test -n "$(APPDIR)" || { echo "usage: make install-app APPDIR=/path/to/app/dir"; exit 1; }
install -d $(APPDIR)
@for f in $(LINUX_OUT) $(MACOS_OUT) $(WIN_OUT); do \
if [ -f $$f ]; then install -m755 $$f $(APPDIR)/ && echo "installed $$f -> $(APPDIR)/"; fi; \
done
clean:
rm -f $(LINUX_OUT) $(MACOS_OUT) $(WIN_OUT) \
$(OUTDIR)/lib$(NAME).dll.a $(OUTDIR)/$(NAME).lib $(OUTDIR)/$(NAME).exp \
$(NAME).obj
+43
View File
@@ -0,0 +1,43 @@
@echo off
rem ---------------------------------------------------------------------------
rem Builds qlclglwidget.dll natively on Windows with MSVC.
rem
rem Use this when the target Qt6 / libQt6Pas is an MSVC build (the official
rem Qt online-installer binaries are MSVC). A MinGW-built DLL cannot link
rem against MSVC Qt DLLs - the C++ ABIs are incompatible.
rem
rem How to run:
rem 1. Open "x64 Native Tools Command Prompt for VS" (vcvars64 environment).
rem 2. set QTDIR=C:\Qt\6.7.2\msvc2019_64 (your MSVC Qt kit)
rem 3. build-msvc.bat
rem
rem Output: ..\qlclglwidget.dll (put it next to the application executable).
rem ---------------------------------------------------------------------------
if "%QTDIR%"=="" (
echo error: set QTDIR to your MSVC Qt kit first, e.g.:
echo set QTDIR=C:\Qt\6.7.2\msvc2019_64
exit /b 1
)
if not exist "%QTDIR%\include\QtOpenGLWidgets" (
echo error: %QTDIR%\include\QtOpenGLWidgets not found - is QTDIR an MSVC Qt6 kit?
exit /b 1
)
cl /nologo /LD /EHsc /std:c++17 /permissive- /Zc:__cplusplus /MD /O2 ^
/I"%QTDIR%\include" ^
/I"%QTDIR%\include\QtCore" ^
/I"%QTDIR%\include\QtGui" ^
/I"%QTDIR%\include\QtWidgets" ^
/I"%QTDIR%\include\QtOpenGL" ^
/I"%QTDIR%\include\QtOpenGLWidgets" ^
qlclglwidget.cpp /Fe:..\qlclglwidget.dll ^
/link /LIBPATH:"%QTDIR%\lib" Qt6Widgets.lib Qt6OpenGLWidgets.lib Qt6Gui.lib Qt6Core.lib
if errorlevel 1 (
echo build FAILED
exit /b 1
)
del qlclglwidget.obj ..\qlclglwidget.exp ..\qlclglwidget.lib 2>nul
echo built ..\qlclglwidget.dll
+124
View File
@@ -0,0 +1,124 @@
//******************************************************************************
// qlclglwidget - a real QOpenGLWidget subclass with a Pascal paintGL hook.
//
// libQt6Pas's QLCLOpenGLWidget is NOT a QOpenGLWidget: it is a plain QWidget
// with WA_NativeWindow/paintEngine()=nullptr, designed as a target for an
// external GLX context (X11 only). This tiny library provides the missing
// piece: a genuine QOpenGLWidget whose context is created and managed by Qt
// itself (EGL on Wayland, GLX on X11, WGL on Windows, CGL on macOS), plus
// flat C exports for the methods the Lazarus side needs.
//
// Build: make (Linux .so / macOS .dylib)
// make windows (cross-build .dll with mingw-w64, see Makefile)
// make install (into /usr/local/lib)
//******************************************************************************
#include <QOpenGLWidget>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QSurfaceFormat>
// mirrors QHook/QOverrideHook from libQt6Pas pascalbind.h:
// an FPC "procedure of object; cdecl" method pointer passed by value
typedef struct {
void *func;
void *data;
} QGLOverrideHook;
class QLCLGLWidget : public QOpenGLWidget {
public:
QGLOverrideHook paintGLHook;
// The widget renders into an RGBA FBO that Qt composites with alpha into
// the window. Legacy GL code written for GLX/WGL windows leaves arbitrary
// alpha in the framebuffer (there it was simply ignored), which makes the
// widget translucent here. Unless the user explicitly asked for an alpha
// channel, force alpha to 1 after each paint.
bool forceOpaque;
explicit QLCLGLWidget(QWidget *parent = nullptr,
Qt::WindowFlags flags = Qt::WindowFlags())
: QOpenGLWidget(parent, flags) {
paintGLHook.func = nullptr;
paintGLHook.data = nullptr;
forceOpaque = true;
}
protected:
// Qt makes the context current and binds the widget's FBO before this call
void paintGL() override {
if (paintGLHook.func) {
typedef void (*func_type)(void *data);
(*(func_type)paintGLHook.func)(paintGLHook.data);
if (forceOpaque)
fillAlpha();
} else {
QOpenGLWidget::paintGL();
}
}
private:
void fillAlpha() {
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
GLboolean mask[4];
GLfloat clearColor[4];
f->glGetBooleanv(GL_COLOR_WRITEMASK, mask);
f->glGetFloatv(GL_COLOR_CLEAR_VALUE, clearColor);
GLboolean scissor = f->glIsEnabled(GL_SCISSOR_TEST);
if (scissor)
f->glDisable(GL_SCISSOR_TEST);
f->glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
f->glClearColor(0.f, 0.f, 0.f, 1.f);
f->glClear(GL_COLOR_BUFFER_BIT);
f->glColorMask(mask[0], mask[1], mask[2], mask[3]);
f->glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
if (scissor)
f->glEnable(GL_SCISSOR_TEST);
}
};
extern "C" {
Q_DECL_EXPORT void *QLCLGLWidget_Create(void *parent, unsigned int flags) {
return (void *)new QLCLGLWidget((QWidget *)parent, (Qt::WindowFlags)flags);
}
Q_DECL_EXPORT void QLCLGLWidget_Destroy(void *handle) {
delete (QLCLGLWidget *)handle;
}
Q_DECL_EXPORT void QLCLGLWidget_override_paintGL(void *handle,
QGLOverrideHook hook) {
((QLCLGLWidget *)handle)->paintGLHook = hook;
}
Q_DECL_EXPORT void QLCLGLWidget_makeCurrent(void *handle) {
((QLCLGLWidget *)handle)->makeCurrent();
}
Q_DECL_EXPORT void QLCLGLWidget_doneCurrent(void *handle) {
((QLCLGLWidget *)handle)->doneCurrent();
}
Q_DECL_EXPORT bool QLCLGLWidget_isValid(void *handle) {
return ((QLCLGLWidget *)handle)->isValid();
}
// fmt is a QSurfaceFormatH created via libQt6Pas QSurfaceFormat_Create
Q_DECL_EXPORT void QLCLGLWidget_setFormat(void *handle, void *fmt) {
((QLCLGLWidget *)handle)->setFormat(*(const QSurfaceFormat *)fmt);
}
// enabled by default; pass false when the control requests AlphaBits > 0
Q_DECL_EXPORT void QLCLGLWidget_setForceOpaque(void *handle, bool enable) {
((QLCLGLWidget *)handle)->forceOpaque = enable;
}
Q_DECL_EXPORT unsigned int QLCLGLWidget_defaultFramebufferObject(void *handle) {
return ((QLCLGLWidget *)handle)->defaultFramebufferObject();
}
Q_DECL_EXPORT double QLCLGLWidget_devicePixelRatioF(void *handle) {
return ((QLCLGLWidget *)handle)->devicePixelRatioF();
}
} // extern "C"