diff --git a/gapic/src/platform/BUILD.bazel b/gapic/src/platform/BUILD.bazel index 9ca9c30840..0f7e2a668d 100644 --- a/gapic/src/platform/BUILD.bazel +++ b/gapic/src/platform/BUILD.bazel @@ -24,4 +24,32 @@ java_library( "//gapic/third_party:lwjgl", "//gapic/third_party:swt", ], + resources = select({ + "//tools/build:linux": [":liblinux_glcanvas.so"], + "//conditions:default": [], + }), + resource_strip_prefix = "gapic/src/platform/", +) + +cc_binary( + name = "liblinux_glcanvas.so", + srcs = ["linux/glcanvas.cc"], + deps = [ + ":jni.h", + ":jni_md.h" + ], + linkopts = ["-lGL", "-lX11"], + linkshared = 1, +) + +cc_library( + name = "jni.h", + hdrs = ["@local_jdk//:jni_header"], + strip_include_prefix = "/external/local_jdk/include", +) + +cc_library( + name = "jni_md.h", + hdrs = ["@local_jdk//:jni_md_header-linux"], + strip_include_prefix = "/external/local_jdk/include/linux", ) diff --git a/gapic/src/platform/linux/com/google/gapid/glcanvas/GlCanvas.java b/gapic/src/platform/linux/com/google/gapid/glcanvas/GlCanvas.java index a731fc6639..6e28cdfbbb 100644 --- a/gapic/src/platform/linux/com/google/gapid/glcanvas/GlCanvas.java +++ b/gapic/src/platform/linux/com/google/gapid/glcanvas/GlCanvas.java @@ -15,36 +15,204 @@ */ package com.google.gapid.glcanvas; -import org.eclipse.swt.opengl.GLCanvas; -import org.eclipse.swt.opengl.GLData; +import static java.util.logging.Level.SEVERE; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.internal.DPIUtil; +import org.eclipse.swt.internal.gtk.GdkWindowAttr; +import org.eclipse.swt.internal.gtk.OS; +import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; +import org.lwjgl.PointerBuffer; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GLX; +import org.lwjgl.opengl.GLX13; +import org.lwjgl.opengl.GLX14; +import org.lwjgl.opengl.GLXCapabilities; +import org.lwjgl.system.Library; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.linux.X11; +import org.lwjgl.system.linux.XVisualInfo; + +import java.nio.IntBuffer; +import java.util.logging.Logger; + +public abstract class GlCanvas extends Canvas { + private static final Logger LOG = Logger.getLogger(GlCanvas.class.getName()); + + private static final boolean jniLibraryLoaded; + + static { + boolean loaded = false; + try { + Library.loadSystem("linux_glcanvas"); + loaded = true; + } catch (UnsatisfiedLinkError e) { + LOG.log(SEVERE, "Failed to load GlCanvas JNI library", e); + } + jniLibraryLoaded = loaded; + } + + private long gdkWindow; + private long xWindow; + private long context; -// On linux, simply use the SWT GLCanvas as it works fine out of the box. -public abstract class GlCanvas extends GLCanvas { public GlCanvas(Composite parent, int style) { - super(parent, style, getGlData()); - // TODO: hook in the the terminate - currently the dispose event would always be *after* the - // parent's dispose handling, which destroys the context and looses the context pointer. The - // below can thus lead to crashes if any context is bound on the current thread (UI thread, so - // this does indeed happen). - //addListener(SWT.Dispose, e -> terminate()); + super(parent, style); + if (jniLibraryLoaded && createContext()) { + addListener(SWT.Resize, e -> { + Rectangle clientArea = DPIUtil.autoScaleUp(getClientArea()); + OS.gdk_window_move(gdkWindow, clientArea.x, clientArea.y); + OS.gdk_window_resize(gdkWindow, clientArea.width, clientArea.height); + }); + addListener(SWT.Dispose, e -> { + long display = getXDisplay(); + if (context != 0) { + terminate(); + GLX.glXMakeCurrent(display, 0, 0); + GLX.glXDestroyContext(display, context); + context = 0; + } + if (gdkWindow != 0) { + OS.gdk_window_destroy(gdkWindow); + gdkWindow = 0; + } + }); + } } - private static GLData getGlData() { - GLData result = new GLData(); - result.doubleBuffer = true; - result.sampleBuffers = 1; - result.samples = 4; - return result; + private boolean createContext() { + OS.gtk_widget_realize(handle); + long display = getXDisplay(); + int screen = X11.XDefaultScreen(display); + + GLXCapabilities glxCaps = GL.createCapabilitiesGLX(display, screen); + if (!glxCaps.GLX13 || !glxCaps.GLX_ARB_create_context || !glxCaps.GLX_ARB_create_context_profile) { + LOG.log(SEVERE, "Inssufficient GLX capabilities. GLX13: " + glxCaps.GLX13 + + " GLX_ARB_create_context: " + glxCaps.GLX_ARB_create_context + + " GLX_ARB_create_context_profile: " + glxCaps.GLX_ARB_create_context_profile); + return false; + } + + long config = chooseConfig(glxCaps, display); + if (config == -1) { + return false; + } + + if (!createWindow(display, config)) { + return false; + } + + context = createContext0(display, config); + if (context == 0) { + LOG.log(SEVERE, "Failed to create an OpenGL 3.2 Core context"); + return false; + } + + return true; } - public boolean isOpenGL() { - // TODO: do our own initialization, so we can handle the failure case. + private static long chooseConfig(GLXCapabilities glxCaps, long display) { + try (MemoryStack stack = MemoryStack.stackPush()) { + IntBuffer attr = stack.mallocInt(60); + set(attr, GLX13.GLX_X_RENDERABLE, 1); + set(attr, GLX13.GLX_DRAWABLE_TYPE, GLX13.GLX_WINDOW_BIT); + set(attr, GLX13.GLX_RENDER_TYPE, GLX13.GLX_RGBA_BIT); + set(attr, GLX13.GLX_X_VISUAL_TYPE, GLX13.GLX_TRUE_COLOR); + set(attr, GLX.GLX_DOUBLEBUFFER, 1); + set(attr, GLX.GLX_RED_SIZE, 8); + set(attr, GLX.GLX_GREEN_SIZE, 8); + set(attr, GLX.GLX_BLUE_SIZE, 8); + set(attr, GLX.GLX_DEPTH_SIZE, 24); + if (glxCaps.GLX14 || glxCaps.GLX_ARB_multisample) { + set(attr, GLX14.GLX_SAMPLE_BUFFERS, 1); + set(attr, GLX14.GLX_SAMPLES, 4); + } + set(attr, X11.None, X11.None); + + attr.flip(); + PointerBuffer configs = GLX13.glXChooseFBConfig(display, X11.XDefaultScreen(display), attr); + if (configs == null || configs.capacity() < 1) { + LOG.log(SEVERE, "glXChooseFBConfig returned no matching configs"); + return -1; + } + + long config = configs.get(0); + X11.XFree(configs); + return config; + } + } + + private boolean createWindow(long display, long config) { + try (XVisualInfo visual = GLX13.glXGetVisualFromFBConfig(display, config)) { + if (visual == null) { + LOG.log(SEVERE, "glXGetVisualFromFBConfig returned null"); + return false; + } + + GdkWindowAttr attrs = new GdkWindowAttr(); + attrs.width = 1; + attrs.height = 1; + attrs.event_mask = OS.GDK_KEY_PRESS_MASK | OS.GDK_KEY_RELEASE_MASK | + OS.GDK_FOCUS_CHANGE_MASK | OS.GDK_POINTER_MOTION_MASK | + OS.GDK_BUTTON_PRESS_MASK | OS.GDK_BUTTON_RELEASE_MASK | + OS.GDK_ENTER_NOTIFY_MASK | OS.GDK_LEAVE_NOTIFY_MASK | + OS.GDK_EXPOSURE_MASK | OS.GDK_POINTER_MOTION_HINT_MASK; + attrs.window_type = OS.GDK_WINDOW_CHILD; + attrs.visual = + OS.gdk_x11_screen_lookup_visual(OS.gdk_screen_get_default(), (int)visual.visualid()); + gdkWindow = OS.gdk_window_new(OS.gtk_widget_get_window(handle), attrs, OS.GDK_WA_VISUAL); + if (gdkWindow == 0) { + LOG.log(SEVERE, "Failed to create the GDK window"); + return false; + } + OS.gdk_window_set_user_data(gdkWindow, handle); + } + + xWindow = (OS.GTK3) ? + OS.gdk_x11_window_get_xid(gdkWindow) : OS.gdk_x11_drawable_get_xid(gdkWindow); + OS.gdk_window_show(gdkWindow); return true; } + private static void set(IntBuffer buf, int name, int value) { + buf.put(name).put(value); + } + + private static native long createContext0(long display, long config); + + public boolean isOpenGL() { + return context != 0; + } + + public void setCurrent() { + if (context == 0) { + return; + } + + checkWidget(); + GLX.glXMakeCurrent(getXDisplay(), xWindow, context); + } + + public void swapBuffers () { + if (context == 0) { + return; + } + + checkWidget(); + GLX.glXSwapBuffers(getXDisplay(), xWindow); + } + /** * Override to perform GL cleanup handling. */ protected abstract void terminate(); + + private long getXDisplay() { + long window = OS.gtk_widget_get_window(handle); + return (OS.GTK_VERSION >= OS.VERSION(2, 24, 0)) ? + OS.gdk_x11_display_get_xdisplay(OS.gdk_window_get_display(window)) : + OS.gdk_x11_drawable_get_xdisplay(window); + } } diff --git a/gapic/src/platform/linux/glcanvas.cc b/gapic/src/platform/linux/glcanvas.cc new file mode 100644 index 0000000000..bf00dcc8e1 --- /dev/null +++ b/gapic/src/platform/linux/glcanvas.cc @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Native JNI helper function to create an OpenGL 3.2 Core context. + * This is done in native code to catch the X11 error, when creating + * the context, to prevent it from taking down the whole process. + */ +JNIEXPORT jlong JNICALL Java_com_google_gapid_glcanvas_GlCanvas_createContext0( + JNIEnv* env, jlong clazz, Display* display, GLXFBConfig config) { + PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = + (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress( + (const GLubyte*)"glXCreateContextAttribsARB"); + if (glXCreateContextAttribsARB == nullptr) { + // This shouldn't really happen, as we check this Java side. + return 0; + } + + const int attr[] = { + GLX_RENDER_TYPE, GLX_RGBA_TYPE, + GLX_CONTEXT_MAJOR_VERSION_ARB, 3, + GLX_CONTEXT_MINOR_VERSION_ARB, 2, + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + None, + }; + auto oldHandler = XSetErrorHandler([](Display*, XErrorEvent*)->int{ return 0; }); + auto context = glXCreateContextAttribsARB(display, config, 0, true, attr); + XSetErrorHandler(oldHandler); + + return (jlong)context; +} + +#ifdef __cplusplus +} +#endif