Unverified Commit 2fac6861 authored by Igor Demin's avatar Igor Demin Committed by GitHub

Fix crash on Windows machines without OpenGL (#811)

* Fix crash on Windows machines without OpenGL

Some Windows CI machines don't contain OpenGL, so Skiko/Compose will crash on them. It shouldn't crash, it should fallback to Software rendering

To properly fallback, we need to load opengl32.dll dynamically in runtime.

Fixes https://github.com/JetBrains/compose-multiplatform/issues/3243

# Test 1
- `gradlew publishToMavenLocal` is successful (if we use functions directly, it fails with "error LNK2019: unresolved external symbol")
- `SkiaLayerTest` is successful (it tests OpenGL as well)

# Test 2
1. Remove `C:\Windows\System32\opengl32.dll`
2. `SkiaLayerTest` still should be successful, but logs should contain that it fallbacks to Software rendering.

# Test 3
1. Compile local skiko
2. Run Compose run1 and tests

* Refactor

* Discussions

* Discussions

* Split throwJavaRenderException

It isn't valid to use FormatMessage for code from GetLastError
parent bd47a99a
......@@ -1034,6 +1034,13 @@ fun createLinkJvmBindings(
libDirs.set(windowsSdkPaths.libDirs)
osFlags = mutableListOf<String>().apply {
addAll(buildType.msvcLinkerFlags)
addAll(
arrayOf(
// ignore https://learn.microsoft.com/en-us/cpp/error-messages/tool-errors/linker-tools-warning-lnk4217
// because we link OpenGl dynamically, defining functions in our own file in OpenGLLibrary.cc
"/ignore:4217"
)
)
addAll(
arrayOf(
"/NOLOGO",
......@@ -1041,7 +1048,6 @@ fun createLinkJvmBindings(
"Advapi32.lib",
"gdi32.lib",
"Dwmapi.lib",
"opengl32.lib",
"shcore.lib",
"user32.lib",
)
......
......@@ -4,6 +4,7 @@
#include <windows.h>
#include <stdio.h>
void throwJavaException(JNIEnv *env, const char * function, DWORD sehCode);
void throwJavaRenderExceptionByExceptionCode(JNIEnv *env, const char * function, DWORD code);
void throwJavaRenderExceptionByErrorCode(JNIEnv *env, const char * function, DWORD code);
#endif
\ No newline at end of file
#include <windows.h>
#include <gl/GL.h>
#include <jawt_md.h>
#include "exceptions_handler.h"
static HINSTANCE OpenGL32Library = nullptr;
extern "C" {
void glFinish(void) {
typedef void (*PROC_glFinish) (void);
static auto glFinish = (PROC_glFinish) GetProcAddress(OpenGL32Library, "glFinish");
glFinish();
}
void glGetIntegerv(GLenum pname, GLint *data) {
typedef void (*PROC_glGetIntegerv) (GLenum pname, GLint *data);
static auto glGetIntegerv = (PROC_glGetIntegerv) GetProcAddress(OpenGL32Library, "glGetIntegerv");
glGetIntegerv(pname, data);
}
const GLubyte * glGetString(GLenum name) {
typedef const GLubyte *(*PROC_glGetString) (GLenum name);
static auto glGetString = (PROC_glGetString) GetProcAddress(OpenGL32Library, "glGetString");
return glGetString(name);
}
HGLRC WINAPI wglCreateContext(HDC hDc) {
typedef HGLRC (WINAPI * PROC_wglCreateContext) (HDC hDc);
static auto wglCreateContext = (PROC_wglCreateContext) GetProcAddress(OpenGL32Library, "wglCreateContext");
return wglCreateContext(hDc);
}
BOOL WINAPI wglDeleteContext(HGLRC oldContext) {
typedef BOOL (WINAPI * PROC_wglDeleteContext) (HGLRC oldContext);
static auto wglDeleteContext = (PROC_wglDeleteContext) GetProcAddress(OpenGL32Library, "wglDeleteContext");
return wglDeleteContext(oldContext);
}
PROC WINAPI wglGetProcAddress(LPCSTR lpszProc) {
typedef PROC (WINAPI * PROC_wglGetProcAddress) (LPCSTR lpszProc);
static auto wglGetProcAddress = (PROC_wglGetProcAddress) GetProcAddress(OpenGL32Library, "wglGetProcAddress");
return wglGetProcAddress(lpszProc);
}
BOOL WINAPI wglMakeCurrent(HDC hDc, HGLRC newContext) {
typedef BOOL (WINAPI * PROC_wglMakeCurrent) (HDC hDc, HGLRC newContext);
static auto wglMakeCurrent = (PROC_wglMakeCurrent) GetProcAddress(OpenGL32Library, "wglMakeCurrent");
return wglMakeCurrent(hDc, newContext);
}
HGLRC WINAPI wglGetCurrentContext() {
typedef HGLRC (WINAPI * PROC_wglGetCurrentContext) (void);
static auto wglGetCurrentContext = (PROC_wglGetCurrentContext) GetProcAddress(OpenGL32Library, "wglGetCurrentContext");
return wglGetCurrentContext();
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_OpenGLLibraryKt_loadOpenGLLibraryWindows(JNIEnv *env, jobject obj) {
OpenGL32Library = LoadLibrary("opengl32.dll");
if (OpenGL32Library == nullptr) {
auto code = GetLastError();
throwJavaRenderExceptionByErrorCode(env, __FUNCTION__, code);
}
}
}
\ No newline at end of file
......@@ -64,7 +64,7 @@ extern "C"
}
__except(EXCEPTION_EXECUTE_HANDLER) {
auto code = GetExceptionCode();
throwJavaException(env, __FUNCTION__, code);
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
}
......@@ -91,7 +91,7 @@ extern "C"
}
__except(EXCEPTION_EXECUTE_HANDLER) {
auto code = GetExceptionCode();
throwJavaException(env, __FUNCTION__, code);
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
}
......
......@@ -25,7 +25,7 @@ extern "C"
}
__except(EXCEPTION_EXECUTE_HANDLER) {
auto code = GetExceptionCode();
throwJavaException(env, __FUNCTION__, code);
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
}
}
......
......@@ -321,7 +321,7 @@ extern "C"
}
__except(EXCEPTION_EXECUTE_HANDLER) {
auto code = GetExceptionCode();
throwJavaException(env, __FUNCTION__, code);
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
}
......@@ -341,7 +341,7 @@ extern "C"
}
__except(EXCEPTION_EXECUTE_HANDLER) {
auto code = GetExceptionCode();
throwJavaException(env, __FUNCTION__, code);
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
}
......@@ -396,7 +396,7 @@ extern "C"
}
__except(EXCEPTION_EXECUTE_HANDLER) {
auto code = GetExceptionCode();
throwJavaException(env, __FUNCTION__, code);
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
}
......@@ -413,7 +413,7 @@ extern "C"
}
__except(EXCEPTION_EXECUTE_HANDLER) {
auto code = GetExceptionCode();
throwJavaException(env, __FUNCTION__, code);
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
}
......@@ -441,7 +441,7 @@ extern "C"
}
__except(EXCEPTION_EXECUTE_HANDLER) {
auto code = GetExceptionCode();
throwJavaException(env, __FUNCTION__, code);
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
}
......
#if SK_BUILD_FOR_WIN
#include "exceptions_handler.h"
#include "../common/interop.hh"
const char *getDescription(DWORD code)
{
switch (code)
{
const char *getDescription(DWORD code) {
switch (code) {
case EXCEPTION_ACCESS_VIOLATION:
return "EXCEPTION_ACCESS_VIOLATION";
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
......@@ -47,19 +43,35 @@ const char *getDescription(DWORD code)
return "EXCEPTION_SINGLE_STEP";
case EXCEPTION_STACK_OVERFLOW:
return "EXCEPTION_STACK_OVERFLOW";
case ERROR_MOD_NOT_FOUND:
return "ERROR_MOD_NOT_FOUND";
default:
return "UNKNOWN EXCEPTION";
}
}
void throwJavaException(JNIEnv *env, const char *function, DWORD sehCode)
{
char buffer[200];
int result = snprintf(
buffer, sizeof(buffer) - 1, "Native exception in [%s]:\nSEH description: %s\n", function, getDescription(sehCode));
static jclass logClass = (jclass) env->NewGlobalRef(env->FindClass("org/jetbrains/skiko/RenderExceptionsHandler"));
static jmethodID logMethod = env->GetStaticMethodID(logClass, "throwException", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(logClass, logMethod, env->NewStringUTF(buffer));
void throwJavaRenderExceptionByExceptionCode(JNIEnv *env, const char *function, DWORD code) {
char fullMsg[200];
int result = snprintf(fullMsg, sizeof(fullMsg) - 1,
"Native exception in [%s], code %lu: %s", function, code, getDescription(code));
static jclass cls = (jclass) env->NewGlobalRef(env->FindClass("org/jetbrains/skiko/RenderExceptionsHandler"));
static jmethodID method = env->GetStaticMethodID(cls, "throwException", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(cls, method, env->NewStringUTF(fullMsg));
}
#endif
\ No newline at end of file
void throwJavaRenderExceptionByErrorCode(JNIEnv *env, const char *function, DWORD code) {
char fullMsg[1024];
char *msg = 0;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &msg, 0, NULL);
int result = snprintf(fullMsg, sizeof(fullMsg) - 1,
"Native exception in [%s], code %lu: %s", function, code, msg);
LocalFree(msg);
static jclass cls = (jclass) env->NewGlobalRef(env->FindClass("org/jetbrains/skiko/RenderExceptionsHandler"));
static jmethodID method = env->GetStaticMethodID(cls, "throwException", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(cls, method, env->NewStringUTF(fullMsg));
}
......@@ -34,7 +34,7 @@ extern "C"
}
__except(EXCEPTION_EXECUTE_HANDLER) {
auto code = GetExceptionCode();
throwJavaException(env, __FUNCTION__, code);
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
return (jlong) 0;
}
......@@ -54,7 +54,7 @@ extern "C"
}
__except(EXCEPTION_EXECUTE_HANDLER) {
auto code = GetExceptionCode();
throwJavaException(env, __FUNCTION__, code);
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
}
......@@ -67,7 +67,7 @@ extern "C"
}
__except(EXCEPTION_EXECUTE_HANDLER) {
auto code = GetExceptionCode();
throwJavaException(env, __FUNCTION__, code);
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
}
......@@ -93,7 +93,7 @@ extern "C"
}
__except(EXCEPTION_EXECUTE_HANDLER) {
auto code = GetExceptionCode();
throwJavaException(env, __FUNCTION__, code);
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
return (jlong) 0;
}
......
......@@ -9,7 +9,7 @@ internal class RenderExceptionsHandler {
companion object {
private var output: File? = null
@JvmStatic
fun logAndThrow(message: String) {
fun throwException(message: String) {
if (output == null) {
output = File(
"${Library.cacheRoot}/skiko-render-exception-${ProcessHandle.current().pid()}.log"
......
......@@ -9,6 +9,10 @@ internal class LinuxOpenGLRedrawer(
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties
) : AWTRedrawer(layer, analytics, GraphicsApi.OPENGL) {
init {
loadOpenGLLibrary()
}
private val contextHandler = OpenGLContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
......
......@@ -3,13 +3,16 @@ package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.*
import org.jetbrains.skiko.*
import org.jetbrains.skiko.context.OpenGLContextHandler
import java.util.concurrent.Executors
internal class WindowsOpenGLRedrawer(
private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties
) : AWTRedrawer(layer, analytics, GraphicsApi.OPENGL) {
init {
loadOpenGLLibrary()
}
private val contextHandler = OpenGLContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
......
package org.jetbrains.skiko
/**
* An exception related to a rendering failure
* (driver failure, rendering library failure, rendering device failure)
*/
internal class RenderException(
message: String? = null,
cause: Throwable? = null
......
......@@ -15,30 +15,10 @@
extern "C" {
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_OpenGLApi_glViewport(JNIEnv * env, jobject object, jint x, jint y, jint w, jint h) {
glViewport(x, y, w, h);
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_OpenGLApi_glClearColor(JNIEnv * env, jobject object, jfloat r, jfloat g, jfloat b, jfloat a) {
glClearColor(r, g, b, a);
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_OpenGLApi_glClear(JNIEnv * env, jobject object, jint mask) {
glClear(mask);
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_OpenGLApi_glFinish(JNIEnv * env, jobject object) {
glFinish();
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_OpenGLApi_glEnable(JNIEnv * env, jobject object, jint cap) {
glEnable(cap);
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_OpenGLApi_glBindTexture(JNIEnv * env, jobject object, jint target, jint texture) {
glBindTexture(target, texture);
}
JNIEXPORT jint JNICALL Java_org_jetbrains_skiko_OpenGLApi_glGetIntegerv(JNIEnv * env, jobject object, jint pname) {
GLint data;
glGetIntegerv(pname, &data);
......@@ -47,8 +27,7 @@ JNIEXPORT jint JNICALL Java_org_jetbrains_skiko_OpenGLApi_glGetIntegerv(JNIEnv *
JNIEXPORT jstring JNICALL Java_org_jetbrains_skiko_OpenGLApi_glGetString(JNIEnv * env, jobject object, jint value) {
const char *content = reinterpret_cast<const char *>(glGetString(value));
jstring result = env->NewStringUTF(content);
return result;
return env->NewStringUTF(content);
}
}
\ No newline at end of file
......@@ -5,23 +5,15 @@ package org.jetbrains.skiko
* PS. In further development we should find a common pattern of using OpenGL,
* Vulkan, Metal and implement common interface to all graphics APIs.
*/
class OpenGLApi private constructor() {
internal class OpenGLApi private constructor() {
// OpenGL constants
val GL_TEXTURE_2D = 0x0DE1
val GL_TEXTURE_BINDING_2D = 0x8069
val GL_DRAW_FRAMEBUFFER_BINDING = 0x8CA6
val GL_COLOR_BUFFER_BIT = 0x00004000
val GL_VENDOR = 0x1F00
val GL_RENDERER = 0x1F01
val GL_TOTAL_MEMORY = 0x9048
// OpenGL functions
external fun glViewport(x: Int, y: Int, width: Int, height: Int)
external fun glClearColor(r: Float, g: Float, b: Float, a: Float)
external fun glClear(flags: Int)
external fun glFinish()
external fun glEnable(flag: Int)
external fun glBindTexture(target: Int, texture: Int)
external fun glGetIntegerv(pname: Int): Int
external fun glGetString(value: Int): String?
......
package org.jetbrains.skiko
/**
* Load OpenGL library into memory.
*
* Should be called before any OpenGL operation.
*
* The current implementation loads OpenGl lazily on Windows, and at app startup on Linux/macOs.
*
* Some Windows machines don't have OpenGL (usually CI machines), so we'll fallback to other renderers on them.
*
* Throws [RenderException] if OpenGL library can't be loaded.
*/
internal fun loadOpenGLLibrary() {
if (hostOs.isWindows) {
loadOpenGLLibraryWindows()
} else {
// do nothing, the library should be already available
}
}
@Synchronized
private external fun loadOpenGLLibraryWindows()
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment