Commit 620547af authored by Igor Demin's avatar Igor Demin

Support vsync

parent f970ae3d
...@@ -15,7 +15,9 @@ import kotlin.math.cos ...@@ -15,7 +15,9 @@ import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
fun main(args: Array<String>) { fun main(args: Array<String>) {
createWindow("First window") repeat(1) {
createWindow("window $it")
}
} }
fun createWindow(title: String) = SwingUtilities.invokeLater { fun createWindow(title: String) = SwingUtilities.invokeLater {
...@@ -65,7 +67,6 @@ fun createWindow(title: String) = SwingUtilities.invokeLater { ...@@ -65,7 +67,6 @@ fun createWindow(title: String) = SwingUtilities.invokeLater {
override fun mouseMoved(event: MouseEvent) { override fun mouseMoved(event: MouseEvent) {
mouseX = event.x mouseX = event.x
mouseY = event.y mouseY = event.y
window.display()
} }
}) })
...@@ -93,6 +94,7 @@ class Renderer( ...@@ -93,6 +94,7 @@ class Renderer(
val contentScale = layer.contentScale val contentScale = layer.contentScale
canvas.scale(contentScale, contentScale) canvas.scale(contentScale, contentScale)
displayScene(this, (width / contentScale).toInt(), (height / contentScale).toInt(), nanoTime) displayScene(this, (width / contentScale).toInt(), (height / contentScale).toInt(), nanoTime)
layer.needRedraw()
} }
} }
......
...@@ -69,7 +69,6 @@ fun SwingSkia() { ...@@ -69,7 +69,6 @@ fun SwingSkia() {
override fun mouseMoved(event: MouseEvent) { override fun mouseMoved(event: MouseEvent) {
mouseX = event.x mouseX = event.x
mouseY = event.y mouseY = event.y
panel.layer.display()
} }
}) })
......
...@@ -370,6 +370,7 @@ tasks.withType(LinkSharedLibrary::class.java).configureEach { ...@@ -370,6 +370,7 @@ tasks.withType(LinkSharedLibrary::class.java).configureEach {
linkerArgs.addAll( linkerArgs.addAll(
listOf( listOf(
"gdi32.lib", "gdi32.lib",
"Dwmapi.lib",
"opengl32.lib", "opengl32.lib",
"shcore.lib", "shcore.lib",
"user32.lib" "user32.lib"
......
// drawcanvas.cpp : Defines the exported functions for the DLL application.
#include <jawt_md.h> #include <jawt_md.h>
#include <GL/gl.h> #include <GL/gl.h>
#include <GL/glx.h> #include <GL/glx.h>
...@@ -8,191 +7,19 @@ ...@@ -8,191 +7,19 @@
#include <cstdlib> #include <cstdlib>
#include <unistd.h> #include <unistd.h>
#include <stdio.h> #include <stdio.h>
#include <set>
using namespace std;
typedef GLXContext (*glXCreateContextAttribsARBProc)(Display *, GLXFBConfig, GLXContext, Bool, const int *); typedef GLXContext (*glXCreateContextAttribsARBProc)(Display *, GLXFBConfig, GLXContext, Bool, const int *);
extern "C" jboolean Skiko_GetAWT(JNIEnv *env, JAWT *awt); extern "C" jboolean Skiko_GetAWT(JNIEnv *env, JAWT *awt);
JavaVM *jvm = NULL;
class LayerHandler
{
public:
jobject canvasGlobalRef;
GLXContext context;
void updateLayerContent()
{
draw();
}
void disposeLayer(JNIEnv *env)
{
env->DeleteGlobalRef(canvasGlobalRef);
canvasGlobalRef = NULL;
context = NULL;
}
private:
void draw()
{
if (jvm != NULL)
{
JNIEnv *env;
jvm->GetEnv((void **)&env, JNI_VERSION_10);
JAWT awt;
JAWT_DrawingSurface *ds = NULL;
JAWT_DrawingSurfaceInfo *dsi = NULL;
jboolean result = JNI_FALSE;
jint lock = 0;
JAWT_X11DrawingSurfaceInfo *dsi_x11;
awt.version = (jint)JAWT_VERSION_9;
result = Skiko_GetAWT(env, &awt);
if (result == JNI_FALSE)
{
fprintf(stderr, "JAWT_GetAWT failed! Result is JNI_FALSE\n");
return;
}
ds = awt.GetDrawingSurface(env, canvasGlobalRef);
lock = ds->Lock(ds);
dsi = ds->GetDrawingSurfaceInfo(ds);
dsi_x11 = (JAWT_X11DrawingSurfaceInfo *)dsi->platformInfo;
Display *display = dsi_x11->display;
Window window = dsi_x11->drawable;
if (dsi != NULL)
{
jvm->AttachCurrentThread((void **)&env, NULL);
glXMakeCurrent(display, window, context);
static jclass wndClass = NULL;
if (!wndClass) wndClass = env->GetObjectClass(canvasGlobalRef);
static jmethodID drawMethod = NULL;
if (!drawMethod) drawMethod = env->GetMethodID(wndClass, "draw", "()V");
if (NULL == drawMethod)
{
fprintf(stderr, "The method Window.draw() not found!\n");
return;
}
env->CallVoidMethod(canvasGlobalRef, drawMethod);
glFinish();
glXSwapBuffers(display, window);
}
ds->FreeDrawingSurfaceInfo(dsi);
ds->Unlock(ds);
awt.FreeDrawingSurface(ds);
}
}
};
set<LayerHandler *> *layerStorage = NULL;
LayerHandler *findByObject(JNIEnv *env, jobject object)
{
if (layerStorage == NULL)
{
return NULL;
}
for (auto &layer : *layerStorage)
{
if (env->IsSameObject(object, layer->canvasGlobalRef) == JNI_TRUE)
{
return layer;
}
}
return NULL;
}
extern "C" extern "C"
{ {
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_updateLayer(JNIEnv *env, jobject canvas) JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_init(JNIEnv *env, jobject canvas)
{ {
if (layerStorage != NULL)
{
LayerHandler *layer = findByObject(env, canvas);
if (layer != NULL)
{
return;
}
}
else
{
layerStorage = new set<LayerHandler *>();
}
if (jvm == NULL)
{
env->GetJavaVM(&jvm);
}
JAWT awt;
JAWT_DrawingSurface *ds = NULL;
JAWT_DrawingSurfaceInfo *dsi = NULL;
jboolean result = JNI_FALSE;
jint lock = 0;
JAWT_X11DrawingSurfaceInfo *dsi_x11;
awt.version = (jint)JAWT_VERSION_9;
result = Skiko_GetAWT(env, &awt);
if (result == JNI_FALSE)
{
fprintf(stderr, "JAWT_GetAWT failed! Result is JNI_FALSE\n");
return;
}
ds = awt.GetDrawingSurface(env, canvas);
lock = ds->Lock(ds);
dsi = ds->GetDrawingSurfaceInfo(ds);
dsi_x11 = (JAWT_X11DrawingSurfaceInfo *)dsi->platformInfo;
Display *display = dsi_x11->display;
Window window = dsi_x11->drawable;
if (dsi != NULL)
{
GLint att[] = {GLX_RGBA, GLX_DOUBLEBUFFER, True, None};
XVisualInfo *vi = glXChooseVisual(display, 0, att);
GLXContext context = glXCreateContext(display, vi, NULL, GL_TRUE);
LayerHandler *layer = new LayerHandler();
layerStorage->insert(layer);
jobject canvasRef = env->NewGlobalRef(canvas);
layer->canvasGlobalRef = canvasRef;
layer->context = context;
}
ds->FreeDrawingSurfaceInfo(dsi);
ds->Unlock(ds);
awt.FreeDrawingSurface(ds);
} }
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_redrawLayer(JNIEnv *env, jobject canvas) JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_dispose(JNIEnv *env, jobject canvas)
{ {
LayerHandler *layer = findByObject(env, canvas);
if (layer != NULL)
{
layer->updateLayerContent();
}
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_disposeLayer(JNIEnv *env, jobject canvas)
{
LayerHandler *layer = findByObject(env, canvas);
if (layer != NULL)
{
layerStorage->erase(layer);
layer->disposeLayer(env);
delete layer;
}
} }
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_HardwareLayer_getWindowHandle(JNIEnv *env, jobject canvas) JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_HardwareLayer_getWindowHandle(JNIEnv *env, jobject canvas)
...@@ -214,11 +41,6 @@ extern "C" ...@@ -214,11 +41,6 @@ extern "C"
return -1; return -1;
} }
if (jvm == NULL)
{
env->GetJavaVM(&jvm);
}
ds = awt.GetDrawingSurface(env, canvas); ds = awt.GetDrawingSurface(env, canvas);
lock = ds->Lock(ds); lock = ds->Lock(ds);
dsi = ds->GetDrawingSurfaceInfo(ds); dsi = ds->GetDrawingSurfaceInfo(ds);
...@@ -274,11 +96,6 @@ extern "C" ...@@ -274,11 +96,6 @@ extern "C"
return -1; return -1;
} }
if (jvm == NULL)
{
env->GetJavaVM(&jvm);
}
ds = awt.GetDrawingSurface(env, component); ds = awt.GetDrawingSurface(env, component);
lock = ds->Lock(ds); lock = ds->Lock(ds);
dsi = ds->GetDrawingSurfaceInfo(ds); dsi = ds->GetDrawingSurfaceInfo(ds);
......
#include <jawt_md.h>
#include <GL/gl.h>
#include <GL/glx.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xresource.h>
#include <cstdlib>
#include <unistd.h>
#include <stdio.h>
typedef GLXContext (*glXCreateContextAttribsARBProc)(Display *, GLXFBConfig, GLXContext, Bool, const int *);
extern "C" jboolean Skiko_GetAWT(JNIEnv *env, JAWT *awt);
extern "C"
{
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_LinuxRedrawerKt_lockDrawingSurfaceNative(JNIEnv *env, jobject redrawer, jobject layer)
{
JAWT awt;
awt.version = (jint)JAWT_VERSION_9;
if (!Skiko_GetAWT(env, &awt))
{
fprintf(stderr, "JAWT_GetAWT failed! Result is JNI_FALSE\n");
return 0;
}
JAWT_DrawingSurface *ds = awt.GetDrawingSurface(env, layer);
ds->Lock(ds);
return static_cast<jlong>(reinterpret_cast<uintptr_t>(ds));
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_LinuxRedrawerKt_unlockDrawingSurfaceNative(JNIEnv *env, jobject redrawer, jlong drawingSurfacePtr)
{
JAWT_DrawingSurface *ds = reinterpret_cast<JAWT_DrawingSurface *>(static_cast<uintptr_t>(drawingSurfacePtr));
JAWT awt;
awt.version = (jint)JAWT_VERSION_9;
if (!Skiko_GetAWT(env, &awt))
{
fprintf(stderr, "JAWT_GetAWT failed! Result is JNI_FALSE\n");
return 0;
}
ds->Unlock(ds);
awt.FreeDrawingSurface(ds);
return static_cast<jlong>(reinterpret_cast<uintptr_t>(ds));
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_LinuxRedrawerKt_getDisplay(JNIEnv *env, jobject redrawer, jlong drawingSurfacePtr)
{
JAWT_DrawingSurface *ds = reinterpret_cast<JAWT_DrawingSurface *>(static_cast<uintptr_t>(drawingSurfacePtr));
JAWT_DrawingSurfaceInfo *dsi = ds->GetDrawingSurfaceInfo(ds);
JAWT_X11DrawingSurfaceInfo *dsi_x11 = (JAWT_X11DrawingSurfaceInfo *)dsi->platformInfo;
Display *display = dsi_x11->display;
ds->FreeDrawingSurfaceInfo(dsi);
return static_cast<jlong>(reinterpret_cast<uintptr_t>(display));
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_LinuxRedrawerKt_getWindow(JNIEnv *env, jobject redrawer, jlong drawingSurfacePtr)
{
JAWT_DrawingSurface *ds = reinterpret_cast<JAWT_DrawingSurface *>(static_cast<uintptr_t>(drawingSurfacePtr));
JAWT_DrawingSurfaceInfo *dsi = ds->GetDrawingSurfaceInfo(ds);
JAWT_X11DrawingSurfaceInfo *dsi_x11 = (JAWT_X11DrawingSurfaceInfo *)dsi->platformInfo;
Window window = dsi_x11->drawable;
ds->FreeDrawingSurfaceInfo(dsi);
return static_cast<jlong>(reinterpret_cast<uintptr_t>(window));
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_LinuxRedrawerKt_setSwapInterval(JNIEnv *env, jobject redrawer, jlong displayPtr, jlong windowPtr, jint interval)
{
Display *display = reinterpret_cast<Display *>(static_cast<uintptr_t>(displayPtr));
Window window = reinterpret_cast<Window>(static_cast<uintptr_t>(windowPtr));
// according to:
// https://opengl.gpuinfo.org/listreports.php?extension=GLX_EXT_swap_control
// https://opengl.gpuinfo.org/listreports.php?extension=GLX_MESA_swap_control
// https://opengl.gpuinfo.org/listreports.php?extension=GLX_SGI_swap_control
// there is no Linux that doesn't support at least one of these extensions
static PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) glXGetProcAddress((const GLubyte*)"glXSwapIntervalEXT");
if (glXSwapIntervalEXT != NULL)
{
glXSwapIntervalEXT(display, window, interval);
}
else
{
static PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) glXGetProcAddress((const GLubyte*)"glXSwapIntervalMESA");
if (glXSwapIntervalMESA != NULL)
{
glXSwapIntervalMESA(interval);
}
else
{
static PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC) glXGetProcAddress((const GLubyte*)"glXSwapIntervalSGI");
if (glXSwapIntervalSGI != NULL)
{
glXSwapIntervalSGI(interval);
}
}
}
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_LinuxRedrawerKt_swapBuffers(JNIEnv *env, jobject redrawer, jlong displayPtr, jlong windowPtr)
{
Display *display = reinterpret_cast<Display *>(static_cast<uintptr_t>(displayPtr));
Window window = reinterpret_cast<Window>(static_cast<uintptr_t>(windowPtr));
glXSwapBuffers(display, window);
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_LinuxRedrawerKt_makeCurrent(JNIEnv *env, jobject redrawer, jlong displayPtr, jlong windowPtr, jlong contextPtr)
{
Display *display = reinterpret_cast<Display *>(static_cast<uintptr_t>(displayPtr));
Window window = reinterpret_cast<Window>(static_cast<uintptr_t>(windowPtr));
GLXContext *context = reinterpret_cast<GLXContext *>(static_cast<uintptr_t>(contextPtr));
glXMakeCurrent(display, window, *context);
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_LinuxRedrawerKt_createContext(JNIEnv *env, jobject redrawer, jlong displayPtr)
{
Display *display = reinterpret_cast<Display *>(static_cast<uintptr_t>(displayPtr));
GLint att[] = {GLX_RGBA, GLX_DOUBLEBUFFER, True, None};
XVisualInfo *vi = glXChooseVisual(display, 0, att);
GLXContext *context = new GLXContext(glXCreateContext(display, vi, NULL, GL_TRUE));
return static_cast<jlong>(reinterpret_cast<uintptr_t>(context));
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_LinuxRedrawerKt_destroyContext(JNIEnv *env, jobject redrawer, jlong displayPtr, jlong contextPtr)
{
Display *display = reinterpret_cast<Display *>(static_cast<uintptr_t>(displayPtr));
GLXContext *context = reinterpret_cast<GLXContext *>(static_cast<uintptr_t>(contextPtr));
glXDestroyContext(display, *context);
delete context;
}
}
\ No newline at end of file
// drawcanvas.cpp : Defines the exported functions for the DLL application.
#include <SDKDDKVer.h>
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <gl/GL.h>
#include <jawt_md.h> #include <jawt_md.h>
#include <set>
#include <Shellscalingapi.h>
#include <stdio.h>
#include <Wingdi.h>
using namespace std;
JavaVM *jvm = NULL;
extern "C" jboolean Skiko_GetAWT(JNIEnv *env, JAWT *awt); extern "C" jboolean Skiko_GetAWT(JNIEnv *env, JAWT *awt);
class LayerHandler
{
public:
jobject canvasGlobalRef;
HGLRC context;
HDC device;
void updateLayerContent()
{
draw();
}
void disposeLayer(JNIEnv *env)
{
env->DeleteGlobalRef(canvasGlobalRef);
canvasGlobalRef = NULL;
context = NULL;
device = NULL;
}
private:
void draw()
{
if (jvm != NULL)
{
JNIEnv *env;
jvm->GetEnv((void **)&env, JNI_VERSION_10);
wglMakeCurrent(device, context);
static jclass wndClass = NULL;
if (!wndClass) wndClass = env->GetObjectClass(canvasGlobalRef);
static jmethodID drawMethod = NULL;
if (!drawMethod) drawMethod = env->GetMethodID(wndClass, "draw", "()V");
if (NULL == drawMethod)
{
fprintf(stderr, "The method Window.draw() not found!\n");
return;
}
env->CallVoidMethod(canvasGlobalRef, drawMethod);
glFinish();
SwapBuffers(device);
}
}
};
set<LayerHandler *> *layerStorage = NULL;
LayerHandler *findByObject(JNIEnv *env, jobject object)
{
if (layerStorage == NULL)
{
return NULL;
}
for (auto &layer : *layerStorage)
{
if (env->IsSameObject(object, layer->canvasGlobalRef) == JNI_TRUE)
{
return layer;
}
}
return NULL;
}
extern "C" extern "C"
{ {
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_updateLayer(JNIEnv *env, jobject canvas) JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_init(JNIEnv *env, jobject canvas)
{
if (layerStorage != NULL)
{
LayerHandler *layer = findByObject(env, canvas);
if (layer != NULL)
{
return;
}
}
else
{ {
layerStorage = new set<LayerHandler *>();
} }
JAWT awt; JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_dispose(JNIEnv *env, jobject canvas)
JAWT_DrawingSurface *ds = NULL;
JAWT_DrawingSurfaceInfo *dsi = NULL;
PIXELFORMATDESCRIPTOR pixFormatDscr;
HGLRC context = NULL;
jboolean result = JNI_FALSE;
jint lock = 0;
JAWT_Win32DrawingSurfaceInfo *dsi_win;
awt.version = (jint)JAWT_VERSION_9;
result = Skiko_GetAWT(env, &awt);
if (result == JNI_FALSE)
{
fprintf(stderr, "JAWT_GetAWT failed! Result is JNI_FALSE\n");
return;
}
if (jvm == NULL)
{
env->GetJavaVM(&jvm);
}
ds = awt.GetDrawingSurface(env, canvas);
lock = ds->Lock(ds);
dsi = ds->GetDrawingSurfaceInfo(ds);
dsi_win = (JAWT_Win32DrawingSurfaceInfo *)dsi->platformInfo;
HWND hwnd = dsi_win->hwnd;
HDC device = GetDC(hwnd);
if (dsi != NULL)
{
memset(&pixFormatDscr, 0, sizeof(PIXELFORMATDESCRIPTOR));
pixFormatDscr.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pixFormatDscr.nVersion = 1;
pixFormatDscr.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pixFormatDscr.iPixelType = PFD_TYPE_RGBA;
pixFormatDscr.cColorBits = 32;
int iPixelFormat = ChoosePixelFormat(device, &pixFormatDscr);
SetPixelFormat(device, iPixelFormat, &pixFormatDscr);
DescribePixelFormat(device, iPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pixFormatDscr);
context = wglCreateContext(device);
LayerHandler *layer = new LayerHandler();
layerStorage->insert(layer);
jobject canvasRef = env->NewGlobalRef(canvas);
layer->canvasGlobalRef = canvasRef;
layer->context = context;
layer->device = device;
}
ds->FreeDrawingSurfaceInfo(dsi);
ds->Unlock(ds);
awt.FreeDrawingSurface(ds);
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_redrawLayer(JNIEnv *env, jobject canvas)
{ {
LayerHandler *layer = findByObject(env, canvas);
if (layer != NULL)
{
layer->updateLayerContent();
}
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_disposeLayer(JNIEnv *env, jobject canvas)
{
LayerHandler *layer = findByObject(env, canvas);
if (layer != NULL)
{
layerStorage->erase(layer);
layer->disposeLayer(env);
delete layer;
}
} }
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_HardwareLayer_getWindowHandle(JNIEnv *env, jobject canvas) JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_HardwareLayer_getWindowHandle(JNIEnv *env, jobject canvas)
...@@ -182,8 +18,6 @@ extern "C" ...@@ -182,8 +18,6 @@ extern "C"
JAWT awt; JAWT awt;
JAWT_DrawingSurface *ds = NULL; JAWT_DrawingSurface *ds = NULL;
JAWT_DrawingSurfaceInfo *dsi = NULL; JAWT_DrawingSurfaceInfo *dsi = NULL;
PIXELFORMATDESCRIPTOR pixFormatDscr;
HGLRC context = NULL;
jboolean result = JNI_FALSE; jboolean result = JNI_FALSE;
jint lock = 0; jint lock = 0;
...@@ -198,11 +32,6 @@ extern "C" ...@@ -198,11 +32,6 @@ extern "C"
return -1; return -1;
} }
if (jvm == NULL)
{
env->GetJavaVM(&jvm);
}
ds = awt.GetDrawingSurface(env, canvas); ds = awt.GetDrawingSurface(env, canvas);
lock = ds->Lock(ds); lock = ds->Lock(ds);
dsi = ds->GetDrawingSurfaceInfo(ds); dsi = ds->GetDrawingSurfaceInfo(ds);
......
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <gl/GL.h>
#include <jawt_md.h>
#include <dwmapi.h>
extern "C" jboolean Skiko_GetAWT(JNIEnv *env, JAWT *awt);
extern "C"
{
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_WindowsRedrawerKt_getDevice(JNIEnv *env, jobject redrawer, jobject layer)
{
JAWT awt;
awt.version = (jint)JAWT_VERSION_9;
if (!Skiko_GetAWT(env, &awt))
{
fprintf(stderr, "JAWT_GetAWT failed! Result is JNI_FALSE\n");
return 0;
}
JAWT_DrawingSurface *ds = awt.GetDrawingSurface(env, layer);
ds->Lock(ds);
JAWT_DrawingSurfaceInfo *dsi = ds->GetDrawingSurfaceInfo(ds);
JAWT_Win32DrawingSurfaceInfo *dsi_win = (JAWT_Win32DrawingSurfaceInfo *)dsi->platformInfo;
HWND hwnd = dsi_win->hwnd;
HDC device = GetDC(hwnd);
if (dsi != NULL)
{
PIXELFORMATDESCRIPTOR pixFormatDscr;
memset(&pixFormatDscr, 0, sizeof(PIXELFORMATDESCRIPTOR));
pixFormatDscr.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pixFormatDscr.nVersion = 1;
pixFormatDscr.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pixFormatDscr.iPixelType = PFD_TYPE_RGBA;
pixFormatDscr.cColorBits = 32;
int iPixelFormat = ChoosePixelFormat(device, &pixFormatDscr);
SetPixelFormat(device, iPixelFormat, &pixFormatDscr);
DescribePixelFormat(device, iPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pixFormatDscr);
}
ds->FreeDrawingSurfaceInfo(dsi);
ds->Unlock(ds);
awt.FreeDrawingSurface(ds);
return static_cast<jlong>(reinterpret_cast<uintptr_t>(device));
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_WindowsRedrawerKt_setSwapInterval(JNIEnv *env, jobject redrawer, jint interval)
{
typedef BOOL (WINAPI *PFNWGLSWAPINTERVALEXTPROC)(int interval);
// according to [https://opengl.gpuinfo.org/listreports.php?extension=WGL_EXT_swap_control&option=not] (filter by OS=windows)
// there a very few devices that doesn't support swap control
static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress("wglSwapIntervalEXT");
if (wglSwapIntervalEXT != NULL)
{
wglSwapIntervalEXT(interval);
}
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_WindowsRedrawerKt_swapBuffers(JNIEnv *env, jobject redrawer, jlong devicePtr)
{
HDC device = reinterpret_cast<HDC>(static_cast<uintptr_t>(devicePtr));
SwapBuffers(device);
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_WindowsRedrawerKt_makeCurrent(JNIEnv *env, jobject redrawer, jlong devicePtr, jlong contextPtr)
{
HDC device = reinterpret_cast<HDC>(static_cast<uintptr_t>(devicePtr));
HGLRC context = reinterpret_cast<HGLRC>(static_cast<uintptr_t>(contextPtr));
wglMakeCurrent(device, context);
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_WindowsRedrawerKt_createContext(JNIEnv *env, jobject redrawer, jlong devicePtr)
{
HDC device = reinterpret_cast<HDC>(static_cast<uintptr_t>(devicePtr));
return static_cast<jlong>(reinterpret_cast<uintptr_t>(wglCreateContext(device)));
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_WindowsRedrawerKt_deleteContext(JNIEnv *env, jobject redrawer, jlong contextPtr)
{
HGLRC context = reinterpret_cast<HGLRC>(static_cast<uintptr_t>(contextPtr));
wglDeleteContext(context);
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_WindowsRedrawerKt_dwmFlush(JNIEnv *env, jobject redrawer)
{
DwmFlush();
}
}
\ No newline at end of file
package org.jetbrains.skiko
interface Drawable {
fun redrawLayer()
fun updateLayer()
fun disposeLayer()
val windowHandle: Long
val contentScale: Float
}
\ No newline at end of file
package org.jetbrains.skiko
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
import kotlin.coroutines.CoroutineContext
/**
* Dispatch frame after call of [scheduleFrame]
*/
class FrameDispatcher(
context: CoroutineContext,
private val onFrame: suspend () -> Unit
) {
private var needFrame = CompletableDeferred<Unit>()
private val job = GlobalScope.launch(context) {
while (true) {
needFrame.await()
needFrame = CompletableDeferred()
onFrame()
yield()
}
}
fun cancel() {
job.cancel()
}
fun scheduleFrame() {
needFrame.complete(Unit)
}
}
\ No newline at end of file
...@@ -3,10 +3,8 @@ package org.jetbrains.skiko ...@@ -3,10 +3,8 @@ package org.jetbrains.skiko
import java.awt.Canvas import java.awt.Canvas
import java.awt.Graphics import java.awt.Graphics
import java.awt.event.HierarchyEvent import java.awt.event.HierarchyEvent
import javax.swing.SwingUtilities.convertPoint
import javax.swing.SwingUtilities.getRootPane
abstract class HardwareLayer : Canvas(), Drawable { abstract class HardwareLayer : Canvas() {
companion object { companion object {
init { init {
Library.load() Library.load()
...@@ -29,10 +27,14 @@ abstract class HardwareLayer : Canvas(), Drawable { ...@@ -29,10 +27,14 @@ abstract class HardwareLayer : Canvas(), Drawable {
private fun checkIsShowing() { private fun checkIsShowing() {
if (!isInit && isShowing) { if (!isInit && isShowing) {
_contentScale = platformOperations.getDpiScale(this) _contentScale = platformOperations.getDpiScale(this)
init()
isInit = true isInit = true
} }
} }
protected open external fun init()
open external fun dispose()
protected open fun contentScaleChanged() = Unit protected open fun contentScaleChanged() = Unit
override fun paint(g: Graphics) { override fun paint(g: Graphics) {
...@@ -41,34 +43,20 @@ abstract class HardwareLayer : Canvas(), Drawable { ...@@ -41,34 +43,20 @@ abstract class HardwareLayer : Canvas(), Drawable {
_contentScale = contentScale _contentScale = contentScale
contentScaleChanged() contentScaleChanged()
} }
display()
}
open fun display() {
this.updateLayer()
this.redrawLayer()
} }
open fun draw() {} // Should be called in Swing thread
internal abstract suspend fun update(nanoTime: Long)
external override fun redrawLayer()
external override fun updateLayer() // Should be called in the OpenGL thread, and only once after update
internal abstract fun draw()
external override fun disposeLayer() val windowHandle: Long
override val windowHandle: Long
external get external get
override val contentScale: Float val contentScale: Float
get() = _contentScale!! get() = _contentScale!!
val absoluteX: Int
get() = convertPoint(this, x, y, getRootPane(this)).x
val absoluteY: Int
get() = convertPoint(this, x, y, getRootPane(this)).y
var fullscreen: Boolean var fullscreen: Boolean
get() = platformOperations.isFullscreen(this) get() = platformOperations.isFullscreen(this)
set(value) = platformOperations.setFullscreen(this, value) set(value) = platformOperations.setFullscreen(this, value)
......
package org.jetbrains.skiko package org.jetbrains.skiko
import org.jetbrains.skiko.redrawer.LinuxRedrawer
import org.jetbrains.skiko.redrawer.MacOsRedrawer
import org.jetbrains.skiko.redrawer.Redrawer
import org.jetbrains.skiko.redrawer.WindowsRedrawer
import java.awt.Component import java.awt.Component
import java.awt.Window import java.awt.Window
import javax.swing.SwingUtilities import javax.swing.SwingUtilities
...@@ -8,6 +12,7 @@ internal interface PlatformOperations { ...@@ -8,6 +12,7 @@ internal interface PlatformOperations {
fun isFullscreen(component: Component): Boolean fun isFullscreen(component: Component): Boolean
fun setFullscreen(component: Component, value: Boolean) fun setFullscreen(component: Component, value: Boolean)
fun getDpiScale(component: Component): Float fun getDpiScale(component: Component): Float
fun createHardwareRedrawer(layer: HardwareLayer): Redrawer
} }
internal val platformOperations: PlatformOperations by lazy { internal val platformOperations: PlatformOperations by lazy {
...@@ -24,6 +29,8 @@ internal val platformOperations: PlatformOperations by lazy { ...@@ -24,6 +29,8 @@ internal val platformOperations: PlatformOperations by lazy {
override fun getDpiScale(component: Component): Float { override fun getDpiScale(component: Component): Float {
return component.graphicsConfiguration.defaultTransform.scaleX.toFloat() return component.graphicsConfiguration.defaultTransform.scaleX.toFloat()
} }
override fun createHardwareRedrawer(layer: HardwareLayer) = MacOsRedrawer(layer)
} }
OS.Windows -> { OS.Windows -> {
object: PlatformOperations { object: PlatformOperations {
...@@ -42,6 +49,8 @@ internal val platformOperations: PlatformOperations by lazy { ...@@ -42,6 +49,8 @@ internal val platformOperations: PlatformOperations by lazy {
override fun getDpiScale(component: Component): Float { override fun getDpiScale(component: Component): Float {
return component.graphicsConfiguration.defaultTransform.scaleX.toFloat() return component.graphicsConfiguration.defaultTransform.scaleX.toFloat()
} }
override fun createHardwareRedrawer(layer: HardwareLayer) = WindowsRedrawer(layer)
} }
} }
OS.Linux -> { OS.Linux -> {
...@@ -61,6 +70,8 @@ internal val platformOperations: PlatformOperations by lazy { ...@@ -61,6 +70,8 @@ internal val platformOperations: PlatformOperations by lazy {
override fun getDpiScale(component: Component): Float { override fun getDpiScale(component: Component): Float {
return linuxGetDpiScaleNative(component) return linuxGetDpiScaleNative(component)
} }
override fun createHardwareRedrawer(layer: HardwareLayer) = LinuxRedrawer(layer)
} }
} }
} }
......
package org.jetbrains.skiko package org.jetbrains.skiko
import kotlinx.coroutines.runBlocking
import org.jetbrains.skija.* import org.jetbrains.skija.*
import org.jetbrains.skiko.redrawer.Redrawer
import java.awt.Graphics
import javax.swing.SwingUtilities.isEventDispatchThread import javax.swing.SwingUtilities.isEventDispatchThread
private class SkijaState { private class SkijaState {
...@@ -33,19 +34,40 @@ open class SkiaLayer : HardwareLayer() { ...@@ -33,19 +34,40 @@ open class SkiaLayer : HardwareLayer() {
@Volatile @Volatile
private var isDisposed = false private var isDisposed = false
private var redrawer: Redrawer? = null
@Volatile @Volatile
private var picture: PictureHolder? = null private var picture: PictureHolder? = null
private val pictureRecorder = PictureRecorder() private val pictureRecorder = PictureRecorder()
private val pictureLock = Any() private val pictureLock = Any()
override fun disposeLayer() { override fun init() {
redrawer = platformOperations.createHardwareRedrawer(this)
redrawer?.syncSize()
needRedraw()
}
override fun dispose() {
check(!isDisposed) check(!isDisposed)
check(isEventDispatchThread()) check(isEventDispatchThread())
redrawer?.dispose()
picture?.instance?.close() picture?.instance?.close()
pictureRecorder.close() pictureRecorder.close()
isDisposed = true isDisposed = true
super.disposeLayer() super.dispose()
}
override fun paint(g: Graphics) {
super.paint(g)
// we don't have to call it in setBounds method, because paint will always be called after setBounds
redrawer?.syncSize()
needRedraw()
}
fun needRedraw() {
check(!isDisposed)
check(isEventDispatchThread())
redrawer?.needRedraw()
} }
private val fpsCounter = FPSCounter( private val fpsCounter = FPSCounter(
...@@ -53,16 +75,9 @@ open class SkiaLayer : HardwareLayer() { ...@@ -53,16 +75,9 @@ open class SkiaLayer : HardwareLayer() {
probability = System.getProperty("skiko.fps.probability")?.toDouble() ?: 0.97 probability = System.getProperty("skiko.fps.probability")?.toDouble() ?: 0.97
) )
override fun draw() { override suspend fun update(nanoTime: Long) {
runBlocking {
performUpdate(System.nanoTime())
}
performDraw()
}
private suspend fun performUpdate(nanoTime: Long) {
check(!isDisposed) check(!isDisposed)
//check(isEventDispatchThread()) // TODO run from AWT Thread check(isEventDispatchThread())
if (System.getProperty("skiko.fps.enabled") == "true") { if (System.getProperty("skiko.fps.enabled") == "true") {
fpsCounter.tick() fpsCounter.tick()
...@@ -88,7 +103,7 @@ open class SkiaLayer : HardwareLayer() { ...@@ -88,7 +103,7 @@ open class SkiaLayer : HardwareLayer() {
} }
} }
private fun performDraw() { override fun draw() {
check(!isDisposed) check(!isDisposed)
if (skijaState.context == null) { if (skijaState.context == null) {
......
...@@ -10,8 +10,4 @@ open class SkiaWindow : JFrame() { ...@@ -10,8 +10,4 @@ open class SkiaWindow : JFrame() {
init { init {
contentPane.add(layer) contentPane.add(layer)
} }
fun display() {
layer.display()
}
} }
package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.swing.Swing
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.HardwareLayer
import org.jetbrains.skiko.OpenGLApi
internal class LinuxRedrawer(
private val layer: HardwareLayer
) : Redrawer {
private val context = layer.lockDrawingSurface {
val context = it.createContext()
it.makeCurrent(context)
it.setSwapInterval(1)
context
}
private var isDisposed = false
override fun dispose() {
check(!isDisposed)
layer.lockDrawingSurface {
it.destroyContext(context)
}
isDisposed = true
}
override fun needRedraw() {
toRedraw.add(this)
frameDispatcher.scheduleFrame()
}
companion object {
private val toRedraw = mutableSetOf<LinuxRedrawer>()
private val toRedrawCopy = mutableSetOf<LinuxRedrawer>()
private val toRedrawAlive = toRedrawCopy.asSequence().filterNot(LinuxRedrawer::isDisposed)
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
toRedrawCopy.clear()
toRedrawCopy.addAll(toRedraw)
toRedraw.clear()
val nanoTime = System.nanoTime()
for (redrawer in toRedrawAlive) {
redrawer.layer.update(nanoTime)
}
val drawingSurfaces = toRedrawAlive.map { lockDrawingSurface(it.layer) }.toList()
try {
toRedrawAlive.forEachIndexed { index, redrawer ->
drawingSurfaces[index].makeCurrent(redrawer.context)
redrawer.layer.draw()
}
toRedrawAlive.forEachIndexed { index, _ ->
drawingSurfaces[index].swapBuffers()
}
toRedrawAlive.forEachIndexed { index, redrawer ->
drawingSurfaces[index].makeCurrent(redrawer.context)
OpenGLApi.instance.glFinish()
}
} finally {
drawingSurfaces.forEach(::unlockDrawingSurface)
}
}
}
}
private inline fun <T> HardwareLayer.lockDrawingSurface(action: (DrawingSurface) -> T): T {
val drawingSurface = lockDrawingSurface(this)
try {
return action(drawingSurface)
} finally {
unlockDrawingSurface(drawingSurface)
}
}
private fun lockDrawingSurface(layer: HardwareLayer): DrawingSurface {
val ptr = lockDrawingSurfaceNative(layer)
return DrawingSurface(ptr, getDisplay(ptr), getWindow(ptr))
}
private fun unlockDrawingSurface(drawingSurface: DrawingSurface) {
unlockDrawingSurfaceNative(drawingSurface.ptr)
}
private class DrawingSurface(
val ptr: Long,
val display: Long,
val window: Long
) {
fun createContext() = createContext(display)
fun destroyContext(context: Long) = destroyContext(display, context)
fun makeCurrent(context: Long) = makeCurrent(display, window, context)
fun swapBuffers() = swapBuffers(display, window)
fun setSwapInterval(interval: Int) = setSwapInterval(display, window, interval)
}
private external fun lockDrawingSurfaceNative(layer: HardwareLayer): Long
private external fun unlockDrawingSurfaceNative(drawingSurface: Long)
private external fun getDisplay(drawingSurface: Long): Long
private external fun getWindow(drawingSurface: Long): Long
private external fun makeCurrent(display: Long, window: Long, context: Long)
private external fun createContext(display: Long): Long
private external fun destroyContext(display: Long, context: Long)
private external fun setSwapInterval(display: Long, window: Long, interval: Int)
private external fun swapBuffers(display: Long, window: Long)
\ No newline at end of file
package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.swing.Swing
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.HardwareLayer
import org.jetbrains.skiko.OpenGLApi
import javax.swing.SwingUtilities.convertPoint
import javax.swing.SwingUtilities.getRootPane
internal class MacOsRedrawer(
private val layer: HardwareLayer
) : Redrawer {
private val containerLayerPtr = initContainer(layer)
private val drawLayer = object : AWTGLLayer(containerLayerPtr) {
override fun draw() = layer.draw()
}
// use separate layer for vsync, because with single layer we cannot asynchronously update layer
// `update` is suspend, and runBlocking(Dispatchers.Swing) causes dead lock with AppKit Thread.
// AWT has internal method to avoid dead locks but it is internal (sun.lwawt.macosx.LWCToolkit.invokeAndWait)
private val vsyncLayer = object : AWTGLLayer(containerLayerPtr) {
@Volatile
private var needDraw: CompletableDeferred<Unit>? = null
init {
setFrame(0, 0, 1, 1) // if frame has zero size then it will be not drawn at all
}
override fun draw() {
// Clear layer with transparent color, so it will be not pink color.
val opengl = OpenGLApi.instance
opengl.glClearColor(0f, 0f, 0f, 0f)
opengl.glClear(opengl.GL_COLOR_BUFFER_BIT)
needDraw!!.complete(Unit)
}
override fun canDraw(): Boolean {
val canDraw = needDraw != null
if (!canDraw) {
isAsynchronous = false // stop asynchronous mode so we don't waste CPU cycles
}
return canDraw
}
suspend fun sync() {
check(needDraw == null)
needDraw = CompletableDeferred()
// Use asynchronous mode instead of just setNeedsDisplay,
// so Core Animation will wait for the next frame in vsync signal
//
// Asynchronous mode means that Core Animation will automatically
// call canDraw/draw every vsync signal (~16.7ms on 60Hz monitor)
//
// Similar is implemented in Chromium:
// https://chromium.googlesource.com/chromium/chromium/+/0489078bf98350b00876070cf2fdce230905f47e/content/browser/renderer_host/compositing_iosurface_layer_mac.mm#57
if (!isAsynchronous) {
isAsynchronous = true
super.setNeedsDisplay()
}
needDraw!!.await()
needDraw = null
}
}
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
layer.update(System.nanoTime())
drawLayer.setNeedsDisplay()
vsyncLayer.sync()
}
override fun dispose() {
frameDispatcher.cancel()
vsyncLayer.dispose()
drawLayer.dispose()
}
override fun syncSize() {
val globalPosition = convertPoint(layer, layer.x, layer.y, getRootPane(layer))
setContentScale(containerLayerPtr, layer.contentScale)
setContentScale(drawLayer.ptr, layer.contentScale)
drawLayer.setFrame(globalPosition.x, globalPosition.y, layer.width, layer.height)
}
override fun needRedraw() {
frameDispatcher.scheduleFrame()
}
}
private open class AWTGLLayer(private val containerPtr: Long) {
@Suppress("LeakingThis")
val ptr = initAWTGLLayer(containerPtr, this)
fun setFrame(x: Int, y: Int, width: Int, height: Int) {
setFrame(containerPtr, ptr, x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat())
}
// Called in AWT Thread
open fun dispose() = disposeAWTGLLayer(ptr)
var isAsynchronous: Boolean
get() = isAsynchronous(ptr)
set(value) = setAsynchronous(ptr, value)
open fun setNeedsDisplay() = setNeedsDisplayOnMainThread(ptr)
// Called in AppKit Thread
protected open fun canDraw() = true
// Called in AppKit Thread, shouldn't be called if canDraw returned false
protected open fun draw() = Unit
private external fun isAsynchronous(ptr: Long): Boolean
private external fun setAsynchronous(ptr: Long, isAsynchronous: Boolean)
private external fun setNeedsDisplayOnMainThread(nativePtr: Long)
protected external fun setFrame(containerPtr: Long, ptr: Long, x: Float, y: Float, width: Float, height: Float)
}
private external fun initContainer(layer: HardwareLayer): Long
private external fun setContentScale(layerNativePtr: Long, contentScale: Float)
private external fun initAWTGLLayer(containerPtr: Long, layer: AWTGLLayer): Long
private external fun disposeAWTGLLayer(ptr: Long)
package org.jetbrains.skiko.redrawer
interface Redrawer {
fun dispose()
fun needRedraw()
fun syncSize() = Unit
}
\ No newline at end of file
package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.swing.Swing
import kotlinx.coroutines.withContext
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.HardwareLayer
import org.jetbrains.skiko.OpenGLApi
internal class WindowsRedrawer(
private val layer: HardwareLayer
) : Redrawer {
private val device = getDevice(layer)
private val context = createContext(device)
private var isDisposed = false
init {
makeCurrent()
// For vsync we will use dwmFlush instead of swapInterval,
// because it isn't reliable with DWM (Desktop Windows Manager): interval between frames isn't stable (14-19ms).
// With dwmFlush it is stable (16.6-16.8 ms)
// GLFW also uses dwmFlush (https://www.glfw.org/docs/3.0/window.html#window_swap)
setSwapInterval(0)
}
override fun dispose() {
check(!isDisposed)
deleteContext(context)
isDisposed = true
}
override fun needRedraw() {
toRedraw.add(this)
frameDispatcher.scheduleFrame()
}
private fun makeCurrent() = makeCurrent(device, context)
private fun swapBuffers() = swapBuffers(device)
companion object {
private val toRedraw = mutableSetOf<WindowsRedrawer>()
private val toRedrawCopy = mutableSetOf<WindowsRedrawer>()
private val toRedrawAlive = toRedrawCopy.asSequence().filterNot(WindowsRedrawer::isDisposed)
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
toRedrawCopy.clear()
toRedrawCopy.addAll(toRedraw)
toRedraw.clear()
val nanoTime = System.nanoTime()
for (redrawer in toRedrawAlive) {
redrawer.layer.update(nanoTime)
}
for (redrawer in toRedrawAlive) {
redrawer.makeCurrent()
redrawer.layer.draw()
}
for (redrawer in toRedrawAlive) {
redrawer.swapBuffers()
}
for (redrawer in toRedrawAlive) {
redrawer.makeCurrent()
OpenGLApi.instance.glFinish()
}
withContext(Dispatchers.IO) {
dwmFlush() // wait for vsync
}
}
}
}
private external fun makeCurrent(device: Long, context: Long)
private external fun getDevice(layer: HardwareLayer): Long
private external fun createContext(device: Long): Long
private external fun deleteContext(context: Long)
private external fun setSwapInterval(interval: Int)
private external fun swapBuffers(device: Long)
// TODO according to https://bugs.chromium.org/p/chromium/issues/detail?id=467617 dwmFlush has lag 3 ms after vsync.
// Maybe we should use D3DKMTWaitForVerticalBlankEvent? See also https://www.vsynctester.com/chromeisbroken.html
// TODO should we support Windows 7? DWM can be disabled on Windows 7.
// it that case there will be a crash or just no frame limit (I don't know exactly).
private external fun dwmFlush()
\ No newline at end of file
...@@ -7,69 +7,13 @@ ...@@ -7,69 +7,13 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h> #import <QuartzCore/QuartzCore.h>
#import <OpenGL/gl3.h>
#import <Metal/Metal.h> #import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h> #import <QuartzCore/CAMetalLayer.h>
#import <pthread.h>
JavaVM *jvm = NULL;
@interface AWTGLLayer : CAOpenGLLayer
@property jobject canvasGlobalRef;
@end
@implementation AWTGLLayer
- (id)init
{
self = [super init];
if (self)
{
[self removeAllAnimations];
[self setAutoresizingMask: (kCALayerWidthSizable|kCALayerHeightSizable)];
[self setNeedsDisplayOnBoundsChange: YES];
self.canvasGlobalRef = NULL;
}
return self;
}
-(void)drawInCGLContext:(CGLContextObj)ctx
pixelFormat:(CGLPixelFormatObj)pf
forLayerTime:(CFTimeInterval)t
displayTime:(const CVTimeStamp *)ts
{
CGLSetCurrentContext(ctx);
if (jvm != NULL) {
JNIEnv *env;
(*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
static jclass wndClass = NULL;
if (!wndClass) wndClass = (*env)->GetObjectClass(env, self.canvasGlobalRef);
static jmethodID drawMethod = NULL;
if (!drawMethod) drawMethod = (*env)->GetMethodID(env, wndClass, "draw", "()V");
if (NULL == drawMethod)
{
NSLog(@"The method HardwareLayer.draw() not found!");
return;
}
(*env)->CallVoidMethod(env, self.canvasGlobalRef, drawMethod);
}
[super drawInCGLContext:ctx pixelFormat:pf forLayerTime:t displayTime:ts];
}
@end
@interface LayerHandler : NSObject @interface LayerHandler : NSObject
@property jobject canvasGlobalRef;
@property (retain, strong) CALayer *container; @property (retain, strong) CALayer *container;
@property (retain, strong) AWTGLLayer *glLayer;
@property (retain, strong) NSWindow *window; @property (retain, strong) NSWindow *window;
@end @end
...@@ -82,107 +26,18 @@ JavaVM *jvm = NULL; ...@@ -82,107 +26,18 @@ JavaVM *jvm = NULL;
if (self) if (self)
{ {
self.canvasGlobalRef = NULL;
self.container = NULL; self.container = NULL;
self.glLayer = NULL;
self.window = NULL; self.window = NULL;
} }
return self; return self;
} }
- (void) syncLayersSize
{
if (jvm != NULL) {
JNIEnv *env;
(*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
static jclass wndClass = NULL;
if (!wndClass)
{
wndClass = (*env)->GetObjectClass(env, self.glLayer.canvasGlobalRef);
}
// scale factor
static jmethodID contentScaleMethod = NULL;
if (!contentScaleMethod)
{
contentScaleMethod = (*env)->GetMethodID(env, wndClass, "getContentScale", "()F");
}
if (NULL == contentScaleMethod)
{
NSLog(@"The method HardwareLayer.getContentScale() not found!");
return;
}
float scaleFactor = (*env)->CallFloatMethod(env, self.glLayer.canvasGlobalRef, contentScaleMethod);
assert(scaleFactor != 0);
self.container.contentsScale = scaleFactor;
self.glLayer.contentsScale = scaleFactor;
// size & position
static jmethodID getXMethod = NULL;
if (!getXMethod)
{
getXMethod = (*env)->GetMethodID(env, wndClass, "getAbsoluteX", "()I");
}
if (NULL == getXMethod)
{
NSLog(@"The method HardwareLayer.getAbsoluteX() not found!");
return;
}
static jmethodID getYMethod = NULL;
if (!getYMethod)
{
getYMethod = (*env)->GetMethodID(env, wndClass, "getAbsoluteY", "()I");
}
if (NULL == getYMethod)
{
NSLog(@"The method HardwareLayer.getAbsoluteY() not found!");
return;
}
static jmethodID getWidthMethod = NULL;
if (!getWidthMethod)
{
getWidthMethod = (*env)->GetMethodID(env, wndClass, "getWidth", "()I");
}
if (NULL == getWidthMethod)
{
NSLog(@"The method HardwareLayer.getWidth() not found!");
return;
}
static jmethodID getHeightMethod = NULL;
if (!getHeightMethod)
{
getHeightMethod = (*env)->GetMethodID(env, wndClass, "getHeight", "()I");
}
if (NULL == getHeightMethod)
{
NSLog(@"The method HardwareLayer.getHeight() not found!");
return;
}
int x = (*env)->CallIntMethod(env, self.glLayer.canvasGlobalRef, getXMethod);
int y = (*env)->CallIntMethod(env, self.glLayer.canvasGlobalRef, getYMethod);
int w = (*env)->CallIntMethod(env, self.glLayer.canvasGlobalRef, getWidthMethod);
int h = (*env)->CallIntMethod(env, self.glLayer.canvasGlobalRef, getHeightMethod);
y = (int)self.container.frame.size.height - y - h;
CGRect boundsRect = CGRectMake(x, y, w, h);
self.glLayer.frame = boundsRect;
}
}
- (void) updateLayerContent
{
[self.glLayer performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:0 waitUntilDone:NO];
}
- (void) disposeLayer:(JNIEnv *) env - (void) disposeLayer:(JNIEnv *) env
{ {
[self.glLayer removeFromSuperlayer]; (*env)->DeleteGlobalRef(env, self.canvasGlobalRef);
(*env)->DeleteGlobalRef(env, self.glLayer.canvasGlobalRef); self.canvasGlobalRef = NULL;
self.glLayer.canvasGlobalRef = NULL;
self.glLayer = NULL;
self.container = NULL; self.container = NULL;
self.window = NULL; self.window = NULL;
} }
...@@ -209,15 +64,6 @@ JavaVM *jvm = NULL; ...@@ -209,15 +64,6 @@ JavaVM *jvm = NULL;
NSMutableArray *unknownWindows = nil; NSMutableArray *unknownWindows = nil;
NSMutableSet *layerStorage = nil; NSMutableSet *layerStorage = nil;
pthread_mutex_t layerStorageMutex = { 0 };
void lockLayers() {
pthread_mutex_lock(&layerStorageMutex);
}
void unlockLayers() {
pthread_mutex_unlock(&layerStorageMutex);
}
LayerHandler * findByObject(JNIEnv *env, jobject object) LayerHandler * findByObject(JNIEnv *env, jobject object)
{ {
...@@ -227,7 +73,7 @@ LayerHandler * findByObject(JNIEnv *env, jobject object) ...@@ -227,7 +73,7 @@ LayerHandler * findByObject(JNIEnv *env, jobject object)
} }
for (LayerHandler* layer in layerStorage) for (LayerHandler* layer in layerStorage)
{ {
if ((*env)->IsSameObject(env, object, layer.glLayer.canvasGlobalRef) == JNI_TRUE) if ((*env)->IsSameObject(env, object, layer.canvasGlobalRef) == JNI_TRUE)
{ {
return layer; return layer;
} }
...@@ -237,20 +83,9 @@ LayerHandler * findByObject(JNIEnv *env, jobject object) ...@@ -237,20 +83,9 @@ LayerHandler * findByObject(JNIEnv *env, jobject object)
extern jboolean Skiko_GetAWT(JNIEnv* env, JAWT* awt); extern jboolean Skiko_GetAWT(JNIEnv* env, JAWT* awt);
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_updateLayer(JNIEnv *env, jobject canvas) JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_init(JNIEnv *env, jobject canvas)
{ {
lockLayers(); if (layerStorage == nil)
if (layerStorage != nil)
{
LayerHandler *layer = findByObject(env, canvas);
if (layer != NULL)
{
[layer syncLayersSize];
unlockLayers();
return;
}
}
else
{ {
layerStorage = [[NSMutableSet alloc] init]; layerStorage = [[NSMutableSet alloc] init];
} }
...@@ -258,8 +93,6 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_updateLayer(JNIEnv ...@@ -258,8 +93,6 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_updateLayer(JNIEnv
JAWT awt; JAWT awt;
JAWT_DrawingSurface *ds = NULL; JAWT_DrawingSurface *ds = NULL;
JAWT_DrawingSurfaceInfo *dsi = NULL; JAWT_DrawingSurfaceInfo *dsi = NULL;
CGLPixelFormatObj pixFormatObj = NULL;
CGLContextObj context;
jboolean result = JNI_FALSE; jboolean result = JNI_FALSE;
jint lock = 0; jint lock = 0;
...@@ -269,8 +102,6 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_updateLayer(JNIEnv ...@@ -269,8 +102,6 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_updateLayer(JNIEnv
result = Skiko_GetAWT(env, &awt); result = Skiko_GetAWT(env, &awt);
assert(result != JNI_FALSE); assert(result != JNI_FALSE);
(*env)->GetJavaVM(env, &jvm);
ds = awt.GetDrawingSurface(env, canvas); ds = awt.GetDrawingSurface(env, canvas);
assert(ds != NULL); assert(ds != NULL);
...@@ -286,18 +117,8 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_updateLayer(JNIEnv ...@@ -286,18 +117,8 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_updateLayer(JNIEnv
LayerHandler *layersSet = [[LayerHandler alloc] init]; LayerHandler *layersSet = [[LayerHandler alloc] init];
layersSet.container = [dsi_mac windowLayer]; layersSet.container = [dsi_mac windowLayer];
[layersSet.container removeAllAnimations];
[layersSet.container setAutoresizingMask: (kCALayerWidthSizable|kCALayerHeightSizable)];
[layersSet.container setNeedsDisplayOnBoundsChange: YES];
layersSet.glLayer = [AWTGLLayer new];
[layersSet.container addSublayer: layersSet.glLayer];
jobject canvasGlobalRef = (*env)->NewGlobalRef(env, canvas); jobject canvasGlobalRef = (*env)->NewGlobalRef(env, canvas);
[layersSet setCanvasGlobalRef: canvasGlobalRef];
[layersSet.glLayer setCanvasGlobalRef: canvasGlobalRef];
[layersSet syncLayersSize];
if (unknownWindows == nil) if (unknownWindows == nil)
{ {
...@@ -332,25 +153,11 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_updateLayer(JNIEnv ...@@ -332,25 +153,11 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_updateLayer(JNIEnv
ds->FreeDrawingSurfaceInfo(dsi); ds->FreeDrawingSurfaceInfo(dsi);
ds->Unlock(ds); ds->Unlock(ds);
awt.FreeDrawingSurface(ds); awt.FreeDrawingSurface(ds);
unlockLayers();
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_redrawLayer(JNIEnv *env, jobject canvas)
{
lockLayers();
LayerHandler *layer = findByObject(env, canvas);
unlockLayers();
if (layer != NULL)
{
[layer updateLayerContent];
}
} }
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_disposeLayer(JNIEnv *env, jobject canvas) JNIEXPORT void JNICALL Java_org_jetbrains_skiko_HardwareLayer_dispose(JNIEnv *env, jobject canvas)
{ {
lockLayers();
LayerHandler *layer = findByObject(env, canvas); LayerHandler *layer = findByObject(env, canvas);
unlockLayers();
if (layer != NULL) if (layer != NULL)
{ {
[layerStorage removeObject: layer]; [layerStorage removeObject: layer];
...@@ -365,9 +172,7 @@ JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_HardwareLayer_getWindowHandle(J ...@@ -365,9 +172,7 @@ JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_HardwareLayer_getWindowHandle(J
JNIEXPORT jboolean JNICALL Java_org_jetbrains_skiko_PlatformOperationsKt_osxIsFullscreenNative(JNIEnv *env, jobject properties, jobject component) JNIEXPORT jboolean JNICALL Java_org_jetbrains_skiko_PlatformOperationsKt_osxIsFullscreenNative(JNIEnv *env, jobject properties, jobject component)
{ {
lockLayers();
LayerHandler *layer = findByObject(env, component); LayerHandler *layer = findByObject(env, component);
unlockLayers();
if (layer != NULL) if (layer != NULL)
{ {
return [layer isFullScreen]; return [layer isFullScreen];
...@@ -377,9 +182,7 @@ JNIEXPORT jboolean JNICALL Java_org_jetbrains_skiko_PlatformOperationsKt_osxIsFu ...@@ -377,9 +182,7 @@ JNIEXPORT jboolean JNICALL Java_org_jetbrains_skiko_PlatformOperationsKt_osxIsFu
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_PlatformOperationsKt_osxSetFullscreenNative(JNIEnv *env, jobject properties, jobject component, jboolean value) JNIEXPORT void JNICALL Java_org_jetbrains_skiko_PlatformOperationsKt_osxSetFullscreenNative(JNIEnv *env, jobject properties, jobject component, jboolean value)
{ {
lockLayers();
LayerHandler *layer = findByObject(env, component); LayerHandler *layer = findByObject(env, component);
unlockLayers();
if (layer != NULL) if (layer != NULL)
{ {
[layer makeFullscreen:value]; [layer makeFullscreen:value];
......
#import "jawt.h"
#import "jawt_md.h"
#define GL_SILENCE_DEPRECATION
#import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h>
#import <OpenGL/gl3.h>
extern jboolean Skiko_GetAWT(JNIEnv* env, JAWT* awt);
JavaVM *jvm = NULL;
@interface AWTGLLayer : CAOpenGLLayer
@property jobject javaRef;
@end
@implementation AWTGLLayer
- (id)init
{
self = [super init];
assert(self != NULL);
[self removeAllAnimations];
[self setAutoresizingMask: (kCALayerWidthSizable|kCALayerHeightSizable)];
[self setNeedsDisplayOnBoundsChange: YES];
return self;
}
-(BOOL)canDrawInCGLContext:(CGLContextObj)ctx
pixelFormat:(CGLPixelFormatObj)pf
forLayerTime:(CFTimeInterval)t
displayTime:(const CVTimeStamp *)ts
{
assert(jvm != NULL);
JNIEnv *env;
(*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
static jclass cls = NULL;
static jmethodID method = NULL;
if (!cls) cls = (*env)->GetObjectClass(env, self.javaRef);
if (!method) method = (*env)->GetMethodID(env, cls, "canDraw", "()Z");
return (*env)->CallBooleanMethod(env, self.javaRef, method);
}
-(void)drawInCGLContext:(CGLContextObj)ctx
pixelFormat:(CGLPixelFormatObj)pf
forLayerTime:(CFTimeInterval)t
displayTime:(const CVTimeStamp *)ts
{
CGLSetCurrentContext(ctx);
assert(jvm != NULL);
JNIEnv *env;
(*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
static jclass cls = NULL;
static jmethodID method = NULL;
if (!cls) cls = (*env)->GetObjectClass(env, self.javaRef);
if (!method) method = (*env)->GetMethodID(env, cls, "draw", "()V");
(*env)->CallVoidMethod(env, self.javaRef, method);
[super drawInCGLContext:ctx pixelFormat:pf forLayerTime:t displayTime:ts];
}
@end
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MacOsRedrawerKt_initContainer(JNIEnv *env, jobject redrawer, jobject layer)
{
JAWT awt;
awt.version = JAWT_VERSION_9;
jboolean result = Skiko_GetAWT(env, &awt);
assert(result != JNI_FALSE);
(*env)->GetJavaVM(env, &jvm);
JAWT_DrawingSurface *ds = awt.GetDrawingSurface(env, layer);
assert(ds != NULL);
jint lock = ds->Lock(ds);
assert((lock & JAWT_LOCK_ERROR) == 0);
JAWT_DrawingSurfaceInfo *dsi = ds->GetDrawingSurfaceInfo(ds);
assert(dsi != NULL);
NSObject<JAWT_SurfaceLayers>* dsi_mac = (__bridge NSObject<JAWT_SurfaceLayers> *) dsi->platformInfo;
CALayer *container = [dsi_mac windowLayer];
[container removeAllAnimations];
[container setAutoresizingMask: (kCALayerWidthSizable|kCALayerHeightSizable)];
[container setNeedsDisplayOnBoundsChange: YES];
ds->FreeDrawingSurfaceInfo(dsi);
ds->Unlock(ds);
awt.FreeDrawingSurface(ds);
return (jlong) container;
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_MacOsRedrawerKt_setContentScale(JNIEnv *env, jobject obj, jlong layerPtr, jfloat contentScale)
{
CALayer *layer = (CALayer *) layerPtr;
assert(contentScale != 0);
layer.contentsScale = contentScale;
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MacOsRedrawerKt_initAWTGLLayer(JNIEnv *env, jobject obj, jlong containerPtr, jobject layer)
{
CALayer *container = (CALayer *) containerPtr;
AWTGLLayer *glLayer = [AWTGLLayer new];
glLayer.javaRef = (*env)->NewGlobalRef(env, layer);
[container addSublayer: glLayer];
return (jlong) glLayer;
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_MacOsRedrawerKt_disposeAWTGLLayer(JNIEnv *env, jobject obj, jlong ptr)
{
AWTGLLayer *glLayer = (AWTGLLayer *) ptr;
[glLayer removeFromSuperlayer];
(*env)->DeleteGlobalRef(env, glLayer.javaRef);
[glLayer release];
}
JNIEXPORT jboolean JNICALL Java_org_jetbrains_skiko_redrawer_AWTGLLayer_isAsynchronous(JNIEnv *env, jobject obj, jlong ptr)
{
AWTGLLayer *glLayer = (AWTGLLayer *) ptr;
return glLayer.isAsynchronous;
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AWTGLLayer_setAsynchronous(JNIEnv *env, jobject obj, jlong ptr, jboolean isAsynchronous)
{
AWTGLLayer *glLayer = (AWTGLLayer *) ptr;
[glLayer setAsynchronous: isAsynchronous];
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AWTGLLayer_setNeedsDisplayOnMainThread(JNIEnv *env, jobject obj, jlong ptr)
{
AWTGLLayer *glLayer = (AWTGLLayer *) ptr;
[glLayer performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:0 waitUntilDone:NO];
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AWTGLLayer_setFrame(JNIEnv *env, jobject obj, jlong containerPtr, jlong ptr, jfloat x, jfloat y, jfloat width, jfloat height)
{
CALayer *container = (AWTGLLayer *) containerPtr;
AWTGLLayer *glLayer = (AWTGLLayer *) ptr;
y = (int)container.frame.size.height - y - height;
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
forKey:kCATransactionDisableActions]; // disable animations
glLayer.frame = CGRectMake(x, y, width, height);
[CATransaction commit];
}
\ 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