Unverified Commit f7fe8816 authored by Roman Sedaikin's avatar Roman Sedaikin Committed by GitHub

New software rendering for Windows and Linux. Boosted performance. (#215)

Added direct software rendering for windows and linux. Fix fallback on Linux.
parent d7320949
...@@ -67,7 +67,7 @@ val casualRun = tasks.named<JavaExec>("run") { ...@@ -67,7 +67,7 @@ val casualRun = tasks.named<JavaExec>("run") {
} }
tasks.register("runSoftware") { tasks.register("runSoftware") {
additionalArguments += mapOf("skiko.renderApi" to "SOFTWARE") additionalArguments += mapOf("skiko.renderApi" to "DIRECT_SOFTWARE")
dependsOn(casualRun) dependsOn(casualRun)
} }
......
#include <jawt_md.h>
#include <X11/Xresource.h>
#include <stdint.h>
#include "jni_helpers.h"
#include "SkSurface.h"
#include "src/core/SkAutoMalloc.h"
class SoftwareDevice
{
public:
Display* display;
Window window;
GC gc;
SkAutoMalloc surfaceMemory;
sk_sp<SkSurface> surface;
unsigned int depth = 0;
SkColorType colorSpace = kUnknown_SkColorType;
void initDevice() {
Window wnd;
int x, y;
unsigned int w, h, border;
XGetGeometry(display, window, &wnd, &x, &y, &w, &h, &border, &depth);
switch(depth) {
case 16:
colorSpace = kRGB_565_SkColorType;
break;
case 24: case 32:
colorSpace = kBGRA_8888_SkColorType;
break;
default:
colorSpace = kUnknown_SkColorType;
}
}
~SoftwareDevice() {
if (display != NULL && gc != NULL)
{
XFreeGC(display, gc);
}
}
};
extern "C"
{
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_LinuxSoftwareRedrawer_createDevice(
JNIEnv *env, jobject redrawer, jlong displayPtr, jlong windowPtr)
{
Display *display = fromJavaPointer<Display *>(displayPtr);
Window window = fromJavaPointer<Window>(windowPtr);
SoftwareDevice *device = new SoftwareDevice();
device->display = display;
device->window = window;
device->gc = XCreateGC(device->display, device->window, 0, nullptr);
device->initDevice();
if (device->colorSpace == kUnknown_SkColorType)
{
return 0L;
}
return toJavaPointer(device);
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AbstractDirectSoftwareRedrawer_resize(
JNIEnv *env, jobject redrawer, jlong devicePtr, jint width, jint height)
{
SoftwareDevice *device = fromJavaPointer<SoftwareDevice *>(devicePtr);
device->surface.reset();
SkImageInfo info = SkImageInfo::Make(
width, height, device->colorSpace, kPremul_SkAlphaType,
SkColorSpace::MakeSRGB());
device->surface = SkSurface::MakeRaster(info);
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_AbstractDirectSoftwareRedrawer_getSurface(
JNIEnv *env, jobject redrawer, jlong devicePtr)
{
SoftwareDevice *device = fromJavaPointer<SoftwareDevice *>(devicePtr);
return toJavaPointer(device->surface.get());
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AbstractDirectSoftwareRedrawer_finishFrame(
JNIEnv *env, jobject redrawer, jlong devicePtr)
{
SoftwareDevice *device = fromJavaPointer<SoftwareDevice *>(devicePtr);
SkPixmap pm;
if (!device->surface->peekPixels(&pm)) {
return;
}
int bitsPerPixel = pm.info().bytesPerPixel() * 8;
XImage image;
memset(&image, 0, sizeof(image));
image.width = pm.width();
image.height = pm.height();
image.format = ZPixmap;
image.data = (char*) pm.addr();
image.byte_order = LSBFirst;
image.bitmap_unit = bitsPerPixel;
image.bitmap_bit_order = LSBFirst;
image.bitmap_pad = bitsPerPixel;
image.depth = device->depth;
image.bytes_per_line = pm.rowBytes() - pm.width() * pm.info().bytesPerPixel();
image.bits_per_pixel = bitsPerPixel;
if (!XInitImage(&image)) {
return;
}
XPutImage(device->display, device->window, device->gc, &image, 0, 0, 0, 0, pm.width(), pm.height());
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AbstractDirectSoftwareRedrawer_disposeDevice(
JNIEnv *env, jobject redrawer, jlong devicePtr)
{
SoftwareDevice *device = fromJavaPointer<SoftwareDevice *>(devicePtr);
delete device;
}
}
#ifdef SK_DIRECT3D
#include <Windows.h> #include <Windows.h>
#include <sstream> #include <sstream>
#include <iostream> #include <iostream>
...@@ -59,5 +57,3 @@ extern "C" ...@@ -59,5 +57,3 @@ extern "C"
return env->NewStringUTF("Can't get CPU info."); return env->NewStringUTF("Can't get CPU info.");
} }
} }
#endif
\ No newline at end of file
#include <Windows.h>
#include <jawt_md.h>
#include "jni_helpers.h"
#include "exceptions_handler.h"
#include "SkSurface.h"
#include "src/core/SkAutoMalloc.h"
class SoftwareDevice
{
public:
HWND window;
RECT clientRect;
SkAutoMalloc surfaceMemory;
sk_sp<SkSurface> surface;
~SoftwareDevice() {}
};
extern "C"
{
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_WindowsSoftwareRedrawer_createDevice(
JNIEnv *env, jobject redrawer, jlong contentHandle)
{
SoftwareDevice *device = new SoftwareDevice();
device->window = (HWND)contentHandle;
GetClientRect(device->window, &device->clientRect);
return toJavaPointer(device);
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AbstractDirectSoftwareRedrawer_resize(
JNIEnv *env, jobject redrawer, jlong devicePtr, jint width, jint height)
{
__try
{
SoftwareDevice *device = fromJavaPointer<SoftwareDevice *>(devicePtr);
device->surface.reset();
const size_t bmpSize = sizeof(BITMAPINFOHEADER) + width * height * sizeof(uint32_t);
device->surfaceMemory.reset(bmpSize);
BITMAPINFO *bmpInfo = reinterpret_cast<BITMAPINFO *>(device->surfaceMemory.get());
ZeroMemory(bmpInfo, sizeof(BITMAPINFO));
bmpInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfo->bmiHeader.biWidth = width;
bmpInfo->bmiHeader.biHeight = -height;
bmpInfo->bmiHeader.biPlanes = 1;
bmpInfo->bmiHeader.biBitCount = 32;
bmpInfo->bmiHeader.biCompression = BI_RGB;
void *pixels = bmpInfo->bmiColors;
SkImageInfo info = SkImageInfo::Make(
width, height, kBGRA_8888_SkColorType, kPremul_SkAlphaType,
SkColorSpace::MakeSRGB());
device->surface = SkSurface::MakeRasterDirect(info, pixels, sizeof(uint32_t) * width);
GetClientRect(device->window, &device->clientRect);
}
__except(EXCEPTION_EXECUTE_HANDLER) {
auto code = GetExceptionCode();
logJavaException(env, __FUNCTION__, code);
}
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_AbstractDirectSoftwareRedrawer_getSurface(
JNIEnv *env, jobject redrawer, jlong devicePtr)
{
SoftwareDevice *device = fromJavaPointer<SoftwareDevice *>(devicePtr);
return toJavaPointer(device->surface.release());
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AbstractDirectSoftwareRedrawer_finishFrame(
JNIEnv *env, jobject redrawer, jlong devicePtr)
{
__try
{
SoftwareDevice *device = fromJavaPointer<SoftwareDevice *>(devicePtr);
BITMAPINFO *bmpInfo = reinterpret_cast<BITMAPINFO *>(device->surfaceMemory.get());
HDC dc = GetDC(device->window);
int width = device->clientRect.right - device->clientRect.left;
int height = device->clientRect.bottom - device->clientRect.top;
StretchDIBits(dc, 0, 0, width, height, 0, 0, width, height, bmpInfo->bmiColors, bmpInfo,
DIB_RGB_COLORS, SRCCOPY);
ReleaseDC(device->window, dc);
}
__except(EXCEPTION_EXECUTE_HANDLER) {
auto code = GetExceptionCode();
logJavaException(env, __FUNCTION__, code);
}
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_AbstractDirectSoftwareRedrawer_disposeDevice(
JNIEnv *env, jobject redrawer, jlong devicePtr)
{
SoftwareDevice *device = fromJavaPointer<SoftwareDevice *>(devicePtr);
delete device;
}
}
...@@ -5,7 +5,7 @@ import org.jetbrains.skiko.redrawer.WindowsOpenGLRedrawer ...@@ -5,7 +5,7 @@ import org.jetbrains.skiko.redrawer.WindowsOpenGLRedrawer
import org.jetbrains.skiko.redrawer.LinuxOpenGLRedrawer import org.jetbrains.skiko.redrawer.LinuxOpenGLRedrawer
enum class GraphicsApi { enum class GraphicsApi {
UNKNOWN, SOFTWARE, OPENGL, DIRECT3D, VULKAN, METAL UNKNOWN, SOFTWARE, DIRECT_SOFTWARE, OPENGL, DIRECT3D, VULKAN, METAL
} }
enum class GpuPriority(val value: String) { enum class GpuPriority(val value: String) {
......
...@@ -11,6 +11,10 @@ internal interface RenderFactory { ...@@ -11,6 +11,10 @@ internal interface RenderFactory {
val Default = object : RenderFactory { val Default = object : RenderFactory {
override fun createContextHandler(layer: SkiaLayer, renderApi: GraphicsApi): ContextHandler { override fun createContextHandler(layer: SkiaLayer, renderApi: GraphicsApi): ContextHandler {
return when (renderApi) { return when (renderApi) {
GraphicsApi.DIRECT_SOFTWARE -> return when (hostOs) {
OS.Windows, OS.Linux -> DirectSoftwareContextHandler(layer)
else -> SoftwareContextHandler(layer)
}
GraphicsApi.SOFTWARE -> SoftwareContextHandler(layer) GraphicsApi.SOFTWARE -> SoftwareContextHandler(layer)
GraphicsApi.OPENGL -> OpenGLContextHandler(layer) GraphicsApi.OPENGL -> OpenGLContextHandler(layer)
GraphicsApi.DIRECT3D -> Direct3DContextHandler(layer) GraphicsApi.DIRECT3D -> Direct3DContextHandler(layer)
...@@ -25,16 +29,18 @@ internal interface RenderFactory { ...@@ -25,16 +29,18 @@ internal interface RenderFactory {
properties: SkiaLayerProperties properties: SkiaLayerProperties
): Redrawer = when (hostOs) { ): Redrawer = when (hostOs) {
OS.MacOS -> when (renderApi) { OS.MacOS -> when (renderApi) {
GraphicsApi.SOFTWARE -> SoftwareRedrawer(layer, properties) GraphicsApi.SOFTWARE, GraphicsApi.DIRECT_SOFTWARE -> SoftwareRedrawer(layer, properties)
else -> MetalRedrawer(layer, properties) else -> MetalRedrawer(layer, properties)
} }
OS.Windows -> when (renderApi) { OS.Windows -> when (renderApi) {
GraphicsApi.SOFTWARE -> SoftwareRedrawer(layer, properties) GraphicsApi.SOFTWARE -> SoftwareRedrawer(layer, properties)
GraphicsApi.DIRECT_SOFTWARE -> WindowsSoftwareRedrawer(layer, properties)
GraphicsApi.OPENGL -> WindowsOpenGLRedrawer(layer, properties) GraphicsApi.OPENGL -> WindowsOpenGLRedrawer(layer, properties)
else -> Direct3DRedrawer(layer, properties) else -> Direct3DRedrawer(layer, properties)
} }
OS.Linux -> when (renderApi) { OS.Linux -> when (renderApi) {
GraphicsApi.SOFTWARE -> SoftwareRedrawer(layer, properties) GraphicsApi.SOFTWARE -> SoftwareRedrawer(layer, properties)
GraphicsApi.DIRECT_SOFTWARE -> LinuxSoftwareRedrawer(layer, properties)
else -> LinuxOpenGLRedrawer(layer, properties) else -> LinuxOpenGLRedrawer(layer, properties)
} }
} }
......
...@@ -37,6 +37,7 @@ internal object SkikoProperties { ...@@ -37,6 +37,7 @@ internal object SkikoProperties {
private fun parseRenderApi(text: String?): GraphicsApi { private fun parseRenderApi(text: String?): GraphicsApi {
when(text) { when(text) {
"SOFTWARE" -> return GraphicsApi.SOFTWARE "SOFTWARE" -> return GraphicsApi.SOFTWARE
"DIRECT_SOFTWARE" -> return GraphicsApi.DIRECT_SOFTWARE
"OPENGL" -> return GraphicsApi.OPENGL "OPENGL" -> return GraphicsApi.OPENGL
"DIRECT3D" -> { "DIRECT3D" -> {
return if (hostOs == OS.Windows) GraphicsApi.DIRECT3D return if (hostOs == OS.Windows) GraphicsApi.DIRECT3D
...@@ -63,9 +64,9 @@ internal object SkikoProperties { ...@@ -63,9 +64,9 @@ internal object SkikoProperties {
var renderApiList = mutableListOf<GraphicsApi>() var renderApiList = mutableListOf<GraphicsApi>()
when (hostOs) { when (hostOs) {
OS.Linux -> renderApiList = mutableListOf(GraphicsApi.OPENGL, GraphicsApi.SOFTWARE) OS.Linux -> renderApiList = mutableListOf(GraphicsApi.OPENGL, GraphicsApi.DIRECT_SOFTWARE, GraphicsApi.SOFTWARE)
OS.MacOS -> renderApiList = mutableListOf(GraphicsApi.METAL, GraphicsApi.SOFTWARE) OS.MacOS -> renderApiList = mutableListOf(GraphicsApi.METAL, GraphicsApi.SOFTWARE)
OS.Windows -> renderApiList = mutableListOf(GraphicsApi.DIRECT3D, GraphicsApi.OPENGL, GraphicsApi.SOFTWARE) OS.Windows -> renderApiList = mutableListOf(GraphicsApi.DIRECT3D, GraphicsApi.OPENGL, GraphicsApi.DIRECT_SOFTWARE, GraphicsApi.SOFTWARE)
} }
renderApiList.remove(head) renderApiList.remove(head)
......
package org.jetbrains.skiko.context
import org.jetbrains.skia.Surface
import org.jetbrains.skia.impl.Native
import org.jetbrains.skia.impl.getPtr
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.AbstractDirectSoftwareRedrawer
import java.lang.ref.Reference
internal class DirectSoftwareContextHandler(layer: SkiaLayer) : ContextHandler(layer) {
override val bleachConstant = -1
var isInited = false
val softwareRedrawer: AbstractDirectSoftwareRedrawer
get() = layer.redrawer!! as AbstractDirectSoftwareRedrawer
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 initContext(): Boolean {
if (!isInited) {
if (System.getProperty("skiko.hardwareInfo.enabled") == "true") {
println(rendererInfo())
}
isInited = true
}
return isInited
}
override fun initCanvas() {
val scale = layer.contentScale
val w = (layer.width * scale).toInt().coerceAtLeast(0)
val h = (layer.height * scale).toInt().coerceAtLeast(0)
if (isSizeChanged(w, h)) {
disposeCanvas()
softwareRedrawer.resize(w, h)
surface = softwareRedrawer.getSurface()
canvas = surface!!.canvas
}
}
override fun flush() {
softwareRedrawer.finishFrame()
}
}
\ No newline at end of file
package org.jetbrains.skiko.redrawer
import org.jetbrains.skia.Surface
import kotlinx.coroutines.*
import kotlinx.coroutines.swing.Swing
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.FrameLimiter
import org.jetbrains.skiko.hostOs
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerProperties
internal abstract class AbstractDirectSoftwareRedrawer(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
) : Redrawer {
private val frameJob = Job()
private val frameLimiter = FrameLimiter(CoroutineScope(Dispatchers.IO + frameJob), layer.backedLayer)
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
if (properties.isVsyncEnabled && properties.isVsyncFramelimitFallbackEnabled) {
frameLimiter.awaitNextFrame()
}
if (layer.isShowing) {
layer.update(System.nanoTime())
layer.inDrawScope(layer::draw)
}
}
protected var device = 0L
override fun needRedraw() {
frameDispatcher.scheduleFrame()
}
override fun redrawImmediately() {
layer.update(System.nanoTime())
layer.inDrawScope(layer::draw)
}
open fun resize(width: Int, height: Int) = resize(device, width, height)
fun getSurface() = Surface(getSurface(device))
open fun finishFrame() = finishFrame(device)
override fun dispose() {
disposeDevice(device)
frameDispatcher.cancel()
runBlocking {
frameJob.cancelAndJoin()
}
}
private external fun resize(devicePtr: Long, width: Int, height: Int)
private external fun getSurface(devicePtr: Long): Long
private external fun finishFrame(devicePtr: Long)
private external fun disposeDevice(devicePtr: Long)
}
\ No newline at end of file
...@@ -15,8 +15,11 @@ internal class LinuxOpenGLRedrawer( ...@@ -15,8 +15,11 @@ internal class LinuxOpenGLRedrawer(
init { init {
layer.backedLayer.lockLinuxDrawingSurface { layer.backedLayer.lockLinuxDrawingSurface {
context = it.createContext() context = it.createContext()
if (context == 0L) {
throw RenderException("Cannot create Linux GL context")
}
it.makeCurrent(context) it.makeCurrent(context)
if (context == 0L || !isVideoCardSupported(layer.renderApi)) { if (!isVideoCardSupported(layer.renderApi)) {
throw RenderException("Cannot create Linux GL context") throw RenderException("Cannot create Linux GL context")
} }
it.setSwapInterval(swapInterval) it.setSwapInterval(swapInterval)
......
package org.jetbrains.skiko.redrawer
import org.jetbrains.skia.Surface
import kotlinx.coroutines.*
import kotlinx.coroutines.swing.Swing
import org.jetbrains.skiko.*
internal class LinuxSoftwareRedrawer(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
) : AbstractDirectSoftwareRedrawer(layer, properties) {
init {
layer.backedLayer.lockLinuxDrawingSurface {
device = createDevice(it.display, it.window).also {
if (it == 0L) {
throw RenderException("Failed to create Software device")
}
}
}
}
override fun redrawImmediately() = layer.backedLayer.lockLinuxDrawingSurface {
super.redrawImmediately()
}
override fun resize(width: Int, height: Int) = layer.backedLayer.lockLinuxDrawingSurface {
super.resize(width, height)
}
override fun finishFrame() = layer.backedLayer.lockLinuxDrawingSurface {
super.finishFrame()
}
private external fun createDevice(display: Long, window: Long): Long
}
\ No newline at end of file
...@@ -12,8 +12,11 @@ internal class WindowsOpenGLRedrawer( ...@@ -12,8 +12,11 @@ internal class WindowsOpenGLRedrawer(
) : Redrawer { ) : Redrawer {
private val device = layer.backedLayer.useDrawingSurfacePlatformInfo(::getDevice) private val device = layer.backedLayer.useDrawingSurfacePlatformInfo(::getDevice)
private val context = createContext(device).also { private val context = createContext(device).also {
if (it == 0L) {
throw RenderException("Cannot create Windows GL context")
}
makeCurrent(device, it) makeCurrent(device, it)
if (it == 0L || !isVideoCardSupported(layer.renderApi)) { if (!isVideoCardSupported(layer.renderApi)) {
throw RenderException("Cannot create Windows GL context") throw RenderException("Cannot create Windows GL context")
} }
} }
......
package org.jetbrains.skiko.redrawer
import org.jetbrains.skia.Surface
import kotlinx.coroutines.*
import kotlinx.coroutines.swing.Swing
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.FrameLimiter
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerProperties
import org.jetbrains.skiko.RenderException
internal class WindowsSoftwareRedrawer(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
) : AbstractDirectSoftwareRedrawer(layer, properties) {
init {
device = createDevice(layer.contentHandle).also {
if (it == 0L) {
throw RenderException("Failed to create Software device")
}
}
}
private external fun createDevice(contentHandle: Long): Long
}
\ 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