Unverified Commit eda88cdc authored by Alovchin91's avatar Alovchin91 Committed by GitHub

ANGLE support (#1017)

This change adds ANGLE renderer support to Skiko.

[ANGLE project](https://chromium.googlesource.com/angle/angle)
translates OpenGL ES API calls to the native platform's graphics API.
This is mostly useful on Windows where ANGLE provides the Direct3D 11
backend.

Right now, there is a gap in Skiko's graphics support. On Windows ARM,
Skia doesn't support OpenGL yet. At the same time, some GPUs, like
Parallels or VMware virtual GPUs, don't support DirectX 12. This means
Skiko falls back to the slow software rendering on those GPUs. ANGLE
solves this problem by providing an easy-to-use DirectX 11 backend.

In addition, ANGLE is used by Chromium-based browsers and apps, so we
expect it to be more stable than Skia's DirectX backend.

---------
Co-authored-by: 's avatarIgor Demin <igor.demin@jetbrains.com>
parent 60c332f3
...@@ -92,7 +92,8 @@ fun skiaPreprocessorFlags(os: OS, buildType: SkiaBuildType): Array<String> { ...@@ -92,7 +92,8 @@ fun skiaPreprocessorFlags(os: OS, buildType: SkiaBuildType): Array<String> {
"-DWIN32_LEAN_AND_MEAN", "-DWIN32_LEAN_AND_MEAN",
"-DNOMINMAX", "-DNOMINMAX",
"-DSK_GAMMA_APPLY_TO_A8", "-DSK_GAMMA_APPLY_TO_A8",
"-DSK_DIRECT3D" "-DSK_DIRECT3D",
"-DSK_ANGLE"
) )
OS.Linux -> listOf( OS.Linux -> listOf(
"-DSK_BUILD_FOR_LINUX", "-DSK_BUILD_FOR_LINUX",
......
...@@ -59,7 +59,8 @@ fun SkikoProjectContext.createCompileJvmBindingsTask( ...@@ -59,7 +59,8 @@ fun SkikoProjectContext.createCompileJvmBindingsTask(
) )
sourceRoots.set(srcDirs) sourceRoots.set(srcDirs)
if (targetOs != OS.Android) includeHeadersNonRecursive(jdkHome.resolve("include")) if (targetOs != OS.Android) includeHeadersNonRecursive(jdkHome.resolve("include"))
includeHeadersNonRecursive(skiaHeadersDirs(skiaJvmBindingsDir.get())) val skiaDir = skiaJvmBindingsDir.get()
includeHeadersNonRecursive(skiaHeadersDirs(skiaDir))
val projectDir = project.projectDir val projectDir = project.projectDir
includeHeadersNonRecursive(projectDir.resolve("src/awtMain/cpp/include")) includeHeadersNonRecursive(projectDir.resolve("src/awtMain/cpp/include"))
includeHeadersNonRecursive(projectDir.resolve("src/jvmMain/cpp/common")) includeHeadersNonRecursive(projectDir.resolve("src/jvmMain/cpp/common"))
...@@ -98,6 +99,9 @@ fun SkikoProjectContext.createCompileJvmBindingsTask( ...@@ -98,6 +99,9 @@ fun SkikoProjectContext.createCompileJvmBindingsTask(
OS.Windows -> { OS.Windows -> {
includeHeadersNonRecursive(windowsSdkPaths.includeDirs) includeHeadersNonRecursive(windowsSdkPaths.includeDirs)
includeHeadersNonRecursive(jdkHome.resolve("include/win32")) includeHeadersNonRecursive(jdkHome.resolve("include/win32"))
includeHeadersNonRecursive(skiaDir.resolve("third_party/externals/angle2/include"))
includeHeadersNonRecursive(skiaDir.resolve("include/gpu"))
includeHeadersNonRecursive(skiaDir.resolve("src/gpu"))
val targetArgs = if (targetArch == Arch.Arm64) arrayOf("/clang:--target=arm64-windows") else arrayOf() val targetArgs = if (targetArch == Arch.Arm64) arrayOf("/clang:--target=arm64-windows") else arrayOf()
osFlags = arrayOf( osFlags = arrayOf(
"/nologo", "/nologo",
...@@ -106,10 +110,6 @@ fun SkikoProjectContext.createCompileJvmBindingsTask( ...@@ -106,10 +110,6 @@ fun SkikoProjectContext.createCompileJvmBindingsTask(
"/GR-", // no-RTTI. "/GR-", // no-RTTI.
"/FS", // Due to an error when building in Teamcity. https://docs.microsoft.com/en-us/cpp/build/reference/fs-force-synchronous-pdb-writes "/FS", // Due to an error when building in Teamcity. https://docs.microsoft.com/en-us/cpp/build/reference/fs-force-synchronous-pdb-writes
*targetArgs, *targetArgs,
// LATER. Angle rendering arguments:
// "-I$skiaDir/third_party/externals/angle2/include",
// "-I$skiaDir/src/gpu",
// "-DSK_ANGLE",
) )
} }
OS.Android -> { OS.Android -> {
...@@ -311,6 +311,7 @@ fun SkikoProjectContext.createLinkJvmBindings( ...@@ -311,6 +311,7 @@ fun SkikoProjectContext.createLinkJvmBindings(
"ole32.lib", "ole32.lib",
"Propsys.lib", "Propsys.lib",
"shcore.lib", "shcore.lib",
"Shlwapi.lib",
"user32.lib", "user32.lib",
) )
) )
......
...@@ -6,5 +6,6 @@ ...@@ -6,5 +6,6 @@
void throwJavaRenderExceptionByExceptionCode(JNIEnv *env, const char * function, DWORD code); void throwJavaRenderExceptionByExceptionCode(JNIEnv *env, const char * function, DWORD code);
void throwJavaRenderExceptionByErrorCode(JNIEnv *env, const char * function, DWORD code); void throwJavaRenderExceptionByErrorCode(JNIEnv *env, const char * function, DWORD code);
void throwJavaRenderExceptionWithMessage(JNIEnv *env, const char * function, const char * msg);
#endif #endif
\ No newline at end of file
#ifdef SK_ANGLE
#include <GLES/gl.h>
#include <EGL/egl.h>
#include <jni.h>
extern "C" {
JNIEXPORT jstring JNICALL Java_org_jetbrains_skiko_AngleApi_glGetString(JNIEnv *env, jobject object, jint value)
{
static auto glGetString = (PFNGLGETSTRINGPROC) eglGetProcAddress("glGetString");
const char *content = reinterpret_cast<const char *>(glGetString(value));
return env->NewStringUTF(content);
}
}
#endif
#ifdef SK_ANGLE
#include <Windows.h>
#include <jawt_md.h>
#include "jni_helpers.h"
#include "exceptions_handler.h"
#include "ganesh/GrBackendSurface.h"
#include "ganesh/GrDirectContext.h"
#include "ganesh/gl/GrGLBackendSurface.h"
#include "ganesh/gl/GrGLDirectContext.h"
#include "SkSurface.h"
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "ganesh/gl/GrGLAssembleInterface.h"
#include "ganesh/gl/GrGLDefines.h"
#include <GL/gl.h>
#include "window_util.h"
class AngleDevice
{
public:
HWND window;
HDC device;
EGLDisplay display = EGL_NO_DISPLAY;
EGLContext context = EGL_NO_CONTEXT;
EGLSurface surface = EGL_NO_SURFACE;
EGLConfig surfaceConfig;
sk_sp<const GrGLInterface> backendContext;
~AngleDevice()
{
backendContext.reset(nullptr);
if (EGL_NO_CONTEXT != context)
{
eglDestroyContext(display, context);
}
if (EGL_NO_SURFACE != surface)
{
eglDestroySurface(display, surface);
}
if (EGL_NO_DISPLAY != display)
{
eglTerminate(display);
}
}
};
EGLDisplay getAngleEGLDisplay(HDC hdc)
{
PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT;
eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC) eglGetProcAddress("eglGetPlatformDisplayEXT");
// We expect ANGLE to support this extension
if (!eglGetPlatformDisplayEXT)
{
return EGL_NO_DISPLAY;
}
static constexpr EGLint attribs[] = {
// We currently only support D3D11 ANGLE.
EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
EGL_NONE, EGL_NONE
};
return eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, hdc, attribs);
}
bool initAngleSurface(JNIEnv *env, AngleDevice *angleDevice, EGLint width, EGLint height)
{
const EGLint surfaceAttribs[] = {
EGL_FIXED_SIZE_ANGLE, EGL_TRUE,
EGL_WIDTH, width,
EGL_HEIGHT, height,
EGL_NONE, EGL_NONE
};
if (EGL_NO_SURFACE != angleDevice->surface)
{
eglDestroySurface(angleDevice->display, angleDevice->surface);
}
angleDevice->surface = eglCreateWindowSurface(angleDevice->display, angleDevice->surfaceConfig, angleDevice->window, surfaceAttribs);
if (EGL_NO_SURFACE == angleDevice->surface)
{
return false;
}
if (!eglMakeCurrent(angleDevice->display, angleDevice->surface, angleDevice->surface, angleDevice->context))
{
throwJavaRenderExceptionWithMessage(env, __FUNCTION__, "Could not make context current!");
return false;
}
eglSwapInterval(angleDevice->display, 1);
return true;
}
extern "C"
{
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_createAngleDevice(
JNIEnv *env, jobject redrawer, jlong platformInfoPtr, jboolean transparency)
{
__try
{
JAWT_Win32DrawingSurfaceInfo *dsi_win = fromJavaPointer<JAWT_Win32DrawingSurfaceInfo *>(platformInfoPtr);
HWND hwnd = dsi_win->hwnd;
HDC hdc = GetDC(hwnd);
if (transparency)
{
enableTransparentWindow(hwnd);
}
AngleDevice *angleDevice = new AngleDevice();
angleDevice->window = hwnd;
angleDevice->device = hdc;
angleDevice->display = getAngleEGLDisplay(angleDevice->device);
if (EGL_NO_DISPLAY == angleDevice->display)
{
throwJavaRenderExceptionWithMessage(env, __FUNCTION__, "Could not get display!");
return (jlong) 0;
}
EGLint majorVersion;
EGLint minorVersion;
if (!eglInitialize(angleDevice->display, &majorVersion, &minorVersion))
{
throwJavaRenderExceptionWithMessage(env, __FUNCTION__, "Could not initialize display!");
return (jlong) 0;
}
static constexpr int fSampleCount = 1;
static constexpr int sampleBuffers = fSampleCount > 1 ? 1 : 0;
static constexpr int eglSampleCnt = fSampleCount > 1 ? fSampleCount : 0;
static constexpr EGLint configAttribs[] = {
// We currently only support ES3.
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_SAMPLE_BUFFERS, sampleBuffers,
EGL_SAMPLES, eglSampleCnt,
EGL_NONE, EGL_NONE
};
EGLint numConfigs;
if (!eglChooseConfig(angleDevice->display, configAttribs, &angleDevice->surfaceConfig, 1, &numConfigs))
{
throwJavaRenderExceptionWithMessage(env, __FUNCTION__, "Could not create choose config!");
return (jlong) 0;
}
// We currently only support ES3.
static constexpr EGLint contextAttribs[] = {
EGL_CONTEXT_MAJOR_VERSION, 3,
EGL_CONTEXT_MINOR_VERSION, 0,
EGL_NONE, EGL_NONE
};
angleDevice->context = eglCreateContext(angleDevice->display, angleDevice->surfaceConfig, nullptr, contextAttribs);
if (EGL_NO_CONTEXT == angleDevice->context)
{
throwJavaRenderExceptionWithMessage(env, __FUNCTION__, "Could not create context!");
return (jlong) 0;
}
// initial surface
if (!initAngleSurface(env, angleDevice, 0, 0))
{
throwJavaRenderExceptionWithMessage(env, __FUNCTION__, "Could not create surface!");
return (jlong) 0;
}
sk_sp<const GrGLInterface> glInterface(GrGLMakeAssembledInterface(
nullptr,
[](void *ctx, const char name[]) -> GrGLFuncPtr { return eglGetProcAddress(name); }));
angleDevice->backendContext = glInterface;
return toJavaPointer(angleDevice);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
auto code = GetExceptionCode();
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
return (jlong) 0;
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_makeCurrent(
JNIEnv *env, jobject redrawer, jlong devicePtr)
{
AngleDevice *angleDevice = fromJavaPointer<AngleDevice *>(devicePtr);
if (!eglMakeCurrent(angleDevice->display, angleDevice->surface, angleDevice->surface, angleDevice->context))
{
throwJavaRenderExceptionWithMessage(env, __FUNCTION__, "Could not make context current!");
}
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_makeAngleContext(
JNIEnv *env, jobject redrawer, jlong devicePtr)
{
AngleDevice *angleDevice = fromJavaPointer<AngleDevice *>(devicePtr);
sk_sp<const GrGLInterface> backendContext = angleDevice->backendContext;
return toJavaPointer(GrDirectContexts::MakeGL(backendContext).release());
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_makeAngleRenderTarget(
JNIEnv *env, jobject redrawer, jlong devicePtr, jint width, jint height)
{
__try
{
AngleDevice *angleDevice = fromJavaPointer<AngleDevice *>(devicePtr);
if (!initAngleSurface(env, angleDevice, width, height))
{
throwJavaRenderExceptionWithMessage(env, __FUNCTION__, "Could not create surface!");
return (jlong) 0;
}
angleDevice->backendContext->fFunctions.fViewport(0, 0, width, height);
GrGLint buffer;
angleDevice->backendContext->fFunctions.fGetIntegerv(GR_GL_FRAMEBUFFER_BINDING, &buffer);
GrGLFramebufferInfo glInfo = { static_cast<unsigned int>(buffer), GR_GL_RGBA8 };
GrBackendRenderTarget renderTarget = GrBackendRenderTargets::MakeGL(width,
height,
0,
8,
glInfo);
GrBackendRenderTarget *pRenderTarget = new GrBackendRenderTarget(renderTarget);
return toJavaPointer(pRenderTarget);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
auto code = GetExceptionCode();
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
return (jlong) 0;
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_swapBuffers(
JNIEnv *env, jobject redrawer, jlong devicePtr, jboolean waitForVsync)
{
__try
{
AngleDevice *angleDevice = fromJavaPointer<AngleDevice *>(devicePtr);
eglSwapInterval(angleDevice->display, waitForVsync ? 1 : 0);
if (!eglSwapBuffers(angleDevice->display, angleDevice->surface))
{
throwJavaRenderExceptionWithMessage(env, __FUNCTION__, "Could not complete eglSwapBuffers.");
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
auto code = GetExceptionCode();
throwJavaRenderExceptionByExceptionCode(env, __FUNCTION__, code);
}
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_disposeDevice(
JNIEnv *env, jobject redrawer, jlong devicePtr)
{
AngleDevice *angleDevice = fromJavaPointer<AngleDevice *>(devicePtr);
eglMakeCurrent(angleDevice->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
delete angleDevice;
}
} // end extern C
#endif
#ifdef SK_ANGLE
#include <windows.h>
#include <shlwapi.h>
#define GL_GLES_PROTOTYPES 0
#define EGL_EGL_PROTOTYPES 0
#include <GLES/gl.h>
#include <EGL/egl.h>
#include "exceptions_handler.h"
#define THROW_IF_NULL(action) \
do { \
auto __result { action }; \
if (0 == __result) { \
auto __code = GetLastError(); \
throwJavaRenderExceptionByErrorCode(env, __FUNCTION__, __code); \
return; \
} \
} while ((void)0, 0)
static HINSTANCE AngleEGLLibrary = nullptr;
extern "C" {
EGLBoolean EGLAPIENTRY eglInitialize (EGLDisplay dpy, EGLint *major, EGLint *minor) {
static auto eglInitialize = (PFNEGLINITIALIZEPROC) GetProcAddress(AngleEGLLibrary, "eglInitialize");
return eglInitialize(dpy, major, minor);
}
EGLBoolean EGLAPIENTRY eglTerminate (EGLDisplay dpy) {
static auto eglTerminate = (PFNEGLTERMINATEPROC) GetProcAddress(AngleEGLLibrary, "eglTerminate");
return eglTerminate(dpy);
}
EGLBoolean EGLAPIENTRY eglMakeCurrent (EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
static auto eglMakeCurrent = (PFNEGLMAKECURRENTPROC) GetProcAddress(AngleEGLLibrary, "eglMakeCurrent");
return eglMakeCurrent(dpy, draw, read, ctx);
}
EGLBoolean EGLAPIENTRY eglChooseConfig (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config) {
static auto eglChooseConfig = (PFNEGLCHOOSECONFIGPROC) GetProcAddress(AngleEGLLibrary, "eglChooseConfig");
return eglChooseConfig(dpy, attrib_list, configs, config_size, num_config);
}
EGLContext EGLAPIENTRY eglCreateContext (EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list) {
static auto eglCreateContext = (PFNEGLCREATECONTEXTPROC) GetProcAddress(AngleEGLLibrary, "eglCreateContext");
return eglCreateContext(dpy, config, share_context, attrib_list);
}
EGLBoolean EGLAPIENTRY eglDestroyContext (EGLDisplay dpy, EGLContext ctx) {
static auto eglDestroyContext = (PFNEGLDESTROYCONTEXTPROC) GetProcAddress(AngleEGLLibrary, "eglDestroyContext");
return eglDestroyContext(dpy, ctx);
}
EGLSurface EGLAPIENTRY eglCreateWindowSurface (EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list) {
static auto eglCreateWindowSurface = (PFNEGLCREATEWINDOWSURFACEPROC) GetProcAddress(AngleEGLLibrary, "eglCreateWindowSurface");
return eglCreateWindowSurface(dpy, config, win, attrib_list);
}
EGLBoolean EGLAPIENTRY eglDestroySurface (EGLDisplay dpy, EGLSurface surface) {
static auto eglDestroySurface = (PFNEGLDESTROYSURFACEPROC) GetProcAddress(AngleEGLLibrary, "eglDestroySurface");
return eglDestroySurface(dpy, surface);
}
EGLBoolean EGLAPIENTRY eglSwapInterval (EGLDisplay dpy, EGLint interval) {
static auto eglSwapInterval = (PFNEGLSWAPINTERVALPROC) GetProcAddress(AngleEGLLibrary, "eglSwapInterval");
return eglSwapInterval(dpy, interval);
}
EGLBoolean EGLAPIENTRY eglSwapBuffers (EGLDisplay dpy, EGLSurface surface) {
static auto eglSwapBuffers = (PFNEGLSWAPBUFFERSPROC) GetProcAddress(AngleEGLLibrary, "eglSwapBuffers");
return eglSwapBuffers(dpy, surface);
}
__eglMustCastToProperFunctionPointerType EGLAPIENTRY eglGetProcAddress (const char *procname) {
static auto eglGetProcAddress = (PFNEGLGETPROCADDRESSPROC) GetProcAddress(AngleEGLLibrary, "eglGetProcAddress");
return eglGetProcAddress(procname);
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_AngleSupport_1jvmKt_loadAngleLibraryWindows(JNIEnv *env, jobject obj) {
TCHAR basePath[MAX_PATH] = TEXT("");
TCHAR libEGL[MAX_PATH] = TEXT("");
TCHAR libGLESv2[MAX_PATH] = TEXT("");
HMODULE hmodule = nullptr;
THROW_IF_NULL(GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPTSTR) &Java_org_jetbrains_skiko_AngleSupport_1jvmKt_loadAngleLibraryWindows,
&hmodule));
THROW_IF_NULL(GetModuleFileName(hmodule, basePath, sizeof(basePath)));
THROW_IF_NULL(PathRemoveFileSpec(basePath));
THROW_IF_NULL(PathCombine(libEGL, basePath, TEXT("libEGL.dll")));
THROW_IF_NULL(PathCombine(libGLESv2, basePath, TEXT("libGLESv2.dll")));
THROW_IF_NULL(AngleEGLLibrary = LoadLibrary(libEGL));
THROW_IF_NULL(LoadLibrary(libGLESv2));
}
}
#endif
...@@ -74,3 +74,13 @@ void throwJavaRenderExceptionByErrorCode(JNIEnv *env, const char *function, DWOR ...@@ -74,3 +74,13 @@ void throwJavaRenderExceptionByErrorCode(JNIEnv *env, const char *function, DWOR
static jmethodID method = env->GetStaticMethodID(cls, "throwException", "(Ljava/lang/String;)V"); static jmethodID method = env->GetStaticMethodID(cls, "throwException", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(cls, method, env->NewStringUTF(fullMsg)); env->CallStaticVoidMethod(cls, method, env->NewStringUTF(fullMsg));
} }
void throwJavaRenderExceptionWithMessage(JNIEnv *env, const char *function, const char *msg) {
char fullMsg[1024];
snprintf(fullMsg, sizeof(fullMsg) - 1, "Native exception in [%s], message: %s", function, 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));
}
...@@ -25,6 +25,7 @@ internal actual fun makeDefaultRenderFactory(): RenderFactory = ...@@ -25,6 +25,7 @@ internal actual fun makeDefaultRenderFactory(): RenderFactory =
GraphicsApi.SOFTWARE_COMPAT -> SoftwareRedrawer(layer, analytics, properties) GraphicsApi.SOFTWARE_COMPAT -> SoftwareRedrawer(layer, analytics, properties)
GraphicsApi.SOFTWARE_FAST -> WindowsSoftwareRedrawer(layer, analytics, properties) GraphicsApi.SOFTWARE_FAST -> WindowsSoftwareRedrawer(layer, analytics, properties)
GraphicsApi.OPENGL -> WindowsOpenGLRedrawer(layer, analytics, properties) GraphicsApi.OPENGL -> WindowsOpenGLRedrawer(layer, analytics, properties)
GraphicsApi.ANGLE -> AngleRedrawer(layer, analytics, properties)
else -> Direct3DRedrawer(layer, analytics, properties) else -> Direct3DRedrawer(layer, analytics, properties)
} }
OS.Linux -> when (renderApi) { OS.Linux -> when (renderApi) {
......
package org.jetbrains.skiko.context
import org.jetbrains.skia.*
import org.jetbrains.skiko.AngleApi
import org.jetbrains.skiko.Logger
import org.jetbrains.skiko.RenderException
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.AngleRedrawer
internal class AngleContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) {
private val angleRedrawer: AngleRedrawer
get() = layer.redrawer!! as AngleRedrawer
override fun initContext(): Boolean {
try {
if (context == null) {
context = angleRedrawer.makeContext()
if (System.getProperty("skiko.hardwareInfo.enabled") == "true") {
Logger.info { "Renderer info:\n ${rendererInfo()}" }
}
}
} catch (e: Exception) {
Logger.warn(e) { "Failed to create Skia ANGLE context!" }
return false
}
return true
}
private var currentWidth = 0
private var currentHeight = 0
private fun isSizeChanged(width: Int, height: Int): Boolean {
if (width != currentWidth || height != currentHeight) {
currentWidth = width
currentHeight = height
return true
}
return false
}
override fun initCanvas() {
val context = context ?: return
val scale = layer.contentScale
val w = (layer.width * scale).toInt().coerceAtLeast(0)
val h = (layer.height * scale).toInt().coerceAtLeast(0)
if (isSizeChanged(w, h) || surface == null) {
disposeCanvas()
context.flush()
renderTarget = angleRedrawer.makeRenderTarget(w, h)
surface = Surface.makeFromBackendRenderTarget(
context,
renderTarget!!,
SurfaceOrigin.BOTTOM_LEFT,
SurfaceColorFormat.RGBA_8888,
ColorSpace.sRGB,
SurfaceProps(pixelGeometry = layer.pixelGeometry)
) ?: throw RenderException("Cannot create surface")
}
canvas = surface!!.canvas
}
override fun rendererInfo(): String {
return super.rendererInfo() +
"Vendor: ${AngleApi.glGetString(AngleApi.GL_VENDOR)}\n" +
"Model: ${AngleApi.glGetString(AngleApi.GL_RENDERER)}\n" +
"Version: ${AngleApi.glGetString(AngleApi.GL_VERSION)}\n"
// "Total VRAM: ${AngleApi.glGetIntegerv(AngleApi.GL_TOTAL_MEMORY) / 1024} MB\n"
}
}
package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.withContext
import org.jetbrains.skia.BackendRenderTarget
import org.jetbrains.skia.DirectContext
import org.jetbrains.skiko.*
import org.jetbrains.skiko.context.AngleContextHandler
internal class AngleRedrawer(
private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties
) : AWTRedrawer(layer, analytics, GraphicsApi.ANGLE) {
init {
loadAngleLibrary()
}
private val contextHandler = AngleContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
private var drawLock = Any()
private var device: Long = 0L
get() {
if (field == 0L) {
throw RenderException("ANGLE device is not initialized or already disposed")
}
return field
}
private val frameDispatcher = FrameDispatcher(MainUIDispatcher) {
if (layer.isShowing) {
update(System.nanoTime())
draw()
}
}
private val adapterName get() = AngleApi.glGetString(AngleApi.GL_RENDERER)
init {
device = layer.backedLayer.useDrawingSurfacePlatformInfo { platformInfo ->
createAngleDevice(platformInfo, layer.transparency).takeIf { it != 0L }
?: throw RenderException("Failed to create ANGLE device.")
}
adapterName.let { adapterName ->
if (adapterName != null && !isVideoCardSupported(GraphicsApi.ANGLE, hostOs, adapterName)) {
throw RenderException("Cannot create ANGLE redrawer.")
}
onDeviceChosen(adapterName)
}
onContextInit()
}
override fun dispose() = synchronized(drawLock) {
frameDispatcher.cancel()
makeCurrent(device)
contextHandler.dispose()
disposeDevice(device)
device = 0L
super.dispose()
}
override fun needRedraw() {
check(!isDisposed) { "ANGLE redrawer is disposed" }
frameDispatcher.scheduleFrame()
}
override fun redrawImmediately() {
check(!isDisposed) { "ANGLE redrawer is disposed" }
inDrawScope {
update(System.nanoTime())
drawAndSwap(withVsync = SkikoProperties.windowsWaitForVsyncOnRedrawImmediately)
}
}
private fun draw() {
inDrawScope {
drawAndSwap(withVsync = properties.isVsyncEnabled)
}
}
private fun drawAndSwap(withVsync: Boolean) = synchronized(drawLock) {
if (isDisposed) {
return
}
makeCurrent(device)
contextHandler.draw()
swapBuffers(device, withVsync)
}
fun makeContext() = DirectContext(
makeAngleContext(device).takeIf { it != 0L }
?: throw RenderException("Failed to make GL context.")
)
fun makeRenderTarget(width: Int, height: Int) = BackendRenderTarget(
makeAngleRenderTarget(device, width, height).takeIf { it != 0L }
?: throw RenderException("Failed to make ANGLE render target.")
)
}
private external fun createAngleDevice(platformInfo: Long, transparency: Boolean): Long
private external fun makeCurrent(device: Long)
private external fun makeAngleContext(device: Long): Long
private external fun makeAngleRenderTarget(device: Long, width: Int, height: Int): Long
private external fun swapBuffers(device: Long, waitForVsync: Boolean)
private external fun disposeDevice(device: Long)
package org.jetbrains.skiko
/**
* Load ANGLE library into memory.
*
* Should be called before any ANGLE operation.
*
* We don't ship ANGLE with Skiko by default. If it's absent in the Skiko native library directory,
* we'll fallback to other renderers.
*
* @throws OptionalRenderApiException if ANGLE library can't be loaded.
*/
internal expect fun loadAngleLibrary()
\ No newline at end of file
...@@ -17,6 +17,7 @@ enum class GraphicsApi { ...@@ -17,6 +17,7 @@ enum class GraphicsApi {
OPENGL, OPENGL,
DIRECT3D, DIRECT3D,
ANGLE,
VULKAN, VULKAN,
METAL, METAL,
WEBGL WEBGL
......
package org.jetbrains.skiko
/**
* An exception related to inability of loading an optional rendering API
* (e.g. ANGLE on Windows)
*/
internal class OptionalRenderApiException(
message: String? = null,
cause: Throwable? = null
) : RenderException(message, cause)
...@@ -4,7 +4,7 @@ package org.jetbrains.skiko ...@@ -4,7 +4,7 @@ package org.jetbrains.skiko
* An exception related to a rendering failure * An exception related to a rendering failure
* (driver failure, rendering library failure, rendering device failure) * (driver failure, rendering library failure, rendering device failure)
*/ */
internal class RenderException( internal open class RenderException(
message: String? = null, message: String? = null,
cause: Throwable? = null cause: Throwable? = null
) : RuntimeException(message, cause) ) : RuntimeException(message, cause)
...@@ -35,4 +35,8 @@ internal actual fun getCursorById(id: PredefinedCursorsId): Cursor = ...@@ -35,4 +35,8 @@ internal actual fun getCursorById(id: PredefinedCursorsId): Cursor =
internal actual fun loadOpenGLLibrary() { internal actual fun loadOpenGLLibrary() {
// Nothing to do here // Nothing to do here
}
internal actual fun loadAngleLibrary() {
// Nothing to do here
} }
\ No newline at end of file
...@@ -221,4 +221,53 @@ JNIEXPORT jboolean JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_isOcc ...@@ -221,4 +221,53 @@ JNIEXPORT jboolean JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_isOcc
skikoUnimplemented("Java_org_jetbrains_skiko_redrawer_MetalRedrawer_isOccluded"); skikoUnimplemented("Java_org_jetbrains_skiko_redrawer_MetalRedrawer_isOccluded");
return false; return false;
} }
#endif #endif
\ No newline at end of file
#ifndef SK_ANGLE
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_createAngleDevice(
JNIEnv *env, jobject redrawer, jlong platformInfoPtr, jboolean transparency)
{
skikoUnimplemented("Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_createAngleDevice");
return 0;
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_makeCurrent(
JNIEnv *env, jobject redrawer, jlong devicePtr)
{
skikoUnimplemented("Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_makeCurrent");
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_makeAngleContext(
JNIEnv *env, jobject redrawer, jlong devicePtr)
{
skikoUnimplemented("Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_makeAngleContext");
return 0;
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_makeAngleRenderTarget(
JNIEnv *env, jobject redrawer, jlong devicePtr, jint width, jint height)
{
skikoUnimplemented("Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_makeAngleRenderTarget");
return 0;
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_swapBuffers(
JNIEnv *env, jobject redrawer, jlong devicePtr, jboolean waitForVsync)
{
skikoUnimplemented("Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_swapBuffers");
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_disposeDevice(
JNIEnv *env, jobject redrawer, jlong devicePtr)
{
skikoUnimplemented("Java_org_jetbrains_skiko_redrawer_AngleRedrawerKt_disposeDevice");
}
JNIEXPORT jstring JNICALL Java_org_jetbrains_skiko_AngleApi_glGetString(
JNIEnv *env, jobject object, jint value)
{
skikoUnimplemented("Java_org_jetbrains_skiko_AngleApi_glGetString");
return 0;
}
#endif
package org.jetbrains.skiko
internal object AngleApi {
// OpenGL constants
val GL_VENDOR = 0x1F00
val GL_RENDERER = 0x1F01
val GL_VERSION = 0x1F02
// OpenGL functions
external fun glGetString(value: Int): String?
}
package org.jetbrains.skiko
import org.jetbrains.skia.impl.Library
private external fun loadAngleLibraryWindows()
private var isLoaded = false
@Synchronized
internal actual fun loadAngleLibrary() {
if (!isLoaded) {
when {
hostOs.isWindows -> {
Library.staticLoad()
try {
loadAngleLibraryWindows()
}
catch (e: Exception) {
throw OptionalRenderApiException("Failed to load ANGLE library: ${e}")
}
}
else -> Unit
}
isLoaded = true
}
}
\ No newline at end of file
...@@ -54,6 +54,8 @@ object SkikoProperties { ...@@ -54,6 +54,8 @@ object SkikoProperties {
return getProperty("skiko.rendering.linux.waitForFrameVsyncOnRedrawImmediately")?.toBoolean() ?: false return getProperty("skiko.rendering.linux.waitForFrameVsyncOnRedrawImmediately")?.toBoolean() ?: false
} }
val renderingAngleEnabled: Boolean get() = getProperty("skiko.rendering.angle.enabled")?.toBoolean() ?: false
/** /**
* If vsync is enabled, but platform can't support it (Software renderer, Linux with uninstalled drivers), * If vsync is enabled, but platform can't support it (Software renderer, Linux with uninstalled drivers),
* we enable frame limit by the display refresh rate. * we enable frame limit by the display refresh rate.
...@@ -107,10 +109,15 @@ object SkikoProperties { ...@@ -107,10 +109,15 @@ object SkikoProperties {
"SOFTWARE_COMPAT" -> return GraphicsApi.SOFTWARE_COMPAT "SOFTWARE_COMPAT" -> return GraphicsApi.SOFTWARE_COMPAT
"SOFTWARE_FAST", "DIRECT_SOFTWARE" -> return GraphicsApi.SOFTWARE_FAST "SOFTWARE_FAST", "DIRECT_SOFTWARE" -> return GraphicsApi.SOFTWARE_FAST
"SOFTWARE" -> return if (hostOs == OS.MacOS) GraphicsApi.SOFTWARE_COMPAT else GraphicsApi.SOFTWARE_FAST "SOFTWARE" -> return if (hostOs == OS.MacOS) GraphicsApi.SOFTWARE_COMPAT else GraphicsApi.SOFTWARE_FAST
"OPENGL" -> "OPENGL" -> {
// Skia isn't properly tested on OpenGL and Windows ARM (https://groups.google.com/g/skia-discuss/c/McoclAhLpvg?pli=1) // Skia isn't properly tested on OpenGL and Windows ARM (https://groups.google.com/g/skia-discuss/c/McoclAhLpvg?pli=1)
return if (hostOs != OS.Windows || hostArch != Arch.Arm64) GraphicsApi.OPENGL return if (hostOs != OS.Windows || hostArch != Arch.Arm64) GraphicsApi.OPENGL
else throw Exception("$hostOs-$hostArch does not support OpenGL rendering API.") else throw Exception("$hostOs-$hostArch does not support OpenGL rendering API.")
}
"ANGLE" -> {
return if (hostOs == OS.Windows) GraphicsApi.ANGLE
else throw Exception("$hostOs does not support ANGLE rendering API.")
}
"DIRECT3D" -> { "DIRECT3D" -> {
return if (hostOs == OS.Windows) GraphicsApi.DIRECT3D return if (hostOs == OS.Windows) GraphicsApi.DIRECT3D
else throw Exception("$hostOs does not support DirectX rendering API.") else throw Exception("$hostOs does not support DirectX rendering API.")
...@@ -127,7 +134,7 @@ object SkikoProperties { ...@@ -127,7 +134,7 @@ object SkikoProperties {
when(hostOs) { when(hostOs) {
OS.MacOS -> return GraphicsApi.METAL OS.MacOS -> return GraphicsApi.METAL
OS.Linux -> return GraphicsApi.OPENGL OS.Linux -> return GraphicsApi.OPENGL
OS.Windows -> return GraphicsApi.DIRECT3D OS.Windows -> return if (renderingAngleEnabled) GraphicsApi.ANGLE else GraphicsApi.DIRECT3D
OS.Android -> return GraphicsApi.OPENGL OS.Android -> return GraphicsApi.OPENGL
OS.JS, OS.Ios, OS.Tvos, OS.Unknown -> TODO("commonize me") OS.JS, OS.Ios, OS.Tvos, OS.Unknown -> TODO("commonize me")
} }
...@@ -139,8 +146,8 @@ object SkikoProperties { ...@@ -139,8 +146,8 @@ object SkikoProperties {
OS.MacOS -> listOf(GraphicsApi.METAL, GraphicsApi.SOFTWARE_COMPAT) OS.MacOS -> listOf(GraphicsApi.METAL, GraphicsApi.SOFTWARE_COMPAT)
OS.Windows -> when (hostArch) { OS.Windows -> when (hostArch) {
// Skia isn't properly tested on OpenGL and Windows ARM (https://groups.google.com/g/skia-discuss/c/McoclAhLpvg?pli=1) // Skia isn't properly tested on OpenGL and Windows ARM (https://groups.google.com/g/skia-discuss/c/McoclAhLpvg?pli=1)
Arch.Arm64 -> listOf(GraphicsApi.DIRECT3D, GraphicsApi.SOFTWARE_FAST, GraphicsApi.SOFTWARE_COMPAT) Arch.Arm64 -> listOf(GraphicsApi.ANGLE, GraphicsApi.DIRECT3D, GraphicsApi.SOFTWARE_FAST, GraphicsApi.SOFTWARE_COMPAT)
else -> listOf(GraphicsApi.DIRECT3D, GraphicsApi.OPENGL, GraphicsApi.SOFTWARE_FAST, GraphicsApi.SOFTWARE_COMPAT) else -> listOf(GraphicsApi.ANGLE, GraphicsApi.DIRECT3D, GraphicsApi.OPENGL, GraphicsApi.SOFTWARE_FAST, GraphicsApi.SOFTWARE_COMPAT)
} }
OS.Android -> return listOf(GraphicsApi.OPENGL) OS.Android -> return listOf(GraphicsApi.OPENGL)
OS.JS, OS.Ios, OS.Tvos, OS.Unknown -> TODO("commonize me") OS.JS, OS.Ios, OS.Tvos, OS.Unknown -> TODO("commonize me")
......
...@@ -3,4 +3,8 @@ package org.jetbrains.skiko ...@@ -3,4 +3,8 @@ package org.jetbrains.skiko
internal actual inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R = internal actual inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R =
block() block()
actual fun currentNanoTime(): Long = kotlin.system.getTimeNanos() actual fun currentNanoTime(): Long = kotlin.system.getTimeNanos()
\ No newline at end of file
internal actual fun loadAngleLibrary() {
// Nothing to do here
}
\ 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