Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
QtSurface.java
Go to the documentation of this file.
1// Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
2// Copyright (C) 2026 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5package org.qtproject.qt.android;
6
7import android.annotation.SuppressLint;
8import android.content.Context;
9import android.content.res.TypedArray;
10import android.graphics.PixelFormat;
11import android.graphics.Rect;
12import android.opengl.EGL14;
13import android.opengl.EGLConfig;
14import android.opengl.EGLContext;
15import android.opengl.EGLDisplay;
16import android.opengl.EGLSurface;
17import android.opengl.GLES20;
18import android.os.Build;
19import android.util.Log;
20import android.view.Surface;
21import android.view.SurfaceControl;
22import android.view.SurfaceHolder;
23import android.view.SurfaceView;
24
25@SuppressLint("ViewConstructor")
26class QtSurface extends SurfaceView implements SurfaceHolder.Callback2
27{
28 private final int m_format;
29 private final QtSurfaceInterface m_surfaceCallback;
30 private boolean m_backgroundPrimed = false;
31 private SurfaceControl m_renderControl;
32 private Surface m_renderSurface;
33
34 QtSurface(Context context, QtSurfaceInterface surfaceCallback, boolean onTop, int imageDepth)
35 {
36 super(context);
37 setFocusable(false);
38 setFocusableInTouchMode(false);
39 setZOrderMediaOverlay(onTop);
40 m_surfaceCallback = surfaceCallback;
41 m_format = imageDepth == 16 ? PixelFormat.RGB_565 : PixelFormat.RGBA_8888;
42 SurfaceHolder holder = getHolder();
43 holder.setFormat(m_format);
44 holder.addCallback(this);
45 }
46
48 public void surfaceCreated(SurfaceHolder holder)
49 {
50 // Prime before Qt gets the Surface so the two producers never collide.
51 final Rect frame = holder.getSurfaceFrame();
52 m_backgroundPrimed = frame != null && frame.width() > 0 && frame.height() > 0
53 && paintBackgroundColor(holder.getSurface());
54 if (Build.VERSION.SDK_INT >= 29) {
55 // The holder forcibly disconnects its queue when this view disappears.
56 final SurfaceControl control = new SurfaceControl.Builder()
57 .setName("QtSurface")
58 .setParent(getSurfaceControl())
59 .setBufferSize(Math.max(1, getWidth()), Math.max(1, getHeight()))
60 .setFormat(m_format)
61 .build();
62 try {
63 initializeRenderSurface(control, new Surface(control));
64 } catch (RuntimeException | Error failure) {
65 try {
66 control.release();
67 } catch (RuntimeException | Error cleanupFailure) {
68 failure.addSuppressed(cleanupFailure);
69 }
70 throw failure;
71 }
72 }
73 if (m_surfaceCallback != null)
74 m_surfaceCallback.onSurfaceCreated(
75 m_renderSurface != null ? m_renderSurface : holder.getSurface());
76 }
77
78 private void initializeRenderSurface(SurfaceControl control, Surface surface)
79 {
80 try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
81 transaction.setVisibility(control, true).apply();
82 } catch (RuntimeException | Error failure) {
83 try {
84 surface.release();
85 } catch (RuntimeException | Error cleanupFailure) {
86 failure.addSuppressed(cleanupFailure);
87 }
88 throw failure;
89 }
90 m_renderControl = control;
91 m_renderSurface = surface;
92 }
93
95 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
96 {
97 if (m_renderControl != null) {
98 try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
99 transaction.setBufferSize(m_renderControl, width, height).apply();
100 }
101 }
102 // Only an independent holder can be primed after Qt has its producer.
103 if (!m_backgroundPrimed && m_renderSurface != null && width > 0 && height > 0)
104 m_backgroundPrimed = paintBackgroundColor(holder.getSurface());
105 }
106
107 @Override
108 public void surfaceRedrawNeeded(SurfaceHolder holder)
109 {
110 // Only reached through the default surfaceRedrawNeededAsync().
111 }
112
113 @Override
114 public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable drawingFinished)
115 {
116 // SurfaceView's redraw synchronization still consumes a holder buffer.
117 if (m_renderSurface != null)
118 paintBackgroundColor(holder.getSurface());
119 if (m_surfaceCallback != null)
120 m_surfaceCallback.onSurfaceRedrawNeeded(drawingFinished);
121 else
122 drawingFinished.run();
123 }
124
125 @Override
126 public void surfaceDestroyed(SurfaceHolder holder)
127 {
128 final Surface surface = m_renderSurface;
129 final SurfaceControl control = m_renderControl;
130 m_renderSurface = null;
131 m_renderControl = null;
132 final Runnable released = () -> {
133 if (surface != null)
134 surface.release();
135 if (control != null)
136 control.release();
137 };
138 Throwable detachFailure = null;
139 try {
140 if (control != null) {
141 try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
142 transaction.reparent(control, null).apply();
143 }
144 }
145 } catch (RuntimeException | Error failure) {
146 detachFailure = failure;
147 throw failure;
148 } finally {
149 try {
150 if (m_surfaceCallback != null)
151 m_surfaceCallback.onSurfaceDestroyed(released);
152 else
153 released.run();
154 } catch (RuntimeException | Error failure) {
155 if (detachFailure == null)
156 throw failure;
157 detachFailure.addSuppressed(failure);
158 }
159 }
160 }
161
162 // Fill one buffer with colorBackground to avoid a black flash. lockCanvas()
163 // would leave a producer connected and break Qt's eglCreateWindowSurface().
164 private boolean paintBackgroundColor(Surface surface)
165 {
166 if (surface == null || !surface.isValid()) {
167 Log.w(QtNative.QtTAG, "Cannot prime an invalid background Surface");
168 return false;
169 }
170
171 final EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
172 if (display == EGL14.EGL_NO_DISPLAY)
173 return backgroundEglFailure("get display");
174
175 final int[] version = new int[2];
176 if (!EGL14.eglInitialize(display, version, 0, version, 1))
177 return backgroundEglFailure("initialize display");
178
179 final EGLConfig[] configs = new EGLConfig[1];
180 final int[] matching = new int[1];
181 final int[] configAttribs = {
182 EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT,
183 EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
184 EGL14.EGL_RED_SIZE, 8,
185 EGL14.EGL_GREEN_SIZE, 8,
186 EGL14.EGL_BLUE_SIZE, 8,
187 EGL14.EGL_ALPHA_SIZE, 8,
188 EGL14.EGL_NONE
189 };
190
191 if (!EGL14.eglChooseConfig(display, configAttribs, 0, configs, 0, 1, matching, 0))
192 return backgroundEglFailure("choose config");
193
194 if (matching[0] == 0) {
195 Log.w(QtNative.QtTAG, "No EGL config for the background Surface");
196 return false;
197 }
198
199 final int[] contextAttribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE };
200 final EGLContext context = EGL14.eglCreateContext(display, configs[0],
201 EGL14.EGL_NO_CONTEXT, contextAttribs, 0);
202 if (context == EGL14.EGL_NO_CONTEXT)
203 return backgroundEglFailure("create context");
204
205 final EGLSurface eglSurface = EGL14.eglCreateWindowSurface(display, configs[0],
206 surface, new int[] { EGL14.EGL_NONE }, 0);
207 if (eglSurface == EGL14.EGL_NO_SURFACE) {
208 backgroundEglFailure("create window surface");
209 if (!EGL14.eglDestroyContext(display, context))
210 backgroundEglFailure("destroy context");
211 return false;
212 }
213
214 // Embedded hosts may already have an EGL context current on this thread.
215 final EGLDisplay previousDisplay = EGL14.eglGetCurrentDisplay();
216 final EGLContext previousContext = EGL14.eglGetCurrentContext();
217 final EGLSurface previousDraw = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW);
218 final EGLSurface previousRead = EGL14.eglGetCurrentSurface(EGL14.EGL_READ);
219 boolean madeCurrent = false;
220 try {
221 madeCurrent = EGL14.eglMakeCurrent(display, eglSurface, eglSurface, context);
222 if (!madeCurrent) {
223 Log.w(QtNative.QtTAG, "Cannot bind the background EGL context: 0x"
224 + Integer.toHexString(EGL14.eglGetError()));
225 return false;
226 }
227 final int color = resolveBackgroundColor();
228 GLES20.glClearColor(
229 ((color >> 16) & 0xff) / 255f,
230 ((color >> 8) & 0xff) / 255f,
231 (color & 0xff) / 255f,
232 ((color >>> 24) & 0xff) / 255f);
233 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
234 if (!EGL14.eglSwapBuffers(display, eglSurface)) {
235 Log.w(QtNative.QtTAG, "Cannot post the background EGL buffer: 0x"
236 + Integer.toHexString(EGL14.eglGetError()));
237 return false;
238 }
239 return true;
240 } finally {
241 if (madeCurrent) {
242 final EGLDisplay restoreDisplay = previousDisplay.equals(EGL14.EGL_NO_DISPLAY)
243 ? display : previousDisplay;
244 if (!EGL14.eglMakeCurrent(restoreDisplay, previousDraw,
245 previousRead, previousContext)) {
246 Log.w(QtNative.QtTAG, "Cannot restore the UI thread EGL binding: 0x"
247 + Integer.toHexString(EGL14.eglGetError()));
248 if (!EGL14.eglMakeCurrent(display, EGL14.EGL_NO_SURFACE,
249 EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT)) {
250 Log.w(QtNative.QtTAG, "Cannot release the background EGL binding: 0x"
251 + Integer.toHexString(EGL14.eglGetError()));
252 }
253 }
254 }
255 if (!EGL14.eglDestroySurface(display, eglSurface)) {
256 Log.w(QtNative.QtTAG, "Cannot destroy the background EGL surface: 0x"
257 + Integer.toHexString(EGL14.eglGetError()));
258 }
259 if (!EGL14.eglDestroyContext(display, context)) {
260 Log.w(QtNative.QtTAG, "Cannot destroy the background EGL context: 0x"
261 + Integer.toHexString(EGL14.eglGetError()));
262 }
263 }
264 }
265
266 private static boolean backgroundEglFailure(String operation)
267 {
268 Log.w(QtNative.QtTAG, "Cannot " + operation + " for the background EGL buffer: 0x"
269 + Integer.toHexString(EGL14.eglGetError()));
270 return false;
271 }
272
273 private int resolveBackgroundColor()
274 {
275 TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(
276 new int[] {
277 android.R.attr.windowSplashScreenBackground,
278 android.R.attr.colorBackground
279 });
280 try {
281 // Android 12+ has an API for splash, so prefer that.
282 final int splash = typedArray.getColor(0, 0);
283 if ((splash >>> 24) != 0)
284 return splash | 0xff000000;
285 return typedArray.getColor(1, 0xff000000) | 0xff000000;
286 } finally {
287 typedArray.recycle();
288 }
289 }
290}
int width
the width of the widget excluding any window frame
Definition qwidget.h:114
int height
the height of the widget excluding any window frame
Definition qwidget.h:115
QPainter Context
struct wl_display * display
Definition linuxdmabuf.h:47
Napi::TypedArray TypedArray
Definition qnapi_p.h:76
QVideoFrameFormat::PixelFormat PixelFormat
typedef EGLSurface(EGLAPIENTRYP PFNEGLCREATESTREAMPRODUCERSURFACEKHRPROC)(EGLDisplay dpy
typedef EGLDisplay(EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum platform
GLfloat GLfloat f
[26]
GLuint color
[setDefaultFactory]
GLint GLsizei width
EGLint EGLint EGLint format
QFrame frame
[0]
QSplashScreen * splash